diff --git a/.gitattributes b/.gitattributes
index 1b136160c..12ddc67b8 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -3,6 +3,20 @@
###############################################################################
* text=auto
+###############################################################################
+# C# sources check out CRLF on every platform.
+#
+# .editorconfig pins end_of_line = crlf for [*.cs] and [*.{cs,vb}], and
+# EnforceCodeStyleInBuild makes IDE0055 a build error in src. Under `text=auto`
+# alone the working tree takes the platform's ending, so a Linux or macOS
+# checkout gets LF and every line then violates that rule while Windows builds
+# clean. Pinning the checkout here keeps the one setting that decides this in
+# agreement with the one that enforces it. The index is unaffected — it stores
+# LF either way.
+###############################################################################
+*.cs text eol=crlf
+*.vb text eol=crlf
+
###############################################################################
# Set default behavior for command prompt diff.
#
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 0c47e8920..5f70f03b4 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -103,6 +103,7 @@ jobs:
- 'src/LibRed/**'
- 'test/LibRed.Ado.Tests/**'
- 'test/LibRed.Core.Tests/**'
+ - 'test/LibRed.Core.AccessTests/**'
- 'test/LibRed.EFCore.Tests/**'
- 'test/LibRed.Engine.Tests/**'
- 'test/LibRed.Engine.AccessTests/**'
@@ -676,6 +677,13 @@ jobs:
- name: 'Run Tests: LibRed.Engine.Tests'
if: env.skipTests != 'true'
run: dotnet test ./test/LibRed.Engine.Tests --configuration ${{ env.buildConfiguration }} -p:FixedTestOrder=${{ env.deterministicTests }} --blame-hang-timeout 3m
+ # The file-format half of LibRed.Core.Tests. It reads and writes .accdb files with LibRed alone — the
+ # cross-checks against the real Access engine live in LibRed.Core.AccessTests — so it belongs on this
+ # matrix rather than the Windows one, and reading the on-disk format on Linux/macOS/ARM64 is the part
+ # of the cross-platform claim that was previously only asserted for the engine.
+ - name: 'Run Tests: LibRed.Core.Tests'
+ if: always() && env.skipTests != 'true'
+ run: dotnet test ./test/LibRed.Core.Tests --configuration ${{ env.buildConfiguration }} -p:FixedTestOrder=${{ env.deterministicTests }} --blame-hang-timeout 3m
# The rest of the LibRed suites cross-check LibRed's output against the real engine over OLE DB,
# so unlike the job above they do need Windows with ACE installed.
@@ -718,10 +726,12 @@ jobs:
}
$v
}
- - name: 'Run Tests: LibRed.Core.Tests'
+ # The file-format tests that cross-check against ACE. Their ACE-free half runs on the five-platform
+ # LibRed job above.
+ - name: 'Run Tests: LibRed.Core.AccessTests'
if: env.skipTests != 'true'
shell: pwsh
- run: dotnet test .\test\LibRed.Core.Tests --configuration '${{ env.buildConfiguration }}' -p:FixedTestOrder=${{ env.deterministicTests }} --blame-hang-timeout 5m
+ run: dotnet test .\test\LibRed.Core.AccessTests --configuration '${{ env.buildConfiguration }}' -p:FixedTestOrder=${{ env.deterministicTests }} --blame-hang-timeout 5m
# The engine tests that cross-check against ACE. They belong here rather than in the cross-platform
# LibRed job above, which runs on five platforms precisely to prove LibRed needs no ACE at all.
- name: 'Run Tests: LibRed.Engine.AccessTests'
diff --git a/AGENTS.md b/AGENTS.md
index e3d529006..c78072b44 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -32,6 +32,14 @@ dotnet build EFCore.Jet.sln
Assemblies are **strong-name signed** using `Key.snk`. `TreatWarningsAsErrors=True` is set globally — fix all warnings.
+`src/Directory.Build.props` also turns on `AnalysisMode=Recommended` and `EnforceCodeStyleInBuild`, so **in `src`
+every CA/IDE diagnostic is a build error** (the test projects import the repo-root props and stay on the SDK
+default). Fix them rather than suppress them; where a rule's own fix would change behaviour, a `#pragma` carries a
+written reason. Two traps: `dotnet format whitespace` handles IDE0055 and `dotnet format style` the other IDE rules,
+but **never run either against a single project when the change can touch `src/Shared`** — IDE0005 is evaluated per
+project, and formatting one will delete a using the other four consumers still need. The implicit usings those
+shared files rely on are declared once in `src/Shared/SharedSource.props`.
+
### Local EFCore Repository (optional)
To develop against a local EF Core build instead of NuGet packages, copy `Development.props.sample` to `Development.props` and set `LocalEFCoreRepository` to your EF Core checkout. That local build must be compiled with `AssemblyVersion=11.0.0.0` to avoid binding conflicts.
@@ -267,9 +275,11 @@ The Jet provider (`src/EFCore.Jet*`) has **no** equivalent — it does no ACE ve
`JetTypeMappingSource` keeps `{"bigint", …}` commented out, so `long` maps to `decimal(20,0)` regardless of the
installed engine. That asymmetry is deliberate for now; don't "fix" one side by assuming the other behaves the same.
-**Build configuration:** `src/LibRed/Directory.Build.props` bypasses `src/Directory.Build.props` (it imports the
-repo-root props directly) to set its own build options — not packable, no documentation file, its own `NoWarn`,
-`ImplicitUsings`/`Nullable` on. The Windows stamp is no longer a reason: each driver-bound project applies
+**Build configuration:** `src/LibRed/Directory.Build.props` **imports** `src/Directory.Build.props` and adds to it:
+`ImplicitUsings`/`Nullable` on, `GenerateDocumentationFile` on (IDE0005 cannot run without it), its own `NoWarn`,
+and a LibRed-specific package readme in place of the Jet one the repo root packs. Because it imports rather than
+bypasses, the LibRed projects inherit the same `AnalysisMode`/`EnforceCodeStyleInBuild` as the rest of `src`. The
+Windows stamp is no longer a reason for any of it: each driver-bound project applies
`[SupportedOSPlatform("windows")]` itself. Strong-naming is preserved.
**SQL pipeline** (always run end-to-end, even for trivial queries, so new features add
diff --git a/CLAUDE.md b/CLAUDE.md
index bebf1608d..e7c6234aa 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -31,6 +31,14 @@ dotnet build EFCore.Jet.sln
Assemblies are **strong-name signed** using `Key.snk`. `TreatWarningsAsErrors=True` is set globally — fix all warnings.
+`src/Directory.Build.props` also turns on `AnalysisMode=Recommended` and `EnforceCodeStyleInBuild`, so **in `src`
+every CA/IDE diagnostic is a build error** (the test projects import the repo-root props and stay on the SDK
+default). Fix them rather than suppress them; where a rule's own fix would change behaviour, a `#pragma` carries a
+written reason. Two traps: `dotnet format whitespace` handles IDE0055 and `dotnet format style` the other IDE rules,
+but **never run either against a single project when the change can touch `src/Shared`** — IDE0005 is evaluated per
+project, and formatting one will delete a using the other four consumers still need. The implicit usings those
+shared files rely on are declared once in `src/Shared/SharedSource.props`.
+
### Local EFCore Repository (optional)
To develop against a local EF Core build instead of NuGet packages, copy `Development.props.sample` to `Development.props` and set `LocalEFCoreRepository` to your EF Core checkout. That local build must be compiled with `AssemblyVersion=11.0.0.0` to avoid binding conflicts.
@@ -282,9 +290,11 @@ The Jet provider (`src/EFCore.Jet*`) has **no** equivalent — it does no ACE ve
`JetTypeMappingSource` keeps `{"bigint", …}` commented out, so `long` maps to `decimal(20,0)` regardless of the
installed engine. That asymmetry is deliberate for now; don't "fix" one side by assuming the other behaves the same.
-**Build configuration:** `src/LibRed/Directory.Build.props` bypasses `src/Directory.Build.props` (it imports the
-repo-root props directly) to set its own build options — not packable, no documentation file, its own `NoWarn`,
-`ImplicitUsings`/`Nullable` on. The Windows stamp is no longer a reason: each driver-bound project applies
+**Build configuration:** `src/LibRed/Directory.Build.props` **imports** `src/Directory.Build.props` and adds to it:
+`ImplicitUsings`/`Nullable` on, `GenerateDocumentationFile` on (IDE0005 cannot run without it), its own `NoWarn`,
+and a LibRed-specific package readme in place of the Jet one the repo root packs. Because it imports rather than
+bypasses, the LibRed projects inherit the same `AnalysisMode`/`EnforceCodeStyleInBuild` as the rest of `src`. The
+Windows stamp is no longer a reason for any of it: each driver-bound project applies
`[SupportedOSPlatform("windows")]` itself. Strong-naming is preserved.
**SQL pipeline** (always run end-to-end, even for trivial queries, so new features add
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index c16703271..0277ac6ea 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -8,6 +8,10 @@
$(NoWarn);CS1574;CS1587;CS1591;EF1001;NU1903true
+
+ Recommended
+ true
diff --git a/src/EFCore.Jet.Common/Design/Internal/JetAnnotationCodeGenerator.cs b/src/EFCore.Jet.Common/Design/Internal/JetAnnotationCodeGenerator.cs
index dd04a0270..92835966b 100644
--- a/src/EFCore.Jet.Common/Design/Internal/JetAnnotationCodeGenerator.cs
+++ b/src/EFCore.Jet.Common/Design/Internal/JetAnnotationCodeGenerator.cs
@@ -35,7 +35,7 @@ protected override bool IsHandledByConvention(IModel model, IAnnotation annotati
// CHECK: Jet default schema handling.
if (annotation.Name == RelationalAnnotationNames.DefaultSchema)
{
- return string.Equals(null, (string?)annotation.Value);
+ return string.Equals(null, (string?)annotation.Value, StringComparison.Ordinal);
}
return annotation.Name == JetAnnotationNames.ValueGenerationStrategy
diff --git a/src/EFCore.Jet.Common/Design/Internal/JetCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Jet.Common/Design/Internal/JetCSharpRuntimeAnnotationCodeGenerator.cs
index becfb83c7..9906e7100 100644
--- a/src/EFCore.Jet.Common/Design/Internal/JetCSharpRuntimeAnnotationCodeGenerator.cs
+++ b/src/EFCore.Jet.Common/Design/Internal/JetCSharpRuntimeAnnotationCodeGenerator.cs
@@ -152,4 +152,4 @@ public override void Generate(IRelationalPropertyOverrides overrides, CSharpRunt
base.Generate(overrides, parameters);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Diagnostics/ConflictingValueGenerationStrategiesEventData.cs b/src/EFCore.Jet.Common/Diagnostics/ConflictingValueGenerationStrategiesEventData.cs
index 7ccfb1f0f..36eae609a 100644
--- a/src/EFCore.Jet.Common/Diagnostics/ConflictingValueGenerationStrategiesEventData.cs
+++ b/src/EFCore.Jet.Common/Diagnostics/ConflictingValueGenerationStrategiesEventData.cs
@@ -38,6 +38,10 @@ public class ConflictingValueGenerationStrategiesEventData(
///
/// The property.
///
+ // CA1716 objects to "Property" as a VB keyword; it is the name EF Core's own PropertyEventData uses,
+ // and this type is consumed through that same diagnostics surface.
+#pragma warning disable CA1716
public virtual IProperty Property { get; } = property;
+#pragma warning restore CA1716
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Diagnostics/Internal/JetLoggingDefinitions.cs b/src/EFCore.Jet.Common/Diagnostics/Internal/JetLoggingDefinitions.cs
index 7a398c820..2f6bfe325 100644
--- a/src/EFCore.Jet.Common/Diagnostics/Internal/JetLoggingDefinitions.cs
+++ b/src/EFCore.Jet.Common/Diagnostics/Internal/JetLoggingDefinitions.cs
@@ -8,6 +8,9 @@ namespace EntityFrameworkCore.Jet.Diagnostics.Internal
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
+ // CA1051 wants properties, but a LoggingDefinitions class is a cache of lazily-built event definitions that
+ // the logger extensions assign into by field; EF Core's own SqlServerLoggingDefinitions is written the same way.
+#pragma warning disable CA1051
public class JetLoggingDefinitions : RelationalLoggingDefinitions
{
///
@@ -162,4 +165,5 @@ public class JetLoggingDefinitions : RelationalLoggingDefinitions
///
public EventDefinitionBase? LogConflictingValueGenerationStrategies;
}
+#pragma warning restore CA1051
}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/EFCore.Jet.Common.csproj b/src/EFCore.Jet.Common/EFCore.Jet.Common.csproj
index 0d4502bdb..1721699f7 100644
--- a/src/EFCore.Jet.Common/EFCore.Jet.Common.csproj
+++ b/src/EFCore.Jet.Common/EFCore.Jet.Common.csproj
@@ -16,9 +16,7 @@
-
-
-
+
@@ -37,9 +35,6 @@
-
-
-
diff --git a/src/EFCore.Jet.Common/Extensions/JetDbFunctionsExtensions.cs b/src/EFCore.Jet.Common/Extensions/JetDbFunctionsExtensions.cs
index 575bd40bb..4f4fd3c34 100644
--- a/src/EFCore.Jet.Common/Extensions/JetDbFunctionsExtensions.cs
+++ b/src/EFCore.Jet.Common/Extensions/JetDbFunctionsExtensions.cs
@@ -778,4 +778,4 @@ public static int ByteArrayLength(
#endregion Population variance
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Extensions/JetModelExtensions.cs b/src/EFCore.Jet.Common/Extensions/JetModelExtensions.cs
index e59876505..012c74d76 100644
--- a/src/EFCore.Jet.Common/Extensions/JetModelExtensions.cs
+++ b/src/EFCore.Jet.Common/Extensions/JetModelExtensions.cs
@@ -74,7 +74,7 @@ public static void SetJetIdentityIncrement(this IMutableModel model, int? increm
/// Indicates whether the configuration was specified using a data annotation.
public static int? SetJetIdentityIncrement(
this IConventionModel model, int? increment, bool fromDataAnnotation = false)
- => (int?) model.SetOrRemoveAnnotation(
+ => (int?)model.SetOrRemoveAnnotation(
JetAnnotationNames.IdentityIncrement,
increment,
fromDataAnnotation)?.Value;
diff --git a/src/EFCore.Jet.Common/Infrastructure/JetDualTable.cs b/src/EFCore.Jet.Common/Infrastructure/JetDualTable.cs
index f0bb832ff..3a6d4832c 100644
--- a/src/EFCore.Jet.Common/Infrastructure/JetDualTable.cs
+++ b/src/EFCore.Jet.Common/Infrastructure/JetDualTable.cs
@@ -32,13 +32,16 @@ public static class JetDualTable
///
/// The DUAL table or query
///
- public static string CustomName = "";
+ public static string CustomName { get; set; } = "";
//MSysRelationships
//MSysAccessStorage
//#Dual
//(SELECT COUNT(*) FROM MSysAccessStorage)
- public static string DetectedName = "#Dual";
+ ///
+ /// The DUAL table or query the scaffolding factory last detected in the database.
+ ///
+ public static string DetectedName { get; set; } = "#Dual";
///
/// The name to generate into SQL: the user's when one has been set,
@@ -49,4 +52,4 @@ public static string Name
? DetectedName
: CustomName;
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Internal/JetLoggerExtensions.cs b/src/EFCore.Jet.Common/Internal/JetLoggerExtensions.cs
index 5bf42e8f7..fd0c1348f 100644
--- a/src/EFCore.Jet.Common/Internal/JetLoggerExtensions.cs
+++ b/src/EFCore.Jet.Common/Internal/JetLoggerExtensions.cs
@@ -1,5 +1,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+// Every method here follows EF Core's own logger-extension shape: the message template comes from an
+// EventDefinition rather than being a literal, and the call is already guarded by ShouldLog. That is what
+// CA2254 (varying template), CA1848 (LoggerMessage delegates) and CA1873 (params array) each object to, and
+// EF Core's own generated extensions are written the same way.
+#pragma warning disable CA1848, CA1873, CA2254
+
namespace EntityFrameworkCore.Jet.Internal
{
///
@@ -486,4 +492,4 @@ public static void ReflexiveConstraintIgnored(
// No DiagnosticsSource events because these are purely design-time messages
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Internal/JetModelValidator.cs b/src/EFCore.Jet.Common/Internal/JetModelValidator.cs
index 5a86fa139..a33dc21c5 100644
--- a/src/EFCore.Jet.Common/Internal/JetModelValidator.cs
+++ b/src/EFCore.Jet.Common/Internal/JetModelValidator.cs
@@ -1,8 +1,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System.Text;
using EntityFrameworkCore.Jet.Metadata;
using EntityFrameworkCore.Jet.Metadata.Internal;
+using System.Text;
namespace EntityFrameworkCore.Jet.Internal
{
diff --git a/src/EFCore.Jet.Common/Metadata/Conventions/JetIndexConvention.cs b/src/EFCore.Jet.Common/Metadata/Conventions/JetIndexConvention.cs
index af6a2731a..ee8ba626a 100644
--- a/src/EFCore.Jet.Common/Metadata/Conventions/JetIndexConvention.cs
+++ b/src/EFCore.Jet.Common/Metadata/Conventions/JetIndexConvention.cs
@@ -33,7 +33,11 @@ public class JetIndexConvention(
IPropertyNullabilityChangedConvention,
IPropertyAnnotationChangedConvention
{
+ // Unread: Jet's index filter is the fixed "IGNORE NULL", so nothing here quotes an identifier. The
+ // constructor parameter stays because it is the signature EF Core's convention-set builder constructs.
+#pragma warning disable IDE0052
private readonly ISqlGenerationHelper _sqlGenerationHelper = sqlGenerationHelper;
+#pragma warning restore IDE0052
///
/// Dependencies for this service.
@@ -144,7 +148,7 @@ public virtual void ProcessPropertyAnnotationChanged(
}
}
- private void SetIndexFilter(IConventionIndexBuilder indexBuilder, bool columnNameChanged = false)
+ private static void SetIndexFilter(IConventionIndexBuilder indexBuilder, bool columnNameChanged = false)
{
var index = indexBuilder.Metadata;
if (index.IsUnique
@@ -166,11 +170,11 @@ private void SetIndexFilter(IConventionIndexBuilder indexBuilder, bool columnNam
}
}
- private string CreateIndexFilter(List nullableColumns)
+ private static string CreateIndexFilter(List nullableColumns)
{
var builder = new StringBuilder();
- if (nullableColumns.Any())
+ if (nullableColumns.Count > 0)
{
builder.Append("IGNORE NULL");
}
@@ -214,4 +218,4 @@ private string CreateIndexFilter(List nullableColumns)
return nullableColumns;
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Metadata/Conventions/JetOnDeleteConvention.cs b/src/EFCore.Jet.Common/Metadata/Conventions/JetOnDeleteConvention.cs
index 5ccef3fdc..8073c465d 100644
--- a/src/EFCore.Jet.Common/Metadata/Conventions/JetOnDeleteConvention.cs
+++ b/src/EFCore.Jet.Common/Metadata/Conventions/JetOnDeleteConvention.cs
@@ -43,7 +43,7 @@ protected override DeleteBehavior GetTargetDeleteBehavior(IConventionForeignKey
return ProcessSkipNavigations(foreignKey.GetReferencingSkipNavigations()) ?? deleteBehavior;
}
- private DeleteBehavior? ProcessSkipNavigations(IEnumerable skipNavigations)
+ private static DeleteBehavior? ProcessSkipNavigations(IEnumerable skipNavigations)
{
var skipNavigation = skipNavigations
.FirstOrDefault(
@@ -121,4 +121,4 @@ public virtual void ProcessEntityTypeAnnotationChanged(
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Metadata/Conventions/JetSharedTableConvention.cs b/src/EFCore.Jet.Common/Metadata/Conventions/JetSharedTableConvention.cs
index eddf17e18..c45560265 100644
--- a/src/EFCore.Jet.Common/Metadata/Conventions/JetSharedTableConvention.cs
+++ b/src/EFCore.Jet.Common/Metadata/Conventions/JetSharedTableConvention.cs
@@ -22,4 +22,4 @@ public class JetSharedTableConvention(
///
protected override bool IndexesUniqueAcrossTables
=> false;
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Metadata/Conventions/JetValueGenerationStrategyConvention.cs b/src/EFCore.Jet.Common/Metadata/Conventions/JetValueGenerationStrategyConvention.cs
index 228ffdacb..25e5076b7 100644
--- a/src/EFCore.Jet.Common/Metadata/Conventions/JetValueGenerationStrategyConvention.cs
+++ b/src/EFCore.Jet.Common/Metadata/Conventions/JetValueGenerationStrategyConvention.cs
@@ -85,7 +85,7 @@ public virtual void ProcessModelFinalizing(
static bool IsStrategyNoneNeeded(IReadOnlyProperty property, StoreObjectIdentifier storeObject)
{
if (property.ValueGenerated == ValueGenerated.OnAdd
- && property.TryGetDefaultValue(storeObject, out _) == false
+ && !property.TryGetDefaultValue(storeObject, out _)
&& property.GetDefaultValueSql(storeObject) == null
&& property.GetComputedColumnSql(storeObject) == null
&& property.DeclaringType.Model.GetValueGenerationStrategy() == JetValueGenerationStrategy.IdentityColumn)
diff --git a/src/EFCore.Jet.Common/Metadata/Conventions/JetValueGeneratorConvention.cs b/src/EFCore.Jet.Common/Metadata/Conventions/JetValueGeneratorConvention.cs
index 6c3556d34..6c03f088f 100644
--- a/src/EFCore.Jet.Common/Metadata/Conventions/JetValueGeneratorConvention.cs
+++ b/src/EFCore.Jet.Common/Metadata/Conventions/JetValueGeneratorConvention.cs
@@ -84,7 +84,7 @@ public override void ProcessPropertyAnnotationChanged(
/// The property.
/// The identifier of the store object.
/// The store value generation strategy to set for the given property.
- public new static ValueGenerated? GetValueGenerated(IReadOnlyProperty property, in StoreObjectIdentifier storeObject)
+ public static new ValueGenerated? GetValueGenerated(IReadOnlyProperty property, in StoreObjectIdentifier storeObject)
=> RelationalValueGenerationConvention.GetValueGenerated(property, storeObject)
?? (property.GetValueGenerationStrategy(storeObject) != JetValueGenerationStrategy.None
? ValueGenerated.OnAdd
diff --git a/src/EFCore.Jet.Common/Metadata/Internal/JetAnnotationProvider.cs b/src/EFCore.Jet.Common/Metadata/Internal/JetAnnotationProvider.cs
index c8f00dcec..b22cc57ff 100644
--- a/src/EFCore.Jet.Common/Metadata/Internal/JetAnnotationProvider.cs
+++ b/src/EFCore.Jet.Common/Metadata/Internal/JetAnnotationProvider.cs
@@ -1,5 +1,5 @@
-using System.Globalization;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using System.Globalization;
namespace EntityFrameworkCore.Jet.Metadata.Internal
{
@@ -96,7 +96,7 @@ public override IEnumerable For(IColumn column, bool designTime)
else
{
if (column is JsonColumn) yield break;
- property = column.PropertyMappings.First().Property;
+ property = column.PropertyMappings[0].Property;
if (property.DeclaringType is IEntityType entityType)
{
// Only return auto increment for integer single column primary key
diff --git a/src/EFCore.Jet.Common/Metadata/JetValueGenerationStrategy.cs b/src/EFCore.Jet.Common/Metadata/JetValueGenerationStrategy.cs
index 7aa635537..b46bd80c3 100644
--- a/src/EFCore.Jet.Common/Metadata/JetValueGenerationStrategy.cs
+++ b/src/EFCore.Jet.Common/Metadata/JetValueGenerationStrategy.cs
@@ -8,4 +8,4 @@ public enum JetValueGenerationStrategy
IdentityColumn,
Sequence
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Migrations/Internal/JetMigrationDatabaseLock.cs b/src/EFCore.Jet.Common/Migrations/Internal/JetMigrationDatabaseLock.cs
index 18b260db0..195a60f24 100644
--- a/src/EFCore.Jet.Common/Migrations/Internal/JetMigrationDatabaseLock.cs
+++ b/src/EFCore.Jet.Common/Migrations/Internal/JetMigrationDatabaseLock.cs
@@ -40,6 +40,8 @@ public void Dispose()
{
if (!e.Message.Contains("cannot find the input table")) throw;
}
+
+ GC.SuppressFinalize(this);
}
///
@@ -58,5 +60,7 @@ public async ValueTask DisposeAsync()
{
if (!e.Message.Contains("cannot find the input table")) throw;
}
+
+ GC.SuppressFinalize(this);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Migrations/JetMigrationsSqlGenerator.cs b/src/EFCore.Jet.Common/Migrations/JetMigrationsSqlGenerator.cs
index a52a8ede8..c4347e9ed 100644
--- a/src/EFCore.Jet.Common/Migrations/JetMigrationsSqlGenerator.cs
+++ b/src/EFCore.Jet.Common/Migrations/JetMigrationsSqlGenerator.cs
@@ -4,10 +4,8 @@
using EntityFrameworkCore.Jet.Metadata;
using EntityFrameworkCore.Jet.Metadata.Internal;
using EntityFrameworkCore.Jet.Migrations.Operations;
-using EntityFrameworkCore.Jet.Storage.Internal;
using EntityFrameworkCore.Jet.Update.Internal;
using EntityFrameworkCore.Jet.Utilities;
-using Microsoft.EntityFrameworkCore.Metadata.Internal;
using System.Text;
// ReSharper disable once CheckNamespace
@@ -830,7 +828,7 @@ protected override void ColumnDefinition(
var columnType = GetColumnType(schema, table, name, operation, model);
//int has no size - ignore
- if (columnType != null && columnType.StartsWith("int("))
+ if (columnType != null && columnType.StartsWith("int(", StringComparison.Ordinal))
{
columnType = "int";
}
@@ -846,7 +844,7 @@ protected override void ColumnDefinition(
protected override string GetColumnType(
string? schema,
- string table,
+ string tableName,
string name,
ColumnOperation operation,
IModel? model)
@@ -867,7 +865,7 @@ protected override string GetColumnType(
storeType = "integer";
}
- storeType ??= base.GetColumnType(schema, table, name, operation, model);
+ storeType ??= base.GetColumnType(schema, tableName, name, operation, model);
if (string.Equals(storeType, "counter", StringComparison.OrdinalIgnoreCase) &&
operation[JetAnnotationNames.Identity] is string identity &&
@@ -887,7 +885,7 @@ operation[JetAnnotationNames.Identity] is string identity &&
return storeType;
}
- var fullTableName = schema != null ? $"{schema}.{table}" : table;
+ var fullTableName = schema != null ? $"{schema}.{tableName}" : tableName;
throw new InvalidOperationException(
RelationalStrings.UnsupportedTypeForColumn(fullTableName, name, operation.ClrType?.Name ?? "unknown"));
}
diff --git a/src/EFCore.Jet.Common/Migrations/Operations/JetCreateDatabaseOperation.cs b/src/EFCore.Jet.Common/Migrations/Operations/JetCreateDatabaseOperation.cs
index f573489b2..95e748ac4 100644
--- a/src/EFCore.Jet.Common/Migrations/Operations/JetCreateDatabaseOperation.cs
+++ b/src/EFCore.Jet.Common/Migrations/Operations/JetCreateDatabaseOperation.cs
@@ -13,4 +13,4 @@ public class JetCreateDatabaseOperation : MigrationOperation
public virtual string Name { get; set; } = null!;
public virtual string? Password { get; set; }
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Migrations/Operations/JetDropDatabaseOperation.cs b/src/EFCore.Jet.Common/Migrations/Operations/JetDropDatabaseOperation.cs
index 6a73407aa..63fe2e5c2 100644
--- a/src/EFCore.Jet.Common/Migrations/Operations/JetDropDatabaseOperation.cs
+++ b/src/EFCore.Jet.Common/Migrations/Operations/JetDropDatabaseOperation.cs
@@ -12,4 +12,4 @@ public class JetDropDatabaseOperation : MigrationOperation
///
public virtual string Name { get; set; } = null!;
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetAggregateMethodCallTranslatorProvider.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetAggregateMethodCallTranslatorProvider.cs
index 52ed0b3c4..7321abd00 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetAggregateMethodCallTranslatorProvider.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetAggregateMethodCallTranslatorProvider.cs
@@ -25,4 +25,4 @@ public JetAggregateMethodCallTranslatorProvider(RelationalAggregateMethodCallTra
new JetStatisticsAggregateMethodTranslator(sqlExpressionFactory, typeMappingSource)
]);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetByteArrayMethodTranslator.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetByteArrayMethodTranslator.cs
index aa43e466c..f748afb1c 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetByteArrayMethodTranslator.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetByteArrayMethodTranslator.cs
@@ -76,23 +76,23 @@ public class JetByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFac
switch (method.Name)
{
case nameof(Enumerable.Contains) when arguments is [var source, var item] && source.Type == typeof(byte[]):
- {
- var sourceTypeMapping = source.TypeMapping;
-
- var value = item is SqlConstantExpression constantValue
- ? _sqlExpressionFactory.Constant(new[] { (byte)constantValue.Value! }, sourceTypeMapping)
- : _sqlExpressionFactory.Function(
- "CHR",
- [item],
- nullable: true,
- argumentsPropagateNullability: [true],
- typeof(string));
+ {
+ var sourceTypeMapping = source.TypeMapping;
+
+ var value = item is SqlConstantExpression constantValue
+ ? _sqlExpressionFactory.Constant(new[] { (byte)constantValue.Value! }, sourceTypeMapping)
+ : _sqlExpressionFactory.Function(
+ "CHR",
+ [item],
+ nullable: true,
+ argumentsPropagateNullability: [true],
+ typeof(string));
- return _sqlExpressionFactory.GreaterThan(
- _sqlExpressionFactory.Function(
- "INSTR",
- [
- _sqlExpressionFactory.Constant(1),
+ return _sqlExpressionFactory.GreaterThan(
+ _sqlExpressionFactory.Function(
+ "INSTR",
+ [
+ _sqlExpressionFactory.Constant(1),
_sqlExpressionFactory.Function(
"STRCONV",
[source, _sqlExpressionFactory.Constant(64)],
@@ -101,12 +101,12 @@ public class JetByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFac
typeof(string)),
value,
_sqlExpressionFactory.Constant(0)
- ],
- nullable: true,
- argumentsPropagateNullability: [false, true, true, false],
- typeof(int)),
- _sqlExpressionFactory.Constant(0));
- }
+ ],
+ nullable: true,
+ argumentsPropagateNullability: [false, true, true, false],
+ typeof(int)),
+ _sqlExpressionFactory.Constant(0));
+ }
// First without a predicate
case nameof(Enumerable.First) when arguments is [var source] && source.Type == typeof(byte[]):
@@ -146,4 +146,4 @@ public class JetByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFac
private static string? GetProviderType(SqlExpression expression)
=> expression.TypeMapping?.StoreType;
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateDiffFunctionsTranslator.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateDiffFunctionsTranslator.cs
index 19adbea15..d15284a7f 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateDiffFunctionsTranslator.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateDiffFunctionsTranslator.cs
@@ -207,11 +207,11 @@ private readonly Dictionary _methodInfoDateDiffMapping
"DATEDIFF",
[sqlExpressionFactory.Constant(datePart), startDate, endDate],
false,
- [false, false, false],
+ [false, false, false],
typeof(int));
}
return null;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateOnlyMemberTranslator.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateOnlyMemberTranslator.cs
index ce3a84cd0..8d275a6af 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateOnlyMemberTranslator.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateOnlyMemberTranslator.cs
@@ -52,7 +52,7 @@ public class JetDateOnlyMemberTranslator(ISqlExpressionFactory sqlExpressionFact
],
nullable: true,
argumentsPropagateNullability: [false, true, true],
- returnType),sqlExpressionFactory.Constant(new DateOnly(100,1,1).DayNumber)),
+ returnType), sqlExpressionFactory.Constant(new DateOnly(100, 1, 1).DayNumber)),
_ => null
};
@@ -65,4 +65,4 @@ SqlExpression DatePart(string datePart)
argumentsPropagateNullability: [false, true],
returnType);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateOnlyMethodTranslator.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateOnlyMethodTranslator.cs
index a5755c9d5..2c9ed22f6 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateOnlyMethodTranslator.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateOnlyMethodTranslator.cs
@@ -98,7 +98,7 @@ private static readonly MethodInfo ToDateTimeMethodInfo
instance.TypeMapping);
}
}
-
+
if (method.DeclaringType == typeof(DateOnly)
&& method.Name == nameof(DateOnly.FromDateTime)
@@ -156,4 +156,4 @@ private SqlExpression MapDatePartExpression(string datepart, SqlExpression argum
argumentsPropagateNullability: [true, true],
typeof(int));
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateTimeMemberTranslator.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateTimeMemberTranslator.cs
index 0eb7a2a55..7a8296290 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateTimeMemberTranslator.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateTimeMemberTranslator.cs
@@ -28,7 +28,11 @@ private static readonly Dictionary DatePartMapping
};
private readonly JetSqlExpressionFactory _sqlExpressionFactory = (JetSqlExpressionFactory)sqlExpressionFactory;
+ // Unread: every member this translator handles maps to a DatePart call whose type the factory settles.
+ // The dependency stays because it is the signature the member-translator provider constructs this with.
+#pragma warning disable IDE0052
private readonly IRelationalTypeMappingSource _typeMappingSource = typeMappingSource;
+#pragma warning restore IDE0052
///
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateTimeMethodTranslator.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateTimeMethodTranslator.cs
index 4ae71188a..8760b62bc 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateTimeMethodTranslator.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetDateTimeMethodTranslator.cs
@@ -1,5 +1,3 @@
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
namespace EntityFrameworkCore.Jet.Query.ExpressionTranslators.Internal
@@ -42,8 +40,8 @@ public class JetDateTimeMethodTranslator(ISqlExpressionFactory sqlExpressionFact
{
var amountToAdd = arguments[0];
- if (!datePart.Equals("yyyy")
- && !datePart.Equals("m")
+ if (!datePart.Equals("yyyy", StringComparison.Ordinal)
+ && !datePart.Equals("m", StringComparison.Ordinal)
&& amountToAdd is SqlConstantExpression constantExpression
&& constantExpression.Value is double doubleValue
&& (doubleValue >= int.MaxValue
@@ -97,7 +95,7 @@ public class JetDateTimeMethodTranslator(ISqlExpressionFactory sqlExpressionFact
return null;
}
- private List ExtractColumnExpressions(SqlBinaryExpression binaryexp)
+ private static List ExtractColumnExpressions(SqlBinaryExpression binaryexp)
{
List result = [];
if (binaryexp.Left is SqlBinaryExpression left)
@@ -120,7 +118,7 @@ private List ExtractColumnExpressions(SqlBinaryExpression bina
return result;
}
- private List ExtractColumnExpressions(SqlUnaryExpression unaryexp)
+ private static List ExtractColumnExpressions(SqlUnaryExpression unaryexp)
{
List result = [];
if (unaryexp.Operand is SqlBinaryExpression left)
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetIsDateFunctionTranslator.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetIsDateFunctionTranslator.cs
index 1f7b6eb27..774fb1bf0 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetIsDateFunctionTranslator.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetIsDateFunctionTranslator.cs
@@ -23,4 +23,4 @@ public class JetIsDateFunctionTranslator(ISqlExpressionFactory sqlExpressionFact
: null;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetMemberTranslatorProvider.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetMemberTranslatorProvider.cs
index a2b2d40e3..e042e93d9 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetMemberTranslatorProvider.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetMemberTranslatorProvider.cs
@@ -27,4 +27,4 @@ public JetMemberTranslatorProvider(RelationalMemberTranslatorProviderDependencie
]);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetParseTranslator.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetParseTranslator.cs
index 41d0ae43c..bdbb37584 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetParseTranslator.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetParseTranslator.cs
@@ -45,4 +45,4 @@ private static readonly MethodInfo[] SupportedMethods
arguments[0],
method.ReturnType)
: null;
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetStatisticsAggregateMethodTranslator.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetStatisticsAggregateMethodTranslator.cs
index cda17556e..f04ee67bb 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetStatisticsAggregateMethodTranslator.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetStatisticsAggregateMethodTranslator.cs
@@ -57,4 +57,4 @@ public class JetStatisticsAggregateMethodTranslator(
return _sqlExpressionFactory.Function(functionName, [sqlExpression], nullable: true, argumentsPropagateNullability: [false], typeof(double), _doubleTypeMapping);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetStringMethodTranslator.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetStringMethodTranslator.cs
index 883500daa..d30da7a45 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetStringMethodTranslator.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetStringMethodTranslator.cs
@@ -350,4 +350,4 @@ [new CaseWhenClause(_sqlExpressionFactory.IsNotNull(instance), _sqlExpressionFac
return _sqlExpressionFactory.Subtract(charIndexExpression, offsetExpression);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetTimeOnlyMemberTranslator.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetTimeOnlyMemberTranslator.cs
index e10c49642..fbf580f0f 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetTimeOnlyMemberTranslator.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetTimeOnlyMemberTranslator.cs
@@ -52,4 +52,4 @@ public class JetTimeOnlyMemberTranslator(ISqlExpressionFactory sqlExpressionFact
return null;
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetTimeOnlyMethodTranslator.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetTimeOnlyMethodTranslator.cs
index 72747d217..cdb97f319 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetTimeOnlyMethodTranslator.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetTimeOnlyMethodTranslator.cs
@@ -106,4 +106,4 @@ public class JetTimeOnlyMethodTranslator(ISqlExpressionFactory sqlExpressionFact
return null;
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetTimeSpanMemberTranslator.cs b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetTimeSpanMemberTranslator.cs
index d8d5432a5..cff7a2cc4 100644
--- a/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetTimeSpanMemberTranslator.cs
+++ b/src/EFCore.Jet.Common/Query/ExpressionTranslators/Internal/JetTimeSpanMemberTranslator.cs
@@ -52,4 +52,4 @@ public class JetTimeSpanMemberTranslator(ISqlExpressionFactory sqlExpressionFact
return null;
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetCompatibilityExpressionVisitor.cs b/src/EFCore.Jet.Common/Query/Internal/JetCompatibilityExpressionVisitor.cs
index 1fa063baa..c7064cd22 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetCompatibilityExpressionVisitor.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetCompatibilityExpressionVisitor.cs
@@ -7,8 +7,8 @@ namespace EntityFrameworkCore.Jet.Query.Internal;
public class JetCompatibilityExpressionVisitor : ExpressionVisitor
{
- protected override Expression VisitExtension(Expression extensionExpression)
- => extensionExpression switch
+ protected override Expression VisitExtension(Expression node)
+ => node switch
{
RowNumberExpression rowNumberExpression => VisitRowNumber(rowNumberExpression),
CrossApplyExpression crossApplyExpression => VisitCrossApply(crossApplyExpression),
@@ -21,7 +21,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
SelectExpression selectExpression => VisitSelect(selectExpression),
ShapedQueryExpression shapedQueryExpression => shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), Visit(shapedQueryExpression.ShaperExpression)),
CrossJoinExpression crossJoinExpression => VisitCrossJoin(crossJoinExpression),
- _ => base.VisitExtension(extensionExpression)
+ _ => base.VisitExtension(node)
};
protected virtual Expression VisitRowNumber(RowNumberExpression rowNumberExpression)
@@ -92,7 +92,7 @@ protected virtual Expression VisitCrossJoin(CrossJoinExpression crossJoinExpress
protected virtual Expression TranslationFailed(Expression expression)
=> throw new InvalidOperationException("Unsupported Jet expression: " + expression.Print());
- private bool ContainsUnsupportCol(SqlBinaryExpression binaryexp)
+ private static bool ContainsUnsupportCol(SqlBinaryExpression binaryexp)
{
bool containsunsupported = false;
if (binaryexp.Left is SqlBinaryExpression left)
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetCompiledQueryCacheKeyGenerator.cs b/src/EFCore.Jet.Common/Query/Internal/JetCompiledQueryCacheKeyGenerator.cs
index 520c1deb8..cad189e6d 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetCompiledQueryCacheKeyGenerator.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetCompiledQueryCacheKeyGenerator.cs
@@ -55,4 +55,4 @@ public override int GetHashCode()
=> _relationalCompiledQueryCacheKey.GetHashCode();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetDateTimeRangeConverter.cs b/src/EFCore.Jet.Common/Query/Internal/JetDateTimeRangeConverter.cs
index 37274a24f..d6f0c052d 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetDateTimeRangeConverter.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetDateTimeRangeConverter.cs
@@ -48,9 +48,9 @@ public virtual Expression Process(Expression queryExpression, ParametersCacheDec
}
///
- protected override Expression VisitExtension(Expression extensionExpression)
+ protected override Expression VisitExtension(Expression node)
{
- if (extensionExpression is SqlBinaryExpression
+ if (node is SqlBinaryExpression
{
OperatorType: ExpressionType.GreaterThan
or ExpressionType.GreaterThanOrEqual
@@ -68,7 +68,7 @@ or ExpressionType.LessThanOrEqual
}
}
- return base.VisitExtension(extensionExpression);
+ return base.VisitExtension(node);
}
/// Swaps an operand that is the CLR minimum DateTime for Jet's minimum.
@@ -91,4 +91,4 @@ private SqlExpression Replace(SqlExpression operand)
_ => operand,
};
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetLiftOrderByPostprocessor.cs b/src/EFCore.Jet.Common/Query/Internal/JetLiftOrderByPostprocessor.cs
index a4ba4ab22..669a88b33 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetLiftOrderByPostprocessor.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetLiftOrderByPostprocessor.cs
@@ -1,4 +1,3 @@
-using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
namespace EntityFrameworkCore.Jet.Query.Internal;
@@ -13,7 +12,7 @@ namespace EntityFrameworkCore.Jet.Query.Internal;
public class JetLiftOrderByPostprocessor(IRelationalTypeMappingSource typeMappingSource,
ISqlExpressionFactory sqlExpressionFactory,
SqlAliasManager sqlAliasManager)
- : ExpressionVisitor
+ : ExpressionVisitor
{
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -32,10 +31,10 @@ public virtual Expression Process(Expression expression)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- [return: NotNullIfNotNull(nameof(expression))]
- public override Expression? Visit(Expression? expression)
+ [return: NotNullIfNotNull(nameof(node))]
+ public override Expression? Visit(Expression? node)
{
- switch (expression)
+ switch (node)
{
case ShapedQueryExpression shapedQueryExpression:
return shapedQueryExpression.Update(
@@ -50,7 +49,7 @@ public virtual Expression Process(Expression expression)
relationalSplitCollectionShaperExpression.ChildIdentifier, (SelectExpression)newSelect, newInner);
return relationalSplitCollectionShaperExpression;
case UpdateExpression or DeleteExpression:
- return expression;
+ return node;
case SelectExpression selectExpression:
{
Dictionary columnsToRewrite = [];
@@ -90,9 +89,9 @@ public virtual Expression Process(Expression expression)
}
}
- if (columnsToRewrite.Count == 0 || columnsToRewrite.All(p => p.Value.rewrite == false))
+ if (columnsToRewrite.Count == 0 || columnsToRewrite.All(p => !p.Value.rewrite))
{
- return base.Visit(expression);
+ return base.Visit(node);
}
// A lift IS happening: ClearOrdering below wipes every ordering and we re-append only what's
@@ -163,12 +162,12 @@ public virtual Expression Process(Expression expression)
return result;
}
case RelationalGroupByShaperExpression relationalGroupByShaperExpression:
- {
- return base.VisitExtension(relationalGroupByShaperExpression);
- }
+ {
+ return base.VisitExtension(relationalGroupByShaperExpression);
+ }
}
- return base.Visit(expression);
+ return base.Visit(node);
}
private SelectExpression AddAliasManager(SelectExpression selectExpression)
@@ -195,4 +194,4 @@ private SelectExpression AddAliasManager(SelectExpression selectExpression)
projMap?.SetValue(newselect, projMap.GetValue(selectExpression));
return newselect;
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetLocateScalarSubqueryVisitor.cs b/src/EFCore.Jet.Common/Query/Internal/JetLocateScalarSubqueryVisitor.cs
index 4772c5554..772bd779b 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetLocateScalarSubqueryVisitor.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetLocateScalarSubqueryVisitor.cs
@@ -14,8 +14,8 @@ public class JetLocateScalarSubqueryVisitor : ExpressionVisitor
// Every one of those nodes is an Expression with NodeType Extension, so the dispatch is reproduced here and
// the per-node methods below are unchanged. Anything unrecognised falls through to the base, which visits
// the node's children — the same default the removed base class applied.
- protected override Expression VisitExtension(Expression extensionExpression)
- => extensionExpression switch
+ protected override Expression VisitExtension(Expression node)
+ => node switch
{
AtTimeZoneExpression e => VisitAtTimeZone(e),
CaseExpression e => VisitCase(e),
@@ -53,12 +53,9 @@ protected override Expression VisitExtension(Expression extensionExpression)
UnionExpression e => VisitUnion(e),
UpdateExpression e => VisitUpdate(e),
ValuesExpression e => VisitValues(e),
- _ => base.VisitExtension(extensionExpression),
+ _ => base.VisitExtension(node),
};
- private readonly IRelationalTypeMappingSource _typeMappingSource;
- private readonly ISqlExpressionFactory _sqlExpressionFactory;
-
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -69,7 +66,10 @@ public JetLocateScalarSubqueryVisitor(
IRelationalTypeMappingSource typeMappingSource,
ISqlExpressionFactory sqlExpressionFactory)
{
- (_typeMappingSource, _sqlExpressionFactory) = (typeMappingSource, sqlExpressionFactory);
+ // Neither dependency is needed to locate a scalar subquery; both stay on the signature because that is
+ // what the postprocessor constructs this with.
+ _ = typeMappingSource;
+ _ = sqlExpressionFactory;
}
protected virtual Expression VisitAtTimeZone(AtTimeZoneExpression atTimeZoneExpression)
@@ -120,7 +120,7 @@ protected virtual Expression VisitCrossJoin(CrossJoinExpression crossJoinExpress
protected virtual Expression VisitDelete(DeleteExpression deleteExpression)
{
- return deleteExpression.Update(deleteExpression.Table,(SelectExpression)Visit(deleteExpression.SelectExpression));
+ return deleteExpression.Update(deleteExpression.Table, (SelectExpression)Visit(deleteExpression.SelectExpression));
}
protected virtual Expression VisitDistinct(DistinctExpression distinctExpression)
@@ -460,4 +460,4 @@ protected virtual Expression VisitValues(ValuesExpression valuesExpression)
throw new UnreachableException();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetOuterJoinProjectionGuardExpressionVisitor.cs b/src/EFCore.Jet.Common/Query/Internal/JetOuterJoinProjectionGuardExpressionVisitor.cs
index 16208d5eb..ec0bb5731 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetOuterJoinProjectionGuardExpressionVisitor.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetOuterJoinProjectionGuardExpressionVisitor.cs
@@ -1,7 +1,5 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System.Diagnostics.CodeAnalysis;
-using System.Reflection;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
namespace EntityFrameworkCore.Jet.Query.Internal
@@ -73,12 +71,16 @@ public sealed class JetOuterJoinProjectionGuardExpressionVisitor(ISqlExpressionF
?? throw new InvalidOperationException("Could not find SelectExpression._projection.");
/// Subqueries seen as the target of a LEFT JOIN, by reference.
+ // IDE0028's only fix here is a collection expression, which would drop the comparer and match the
+ // subqueries structurally instead of by reference.
+#pragma warning disable IDE0028
private readonly HashSet _leftJoinTargets = new(ReferenceEqualityComparer.Instance);
+#pragma warning restore IDE0028
- [return: NotNullIfNotNull(nameof(expression))]
- public override Expression? Visit(Expression? expression)
+ [return: NotNullIfNotNull(nameof(node))]
+ public override Expression? Visit(Expression? node)
{
- switch (expression)
+ switch (node)
{
// ShapedQueryExpression and the split-collection shaper forbid generic child visiting.
case ShapedQueryExpression shapedQueryExpression:
@@ -94,30 +96,30 @@ public sealed class JetOuterJoinProjectionGuardExpressionVisitor(ISqlExpressionF
Visit(splitCollectionShaper.InnerShaper));
case UpdateExpression or DeleteExpression:
- return expression;
+ return node;
case SelectExpression selectExpression:
- {
- // Record this select's LEFT JOIN targets before descending, so we recognise one when
- // VisitChildren reaches it.
- foreach (var table in selectExpression.Tables)
{
- if (table is LeftJoinExpression { Table: SelectExpression target })
+ // Record this select's LEFT JOIN targets before descending, so we recognise one when
+ // VisitChildren reaches it.
+ foreach (var table in selectExpression.Tables)
{
- _leftJoinTargets.Add(target);
+ if (table is LeftJoinExpression { Table: SelectExpression target })
+ {
+ _leftJoinTargets.Add(target);
+ }
}
- }
- if (_leftJoinTargets.Contains(selectExpression))
- {
- Guard(selectExpression);
- }
+ if (_leftJoinTargets.Contains(selectExpression))
+ {
+ Guard(selectExpression);
+ }
- return base.VisitExtension(selectExpression);
- }
+ return base.VisitExtension(selectExpression);
+ }
default:
- return base.Visit(expression);
+ return base.Visit(node);
}
}
@@ -125,7 +127,7 @@ private void Guard(SelectExpression selectExpression)
{
// Anchor on something non-NULL for any row that actually matched: the grouping key when the subquery
// groups (referencing anything else there would not be legal), otherwise any projected column.
- var anchor = selectExpression.GroupBy.FirstOrDefault()
+ var anchor = (selectExpression.GroupBy is [var key, ..] ? key : null)
?? selectExpression.Projection.Select(p => p.Expression).OfType().FirstOrDefault();
if (anchor is null)
@@ -176,4 +178,4 @@ private void Guard(SelectExpression selectExpression)
private static bool IsMarker(SqlExpression expression)
=> expression is SqlFragmentExpression;
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetQueryCompilationContext.cs b/src/EFCore.Jet.Common/Query/Internal/JetQueryCompilationContext.cs
index e0f3edb9b..1b529ccf2 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetQueryCompilationContext.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetQueryCompilationContext.cs
@@ -47,4 +47,4 @@ public JetQueryCompilationContext(
///
public override bool SupportsPrecompiledQuery => true;
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetQueryCompilationContextFactory.cs b/src/EFCore.Jet.Common/Query/Internal/JetQueryCompilationContextFactory.cs
index 98f9fcb99..f4a3bcc44 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetQueryCompilationContextFactory.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetQueryCompilationContextFactory.cs
@@ -1,5 +1,3 @@
-using EntityFrameworkCore.Jet.Storage.Internal;
-
namespace EntityFrameworkCore.Jet.Query.Internal;
///
@@ -47,4 +45,4 @@ public virtual QueryCompilationContext Create(bool async)
public virtual QueryCompilationContext CreatePrecompiled(bool async)
=> new JetQueryCompilationContext(
Dependencies, RelationalDependencies, async, precompiling: true);
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetQueryTranslationPreprocessor.cs b/src/EFCore.Jet.Common/Query/Internal/JetQueryTranslationPreprocessor.cs
index 38440cda5..1a51293e5 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetQueryTranslationPreprocessor.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetQueryTranslationPreprocessor.cs
@@ -49,7 +49,7 @@ private static bool IsLongCountMethod(MethodInfo method)
=> method == QueryableMethods.LongCountWithoutPredicate
|| method == QueryableMethods.LongCountWithPredicate;
- private static Expression MakeAnyCall(MethodCallExpression longCountCall)
+ private static MethodCallExpression MakeAnyCall(MethodCallExpression longCountCall)
{
var elementType = longCountCall.Method.GetGenericArguments()[0];
var anyMethod = longCountCall.Arguments.Count == 2
@@ -58,4 +58,4 @@ private static Expression MakeAnyCall(MethodCallExpression longCountCall)
return Expression.Call(anyMethod, longCountCall.Arguments);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetQueryTranslationPreprocessorFactory.cs b/src/EFCore.Jet.Common/Query/Internal/JetQueryTranslationPreprocessorFactory.cs
index b6b097609..ba4c289d5 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetQueryTranslationPreprocessorFactory.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetQueryTranslationPreprocessorFactory.cs
@@ -12,4 +12,4 @@ public virtual QueryTranslationPreprocessor Create(QueryCompilationContext query
dependencies,
relationalDependencies,
(RelationalQueryCompilationContext)queryCompilationContext);
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Jet.Common/Query/Internal/JetQueryableMethodTranslatingExpressionVisitor.cs
index 08e0baed9..e959d37c0 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetQueryableMethodTranslatingExpressionVisitor.cs
@@ -2,9 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using EntityFrameworkCore.Jet.Internal;
-using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
-using System.Diagnostics.CodeAnalysis;
namespace EntityFrameworkCore.Jet.Query.Internal;
@@ -16,7 +14,7 @@ namespace EntityFrameworkCore.Jet.Query.Internal;
///
public class JetQueryableMethodTranslatingExpressionVisitor : RelationalQueryableMethodTranslatingExpressionVisitor
{
- protected readonly RelationalQueryCompilationContext queryCompilationContext;
+ private readonly RelationalQueryCompilationContext _queryCompilationContext;
private readonly bool _subquery;
@@ -32,7 +30,7 @@ public JetQueryableMethodTranslatingExpressionVisitor(
RelationalQueryCompilationContext queryCompilationContext)
: base(dependencies, relationalDependencies, queryCompilationContext)
{
- this.queryCompilationContext = queryCompilationContext;
+ _queryCompilationContext = queryCompilationContext;
_subquery = false;
}
@@ -46,7 +44,7 @@ protected JetQueryableMethodTranslatingExpressionVisitor(
JetQueryableMethodTranslatingExpressionVisitor parentVisitor)
: base(parentVisitor)
{
- this.queryCompilationContext = parentVisitor.queryCompilationContext;
+ _queryCompilationContext = parentVisitor._queryCompilationContext;
_subquery = true;
}
@@ -64,7 +62,7 @@ protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVis
{
//Jet can't handle a left join following a cross join
//so we push the cross join into a subquery wrapped by parentheses and then do the left join on that
- if (outer.QueryExpression is SelectExpression selectExpression && selectExpression.Tables.Last() is CrossJoinExpression)
+ if (outer.QueryExpression is SelectExpression selectExpression && selectExpression.Tables[^1] is CrossJoinExpression)
{
selectExpression.PushdownIntoSubquery();
}
@@ -120,7 +118,7 @@ protected override bool IsValidSelectExpressionForExecuteDelete(
///
protected override bool IsValidSelectExpressionForExecuteUpdate(
SelectExpression selectExpression,
- TableExpressionBase table,
+ TableExpressionBase targetTable,
[NotNullWhen(true)] out TableExpression? tableExpression)
{
if (selectExpression is
@@ -133,12 +131,12 @@ protected override bool IsValidSelectExpressionForExecuteUpdate(
Limit: null
})
{
- if (selectExpression.Tables.Count > 1 && table is JoinExpressionBase joinExpressionBase)
+ if (selectExpression.Tables.Count > 1 && targetTable is JoinExpressionBase joinExpressionBase)
{
- table = joinExpressionBase.Table;
+ targetTable = joinExpressionBase.Table;
}
- if (table is TableExpression te)
+ if (targetTable is TableExpression te)
{
tableExpression = te;
return true;
@@ -163,7 +161,7 @@ protected override bool IsValidSelectExpressionForExecuteUpdate(
if (!IsOrdered(selectExpression))
{
- queryCompilationContext.Logger.RowLimitingOperationWithoutOrderByWarning();
+ _queryCompilationContext.Logger.RowLimitingOperationWithoutOrderByWarning();
}
selectExpression.ApplyOffset(translation);
@@ -193,7 +191,7 @@ protected override bool IsValidSelectExpressionForExecuteUpdate(
if (selectExpression.Predicate == null
&& selectExpression.Orderings.Count == 0)
{
- queryCompilationContext.Logger.FirstWithoutOrderByAndFilterWarning();
+ _queryCompilationContext.Logger.FirstWithoutOrderByAndFilterWarning();
}
JetApplyLimit(selectExpression, TranslateExpression(Expression.Constant(1))!);
@@ -271,7 +269,7 @@ protected override bool IsValidSelectExpressionForExecuteUpdate(
if (!IsOrdered(selectExpression))
{
- queryCompilationContext.Logger.RowLimitingOperationWithoutOrderByWarning();
+ _queryCompilationContext.Logger.RowLimitingOperationWithoutOrderByWarning();
}
JetApplyLimit(selectExpression, translation);
@@ -279,7 +277,7 @@ protected override bool IsValidSelectExpressionForExecuteUpdate(
return source;
}
- private void JetApplyLimit(SelectExpression selectExpression, SqlExpression limit)
+ private static void JetApplyLimit(SelectExpression selectExpression, SqlExpression limit)
{
var oldLimit = selectExpression.Limit;
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetQueryableMethodTranslatingExpressionVisitorFactory.cs b/src/EFCore.Jet.Common/Query/Internal/JetQueryableMethodTranslatingExpressionVisitorFactory.cs
index 864553f0d..bcb3c901c 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetQueryableMethodTranslatingExpressionVisitorFactory.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetQueryableMethodTranslatingExpressionVisitorFactory.cs
@@ -38,4 +38,4 @@ public class JetQueryableMethodTranslatingExpressionVisitorFactory(
///
public virtual QueryableMethodTranslatingExpressionVisitor Create(QueryCompilationContext queryCompilationContext)
=> new JetQueryableMethodTranslatingExpressionVisitor(Dependencies, RelationalDependencies, (RelationalQueryCompilationContext)queryCompilationContext);
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetSkipTakePostprocessor.cs b/src/EFCore.Jet.Common/Query/Internal/JetSkipTakePostprocessor.cs
index 0bd35382f..8a3999f0f 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetSkipTakePostprocessor.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetSkipTakePostprocessor.cs
@@ -1,4 +1,3 @@
-using System.Diagnostics.CodeAnalysis;
using EntityFrameworkCore.Jet.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
@@ -12,8 +11,6 @@ namespace EntityFrameworkCore.Jet.Query.Internal;
///
public class JetSkipTakePostprocessor : ExpressionVisitor
{
- private readonly IRelationalTypeMappingSource _typeMappingSource;
- private readonly ISqlExpressionFactory _sqlExpressionFactory;
private Stack parent = new();
private readonly QuerySplittingBehavior? _splittingBehavior;
///
@@ -27,7 +24,10 @@ public JetSkipTakePostprocessor(
ISqlExpressionFactory sqlExpressionFactory,
QuerySplittingBehavior? splittingBehavior)
{
- (_typeMappingSource, _sqlExpressionFactory) = (typeMappingSource, sqlExpressionFactory);
+ // Neither dependency is needed by the rewrite; both stay on the signature because that is what the
+ // postprocessor constructs this with.
+ _ = typeMappingSource;
+ _ = sqlExpressionFactory;
_splittingBehavior = splittingBehavior;
}
@@ -48,10 +48,10 @@ public virtual Expression Process(Expression expression)
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- [return: NotNullIfNotNull(nameof(expression))]
- public override Expression? Visit(Expression? expression)
+ [return: NotNullIfNotNull(nameof(node))]
+ public override Expression? Visit(Expression? node)
{
- switch (expression)
+ switch (node)
{
case ShapedQueryExpression shapedQueryExpression:
return shapedQueryExpression.UpdateQueryExpression(Visit(shapedQueryExpression.QueryExpression));
@@ -100,7 +100,7 @@ public virtual Expression Process(Expression expression)
return newselectExpression;
}
default:
- return base.Visit(expression);
+ return base.Visit(node);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetSqlNullabilityProcessor.cs b/src/EFCore.Jet.Common/Query/Internal/JetSqlNullabilityProcessor.cs
index 1a51a03d9..010ed10f0 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetSqlNullabilityProcessor.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetSqlNullabilityProcessor.cs
@@ -19,4 +19,4 @@ public class JetSqlNullabilityProcessor(
RelationalParameterBasedSqlProcessorDependencies dependencies,
RelationalParameterBasedSqlProcessorParameters parameters) : SqlNullabilityProcessor(dependencies, parameters)
{
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetSqlTranslatingExpressionVisitor.cs b/src/EFCore.Jet.Common/Query/Internal/JetSqlTranslatingExpressionVisitor.cs
index 298950676..8a32e9f0c 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetSqlTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetSqlTranslatingExpressionVisitor.cs
@@ -1,8 +1,7 @@
-using System.Diagnostics.CodeAnalysis;
-using System.Globalization;
+using EntityFrameworkCore.Jet.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
+using System.Globalization;
using System.Text;
-using EntityFrameworkCore.Jet.Internal;
using ExpressionExtensions = Microsoft.EntityFrameworkCore.Query.ExpressionExtensions;
namespace EntityFrameworkCore.Jet.Query.Internal;
@@ -189,12 +188,12 @@ protected override Expression VisitUnary(UnaryExpression unaryExpression)
protected override Expression VisitExtension(Expression extensionExpression)
{
- var result = base.VisitExtension(extensionExpression);
+ var result = base.VisitExtension(extensionExpression);
if (extensionExpression is ShapedQueryExpression shapedQueryExpression)
{
var shaperExpression = shapedQueryExpression.ShaperExpression;
if (shapedQueryExpression.ResultCardinality == ResultCardinality.SingleOrDefault
- && !shaperExpression.Type.IsNullableType() && result is SqlFunctionExpression { Name: "COALESCE"} sqlFunctionExpression)
+ && !shaperExpression.Type.IsNullableType() && result is SqlFunctionExpression { Name: "COALESCE" } sqlFunctionExpression)
{
if (sqlFunctionExpression.Arguments?[1] is SqlConstantExpression { Value: DateTime { Ticks: 0 } })
{
@@ -299,7 +298,7 @@ bool TryTranslateStartsEndsWithContains(
// IsLikeWildChar) and send a simple LIKE
translation = patternConstant.Value switch
{
- null => _sqlExpressionFactory.Like(translatedInstance, _sqlExpressionFactory.Constant(null,typeof(string), stringTypeMapping)),
+ null => _sqlExpressionFactory.Like(translatedInstance, _sqlExpressionFactory.Constant(null, typeof(string), stringTypeMapping)),
// In .NET, all strings start with/end with/contain the empty string, but SQL LIKE return false for empty patterns.
// Return % which always matches instead.
@@ -535,7 +534,7 @@ private Expression TranslateByteArrayElementAccess(Expression array, Expression
"ASCB",
[ Dependencies.SqlExpressionFactory.Function(
"MIDB",
- [
+ [
sqlArray,
Dependencies.SqlExpressionFactory.Add(
Dependencies.SqlExpressionFactory.ApplyDefaultTypeMapping(sqlIndex),
@@ -614,4 +613,4 @@ private IReadOnlyList ApplyMinMaxResultTypeMapping(
private static string? GetProviderType(SqlExpression expression)
=> expression.TypeMapping?.StoreType;
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetSqlTranslatingExpressionVisitorFactory.cs b/src/EFCore.Jet.Common/Query/Internal/JetSqlTranslatingExpressionVisitorFactory.cs
index 50c6d1596..97acc18f7 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetSqlTranslatingExpressionVisitorFactory.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetSqlTranslatingExpressionVisitorFactory.cs
@@ -34,4 +34,4 @@ public virtual RelationalSqlTranslatingExpressionVisitor Create(
Dependencies,
queryCompilationContext,
queryableMethodTranslatingExpressionVisitor);
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/JetZeroLimitConverter.cs b/src/EFCore.Jet.Common/Query/Internal/JetZeroLimitConverter.cs
index 6b1f61fe4..6a8e8f6ac 100644
--- a/src/EFCore.Jet.Common/Query/Internal/JetZeroLimitConverter.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/JetZeroLimitConverter.cs
@@ -1,4 +1,3 @@
-using EntityFrameworkCore.Jet.Storage.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
namespace EntityFrameworkCore.Jet.Query.Internal;
@@ -52,9 +51,9 @@ public virtual Expression Process(Expression queryExpression, ParametersCacheDec
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- protected override Expression VisitExtension(Expression extensionExpression)
+ protected override Expression VisitExtension(Expression node)
{
- if (extensionExpression is SelectExpression selectExpression)
+ if (node is SelectExpression selectExpression)
{
if (IsZero(selectExpression.Limit))
{
@@ -85,6 +84,6 @@ bool IsZero(SqlExpression? sqlExpression)
}
}
- return base.VisitExtension(extensionExpression);
+ return base.VisitExtension(node);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Internal/SearchConditionConvertingExpressionVisitor.cs b/src/EFCore.Jet.Common/Query/Internal/SearchConditionConvertingExpressionVisitor.cs
index 82e0174a2..d746097f4 100644
--- a/src/EFCore.Jet.Common/Query/Internal/SearchConditionConvertingExpressionVisitor.cs
+++ b/src/EFCore.Jet.Common/Query/Internal/SearchConditionConvertingExpressionVisitor.cs
@@ -2,7 +2,6 @@
using EntityFrameworkCore.Jet.Utilities;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
-#nullable enable
namespace EntityFrameworkCore.Jet.Query.Internal;
@@ -13,8 +12,8 @@ public class SearchConditionConvertingExpressionVisitor(ISqlExpressionFactory sq
// Every one of those nodes is an Expression with NodeType Extension, so the dispatch is reproduced here and
// the per-node methods below are unchanged. Anything unrecognised falls through to the base, which visits
// the node's children — the same default the removed base class applied.
- protected override Expression VisitExtension(Expression extensionExpression)
- => extensionExpression switch
+ protected override Expression VisitExtension(Expression node)
+ => node switch
{
AtTimeZoneExpression e => VisitAtTimeZone(e),
CaseExpression e => VisitCase(e),
@@ -52,7 +51,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
UnionExpression e => VisitUnion(e),
UpdateExpression e => VisitUpdate(e),
ValuesExpression e => VisitValues(e),
- _ => base.VisitExtension(extensionExpression),
+ _ => base.VisitExtension(node),
};
private bool _isSearchCondition;
@@ -368,23 +367,23 @@ protected virtual Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpression
{
case ExpressionType.Not
when sqlUnaryExpression.Type == typeof(bool):
- {
- // when possible, avoid converting to/from predicate form
- if (!_isSearchCondition && sqlUnaryExpression.Operand is not (ExistsExpression or InExpression or LikeExpression))
{
- var negatedOperand = (SqlExpression)Visit(sqlUnaryExpression.Operand);
- return sqlExpressionFactory.MakeBinary(
- ExpressionType.ExclusiveOr,
- negatedOperand,
- sqlExpressionFactory.Constant(true, negatedOperand.TypeMapping),
- negatedOperand.TypeMapping
- )!;
- }
+ // when possible, avoid converting to/from predicate form
+ if (!_isSearchCondition && sqlUnaryExpression.Operand is not (ExistsExpression or InExpression or LikeExpression))
+ {
+ var negatedOperand = (SqlExpression)Visit(sqlUnaryExpression.Operand);
+ return sqlExpressionFactory.MakeBinary(
+ ExpressionType.ExclusiveOr,
+ negatedOperand,
+ sqlExpressionFactory.Constant(true, negatedOperand.TypeMapping),
+ negatedOperand.TypeMapping
+ )!;
+ }
- _isSearchCondition = true;
- resultCondition = true;
- break;
- }
+ _isSearchCondition = true;
+ resultCondition = true;
+ break;
+ }
case ExpressionType.Not:
_isSearchCondition = false;
diff --git a/src/EFCore.Jet.Common/Query/JetSqlExpressionFactory.cs b/src/EFCore.Jet.Common/Query/JetSqlExpressionFactory.cs
index 89d7b86c8..6165e2540 100644
--- a/src/EFCore.Jet.Common/Query/JetSqlExpressionFactory.cs
+++ b/src/EFCore.Jet.Common/Query/JetSqlExpressionFactory.cs
@@ -37,7 +37,7 @@ public override SqlExpression Convert(
ExpressionType.Coalesce,
sqlExpression,
Constant(
- null,typeof(string),
+ null, typeof(string),
RelationalTypeMapping.NullMapping),
typeMapping);
diff --git a/src/EFCore.Jet.Common/Query/Sql/Internal/IJetExpressionVisitor.cs b/src/EFCore.Jet.Common/Query/Sql/Internal/IJetExpressionVisitor.cs
index e957a7654..bb8acf928 100644
--- a/src/EFCore.Jet.Common/Query/Sql/Internal/IJetExpressionVisitor.cs
+++ b/src/EFCore.Jet.Common/Query/Sql/Internal/IJetExpressionVisitor.cs
@@ -6,4 +6,4 @@ public interface IJetExpressionVisitor
{
// Expression VisitRowNumber(RowNumberExpression rowNumberExpression);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Sql/Internal/JetQuerySqlGenerator.cs b/src/EFCore.Jet.Common/Query/Sql/Internal/JetQuerySqlGenerator.cs
index b12e2ed2d..632620de0 100644
--- a/src/EFCore.Jet.Common/Query/Sql/Internal/JetQuerySqlGenerator.cs
+++ b/src/EFCore.Jet.Common/Query/Sql/Internal/JetQuerySqlGenerator.cs
@@ -89,7 +89,7 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
GenerateTop(selectExpression);
- if (selectExpression.Projection.Any())
+ if (selectExpression.Projection.Count > 0)
{
GenerateList(selectExpression.Projection, e => Visit(e));
}
@@ -208,7 +208,7 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
private void VisitJetTables(IReadOnlyList tables, bool addFromSql, out List colexp)
{
colexp = [];
- if (!tables.Any())
+ if (tables.Count == 0)
{
GeneratePseudoFromClause();
return;
@@ -301,7 +301,7 @@ private void VisitJetTablesGrouped(IReadOnlyList tables, Li
case CrossApplyExpression or OuterApplyExpression:
throw new UnreachableException();
case CrossJoinExpression cj:
- groups.Add((cj.Table, [], new HashSet { cj.Table.Alias! }));
+ groups.Add((cj.Table, [], [cj.Table.Alias!]));
break;
case PredicateJoinExpressionBase join:
var predicateAliases = ExtractTableAliases(join.JoinPredicate);
@@ -319,7 +319,7 @@ private void VisitJetTablesGrouped(IReadOnlyList tables, Li
}
break;
default:
- groups.Add((table, [], new HashSet { table.Alias! }));
+ groups.Add((table, [], [table.Alias!]));
break;
}
}
@@ -388,7 +388,7 @@ private void EmitJoinInGroup(PredicateJoinExpressionBase join, TableExpressionBa
}
}
- private HashSet ExtractTableAliases(SqlExpression expression)
+ private static HashSet ExtractTableAliases(SqlExpression expression)
{
var result = new HashSet();
CollectTableAliases(expression, result);
@@ -412,7 +412,7 @@ private static void CollectTableAliases(SqlExpression expression, HashSet ExtractColumnExpressions(SqlBinaryExpression binaryexp)
+ private static List ExtractColumnExpressions(SqlBinaryExpression binaryexp)
{
List result = [];
switch (binaryexp.Left)
@@ -437,7 +437,7 @@ private List ExtractColumnExpressions(SqlBinaryExpression bina
return result;
}
- private List ExtractColumnExpressions(SqlUnaryExpression unaryexp)
+ private static List ExtractColumnExpressions(SqlUnaryExpression unaryexp)
{
List result = [];
switch (unaryexp.Operand)
@@ -508,7 +508,7 @@ protected override Expression VisitJsonScalar(JsonScalarExpression jsonScalarExp
throw new UnreachableException();
}
- private bool IsNonComposedSetOperation(SelectExpression selectExpression)
+ private static bool IsNonComposedSetOperation(SelectExpression selectExpression)
=> selectExpression.Offset == null
&& selectExpression.Limit == null
&& selectExpression is { IsDistinct: false, Predicate: null, Having: null, Orderings.Count: 0, GroupBy.Count: 0, Tables: [SetOperationBase setOperation] }
@@ -826,7 +826,7 @@ protected override Expression VisitSqlUnary(SqlUnaryExpression sqlUnaryExpressio
}
- private Expression VisitJetConvertExpression(SqlUnaryExpression convertExpression)
+ private SqlUnaryExpression VisitJetConvertExpression(SqlUnaryExpression convertExpression)
{
var typeMapping = convertExpression.TypeMapping ?? throw new InvalidOperationException(
RelationalStrings.UnsupportedType(convertExpression.Type.ShortDisplayName()));
@@ -986,11 +986,11 @@ static void CollectEqualityColumns(SqlExpression expression, HashSet columnNames)
+ private RowValueExpression VisitRowValuePrivate(RowValueExpression rowValueExpression, IReadOnlyList columnNames)
{
var values = rowValueExpression.Values;
var count = values.Count;
@@ -1436,4 +1436,4 @@ protected override void CheckComposableSqlTrimmed(ReadOnlySpan sql)
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Query/Sql/Internal/JetQuerySqlGeneratorFactory.cs b/src/EFCore.Jet.Common/Query/Sql/Internal/JetQuerySqlGeneratorFactory.cs
index 5883225c7..e830a70cc 100644
--- a/src/EFCore.Jet.Common/Query/Sql/Internal/JetQuerySqlGeneratorFactory.cs
+++ b/src/EFCore.Jet.Common/Query/Sql/Internal/JetQuerySqlGeneratorFactory.cs
@@ -18,4 +18,4 @@ public class JetQuerySqlGeneratorFactory(
public virtual QuerySqlGenerator Create()
=> new JetQuerySqlGenerator(_dependencies, _typeMappingSource);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Storage/Internal/JetSqlGenerationHelper.cs b/src/EFCore.Jet.Common/Storage/Internal/JetSqlGenerationHelper.cs
index 630e82eef..1f0510154 100644
--- a/src/EFCore.Jet.Common/Storage/Internal/JetSqlGenerationHelper.cs
+++ b/src/EFCore.Jet.Common/Storage/Internal/JetSqlGenerationHelper.cs
@@ -1,7 +1,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System.Text;
using EntityFrameworkCore.Jet.Utilities;
+using System.Globalization;
+using System.Text;
namespace EntityFrameworkCore.Jet.Storage.Internal
{
@@ -43,7 +44,7 @@ public override void EscapeIdentifier(StringBuilder builder, string identifier)
identifier = identifier
.Replace(".", "#")
.Replace("`", "``");
-
+
builder.Append(identifier);
}
@@ -86,7 +87,8 @@ public static string TruncateIdentifier(string identifier)
if (identifier.Length <= 64)
return identifier;
- return identifier[..56] + identifier.ToLowerInvariant().GetHashCode().ToString("X8");
+ return identifier[..56]
+ + identifier.ToLowerInvariant().GetHashCode().ToString("X8", CultureInfo.InvariantCulture);
}
public override string GenerateCreateSavepointStatement(string name)
@@ -107,4 +109,4 @@ public override string GenerateReleaseSavepointStatement(string name)
public override string StartTransactionStatement => "BEGIN TRANSACTION" + StatementTerminator;
public override string CommitTransactionStatement => "COMMIT TRANSACTION" + StatementTerminator;
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Update/Internal/IJetUpdateSqlGenerator.cs b/src/EFCore.Jet.Common/Update/Internal/IJetUpdateSqlGenerator.cs
index 53a6d0729..e51e73dcb 100644
--- a/src/EFCore.Jet.Common/Update/Internal/IJetUpdateSqlGenerator.cs
+++ b/src/EFCore.Jet.Common/Update/Internal/IJetUpdateSqlGenerator.cs
@@ -43,4 +43,4 @@ ResultSetMapping AppendBulkInsertOperation(
int commandPosition)
=> AppendBulkInsertOperation(commandStringBuilder, modificationCommands, commandPosition, out _);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Update/Internal/JetModificationCommandBatch.cs b/src/EFCore.Jet.Common/Update/Internal/JetModificationCommandBatch.cs
index 12c1a41e5..ac974827c 100644
--- a/src/EFCore.Jet.Common/Update/Internal/JetModificationCommandBatch.cs
+++ b/src/EFCore.Jet.Common/Update/Internal/JetModificationCommandBatch.cs
@@ -15,10 +15,12 @@ namespace EntityFrameworkCore.Jet.Update.Internal
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public class JetModificationCommandBatch(
- ModificationCommandBatchFactoryDependencies dependencies) : AffectedCountModificationCommandBatch(dependencies, 1)
+ ModificationCommandBatchFactoryDependencies dependencies)
+ : AffectedCountModificationCommandBatch(dependencies, MaxRowCount)
{
+ // Jet executes one statement at a time: a batch is a single command, never a multi-row one.
private const int MaxRowCount = 1;
- private const int MaxParameterCount = 2100 - 2;
+
private readonly List _pendingBulkInsertCommands = [];
///
@@ -176,4 +178,4 @@ public override async Task ExecuteAsync(
await base.ExecuteAsync(connection, cancellationToken).ConfigureAwait(false);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Update/Internal/JetModificationCommandBatchFactory.cs b/src/EFCore.Jet.Common/Update/Internal/JetModificationCommandBatchFactory.cs
index 3c02db71a..bc0ab3a42 100644
--- a/src/EFCore.Jet.Common/Update/Internal/JetModificationCommandBatchFactory.cs
+++ b/src/EFCore.Jet.Common/Update/Internal/JetModificationCommandBatchFactory.cs
@@ -47,4 +47,4 @@ public JetModificationCommandBatchFactory(
public virtual ModificationCommandBatch Create()
=> new JetModificationCommandBatch(_dependencies);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/Update/Internal/JetUpdateSqlGenerator.cs b/src/EFCore.Jet.Common/Update/Internal/JetUpdateSqlGenerator.cs
index 4eacf1ffd..80902cce1 100644
--- a/src/EFCore.Jet.Common/Update/Internal/JetUpdateSqlGenerator.cs
+++ b/src/EFCore.Jet.Common/Update/Internal/JetUpdateSqlGenerator.cs
@@ -1,10 +1,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using EntityFrameworkCore.Jet.Metadata;
+using EntityFrameworkCore.Jet.Utilities;
using System.Data;
using System.Globalization;
using System.Text;
-using EntityFrameworkCore.Jet.Metadata;
-using EntityFrameworkCore.Jet.Utilities;
namespace EntityFrameworkCore.Jet.Update.Internal
{
@@ -225,7 +225,7 @@ public override ResultSetMapping AppendStoredProcedureCall(
}
Check.DebugAssert(
- storedProcedure.Parameters.Any() || storedProcedure.ResultColumns.Any(),
+ storedProcedure.Parameters.Count > 0 || storedProcedure.ResultColumns.Any(),
"Stored procedure call with neither parameters nor result columns");
commandStringBuilder.Append("EXEC ");
@@ -246,7 +246,7 @@ public override ResultSetMapping AppendStoredProcedureCall(
SqlGenerationHelper.DelimitIdentifier(commandStringBuilder, storedProcedure.Name, storedProcedure.Schema);
- if (storedProcedure.Parameters.Any())
+ if (storedProcedure.Parameters.Count > 0)
{
commandStringBuilder.Append(' ');
@@ -296,4 +296,4 @@ public override ResultSetMapping AppendStoredProcedureCall(
return resultSetMapping;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Common/ValueGeneration/JetSequentialGuidValueGenerator.cs b/src/EFCore.Jet.Common/ValueGeneration/JetSequentialGuidValueGenerator.cs
index 2cff99ce5..a68fbcf20 100644
--- a/src/EFCore.Jet.Common/ValueGeneration/JetSequentialGuidValueGenerator.cs
+++ b/src/EFCore.Jet.Common/ValueGeneration/JetSequentialGuidValueGenerator.cs
@@ -58,4 +58,4 @@ public override Guid Next(EntityEntry entry)
///
public override bool GeneratesTemporaryValues
=> false;
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Data/AdoxDatabaseCreator.cs b/src/EFCore.Jet.Data/AdoxDatabaseCreator.cs
index ac2ba6ad9..8c9eff79c 100644
--- a/src/EFCore.Jet.Data/AdoxDatabaseCreator.cs
+++ b/src/EFCore.Jet.Data/AdoxDatabaseCreator.cs
@@ -1,5 +1,9 @@
-using System;
using EntityFrameworkCore.Jet.Data.JetStoreSchemaDefinition;
+using System;
+
+// The enums nested below are ADO type-library enums transcribed name-for-name and value-for-value, so a
+// caller can match them against the library's own documentation. CA1711 objects to their "Enum" suffix.
+#pragma warning disable CA1711
namespace EntityFrameworkCore.Jet.Data
{
@@ -17,11 +21,11 @@ public override void CreateDatabase(
{
throw new ArgumentOutOfRangeException(nameof(databasePassword));
}
-
+
var filePath = JetStoreDatabaseHandling.ExpandFileName(JetStoreDatabaseHandling.ExtractFileNameFromConnectionString(fileNameOrConnectionString));
-
+
if (version == DatabaseVersion.NewestSupported &&
- string.Equals(System.IO.Path.GetExtension(filePath), ".mdb"))
+ string.Equals(System.IO.Path.GetExtension(filePath), ".mdb", StringComparison.OrdinalIgnoreCase))
{
version = DatabaseVersion.Version40;
}
@@ -29,17 +33,17 @@ public override void CreateDatabase(
try
{
using dynamic catalog = new ComObject("ADOX.Catalog");
-
+
// ADOX is an ADO eXtension and ADO is build on top of OLE DB.
var connectionString = GetConnectionString(filePath, version, collatingOrder, databasePassword);
using var connection = catalog.Create(connectionString);
}
catch (Exception e)
{
- throw new Exception($"Cannot create database \"{filePath}\" using DAO.", e);
+ throw new InvalidOperationException($"Cannot create database \"{filePath}\" using ADOX.", e);
}
}
-
+
private static string GetConnectionString(string filePath, DatabaseVersion version, CollatingOrder collatingOrder, string? databasePassword)
{
var connectionString = JetConnection.GetConnectionString(filePath, DataAccessProviderType.OleDb);
@@ -72,7 +76,7 @@ private static string GetConnectionString(string filePath, DatabaseVersion versi
return connectionString;
}
-
+
[Flags]
protected enum CommandTypeEnum
{
@@ -84,7 +88,7 @@ protected enum CommandTypeEnum
adCmdFile = 0x00000100,
adCmdTableDirect = 0x00000200,
}
-
+
[Flags]
protected enum ExecuteOptionEnum
{
diff --git a/src/EFCore.Jet.Data/AdoxSchema.cs b/src/EFCore.Jet.Data/AdoxSchema.cs
index 8d567965d..8aa59148e 100644
--- a/src/EFCore.Jet.Data/AdoxSchema.cs
+++ b/src/EFCore.Jet.Data/AdoxSchema.cs
@@ -1,10 +1,15 @@
+using EntityFrameworkCore.Jet.Data.JetStoreSchemaDefinition;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
-using EntityFrameworkCore.Jet.Data.JetStoreSchemaDefinition;
using System.Diagnostics;
+// The enums nested below are ADO/ADOX type-library enums (RuleEnum, DataTypeEnum, …) transcribed
+// name-for-name and value-for-value, so a caller can match them against the library's own documentation.
+// CA1711 objects to the "Enum" suffix those names carry.
+#pragma warning disable CA1711
+
namespace EntityFrameworkCore.Jet.Data
{
public class AdoxSchema : SchemaProvider
@@ -599,7 +604,7 @@ public override void EnsureDualTable()
}
catch (Exception e)
{
- throw new Exception($"Cannot create dual table '{JetConnection.DefaultDualTableName}' using ADOX.", e);
+ throw new InvalidOperationException($"Cannot create dual table '{JetConnection.DefaultDualTableName}' using ADOX.", e);
}
}
}
@@ -620,7 +625,7 @@ public override void RenameTable(string oldTableName, string newTableName)
catch (Exception e)
{
// TODO: Try interating over the collections instead of using Item["Name"].
- throw new Exception($"Cannot rename table '{oldTableName}' to '{newTableName}'.", e);
+ throw new InvalidOperationException($"Cannot rename table '{oldTableName}' to '{newTableName}'.", e);
}
}
@@ -644,7 +649,7 @@ public override void RenameColumn(string tableName, string oldColumnName, string
catch (Exception e)
{
// TODO: Try interating over the collections instead of using Item["Name"].
- throw new Exception($"Cannot rename column '{oldColumnName}' to '{newColumnName}' of table '{tableName}'.", e);
+ throw new InvalidOperationException($"Cannot rename column '{oldColumnName}' to '{newColumnName}' of table '{tableName}'.", e);
}
}
@@ -720,7 +725,7 @@ private static string GetDataTypeString(DataTypeEnum dataType, bool isIdentity =
case DataTypeEnum.adPropVariant:
case DataTypeEnum.adVarNumeric:
default:
- throw new ArgumentOutOfRangeException(nameof(dataType), $"Could not map a data type of '{Enum.GetName(typeof(DataTypeEnum), dataType)}'.");
+ throw new ArgumentOutOfRangeException(nameof(dataType), $"Could not map a data type of '{Enum.GetName(dataType)}'.");
}
}
@@ -783,6 +788,7 @@ public override void Dispose()
{
_connection.Dispose();
_catalog.Dispose();
+ GC.SuppressFinalize(this);
}
protected enum RuleEnum
diff --git a/src/EFCore.Jet.Data/ComObject.cs b/src/EFCore.Jet.Data/ComObject.cs
index 0768e2e85..5cd986b45 100644
--- a/src/EFCore.Jet.Data/ComObject.cs
+++ b/src/EFCore.Jet.Data/ComObject.cs
@@ -1,5 +1,6 @@
using System;
using System.Dynamic;
+using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
@@ -7,11 +8,14 @@ namespace EntityFrameworkCore.Jet.Data
{
// A small wrapper around COM interop to make it more easy to use.
// See https://github.com/dotnet/runtime/issues/12587#issuecomment-534611966
- internal class ComObject : DynamicObject, IDisposable
+ internal sealed class ComObject : DynamicObject, IDisposable
{
private object _instance;
#if DEBUG
+ // Read only in a debugger, to tell two wrappers of the same COM object apart.
+#pragma warning disable IDE0052
private readonly Guid _trackingId = Guid.NewGuid();
+#pragma warning restore IDE0052
#endif
public static ComObject CreateFirstFrom(params string[] progids)
@@ -28,10 +32,10 @@ public static ComObject CreateFirstFrom(params string[] progids)
}
}
}
-
+
throw new TypeLoadException("Could not create an instance using any of the supplied ProgIDs.");
}
-
+
public static ComObject CreateFirstFrom(params Guid[] clsids)
{
foreach (var clsid in clsids)
@@ -46,7 +50,7 @@ public static ComObject CreateFirstFrom(params Guid[] clsids)
}
}
}
-
+
throw new TypeLoadException("Could not create an instance using any of the supplied CLSIDs.");
}
@@ -56,7 +60,7 @@ public ComObject(object instance)
{
throw new ArgumentException("The object is already a ComObject.", nameof(instance));
}
-
+
_instance = instance;
}
@@ -86,7 +90,8 @@ public override bool TryGetMember(GetMemberBinder binder, out object? result)
BindingFlags.GetProperty,
Type.DefaultBinder,
_instance,
- []
+ [],
+ CultureInfo.InvariantCulture
));
return true;
}
@@ -103,7 +108,8 @@ public override bool TrySetMember(SetMemberBinder binder, object? value)
value is ComObject comObject
? comObject._instance
: value
- ]
+ ],
+ CultureInfo.InvariantCulture
);
return true;
}
@@ -117,7 +123,8 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args,
BindingFlags.InvokeMethod,
Type.DefaultBinder,
_instance,
- args
+ args,
+ CultureInfo.InvariantCulture
));
return true;
}
@@ -132,7 +139,8 @@ public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out ob
BindingFlags.GetProperty,
Type.DefaultBinder,
_instance,
- indexes
+ indexes,
+ CultureInfo.InvariantCulture
));
return true;
}
diff --git a/src/EFCore.Jet.Data/ConnectionPooling/ConnectionSet.cs b/src/EFCore.Jet.Data/ConnectionPooling/ConnectionSet.cs
index 8ab5a54b3..db9484705 100644
--- a/src/EFCore.Jet.Data/ConnectionPooling/ConnectionSet.cs
+++ b/src/EFCore.Jet.Data/ConnectionPooling/ConnectionSet.cs
@@ -3,7 +3,7 @@
namespace EntityFrameworkCore.Jet.Data.ConnectionPooling
{
- class ConnectionSet(string connectionString) : IDisposable
+ sealed class ConnectionSet(string connectionString) : IDisposable
{
public string ConnectionString { get; } = connectionString;
@@ -12,7 +12,7 @@ class ConnectionSet(string connectionString) : IDisposable
public void AddConnection(DbConnection connection)
{
- lock(_connections)
+ lock (_connections)
{
if (ConnectionCount == _connections.Length)
{
diff --git a/src/EFCore.Jet.Data/ConnectionPooling/ConnectionSetCollection.cs b/src/EFCore.Jet.Data/ConnectionPooling/ConnectionSetCollection.cs
index c708f11c9..a359d8a8c 100644
--- a/src/EFCore.Jet.Data/ConnectionPooling/ConnectionSetCollection.cs
+++ b/src/EFCore.Jet.Data/ConnectionPooling/ConnectionSetCollection.cs
@@ -1,13 +1,8 @@
-using System;
-using System.Collections.Generic;
using System.Collections.ObjectModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace EntityFrameworkCore.Jet.Data.ConnectionPooling
{
- class ConnectionSetCollection : KeyedCollection
+ sealed class ConnectionSetCollection : KeyedCollection
{
protected override string GetKeyForItem(ConnectionSet item)
{
@@ -15,7 +10,7 @@ protected override string GetKeyForItem(ConnectionSet item)
}
// TryGetValue has been added in .NET Core 2.0.
- #pragma warning disable 109
+#pragma warning disable 109
public new bool TryGetValue(string key, out ConnectionSet? connectionSet)
{
try
@@ -29,6 +24,6 @@ protected override string GetKeyForItem(ConnectionSet item)
return false;
}
}
- #pragma warning restore 109
+#pragma warning restore 109
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Data/DaoDatabaseCreator.cs b/src/EFCore.Jet.Data/DaoDatabaseCreator.cs
index 1f4ebb574..a3626a554 100644
--- a/src/EFCore.Jet.Data/DaoDatabaseCreator.cs
+++ b/src/EFCore.Jet.Data/DaoDatabaseCreator.cs
@@ -1,7 +1,12 @@
-using System;
using EntityFrameworkCore.Jet.Data.JetStoreSchemaDefinition;
+using System;
using System.Linq;
+// The enums nested below are DAO type-library enums transcribed name-for-name and value-for-value, so a
+// caller can match them against the library's own documentation. CA1711 objects to the "Enum" suffix those
+// names carry, and CA1069 to the members DAO gives the same value (dbSortPDXNor/dbSortNorwdan and friends).
+#pragma warning disable CA1711, CA1069
+
namespace EntityFrameworkCore.Jet.Data
{
public class DaoDatabaseCreator
@@ -19,9 +24,9 @@ public override void CreateDatabase(
}
var filePath = JetStoreDatabaseHandling.ExpandFileName(JetStoreDatabaseHandling.ExtractFileNameFromConnectionString(fileNameOrConnectionString));
-
+
if (version == DatabaseVersion.NewestSupported &&
- string.Equals(System.IO.Path.GetExtension(filePath), ".mdb"))
+ string.Equals(System.IO.Path.GetExtension(filePath), ".mdb", StringComparison.OrdinalIgnoreCase))
{
version = DatabaseVersion.Version40;
}
@@ -32,16 +37,16 @@ public override void CreateDatabase(
var databaseType = version switch
{
- DatabaseVersion.Version10 => (int) DatabaseTypeEnum.dbVersion10,
- DatabaseVersion.Version11 => (int) DatabaseTypeEnum.dbVersion11,
- DatabaseVersion.Version20 => (int) DatabaseTypeEnum.dbVersion20,
- DatabaseVersion.Version30 => (int) DatabaseTypeEnum.dbVersion30,
- DatabaseVersion.Version40 => (int) DatabaseTypeEnum.dbVersion40,
- DatabaseVersion.Version120 => (int) DatabaseTypeEnum.dbVersion120,
+ DatabaseVersion.Version10 => (int)DatabaseTypeEnum.dbVersion10,
+ DatabaseVersion.Version11 => (int)DatabaseTypeEnum.dbVersion11,
+ DatabaseVersion.Version20 => (int)DatabaseTypeEnum.dbVersion20,
+ DatabaseVersion.Version30 => (int)DatabaseTypeEnum.dbVersion30,
+ DatabaseVersion.Version40 => (int)DatabaseTypeEnum.dbVersion40,
+ DatabaseVersion.Version120 => (int)DatabaseTypeEnum.dbVersion120,
_ => 0,
};
- var daoCollatingOrder = (CollatingOrderEnum) collatingOrder;
+ var daoCollatingOrder = (CollatingOrderEnum)collatingOrder;
var collatingOrderString = daoCollatingOrder switch
{
CollatingOrderEnum.dbSortArabic => ";LANGID=0x0401;CP=1256;COUNTRY=0",
@@ -80,10 +85,12 @@ public override void CreateDatabase(
}
catch (Exception e)
{
- throw new Exception($"Cannot create database \"{filePath}\" using DAO.", e);
+ throw new InvalidOperationException($"Cannot create database \"{filePath}\" using DAO.", e);
}
}
-
+
+ private static readonly int[] Dao36Only = [36];
+
private static dynamic CreateDbEngine()
{
var progids = Enumerable.Range(12, 6)
@@ -92,7 +99,7 @@ private static dynamic CreateDbEngine()
.Concat(
Environment.Is64BitProcess
? []
- : new[] {36}) // DAO 3.6 is only available as an x86 library
+ : Dao36Only) // DAO 3.6 is only available as an x86 library
.Select(n => "DAO.DBEngine." + n)
.ToArray();
return ComObject.CreateFirstFrom(progids);
@@ -143,7 +150,7 @@ protected enum DatabaseTypeEnum
dbVersion40 = 0x00000040,
dbVersion120 = 0x00000080
}
-
+
[Flags]
protected enum TableDefAttributeEnum
{
diff --git a/src/EFCore.Jet.Data/DaoSchema.cs b/src/EFCore.Jet.Data/DaoSchema.cs
index cdedc6c76..ac1b9f535 100644
--- a/src/EFCore.Jet.Data/DaoSchema.cs
+++ b/src/EFCore.Jet.Data/DaoSchema.cs
@@ -1,10 +1,16 @@
+using EntityFrameworkCore.Jet.Data.JetStoreSchemaDefinition;
using System;
using System.Collections.Generic;
using System.Data;
-using EntityFrameworkCore.Jet.Data.JetStoreSchemaDefinition;
using System.Diagnostics;
+using System.Globalization;
using System.Linq;
+// The enums nested below are DAO type-library enums transcribed name-for-name and value-for-value, so a
+// caller can match them against the library's own documentation. CA1711 objects to the "Enum" suffix those
+// names carry, and CA1069 to the members DAO gives the same value (dbFixedField/dbDescending).
+#pragma warning disable CA1711, CA1069
+
namespace EntityFrameworkCore.Jet.Data
{
public class DaoSchema : SchemaProvider
@@ -29,7 +35,7 @@ public DaoSchema(JetConnection connection, bool readOnly)
Enumerable.Range(12, 6)
.Reverse()
.Concat([36])
- .Select(n => "DAO.DBEngine." + (n * 10).ToString())
+ .Select(n => "DAO.DBEngine." + (n * 10).ToString(CultureInfo.InvariantCulture))
.ToArray());
try
@@ -525,7 +531,7 @@ public override void RenameTable(string oldTableName, string newTableName)
catch (Exception e)
{
// TODO: Try interating over the collections instead of using Item["Name"].
- throw new Exception($"Cannot rename table '{oldTableName}' to '{newTableName}'.", e);
+ throw new InvalidOperationException($"Cannot rename table '{oldTableName}' to '{newTableName}'.", e);
}
}
@@ -549,7 +555,7 @@ public override void RenameColumn(string tableName, string oldColumnName, string
catch (Exception e)
{
// TODO: Try interating over the collections instead of using Item["Name"].
- throw new Exception($"Cannot rename column '{oldColumnName}' to '{newColumnName}' of table '{tableName}'.", e);
+ throw new InvalidOperationException($"Cannot rename column '{oldColumnName}' to '{newColumnName}' of table '{tableName}'.", e);
}
}
@@ -714,6 +720,7 @@ public override void Dispose()
_database.Dispose();
_workspace.Dispose();
_dbEngine.Dispose();
+ GC.SuppressFinalize(this);
}
[Flags]
diff --git a/src/EFCore.Jet.Data/DbColumnFlagEnum.cs b/src/EFCore.Jet.Data/DbColumnFlagEnum.cs
index 91ee99fd3..1c35cb805 100644
--- a/src/EFCore.Jet.Data/DbColumnFlagEnum.cs
+++ b/src/EFCore.Jet.Data/DbColumnFlagEnum.cs
@@ -1,7 +1,4 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
namespace EntityFrameworkCore.Jet.Data
{
@@ -21,4 +18,4 @@ enum DbColumnFlag
IsRowVer = 512,
CachedDeferred = 4096
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Data/DbConnectionStringBuilderExtensions.cs b/src/EFCore.Jet.Data/DbConnectionStringBuilderExtensions.cs
index 35b8e0e86..8c5d72de1 100644
--- a/src/EFCore.Jet.Data/DbConnectionStringBuilderExtensions.cs
+++ b/src/EFCore.Jet.Data/DbConnectionStringBuilderExtensions.cs
@@ -47,7 +47,7 @@ public static void SetProvider(this DbConnectionStringBuilder builder, string va
else
throw new InvalidOperationException("This extension method only supports OdbcConnectionStringBuilder and OleDbConnectionStringBuilder.");
}
-
+
public static string? GetDataSource(this DbConnectionStringBuilder builder, DataAccessProviderType providerType = DataAccessProviderType.Unconfigured)
{
if (providerType == DataAccessProviderType.OleDb ||
@@ -88,7 +88,7 @@ public static void SetDataSource(this DbConnectionStringBuilder builder, string
else
throw new InvalidOperationException("This extension method only supports OdbcConnectionStringBuilder and OleDbConnectionStringBuilder.");
}
-
+
public static string? GetUserId(this DbConnectionStringBuilder builder, DataAccessProviderType providerType = DataAccessProviderType.Unconfigured)
{
if (IsOleDb(builder))
@@ -128,7 +128,7 @@ public static void SetUserId(this DbConnectionStringBuilder builder, string valu
else
throw new InvalidOperationException("This extension method only supports OdbcConnectionStringBuilder and OleDbConnectionStringBuilder.");
}
-
+
public static string? GetPassword(this DbConnectionStringBuilder builder, DataAccessProviderType providerType = DataAccessProviderType.Unconfigured)
{
if (providerType == DataAccessProviderType.OleDb ||
@@ -149,16 +149,16 @@ public static void SetUserId(this DbConnectionStringBuilder builder, string valu
{
return !string.IsNullOrEmpty(builder.GetSystemDatabase(providerType))
? builder.TryGetValue("PWD", out var value)
- ? (string) value
+ ? (string)value
: null
: null;
}
return builder.TryGetValue("Password", out var oleDbValue)
- ? (string) oleDbValue
+ ? (string)oleDbValue
: !string.IsNullOrEmpty(builder.GetSystemDatabase(providerType))
? builder.TryGetValue("PWD", out var odbcValue)
- ? (string) odbcValue
+ ? (string)odbcValue
: null
: null;
}
@@ -178,7 +178,7 @@ public static void SetPassword(this DbConnectionStringBuilder builder, string va
else
throw new InvalidOperationException("This extension method only supports OdbcConnectionStringBuilder and OleDbConnectionStringBuilder.");
}
-
+
public static string? GetSystemDatabase(this DbConnectionStringBuilder builder, DataAccessProviderType providerType = DataAccessProviderType.Unconfigured)
{
if (providerType == DataAccessProviderType.OleDb ||
@@ -240,16 +240,16 @@ public static void SetSystemDatabase(this DbConnectionStringBuilder builder, str
{
return string.IsNullOrEmpty(builder.GetSystemDatabase(providerType))
? builder.TryGetValue("PWD", out var value)
- ? (string) value
+ ? (string)value
: null
: null;
}
return builder.TryGetValue("Jet OLEDB:Database Password", out var oleDbValue)
- ? (string) oleDbValue
+ ? (string)oleDbValue
: string.IsNullOrEmpty(builder.GetSystemDatabase(providerType))
? builder.TryGetValue("PWD", out var odbcValue)
- ? (string) odbcValue
+ ? (string)odbcValue
: null
: null;
}
@@ -269,13 +269,13 @@ public static void SetDatabasePassword(this DbConnectionStringBuilder builder, s
else
throw new InvalidOperationException("This extension method only supports OdbcConnectionStringBuilder and OleDbConnectionStringBuilder.");
}
-
+
private static bool IsOdbc(DbConnectionStringBuilder builder)
{
return builder
.GetType()
.GetTypesInHierarchy()
- .Any(t => string.Equals(t.FullName, "System.Data.Odbc.OdbcConnectionStringBuilder"));
+ .Any(t => string.Equals(t.FullName, "System.Data.Odbc.OdbcConnectionStringBuilder", StringComparison.Ordinal));
}
private static bool IsOleDb(DbConnectionStringBuilder builder)
@@ -283,7 +283,7 @@ private static bool IsOleDb(DbConnectionStringBuilder builder)
return builder
.GetType()
.GetTypesInHierarchy()
- .Any(t => string.Equals(t.FullName, "System.Data.OleDb.OleDbConnectionStringBuilder"));
+ .Any(t => string.Equals(t.FullName, "System.Data.OleDb.OleDbConnectionStringBuilder", StringComparison.Ordinal));
}
}
}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Data/EFCore.Jet.Data.csproj b/src/EFCore.Jet.Data/EFCore.Jet.Data.csproj
index 0370df0b2..516591570 100644
--- a/src/EFCore.Jet.Data/EFCore.Jet.Data.csproj
+++ b/src/EFCore.Jet.Data/EFCore.Jet.Data.csproj
@@ -8,6 +8,8 @@
EntityFrameworkCore.Jet.Data$(PackageTags);System.Data.Jetannotations
+
+ en
diff --git a/src/EFCore.Jet.Data/IJetDatabaseCreator.cs b/src/EFCore.Jet.Data/IJetDatabaseCreator.cs
index 8c8dffbc1..fefd971f2 100644
--- a/src/EFCore.Jet.Data/IJetDatabaseCreator.cs
+++ b/src/EFCore.Jet.Data/IJetDatabaseCreator.cs
@@ -1,3 +1,8 @@
+// The constants below are DAO's own CollatingOrderEnum, transcribed value-for-value. Several of its names
+// share a value (PDXNor/Norwdan, PDXIntl/General, SwedFin/PDXSwe), which CA1069 reads as duplicates; keeping
+// DAO's spelling is what lets a caller match these against the type library and Access's documentation.
+#pragma warning disable CA1069
+
namespace EntityFrameworkCore.Jet.Data
{
public enum CollatingOrder
@@ -40,7 +45,7 @@ public enum DatabaseVersion
Version40 = 40,
Version120 = 120
}
-
+
public interface IJetDatabaseCreator
{
void CreateDatabase(
diff --git a/src/EFCore.Jet.Data/InnerConnectionFactory.cs b/src/EFCore.Jet.Data/InnerConnectionFactory.cs
index 393ed05b1..d5357a0c6 100644
--- a/src/EFCore.Jet.Data/InnerConnectionFactory.cs
+++ b/src/EFCore.Jet.Data/InnerConnectionFactory.cs
@@ -1,11 +1,11 @@
+using EntityFrameworkCore.Jet.Data.ConnectionPooling;
using System;
using System.Data;
using System.Data.Common;
-using EntityFrameworkCore.Jet.Data.ConnectionPooling;
namespace EntityFrameworkCore.Jet.Data
{
- class InnerConnectionFactory : IDisposable
+ sealed class InnerConnectionFactory : IDisposable
{
public static readonly InnerConnectionFactory Instance = new();
@@ -24,7 +24,7 @@ public DbConnection OpenConnection(string? connectionString, DbProviderFactory d
var connection = dataAccessProviderFactory.CreateConnection();
connection.ConnectionString = connectionString;
connection.Open();
-
+
return connection;
}
@@ -37,7 +37,7 @@ public DbConnection OpenConnection(string? connectionString, DbProviderFactory d
var connection = dataAccessProviderFactory.CreateConnection();
connection.ConnectionString = connectionString;
connection.Open();
-
+
return connection;
}
diff --git a/src/EFCore.Jet.Data/JetCommand.cs b/src/EFCore.Jet.Data/JetCommand.cs
index 426140aa3..1e5ebe049 100644
--- a/src/EFCore.Jet.Data/JetCommand.cs
+++ b/src/EFCore.Jet.Data/JetCommand.cs
@@ -1,9 +1,10 @@
+using EntityFrameworkCore.Jet.Data.JetStoreSchemaDefinition;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
-using EntityFrameworkCore.Jet.Data.JetStoreSchemaDefinition;
+using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
@@ -283,8 +284,7 @@ protected virtual int ExecuteNonQueryCore()
if (InnerCommand.CommandType != CommandType.Text)
return InnerCommand.ExecuteNonQuery();
- if (_selectRowCountRegularExpression.Match(InnerCommand.CommandText)
- .Success)
+ if (_selectRowCountRegularExpression.IsMatch(InnerCommand.CommandText))
{
return _connection.RowCount;
}
@@ -293,7 +293,7 @@ protected virtual int ExecuteNonQueryCore()
if (!CheckExists(InnerCommand.CommandText, out var newCommandText))
return 0;
- bool isexistssql = newCommandText != InnerCommand.CommandText && (InnerCommand.CommandText.StartsWith("IF EXISTS",StringComparison.OrdinalIgnoreCase) || InnerCommand.CommandText.StartsWith("IF NOT EXISTS", StringComparison.OrdinalIgnoreCase));
+ bool isexistssql = newCommandText != InnerCommand.CommandText && (InnerCommand.CommandText.StartsWith("IF EXISTS", StringComparison.OrdinalIgnoreCase) || InnerCommand.CommandText.StartsWith("IF NOT EXISTS", StringComparison.OrdinalIgnoreCase));
InnerCommand.CommandText = newCommandText;
@@ -396,7 +396,7 @@ protected virtual IList SplitCommands()
{
//Remove any tag lines from the sql created by ef core. Jet doesn't like comments/tags in the SQL
var lines = CommandText.Split([Environment.NewLine], StringSplitOptions.None);
- var filteredLines = lines.Where(line => !line.TrimStart().StartsWith("--"));
+ var filteredLines = lines.Where(line => !line.TrimStart().StartsWith("--", StringComparison.Ordinal));
CommandText = string.Join(Environment.NewLine, filteredLines).TrimStart();
// At this point, all parameters have already been expanded.
@@ -473,10 +473,9 @@ protected virtual IList SplitCommands()
return commands;
}
- private DbDataReader TryGetDataReaderForSelectRowCount(string commandText)
+ private DataTableReader? TryGetDataReaderForSelectRowCount(string commandText)
{
- if (_selectRowCountRegularExpression.Match(commandText)
- .Success)
+ if (_selectRowCountRegularExpression.IsMatch(commandText))
{
var dataTable = new DataTable("Rowcount");
dataTable.Columns.Add("ROWCOUNT", typeof(int));
@@ -553,7 +552,7 @@ protected virtual string FixupIdentity(string commandText)
command.CommandText = $"SELECT {placeholder}";
command.Parameters.Clear();
- var identityValue = Convert.ToInt32(command.ExecuteScalar());
+ var identityValue = Convert.ToInt32(command.ExecuteScalar(), CultureInfo.InvariantCulture);
LogHelper.ShowInfo($"{placeholder} = {identityValue}");
@@ -600,10 +599,10 @@ private void InlineTopParameters()
{
var first = match.Groups["first"];
var sec = match.Groups["sec"];
- var sp = Convert.ToInt32(ExtractParameter(commandText, sec.Index, parameters).Value);
- var fp = Convert.ToInt32(ExtractParameter(commandText, first.Index, parameters).Value);
+ var sp = Convert.ToInt32(ExtractParameter(commandText, sec.Index, parameters).Value, CultureInfo.InvariantCulture);
+ var fp = Convert.ToInt32(ExtractParameter(commandText, first.Index, parameters).Value, CultureInfo.InvariantCulture);
var total = fp + sp;
- return total.ToString();
+ return total.ToString(CultureInfo.InvariantCulture);
},
1)) != lastCommandText)
{
@@ -614,8 +613,8 @@ private void InlineTopParameters()
lastCommandText,
match => Convert.ToInt32(
ExtractParameter(commandText, match.Index, parameters)
- .Value)
- .ToString(),
+ .Value, CultureInfo.InvariantCulture)
+ .ToString(CultureInfo.InvariantCulture),
1)) != lastCommandText)
{
lastCommandText = commandText;
@@ -630,7 +629,8 @@ private void InlineTopParameters()
{
var first = match.Groups["first"];
var sec = match.Groups["sec"];
- return (Convert.ToInt32(first.Value) + Convert.ToInt32(sec.Value)).ToString();
+ return (Convert.ToInt32(first.Value, CultureInfo.InvariantCulture)
+ + Convert.ToInt32(sec.Value, CultureInfo.InvariantCulture)).ToString(CultureInfo.InvariantCulture);
},
1)) != lastCommandText)
{
@@ -651,7 +651,8 @@ private void ModifyOuterSelectTopValueForOuterSelectSkipEmulationViaDataReader()
{
InnerCommand.CommandText = _outerSelectTopValueRegularExpression.Replace(
InnerCommand.CommandText,
- match => (int.Parse(match.Value) + _outerSelectSkipEmulationViaDataReaderSkipCount).ToString());
+ match => (int.Parse(match.Value, CultureInfo.InvariantCulture)
+ + _outerSelectSkipEmulationViaDataReaderSkipCount).ToString(CultureInfo.InvariantCulture));
}
}
@@ -681,21 +682,21 @@ private void PrepareOuterSelectSkipEmulationViaDataReader()
}
var parameter = ExtractParameter(InnerCommand.CommandText, match.Index, parameters);
- _outerSelectSkipEmulationViaDataReaderSkipCount = Convert.ToInt32(parameter.Value);
+ _outerSelectSkipEmulationViaDataReaderSkipCount = Convert.ToInt32(parameter.Value, CultureInfo.InvariantCulture);
InnerCommand.Parameters.Clear();
InnerCommand.Parameters.AddRange(parameters.ToArray());
}
else
{
- _outerSelectSkipEmulationViaDataReaderSkipCount = int.Parse(skipValueOrParameter.Value);
+ _outerSelectSkipEmulationViaDataReaderSkipCount = int.Parse(skipValueOrParameter.Value, CultureInfo.InvariantCulture);
}
InnerCommand.CommandText = InnerCommand.CommandText.Remove(match.Index, match.Length);
}
protected virtual bool IsParameter(string fragment)
- => fragment.Equals("?") ||
+ => fragment.Equals("?", StringComparison.Ordinal) ||
fragment.Length >= 2 && fragment[0] == '@' && fragment[1] != '@';
protected virtual DbParameter ExtractParameter(string commandText, int count, List parameters)
@@ -724,7 +725,7 @@ protected virtual void ExpandParameters()
var placeholders = GetParameterPlaceholders(InnerCommand.CommandText, indices);
- if (placeholders.All(t => t.Name.StartsWith("@")))
+ if (placeholders.All(t => t.Name.StartsWith('@')))
{
MatchParametersAndPlaceholders(placeholders);
diff --git a/src/EFCore.Jet.Data/JetConfiguration.cs b/src/EFCore.Jet.Data/JetConfiguration.cs
index 5c4a4cc59..ca150427f 100644
--- a/src/EFCore.Jet.Data/JetConfiguration.cs
+++ b/src/EFCore.Jet.Data/JetConfiguration.cs
@@ -32,16 +32,16 @@ public static object IntegerNullValue
_integerNullValue = value;
}
}
-
- public static DataAccessProviderType DefaultDataAccessProviderType { get; set; } = DataAccessProviderType.Odbc;
-
+
+ public static DataAccessProviderType DefaultDataAccessProviderType { get; set; } = DataAccessProviderType.Odbc;
+
///
/// Gets or sets a value indicating whether show SQL statements.
///
///
/// true to show SQL statements; otherwise, false.
///
- public static bool ShowSqlStatements { get; set; } = false;
+ public static bool ShowSqlStatements { get; set; }
///
/// Gets or sets a value indicating whether the connection pooling should be used
@@ -49,7 +49,7 @@ public static object IntegerNullValue
///
/// true to use the connection pooling; otherwise, false.
///
- public static bool UseConnectionPooling { get; set; } = false;
+ public static bool UseConnectionPooling { get; set; }
///
/// Gets or sets a value indicating whether to return a default value for the column
@@ -58,6 +58,6 @@ public static object IntegerNullValue
///
/// true to return a default value; otherwise, false.
///
- public static bool UseDefaultValueOnDBNullConversionError { get; set; } = false;
+ public static bool UseDefaultValueOnDBNullConversionError { get; set; }
}
}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Data/JetConnection.cs b/src/EFCore.Jet.Data/JetConnection.cs
index ddcc0eeb6..2a8736a60 100644
--- a/src/EFCore.Jet.Data/JetConnection.cs
+++ b/src/EFCore.Jet.Data/JetConnection.cs
@@ -1,15 +1,13 @@
+using EntityFrameworkCore.Jet.Data.JetStoreSchemaDefinition;
+using Microsoft.Win32;
using System;
using System.Collections.Generic;
-using System.ComponentModel.Design;
using System.Data;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
-using EntityFrameworkCore.Jet.Data.JetStoreSchemaDefinition;
using System.IO;
using System.Linq;
-using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
-using Microsoft.Win32;
namespace EntityFrameworkCore.Jet.Data
{
@@ -85,7 +83,7 @@ public JetConnection(string? fileNameOrConnectionString, DbProviderFactory? data
public bool IgnoreMsys => _ignoreMSys;
///
- /// Gets the for this .
+ /// Gets the for this .
///
protected override DbProviderFactory DbProviderFactory => JetFactory;
@@ -207,10 +205,10 @@ public override int ConnectionTimeout
=> 0;
///
- /// Creates and returns a object associated with the current connection.
+ /// Creates and returns a object associated with the current connection.
///
///
- /// A object.
+ /// A object.
///
protected override DbCommand CreateDbCommand()
{
@@ -232,7 +230,7 @@ public override string DataSource
=> JetStoreDatabaseHandling.ExtractFileNameFromConnectionString(_connectionString);
///
- /// Releases the unmanaged resources used by the and optionally releases the managed resources.
+ /// Releases the unmanaged resources used by the and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
@@ -248,7 +246,7 @@ protected override void Dispose(bool disposing)
///
/// Enlists in the specified transaction.
///
- /// A reference to an existing in which to enlist.
+ /// A reference to an existing in which to enlist.
public override void EnlistTransaction(System.Transactions.Transaction? transaction)
{
if (InnerConnection == null)
@@ -257,11 +255,11 @@ public override void EnlistTransaction(System.Transactions.Transaction? transact
}
///
- /// Returns schema information for the data source of this using the specified string for the schema name.
+ /// Returns schema information for the data source of this using the specified string for the schema name.
///
/// Specifies the name of the schema to return.
///
- /// A that contains schema information.
+ /// A that contains schema information.
///
///
///
@@ -276,10 +274,10 @@ public override DataTable GetSchema(string collectionName)
}
///
- /// Returns schema information for the data source of this .
+ /// Returns schema information for the data source of this .
///
///
- /// A that contains schema information.
+ /// A that contains schema information.
///
///
///
@@ -294,12 +292,12 @@ public override DataTable GetSchema()
}
///
- /// Returns schema information for the data source of this using the specified string for the schema name and the specified string array for the restriction values.
+ /// Returns schema information for the data source of this using the specified string for the schema name and the specified string array for the restriction values.
///
/// Specifies the name of the schema to return.
/// Specifies a set of restriction values for the requested schema.
///
- /// A that contains schema information.
+ /// A that contains schema information.
///
///
///
@@ -314,7 +312,7 @@ public override DataTable GetSchema(string collectionName, string?[] restriction
}
///
- /// Opens a database connection with the settings specified by the .
+ /// Opens a database connection with the settings specified by the .
///
public override void Open()
{
@@ -373,7 +371,7 @@ public override void Open()
{
var provider = GetMostRecentCompatibleProviders(dataAccessProviderType)
.FirstOrDefault()
- .Key ?? throw new InvalidOperationException($"Unable to find any compatible {Enum.GetName(typeof(DataAccessProviderType), dataAccessProviderType)} provider for the connection string: {fileNameOrConnectionString}");
+ .Key ?? throw new InvalidOperationException($"Unable to find any compatible {Enum.GetName(dataAccessProviderType)} provider for the connection string: {fileNameOrConnectionString}");
connectionStringBuilder.SetProvider(provider, dataAccessProviderType);
}
@@ -578,7 +576,7 @@ public static string GetConnectionString(string fileNameOrConnectionString, Data
internal static string GetConnectionString(string fileNameOrConnectionString, DataAccessProviderType dataAccessProviderType, DbProviderFactory dataAccessProviderFactory)
{
- var provider = GetMostRecentCompatibleProviders(dataAccessProviderType).FirstOrDefault().Key ?? throw new InvalidOperationException($"Unable to find any compatible {Enum.GetName(typeof(DataAccessProviderType), dataAccessProviderType)} provider for the connection string: {fileNameOrConnectionString}");
+ var provider = GetMostRecentCompatibleProviders(dataAccessProviderType).FirstOrDefault().Key ?? throw new InvalidOperationException($"Unable to find any compatible {Enum.GetName(dataAccessProviderType)} provider for the connection string: {fileNameOrConnectionString}");
return IsConnectionString(fileNameOrConnectionString)
? ExpandDatabaseFilePath(fileNameOrConnectionString, dataAccessProviderFactory)
: GetConnectionString(
@@ -715,20 +713,20 @@ public static DataAccessProviderType GetDataAccessProviderType(string? connectio
case true when isOleDb:
throw new InvalidOperationException("The connection string appears to be for ODBC and OLE DB. Only one distinct style is supported at a time.");
case false when !isOleDb:
- {
- isOleDb = Regex.IsMatch(connectionString, @"^(?:.*;)?\s*Data Source\s*=", RegexOptions.IgnoreCase);
- isOdbc = Regex.IsMatch(connectionString, @"^(?:.*;)?\s*DBQ\s*=", RegexOptions.IgnoreCase);
-
- switch (isOdbc)
{
- case true when isOleDb:
- throw new InvalidOperationException("The connection string appears to be for ODBC and OLE DB. Only one distinct style is supported at a time.");
- case false when !isOleDb:
- throw new ArgumentException("The connection string appears to be neither ODBC nor OLE DB compliant.", nameof(connectionString));
- }
+ isOleDb = Regex.IsMatch(connectionString, @"^(?:.*;)?\s*Data Source\s*=", RegexOptions.IgnoreCase);
+ isOdbc = Regex.IsMatch(connectionString, @"^(?:.*;)?\s*DBQ\s*=", RegexOptions.IgnoreCase);
- break;
- }
+ switch (isOdbc)
+ {
+ case true when isOleDb:
+ throw new InvalidOperationException("The connection string appears to be for ODBC and OLE DB. Only one distinct style is supported at a time.");
+ case false when !isOleDb:
+ throw new ArgumentException("The connection string appears to be neither ODBC nor OLE DB compliant.", nameof(connectionString));
+ }
+
+ break;
+ }
}
return isOleDb
@@ -876,4 +874,4 @@ public static bool IsFileName(string? fileNameOrConnectionString)
[GeneratedRegex(@"^(?:.*;)?\s*Provider\s*=\s*\w+", RegexOptions.IgnoreCase, "en-AU")]
private static partial Regex IsOleDbRegex();
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Data/JetConnectionStringBuilder.cs b/src/EFCore.Jet.Data/JetConnectionStringBuilder.cs
index 32ed956c4..326d466fd 100644
--- a/src/EFCore.Jet.Data/JetConnectionStringBuilder.cs
+++ b/src/EFCore.Jet.Data/JetConnectionStringBuilder.cs
@@ -3,7 +3,11 @@
namespace EntityFrameworkCore.Jet.Data
{
+ // CA1010 asks for ICollection; the non-generic ICollection comes from DbConnectionStringBuilder, which
+ // every ADO.NET provider inherits as-is.
+#pragma warning disable CA1010
public class JetConnectionStringBuilder : DbConnectionStringBuilder
+#pragma warning restore CA1010
{
private readonly DbConnectionStringBuilder _innerBuilder;
diff --git a/src/EFCore.Jet.Data/JetDataReader.cs b/src/EFCore.Jet.Data/JetDataReader.cs
index 0a09de0f9..0f09d57eb 100644
--- a/src/EFCore.Jet.Data/JetDataReader.cs
+++ b/src/EFCore.Jet.Data/JetDataReader.cs
@@ -8,7 +8,7 @@
namespace EntityFrameworkCore.Jet.Data
{
- internal class JetDataReader : DbDataReader
+ internal sealed class JetDataReader : DbDataReader
{
#if DEBUG
private static int _activeObjectsCount;
@@ -212,7 +212,7 @@ public TimeOnly GetTimeOnly(int ordinal)
return (TimeOnly)value;
}
- public virtual TimeSpan GetTimeSpan(int ordinal)
+ public TimeSpan GetTimeSpan(int ordinal)
{
var value = _wrappedDataReader.GetValue(ordinal);
@@ -227,7 +227,7 @@ public virtual TimeSpan GetTimeSpan(int ordinal)
return (TimeSpan)value;
}
- public virtual DateTimeOffset GetDateTimeOffset(int ordinal)
+ public DateTimeOffset GetDateTimeOffset(int ordinal)
{
var value = _wrappedDataReader.GetValue(ordinal);
if (value is String stringValue)
@@ -270,7 +270,7 @@ public override decimal GetDecimal(int ordinal)
try
{
- return Convert.ToDecimal(value);
+ return Convert.ToDecimal(value, CultureInfo.InvariantCulture);
}
catch
{
@@ -289,7 +289,7 @@ public override double GetDouble(int ordinal)
try
{
- return Convert.ToDouble(value);
+ return Convert.ToDouble(value, CultureInfo.InvariantCulture);
}
catch
{
@@ -314,7 +314,7 @@ public override float GetFloat(int ordinal)
try
{
- return Convert.ToSingle(value);
+ return Convert.ToSingle(value, CultureInfo.InvariantCulture);
}
catch
{
@@ -360,7 +360,7 @@ public override short GetInt16(int ordinal)
try
{
- return Convert.ToInt16(value);
+ return Convert.ToInt16(value, CultureInfo.InvariantCulture);
}
catch
{
@@ -401,7 +401,7 @@ public override int GetInt32(int ordinal)
//The text e.g. "Chai" should not be attempted to convert to bytes and then to an int but should throw an exception
try
{
- return Convert.ToInt32(value);
+ return Convert.ToInt32(value, CultureInfo.InvariantCulture);
}
catch
{
@@ -429,7 +429,7 @@ public override long GetInt64(int ordinal)
try
{
- return Convert.ToInt64(value);
+ return Convert.ToInt64(value, CultureInfo.InvariantCulture);
}
catch
{
diff --git a/src/EFCore.Jet.Data/JetFactory.cs b/src/EFCore.Jet.Data/JetFactory.cs
index e6ff14c6c..c2beb7c51 100644
--- a/src/EFCore.Jet.Data/JetFactory.cs
+++ b/src/EFCore.Jet.Data/JetFactory.cs
@@ -31,16 +31,16 @@ internal JetFactory(JetConnection connection, DbProviderFactory innerFactory)
}
///
- /// Specifies whether the specific supports the class.
+ /// Specifies whether the specific supports the class.
///
public override bool CanCreateDataSourceEnumerator
=> false;
///
- /// Returns a new instance of the provider's class that implements the class.
+ /// Returns a new instance of the provider's class that implements the class.
///
///
- /// A new instance of .
+ /// A new instance of .
///
public override DbCommand CreateCommand()
=> InnerFactory == null
@@ -48,10 +48,10 @@ public override DbCommand CreateCommand()
: new JetCommand(Connection);
///
- /// Returns a new instance of the provider's class that implements the class.
+ /// Returns a new instance of the provider's class that implements the class.
///
///
- /// A new instance of .
+ /// A new instance of .
///
public override DbCommandBuilder CreateCommandBuilder()
{
@@ -66,10 +66,10 @@ public override DbCommandBuilder CreateCommandBuilder()
}
///
- /// Returns a new instance of the provider's class that implements the class.
+ /// Returns a new instance of the provider's class that implements the class.
///
///
- /// A new instance of .
+ /// A new instance of .
///
public override DbConnection CreateConnection()
=> InnerFactory == null
@@ -77,10 +77,10 @@ public override DbConnection CreateConnection()
: new JetConnection(InnerFactory);
///
- /// Returns a new instance of the provider's class that implements the class.
+ /// Returns a new instance of the provider's class that implements the class.
///
///
- /// A new instance of .
+ /// A new instance of .
///
public override DbConnectionStringBuilder CreateConnectionStringBuilder()
=> InnerFactory == null
@@ -88,10 +88,10 @@ public override DbConnectionStringBuilder CreateConnectionStringBuilder()
: new JetConnectionStringBuilder(InnerFactory);
///
- /// Returns a new instance of the provider's class that implements the class.
+ /// Returns a new instance of the provider's class that implements the class.
///
///
- /// A new instance of .
+ /// A new instance of .
///
public override DbDataAdapter? CreateDataAdapter()
=> InnerFactory == null
@@ -99,19 +99,19 @@ public override DbConnectionStringBuilder CreateConnectionStringBuilder()
: InnerFactory.CreateDataAdapter();
///
- /// Returns a new instance of the provider's class that implements the class.
+ /// Returns a new instance of the provider's class that implements the class.
///
///
- /// A new instance of .
+ /// A new instance of .
///
public override DbDataSourceEnumerator? CreateDataSourceEnumerator()
=> null;
///
- /// Returns a new instance of the provider's class that implements the class.
+ /// Returns a new instance of the provider's class that implements the class.
///
///
- /// A new instance of .
+ /// A new instance of .
///
public override DbParameter? CreateParameter()
=> InnerFactory == null
@@ -133,8 +133,8 @@ public static DbProviderFactory GetDataAccessProviderFactory(DataAccessProviderT
{
throw new TypeLoadException($"The referenced version '{version}' of 'System.Data.OleDb' is lower than the minimum required version {MinimumRequiredOleDbVersion}.");
}
-
- return (DbProviderFactory) type
+
+ return (DbProviderFactory)type
.GetField("Instance", BindingFlags.Static | BindingFlags.Public)
.GetValue(null);
}
@@ -156,8 +156,8 @@ public static DbProviderFactory GetDataAccessProviderFactory(DataAccessProviderT
{
throw new TypeLoadException($"The referenced version '{version}' of 'System.Data.Odbc' is lower than the minimum required version {MinimumRequiredOdbcVersion}.");
}
-
- return (DbProviderFactory) type
+
+ return (DbProviderFactory)type
.GetField("Instance", BindingFlags.Static | BindingFlags.Public)
.GetValue(null);
}
diff --git a/src/EFCore.Jet.Data/JetParameterHelper.cs b/src/EFCore.Jet.Data/JetParameterHelper.cs
index 0f5c65e3d..5b172c30b 100644
--- a/src/EFCore.Jet.Data/JetParameterHelper.cs
+++ b/src/EFCore.Jet.Data/JetParameterHelper.cs
@@ -1,6 +1,7 @@
using System;
using System.Data;
using System.Data.Common;
+using System.Globalization;
namespace EntityFrameworkCore.Jet.Data
{
public static class JetParameterHelper
@@ -10,23 +11,24 @@ internal static string GetParameterValueToDisplay(DbParameter parameter)
if (parameter.Value == DBNull.Value || parameter.Value == null)
return "null";
else if (IsString(parameter))
- return String.Format("'{0}'", parameter.Value);
+ return String.Format(CultureInfo.InvariantCulture, "'{0}'", parameter.Value);
else if (IsDateTime(parameter))
{
if (parameter.Value is TimeSpan)
- return String.Format("#{0:c}#", parameter.Value);
+ return String.Format(CultureInfo.InvariantCulture, "#{0:c}#", parameter.Value);
else
- return String.Format("#{0:yyyy-MM-ddTHH:mm:ssZ}#", parameter.Value);
-
+ return String.Format(CultureInfo.InvariantCulture, "#{0:yyyy-MM-ddTHH:mm:ssZ}#", parameter.Value);
+
}
else if (IsTimeSpan(parameter))
- return String.Format("#{0:c}#", parameter.Value);
+ return String.Format(CultureInfo.InvariantCulture, "#{0:c}#", parameter.Value);
else if (IsGuid(parameter))
- return String.Format("{{{0}}}", parameter.Value);
+ return String.Format(CultureInfo.InvariantCulture, "{{{0}}}", parameter.Value);
else if (parameter.Value is Enum)
- return String.Format("{0}({1})", Convert.ToInt32(parameter.Value), parameter.Value);
+ return String.Format(CultureInfo.InvariantCulture, "{0}({1})",
+ Convert.ToInt32(parameter.Value, CultureInfo.InvariantCulture), parameter.Value);
else
- return String.Format("{0}", parameter.Value);
+ return String.Format(CultureInfo.InvariantCulture, "{0}", parameter.Value);
}
@@ -39,17 +41,6 @@ private static bool IsGuid(DbParameter parameter)
};
}
- private static bool IsNumeric(DbParameter parameter)
- {
- return parameter.DbType switch
- {
- DbType.Byte or DbType.Int16 or DbType.Int32 or DbType.Int64 or DbType.Object or DbType.SByte
- or DbType.UInt16 or DbType.UInt32 or DbType.UInt64 => true,
- _ => false
- };
- }
-
-
internal static bool IsTimeSpan(DbParameter parameter)
{
if (!(parameter is DbParameter))
diff --git a/src/EFCore.Jet.Data/JetStoreSchemaDefinition/Column.cs b/src/EFCore.Jet.Data/JetStoreSchemaDefinition/Column.cs
index bdcf55994..bf6ca3bda 100644
--- a/src/EFCore.Jet.Data/JetStoreSchemaDefinition/Column.cs
+++ b/src/EFCore.Jet.Data/JetStoreSchemaDefinition/Column.cs
@@ -1,8 +1,6 @@
-using System;
-
namespace EntityFrameworkCore.Jet.Data.JetStoreSchemaDefinition
{
- class Column
+ sealed class Column
{
public string Name { get; set; }
public string Type { get; set; }
@@ -11,7 +9,9 @@ class Column
public override string ToString()
{
- return string.Format("{0} {2}({3}) {1}", Name, Nullable ? "Null" : "NotNull", Type, MaxLength);
+ return string.Format(
+ System.Globalization.CultureInfo.InvariantCulture,
+ "{0} {2}({3}) {1}", Name, Nullable ? "Null" : "NotNull", Type, MaxLength);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Data/JetStoreSchemaDefinition/ColumnCollection.cs b/src/EFCore.Jet.Data/JetStoreSchemaDefinition/ColumnCollection.cs
index 42396e43c..69e03f132 100644
--- a/src/EFCore.Jet.Data/JetStoreSchemaDefinition/ColumnCollection.cs
+++ b/src/EFCore.Jet.Data/JetStoreSchemaDefinition/ColumnCollection.cs
@@ -2,7 +2,7 @@
namespace EntityFrameworkCore.Jet.Data.JetStoreSchemaDefinition
{
- class ColumnCollection : List
+ sealed class ColumnCollection : List
{
}
}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetInformationSchema.cs b/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetInformationSchema.cs
index 62fe40f8d..b0ca28e0f 100644
--- a/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetInformationSchema.cs
+++ b/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetInformationSchema.cs
@@ -21,8 +21,8 @@ public static bool TryGetDataReaderFromInformationSchemaCommand(JetCommand comma
dataReader = GetDbDataReaderFromSimpleStatement(command);
return dataReader != null;
}
-
- private static DbDataReader? GetDbDataReaderFromSimpleStatement(JetCommand command)
+
+ private static DataTableReader? GetDbDataReaderFromSimpleStatement(JetCommand command)
{
// Command text format is
// SELECT * FROM `INFORMATION_SCHEMA.` [WHERE ] [ORDER BY ]
@@ -35,7 +35,7 @@ public static bool TryGetDataReaderFromInformationSchemaCommand(JetCommand comma
// relation_columns
// check_constraints
- var jetConnection = (JetConnection) command.Connection;
+ var jetConnection = (JetConnection)command.Connection;
var innerCommand = command.InnerCommand;
var commandText = innerCommand.CommandText;
var innerConnection = command.InnerCommand.Connection;
@@ -51,7 +51,7 @@ public static bool TryGetDataReaderFromInformationSchemaCommand(JetCommand comma
var conditions = match.Groups["conditions"].Value;
var orderColumns = match.Groups["orderColumns"].Value;
- Func? schemaMethod = dbObject.ToLower() switch
+ Func? schemaMethod = dbObject.ToLowerInvariant() switch
{
"tables" => GetTables,
"columns" => GetColumns,
@@ -105,7 +105,7 @@ public static bool TryGetDataReaderFromInformationSchemaCommand(JetCommand comma
foreach (var row in selectedRows)
selectedDataTable.ImportRow(row);
-
+
return selectedDataTable.CreateDataReader();
}
@@ -144,7 +144,7 @@ private static DataTable GetRelationColumns(JetConnection connection)
using var schemaProvider = SchemaProvider.CreateInstance(connection.SchemaProviderType, connection);
return schemaProvider.GetRelationColumns();
}
-
+
private static DataTable GetCheckConstraints(JetConnection connection)
{
using var schemaProvider = SchemaProvider.CreateInstance(connection.SchemaProviderType, connection);
diff --git a/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetSchemaOperationsHandling.cs b/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetSchemaOperationsHandling.cs
index 57bb76a85..c6a7a04c7 100644
--- a/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetSchemaOperationsHandling.cs
+++ b/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetSchemaOperationsHandling.cs
@@ -22,7 +22,7 @@ public static bool TryDatabaseOperation(JetConnection connection, string command
{
var oldTableName = match.Groups["OldTableName"].Value;
var newTableName = match.Groups["NewTableName"].Value;
-
+
RenameTable(connection, oldTableName, newTableName);
return true;
@@ -34,9 +34,9 @@ public static bool TryDatabaseOperation(JetConnection connection, string command
var tableName = match.Groups["TableName"].Value;
var oldColumnName = match.Groups["OldColumnName"].Value;
var newColumnName = match.Groups["NewColumnName"].Value;
-
+
RenameColumn(connection, tableName, oldColumnName, newColumnName);
-
+
return true;
}
@@ -45,7 +45,7 @@ public static bool TryDatabaseOperation(JetConnection connection, string command
private static string GetIdentifierPattern(string key)
=> $@"(?:`(?<{key}>.*?)`|\[(?<{key}>.*?)\]|(?<{key}>\S*))";
-
+
private static void RenameTable(JetConnection connection, string oldTableName, string newTableName)
{
using var schemaProvider = SchemaProvider.CreateInstance(connection.SchemaProviderType, connection, false);
@@ -58,4 +58,4 @@ private static void RenameColumn(JetConnection connection, string tableName, str
schemaProvider.RenameColumn(tableName, oldColumnName, newColumnName);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetStoreDatabaseHandling.cs b/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetStoreDatabaseHandling.cs
index 0e63024db..ba134f962 100644
--- a/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetStoreDatabaseHandling.cs
+++ b/src/EFCore.Jet.Data/JetStoreSchemaDefinition/JetStoreDatabaseHandling.cs
@@ -1,8 +1,6 @@
using System;
-using System.Diagnostics;
using System.IO;
using System.Linq;
-using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
namespace EntityFrameworkCore.Jet.Data.JetStoreSchemaDefinition
@@ -92,7 +90,7 @@ public static bool ProcessDatabaseOperation(JetCommand command)
//
// CREATE DATABASE:
//
-
+
match = _regExParseCreateDatabaseCommand.Match(commandText);
if (match.Success)
{
@@ -101,8 +99,8 @@ public static bool ProcessDatabaseOperation(JetCommand command)
if (string.IsNullOrWhiteSpace(fileName))
throw new InvalidOperationException("CREATE DATABASE statement is missing database file name.");
-
- JetConnection.CreateDatabase(fileName, databasePassword: databasePassword, dataAccessProviderType:JetConnection.GetDataAccessProviderType(((JetConnection)command.Connection)?.DataAccessProviderFactory));
+
+ JetConnection.CreateDatabase(fileName, databasePassword: databasePassword, dataAccessProviderType: JetConnection.GetDataAccessProviderType(((JetConnection)command.Connection)?.DataAccessProviderFactory));
return true;
}
@@ -110,22 +108,22 @@ public static bool ProcessDatabaseOperation(JetCommand command)
if (match.Success)
{
var fileName = match.Groups["filename"].Value;
-
+
if (string.IsNullOrWhiteSpace(fileName))
throw new InvalidOperationException("CREATE DATABASE statement is missing database file name or connection string.");
-
+
JetConnection.CreateDatabase(fileName, dataAccessProviderType: JetConnection.GetDataAccessProviderType(((JetConnection)command.Connection)?.DataAccessProviderFactory));
return true;
}
-
+
match = _regExParseObsoleteCreateDatabaseCommandFromConnection.Match(commandText);
if (match.Success)
{
var connectionString = ExtractFileNameFromConnectionString(match.Groups["connectionString"].Value);
-
+
if (string.IsNullOrWhiteSpace(connectionString))
throw new InvalidOperationException("CREATE DATABASE statement is missing database file name or connection string.");
-
+
JetConnection.CreateDatabase(connectionString);
return true;
}
@@ -133,7 +131,7 @@ public static bool ProcessDatabaseOperation(JetCommand command)
//
// DROP DATABASE:
//
-
+
match = _regExParseDropDatabaseCommand.Match(commandText);
if (match.Success)
{
@@ -169,8 +167,8 @@ public static bool ProcessDatabaseOperation(JetCommand command)
JetConnection.DropDatabase(connectionString);
return true;
}
-
- throw new Exception($"\"{commandText}\" is not a valid database command.");
+
+ throw new InvalidOperationException($"\"{commandText}\" is not a valid database command.");
}
public static string ExtractFileNameFromConnectionString(string? connectionString)
@@ -204,17 +202,17 @@ public static bool IsFileName(string? fileName)
!fileName.ToCharArray()
.Intersect(Path.GetInvalidPathChars())
.Any();
-
+
public static bool HasProvider(string connectionString)
=> _regExHasProvider.IsMatch(connectionString);
-
+
public static void DeleteFile(string fileName)
{
JetConnection.ClearAllPools();
fileName = ExpandFileName(fileName);
ValidateDatabaseFileExtension(fileName);
-
+
var directoryPath = Path.GetDirectoryName(fileName) ?? string.Empty;
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
var extension = Path.GetExtension(fileName);
@@ -232,7 +230,7 @@ public static void DeleteFile(string fileName)
{
File.Delete(Path.Combine(directoryPath, fileNameWithoutExtension + ".laccdb"));
}
-
+
if (string.IsNullOrEmpty(extension) ||
string.Equals(extension, ".mdb", StringComparison.OrdinalIgnoreCase) ||
!string.Equals(extension, ".accdb", StringComparison.OrdinalIgnoreCase))
@@ -257,8 +255,7 @@ private static string UnescapeSingleQuotes(string value)
public static string ExpandFileName(string? fileName)
{
- if (fileName == null)
- throw new ArgumentNullException(nameof(fileName));
+ ArgumentNullException.ThrowIfNull(fileName);
if (fileName.StartsWith("|DataDirectory|", StringComparison.OrdinalIgnoreCase))
{
@@ -285,4 +282,4 @@ public static string EnsureFileExtension(string fileName)
return fileName;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Data/JetStoreSchemaDefinition/SchemaTables.cs b/src/EFCore.Jet.Data/JetStoreSchemaDefinition/SchemaTables.cs
index 54138b65e..399fb88dd 100644
--- a/src/EFCore.Jet.Data/JetStoreSchemaDefinition/SchemaTables.cs
+++ b/src/EFCore.Jet.Data/JetStoreSchemaDefinition/SchemaTables.cs
@@ -7,7 +7,7 @@ internal static class SchemaTables
public static DataTable GetTablesDataTable()
{
var dataTable = new DataTable("INFORMATION_SCHEMA.TABLES");
-
+
dataTable.Columns.AddRange(
[
new DataColumn("TABLE_NAME", typeof(string)),
@@ -15,7 +15,7 @@ public static DataTable GetTablesDataTable()
new DataColumn("VALIDATION_RULE", typeof(string)),
new DataColumn("VALIDATION_TEXT", typeof(string))
]);
-
+
return dataTable;
}
@@ -40,7 +40,7 @@ public static DataTable GetColumnsDataTable()
new DataColumn("IDENTITY_INCREMENT", typeof(int))
// TODO: Add ALLOW_ZERO_LENGTH_STRING
]);
-
+
return dataTable;
}
@@ -56,7 +56,7 @@ public static DataTable GetIndexesDataTable()
new DataColumn("IS_NULLABLE", typeof(bool)),
new DataColumn("IGNORES_NULLS", typeof(bool))
]);
-
+
return dataTable;
}
@@ -72,7 +72,7 @@ public static DataTable GetIndexColumnsDataTable()
new DataColumn("COLUMN_NAME", typeof(string)),
new DataColumn("IS_DESCENDING", typeof(bool))
]);
-
+
return dataTable;
}
@@ -91,10 +91,10 @@ public static DataTable GetRelationsDataTable()
new DataColumn("IS_ENFORCED", typeof(bool)),
new DataColumn("IS_INHERITED", typeof(bool))
]);
-
+
return dataTable;
}
-
+
public static DataTable GetRelationColumnsDataTable()
{
var dataTable = new DataTable("INFORMATION_SCHEMA.RELATION_COLUMNS");
@@ -106,10 +106,10 @@ public static DataTable GetRelationColumnsDataTable()
new DataColumn("PRINCIPAL_COLUMN_NAME", typeof(string)),
new DataColumn("ORDINAL_POSITION", typeof(int))
]);
-
+
return dataTable;
}
-
+
public static DataTable GetCheckConstraintsDataTable()
{
var dataTable = new DataTable("INFORMATION_SCHEMA.CHECK_CONSTRAINTS");
@@ -120,7 +120,7 @@ public static DataTable GetCheckConstraintsDataTable()
new DataColumn("CONSTRAINT_NAME", typeof(string)),
new DataColumn("CHECK_CLAUSE", typeof(string))
]);
-
+
return dataTable;
}
}
diff --git a/src/EFCore.Jet.Data/JetSyntaxHelper.cs b/src/EFCore.Jet.Data/JetSyntaxHelper.cs
index 883ae99bd..ddadfc4fa 100644
--- a/src/EFCore.Jet.Data/JetSyntaxHelper.cs
+++ b/src/EFCore.Jet.Data/JetSyntaxHelper.cs
@@ -52,7 +52,7 @@ static string ByteArrayToBinaryString(byte[] binaryArray)
StringBuilder sb = new(binaryArray.Length * 2);
foreach (byte b in binaryArray)
- sb.Append(b.ToString("X2"));
+ sb.Append(b.ToString("X2", CultureInfo.InvariantCulture));
return sb.ToString();
}
@@ -101,7 +101,7 @@ internal static string EscapeLikeText(string text, out bool usedEscapeChar)
c == '*' || c == '?' || c == '[' || c == '#'
)
{
- sb.AppendFormat("[{0}]", c);
+ sb.AppendFormat(CultureInfo.InvariantCulture, "[{0}]", c);
usedEscapeChar = true;
}
else
@@ -112,4 +112,4 @@ internal static string EscapeLikeText(string text, out bool usedEscapeChar)
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Data/JetTransaction.cs b/src/EFCore.Jet.Data/JetTransaction.cs
index 81d87765f..9c1cf4006 100644
--- a/src/EFCore.Jet.Data/JetTransaction.cs
+++ b/src/EFCore.Jet.Data/JetTransaction.cs
@@ -6,12 +6,12 @@
namespace EntityFrameworkCore.Jet.Data
{
- internal class JetTransaction : DbTransaction
+ internal sealed class JetTransaction : DbTransaction
{
private JetConnection _connection;
private bool _disposed;
- internal virtual DbTransaction WrappedTransaction { get; }
+ internal DbTransaction WrappedTransaction { get; }
public JetTransaction(JetConnection connection, IsolationLevel isolationLevel)
{
@@ -22,9 +22,8 @@ public JetTransaction(JetConnection connection, IsolationLevel isolationLevel)
public override void Commit()
{
- if (_disposed)
- throw new ObjectDisposedException(nameof(JetTransaction));
-
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
LogHelper.ShowCommandHeader("--- Commit");
WrappedTransaction.Commit();
@@ -39,9 +38,8 @@ public override IsolationLevel IsolationLevel
public override void Rollback()
{
- if (_disposed)
- throw new ObjectDisposedException(nameof(JetTransaction));
-
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
LogHelper.ShowCommandHeader("^^^ Rollback");
WrappedTransaction.Rollback();
@@ -78,25 +76,22 @@ protected override void Dispose(bool disposing)
public override Task CommitAsync(CancellationToken cancellationToken = new())
{
- if (_disposed)
- throw new ObjectDisposedException(nameof(JetTransaction));
-
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
return base.CommitAsync(cancellationToken);
}
public override ValueTask DisposeAsync()
{
- if (_disposed)
- throw new ObjectDisposedException(nameof(JetTransaction));
-
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
return base.DisposeAsync();
}
public override Task RollbackAsync(CancellationToken cancellationToken = new())
{
- if (_disposed)
- throw new ObjectDisposedException(nameof(JetTransaction));
-
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
return base.RollbackAsync(cancellationToken);
}
}
diff --git a/src/EFCore.Jet.Data/LogHelper.cs b/src/EFCore.Jet.Data/LogHelper.cs
index fcf34213f..4f1ec331f 100644
--- a/src/EFCore.Jet.Data/LogHelper.cs
+++ b/src/EFCore.Jet.Data/LogHelper.cs
@@ -1,9 +1,8 @@
+using EntityFrameworkCore.Jet.Data;
using System;
-using System.Data;
using System.Data.Common;
-using EntityFrameworkCore.Jet.Data;
-static internal class LogHelper
+internal static class LogHelper
{
internal static void ShowInfo(string info)
diff --git a/src/EFCore.Jet.Data/Messages.cs b/src/EFCore.Jet.Data/Messages.cs
index 8a41d6946..df8c25c75 100644
--- a/src/EFCore.Jet.Data/Messages.cs
+++ b/src/EFCore.Jet.Data/Messages.cs
@@ -1,4 +1,3 @@
-using System;
using System.Data;
namespace EntityFrameworkCore.Jet.Data
@@ -24,12 +23,12 @@ public static string CannotCallMethodInThisConnectionState(string methodName, Co
{
return $"Cannot call method \"{methodName}\" in this connection state. Current connection state is {state}";
}
-
+
public static string CannotCallJetProviderFactoryMethodOnSingletonInstance(string methodName)
=> $"""The method "{methodName}" can not be called on a singleton instance of JetProviderFactory. This method can only be called on a JetProviderFactory object retrieved from the JetConnnection.JetProviderFactory property.""";
-
+
public static string MethodUnsupportedByJet(string methodName)
{
return $"\"{methodName}\" is not supported by Jet";
@@ -45,4 +44,4 @@ public static string UnsupportedParallelTransactions()
return "JetConnection does not support parallel transactions";
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Data/PreciseSchema.cs b/src/EFCore.Jet.Data/PreciseSchema.cs
index 8275e8dbf..fe9812264 100644
--- a/src/EFCore.Jet.Data/PreciseSchema.cs
+++ b/src/EFCore.Jet.Data/PreciseSchema.cs
@@ -19,14 +19,14 @@ public override DataTable GetColumns()
// DAO lacks seed and increment values for auto increment columns and needs a costly workaround to get the
// numeric scale of decimal columns.
// ADOX lacks ordinal position information and has very unreliable nullable information.
-
+
var dataTable = _adoxSchema.Value.GetColumns();
var ordinalPositionsAndNullables = _daoSchema.Value.GetOrdinalPositionsAndNullables();
foreach (DataRow row in dataTable.Rows)
{
- var tableName = (string) row["TABLE_NAME"];
- var columnName = (string) row["COLUMN_NAME"];
+ var tableName = (string)row["TABLE_NAME"];
+ var columnName = (string)row["COLUMN_NAME"];
if (ordinalPositionsAndNullables.TryGetValue((tableName, columnName), out var ordinalPositionAndNullable))
{
@@ -34,7 +34,7 @@ public override DataTable GetColumns()
row["IS_NULLABLE"] = ordinalPositionAndNullable.Nullable;
}
}
-
+
dataTable.AcceptChanges();
return dataTable;
}
@@ -73,7 +73,7 @@ public override DataTable GetRelationColumns()
public override DataTable GetCheckConstraints()
=> _adoxSchema.Value.GetCheckConstraints(); // DAO does not support CHECK CONSTRAINTs, but ADOX does
-
+
public override void RenameTable(string oldTableName, string newTableName)
=> _adoxSchema.Value.RenameTable(oldTableName, newTableName); // either ADOX or DAO is fine
@@ -91,6 +91,8 @@ public override void Dispose()
{
_adoxSchema.Value.Dispose();
}
+
+ GC.SuppressFinalize(this);
}
}
}
\ No newline at end of file
diff --git a/src/EFCore.Jet.Data/SchemaProvider.cs b/src/EFCore.Jet.Data/SchemaProvider.cs
index f07dd6ddd..8f8d74e55 100644
--- a/src/EFCore.Jet.Data/SchemaProvider.cs
+++ b/src/EFCore.Jet.Data/SchemaProvider.cs
@@ -22,7 +22,7 @@ public static SchemaProvider CreateInstance(SchemaProviderType type, JetConnecti
};
public abstract void Dispose();
-
+
public abstract DataTable GetTables();
public abstract DataTable GetColumns();
public abstract DataTable GetIndexes();
diff --git a/src/EFCore.Jet.Data/TypesExtensions.cs b/src/EFCore.Jet.Data/TypesExtensions.cs
index f2acc24d8..b3ffd306c 100644
--- a/src/EFCore.Jet.Data/TypesExtensions.cs
+++ b/src/EFCore.Jet.Data/TypesExtensions.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Reflection;
namespace EntityFrameworkCore.Jet.Data
{
diff --git a/src/EFCore.Jet.Odbc/EFCore.Jet.Odbc.csproj b/src/EFCore.Jet.Odbc/EFCore.Jet.Odbc.csproj
index 89c682362..be3890af3 100644
--- a/src/EFCore.Jet.Odbc/EFCore.Jet.Odbc.csproj
+++ b/src/EFCore.Jet.Odbc/EFCore.Jet.Odbc.csproj
@@ -10,9 +10,7 @@
enable
-
-
-
+
@@ -33,8 +31,6 @@
-
-
diff --git a/src/EFCore.Jet.Odbc/Extensions/JetOdbcDbContextOptionsBuilderExtensions.cs b/src/EFCore.Jet.Odbc/Extensions/JetOdbcDbContextOptionsBuilderExtensions.cs
index 6aebbdb9e..404568a52 100644
--- a/src/EFCore.Jet.Odbc/Extensions/JetOdbcDbContextOptionsBuilderExtensions.cs
+++ b/src/EFCore.Jet.Odbc/Extensions/JetOdbcDbContextOptionsBuilderExtensions.cs
@@ -1,6 +1,5 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System;
using EntityFrameworkCore.Jet.Data;
using System.Data.Odbc;
using EntityFrameworkCore.Jet.Infrastructure;
@@ -15,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore
public static class JetOdbcDbContextOptionsBuilderExtensions
{
#region Connection String
-
+
///
/// Configures the context to connect to a Microsoft Jet database using ODBC.
///
@@ -48,9 +47,9 @@ public static DbContextOptionsBuilder UseJetOdbc(
=> optionsBuilder.UseJet(fileNameOrConnectionString, DataAccessProviderType.Odbc, jetOptionsAction);
#endregion
-
+
#region Connection
-
+
///
/// Configures the context to connect to a Microsoft Jet database using ODBC.
///
@@ -96,7 +95,7 @@ public static DbContextOptionsBuilder UseJetOdbc(
return optionsBuilder.UseJet(connection, jetOptionsAction);
}
-
+
#endregion
}
}
\ No newline at end of file
diff --git a/src/EFCore.Jet.OleDb/EFCore.Jet.OleDb.csproj b/src/EFCore.Jet.OleDb/EFCore.Jet.OleDb.csproj
index 567947c43..6bbbde29d 100644
--- a/src/EFCore.Jet.OleDb/EFCore.Jet.OleDb.csproj
+++ b/src/EFCore.Jet.OleDb/EFCore.Jet.OleDb.csproj
@@ -10,9 +10,7 @@
enable
-
-
-
+
@@ -33,8 +31,6 @@
-
-
diff --git a/src/EFCore.Jet.OleDb/Extensions/JetDbContextOptionsBuilderExtensions.cs b/src/EFCore.Jet.OleDb/Extensions/JetDbContextOptionsBuilderExtensions.cs
index 9f55c4108..f07485c1c 100644
--- a/src/EFCore.Jet.OleDb/Extensions/JetDbContextOptionsBuilderExtensions.cs
+++ b/src/EFCore.Jet.OleDb/Extensions/JetDbContextOptionsBuilderExtensions.cs
@@ -1,6 +1,5 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System;
using EntityFrameworkCore.Jet.Data;
using System.Data.OleDb;
using EntityFrameworkCore.Jet.Infrastructure;
@@ -15,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore
public static class JetOleDbDbContextOptionsBuilderExtensions
{
#region Connection String
-
+
///
/// Configures the context to connect to a Microsoft Jet database using OLE DB.
///
@@ -48,9 +47,9 @@ public static DbContextOptionsBuilder UseJetOleDb(
=> optionsBuilder.UseJet(fileNameOrConnectionString, DataAccessProviderType.OleDb, jetOptionsAction);
#endregion
-
+
#region Connection
-
+
///
/// Configures the context to connect to a Microsoft Jet database using OLE DB.
///
@@ -96,7 +95,7 @@ public static DbContextOptionsBuilder UseJetOleDb(
return optionsBuilder.UseJet(connection, jetOptionsAction);
}
-
+
#endregion
}
}
\ No newline at end of file
diff --git a/src/EFCore.Jet/EFCore.Jet.csproj b/src/EFCore.Jet/EFCore.Jet.csproj
index de9833857..e432d4ab5 100644
--- a/src/EFCore.Jet/EFCore.Jet.csproj
+++ b/src/EFCore.Jet/EFCore.Jet.csproj
@@ -17,9 +17,7 @@
-
-
-
+
@@ -75,9 +73,6 @@
-
-
-
diff --git a/src/EFCore.Jet/Extensions/JetDatabaseFacadeExtensions.cs b/src/EFCore.Jet/Extensions/JetDatabaseFacadeExtensions.cs
index 29bf56c76..ced4dd046 100644
--- a/src/EFCore.Jet/Extensions/JetDatabaseFacadeExtensions.cs
+++ b/src/EFCore.Jet/Extensions/JetDatabaseFacadeExtensions.cs
@@ -26,4 +26,4 @@ public static class JetDatabaseFacadeExtensions
public static bool IsJet(this DatabaseFacade database)
=> database.ProviderName == typeof(JetOptionsExtension).Assembly.GetName().Name;
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Extensions/JetServiceCollectionExtensions.cs b/src/EFCore.Jet/Extensions/JetServiceCollectionExtensions.cs
index 34909ea3d..bd0a1f4f6 100644
--- a/src/EFCore.Jet/Extensions/JetServiceCollectionExtensions.cs
+++ b/src/EFCore.Jet/Extensions/JetServiceCollectionExtensions.cs
@@ -11,8 +11,8 @@
using EntityFrameworkCore.Jet.Query.Sql.Internal;
using EntityFrameworkCore.Jet.Storage.Internal;
using EntityFrameworkCore.Jet.Update.Internal;
-using EntityFrameworkCore.Jet.ValueGeneration.Internal;
using EntityFrameworkCore.Jet.Utilities;
+using EntityFrameworkCore.Jet.ValueGeneration.Internal;
// ReSharper disable once CheckNamespace
namespace Microsoft.Extensions.DependencyInjection
@@ -69,4 +69,4 @@ public static IServiceCollection AddEntityFrameworkJet(this IServiceCollection s
return serviceCollection;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Infrastructure/Internal/IJetOptions.cs b/src/EFCore.Jet/Infrastructure/Internal/IJetOptions.cs
index ecba6e809..a4268b8c3 100644
--- a/src/EFCore.Jet/Infrastructure/Internal/IJetOptions.cs
+++ b/src/EFCore.Jet/Infrastructure/Internal/IJetOptions.cs
@@ -14,4 +14,4 @@ public interface IJetOptions : ISingletonOptions
bool UseOuterSelectSkipEmulationViaDataReader { get; }
bool UseShortTextForSystemString { get; }
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Internal/JetOptions.cs b/src/EFCore.Jet/Internal/JetOptions.cs
index 2774a9665..feb14c312 100644
--- a/src/EFCore.Jet/Internal/JetOptions.cs
+++ b/src/EFCore.Jet/Internal/JetOptions.cs
@@ -1,6 +1,5 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System.Linq;
using EntityFrameworkCore.Jet.Data;
using EntityFrameworkCore.Jet.Infrastructure.Internal;
@@ -155,4 +154,4 @@ private static DataAccessProviderType GetDataAccessProviderTypeFromOptions(JetOp
///
public virtual string? ConnectionString { get; private set; }
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Migrations/Internal/JetHistoryRepository.cs b/src/EFCore.Jet/Migrations/Internal/JetHistoryRepository.cs
index 196a01ca9..d1dc0e47b 100644
--- a/src/EFCore.Jet/Migrations/Internal/JetHistoryRepository.cs
+++ b/src/EFCore.Jet/Migrations/Internal/JetHistoryRepository.cs
@@ -1,7 +1,7 @@
-using System.Text;
using EntityFrameworkCore.Jet.Infrastructure;
using EntityFrameworkCore.Jet.Internal;
using EntityFrameworkCore.Jet.Utilities;
+using System.Text;
namespace EntityFrameworkCore.Jet.Migrations.Internal
{
diff --git a/src/EFCore.Jet/Migrations/Internal/JetMigrationCommandExecutor.cs b/src/EFCore.Jet/Migrations/Internal/JetMigrationCommandExecutor.cs
index 984338e72..fa8268d3e 100644
--- a/src/EFCore.Jet/Migrations/Internal/JetMigrationCommandExecutor.cs
+++ b/src/EFCore.Jet/Migrations/Internal/JetMigrationCommandExecutor.cs
@@ -1,4 +1,3 @@
-using EntityFrameworkCore.Jet.Data;
using EntityFrameworkCore.Jet.Data.JetStoreSchemaDefinition;
using Microsoft.EntityFrameworkCore.Migrations.Internal;
@@ -53,7 +52,7 @@ public override async Task ExecuteNonQueryAsync(
return -1;
}
- List<(IReadOnlyList,bool)> CreateMigrationBatches(IReadOnlyList migrationCommands)
+ static List<(IReadOnlyList, bool)> CreateMigrationBatches(IReadOnlyList migrationCommands)
{
//create new batch if JetSchemaOperationsHandling.IsDatabaseOperation is true otherwise had to current batch
var migrationBatches = new List<(IReadOnlyList, bool)>();
@@ -64,10 +63,10 @@ public override async Task ExecuteNonQueryAsync(
{
if (currentBatch.Count != 0)
{
- migrationBatches.Add((currentBatch,false));
+ migrationBatches.Add((currentBatch, false));
currentBatch = [];
}
- migrationBatches.Add(([migrationCommand],true));
+ migrationBatches.Add(([migrationCommand], true));
}
else
{
@@ -76,9 +75,9 @@ public override async Task ExecuteNonQueryAsync(
}
if (currentBatch.Count != 0)
{
- migrationBatches.Add((currentBatch,false));
+ migrationBatches.Add((currentBatch, false));
}
return migrationBatches;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Properties/InternalsVisibleTo.cs b/src/EFCore.Jet/Properties/InternalsVisibleTo.cs
index 82472137b..045b4a3df 100644
--- a/src/EFCore.Jet/Properties/InternalsVisibleTo.cs
+++ b/src/EFCore.Jet/Properties/InternalsVisibleTo.cs
@@ -2,4 +2,4 @@
using System.Runtime.CompilerServices;
-[assembly: InternalsVisibleTo("EntityFrameworkCore.Jet.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("EntityFrameworkCore.Jet.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
\ No newline at end of file
diff --git a/src/EFCore.Jet/Query/Internal/JetParameterBasedSqlProcessor.cs b/src/EFCore.Jet/Query/Internal/JetParameterBasedSqlProcessor.cs
index e33971e53..4f99e5a31 100644
--- a/src/EFCore.Jet/Query/Internal/JetParameterBasedSqlProcessor.cs
+++ b/src/EFCore.Jet/Query/Internal/JetParameterBasedSqlProcessor.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using EntityFrameworkCore.Jet.Utilities;
-
namespace EntityFrameworkCore.Jet.Query.Internal;
///
@@ -56,9 +54,9 @@ public override Expression Process(Expression queryExpression, ParametersCacheDe
}
///
- protected override Expression ProcessSqlNullability(Expression selectExpression, ParametersCacheDecorator Decorator)
+ protected override Expression ProcessSqlNullability(Expression queryExpression, ParametersCacheDecorator decorator)
{
return new JetSqlNullabilityProcessor(Dependencies, Parameters).Process(
- selectExpression, Decorator);
+ queryExpression, decorator);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Query/Internal/JetParameterBasedSqlProcessorFactory.cs b/src/EFCore.Jet/Query/Internal/JetParameterBasedSqlProcessorFactory.cs
index 8fa941944..6a3a8b9cd 100644
--- a/src/EFCore.Jet/Query/Internal/JetParameterBasedSqlProcessorFactory.cs
+++ b/src/EFCore.Jet/Query/Internal/JetParameterBasedSqlProcessorFactory.cs
@@ -31,4 +31,4 @@ public class JetParameterBasedSqlProcessorFactory(RelationalParameterBasedSqlPro
///
public RelationalParameterBasedSqlProcessor Create(RelationalParameterBasedSqlProcessorParameters parameters)
=> new JetParameterBasedSqlProcessor(Dependencies, parameters);
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Query/Internal/JetQueryTranslationPostprocessor.cs b/src/EFCore.Jet/Query/Internal/JetQueryTranslationPostprocessor.cs
index 0b2a18739..226abf090 100644
--- a/src/EFCore.Jet/Query/Internal/JetQueryTranslationPostprocessor.cs
+++ b/src/EFCore.Jet/Query/Internal/JetQueryTranslationPostprocessor.cs
@@ -1,12 +1,7 @@
-using System.Diagnostics.CodeAnalysis;
-using EntityFrameworkCore.Jet.Internal;
-using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
-
namespace EntityFrameworkCore.Jet.Query.Internal
{
public class JetQueryTranslationPostprocessor : RelationalQueryTranslationPostprocessor
{
- private readonly IRelationalTypeMappingSource _relationalTypeMappingSource;
private readonly JetLiftOrderByPostprocessor _liftOrderByPostprocessor;
private readonly JetSkipTakePostprocessor _skipTakePostprocessor;
@@ -17,7 +12,6 @@ public JetQueryTranslationPostprocessor(
IRelationalTypeMappingSource relationalTypeMappingSource)
: base(dependencies, relationalDependencies, queryCompilationContext)
{
- _relationalTypeMappingSource = relationalTypeMappingSource;
_liftOrderByPostprocessor = new JetLiftOrderByPostprocessor(relationalTypeMappingSource, relationalDependencies.SqlExpressionFactory, queryCompilationContext.SqlAliasManager);
_skipTakePostprocessor = new JetSkipTakePostprocessor(relationalTypeMappingSource,
relationalDependencies.SqlExpressionFactory, ((RelationalQueryCompilationContext)QueryCompilationContext).QuerySplittingBehavior);
@@ -34,4 +28,4 @@ public override Expression Process(Expression query)
return query;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Query/Internal/JetQueryTranslationPostprocessorFactory.cs b/src/EFCore.Jet/Query/Internal/JetQueryTranslationPostprocessorFactory.cs
index 6a7b5d8d6..55ab8565f 100644
--- a/src/EFCore.Jet/Query/Internal/JetQueryTranslationPostprocessorFactory.cs
+++ b/src/EFCore.Jet/Query/Internal/JetQueryTranslationPostprocessorFactory.cs
@@ -26,4 +26,4 @@ public virtual QueryTranslationPostprocessor Create(QueryCompilationContext quer
(RelationalQueryCompilationContext)queryCompilationContext,
relationalTypeMappingSource);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Scaffolding/Internal/JetCodeGenerator.cs b/src/EFCore.Jet/Scaffolding/Internal/JetCodeGenerator.cs
index 81df76fcb..9eb51f1e5 100644
--- a/src/EFCore.Jet/Scaffolding/Internal/JetCodeGenerator.cs
+++ b/src/EFCore.Jet/Scaffolding/Internal/JetCodeGenerator.cs
@@ -35,4 +35,4 @@ public override MethodCallCodeFragment GenerateUseProvider(
? [connectionString]
: [connectionString, new NestedClosureCodeFragment("x", providerOptions)]);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Scaffolding/Internal/JetDataReaderExtension.cs b/src/EFCore.Jet/Scaffolding/Internal/JetDataReaderExtension.cs
index debe8f5d7..9a626825c 100644
--- a/src/EFCore.Jet/Scaffolding/Internal/JetDataReaderExtension.cs
+++ b/src/EFCore.Jet/Scaffolding/Internal/JetDataReaderExtension.cs
@@ -20,4 +20,4 @@ public static class JetDataReaderExtension
: reader.GetFieldValue(idx);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs b/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs
index 8c4770ca3..12d69bbc9 100644
--- a/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs
+++ b/src/EFCore.Jet/Scaffolding/Internal/JetDatabaseModelFactory.cs
@@ -1,14 +1,14 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System.Data;
using EntityFrameworkCore.Jet.Data;
-using System.Globalization;
-using System.Text.RegularExpressions;
using EntityFrameworkCore.Jet.Infrastructure;
using EntityFrameworkCore.Jet.Internal;
using EntityFrameworkCore.Jet.Metadata.Internal;
-using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
using EntityFrameworkCore.Jet.Utilities;
+using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
+using System.Data;
+using System.Globalization;
+using System.Text.RegularExpressions;
namespace EntityFrameworkCore.Jet.Scaffolding.Internal
{
@@ -18,18 +18,6 @@ namespace EntityFrameworkCore.Jet.Scaffolding.Internal
///
public class JetDatabaseModelFactory : DatabaseModelFactory
{
- private static string ObjectKey(string name)
- => "`" + name + "`";
-
- private static string TableKey(DatabaseTable table)
- => TableKey(table.Name);
-
- private static string TableKey(String tableName)
- => ObjectKey(tableName);
-
- private static string ColumnKey(DatabaseTable table, string columnName)
- => TableKey(table) + "." + ObjectKey(columnName);
-
private static readonly List _tablePatterns =
[
@"(?<=^`).*(?=`$)",
@@ -124,15 +112,15 @@ public override DatabaseModel Create(
var tableNames = databaseModel.Tables.Select(t => t.Name).ToList();
if (tableNames.Contains("MSysAccessStorage") || _msysNames.Contains("MSysAccessStorage"))
{
- JetDualTable.DetectedName ="MSysAccessStorage";
+ JetDualTable.DetectedName = "MSysAccessStorage";
}
else if (tableNames.Contains("MSysRelationships") || _msysNames.Contains("MSysRelationships"))
{
- JetDualTable.DetectedName ="MSysRelationships";
+ JetDualTable.DetectedName = "MSysRelationships";
}
else if (tableNames.Contains("#Dual"))
{
- JetDualTable.DetectedName ="#Dual";
+ JetDualTable.DetectedName = "#Dual";
}
return databaseModel;
@@ -160,7 +148,7 @@ private static string Parse(string tableName)
return tableName;
}
- private IReadOnlyList GetTables(
+ private List GetTables(
DbConnection connection,
DatabaseModel databaseModel,
Func? filter)
@@ -217,7 +205,7 @@ private void GetColumns(DbConnection connection, IReadOnlyList ta
while (reader.Read())
{
var tableName = reader.GetValueOrDefault("TABLE_NAME");
- var table = tables.FirstOrDefault(t => string.Equals(t.Name, tableName)) ??
+ var table = tables.FirstOrDefault(t => string.Equals(t.Name, tableName, StringComparison.Ordinal)) ??
tables.FirstOrDefault(t => string.Equals(t.Name, tableName, StringComparison.OrdinalIgnoreCase));
if (table != null)
{
@@ -316,7 +304,7 @@ private void GetColumns(DbConnection connection, IReadOnlyList ta
{
try
{
- return Convert.ChangeType(defaultValueSql, type);
+ return Convert.ChangeType(defaultValueSql, type, CultureInfo.InvariantCulture);
}
catch
{
@@ -389,7 +377,7 @@ private void GetColumns(DbConnection connection, IReadOnlyList ta
return null;
}
- private string GetStoreType(string dataTypeName, int precision, int scale, int maxLength)
+ private static string GetStoreType(string dataTypeName, int precision, int scale, int maxLength)
{
if (precision > 0 &&
(string.Equals(dataTypeName, "decimal", StringComparison.OrdinalIgnoreCase) ||
@@ -437,7 +425,7 @@ private void GetIndexes(DbConnection connection, IReadOnlyList ta
var nullable = indexRow.GetValueOrDefault("IS_NULLABLE");
var ignoresNulls = indexRow.GetValueOrDefault("IGNORES_NULLS");
- var table = tables.FirstOrDefault(t => string.Equals(t.Name, tableName)) ??
+ var table = tables.FirstOrDefault(t => string.Equals(t.Name, tableName, StringComparison.Ordinal)) ??
tables.FirstOrDefault(t => string.Equals(t.Name, tableName, StringComparison.OrdinalIgnoreCase));
if (table != null)
{
@@ -580,7 +568,7 @@ private void GetRelations(DbConnection connection, IReadOnlyList
var enforced = relationRow.GetValueOrDefault("IS_ENFORCED", true);
var inherited = relationRow.GetValueOrDefault("IS_INHERITED", true);
- var referencingTable = tables.FirstOrDefault(t => string.Equals(t.Name, referencingTableName)) ??
+ var referencingTable = tables.FirstOrDefault(t => string.Equals(t.Name, referencingTableName, StringComparison.Ordinal)) ??
tables.FirstOrDefault(t => string.Equals(t.Name, referencingTableName, StringComparison.OrdinalIgnoreCase));
if (referencingTable != null)
{
@@ -593,7 +581,7 @@ private void GetRelations(DbConnection connection, IReadOnlyList
principalTableName!,
onDelete!);
- var principalTable = tables.FirstOrDefault(t => string.Equals(t.Name, principalTableName)) ??
+ var principalTable = tables.FirstOrDefault(t => string.Equals(t.Name, principalTableName, StringComparison.Ordinal)) ??
tables.FirstOrDefault(t => string.Equals(t.Name, principalTableName, StringComparison.OrdinalIgnoreCase));
if (principalTable == null)
{
diff --git a/src/EFCore.Jet/Storage/Internal/IJetRelationalConnection.cs b/src/EFCore.Jet/Storage/Internal/IJetRelationalConnection.cs
index 42a3a8965..ec9a596e6 100644
--- a/src/EFCore.Jet/Storage/Internal/IJetRelationalConnection.cs
+++ b/src/EFCore.Jet/Storage/Internal/IJetRelationalConnection.cs
@@ -10,4 +10,4 @@ public interface IJetRelationalConnection : IRelationalConnection
{
IJetRelationalConnection CreateEmptyConnection();
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Storage/Internal/JetBoolTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetBoolTypeMapping.cs
index dfcfa2f92..cc9319d2b 100644
--- a/src/EFCore.Jet/Storage/Internal/JetBoolTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetBoolTypeMapping.cs
@@ -4,7 +4,7 @@ namespace EntityFrameworkCore.Jet.Storage.Internal
{
public class JetBoolTypeMapping : BoolTypeMapping
{
- public static new JetBoolTypeMapping Default { get; } = new("smallint");
+ public static new JetBoolTypeMapping Default { get; } = new("smallint");
public JetBoolTypeMapping(
string storeType,
diff --git a/src/EFCore.Jet/Storage/Internal/JetByteArrayTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetByteArrayTypeMapping.cs
index d297b76f7..7c47a859a 100644
--- a/src/EFCore.Jet/Storage/Internal/JetByteArrayTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetByteArrayTypeMapping.cs
@@ -1,8 +1,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+using Microsoft.EntityFrameworkCore.Storage.Json;
using System.Globalization;
using System.Text;
-using Microsoft.EntityFrameworkCore.Storage.Json;
namespace EntityFrameworkCore.Jet.Storage.Internal
{
@@ -108,4 +108,4 @@ protected override string ProcessStoreType(RelationalTypeMappingParameters param
return base.ProcessStoreType(parameters, storeType.Replace("(max)", ""), storeTypeNameBase);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Storage/Internal/JetByteTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetByteTypeMapping.cs
index 571370d61..11751bfea 100644
--- a/src/EFCore.Jet/Storage/Internal/JetByteTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetByteTypeMapping.cs
@@ -57,4 +57,4 @@ protected override string GenerateNonNullSqlLiteral(object value)
{
return $"CBYTE({base.GenerateNonNullSqlLiteral(value)})";
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Storage/Internal/JetDatabaseCreator.cs b/src/EFCore.Jet/Storage/Internal/JetDatabaseCreator.cs
index 5527cd3c6..50c0074de 100644
--- a/src/EFCore.Jet/Storage/Internal/JetDatabaseCreator.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetDatabaseCreator.cs
@@ -85,13 +85,13 @@ private IReadOnlyList CreateCreateOperations()
// Alternative:
// var dataSource = _relationalConnection.DbConnection.DataSource;
- var connection = (JetConnection) relationalConnection.DbConnection;
+ var connection = (JetConnection)relationalConnection.DbConnection;
var fileNameOrConnectionString = connection.ConnectionString;
var connectionString = JetConnection.GetConnectionString(fileNameOrConnectionString, connection.DataAccessProviderFactory);
var csb = (connection.JetFactory?.CreateConnectionStringBuilder()) ?? throw new InvalidOperationException("Failed to create connection string builder.");
csb.ConnectionString = connectionString;
-
+
var dataSource = csb.GetDataSource();
var databasePassword = csb.GetDatabasePassword();
@@ -150,6 +150,6 @@ private static void ClearAllPools()
// Clear connection pool for the database connection since after the 'create database' call, a previously
// invalid connection may now be valid.
private void ClearPool()
- => JetConnection.ClearPool((JetConnection) relationalConnection.DbConnection);
+ => JetConnection.ClearPool((JetConnection)relationalConnection.DbConnection);
}
}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Storage/Internal/JetDateOnlyTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetDateOnlyTypeMapping.cs
index 5a82d699b..8ec8c92c2 100644
--- a/src/EFCore.Jet/Storage/Internal/JetDateOnlyTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetDateOnlyTypeMapping.cs
@@ -68,7 +68,7 @@ protected virtual DateTime ConvertToDateTimeCompatibleValue(object value)
private static DateTime CheckDateTimeValue(DateTime dateTime)
{
- if (dateTime != default && dateTime < new DateTime(100,1,1))
+ if (dateTime != default && dateTime < new DateTime(100, 1, 1))
{
throw new InvalidOperationException($"The {nameof(DateTime)} value '{dateTime}' is smaller than the minimum supported value of '{new DateTime(100, 1, 1)}'.");
}
diff --git a/src/EFCore.Jet/Storage/Internal/JetDateTimeOffsetTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetDateTimeOffsetTypeMapping.cs
index 5149a1d5d..c4e3639ff 100644
--- a/src/EFCore.Jet/Storage/Internal/JetDateTimeOffsetTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetDateTimeOffsetTypeMapping.cs
@@ -1,8 +1,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using EntityFrameworkCore.Jet.Infrastructure.Internal;
-using Microsoft.Extensions.Options;
using System.Globalization;
+using System.Text;
namespace EntityFrameworkCore.Jet.Storage.Internal
{
@@ -10,6 +9,7 @@ public class JetDateTimeOffsetTypeMapping : DateTimeOffsetTypeMapping
{
private const string DateTimeOffsetFormatConst = @"'{0:yyyy-MM-ddTHH:mm:ss.fffffffzzz}'";
private const string DateTimeFormatConst = @"'{0:yyyy-MM-dd HH:mm:ss}'";
+ private static readonly CompositeFormat DateTimeFormat = CompositeFormat.Parse(DateTimeFormatConst);
public static new JetDateTimeOffsetTypeMapping Default { get; } = new JetDateTimeOffsetTypeMapping("datetime");
public JetDateTimeOffsetTypeMapping(
@@ -46,7 +46,7 @@ protected override string GenerateNonNullSqlLiteral(object value)
{
if (value is not DateTimeOffset offset) return base.GenerateNonNullSqlLiteral(value);
var dateTime = offset.Ticks == 0 ? DateTime.FromOADate(0) : offset.UtcDateTime;
- return $"CDATE({string.Format(CultureInfo.InvariantCulture, DateTimeFormatConst, dateTime)})";
+ return $"CDATE({string.Format(CultureInfo.InvariantCulture, DateTimeFormat, dateTime)})";
}
}
}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Storage/Internal/JetDateTimeTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetDateTimeTypeMapping.cs
index b711bec37..72c5162c5 100644
--- a/src/EFCore.Jet/Storage/Internal/JetDateTimeTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetDateTimeTypeMapping.cs
@@ -3,7 +3,6 @@
using System.Data;
using System.Globalization;
using System.Text;
-using EntityFrameworkCore.Jet.Infrastructure.Internal;
namespace EntityFrameworkCore.Jet.Storage.Internal
{
diff --git a/src/EFCore.Jet/Storage/Internal/JetDecimalTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetDecimalTypeMapping.cs
index 16840736a..894ed26e4 100644
--- a/src/EFCore.Jet/Storage/Internal/JetDecimalTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetDecimalTypeMapping.cs
@@ -1,5 +1,5 @@
-using System.Data;
using Microsoft.EntityFrameworkCore.Storage.Json;
+using System.Data;
namespace EntityFrameworkCore.Jet.Storage.Internal
{
diff --git a/src/EFCore.Jet/Storage/Internal/JetDoubleTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetDoubleTypeMapping.cs
index 3b0b1243d..981231fbc 100644
--- a/src/EFCore.Jet/Storage/Internal/JetDoubleTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetDoubleTypeMapping.cs
@@ -38,7 +38,7 @@ protected override string GenerateNonNullSqlLiteral(object value)
// double.MinValue renders as -1.7976931348623157E+308, which ACE accepts.
// ("R" was unreliable on .NET Framework — hence the old G17 advice — but has been
// shortest-round-trippable since .NET Core 3.0.)
- var doubleValue = Convert.ToDouble(value);
+ var doubleValue = Convert.ToDouble(value, CultureInfo.InvariantCulture);
var literal = doubleValue.ToString("R", CultureInfo.InvariantCulture);
return !literal.Contains('E')
@@ -50,4 +50,4 @@ protected override string GenerateNonNullSqlLiteral(object value)
: literal;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Storage/Internal/JetExecutionStrategy.cs b/src/EFCore.Jet/Storage/Internal/JetExecutionStrategy.cs
index 78afb0f39..57ff4f26f 100644
--- a/src/EFCore.Jet/Storage/Internal/JetExecutionStrategy.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetExecutionStrategy.cs
@@ -71,4 +71,4 @@ public virtual async Task ExecuteAsync(
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Storage/Internal/JetExecutionStrategyFactory.cs b/src/EFCore.Jet/Storage/Internal/JetExecutionStrategyFactory.cs
index 84f6925c2..862025011 100644
--- a/src/EFCore.Jet/Storage/Internal/JetExecutionStrategyFactory.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetExecutionStrategyFactory.cs
@@ -35,4 +35,4 @@ public class JetExecutionStrategyFactory(
protected override IExecutionStrategy CreateDefaultStrategy(ExecutionStrategyDependencies dependencies)
=> new JetExecutionStrategy(dependencies);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Storage/Internal/JetFloatTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetFloatTypeMapping.cs
index 28d982048..f727a45d7 100644
--- a/src/EFCore.Jet/Storage/Internal/JetFloatTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetFloatTypeMapping.cs
@@ -42,6 +42,6 @@ protected override string GenerateNonNullSqlLiteral(object value)
// G15, G7 rounds the extremes *down*, so it never yields an out-of-range literal.)
// - "R" is the shortest string that parses back exactly: 0.1f/85.55f stay clean, and
// float.MaxValue renders as 3.4028235E+38.
- => Convert.ToSingle(value).ToString("R", CultureInfo.InvariantCulture);
+ => Convert.ToSingle(value, CultureInfo.InvariantCulture).ToString("R", CultureInfo.InvariantCulture);
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Storage/Internal/JetIntTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetIntTypeMapping.cs
index 5e61d6e93..7fc5627e5 100644
--- a/src/EFCore.Jet/Storage/Internal/JetIntTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetIntTypeMapping.cs
@@ -8,7 +8,7 @@ public JetIntTypeMapping(string storeType)
: base(storeType, System.Data.DbType.Int32)
{
}
-
+
protected JetIntTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters)
{
diff --git a/src/EFCore.Jet/Storage/Internal/JetJsonTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetJsonTypeMapping.cs
index 2b0543699..ff7ac2fb4 100644
--- a/src/EFCore.Jet/Storage/Internal/JetJsonTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetJsonTypeMapping.cs
@@ -115,4 +115,4 @@ protected override string GenerateNonNullSqlLiteral(object value)
///
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new JetJsonTypeMapping(parameters);
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Storage/Internal/JetOdbcGuidTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetOdbcGuidTypeMapping.cs
index 738671559..b3986da03 100644
--- a/src/EFCore.Jet/Storage/Internal/JetOdbcGuidTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetOdbcGuidTypeMapping.cs
@@ -1,5 +1,3 @@
-using System.Data;
-
namespace EntityFrameworkCore.Jet.Storage.Internal
{
public class JetOdbcGuidTypeMapping : JetGuidTypeMapping
diff --git a/src/EFCore.Jet/Storage/Internal/JetShortTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetShortTypeMapping.cs
index 974af4c5a..0237a2d45 100644
--- a/src/EFCore.Jet/Storage/Internal/JetShortTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetShortTypeMapping.cs
@@ -56,4 +56,4 @@ protected override string GenerateNonNullSqlLiteral(object value)
{
return $"CINT({base.GenerateNonNullSqlLiteral(value)})";
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Storage/Internal/JetStringTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetStringTypeMapping.cs
index 8e0be35fc..bb8162fba 100644
--- a/src/EFCore.Jet/Storage/Internal/JetStringTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetStringTypeMapping.cs
@@ -1,7 +1,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System.Data;
using Microsoft.EntityFrameworkCore.Storage.Json;
+using System.Data;
namespace EntityFrameworkCore.Jet.Storage.Internal
{
@@ -24,7 +24,7 @@ public class JetStringTypeMapping : StringTypeMapping
///
public JetStringTypeMapping(
string? storeType = null,
- bool unicode = true ,
+ bool unicode = true,
int? size = null,
bool fixedLength = false,
StoreTypePostfix? storeTypePostfix = null,
@@ -32,7 +32,7 @@ public JetStringTypeMapping(
bool useKeyComparison = false)
: this(
new RelationalTypeMappingParameters(
- new CoreTypeMappingParameters(typeof(string), comparer: useKeyComparison ? CaseInsensitiveValueComparer : null, keyComparer:useKeyComparison ? CaseInsensitiveValueComparer : null, jsonValueReaderWriter: JsonStringReaderWriter.Instance),
+ new CoreTypeMappingParameters(typeof(string), comparer: useKeyComparison ? CaseInsensitiveValueComparer : null, keyComparer: useKeyComparison ? CaseInsensitiveValueComparer : null, jsonValueReaderWriter: JsonStringReaderWriter.Instance),
storeType ?? GetStoreName(fixedLength),
storeTypePostfix ?? StoreTypePostfix.Size,
(fixedLength
diff --git a/src/EFCore.Jet/Storage/Internal/JetTimeOnlyTypeMapping.cs b/src/EFCore.Jet/Storage/Internal/JetTimeOnlyTypeMapping.cs
index 2bbd61afe..a5b4a0c4e 100644
--- a/src/EFCore.Jet/Storage/Internal/JetTimeOnlyTypeMapping.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetTimeOnlyTypeMapping.cs
@@ -1,7 +1,5 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using EntityFrameworkCore.Jet.Infrastructure.Internal;
-
namespace EntityFrameworkCore.Jet.Storage.Internal
{
public class JetTimeOnlyTypeMapping : TimeOnlyTypeMapping
diff --git a/src/EFCore.Jet/Storage/Internal/JetTransactionFactory.cs b/src/EFCore.Jet/Storage/Internal/JetTransactionFactory.cs
index 3cb68131a..26c16173f 100644
--- a/src/EFCore.Jet/Storage/Internal/JetTransactionFactory.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetTransactionFactory.cs
@@ -18,6 +18,6 @@ public virtual RelationalTransaction Create(
Guid transactionId,
IDiagnosticsLogger logger,
bool transactionOwned)
- => new JetTransaction(connection, transaction, transactionId, logger, transactionOwned,Dependencies.SqlGenerationHelper);
+ => new JetTransaction(connection, transaction, transactionId, logger, transactionOwned, Dependencies.SqlGenerationHelper);
}
}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Storage/Internal/JetTransientExceptionDetector.cs b/src/EFCore.Jet/Storage/Internal/JetTransientExceptionDetector.cs
index 81c2517b9..84b7a9718 100644
--- a/src/EFCore.Jet/Storage/Internal/JetTransientExceptionDetector.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetTransientExceptionDetector.cs
@@ -14,7 +14,7 @@ public static bool ShouldRetryOn(Exception ex)
DataAccessProviderType dataAccessProviderType;
var exceptionFullName = ex.GetType().FullName;
-
+
if (exceptionFullName == "System.Data.OleDb.OleDbException")
dataAccessProviderType = DataAccessProviderType.OleDb;
else if (exceptionFullName == "System.Data.Odbc.OdbcException")
diff --git a/src/EFCore.Jet/Storage/Internal/JetTypeMappingSource.cs b/src/EFCore.Jet/Storage/Internal/JetTypeMappingSource.cs
index dc82dc71c..667ce8f2d 100644
--- a/src/EFCore.Jet/Storage/Internal/JetTypeMappingSource.cs
+++ b/src/EFCore.Jet/Storage/Internal/JetTypeMappingSource.cs
@@ -1,11 +1,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using EntityFrameworkCore.Jet.Data;
-using System.Collections;
-using System.Data;
-using System.Text.Json;
using EntityFrameworkCore.Jet.Infrastructure.Internal;
using EntityFrameworkCore.Jet.Internal;
+using System.Collections;
+using System.Data;
namespace EntityFrameworkCore.Jet.Storage.Internal
{
@@ -17,7 +16,6 @@ public class JetTypeMappingSource : RelationalTypeMappingSource
{
private readonly JetByteArrayTypeMapping _fixedLengthBinary = new("binary");
private readonly JetByteArrayTypeMapping _variableLengthBinary = new("varbinary");
- private readonly JetByteArrayTypeMapping _variableLengthMaxBinary = new("varbinary", storeTypePostfix: StoreTypePostfix.None);
private readonly JetByteArrayTypeMapping _unboundedBinary = new("longbinary", storeTypePostfix: StoreTypePostfix.None);
private readonly JetBoolTypeMapping _bit = new("bit"); // JET bits are not nullable
@@ -37,7 +35,7 @@ public class JetTypeMappingSource : RelationalTypeMappingSource
private readonly JetDecimalTypeMapping _decimal = new("decimal(18,2)", DbType.Decimal, precision: 18, scale: 2, StoreTypePostfix.PrecisionAndScale);
private readonly JetDecimalTypeMapping _decimal18_0 = new("decimal", DbType.Decimal, precision: 18, scale: 0);
- private readonly JetDecimalTypeMapping _currency = new("currency", DbType.Currency, storeTypePostfix:StoreTypePostfix.None);
+ private readonly JetDecimalTypeMapping _currency = new("currency", DbType.Currency, storeTypePostfix: StoreTypePostfix.None);
private readonly JetDateTimeTypeMapping _datetime;
private readonly JetDateTimeTypeMapping _dateasdatetime;
@@ -48,7 +46,6 @@ public class JetTypeMappingSource : RelationalTypeMappingSource
private readonly JetStringTypeMapping _fixedLengthUnicodeString = new("char", unicode: true);
private readonly JetStringTypeMapping _variableLengthUnicodeString = new("varchar", unicode: true);
- private readonly JetStringTypeMapping _variableLengthMaxUnicodeString = new("varchar", unicode: true, size: 255, storeTypePostfix: StoreTypePostfix.Size);
private readonly JetStringTypeMapping _unboundedUnicodeString = new("longchar", unicode: true, storeTypePostfix: StoreTypePostfix.None);
private readonly JetGuidTypeMapping _guid;
private readonly JetByteArrayTypeMapping _rowversion = new("varbinary", size: 8,
@@ -92,7 +89,7 @@ public JetTypeMappingSource(
_dateonly = JetDateOnlyTypeMapping.Default;
_timeonly = JetTimeOnlyTypeMapping.Default;
_timespan = JetTimeSpanTypeMapping.Default;
-
+
_storeTypeMappings
= new Dictionary(StringComparer.OrdinalIgnoreCase)
{
@@ -204,10 +201,10 @@ public JetTypeMappingSource(
// This is because we don't try to make a new type from this string and any max length value
// specified in the model, which means use of these strings is almost certainly an error, and
// if it is not an error, then using, for example, varbinary(1) will work instead.
- _disallowedMappings
- = new HashSet(StringComparer.OrdinalIgnoreCase)
- {
- };
+ // IDE0028's only fix here is a collection expression, which would drop the comparer.
+#pragma warning disable IDE0028
+ _disallowedMappings = new HashSet(StringComparer.OrdinalIgnoreCase);
+#pragma warning restore IDE0028
_options = options;
}
@@ -439,4 +436,4 @@ protected override void ValidateMapping(CoreTypeMapping? mapping, IProperty? pro
return parsedName;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/EFCore.Jet/Utilities/EnumerableExtensions.cs b/src/EFCore.Jet/Utilities/EnumerableExtensions.cs
index f418faf40..51d8424e5 100644
--- a/src/EFCore.Jet/Utilities/EnumerableExtensions.cs
+++ b/src/EFCore.Jet/Utilities/EnumerableExtensions.cs
@@ -27,4 +27,4 @@ public int GetHashCode(T obj)
=> 0; // force Equals
}
}
-}
+}
\ No newline at end of file
diff --git a/src/LibRed/Directory.Build.props b/src/LibRed/Directory.Build.props
index 24a641d21..8e63c583c 100644
--- a/src/LibRed/Directory.Build.props
+++ b/src/LibRed/Directory.Build.props
@@ -12,7 +12,10 @@
enableenable
- false
+
+ true$(NoWarn);CS1591
diff --git a/src/LibRed/LibRed.Ado/LibRedCommand.cs b/src/LibRed/LibRed.Ado/LibRedCommand.cs
index cf38f1000..772812020 100644
--- a/src/LibRed/LibRed.Ado/LibRedCommand.cs
+++ b/src/LibRed/LibRed.Ado/LibRedCommand.cs
@@ -7,7 +7,7 @@ namespace LibRed.Data;
/// ADO.NET command that runs SQL through the LibRed engine.
public sealed class LibRedCommand : DbCommand
{
- private readonly LibRedParameterCollection _parameters = new();
+ private readonly LibRedParameterCollection _parameters = [];
private string _commandText = string.Empty;
@@ -48,11 +48,16 @@ public override void Prepare() { }
protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
{
+ // SchemaOnly asks what the command WOULD return: the reader carries the columns and no rows, and
+ // nothing runs — not the INSERT in a batch, and not the stored procedure a name stands for.
+ if (behavior.HasFlag(CommandBehavior.SchemaOnly))
+ return new LibRedDataReader(DescribeBatch(), recordsAffected: -1, behavior, Connection);
+
// Route through Execute so the reader path also handles DML/DDL: EF Core runs inserts through
// ExecuteReader and inspects RecordsAffected. A query yields rows (RecordsAffected -1); an
// INSERT/CREATE runs and yields an empty result carrying its rows-affected count.
Engine.CommandResult result = ExecuteBatch();
- return new LibRedDataReader(result.Rows, result.RecordsAffected);
+ return new LibRedDataReader(result.Rows, result.RecordsAffected, behavior, Connection);
}
///
@@ -70,7 +75,7 @@ private Engine.CommandResult ExecuteBatch()
IReadOnlyDictionary parameters = BuildParameters();
Engine.CommandResult? last = null;
- foreach (string statement in SplitStatements(CommandText))
+ foreach (string statement in SplitStatements(StatementText()))
{
// A fragment holding no statement (only comments) is skipped rather than run: it must not become
// the batch's last result, or `INSERT …; -- done` would report the comment's zero rows instead of
@@ -104,15 +109,66 @@ private Engine.CommandResult ExecuteBatch()
return last ?? new Engine.CommandResult(Engine.Execution.ResultSet.Empty, RecordsAffected: 0);
}
+ ///
+ /// The shape the command's batch would return, running none of it. As in the
+ /// batch's result is its last statement's — and since nothing runs, that is the only one worth
+ /// describing.
+ ///
+ private Engine.Execution.ResultSet DescribeBatch()
+ {
+ ValidateTransaction();
+ Engine.QueryEngine engine = RequireEngine();
+
+ string? last = SplitStatements(StatementText()).LastOrDefault(s => !engine.IsStatementless(s));
+ return last is null
+ ? Engine.Execution.ResultSet.Empty
+ : engine.Describe(last, BuildParameters());
+ }
+
+ ///
+ /// The SQL this command runs, which for the two non-text command types is built from the name in
+ /// : a stored procedure — an Access stored query — is executed by name with each
+ /// of the command's parameters bound to the procedure's parameter of the same name, and a table is read
+ /// whole. A name is bracket-quoted, so one containing spaces works as it does in Access.
+ ///
+ private string StatementText() => CommandType switch
+ {
+ CommandType.Text => CommandText,
+ CommandType.TableDirect => $"SELECT * FROM {Quote(CommandText)}",
+ CommandType.StoredProcedure => BuildExecute(),
+ _ => throw new NotSupportedException($"CommandType.{CommandType} is not supported."),
+ };
+
+ /// An EXECUTE for the named stored query, naming each parameter so the order the caller
+ /// added them in does not matter. A procedure taking none runs bare.
+ private string BuildExecute()
+ {
+ var arguments = _parameters.Cast()
+ .Where(p => p.Direction is ParameterDirection.Input or ParameterDirection.InputOutput)
+ .Select(p => $"{Quote(p.ParameterName.TrimStart('@'))} = @{p.ParameterName.TrimStart('@')}")
+ .ToList();
+
+ return arguments.Count == 0
+ ? $"EXECUTE {Quote(CommandText)}"
+ : $"EXECUTE {Quote(CommandText)} {string.Join(", ", arguments)}";
+ }
+
+ private static string Quote(string name) => $"[{name.Trim().Trim('[', ']')}]";
+
///
/// Splits a batch on top-level ; separators, ignoring semicolons inside string literals
/// ('…' / "…") and quoted identifiers ([…] / `…`). Blank statements
/// (e.g. a trailing ;) are dropped. The single-statement common case returns one item.
+ /// An Access PARAMETERS …; clause is not a statement of its own: its semicolon
+ /// ends the clause, and the query it declares for follows. Such a fragment is carried onto the next one
+ /// so the pair reaches the engine as the one statement it is — the form every stored parameterized
+ /// query reads back as.
///
public static IEnumerable SplitStatements(string sql)
{
int start = 0;
char quote = '\0'; // the closing delimiter we're inside, or '\0' at top level
+ string prefix = string.Empty; // a PARAMETERS clause awaiting its query
for (int i = 0; i < sql.Length; i++)
{
char c = sql[i];
@@ -125,15 +181,26 @@ public static IEnumerable SplitStatements(string sql)
else if (c == ';')
{
string part = sql[start..i].Trim();
- if (part.Length > 0) yield return part;
+ if (part.Length > 0)
+ {
+ if (IsParametersClause(part)) prefix += part + "; ";
+ else { yield return prefix + part; prefix = string.Empty; }
+ }
start = i + 1;
}
}
string tail = sql[start..].Trim();
- if (tail.Length > 0) yield return tail;
+ // A clause with nothing after it is yielded as-is, so the engine reports it rather than the batch
+ // silently running nothing.
+ if (tail.Length > 0) yield return prefix + tail;
+ else if (prefix.Length > 0) yield return prefix.TrimEnd(' ', ';');
}
+ private static bool IsParametersClause(string statement) =>
+ statement.StartsWith("PARAMETERS", StringComparison.OrdinalIgnoreCase)
+ && (statement.Length == "PARAMETERS".Length || char.IsWhiteSpace(statement["PARAMETERS".Length]));
+
private Engine.QueryEngine RequireEngine() =>
Connection?.Engine ?? throw new InvalidOperationException("Connection is not open.");
@@ -153,7 +220,7 @@ private void ValidateTransaction()
/// Snapshots the command's parameters as a name→value map for the engine, clipping each to its
/// declared size (see ) and translating to
/// a SQL null.
- private IReadOnlyDictionary BuildParameters()
+ private Dictionary BuildParameters()
{
var map = new Dictionary(StringComparer.OrdinalIgnoreCase);
foreach (LibRedParameter parameter in _parameters.Cast())
@@ -198,4 +265,4 @@ private void ValidateTransaction()
private static TimeSpan Milliseconds(TimeSpan t) =>
TimeSpan.FromTicks(t.Ticks - t.Ticks % TimeSpan.TicksPerMillisecond);
-}
+}
\ No newline at end of file
diff --git a/src/LibRed/LibRed.Ado/LibRedConnection.cs b/src/LibRed/LibRed.Ado/LibRedConnection.cs
index e7bde2f35..603c408dc 100644
--- a/src/LibRed/LibRed.Ado/LibRedConnection.cs
+++ b/src/LibRed/LibRed.Ado/LibRedConnection.cs
@@ -1,7 +1,7 @@
+using LibRed.Engine;
using System.Data;
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
-using LibRed.Engine;
namespace LibRed.Data;
@@ -79,7 +79,7 @@ internal void ReconcileSqlTransactionControl()
internal LibRed.IO.Savepoint CreateSavepoint() =>
(_database ?? throw new InvalidOperationException("The connection is not open.")).CreateSavepoint();
- /// Rolls the active transaction back to a savepoint (called by ).
+ /// Rolls the active transaction back to a savepoint (called by ).
internal void RollbackToSavepoint(LibRed.IO.Savepoint savepoint) => _database?.RollbackToSavepoint(savepoint);
/// Releases a savepoint in the active transaction (called by ).
@@ -125,6 +125,7 @@ public static string GetConnectionString(string fileNameOrConnectionString)
/// Produces an .accdb that LibRed reads and writes fully; the remaining Access-compatibility
/// system tables are still being filled in.
///
+ /// Names the file to create, in the same form a connection uses.
/// The database's default text collating order, written to page 0 and inherited
/// by every column created in it. Defaults to General-Legacy (the order the engine writes); pass
/// for the "General" order Access 2010+ offers.
@@ -239,6 +240,23 @@ public override void Close()
OnStateChange(new StateChangeEventArgs(ConnectionState.Open, ConnectionState.Closed));
}
+ /// The names of the metadata collections this provider serves.
+ public override DataTable GetSchema() => GetSchema(LibRedSchema.MetaDataCollections, null);
+
+ ///
+ public override DataTable GetSchema(string collectionName) => GetSchema(collectionName, null);
+
+ /// A metadata collection, filtered by . The collections match
+ /// the ones ACE's OLE DB provider serves, column for column, so code written against that provider reads
+ /// the same metadata here. A restriction naming a catalog or schema matches everything: a Jet file holds
+ /// one nameless catalog and no schemas.
+ public override DataTable GetSchema(string collectionName, string?[]? restrictionValues)
+ {
+ if (_database is null || _state != ConnectionState.Open)
+ throw new InvalidOperationException("The connection must be open to read schema metadata.");
+ return LibRedSchema.Get(collectionName, restrictionValues, _database);
+ }
+
public override void ChangeDatabase(string databaseName) =>
throw new NotSupportedException("A Jet/ACE connection maps to a single file.");
@@ -296,7 +314,9 @@ private static string ParseDataSource(string connectionString)
}
private static string? TryGetString(DbConnectionStringBuilder builder, string key) =>
- builder.TryGetValue(key, out object? value) ? Convert.ToString(value) : null;
+ builder.TryGetValue(key, out object? value)
+ ? Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture)
+ : null;
///
/// Resolves to a full path and defaults to a ".accdb" extension - matches EFCore.Jet.Data's
@@ -312,4 +332,4 @@ private static string ExpandPath(string path)
var fullPath = Path.GetFullPath(path);
return string.IsNullOrEmpty(Path.GetExtension(fullPath)) ? fullPath + ".accdb" : fullPath;
}
-}
+}
\ No newline at end of file
diff --git a/src/LibRed/LibRed.Ado/LibRedConnectionStringBuilder.cs b/src/LibRed/LibRed.Ado/LibRedConnectionStringBuilder.cs
index c4649bdab..ee20cd8dd 100644
--- a/src/LibRed/LibRed.Ado/LibRedConnectionStringBuilder.cs
+++ b/src/LibRed/LibRed.Ado/LibRedConnectionStringBuilder.cs
@@ -9,7 +9,11 @@ namespace LibRed.Data;
/// DataAccessProviderType/ODBC/OLE DB and throw for any other builder type), this reads
/// and writes the "Data Source" key directly - no provider-type gate to fall into.
///
+// CA1010 asks for ICollection; the non-generic ICollection comes from DbConnectionStringBuilder, which
+// every ADO.NET provider inherits as-is.
+#pragma warning disable CA1010
public class LibRedConnectionStringBuilder : DbConnectionStringBuilder
+#pragma warning restore CA1010
{
///
/// Reads "Data Source", "DataSource", or "DBQ" (whichever is present - matches
@@ -25,4 +29,4 @@ public string? DataSource
: null;
set => this["Data Source"] = value;
}
-}
+}
\ No newline at end of file
diff --git a/src/LibRed/LibRed.Ado/LibRedDataReader.cs b/src/LibRed/LibRed.Ado/LibRedDataReader.cs
index fee4e8f64..c5a4a07fd 100644
--- a/src/LibRed/LibRed.Ado/LibRedDataReader.cs
+++ b/src/LibRed/LibRed.Ado/LibRedDataReader.cs
@@ -1,26 +1,51 @@
+using LibRed.Engine.Execution;
using System.Collections;
+using System.Collections.ObjectModel;
+using System.Data;
using System.Data.Common;
-using LibRed.Engine.Execution;
+using System.Globalization;
namespace LibRed.Data;
/// Forward-only reader projecting an engine as ADO.NET rows.
-public sealed class LibRedDataReader : DbDataReader
+// CA1010 asks for IEnumerable; the non-generic IEnumerable comes from DbDataReader, which every ADO.NET
+// provider inherits as-is, and a generic enumerator over a forward-only reader has no meaning.
+#pragma warning disable CA1010
+public sealed class LibRedDataReader : DbDataReader, IDbColumnSchemaGenerator
+#pragma warning restore CA1010
{
private readonly ResultSet _result;
private readonly IEnumerator
public const int ObjectAlreadyExists = 2714;
-}
+}
\ No newline at end of file
diff --git a/src/LibRed/LibRed.Ado/LibRedFactory.cs b/src/LibRed/LibRed.Ado/LibRedFactory.cs
index 5a8cfa7b4..3d610be9c 100644
--- a/src/LibRed/LibRed.Ado/LibRedFactory.cs
+++ b/src/LibRed/LibRed.Ado/LibRedFactory.cs
@@ -24,4 +24,4 @@ private LibRedFactory() { }
/// rather than sharing that generic extension surface.
///
public override DbConnectionStringBuilder CreateConnectionStringBuilder() => new LibRedConnectionStringBuilder();
-}
+}
\ No newline at end of file
diff --git a/src/LibRed/LibRed.Ado/LibRedParameter.cs b/src/LibRed/LibRed.Ado/LibRedParameter.cs
index ec7021279..80ce2b5d7 100644
--- a/src/LibRed/LibRed.Ado/LibRedParameter.cs
+++ b/src/LibRed/LibRed.Ado/LibRedParameter.cs
@@ -125,4 +125,4 @@ private static byte DecimalPrecision(decimal d)
char => DbType.StringFixedLength,
_ => DbType.Object,
};
-}
+}
\ No newline at end of file
diff --git a/src/LibRed/LibRed.Ado/LibRedParameterCollection.cs b/src/LibRed/LibRed.Ado/LibRedParameterCollection.cs
index e17860754..09aa1328d 100644
--- a/src/LibRed/LibRed.Ado/LibRedParameterCollection.cs
+++ b/src/LibRed/LibRed.Ado/LibRedParameterCollection.cs
@@ -4,7 +4,11 @@
namespace LibRed.Data;
/// Parameter collection backed by a simple list.
+// CA1010 asks for IList; the non-generic IList comes from DbParameterCollection, whose members every
+// ADO.NET provider overrides as-is — SqlParameterCollection included.
+#pragma warning disable CA1010
public sealed class LibRedParameterCollection : DbParameterCollection
+#pragma warning restore CA1010
{
private readonly List _items = [];
@@ -52,4 +56,4 @@ public override int IndexOf(string parameterName) =>
protected override void SetParameter(string parameterName, DbParameter value) =>
_items[IndexOf(parameterName)] = (LibRedParameter)value;
-}
+}
\ No newline at end of file
diff --git a/src/LibRed/LibRed.Ado/LibRedSchema.cs b/src/LibRed/LibRed.Ado/LibRedSchema.cs
new file mode 100644
index 000000000..d18b8e1d9
--- /dev/null
+++ b/src/LibRed/LibRed.Ado/LibRedSchema.cs
@@ -0,0 +1,310 @@
+using LibRed.Engine.Schema;
+using LibRed.Sql;
+using System.Data;
+using System.Data.Common;
+
+namespace LibRed.Data;
+
+///
+/// ADO.NET's metadata collections for an open connection. The catalog collections come from
+/// , shaped as ACE's OLE DB provider serves them; the five collections the
+/// framework defines (MetaDataCollections, DataSourceInformation, DataTypes,
+/// Restrictions, ReservedWords) are built here, describing LibRed itself.
+///
+internal static class LibRedSchema
+{
+ public const string MetaDataCollections = "MetaDataCollections";
+ public const string DataSourceInformation = "DataSourceInformation";
+ public const string DataTypes = "DataTypes";
+ public const string Restrictions = "Restrictions";
+ public const string ReservedWords = "ReservedWords";
+
+ /// Every collection served, the framework's own first, then the catalog ones — the order ACE
+ /// lists them in.
+ public static IReadOnlyList Names { get; } =
+ [MetaDataCollections, DataSourceInformation, DataTypes, Restrictions, ReservedWords, .. SchemaRowsets.Names];
+
+ /// How many identifier parts a collection's rows are named by, as ACE reports: a column takes
+ /// catalog, schema, table and column; a table one fewer.
+ private static readonly Dictionary IdentifierParts = new(StringComparer.OrdinalIgnoreCase)
+ {
+ ["Tables"] = 3,
+ ["Columns"] = 4,
+ ["Indexes"] = 4,
+ ["Views"] = 3,
+ ["Procedures"] = 3,
+ ["ForeignKeys"] = 3,
+ ["PrimaryKeys"] = 3,
+ ["TableConstraints"] = 3,
+ ["KeyColumnUsage"] = 4,
+ ["ConstraintColumnUsage"] = 4,
+ ["ReferentialConstraints"] = 3,
+ ["CheckConstraints"] = 3,
+ ["Statistics"] = 3,
+ ["ProcedureParameters"] = 4,
+ ["ViewColumns"] = 4,
+ };
+
+ /// Each collection's restrictions, in ACE's order — note that Indexes takes the table name last,
+ /// after the index name and type. A restriction names the column its value filters.
+ private static readonly (string Collection, string Restriction)[] RestrictionList =
+ [
+ ("Columns", "TABLE_CATALOG"), ("Columns", "TABLE_SCHEMA"), ("Columns", "TABLE_NAME"), ("Columns", "COLUMN_NAME"),
+ ("Indexes", "TABLE_CATALOG"), ("Indexes", "TABLE_SCHEMA"), ("Indexes", "INDEX_NAME"), ("Indexes", "TYPE"),
+ ("Indexes", "TABLE_NAME"),
+ ("Procedures", "PROCEDURE_CATALOG"), ("Procedures", "PROCEDURE_SCHEMA"), ("Procedures", "PROCEDURE_NAME"),
+ ("Procedures", "PROCEDURE_TYPE"),
+ ("Tables", "TABLE_CATALOG"), ("Tables", "TABLE_SCHEMA"), ("Tables", "TABLE_NAME"), ("Tables", "TABLE_TYPE"),
+ ("Views", "TABLE_CATALOG"), ("Views", "TABLE_SCHEMA"), ("Views", "TABLE_NAME"),
+ // The relational rowsets, restricted as OLE DB restricts them.
+ ("ForeignKeys", "PK_TABLE_CATALOG"), ("ForeignKeys", "PK_TABLE_SCHEMA"), ("ForeignKeys", "PK_TABLE_NAME"),
+ ("ForeignKeys", "FK_TABLE_CATALOG"), ("ForeignKeys", "FK_TABLE_SCHEMA"), ("ForeignKeys", "FK_TABLE_NAME"),
+ ("PrimaryKeys", "TABLE_CATALOG"), ("PrimaryKeys", "TABLE_SCHEMA"), ("PrimaryKeys", "TABLE_NAME"),
+ ("TableConstraints", "CONSTRAINT_CATALOG"), ("TableConstraints", "CONSTRAINT_SCHEMA"),
+ ("TableConstraints", "CONSTRAINT_NAME"), ("TableConstraints", "TABLE_CATALOG"),
+ ("TableConstraints", "TABLE_SCHEMA"), ("TableConstraints", "TABLE_NAME"), ("TableConstraints", "CONSTRAINT_TYPE"),
+ ("KeyColumnUsage", "CONSTRAINT_CATALOG"), ("KeyColumnUsage", "CONSTRAINT_SCHEMA"),
+ ("KeyColumnUsage", "CONSTRAINT_NAME"), ("KeyColumnUsage", "TABLE_CATALOG"),
+ ("KeyColumnUsage", "TABLE_SCHEMA"), ("KeyColumnUsage", "TABLE_NAME"), ("KeyColumnUsage", "COLUMN_NAME"),
+ ("ConstraintColumnUsage", "TABLE_CATALOG"), ("ConstraintColumnUsage", "TABLE_SCHEMA"),
+ ("ConstraintColumnUsage", "TABLE_NAME"), ("ConstraintColumnUsage", "COLUMN_NAME"),
+ ("ConstraintColumnUsage", "CONSTRAINT_CATALOG"), ("ConstraintColumnUsage", "CONSTRAINT_SCHEMA"),
+ ("ConstraintColumnUsage", "CONSTRAINT_NAME"),
+ ("ReferentialConstraints", "CONSTRAINT_CATALOG"), ("ReferentialConstraints", "CONSTRAINT_SCHEMA"),
+ ("ReferentialConstraints", "CONSTRAINT_NAME"),
+ ("CheckConstraints", "CONSTRAINT_CATALOG"), ("CheckConstraints", "CONSTRAINT_SCHEMA"),
+ ("CheckConstraints", "CONSTRAINT_NAME"),
+ ("Statistics", "TABLE_CATALOG"), ("Statistics", "TABLE_SCHEMA"), ("Statistics", "TABLE_NAME"),
+ ("ProcedureParameters", "PROCEDURE_CATALOG"), ("ProcedureParameters", "PROCEDURE_SCHEMA"),
+ ("ProcedureParameters", "PROCEDURE_NAME"), ("ProcedureParameters", "PARAMETER_NAME"),
+ ("ViewColumns", "VIEW_CATALOG"), ("ViewColumns", "VIEW_SCHEMA"), ("ViewColumns", "VIEW_NAME"),
+ ("ViewColumns", "COLUMN_NAME"),
+ ];
+
+ /// A Jet file holds one nameless catalog and no schemas, so these restrictions match everything
+ /// rather than filtering on the null every row carries.
+ private static bool IsCatalogOrSchema(string restriction) =>
+ restriction.EndsWith("_CATALOG", StringComparison.Ordinal) || restriction.EndsWith("_SCHEMA", StringComparison.Ordinal);
+
+ /// Builds a collection, filtered by (a null entry, or none at
+ /// all, matches everything).
+ public static DataTable Get(string collection, string?[]? restrictions, JetDatabase database)
+ {
+ string name = Names.FirstOrDefault(n => n.Equals(collection, StringComparison.OrdinalIgnoreCase))
+ ?? throw new ArgumentException($"There is no metadata collection named '{collection}'.", nameof(collection));
+
+ var allowed = RestrictionList.Where(r => r.Collection.Equals(name, StringComparison.OrdinalIgnoreCase)).ToList();
+ if (restrictions is not null && restrictions.Length > allowed.Count)
+ throw new ArgumentException(
+ $"The '{name}' collection takes {allowed.Count} restrictions; {restrictions.Length} were supplied.",
+ nameof(restrictions));
+
+ return name switch
+ {
+ MetaDataCollections => BuildMetaDataCollections(),
+ DataSourceInformation => BuildDataSourceInformation(database),
+ DataTypes => BuildDataTypes(database),
+ Restrictions => BuildRestrictions(),
+ ReservedWords => BuildReservedWords(),
+ _ => BuildCatalogCollection(name, restrictions, database),
+ };
+ }
+
+ private static DataTable BuildCatalogCollection(string name, string?[]? restrictions, JetDatabase database)
+ {
+ var table = new DataTable(name) { Locale = System.Globalization.CultureInfo.InvariantCulture };
+ IReadOnlyList columns = SchemaRowsets.ColumnsOf(name);
+ IReadOnlyList types = SchemaRowsets.ColumnTypesOf(name);
+ for (int i = 0; i < columns.Count; i++)
+ table.Columns.Add(columns[i], types[i]);
+
+ var allowed = RestrictionList.Where(r => r.Collection.Equals(name, StringComparison.OrdinalIgnoreCase)).ToList();
+ foreach (object?[] row in SchemaRowsets.Rows(name, database))
+ {
+ if (!Matches(row, columns, allowed, restrictions)) continue;
+ table.Rows.Add(row.Select(v => v ?? DBNull.Value).ToArray());
+ }
+
+ return table;
+ }
+
+ /// Whether a row passes every supplied restriction. Values compare as text, case-insensitively,
+ /// because Jet identifiers are.
+ private static bool Matches(
+ object?[] row, IReadOnlyList columns, List<(string Collection, string Restriction)> allowed, string?[]? restrictions)
+ {
+ if (restrictions is null) return true;
+
+ for (int i = 0; i < restrictions.Length; i++)
+ {
+ if (restrictions[i] is not { } wanted) continue;
+ string restriction = allowed[i].Restriction;
+ if (IsCatalogOrSchema(restriction)) continue;
+
+ int column = IndexOf(columns, restriction);
+ if (column < 0) continue;
+ if (!string.Equals(row[column]?.ToString(), wanted, StringComparison.OrdinalIgnoreCase)) return false;
+ }
+
+ return true;
+ }
+
+ private static int IndexOf(IReadOnlyList columns, string name)
+ {
+ for (int i = 0; i < columns.Count; i++)
+ if (columns[i].Equals(name, StringComparison.OrdinalIgnoreCase)) return i;
+ return -1;
+ }
+
+ private static DataTable BuildMetaDataCollections()
+ {
+ var table = Empty(MetaDataCollections,
+ ("CollectionName", typeof(string)), ("NumberOfRestrictions", typeof(int)), ("NumberOfIdentifierParts", typeof(int)));
+ foreach (string name in Names)
+ table.Rows.Add(name,
+ RestrictionList.Count(r => r.Collection.Equals(name, StringComparison.OrdinalIgnoreCase)),
+ IdentifierParts.GetValueOrDefault(name));
+ return table;
+ }
+
+ private static DataTable BuildRestrictions()
+ {
+ var table = Empty(Restrictions,
+ ("CollectionName", typeof(string)), ("RestrictionName", typeof(string)),
+ ("RestrictionDefault", typeof(string)), ("RestrictionNumber", typeof(int)));
+ string? collection = null;
+ int number = 0;
+ foreach ((string name, string restriction) in RestrictionList)
+ {
+ number = name == collection ? number + 1 : 1;
+ collection = name;
+ table.Rows.Add(name, restriction, DBNull.Value, number);
+ }
+ return table;
+ }
+
+ private static DataTable BuildReservedWords()
+ {
+ var table = Empty(ReservedWords, ("ReservedWord", typeof(string)));
+ foreach (string word in SqlKeywords.Reserved)
+ table.Rows.Add(word);
+ return table;
+ }
+
+ private static DataTable BuildDataSourceInformation(JetDatabase database)
+ {
+ var table = Empty(DataSourceInformation,
+ ("CompositeIdentifierSeparatorPattern", typeof(string)), ("DataSourceProductName", typeof(string)),
+ ("DataSourceProductVersion", typeof(string)), ("DataSourceProductVersionNormalized", typeof(string)),
+ ("GroupByBehavior", typeof(GroupByBehavior)), ("IdentifierPattern", typeof(string)),
+ ("IdentifierCase", typeof(IdentifierCase)), ("OrderByColumnsInSelect", typeof(bool)),
+ ("ParameterMarkerFormat", typeof(string)), ("ParameterMarkerPattern", typeof(string)),
+ ("ParameterNameMaxLength", typeof(int)), ("ParameterNamePattern", typeof(string)),
+ ("QuotedIdentifierPattern", typeof(string)), ("QuotedIdentifierCase", typeof(IdentifierCase)),
+ ("StatementSeparatorPattern", typeof(string)), ("StringLiteralPattern", typeof(string)),
+ ("SupportedJoinOperators", typeof(SupportedJoinOperators)));
+
+ table.Rows.Add(
+ DBNull.Value, // no catalog or schema, so nothing separates identifier parts
+ "LibRed",
+ FormatName(database.Format.Version),
+ // Sortable and fixed-width, as the collection requires: the file's format version byte, so a later
+ // format never sorts below an earlier one.
+ $"{(byte)database.Format.Version:00}.00.0000",
+ GroupByBehavior.MustContainAll,
+ @"[^ ][^\.!`\[\]]*", // as ACE describes a Jet identifier
+ IdentifierCase.Insensitive,
+ false, // ORDER BY may name a column the SELECT does not
+ "?",
+ @"\?",
+ 0, // parameters are positional, so a name has no length
+ DBNull.Value,
+ "`(([^`]|``)*)`", // backticks, doubled to escape one
+ IdentifierCase.Insensitive,
+ ";",
+ "'(([^']|'')*)'",
+ SupportedJoinOperators.Inner | SupportedJoinOperators.LeftOuter
+ | SupportedJoinOperators.RightOuter | SupportedJoinOperators.FullOuter);
+ return table;
+ }
+
+ /// The types a column can have, as ACE lists them — its own type names, each with the OLE DB type
+ /// code, the CLR type it reads as, and the literal syntax — plus the two types ACE's own list never learned.
+ ///
+ private static DataTable BuildDataTypes(JetDatabase database)
+ {
+ var table = Empty(DataTypes,
+ ("TypeName", typeof(string)), ("ProviderDbType", typeof(int)), ("ColumnSize", typeof(long)),
+ ("CreateFormat", typeof(string)), ("CreateParameters", typeof(string)), ("DataType", typeof(string)),
+ ("IsAutoIncrementable", typeof(bool)), ("IsBestMatch", typeof(bool)), ("IsCaseSensitive", typeof(bool)),
+ ("IsFixedLength", typeof(bool)), ("IsFixedPrecisionScale", typeof(bool)), ("IsLong", typeof(bool)),
+ ("IsNullable", typeof(bool)), ("IsSearchable", typeof(bool)), ("IsSearchableWithLike", typeof(bool)),
+ ("IsUnsigned", typeof(bool)), ("MaximumScale", typeof(short)), ("MinimumScale", typeof(short)),
+ ("IsConcurrencyType", typeof(bool)), ("IsLiteralSupported", typeof(bool)),
+ ("LiteralPrefix", typeof(string)), ("LiteralSuffix", typeof(string)), ("NativeDataType", typeof(short)));
+
+ void Add(string name, int providerType, long size, string? createParameters, Type clr, bool autoIncrement,
+ bool fixedLength, bool fixedPrecisionScale, bool isLong, bool nullable, bool unsigned,
+ short? maximumScale, short? minimumScale, string? prefix, string? suffix, short nativeType) =>
+ table.Rows.Add(name, providerType, size, DBNull.Value, (object?)createParameters ?? DBNull.Value,
+ clr.FullName, autoIncrement, DBNull.Value, false, fixedLength, fixedPrecisionScale, isLong,
+ nullable, true, true, unsigned,
+ (object?)maximumScale ?? DBNull.Value, (object?)minimumScale ?? DBNull.Value,
+ DBNull.Value, DBNull.Value, (object?)prefix ?? DBNull.Value, (object?)suffix ?? DBNull.Value, nativeType);
+
+ Add("Short", 2, 5, null, typeof(short), false, true, true, false, true, false, null, null, null, null, 2);
+ Add("Long", 3, 10, null, typeof(int), true, true, true, false, true, false, null, null, null, null, 3);
+ Add("Single", 4, 7, null, typeof(float), false, true, false, false, true, false, null, null, null, null, 4);
+ Add("Double", 5, 15, null, typeof(double), false, true, false, false, true, false, null, null, null, null, 5);
+ Add("Currency", 6, 19, null, typeof(decimal), false, true, true, false, true, false, null, null, null, null, 6);
+ Add("DateTime", 7, 8, null, typeof(DateTime), false, true, true, false, true, true, null, null, "#", "#", 7);
+ Add("Bit", 11, 2, null, typeof(bool), false, true, true, false, false, true, null, null, null, null, 11);
+ Add("Byte", 17, 3, null, typeof(byte), false, true, true, false, true, true, null, null, null, null, 17);
+ Add("GUID", 72, 16, null, typeof(Guid), false, true, true, false, true, true, null, null, null, null, 72);
+ Add("BigBinary", 204, 4000, null, typeof(byte[]), false, false, false, false, true, true, null, null, "0x", null, 128);
+ Add("LongBinary", 205, 1073741823, null, typeof(byte[]), false, false, true, true, true, true, null, null, "0x", null, 128);
+ Add("VarBinary", 204, 510, "max length", typeof(byte[]), false, false, true, false, true, true, null, null, "0x", null, 128);
+ Add("LongText", 203, 536870910, null, typeof(string), false, false, true, true, true, true, null, null, "'", "'", 130);
+ Add("VarChar", 202, 255, "max length", typeof(string), false, false, true, false, true, true, null, null, "'", "'", 130);
+ Add("Decimal", 131, 28, "precision,scale", typeof(decimal), false, true, true, false, true, false, 28, 0, null, null, 131);
+
+ // The fixed-length text and binary forms, which ACE's list omits although the engine has both: a
+ // CHAR(n)/NCHAR(n) column is stored fixed, and so is BINARY(n) — even though ACE then reports the
+ // binary one as variable. Without these rows a fixed column's TYPE_NAME would have nothing to join to.
+ Add("Char", 130, 255, "max length", typeof(string), false, true, true, false, true, true, null, null, "'", "'", 130);
+ Add("Binary", 128, 510, "max length", typeof(byte[]), false, true, true, false, true, true, null, null, "0x", null, 128);
+
+ // BIGINT and DATETIME2 arrived after ACE's OLE DB provider, whose list still omits them although it
+ // reports columns of both. Any ACCDB can hold them: a file too old for one is raised to the format it
+ // needs when the column is created, which is what Access itself does. A Jet 3/4 .mdb cannot, so an
+ // .mdb lists exactly the types ACE does.
+ if (database.Format.Version >= LibRed.Formats.JetVersion.Version12_2007)
+ {
+ Add("BigInt", 20, 19, null, typeof(long), false, true, true, false, true, false, null, null, null, null, 20);
+ Add("DateTime2", 135, 42, null, typeof(DateTime), false, true, true, false, true, true, null, null, "#", "#", 135);
+ }
+
+ return table;
+ }
+
+ /// The engine version the open file's format belongs to, named as Access names it.
+ private static string FormatName(LibRed.Formats.JetVersion version) => version switch
+ {
+ LibRed.Formats.JetVersion.Version3 => "Jet 3 (Access 97)",
+ LibRed.Formats.JetVersion.Version4 => "Jet 4 (Access 2000-2003)",
+ LibRed.Formats.JetVersion.Version12_2007 => "ACE 12 (Access 2007)",
+ LibRed.Formats.JetVersion.Version14_2010 => "ACE 14 (Access 2010)",
+ LibRed.Formats.JetVersion.Version15_2013 => "ACE 15 (Access 2013)",
+ LibRed.Formats.JetVersion.Version16_2016 => "ACE 16 (Access 2016)",
+ LibRed.Formats.JetVersion.Version17_2019 => "ACE 17 (Access 2019)",
+ _ => version.ToString(),
+ };
+
+ private static DataTable Empty(string name, params (string Name, Type Type)[] columns)
+ {
+ var table = new DataTable(name) { Locale = System.Globalization.CultureInfo.InvariantCulture };
+ foreach ((string column, Type type) in columns)
+ table.Columns.Add(column, type);
+ return table;
+ }
+}
\ No newline at end of file
diff --git a/src/LibRed/LibRed.Ado/LibRedTransaction.cs b/src/LibRed/LibRed.Ado/LibRedTransaction.cs
index ce4cb8d6a..318db9e7a 100644
--- a/src/LibRed/LibRed.Ado/LibRedTransaction.cs
+++ b/src/LibRed/LibRed.Ado/LibRedTransaction.cs
@@ -1,13 +1,13 @@
+using LibRed.IO;
using System.Data;
using System.Data.Common;
-using LibRed.IO;
namespace LibRed.Data;
///
/// A database transaction over LibRed's deferred-write page overlay. Writes are buffered in the overlay
/// rather than going to disk, so is what makes them visible at all, and
-/// simply discards the overlay — there is nothing on disk to restore. An uncommitted
+/// simply discards the overlay — there is nothing on disk to restore. An uncommitted
/// transaction that is disposed rolls back, which is what gives EF Core's shared-database tests their
/// per-test isolation.
///
@@ -18,7 +18,11 @@ public sealed class LibRedTransaction : DbTransaction
// Named savepoints opened in this transaction (EF names them for nested SaveChanges). Maps the name to the
// engine's savepoint handle.
+ // The comparer is explicit because savepoint names are matched case-sensitively, which IDE0028's
+ // collection expression would leave to the default.
+#pragma warning disable IDE0028
private readonly Dictionary _savepoints = new(StringComparer.Ordinal);
+#pragma warning restore IDE0028
internal LibRedTransaction(LibRedConnection connection, IsolationLevel isolationLevel, int openedAtDepth)
{
@@ -124,4 +128,4 @@ protected override void Dispose(bool disposing)
_connection = null;
base.Dispose(disposing);
}
-}
+}
\ No newline at end of file
diff --git a/src/LibRed/LibRed.Core/Catalog/Collation.cs b/src/LibRed/LibRed.Core/Catalog/Collation.cs
index c9e714cdc..fd3894dc4 100644
--- a/src/LibRed/LibRed.Core/Catalog/Collation.cs
+++ b/src/LibRed/LibRed.Core/Catalog/Collation.cs
@@ -550,4 +550,4 @@ public bool IsIndexKeyEncodable
return Storage.JetLocaleTailoring.For(this) is not null;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/LibRed/LibRed.Core/Catalog/ColumnDef.cs b/src/LibRed/LibRed.Core/Catalog/ColumnDef.cs
index dd7684ff5..b9f041a86 100644
--- a/src/LibRed/LibRed.Core/Catalog/ColumnDef.cs
+++ b/src/LibRed/LibRed.Core/Catalog/ColumnDef.cs
@@ -126,4 +126,4 @@ public sealed class ColumnDef
/// Undocumented flag bits (byte 0x0F) to force-set — the system-catalog column marker (0x10) and
/// security-identifier marker (0x20) Access sets on MSys* columns. 0 for ordinary columns.
public byte SystemFlags { get; init; }
-}
+}
\ No newline at end of file
diff --git a/src/LibRed/LibRed.Core/Catalog/ComplexColumn.cs b/src/LibRed/LibRed.Core/Catalog/ComplexColumn.cs
new file mode 100644
index 000000000..55bb1070b
--- /dev/null
+++ b/src/LibRed/LibRed.Core/Catalog/ComplexColumn.cs
@@ -0,0 +1,224 @@
+using System.Buffers.Binary;
+using System.Text;
+
+namespace LibRed.Catalog;
+
+///
+/// A complex (multi-value / attachment) column and the per-column table its values live in. The column's
+/// in-row value is a 4-byte complex id naming one record; the values for that record are the rows of
+/// whose holds the id.
+///
+///
+/// Everything here is resolved structurally, never by building names. The flat table and its two
+/// bookkeeping columns keep whatever the table and column were called when the complex column was created, and
+/// a later rename does not follow: a column now called BK_category can be backed by
+/// f_…_TempField*7, and a table now called Borrow can have a value-id column still named
+/// Table1_BRW_book. The flat table comes from MSysComplexColumns.FlatTableID and the two columns
+/// from the index shape. See docs/format/system-catalog.md.
+/// A record's id is allocated when the row is created, so a non-null id is no evidence that any
+/// value exists — an empty is the ordinary answer, not an error.
+///
+public sealed class ComplexColumn
+{
+ internal ComplexColumn(
+ string columnName, int complexId, TableDef ownerTable, TableDef flatTable,
+ ColumnDef ownerLink, ColumnDef valueId, string? elementTypeName)
+ {
+ ColumnName = columnName;
+ ComplexId = complexId;
+ OwnerTable = ownerTable;
+ FlatTable = flatTable;
+ OwnerLink = ownerLink;
+ ValueId = valueId;
+ ElementTypeName = elementTypeName;
+ ValueColumns = [.. flatTable.Columns.Where(c => c != ownerLink && c != valueId)];
+ }
+
+ /// The complex column's name on .
+ public string ColumnName { get; }
+
+ /// Its MSysComplexColumns.ComplexID — the same value the column descriptor carries at
+ /// 0x0B.
+ public int ComplexId { get; }
+
+ /// The table holding the complex column.
+ public TableDef OwnerTable { get; }
+
+ /// The per-column table holding the values, one row each.
+ public TableDef FlatTable { get; }
+
+ /// The flat table's link back to the owning record: it holds that record's complex id and
+ /// repeats once per value, which is what makes the column multi-valued. Indexed, not unique.
+ public ColumnDef OwnerLink { get; }
+
+ /// The flat table's per-value id: unique, primary, and an ordinary AutoNumber whose high-water
+ /// is the flat table's own TDEF 0x14.
+ public ColumnDef ValueId { get; }
+
+ /// The value columns proper — everything but the two bookkeeping ones. A scalar multi-value
+ /// column has a single Value; an attachment has the six File* columns.
+ public IReadOnlyList ValueColumns { get; }
+
+ /// The MSysComplexType_* template this column was made from (e.g.
+ /// MSysComplexType_Attachment), or null when the catalog row does not resolve to one.
+ public string? ElementTypeName { get; }
+
+ /// Whether this is an attachment column rather than a multi-value scalar — it carries the
+ /// FileData/FileName shape, so applies.
+ public bool IsAttachment =>
+ FlatTable.FindColumn("FileData") is not null && FlatTable.FindColumn("FileName") is not null;
+}
+
+/// An attachment's stored file: the extension Access recorded, and the bytes themselves.
+/// The extension from the payload's inner header (pdf, png, …), without
+/// a dot. This is the payload's own copy, which need not equal the FileType column.
+/// The file's bytes, decompressed where Access compressed them.
+public readonly record struct ComplexAttachment(string Extension, byte[] Content)
+{
+ ///
+ /// Unwraps the FileData column of an attachment row. The stored blob is an 8-byte header — a
+ /// compression flag (1 = a zlib stream follows, 0 = raw bytes) and the body's decompressed
+ /// length — then the body, which itself opens with a 20-byte header carrying the extension as
+ /// null-terminated UTF-16. Verified against a pdf, an mp3 and a png; see
+ /// docs/format/system-catalog.md.
+ ///
+ /// The blob is too short, or its headers do not describe it.
+ public static ComplexAttachment Unwrap(ReadOnlySpan fileData)
+ {
+ const int OuterHeader = 8, InnerHeaderLength = 20, ExtensionOffset = 12;
+ if (fileData.Length < OuterHeader)
+ throw new InvalidDataException($"Attachment data is {fileData.Length} bytes; the header alone is {OuterHeader}.");
+
+ uint compression = BinaryPrimitives.ReadUInt32LittleEndian(fileData);
+ uint bodyLength = BinaryPrimitives.ReadUInt32LittleEndian(fileData[4..]);
+
+ byte[] body = compression switch
+ {
+ 0 => fileData[OuterHeader..].ToArray(),
+ 1 => Inflate(fileData[OuterHeader..]),
+ _ => throw new InvalidDataException($"Attachment data has compression flag {compression}; expected 0 or 1."),
+ };
+ if (body.Length != bodyLength)
+ throw new InvalidDataException($"Attachment body is {body.Length} bytes; its header declares {bodyLength}.");
+ if (body.Length < InnerHeaderLength)
+ throw new InvalidDataException($"Attachment body is {body.Length} bytes; its inner header alone is {InnerHeaderLength}.");
+
+ int innerLength = BinaryPrimitives.ReadInt32LittleEndian(body);
+ if (innerLength < ExtensionOffset || innerLength > body.Length)
+ throw new InvalidDataException($"Attachment inner header declares {innerLength} bytes, which its {body.Length}-byte body cannot hold.");
+
+ string extension = Encoding.Unicode
+ .GetString(body.AsSpan(ExtensionOffset, innerLength - ExtensionOffset)).TrimEnd('\0');
+ return new ComplexAttachment(extension, body[innerLength..]);
+ }
+
+ ///
+ /// The extensions Access leaves uncompressed because the format already is. From the documented list
+ /// (Attachment object, "Types of files that Access compresses"), which is explicitly partial — anything
+ /// absent is compressed, so this set is the exception rather than the rule.
+ ///
+ // The comparer is explicit because an extension matches case-insensitively (.PNG is still a png), which
+ // IDE0028's collection expression would leave to the default.
+#pragma warning disable IDE0028
+ private static readonly HashSet NativelyCompressed = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "jpg", "jpeg", "gif", "png", "zip", "cab", "docx", "xlsx", "xlsb", "pptx",
+ };
+#pragma warning restore IDE0028
+
+ ///
+ /// The largest attachment Access accepts — "Individual files cannot exceed 256 megabytes". This is
+ /// Access's policy, not the format's capability: a long value's stored length runs to 0x3FFFFFFF,
+ /// which ACE itself accepts and reads back, so the file format holds four times this.
+ ///
+ public const int MaxAccessAttachmentBytes = 256 * 1024 * 1024;
+
+ ///
+ /// Whether Access would deflate a file of this extension rather than store it as it is — its
+ /// convention, not a rule of the format. Both forms are valid on disk and ACE reads either, so this only
+ /// decides whether a file LibRed writes looks like one Access wrote.
+ ///
+ public static bool Compresses(string extension) =>
+ !NativelyCompressed.Contains((extension ?? "").TrimStart('.'));
+
+ ///
+ /// Throws when or breaks one of Access's
+ /// documented attachment limits — the 256 MB per file, the 255-character name, and the characters a name
+ /// may not contain. None of these is a format limit; they are what the Access UI enforces, and a file
+ /// breaking them is one Access would not have created and may not open.
+ ///
+ public static void ValidateAccessLimits(string fileName, ReadOnlySpan content)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(fileName);
+ if (content.Length > MaxAccessAttachmentBytes)
+ throw new ArgumentOutOfRangeException(nameof(content),
+ $"An attachment is {content.Length} bytes; Access accepts at most {MaxAccessAttachmentBytes} per file.");
+ if (fileName.Length > 255)
+ throw new ArgumentException(
+ $"An attachment name is at most 255 characters including the extension; '{fileName}' is {fileName.Length}.",
+ nameof(fileName));
+ if (fileName.IndexOfAny(['?', '"', '/', '\\', '<', '>', '*', '|', ':']) >= 0)
+ throw new ArgumentException(
+ $"An attachment name cannot contain any of ? \" / \\ < > * | : — '{fileName}' does.",
+ nameof(fileName));
+ }
+
+ ///
+ /// Builds the FileData blob for an attachment — the inverse of .
+ ///
+ ///
+ /// Both storage forms are valid and ACE reads either, so compression is a choice, not a
+ /// correctness question. Left to it follows Access's own convention —
+ /// "Access will compress your attached files unless those files are compressed natively" — which matches
+ /// what ACE wrote in the sample file: a png raw, a pdf and an mp3 deflated.
+ ///
+ /// The file's extension without a dot, as the inner header records it.
+ /// The file's bytes.
+ /// Force the stored form, or to follow Access's convention
+ /// for the extension.
+ public static byte[] Pack(string extension, ReadOnlySpan content, bool? compress = null)
+ {
+ ArgumentNullException.ThrowIfNull(extension);
+ bool deflate = compress ?? Compresses(extension);
+ const int OuterHeader = 8, ExtensionOffset = 12;
+
+ // The extension is null-terminated UTF-16, and the count at [8] is its length in UTF-16 units
+ // INCLUDING that terminator — "pdf" gives 4 and a 20-byte inner header, "thmx" 5 and 22.
+ int units = extension.Length + 1;
+ int innerLength = ExtensionOffset + units * 2;
+
+ // The body is the inner header followed by the file; the outer header's length is the body's
+ // UNCOMPRESSED size, whether or not the stream that follows is deflated.
+ byte[] body = new byte[innerLength + content.Length];
+ BinaryPrimitives.WriteInt32LittleEndian(body, innerLength);
+ BinaryPrimitives.WriteInt32LittleEndian(body.AsSpan(4), 1);
+ BinaryPrimitives.WriteInt32LittleEndian(body.AsSpan(8), units);
+ Encoding.Unicode.GetBytes(extension, body.AsSpan(ExtensionOffset)); // terminator stays 0
+ content.CopyTo(body.AsSpan(innerLength));
+
+ byte[] stored = deflate ? Deflate(body) : body;
+ byte[] blob = new byte[OuterHeader + stored.Length];
+ BinaryPrimitives.WriteUInt32LittleEndian(blob, deflate ? 1u : 0u);
+ BinaryPrimitives.WriteUInt32LittleEndian(blob.AsSpan(4), (uint)body.Length);
+ stored.CopyTo(blob.AsSpan(OuterHeader));
+ return blob;
+ }
+
+ private static byte[] Deflate(byte[] body)
+ {
+ using var output = new MemoryStream();
+ using (var zlib = new System.IO.Compression.ZLibStream(
+ output, System.IO.Compression.CompressionLevel.Optimal, leaveOpen: true))
+ zlib.Write(body);
+ return output.ToArray();
+ }
+
+ private static byte[] Inflate(ReadOnlySpan stream)
+ {
+ using var input = new MemoryStream(stream.ToArray());
+ using var zlib = new System.IO.Compression.ZLibStream(input, System.IO.Compression.CompressionMode.Decompress);
+ using var output = new MemoryStream();
+ zlib.CopyTo(output);
+ return output.ToArray();
+ }
+}
diff --git a/src/LibRed/LibRed.Core/Catalog/ForeignKey.cs b/src/LibRed/LibRed.Core/Catalog/ForeignKey.cs
index 07b14b778..36577423e 100644
--- a/src/LibRed/LibRed.Core/Catalog/ForeignKey.cs
+++ b/src/LibRed/LibRed.Core/Catalog/ForeignKey.cs
@@ -17,6 +17,8 @@ namespace LibRed.Catalog;
/// Whether deleting the parent sets the child's FK columns to NULL (Jet's
/// ON DELETE SET NULL, grbit 0x2000; mutually exclusive with cascade delete, and Jet has no
/// ON UPDATE SET NULL).
+/// ON UPDATE SET NULL — pathway only; not read back yet (its storage is
+/// unverified: the ACE OLE DB provider rejects the DDL, so the grbit/info-block bytes could not be probed).
public sealed record ForeignKey(
string Name,
string Table,
@@ -27,6 +29,4 @@ public sealed record ForeignKey(
bool CascadeDelete,
bool IsInherited = false,
bool DeleteSetNull = false,
- /// ON UPDATE SET NULL — pathway only; not read back yet (its storage is unverified — the ACE
- /// OLE DB provider rejects the DDL, so the grbit/info-block bytes couldn't be probed).
- bool UpdateSetNull = false);
+ bool UpdateSetNull = false);
\ No newline at end of file
diff --git a/src/LibRed/LibRed.Core/Catalog/IndexDef.cs b/src/LibRed/LibRed.Core/Catalog/IndexDef.cs
index 078df5bb8..089ae9f87 100644
--- a/src/LibRed/LibRed.Core/Catalog/IndexDef.cs
+++ b/src/LibRed/LibRed.Core/Catalog/IndexDef.cs
@@ -36,4 +36,4 @@ public sealed record IndexDef
/// block. Used to locate the stats block when maintaining the unique-entry count on insert.
///
public int RealIndexOrdinal { get; init; }
-}
+}
\ No newline at end of file
diff --git a/src/LibRed/LibRed.Core/Catalog/JetCatalog.cs b/src/LibRed/LibRed.Core/Catalog/JetCatalog.cs
index 33dedf897..c3ebe2920 100644
--- a/src/LibRed/LibRed.Core/Catalog/JetCatalog.cs
+++ b/src/LibRed/LibRed.Core/Catalog/JetCatalog.cs
@@ -2,6 +2,7 @@
using LibRed.IO;
using LibRed.Pages;
using LibRed.Storage;
+using System.Globalization;
namespace LibRed.Catalog;
@@ -36,7 +37,8 @@ public sealed class JetCatalog(PageChannel channel, int catalogPage = 2)
private List? _relationships;
private Dictionary? _views;
private Dictionary? _actionQueries;
- private Dictionary>? _queryParameters;
+ private Dictionary>? _queryParameters;
+ private List? _complexColumns;
private long _seenSchemaGeneration = channel.SchemaGeneration;
/// All tables in the database (user and system).
@@ -54,7 +56,7 @@ public sealed class JetCatalog(PageChannel channel, int catalogPage = 2)
/// A stored query's declared parameter names in declaration order (its Attribute=2 rows).
/// Used to bind an EXECUTE proc a, b's positional arguments to the procedure's named parameters.
/// Empty for a query with no parameters.
- public IReadOnlyDictionary> QueryParameters { get { EnsureFresh(); EnsureStoredQueries(); return _queryParameters!; } }
+ public IReadOnlyDictionary> QueryParameters { get { EnsureFresh(); EnsureStoredQueries(); return _queryParameters!; } }
/// Drops the cached catalog so a freshly created table is picked up on next read.
public void Invalidate(bool markChanged = true)
@@ -64,10 +66,68 @@ public void Invalidate(bool markChanged = true)
_views = null;
_actionQueries = null;
_queryParameters = null;
+ _complexColumns = null;
_seenSchemaGeneration = _channel.SchemaGeneration;
if (markChanged) _channel.MarkSchemaChanged();
}
+ /// Every complex (multi-value / attachment) column in the database, wired to the table its
+ /// values live in. Empty when the file has no MSysComplexColumns — a Jet 4 database has none.
+ public IReadOnlyList ComplexColumns
+ {
+ get { EnsureFresh(); return _complexColumns ??= LoadComplexColumns(); }
+ }
+
+ /// The complex column of , or null when that
+ /// column is not a complex one.
+ public ComplexColumn? FindComplexColumn(string table, string column) =>
+ ComplexColumns.FirstOrDefault(c =>
+ string.Equals(c.OwnerTable.Name, table, StringComparison.OrdinalIgnoreCase)
+ && string.Equals(c.ColumnName, column, StringComparison.OrdinalIgnoreCase));
+
+ ///
+ /// Reads MSysComplexColumns and resolves each row to its owner table, its flat table and the flat
+ /// table's two bookkeeping columns. A row whose tables are missing is skipped rather than throwing: the
+ /// catalog must stay readable even where the complex wiring is incomplete.
+ ///
+ private List LoadComplexColumns()
+ {
+ var resolved = new List();
+ if (FindTable("MSysComplexColumns") is not { } definition) return resolved;
+
+ int name = ColumnIndex(definition, "ColumnName"), id = ColumnIndex(definition, "ComplexID"),
+ elementType = ColumnIndex(definition, "ComplexTypeObjectID"),
+ owner = ColumnIndex(definition, "ConceptualTableID"), flat = ColumnIndex(definition, "FlatTableID");
+ if (name < 0 || id < 0 || owner < 0 || flat < 0) return resolved;
+
+ foreach (object?[] row in new Storage.Table(_channel, definition).Rows())
+ {
+ if (row[name] is not string columnName) continue;
+ if (TableWithId(row[owner]) is not { } ownerTable || TableWithId(row[flat]) is not { } flatTable) continue;
+ if (ownerTable.FindColumn(columnName) is null) continue;
+
+ // Structural, never by name: the primary index names the per-value id, and the one non-unique
+ // single-column index names the link back to the owning record.
+ ColumnDef? valueId = flatTable.Indexes.FirstOrDefault(i => i.IsPrimaryKey)?.Columns is [{ Column: { } pk }] ? pk : null;
+ ColumnDef? ownerLink = flatTable.Indexes
+ .FirstOrDefault(i => !i.IsUnique && !i.IsPrimaryKey && i.Columns.Count == 1)?.Columns[0].Column;
+ if (valueId is null || ownerLink is null || valueId == ownerLink) continue;
+
+ resolved.Add(new ComplexColumn(
+ columnName, row[id] is null ? 0 : Convert.ToInt32(row[id], CultureInfo.InvariantCulture),
+ ownerTable, flatTable, ownerLink, valueId,
+ elementType < 0 ? null : TableWithId(row[elementType])?.Name));
+ }
+ return resolved;
+
+ static int ColumnIndex(TableDef t, string column) => t.FindColumn(column)?.Index ?? -1;
+ }
+
+ /// The table whose MSysObjects id (its TDEF page) is .
+ private TableDef? TableWithId(object? id) =>
+ id is null ? null
+ : Tables.FirstOrDefault(t => t.DefinitionPage == Convert.ToInt32(id, CultureInfo.InvariantCulture));
+
private void EnsureFresh()
{
long generation = _channel.SchemaGeneration;
@@ -120,6 +180,7 @@ private List LoadTables()
|| name.StartsWith('#');
TableDef definition = ReadTableDefinition(definitionPage, name, isSystem);
+ definition.ObjectFlags = flags;
// Attach column DefaultValue and table CHECK properties from the extended-properties (LvProp) blob.
if (row[lvpropIndex] is byte[] { Length: > 0 } blob)
{
@@ -197,9 +258,9 @@ private List LoadRelationships()
private void EnsureStoredQueries()
{
if (_views is not null) return;
- _views = new Dictionary(StringComparer.OrdinalIgnoreCase);
- _actionQueries = new Dictionary(StringComparer.OrdinalIgnoreCase);
- _queryParameters = new Dictionary>(StringComparer.OrdinalIgnoreCase);
+ _views = [with(StringComparer.OrdinalIgnoreCase)];
+ _actionQueries = [with(StringComparer.OrdinalIgnoreCase)];
+ _queryParameters = [with(StringComparer.OrdinalIgnoreCase)];
TableDef? mqDef = FindTable("MSysQueries");
TableDef? objDef = FindTable("MSysObjects");
@@ -208,7 +269,8 @@ private void EnsureStoredQueries()
// Group MSysQueries rows by ObjectId.
var mq = mqDef.Columns;
int oid = ColumnIndex(mq, "ObjectId"), attr = ColumnIndex(mq, "Attribute"), expr = ColumnIndex(mq, "Expression"),
- flag = ColumnIndex(mq, "Flag"), n1 = ColumnIndex(mq, "Name1"), n2 = ColumnIndex(mq, "Name2"), order = ColumnIndex(mq, "Order");
+ flag = ColumnIndex(mq, "Flag"), n1 = ColumnIndex(mq, "Name1"), n2 = ColumnIndex(mq, "Name2"), order = ColumnIndex(mq, "Order"),
+ lvExtra = ColumnIndex(mq, "LvExtra");
var byObject = new Dictionary>();
foreach (object?[] row in new Table(_channel, mqDef).Rows())
if (row[oid] is int id)
@@ -230,24 +292,40 @@ private void EnsureStoredQueries()
object?[]? operation = rows.FirstOrDefault(r => r[attr] is byte b && b == StoredQueryFormat.AttrOperation);
short kind = operation?[flag] is short k ? k : StoredQueryFormat.OperationSelect;
if (operation is not null && kind != StoredQueryFormat.OperationSelect)
- _actionQueries[name] = ReconstructAction(rows, attr, expr, flag, n1, n2, order);
- else if (Reconstruct(rows, attr, expr, flag, n1, n2, order) is { } sql)
+ _actionQueries[name] = ReconstructAction(rows, attr, expr, flag, n1, n2, order, lvExtra);
+ else if (Reconstruct(rows, attr, expr, flag, n1, n2, order, lvExtra) is { } sql)
_views[name] = sql;
// The declared parameters (Attribute=2 rows), in declaration order, for EXECUTE positional binding.
- var paramNames = rows.Where(r => r[attr] is byte b && b == StoredQueryFormat.AttrParameter)
+ // Each row's Flag is the parameter's Jet type code (0 for Access's untyped parameter).
+ var parameters = rows.Where(r => r[attr] is byte b && b == StoredQueryFormat.AttrParameter)
.OrderBy(r => r[order] is byte[] ob && ob.Length >= 4
? System.Buffers.Binary.BinaryPrimitives.ReadInt32BigEndian(ob) : 0)
- .Select(r => r[n1] as string).Where(s => s is not null).Select(s => s!).ToList();
- if (paramNames.Count > 0) _queryParameters[name] = paramNames;
+ .Where(r => r[n1] is string)
+ .Select(r =>
+ {
+ JetDataType? type = r[flag] is short f and not 0 ? (JetDataType)(byte)f : null;
+ // The declared facets ride in LvExtra: a length for text, precision and scale packed
+ // together for a decimal.
+ var (size, precision, scale) = type is { } t
+ ? StoredQueryFormat.UnpackParameterFacets(t, r[lvExtra] as int?)
+ : (null, null, null);
+ return new StoredQueryParameter((string)r[n1]!, type, size, precision, scale);
+ })
+ .ToList();
+ if (parameters.Count > 0) _queryParameters[name] = parameters;
}
}
- /// Rebuilds a stored action query's executable SQL from its MSysQueries rows. Handles the kinds
- /// LibRed can execute (CREATE/DROP TABLE, INSERT … VALUES); other kinds return an unsupported reason.
- private static StoredActionQuery ReconstructAction(List