diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..0901f69
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+# These are supported funding model platforms
+
+github: msallin # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..0905865
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,36 @@
+# Dependabot keeps the .NET SDK (global.json), NuGet packages and GitHub Actions versions up to date.
+#
+# Minor and patch updates are grouped into a single PR per ecosystem to cut noise; major updates open
+# their own PR so the breaking-change surface stays visible. Updates are proposed as PRs and merged
+# manually after the CI "Build & Test" check passes.
+version: 2
+updates:
+ # Bumps the SDK version pinned in global.json, which setup-dotnet installs in CI. The dotnet-sdk
+ # ecosystem only does version updates (no security updates) and targets global.json files.
+ - package-ecosystem: dotnet-sdk
+ directory: "/"
+ schedule:
+ interval: weekly
+ open-pull-requests-limit: 10
+
+ - package-ecosystem: nuget
+ directory: "/"
+ schedule:
+ interval: weekly
+ open-pull-requests-limit: 10
+ groups:
+ nuget-minor-and-patch:
+ update-types:
+ - minor
+ - patch
+
+ - package-ecosystem: github-actions
+ directory: "/"
+ schedule:
+ interval: weekly
+ open-pull-requests-limit: 10
+ groups:
+ actions-minor-and-patch:
+ update-types:
+ - minor
+ - patch
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
new file mode 100644
index 0000000..59f6664
--- /dev/null
+++ b/.github/workflows/cd.yml
@@ -0,0 +1,136 @@
+name: CD
+
+# Builds, tests and packs the NuGet package on a version tag (v1.2.3) or manual run.
+# Publishing to NuGet uses Trusted Publishing (OIDC) and runs only on a v*.*.* tag push; a manual
+# run packs and uploads the artifact as a dry run without publishing (see the step comments below).
+
+on:
+ push:
+ tags: [ 'v*.*.*' ]
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Package version, e.g. 1.2.3. Defaults to a dev version when omitted.'
+ required: false
+
+permissions:
+ contents: read
+
+jobs:
+ pack-publish:
+ name: Pack & Publish
+ # Windows is required: the library multi-targets net40/net45 and the demo targets net48, which
+ # need the .NET Framework reference assemblies / targeting packs available on the Windows runners.
+ runs-on: windows-latest
+ timeout-minutes: 15
+ permissions:
+ id-token: write # Required for NuGet Trusted Publishing; without it NuGet/login fails with 403.
+ contents: write # Required to create the GitHub Release; a job-level block drops inherited defaults.
+ steps:
+ - uses: actions/checkout@v7
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v5
+ with:
+ # Install the exact SDK pinned in global.json; Dependabot's dotnet-sdk updater keeps it current.
+ global-json-file: global.json
+ # Cache the restored NuGet packages, keyed on the committed lock files.
+ cache: true
+ cache-dependency-path: '**/packages.lock.json'
+
+ - name: Resolve version
+ id: version
+ shell: bash
+ # The manual input is read through an env var, not interpolated into the script, so a crafted
+ # value cannot inject shell. It is then validated as SemVer before anything downstream uses it.
+ env:
+ VERSION_INPUT: ${{ github.event.inputs.version }}
+ run: |
+ if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
+ VERSION="${GITHUB_REF_NAME#v}"
+ elif [[ -n "${VERSION_INPUT}" ]]; then
+ VERSION="${VERSION_INPUT}"
+ if ! [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$ ]]; then
+ echo "Invalid version '${VERSION}'; expected SemVer such as 1.2.3 or 1.2.3-rc.1." >&2
+ exit 1
+ fi
+ else
+ VERSION="0.0.0-dev.${GITHUB_RUN_NUMBER}"
+ fi
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
+ echo "Resolved package version: ${VERSION}"
+
+ - name: Restore
+ # Locked-mode restore fails the build if any packages.lock.json is missing or out of date, so the
+ # restore is reproducible and a forgotten lock-file update cannot slip through.
+ run: dotnet restore SQLite.CodeFirst.slnx --locked-mode
+
+ - name: Build
+ run: dotnet build SQLite.CodeFirst.slnx -c Release --no-restore -p:Version=${{ steps.version.outputs.version }}
+
+ - name: Test
+ # The test project runs on Microsoft.Testing.Platform (opted in via global.json), so target the
+ # solution with the native MTP dotnet test syntax.
+ run: dotnet test --solution SQLite.CodeFirst.slnx -c Release --no-build
+
+ - name: Pack
+ run: >
+ dotnet pack SQLite.CodeFirst/SQLite.CodeFirst.csproj -c Release --no-build
+ -p:Version=${{ steps.version.outputs.version }}
+ -o ${{ github.workspace }}/artifacts
+
+ - name: Upload package artifact
+ uses: actions/upload-artifact@v7
+ with:
+ name: nuget-package
+ path: |
+ ${{ github.workspace }}/artifacts/*.nupkg
+ ${{ github.workspace }}/artifacts/*.snupkg
+
+ # --- NuGet publishing via Trusted Publishing (OIDC). ---
+ # No long-lived API key: NuGet/login swaps the GitHub OIDC token for a short-lived key, authorized
+ # by the trusted publishing policy on nuget.org (the policy must name this workflow file, cd.yml).
+ # Gated to tag pushes so a manual run stays a dry run and never pushes the 0.0.0-dev.* version.
+ # https://learn.microsoft.com/nuget/nuget-org/trusted-publishing
+ - name: NuGet login (OIDC)
+ id: nuget-login
+ if: startsWith(github.ref, 'refs/tags/v')
+ uses: NuGet/login@v1
+ with:
+ user: ${{ secrets.NUGET_USER }} # nuget.org account/profile name, NOT the email address.
+
+ - name: Publish to NuGet
+ if: startsWith(github.ref, 'refs/tags/v')
+ shell: pwsh
+ # Resolve the package explicitly: on the Windows runner the default PowerShell shell does not
+ # expand a "*.nupkg" wildcard for dotnet, so a literal glob path is passed through and not found.
+ # Pushing the .nupkg also pushes the sibling .snupkg symbol package to nuget.org automatically.
+ run: |
+ $package = Get-ChildItem -Path "${{ github.workspace }}/artifacts/*.nupkg" | Select-Object -First 1
+ dotnet nuget push $package.FullName `
+ --api-key "${{ steps.nuget-login.outputs.NUGET_API_KEY }}" `
+ --source https://api.nuget.org/v3/index.json `
+ --skip-duplicate
+
+ # --- GitHub Release. ---
+ # Created only on tag pushes, after a successful NuGet publish, so the Release reflects what was
+ # actually shipped. Uses the gh CLI (preinstalled on the runner) rather than a third-party action.
+ # --generate-notes builds the notes from commits/PRs since the previous tag; --verify-tag guards
+ # against creating a release for a tag the runner cannot see. The .nupkg/.snupkg are attached so
+ # the package is downloadable straight from the Release page, not just the workflow run.
+ - name: Create GitHub Release
+ if: startsWith(github.ref, 'refs/tags/v')
+ shell: pwsh
+ # The tag is passed through an env var rather than interpolated into the script, matching the
+ # version step above. Assets are resolved with Get-ChildItem so the wildcards expand on the
+ # Windows runner (bash would mangle the backslashes in the workspace path).
+ env:
+ GH_TOKEN: ${{ github.token }}
+ TAG: ${{ github.ref_name }}
+ run: |
+ $assets = Get-ChildItem -Path "${{ github.workspace }}/artifacts/*.nupkg", "${{ github.workspace }}/artifacts/*.snupkg" | ForEach-Object { $_.FullName }
+ # SemVer pre-release tags carry a hyphen (e.g. v1.2.3-rc.1); mark those as GitHub pre-releases.
+ $ghArgs = @($env:TAG, '--title', $env:TAG, '--generate-notes', '--verify-tag')
+ if ($env:TAG -like '*-*') { $ghArgs += '--prerelease' }
+ $ghArgs += $assets
+ gh release create $ghArgs
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..9ecf110
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,50 @@
+name: CI
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ build-test:
+ name: Build & Test
+ # Windows is required: the library multi-targets net40/net45 and the demo targets net48, which
+ # need the .NET Framework reference assemblies / targeting packs available on the Windows runners.
+ runs-on: windows-latest
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v7
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v5
+ with:
+ # Install the exact SDK pinned in global.json; Dependabot's dotnet-sdk updater keeps it current.
+ global-json-file: global.json
+ # Cache the restored NuGet packages, keyed on the committed lock files.
+ cache: true
+ cache-dependency-path: '**/packages.lock.json'
+
+ - name: Restore
+ # Locked-mode restore fails the build if any packages.lock.json is missing or out of date, so the
+ # restore is reproducible and a forgotten lock-file update cannot slip through.
+ run: dotnet restore SQLite.CodeFirst.slnx --locked-mode
+
+ - name: Build
+ run: dotnet build SQLite.CodeFirst.slnx -c Release --no-restore
+
+ - name: Test
+ # The test project runs on Microsoft.Testing.Platform (opted in via global.json), so this uses the
+ # native MTP dotnet test syntax: --solution, and the TRX report flags instead of --logger trx.
+ run: >
+ dotnet test --solution SQLite.CodeFirst.slnx -c Release --no-build
+ --report-trx --report-trx-filename test-results.trx
+ --results-directory ${{ github.workspace }}/TestResults
+
+ - name: Upload test results
+ if: always()
+ uses: actions/upload-artifact@v7
+ with:
+ name: test-results
+ path: ${{ github.workspace }}/TestResults/*.trx
+ if-no-files-found: ignore
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..4dcd0ff
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,11 @@
+
+
+
+
+ true
+
+
+
diff --git a/README.md b/README.md
index d24d114..a6b9367 100644
--- a/README.md
+++ b/README.md
@@ -1,52 +1,61 @@
# SQLite CodeFirst
-**Release Build** [](https://ci.appveyor.com/project/msallin/sqlitecodefirst-nv6vn/branch/master)
-
-**CI Build** [](https://ci.appveyor.com/project/msallin/sqlitecodefirst)
+[](https://github.com/msallin/SQLiteCodeFirst/actions/workflows/ci.yml)
+[](https://github.com/msallin/SQLiteCodeFirst/actions/workflows/cd.yml)
Creates a [SQLite Database](https://sqlite.org/) from Code, using [Entity Framework](https://msdn.microsoft.com/en-us/data/ef.aspx) CodeFirst.
## Support the project
+
To support this project you can: *star the repository*, report bugs/request features by creating new issues, write code and create PRs or donate.
Especially if you use it for a commercial project, a donation is welcome.
-If you need a specific feature for a commercial project, I am glad to offer a paid implementation.
+If you need a specific feature for a commercial project, I am glad to offer a paid implementation.
+
+## Project Status
+This project was started when there was .NET Full Framework and EF 6. EF 6 does not offer code first for SQLite and this library fills this gap.
+Nowadays there is .NET Core (or now just called .NET) and EF Core. EF Core supports code first and migrations for SQLite.
+If you use .NET Core 3 or above together with EF Core, there is no need for this library.
+If you use EF 6 (either with .NET Full Framework or with .NET Core 3 or above), this library is an option for you to get code first for SQLite.
+I'm going to maintain this library as long as it is useful for some people (see [History](https://github.com/msallin/SQLiteCodeFirst/issues/166) & [Project Status and Release Schedule](https://github.com/msallin/SQLiteCodeFirst/issues/157)).
## Features
-This Project ships several `IDbInitializer` classes. These create new SQLite Databases based on your model/code.
+
+This project ships several `IDbInitializer` classes. These create new SQLite Databases based on your model/code.
The following features are supported:
+
- Tables from classes (supported annotations: `Table`)
- Columns from properties (supported annotations: `Column`, `Key`, `MaxLength`, `Required`, `NotMapped`, `DatabaseGenerated`, `Index`)
- PrimaryKey constraint (`Key` annotation, key composites are supported)
-- ForeignKey constraint (1-n relationships, support for 'Cascade on delete')
+- ForeignKey constraint (1-n relationships, support for 'Cascade on delete' and 'Cascade on update')
- Not Null constraint
-- Auto increment (An int PrimaryKey will automatically be incremented and you can explicit set the "AUTOINCREMENT" constraint to a PrimaryKey using the Autoincrement-Attribute)
+- Auto increment (An int PrimaryKey will automatically be incremented and you can explicitly set the "AUTOINCREMENT" constraint to a PrimaryKey using the Autoincrement-Attribute)
- Index (Decorate columns with the `Index` attribute. Indices are automatically created for foreign keys by default. To prevent this you can remove the convention `ForeignKeyIndexConvention`)
- Unique constraint (Decorate columns with the `UniqueAttribute`, which is part of this library)
-- Collate constraint (Decorate columns with the `CollateAttribute`, which is part of this library. Use `CollationFunction.Custom` to specify a own collation function.)
+- Collate constraint (Decorate columns with the `CollateAttribute`, which is part of this library. Use `CollationFunction.Custom` to specify your own collation function.)
+- Default collation (pass an instance of Collation as constructor parameter for an initializer to specify a default collation).
- SQL default value (Decorate columns with the `SqlDefaultValueAttribute`, which is part of this library)
+- Cascade on update (Decorate the foreign key property with the `CascadeOnUpdateAttribute`, which is part of this library. Entity Framework cannot express `ON UPDATE CASCADE`, so this opt-in attribute must be placed on the dependent foreign key property, e.g. `TeamId`, not on the navigation property. It requires an explicit foreign key property.)
## Install
+
Either get the assembly from the latest [GitHub Release Page](https://github.com/msallin/SQLiteCodeFirst/releases) or install the NuGet-Package [SQLite.CodeFirst](https://www.nuget.org/packages/SQLite.CodeFirst/) (`PM> Install-Package SQLite.CodeFirst`).
-The project is built to target .NET framework versions 4.0 and 4.5.
+The project is built to target .NET framework versions 4.0 and 4.5 and .NET Standard 2.1.
You can use the SQLite CodeFirst in projects that target the following frameworks:
-- .NET 4.0 (use net40)
-- .NET 4.5 (use net45)
-- .NET 4.5.1 (use net45)
-- .NET 4.5.2 (use net45)
-- .NET 4.6 (use net45)
-- .NET 4.6.1 (use net45)
-- .NET 4.6.2 (use net45)
-- .NET 4.7 (use net45)
-- .NET 4.7.1 (use net45)
-- .NET 4.7.2 (use net45)
+
+- .NET 4.0 (uses net40)
+- .NET 4.5-4.8 (uses net45)
+- .NET Core 3.0-3.1 (uses netstandard2.1)
+- .NET 5 and later, including .NET 10 (uses netstandard2.1)
## How to use
+
The functionality is exposed by using implementations of the `IDbInitializer<>` interface.
Depending on your need, you can choose from the following initializers:
-- SqliteCreateDatabaseIfNotExists
+
+- SqliteCreateDatabaseIfNotExists
- SqliteDropCreateDatabaseAlways
- SqliteDropCreateDatabaseWhenModelChanges
@@ -56,12 +65,13 @@ Or for even more control, use the `SqliteSqlGenerator` (implements `ISqlGenerato
When you want to let the Entity Framework create database if it does not exist, just set `SqliteDropCreateDatabaseAlways<>` or `SqliteCreateDatabaseIfNotExists<>` as your `IDbInitializer<>`.
### Initializer Sample
+
```csharp
public class MyDbContext : DbContext
{
public MyDbContext()
: base("ConnectionStringName") { }
-
+
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var sqliteConnectionInitializer = new SqliteCreateDatabaseIfNotExists(modelBuilder);
@@ -69,13 +79,15 @@ public class MyDbContext : DbContext
}
}
```
-Notice that the `SqliteDropCreateDatabaseWhenModelChanges<>` initializer will create a additional table in your database.
-This table is used to store some information to detect model changes. If you want to use a own entity/table you can implement the
-`IHistory` interface and pass the type of your entity as parameter in the to the constructor from the initializer.
+
+Notice that the `SqliteDropCreateDatabaseWhenModelChanges<>` initializer will create an additional table in your database.
+This table is used to store some information to detect model changes. If you want to use an own entity/table you have to implement the
+`IHistory` interface and pass the type of your entity as parameter to the constructor of the initializer.
In a more advanced scenario, you may want to populate some core- or test-data after the database was created.
To do this, inherit from `SqliteDropCreateDatabaseAlways<>`, `SqliteCreateDatabaseIfNotExists<>` or `SqliteDropCreateDatabaseWhenModelChanges<>` and override the `Seed(MyDbContext context)` function.
-This function will be called in a transaction once the database was created. This function is only executed if a new database was successfully created.
+This function will be called in a transaction, once the database was created. This function is only executed if a new database was successfully created.
+
```csharp
public class MyDbContextInitializer : SqliteDropCreateDatabaseAlways
{
@@ -90,6 +102,7 @@ public class MyDbContextInitializer : SqliteDropCreateDatabaseAlways
+
+```
+
+Add the following class.
+```csharp
+public class MyConfiguration : DbConfiguration, IDbConnectionFactory {
+ public MyConfiguration()
+ {
+ SetProviderFactory("System.Data.SQLite", SQLiteFactory.Instance);
+ SetProviderFactory("System.Data.SQLite.EF6", SQLiteProviderFactory.Instance);
+
+ var providerServices = (DbProviderServices)SQLiteProviderFactory.Instance.GetService(typeof(DbProviderServices));
+
+ SetProviderServices("System.Data.SQLite", providerServices);
+ SetProviderServices("System.Data.SQLite.EF6", providerServices);
+
+ SetDefaultConnectionFactory(this);
+ }
+
+ public DbConnection CreateConnection(string connectionString)
+ => new SQLiteConnection(connectionString);
+ }
+}
+```
+
+Also, make sure you specify the DbConfigurationType on the DBContext class as well
+
+```csharp
+[DbConfigurationType(typeof(MyConfiguration))]
+public class Context: DbContext {
+ //... DBContext things
+}
+```
## Structure
-I tried to write the code in a extensible way.
+
+The code is written in an extensible way.
The logic is divided into two main parts, Builder and Statement.
-The Builder knows how to translate the EdmModel into statements where a statement class creates the SQLite-DDL-Code.
+The Builder knows how to translate the EdmModel into statements where a statement class creates the SQLite-DDL-Code.
The structure of the statements is influenced by the [SQLite Language Specification](https://www.sqlite.org/lang.html).
You will find an extensive usage of the composite pattern.
## Hints
-If you try to reinstall the NuGet-Packages (e.g. if you want to downgrade to .NET 4.0), the app.config will be overwritten and you may getting an exception when you try to run the console project.
-In this case please check the following issue: https://github.com/msallin/SQLiteCodeFirst/issues/13.
+
+If you try to reinstall the NuGet-Packages (e.g. if you want to downgrade to .NET 4.0), the app.config will be overwritten and you may get an exception when you try to run the console project.
+In this case please check the following issue:
## Recognition
-I started with the [code](https://gist.github.com/flaub/1968486e1b3f2b9fddaf) from [flaub](https://github.com/flaub).
+
+I started with the [code](https://gist.github.com/flaub/1968486e1b3f2b9fddaf) from [flaub](https://github.com/flaub).
diff --git a/SQLite.CodeFirst.Console/Entity/FooCompositeKey.cs b/SQLite.CodeFirst.Console/Entity/FooCompositeKey.cs
index 76c4ec4..2301500 100644
--- a/SQLite.CodeFirst.Console/Entity/FooCompositeKey.cs
+++ b/SQLite.CodeFirst.Console/Entity/FooCompositeKey.cs
@@ -4,22 +4,22 @@
namespace SQLite.CodeFirst.Console.Entity
{
- ///
- /// See https://github.com/msallin/SQLiteCodeFirst/issues/109
- ///
- public class FooCompositeKey
- {
- [Key, Column(Order = 1)]
- public int Id { get; set; }
+ ///
+ /// See https://github.com/msallin/SQLiteCodeFirst/issues/109
+ ///
+ public class FooCompositeKey
+ {
+ [Key, Column(Order = 1)]
+ public int Id { get; set; }
- [Key, Column(Order = 2), StringLength(20)]
- public string Version { get; set; }
+ [Key, Column(Order = 2), StringLength(20)]
+ public string Version { get; set; }
- [StringLength(255)]
- public string Name { get; set; }
+ [StringLength(255)]
+ public string Name { get; set; }
- public virtual ICollection FooeyACollection { get; set; }
+ public virtual ICollection FooeyACollection { get; set; }
- public virtual ICollection FooeyBCollection { get; set; }
- }
+ public virtual ICollection FooeyBCollection { get; set; }
+ }
}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.Console/Entity/FooRelationshipA.cs b/SQLite.CodeFirst.Console/Entity/FooRelationshipA.cs
index 09ec0cb..38baaf2 100644
--- a/SQLite.CodeFirst.Console/Entity/FooRelationshipA.cs
+++ b/SQLite.CodeFirst.Console/Entity/FooRelationshipA.cs
@@ -3,16 +3,16 @@
namespace SQLite.CodeFirst.Console.Entity
{
- ///
- /// See https://github.com/msallin/SQLiteCodeFirst/issues/109
- ///
- public class FooRelationshipA
- {
- public int Id { get; set; }
+ ///
+ /// See https://github.com/msallin/SQLiteCodeFirst/issues/109
+ ///
+ public class FooRelationshipA
+ {
+ public int Id { get; set; }
- [StringLength(255)]
- public string Name { get; set; }
+ [StringLength(255)]
+ public string Name { get; set; }
- public virtual ICollection Fooey { get; set; }
- }
+ public virtual ICollection Fooey { get; set; }
+ }
}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.Console/Entity/FooRelationshipB.cs b/SQLite.CodeFirst.Console/Entity/FooRelationshipB.cs
index 907dcdb..271d8f8 100644
--- a/SQLite.CodeFirst.Console/Entity/FooRelationshipB.cs
+++ b/SQLite.CodeFirst.Console/Entity/FooRelationshipB.cs
@@ -3,16 +3,16 @@
namespace SQLite.CodeFirst.Console.Entity
{
- ///
- /// See https://github.com/msallin/SQLiteCodeFirst/issues/109
- ///
- public class FooRelationshipB
- {
- public int Id { get; set; }
+ ///
+ /// See https://github.com/msallin/SQLiteCodeFirst/issues/109
+ ///
+ public class FooRelationshipB
+ {
+ public int Id { get; set; }
- [StringLength(255)]
- public string Name { get; set; }
+ [StringLength(255)]
+ public string Name { get; set; }
- public virtual ICollection Fooey { get; set; }
- }
+ public virtual ICollection Fooey { get; set; }
+ }
}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.Console/Entity/IEntity.cs b/SQLite.CodeFirst.Console/Entity/IEntity.cs
index e88d169..17afb3b 100644
--- a/SQLite.CodeFirst.Console/Entity/IEntity.cs
+++ b/SQLite.CodeFirst.Console/Entity/IEntity.cs
@@ -1,6 +1,6 @@
namespace SQLite.CodeFirst.Console.Entity
{
- interface IEntity
+ public interface IEntity
{
int Id { get; set; }
}
diff --git a/SQLite.CodeFirst.Console/SQLite.CodeFirst.Console.csproj b/SQLite.CodeFirst.Console/SQLite.CodeFirst.Console.csproj
index 76841ab..95f93db 100644
--- a/SQLite.CodeFirst.Console/SQLite.CodeFirst.Console.csproj
+++ b/SQLite.CodeFirst.Console/SQLite.CodeFirst.Console.csproj
@@ -1,7 +1,7 @@
Exe
- net472
+ net48
SQLite.CodeFirst
A console application which demonstrates how to use SQLite.CodeFirst.
1.0.0.0
@@ -10,11 +10,7 @@
..\Shared\SQLite.CodeFirst.StrongNameKey.snk
-
-
-
-
-
+
diff --git a/SQLite.CodeFirst.Console/packages.lock.json b/SQLite.CodeFirst.Console/packages.lock.json
new file mode 100644
index 0000000..fe32642
--- /dev/null
+++ b/SQLite.CodeFirst.Console/packages.lock.json
@@ -0,0 +1,55 @@
+{
+ "version": 1,
+ "dependencies": {
+ ".NETFramework,Version=v4.8": {
+ "System.Data.SQLite": {
+ "type": "Direct",
+ "requested": "[1.0.119, )",
+ "resolved": "1.0.119",
+ "contentHash": "JSOJpnBf9goMnxGTJFGCmm6AffxgtpuXNXV5YvWO8UNC2zwd12qkUe5lAbnY+2ohIkIukgIjbvR1RA/sWILv3w==",
+ "dependencies": {
+ "System.Data.SQLite.Core": "[1.0.119]",
+ "System.Data.SQLite.EF6": "[1.0.119]",
+ "System.Data.SQLite.Linq": "[1.0.119]"
+ }
+ },
+ "EntityFramework": {
+ "type": "Transitive",
+ "resolved": "6.5.2",
+ "contentHash": "8iOcnaKcgkWh35s8EhknnENNwwqvrBJ7jeNbQITbGo1mIeIOhGPfVdNOXZ3Y496T6e6wfXvz0WeXlgMdcgVYSA=="
+ },
+ "Stub.System.Data.SQLite.Core.NetFramework": {
+ "type": "Transitive",
+ "resolved": "1.0.119",
+ "contentHash": "8b4SbSXAxXJ8tfn6bnBu0m+HXMZvkE+BfogUKITRFm+snxfT5BTK7fLZaAsNoFX8DkBBjtCEDd+ajWkcyu55QA=="
+ },
+ "System.Data.SQLite.Core": {
+ "type": "Transitive",
+ "resolved": "1.0.119",
+ "contentHash": "bhQB8HVtRA+OOYw8UTD1F1kU+nGJ0/OZvH1JmlVUI4bGvgVEWeX1NcHjA765NvUoRVuCPlt8PrEpZ1thSsk1jg==",
+ "dependencies": {
+ "Stub.System.Data.SQLite.Core.NetFramework": "[1.0.119]"
+ }
+ },
+ "System.Data.SQLite.EF6": {
+ "type": "Transitive",
+ "resolved": "1.0.119",
+ "contentHash": "BwwgCSeA80gsxdXtU7IQEBrN9kQXWQrD11hNYOJZbXBBI1C4r7hA4QhBAalO1nzijXikthGRUADIEMI3nlucLA==",
+ "dependencies": {
+ "EntityFramework": "6.4.4"
+ }
+ },
+ "System.Data.SQLite.Linq": {
+ "type": "Transitive",
+ "resolved": "1.0.119",
+ "contentHash": "tkzb9aKEiQd18UW8VS6+vGtrkhcp4mjHg/AAKwNN8u7aWoBswquOnU07016MfTXyBUAR67mepWUhFC8zqNdPnA=="
+ },
+ "sqlite.codefirst": {
+ "type": "Project",
+ "dependencies": {
+ "EntityFramework": "[6.5.2, )"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.NetCore.Console/Configuration.cs b/SQLite.CodeFirst.NetCore.Console/Configuration.cs
new file mode 100644
index 0000000..845f483
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Configuration.cs
@@ -0,0 +1,28 @@
+using System.Data.Common;
+using System.Data.Entity;
+using System.Data.Entity.Core.Common;
+using System.Data.Entity.Infrastructure;
+using System.Data.SQLite;
+using System.Data.SQLite.EF6;
+
+namespace SQLite.CodeFirst.NetCore.Console
+{
+ public class Configuration : DbConfiguration, IDbConnectionFactory
+ {
+ public Configuration()
+ {
+ SetProviderFactory("System.Data.SQLite", SQLiteFactory.Instance);
+ SetProviderFactory("System.Data.SQLite.EF6", SQLiteProviderFactory.Instance);
+
+ var providerServices = (DbProviderServices)SQLiteProviderFactory.Instance.GetService(typeof(DbProviderServices));
+
+ SetProviderServices("System.Data.SQLite", providerServices);
+ SetProviderServices("System.Data.SQLite.EF6", providerServices);
+
+ SetDefaultConnectionFactory(this);
+ }
+
+ public DbConnection CreateConnection(string connectionString)
+ => new SQLiteConnection(connectionString);
+ }
+}
diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/Coach.cs b/SQLite.CodeFirst.NetCore.Console/Entity/Coach.cs
new file mode 100644
index 0000000..6dfc65b
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Entity/Coach.cs
@@ -0,0 +1,7 @@
+namespace SQLite.CodeFirst.NetCore.Console.Entity
+{
+ public class Coach : Person
+ {
+ public virtual Team Team { get; set; }
+ }
+}
diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/CustomHistory.cs b/SQLite.CodeFirst.NetCore.Console/Entity/CustomHistory.cs
new file mode 100644
index 0000000..740c3da
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Entity/CustomHistory.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace SQLite.CodeFirst.NetCore.Console.Entity
+{
+ public class CustomHistory : IHistory
+ {
+ public int Id { get; set; }
+ public string Hash { get; set; }
+ public string Context { get; set; }
+ public DateTime CreateDate { get; set; }
+ }
+}
diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/Foo.cs b/SQLite.CodeFirst.NetCore.Console/Entity/Foo.cs
new file mode 100644
index 0000000..f5a1bdd
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Entity/Foo.cs
@@ -0,0 +1,41 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace SQLite.CodeFirst.NetCore.Console.Entity
+{
+ ///
+ /// See https://github.com/msallin/SQLiteCodeFirst/issues/69 and https://github.com/msallin/SQLiteCodeFirst/issues/63
+ ///
+ public class Foo
+ {
+ private ICollection _fooSelves;
+ private ICollection _fooSteps;
+
+ public int FooId { get; set; }
+ public string Name { get; set; }
+ public int? FooSelf1Id { get; set; }
+ public int? FooSelf2Id { get; set; }
+ public int? FooSelf3Id { get; set; }
+
+ [ForeignKey("FooSelf1Id")]
+ public virtual Foo ParentMyEntity1 { get; set; }
+
+ [ForeignKey("FooSelf2Id")]
+ public virtual Foo ParentMyEntity2 { get; set; }
+
+ [ForeignKey("FooSelf3Id")]
+ public virtual Foo ParentMyEntity3 { get; set; }
+
+ public virtual ICollection FooSteps
+ {
+ get { return _fooSteps ?? (_fooSteps = new HashSet()); }
+ set { _fooSteps = value; }
+ }
+
+ public virtual ICollection FooSelves
+ {
+ get { return _fooSelves ?? (_fooSelves = new HashSet()); }
+ set { _fooSelves = value; }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/FooCompositeKey.cs b/SQLite.CodeFirst.NetCore.Console/Entity/FooCompositeKey.cs
new file mode 100644
index 0000000..b4bb2d6
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Entity/FooCompositeKey.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace SQLite.CodeFirst.NetCore.Console.Entity
+{
+ ///
+ /// See https://github.com/msallin/SQLiteCodeFirst/issues/109
+ ///
+ public class FooCompositeKey
+ {
+ [Key, Column(Order = 1)]
+ public int Id { get; set; }
+
+ [Key, Column(Order = 2), StringLength(20)]
+ public string Version { get; set; }
+
+ [StringLength(255)]
+ public string Name { get; set; }
+
+ public virtual ICollection FooeyACollection { get; set; }
+
+ public virtual ICollection FooeyBCollection { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/FooRelationshipA.cs b/SQLite.CodeFirst.NetCore.Console/Entity/FooRelationshipA.cs
new file mode 100644
index 0000000..f733cd7
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Entity/FooRelationshipA.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace SQLite.CodeFirst.NetCore.Console.Entity
+{
+ ///
+ /// See https://github.com/msallin/SQLiteCodeFirst/issues/109
+ ///
+ public class FooRelationshipA
+ {
+ public int Id { get; set; }
+
+ [StringLength(255)]
+ public string Name { get; set; }
+
+ public virtual ICollection Fooey { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/FooRelationshipB.cs b/SQLite.CodeFirst.NetCore.Console/Entity/FooRelationshipB.cs
new file mode 100644
index 0000000..3b75f68
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Entity/FooRelationshipB.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace SQLite.CodeFirst.NetCore.Console.Entity
+{
+ ///
+ /// See https://github.com/msallin/SQLiteCodeFirst/issues/109
+ ///
+ public class FooRelationshipB
+ {
+ public int Id { get; set; }
+
+ [StringLength(255)]
+ public string Name { get; set; }
+
+ public virtual ICollection Fooey { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/FooSelf.cs b/SQLite.CodeFirst.NetCore.Console/Entity/FooSelf.cs
new file mode 100644
index 0000000..6a8ad39
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Entity/FooSelf.cs
@@ -0,0 +1,13 @@
+namespace SQLite.CodeFirst.NetCore.Console.Entity
+{
+ ///
+ /// See https://github.com/msallin/SQLiteCodeFirst/issues/69 and https://github.com/msallin/SQLiteCodeFirst/issues/63
+ ///
+ public class FooSelf
+ {
+ public int FooSelfId { get; set; }
+ public int FooId { get; set; }
+ public int Number { get; set; }
+ public virtual Foo Foo { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/FooStep.cs b/SQLite.CodeFirst.NetCore.Console/Entity/FooStep.cs
new file mode 100644
index 0000000..b13b3a4
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Entity/FooStep.cs
@@ -0,0 +1,13 @@
+namespace SQLite.CodeFirst.NetCore.Console.Entity
+{
+ ///
+ /// See https://github.com/msallin/SQLiteCodeFirst/issues/69 and https://github.com/msallin/SQLiteCodeFirst/issues/63
+ ///
+ public class FooStep
+ {
+ public int FooStepId { get; set; }
+ public int FooId { get; set; }
+ public int Number { get; set; }
+ public virtual Foo Foo { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/IEntity.cs b/SQLite.CodeFirst.NetCore.Console/Entity/IEntity.cs
new file mode 100644
index 0000000..9a97b92
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Entity/IEntity.cs
@@ -0,0 +1,7 @@
+namespace SQLite.CodeFirst.NetCore.Console.Entity
+{
+ public interface IEntity
+ {
+ int Id { get; set; }
+ }
+}
diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/Person.cs b/SQLite.CodeFirst.NetCore.Console/Entity/Person.cs
new file mode 100644
index 0000000..e5ebda4
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Entity/Person.cs
@@ -0,0 +1,28 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace SQLite.CodeFirst.NetCore.Console.Entity
+{
+ public abstract class Person : IEntity
+ {
+ public int Id { get; set; }
+
+ [MaxLength(50)]
+ [Collate(CollationFunction.NoCase)]
+ public string FirstName { get; set; }
+
+ [MaxLength(50)]
+ public string LastName { get; set; }
+
+ [MaxLength(100)]
+ public string Street { get; set; }
+
+ [Required]
+ public string City { get; set; }
+
+ [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
+ [SqlDefaultValue(DefaultValue = "DATETIME('now')")]
+ public DateTime CreatedUtc { get; set; }
+ }
+}
diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/Player.cs b/SQLite.CodeFirst.NetCore.Console/Entity/Player.cs
new file mode 100644
index 0000000..f642654
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Entity/Player.cs
@@ -0,0 +1,21 @@
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace SQLite.CodeFirst.NetCore.Console.Entity
+{
+ [Table("TeamPlayer")]
+ public class Player : Person
+ {
+ [Index] // Automatically named 'IX_TeamPlayer_Number'
+ [Index("IX_TeamPlayer_NumberPerTeam", Order = 1, IsUnique = true)]
+ public int Number { get; set; }
+
+ // The index attribute must be placed on the FK not on the navigation property (team).
+ [Index("IX_TeamPlayer_NumberPerTeam", Order = 2, IsUnique = true)]
+ public int TeamId { get; set; }
+
+ // Its not possible to set an index on this property. Use the FK property (teamId).
+ public virtual Team Team { get; set; }
+
+ public virtual Player Mentor { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/Stadion.cs b/SQLite.CodeFirst.NetCore.Console/Entity/Stadion.cs
new file mode 100644
index 0000000..d8a2c50
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Entity/Stadion.cs
@@ -0,0 +1,26 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace SQLite.CodeFirst.NetCore.Console.Entity
+{
+ public class Stadion
+ {
+ [Key]
+ [Column(Order = 1)]
+ [Index("IX_Stadion_Main", Order = 2, IsUnique = true)] // Test for combined, named index
+ public string Name { get; set; }
+
+ [Key]
+ [Column(Order = 2)]
+ [Index("IX_Stadion_Main", Order = 1, IsUnique = true)] // Test for combined, named index
+ public string Street { get; set; }
+
+ [Key]
+ [Column(Order = 3)]
+ public string City { get; set; }
+
+ [Column(Order = 4)]
+ [Index("ReservedKeyWordTest", IsUnique = true)]
+ public int Order { get; set; }
+ }
+}
diff --git a/SQLite.CodeFirst.NetCore.Console/Entity/Team.cs b/SQLite.CodeFirst.NetCore.Console/Entity/Team.cs
new file mode 100644
index 0000000..d448a5f
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Entity/Team.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace SQLite.CodeFirst.NetCore.Console.Entity
+{
+ public class Team : IEntity
+ {
+ [Autoincrement]
+ public int Id { get; set; }
+
+ [Index("IX_Team_TeamsName")] // Test for named index.
+ [Required]
+ public string Name { get; set; }
+
+ public virtual Coach Coach { get; set; }
+
+ public virtual ICollection Players { get; set; }
+
+ public virtual Stadion Stadion { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.NetCore.Console/FootballDbContext.cs b/SQLite.CodeFirst.NetCore.Console/FootballDbContext.cs
new file mode 100644
index 0000000..330c6c0
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/FootballDbContext.cs
@@ -0,0 +1,33 @@
+using System.Data.Common;
+using System.Data.Entity;
+
+namespace SQLite.CodeFirst.NetCore.Console
+{
+ public class FootballDbContext : DbContext
+ {
+ public FootballDbContext(string nameOrConnectionString)
+ : base(nameOrConnectionString)
+ {
+ Configure();
+ }
+
+ public FootballDbContext(DbConnection connection, bool contextOwnsConnection)
+ : base(connection, contextOwnsConnection)
+ {
+ Configure();
+ }
+
+ private void Configure()
+ {
+ Configuration.ProxyCreationEnabled = true;
+ Configuration.LazyLoadingEnabled = true;
+ }
+
+ protected override void OnModelCreating(DbModelBuilder modelBuilder)
+ {
+ ModelConfiguration.Configure(modelBuilder);
+ var initializer = new FootballDbInitializer(modelBuilder);
+ Database.SetInitializer(initializer);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.NetCore.Console/FootballDbInitializer.cs b/SQLite.CodeFirst.NetCore.Console/FootballDbInitializer.cs
new file mode 100644
index 0000000..0d4a426
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/FootballDbInitializer.cs
@@ -0,0 +1,17 @@
+using System.Data.Entity;
+using SQLite.CodeFirst.NetCore.Console.Entity;
+
+namespace SQLite.CodeFirst.NetCore.Console
+{
+ public class FootballDbInitializer : SqliteDropCreateDatabaseWhenModelChanges
+ {
+ public FootballDbInitializer(DbModelBuilder modelBuilder)
+ : base(modelBuilder, typeof(CustomHistory))
+ { }
+
+ protected override void Seed(FootballDbContext context)
+ {
+ // Here you can seed your core data if you have any.
+ }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.NetCore.Console/ModelConfiguration.cs b/SQLite.CodeFirst.NetCore.Console/ModelConfiguration.cs
new file mode 100644
index 0000000..254f6f8
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/ModelConfiguration.cs
@@ -0,0 +1,66 @@
+using System.Data.Entity;
+using SQLite.CodeFirst.NetCore.Console.Entity;
+
+namespace SQLite.CodeFirst.NetCore.Console
+{
+ public class ModelConfiguration
+ {
+ public static void Configure(DbModelBuilder modelBuilder)
+ {
+ ConfigureTeamEntity(modelBuilder);
+ ConfigureStadionEntity(modelBuilder);
+ ConfigureCoachEntity(modelBuilder);
+ ConfigurePlayerEntity(modelBuilder);
+ ConfigureSelfReferencingEntities(modelBuilder);
+ ConfigureCompositeKeyEntities(modelBuilder);
+ }
+
+ private static void ConfigureTeamEntity(DbModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity().ToTable("Base.MyTable")
+ .HasRequired(t => t.Coach)
+ .WithMany()
+ .WillCascadeOnDelete(false);
+
+ modelBuilder.Entity()
+ .HasRequired(t => t.Stadion)
+ .WithRequiredPrincipal()
+ .WillCascadeOnDelete(true);
+ }
+
+ private static void ConfigureStadionEntity(DbModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity();
+ }
+
+ private static void ConfigureCoachEntity(DbModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity()
+ .HasRequired(p => p.Team)
+ .WithRequiredPrincipal(t => t.Coach)
+ .WillCascadeOnDelete(false);
+ }
+
+ private static void ConfigurePlayerEntity(DbModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity()
+ .HasRequired(p => p.Team)
+ .WithMany(team => team.Players)
+ .WillCascadeOnDelete(true);
+ }
+
+ private static void ConfigureSelfReferencingEntities(DbModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity();
+ modelBuilder.Entity();
+ modelBuilder.Entity();
+ }
+
+ private static void ConfigureCompositeKeyEntities(DbModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity();
+ modelBuilder.Entity();
+ modelBuilder.Entity();
+ }
+ }
+}
diff --git a/SQLite.CodeFirst.NetCore.Console/Program.cs b/SQLite.CodeFirst.NetCore.Console/Program.cs
new file mode 100644
index 0000000..3bd2b42
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/Program.cs
@@ -0,0 +1,147 @@
+using System.Collections.Generic;
+using System.Data.Entity;
+using System.Data.SQLite;
+using System.Linq;
+using SQLite.CodeFirst.NetCore.Console.Entity;
+
+namespace SQLite.CodeFirst.NetCore.Console
+{
+ public static class Program
+ {
+ private static void Main()
+ {
+ StartDemoUseInMemory();
+ StartDemoUseFile();
+ PressEnterToExit();
+ }
+
+ private static void StartDemoUseInMemory()
+ {
+ System.Console.WriteLine("Starting Demo Application (In Memory)");
+ System.Console.WriteLine(string.Empty);
+
+ using (var sqLiteConnection = new SQLiteConnection("data source=:memory:"))
+ {
+ // This is required if a in memory db is used.
+ sqLiteConnection.Open();
+
+ using (var context = new FootballDbContext(sqLiteConnection, false))
+ {
+ CreateAndSeedDatabase(context);
+ DisplaySeededData(context);
+ }
+ }
+ }
+
+ private static void StartDemoUseFile()
+ {
+ System.Console.WriteLine("Starting Demo Application (File)");
+ System.Console.WriteLine(string.Empty);
+
+ using (var context = new FootballDbContext(@"data source=.\db\footballDb\footballDb.sqlite;foreign keys=true"))
+ {
+ CreateAndSeedDatabase(context);
+ DisplaySeededData(context);
+ }
+ }
+
+ private static void CreateAndSeedDatabase(DbContext context)
+ {
+ System.Console.WriteLine("Create and seed the database.");
+
+ if (context.Set().Count() != 0)
+ {
+ return;
+ }
+
+ context.Set().Add(new Team
+ {
+ Name = "YB",
+ Coach = new Coach
+ {
+ City = "Zürich",
+ FirstName = "Masssaman",
+ LastName = "Nachn",
+ Street = "Testingstreet 844"
+ },
+ Players = new List
+ {
+ new Player
+ {
+ City = "Bern",
+ FirstName = "Marco",
+ LastName = "Bürki",
+ Street = "Wunderstrasse 43",
+ Number = 12
+ },
+ new Player
+ {
+ City = "Berlin",
+ FirstName = "Alain",
+ LastName = "Rochat",
+ Street = "Wonderstreet 13",
+ Number = 14
+ }
+ },
+ Stadion = new Stadion
+ {
+ Name = "Stade de Suisse",
+ City = "Bern",
+ Street = "Papiermühlestrasse 71"
+ }
+ });
+
+ context.SaveChanges();
+
+ System.Console.WriteLine("Completed.");
+ System.Console.WriteLine();
+ }
+
+ private static void DisplaySeededData(DbContext context)
+ {
+ System.Console.WriteLine("Display seeded data.");
+
+ foreach (Team team in context.Set())
+ {
+ System.Console.WriteLine("\t Team:");
+ System.Console.WriteLine("\t Id: {0}", team.Id);
+ System.Console.WriteLine("\t Name: {0}", team.Name);
+ System.Console.WriteLine();
+
+ System.Console.WriteLine("\t\t Stadion:");
+ System.Console.WriteLine("\t\t Name: {0}", team.Stadion.Name);
+ System.Console.WriteLine("\t\t Street: {0}", team.Stadion.Street);
+ System.Console.WriteLine("\t\t City: {0}", team.Stadion.City);
+ System.Console.WriteLine();
+
+ System.Console.WriteLine("\t\t Coach:");
+ System.Console.WriteLine("\t\t Id: {0}", team.Coach.Id);
+ System.Console.WriteLine("\t\t FirstName: {0}", team.Coach.FirstName);
+ System.Console.WriteLine("\t\t LastName: {0}", team.Coach.LastName);
+ System.Console.WriteLine("\t\t Street: {0}", team.Coach.Street);
+ System.Console.WriteLine("\t\t City: {0}", team.Coach.City);
+ System.Console.WriteLine();
+
+ foreach (Player player in team.Players)
+ {
+ System.Console.WriteLine("\t\t Player:");
+ System.Console.WriteLine("\t\t Id: {0}", player.Id);
+ System.Console.WriteLine("\t\t Number: {0}", player.Number);
+ System.Console.WriteLine("\t\t FirstName: {0}", player.FirstName);
+ System.Console.WriteLine("\t\t LastName: {0}", player.LastName);
+ System.Console.WriteLine("\t\t Street: {0}", player.Street);
+ System.Console.WriteLine("\t\t City: {0}", player.City);
+ System.Console.WriteLine("\t\t Created: {0}", player.CreatedUtc);
+ System.Console.WriteLine();
+ }
+ }
+ }
+
+ private static void PressEnterToExit()
+ {
+ System.Console.WriteLine();
+ System.Console.WriteLine("Press 'Enter' to exit.");
+ System.Console.ReadLine();
+ }
+ }
+}
diff --git a/SQLite.CodeFirst.NetCore.Console/SQLite.CodeFirst.NetCore.Console.csproj b/SQLite.CodeFirst.NetCore.Console/SQLite.CodeFirst.NetCore.Console.csproj
new file mode 100644
index 0000000..d8dbac3
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/SQLite.CodeFirst.NetCore.Console.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ net10.0
+ true
+ ..\Shared\SQLite.CodeFirst.StrongNameKey.snk
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SQLite.CodeFirst.NetCore.Console/packages.lock.json b/SQLite.CodeFirst.NetCore.Console/packages.lock.json
new file mode 100644
index 0000000..c50d503
--- /dev/null
+++ b/SQLite.CodeFirst.NetCore.Console/packages.lock.json
@@ -0,0 +1,142 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net10.0": {
+ "System.Data.SQLite": {
+ "type": "Direct",
+ "requested": "[1.0.119, )",
+ "resolved": "1.0.119",
+ "contentHash": "JSOJpnBf9goMnxGTJFGCmm6AffxgtpuXNXV5YvWO8UNC2zwd12qkUe5lAbnY+2ohIkIukgIjbvR1RA/sWILv3w==",
+ "dependencies": {
+ "System.Data.SQLite.Core": "[1.0.119]",
+ "System.Data.SQLite.EF6": "[1.0.119]"
+ }
+ },
+ "System.Data.SQLite.EF6": {
+ "type": "Direct",
+ "requested": "[1.0.119, )",
+ "resolved": "1.0.119",
+ "contentHash": "BwwgCSeA80gsxdXtU7IQEBrN9kQXWQrD11hNYOJZbXBBI1C4r7hA4QhBAalO1nzijXikthGRUADIEMI3nlucLA==",
+ "dependencies": {
+ "EntityFramework": "6.4.4"
+ }
+ },
+ "EntityFramework": {
+ "type": "Transitive",
+ "resolved": "6.5.2",
+ "contentHash": "8iOcnaKcgkWh35s8EhknnENNwwqvrBJ7jeNbQITbGo1mIeIOhGPfVdNOXZ3Y496T6e6wfXvz0WeXlgMdcgVYSA==",
+ "dependencies": {
+ "System.CodeDom": "6.0.0",
+ "System.ComponentModel.Annotations": "5.0.0",
+ "System.Configuration.ConfigurationManager": "6.0.1",
+ "System.Data.SqlClient": "4.8.6"
+ }
+ },
+ "Microsoft.Win32.SystemEvents": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A=="
+ },
+ "runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.7.0",
+ "contentHash": "9kyFSIdN3T0qjDQ2R0HRXYIhS3l5psBzQi6qqhdLz+SzFyEy4sVxNOke+yyYv8Cu8rPER12c3RDjLT8wF3WBYQ==",
+ "dependencies": {
+ "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
+ "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
+ "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0"
+ }
+ },
+ "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg=="
+ },
+ "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ=="
+ },
+ "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA=="
+ },
+ "Stub.System.Data.SQLite.Core.NetStandard": {
+ "type": "Transitive",
+ "resolved": "1.0.119",
+ "contentHash": "dI7ngiCNgdm+n00nQvFTa+LbHvE9MIQXwMSLRzJI/KAJ7G1WmCachsvfE1CD6xvb3OXJvYYEfv3+S/LHyhN0Rg=="
+ },
+ "System.CodeDom": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA=="
+ },
+ "System.ComponentModel.Annotations": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg=="
+ },
+ "System.Configuration.ConfigurationManager": {
+ "type": "Transitive",
+ "resolved": "6.0.1",
+ "contentHash": "jXw9MlUu/kRfEU0WyTptAVueupqIeE3/rl0EZDMlf8pcvJnitQ8HeVEp69rZdaStXwTV72boi/Bhw8lOeO+U2w==",
+ "dependencies": {
+ "System.Security.Cryptography.ProtectedData": "6.0.0",
+ "System.Security.Permissions": "6.0.0"
+ }
+ },
+ "System.Data.SqlClient": {
+ "type": "Transitive",
+ "resolved": "4.8.6",
+ "contentHash": "2Ij/LCaTQRyAi5lAv7UUTV9R2FobC8xN9mE0fXBZohum/xLl8IZVmE98Rq5ugQHjCgTBRKqpXRb4ORulRdA6Ig==",
+ "dependencies": {
+ "runtime.native.System.Data.SqlClient.sni": "4.7.0"
+ }
+ },
+ "System.Data.SQLite.Core": {
+ "type": "Transitive",
+ "resolved": "1.0.119",
+ "contentHash": "bhQB8HVtRA+OOYw8UTD1F1kU+nGJ0/OZvH1JmlVUI4bGvgVEWeX1NcHjA765NvUoRVuCPlt8PrEpZ1thSsk1jg==",
+ "dependencies": {
+ "Stub.System.Data.SQLite.Core.NetStandard": "[1.0.119]"
+ }
+ },
+ "System.Drawing.Common": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==",
+ "dependencies": {
+ "Microsoft.Win32.SystemEvents": "6.0.0"
+ }
+ },
+ "System.Security.Cryptography.ProtectedData": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ=="
+ },
+ "System.Security.Permissions": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==",
+ "dependencies": {
+ "System.Windows.Extensions": "6.0.0"
+ }
+ },
+ "System.Windows.Extensions": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==",
+ "dependencies": {
+ "System.Drawing.Common": "6.0.0"
+ }
+ },
+ "sqlite.codefirst": {
+ "type": "Project",
+ "dependencies": {
+ "EntityFramework": "[6.5.2, )"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.Test/IntegrationTests/CascadeOnUpdateTest.cs b/SQLite.CodeFirst.Test/IntegrationTests/CascadeOnUpdateTest.cs
new file mode 100644
index 0000000..1e7b45c
--- /dev/null
+++ b/SQLite.CodeFirst.Test/IntegrationTests/CascadeOnUpdateTest.cs
@@ -0,0 +1,125 @@
+using System.Collections.Generic;
+using System.Data.Common;
+using System.Data.Entity;
+using System.Data.Entity.Infrastructure;
+using System.Data.SQLite;
+using System.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace SQLite.CodeFirst.Test.IntegrationTests
+{
+ ///
+ /// Verifies that the placed on a foreign key property
+ /// results in 'ON UPDATE CASCADE' being emitted for the corresponding foreign key constraint.
+ /// This exercises the full pipeline: the attribute is registered as a column annotation, survives
+ /// the EF model build and is read back from the store model when the SQL is generated.
+ ///
+ [TestClass]
+ public class CascadeOnUpdateTest
+ {
+ private static string generatedSql;
+
+ [TestMethod]
+ public void CascadeOnUpdateAttributeEmitsOnUpdateCascade()
+ {
+ using (DbConnection connection = new SQLiteConnection("FullUri=file::memory:"))
+ {
+ // This is important! Else the in memory database will not work.
+ connection.Open();
+
+ using (var context = new CascadeDbContext(connection))
+ {
+ // Touching the set forces the initializer (and therefore the SQL generation) to run.
+ context.Set().FirstOrDefault();
+ }
+ }
+
+ // Decorated FK with cascade-on-delete: both keywords, delete before update.
+ StringAssert.Contains(generatedSql, "FOREIGN KEY ([CustomerId]) REFERENCES \"Customers\"([Id]) ON DELETE CASCADE ON UPDATE CASCADE");
+
+ // Decorated FK without cascade-on-delete: only the update keyword.
+ StringAssert.Contains(generatedSql, "FOREIGN KEY ([WarehouseId]) REFERENCES \"Warehouses\"([Id]) ON UPDATE CASCADE");
+
+ // Undecorated FK: no cascade keyword at all.
+ Assert.IsFalse(generatedSql.Contains("[RegionId]) REFERENCES \"Regions\"([Id]) ON"),
+ "The foreign key without the attribute must not emit any cascade clause.");
+ }
+
+ private class Customer
+ {
+ public int Id { get; set; }
+ }
+
+ private class Warehouse
+ {
+ public int Id { get; set; }
+ }
+
+ private class Region
+ {
+ public int Id { get; set; }
+ }
+
+ private class Order
+ {
+ public int Id { get; set; }
+
+ [CascadeOnUpdate]
+ public int CustomerId { get; set; }
+ public Customer Customer { get; set; }
+
+ [CascadeOnUpdate]
+ public int? WarehouseId { get; set; }
+ public Warehouse Warehouse { get; set; }
+
+ public int? RegionId { get; set; }
+ public Region Region { get; set; }
+ }
+
+ private class CascadeDbContext : DbContext
+ {
+ public CascadeDbContext(DbConnection connection)
+ : base(connection, false)
+ {
+ }
+
+ protected override void OnModelCreating(DbModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity()
+ .HasRequired(o => o.Customer)
+ .WithMany()
+ .HasForeignKey(o => o.CustomerId)
+ .WillCascadeOnDelete(true);
+
+ modelBuilder.Entity()
+ .HasOptional(o => o.Warehouse)
+ .WithMany()
+ .HasForeignKey(o => o.WarehouseId)
+ .WillCascadeOnDelete(false);
+
+ modelBuilder.Entity()
+ .HasOptional(o => o.Region)
+ .WithMany()
+ .HasForeignKey(o => o.RegionId)
+ .WillCascadeOnDelete(false);
+
+ Database.SetInitializer(new CaptureInitializer(modelBuilder));
+ }
+ }
+
+ private class CaptureInitializer : SqliteInitializerBase
+ {
+ public CaptureInitializer(DbModelBuilder modelBuilder)
+ : base(modelBuilder)
+ {
+ }
+
+ public override void InitializeDatabase(CascadeDbContext context)
+ {
+ DbModel model = ModelBuilder.Build(context.Database.Connection);
+ generatedSql = new SqliteSqlGenerator().Generate(model.StoreModel);
+ base.InitializeDatabase(context);
+ }
+ }
+ }
+}
diff --git a/SQLite.CodeFirst.Test/IntegrationTests/InMemoryDbCreationTest.cs b/SQLite.CodeFirst.Test/IntegrationTests/InMemoryDbCreationTest.cs
index c707341..d5ef6ab 100644
--- a/SQLite.CodeFirst.Test/IntegrationTests/InMemoryDbCreationTest.cs
+++ b/SQLite.CodeFirst.Test/IntegrationTests/InMemoryDbCreationTest.cs
@@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
-using SQLite.CodeFirst.Console;
-using SQLite.CodeFirst.Console.Entity;
+using SQLite.CodeFirst.NetCore.Console;
+using SQLite.CodeFirst.NetCore.Console.Entity;
namespace SQLite.CodeFirst.Test.IntegrationTests
{
diff --git a/SQLite.CodeFirst.Test/IntegrationTests/SqlGenerationDefaultCollationTest.cs b/SQLite.CodeFirst.Test/IntegrationTests/SqlGenerationDefaultCollationTest.cs
new file mode 100644
index 0000000..35acd38
--- /dev/null
+++ b/SQLite.CodeFirst.Test/IntegrationTests/SqlGenerationDefaultCollationTest.cs
@@ -0,0 +1,118 @@
+using System.Data.Common;
+using System.Data.Entity;
+using System.Data.Entity.Infrastructure;
+using System.Data.SQLite;
+using System.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SQLite.CodeFirst.NetCore.Console;
+using SQLite.CodeFirst.NetCore.Console.Entity;
+
+namespace SQLite.CodeFirst.Test.IntegrationTests
+{
+ [TestClass]
+ public class SqlGenerationDefaultCollationTest
+ {
+ private const string ReferenceSql =
+ @"
+CREATE TABLE ""MyTable"" ([Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, [Name] nvarchar NOT NULL COLLATE custom_collate, FOREIGN KEY ([Id]) REFERENCES ""Coaches""([Id]));
+CREATE TABLE ""Coaches"" ([Id] INTEGER PRIMARY KEY, [FirstName] nvarchar (50) COLLATE NOCASE, [LastName] nvarchar (50) COLLATE custom_collate, [Street] nvarchar (100) COLLATE custom_collate, [City] nvarchar NOT NULL COLLATE custom_collate, [CreatedUtc] datetime NOT NULL DEFAULT (DATETIME('now')));
+CREATE TABLE ""TeamPlayer"" ([Id] INTEGER PRIMARY KEY, [Number] int NOT NULL, [TeamId] int NOT NULL, [FirstName] nvarchar (50) COLLATE NOCASE, [LastName] nvarchar (50) COLLATE custom_collate, [Street] nvarchar (100) COLLATE custom_collate, [City] nvarchar NOT NULL COLLATE custom_collate, [CreatedUtc] datetime NOT NULL DEFAULT (DATETIME('now')), [Mentor_Id] int, FOREIGN KEY ([Mentor_Id]) REFERENCES ""TeamPlayer""([Id]), FOREIGN KEY ([TeamId]) REFERENCES ""MyTable""([Id]) ON DELETE CASCADE);
+CREATE TABLE ""Stadions"" ([Name] nvarchar (128) NOT NULL COLLATE custom_collate, [Street] nvarchar (128) NOT NULL COLLATE custom_collate, [City] nvarchar (128) NOT NULL COLLATE custom_collate, [Order] int NOT NULL, [Team_Id] int NOT NULL, PRIMARY KEY([Name], [Street], [City]), FOREIGN KEY ([Team_Id]) REFERENCES ""MyTable""([Id]) ON DELETE CASCADE);
+CREATE TABLE ""Foos"" ([FooId] INTEGER PRIMARY KEY, [Name] nvarchar COLLATE custom_collate, [FooSelf1Id] int, [FooSelf2Id] int, [FooSelf3Id] int, FOREIGN KEY ([FooSelf1Id]) REFERENCES ""Foos""([FooId]), FOREIGN KEY ([FooSelf2Id]) REFERENCES ""Foos""([FooId]), FOREIGN KEY ([FooSelf3Id]) REFERENCES ""Foos""([FooId]));
+CREATE TABLE ""FooSelves"" ([FooSelfId] INTEGER PRIMARY KEY, [FooId] int NOT NULL, [Number] int NOT NULL, FOREIGN KEY ([FooId]) REFERENCES ""Foos""([FooId]) ON DELETE CASCADE);
+CREATE TABLE ""FooSteps"" ([FooStepId] INTEGER PRIMARY KEY, [FooId] int NOT NULL, [Number] int NOT NULL, FOREIGN KEY ([FooId]) REFERENCES ""Foos""([FooId]) ON DELETE CASCADE);
+CREATE TABLE ""FooCompositeKeys"" ([Id] int NOT NULL, [Version] nvarchar (20) NOT NULL COLLATE custom_collate, [Name] nvarchar (255) COLLATE custom_collate, PRIMARY KEY([Id], [Version]));
+CREATE TABLE ""FooRelationshipAs"" ([Id] INTEGER PRIMARY KEY, [Name] nvarchar (255) COLLATE custom_collate);
+CREATE TABLE ""FooRelationshipBs"" ([Id] INTEGER PRIMARY KEY, [Name] nvarchar (255) COLLATE custom_collate);
+CREATE TABLE ""FooRelationshipAFooCompositeKeys"" ([FooRelationshipA_Id] int NOT NULL, [FooCompositeKey_Id] int NOT NULL, [FooCompositeKey_Version] nvarchar (20) NOT NULL COLLATE custom_collate, PRIMARY KEY([FooRelationshipA_Id], [FooCompositeKey_Id], [FooCompositeKey_Version]), FOREIGN KEY ([FooRelationshipA_Id]) REFERENCES ""FooRelationshipAs""([Id]) ON DELETE CASCADE, FOREIGN KEY ([FooCompositeKey_Id], [FooCompositeKey_Version]) REFERENCES ""FooCompositeKeys""([Id], [Version]) ON DELETE CASCADE);
+CREATE TABLE ""FooRelationshipBFooCompositeKeys"" ([FooRelationshipB_Id] int NOT NULL, [FooCompositeKey_Id] int NOT NULL, [FooCompositeKey_Version] nvarchar (20) NOT NULL COLLATE custom_collate, PRIMARY KEY([FooRelationshipB_Id], [FooCompositeKey_Id], [FooCompositeKey_Version]), FOREIGN KEY ([FooRelationshipB_Id]) REFERENCES ""FooRelationshipBs""([Id]) ON DELETE CASCADE, FOREIGN KEY ([FooCompositeKey_Id], [FooCompositeKey_Version]) REFERENCES ""FooCompositeKeys""([Id], [Version]) ON DELETE CASCADE);
+CREATE INDEX ""IX_MyTable_Id"" ON ""MyTable"" (""Id"");
+CREATE INDEX ""IX_Team_TeamsName"" ON ""MyTable"" (""Name"");
+CREATE INDEX ""IX_TeamPlayer_Number"" ON ""TeamPlayer"" (""Number"");
+CREATE UNIQUE INDEX ""IX_TeamPlayer_NumberPerTeam"" ON ""TeamPlayer"" (""Number"", ""TeamId"");
+CREATE INDEX ""IX_TeamPlayer_Mentor_Id"" ON ""TeamPlayer"" (""Mentor_Id"");
+CREATE UNIQUE INDEX ""IX_Stadion_Main"" ON ""Stadions"" (""Street"", ""Name"");
+CREATE UNIQUE INDEX ""ReservedKeyWordTest"" ON ""Stadions"" (""Order"");
+CREATE INDEX ""IX_Stadion_Team_Id"" ON ""Stadions"" (""Team_Id"");
+CREATE INDEX ""IX_Foo_FooSelf1Id"" ON ""Foos"" (""FooSelf1Id"");
+CREATE INDEX ""IX_Foo_FooSelf2Id"" ON ""Foos"" (""FooSelf2Id"");
+CREATE INDEX ""IX_Foo_FooSelf3Id"" ON ""Foos"" (""FooSelf3Id"");
+CREATE INDEX ""IX_FooSelf_FooId"" ON ""FooSelves"" (""FooId"");
+CREATE INDEX ""IX_FooStep_FooId"" ON ""FooSteps"" (""FooId"");
+CREATE INDEX ""IX_FooRelationshipAFooCompositeKey_FooRelationshipA_Id"" ON ""FooRelationshipAFooCompositeKeys"" (""FooRelationshipA_Id"");
+CREATE INDEX ""IX_FooRelationshipAFooCompositeKey_FooCompositeKey_Id_FooCompositeKey_Version"" ON ""FooRelationshipAFooCompositeKeys"" (""FooCompositeKey_Id"", ""FooCompositeKey_Version"");
+CREATE INDEX ""IX_FooRelationshipBFooCompositeKey_FooRelationshipB_Id"" ON ""FooRelationshipBFooCompositeKeys"" (""FooRelationshipB_Id"");
+CREATE INDEX ""IX_FooRelationshipBFooCompositeKey_FooCompositeKey_Id_FooCompositeKey_Version"" ON ""FooRelationshipBFooCompositeKeys"" (""FooCompositeKey_Id"", ""FooCompositeKey_Version"");
+";
+
+ private static string generatedSql;
+
+ // Does not work on the build server. No clue why.
+
+ [TestMethod]
+ public void SqliteSqlGeneratorWithDefaultCollationTest()
+ {
+ using (DbConnection connection = new SQLiteConnection("FullUri=file::memory:"))
+ {
+ // This is important! Else the in memory database will not work.
+ connection.Open();
+
+ var defaultCollation = new Collation() { Function = CollationFunction.Custom, CustomFunction = "custom_collate" };
+ using (var context = new DummyDbContext(connection, defaultCollation))
+ {
+ // ReSharper disable once UnusedVariable
+ Player fo = context.Set().FirstOrDefault();
+
+ Assert.AreEqual(RemoveLineEndings(ReferenceSql), RemoveLineEndings(generatedSql));
+ }
+ }
+ }
+
+ private static string RemoveLineEndings(string input)
+ {
+ string lineSeparator = ((char)0x2028).ToString();
+ string paragraphSeparator = ((char)0x2029).ToString();
+ return input.Replace("\r\n", string.Empty).Replace("\n", string.Empty).Replace("\r", string.Empty).Replace(lineSeparator, string.Empty).Replace(paragraphSeparator, string.Empty);
+ }
+
+ private class DummyDbContext : DbContext
+ {
+ private readonly Collation defaultCollation;
+
+ public DummyDbContext(DbConnection connection, Collation defaultCollation = null)
+ : base(connection, false)
+ {
+ this.defaultCollation = defaultCollation;
+ }
+
+ protected override void OnModelCreating(DbModelBuilder modelBuilder)
+ {
+ // This configuration contains all supported cases.
+ // So it makes a perfect test to validate whether the
+ // generated SQL is correct.
+ ModelConfiguration.Configure(modelBuilder);
+ var initializer = new AssertInitializer(modelBuilder, defaultCollation);
+ Database.SetInitializer(initializer);
+ }
+
+ private class AssertInitializer : SqliteInitializerBase
+ {
+ private readonly Collation defaultCollation;
+
+ public AssertInitializer(DbModelBuilder modelBuilder, Collation defaultCollation)
+ : base(modelBuilder)
+ {
+ this.defaultCollation = defaultCollation;
+ }
+
+ public override void InitializeDatabase(DummyDbContext context)
+ {
+ DbModel model = ModelBuilder.Build(context.Database.Connection);
+ var sqliteSqlGenerator = new SqliteSqlGenerator(defaultCollation);
+ generatedSql = sqliteSqlGenerator.Generate(model.StoreModel);
+ base.InitializeDatabase(context);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.Test/IntegrationTests/SqlGenerationTest.cs b/SQLite.CodeFirst.Test/IntegrationTests/SqlGenerationTest.cs
index 6db4301..22b3efa 100644
--- a/SQLite.CodeFirst.Test/IntegrationTests/SqlGenerationTest.cs
+++ b/SQLite.CodeFirst.Test/IntegrationTests/SqlGenerationTest.cs
@@ -1,12 +1,11 @@
-using System;
-using System.Data.Common;
+using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.SQLite;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
-using SQLite.CodeFirst.Console;
-using SQLite.CodeFirst.Console.Entity;
+using SQLite.CodeFirst.NetCore.Console;
+using SQLite.CodeFirst.NetCore.Console.Entity;
namespace SQLite.CodeFirst.Test.IntegrationTests
{
@@ -14,18 +13,19 @@ namespace SQLite.CodeFirst.Test.IntegrationTests
public class SqlGenerationTest
{
private const string ReferenceSql =
- @"CREATE TABLE ""MyTable"" ([Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, [Name] nvarchar NOT NULL, FOREIGN KEY (Id) REFERENCES ""Coaches""(Id));
+ @"
+CREATE TABLE ""MyTable"" ([Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, [Name] nvarchar NOT NULL, FOREIGN KEY ([Id]) REFERENCES ""Coaches""([Id]));
CREATE TABLE ""Coaches"" ([Id] INTEGER PRIMARY KEY, [FirstName] nvarchar (50) COLLATE NOCASE, [LastName] nvarchar (50), [Street] nvarchar (100), [City] nvarchar NOT NULL, [CreatedUtc] datetime NOT NULL DEFAULT (DATETIME('now')));
-CREATE TABLE ""TeamPlayer"" ([Id] INTEGER PRIMARY KEY, [Number] int NOT NULL, [TeamId] int NOT NULL, [FirstName] nvarchar (50) COLLATE NOCASE, [LastName] nvarchar (50), [Street] nvarchar (100), [City] nvarchar NOT NULL, [CreatedUtc] datetime NOT NULL DEFAULT (DATETIME('now')), [Mentor_Id] int, FOREIGN KEY (Mentor_Id) REFERENCES ""TeamPlayer""(Id), FOREIGN KEY (TeamId) REFERENCES ""MyTable""(Id) ON DELETE CASCADE);
-CREATE TABLE ""Stadions"" ([Name] nvarchar (128) NOT NULL, [Street] nvarchar (128) NOT NULL, [City] nvarchar (128) NOT NULL, [Order] int NOT NULL, [Team_Id] int NOT NULL, PRIMARY KEY(Name, Street, City), FOREIGN KEY (Team_Id) REFERENCES ""MyTable""(Id) ON DELETE CASCADE);
-CREATE TABLE ""Foos"" ([FooId] INTEGER PRIMARY KEY, [Name] nvarchar, [FooSelf1Id] int, [FooSelf2Id] int, [FooSelf3Id] int, FOREIGN KEY (FooSelf1Id) REFERENCES ""Foos""(FooId), FOREIGN KEY (FooSelf2Id) REFERENCES ""Foos""(FooId), FOREIGN KEY (FooSelf3Id) REFERENCES ""Foos""(FooId));
-CREATE TABLE ""FooSelves"" ([FooSelfId] INTEGER PRIMARY KEY, [FooId] int NOT NULL, [Number] int NOT NULL, FOREIGN KEY (FooId) REFERENCES ""Foos""(FooId) ON DELETE CASCADE);
-CREATE TABLE ""FooSteps"" ([FooStepId] INTEGER PRIMARY KEY, [FooId] int NOT NULL, [Number] int NOT NULL, FOREIGN KEY (FooId) REFERENCES ""Foos""(FooId) ON DELETE CASCADE);
-CREATE TABLE ""FooCompositeKeys"" ([Id] int NOT NULL, [Version] nvarchar (20) NOT NULL, [Name] nvarchar (255), PRIMARY KEY(Id, Version));
+CREATE TABLE ""TeamPlayer"" ([Id] INTEGER PRIMARY KEY, [Number] int NOT NULL, [TeamId] int NOT NULL, [FirstName] nvarchar (50) COLLATE NOCASE, [LastName] nvarchar (50), [Street] nvarchar (100), [City] nvarchar NOT NULL, [CreatedUtc] datetime NOT NULL DEFAULT (DATETIME('now')), [Mentor_Id] int, FOREIGN KEY ([Mentor_Id]) REFERENCES ""TeamPlayer""([Id]), FOREIGN KEY ([TeamId]) REFERENCES ""MyTable""([Id]) ON DELETE CASCADE);
+CREATE TABLE ""Stadions"" ([Name] nvarchar (128) NOT NULL, [Street] nvarchar (128) NOT NULL, [City] nvarchar (128) NOT NULL, [Order] int NOT NULL, [Team_Id] int NOT NULL, PRIMARY KEY([Name], [Street], [City]), FOREIGN KEY ([Team_Id]) REFERENCES ""MyTable""([Id]) ON DELETE CASCADE);
+CREATE TABLE ""Foos"" ([FooId] INTEGER PRIMARY KEY, [Name] nvarchar, [FooSelf1Id] int, [FooSelf2Id] int, [FooSelf3Id] int, FOREIGN KEY ([FooSelf1Id]) REFERENCES ""Foos""([FooId]), FOREIGN KEY ([FooSelf2Id]) REFERENCES ""Foos""([FooId]), FOREIGN KEY ([FooSelf3Id]) REFERENCES ""Foos""([FooId]));
+CREATE TABLE ""FooSelves"" ([FooSelfId] INTEGER PRIMARY KEY, [FooId] int NOT NULL, [Number] int NOT NULL, FOREIGN KEY ([FooId]) REFERENCES ""Foos""([FooId]) ON DELETE CASCADE);
+CREATE TABLE ""FooSteps"" ([FooStepId] INTEGER PRIMARY KEY, [FooId] int NOT NULL, [Number] int NOT NULL, FOREIGN KEY ([FooId]) REFERENCES ""Foos""([FooId]) ON DELETE CASCADE);
+CREATE TABLE ""FooCompositeKeys"" ([Id] int NOT NULL, [Version] nvarchar (20) NOT NULL, [Name] nvarchar (255), PRIMARY KEY([Id], [Version]));
CREATE TABLE ""FooRelationshipAs"" ([Id] INTEGER PRIMARY KEY, [Name] nvarchar (255));
CREATE TABLE ""FooRelationshipBs"" ([Id] INTEGER PRIMARY KEY, [Name] nvarchar (255));
-CREATE TABLE ""FooRelationshipAFooCompositeKeys"" ([FooRelationshipA_Id] int NOT NULL, [FooCompositeKey_Id] int NOT NULL, [FooCompositeKey_Version] nvarchar (20) NOT NULL, PRIMARY KEY(FooRelationshipA_Id, FooCompositeKey_Id, FooCompositeKey_Version), FOREIGN KEY (FooRelationshipA_Id) REFERENCES ""FooRelationshipAs""(Id) ON DELETE CASCADE, FOREIGN KEY (FooCompositeKey_Id, FooCompositeKey_Version) REFERENCES ""FooCompositeKeys""(Id, Version) ON DELETE CASCADE);
-CREATE TABLE ""FooRelationshipBFooCompositeKeys"" ([FooRelationshipB_Id] int NOT NULL, [FooCompositeKey_Id] int NOT NULL, [FooCompositeKey_Version] nvarchar (20) NOT NULL, PRIMARY KEY(FooRelationshipB_Id, FooCompositeKey_Id, FooCompositeKey_Version), FOREIGN KEY (FooRelationshipB_Id) REFERENCES ""FooRelationshipBs""(Id) ON DELETE CASCADE, FOREIGN KEY (FooCompositeKey_Id, FooCompositeKey_Version) REFERENCES ""FooCompositeKeys""(Id, Version) ON DELETE CASCADE);
+CREATE TABLE ""FooRelationshipAFooCompositeKeys"" ([FooRelationshipA_Id] int NOT NULL, [FooCompositeKey_Id] int NOT NULL, [FooCompositeKey_Version] nvarchar (20) NOT NULL, PRIMARY KEY([FooRelationshipA_Id], [FooCompositeKey_Id], [FooCompositeKey_Version]), FOREIGN KEY ([FooRelationshipA_Id]) REFERENCES ""FooRelationshipAs""([Id]) ON DELETE CASCADE, FOREIGN KEY ([FooCompositeKey_Id], [FooCompositeKey_Version]) REFERENCES ""FooCompositeKeys""([Id], [Version]) ON DELETE CASCADE);
+CREATE TABLE ""FooRelationshipBFooCompositeKeys"" ([FooRelationshipB_Id] int NOT NULL, [FooCompositeKey_Id] int NOT NULL, [FooCompositeKey_Version] nvarchar (20) NOT NULL, PRIMARY KEY([FooRelationshipB_Id], [FooCompositeKey_Id], [FooCompositeKey_Version]), FOREIGN KEY ([FooRelationshipB_Id]) REFERENCES ""FooRelationshipBs""([Id]) ON DELETE CASCADE, FOREIGN KEY ([FooCompositeKey_Id], [FooCompositeKey_Version]) REFERENCES ""FooCompositeKeys""([Id], [Version]) ON DELETE CASCADE);
CREATE INDEX ""IX_MyTable_Id"" ON ""MyTable"" (""Id"");
CREATE INDEX ""IX_Team_TeamsName"" ON ""MyTable"" (""Name"");
CREATE INDEX ""IX_TeamPlayer_Number"" ON ""TeamPlayer"" (""Number"");
@@ -42,12 +42,13 @@ public class SqlGenerationTest
CREATE INDEX ""IX_FooRelationshipAFooCompositeKey_FooRelationshipA_Id"" ON ""FooRelationshipAFooCompositeKeys"" (""FooRelationshipA_Id"");
CREATE INDEX ""IX_FooRelationshipAFooCompositeKey_FooCompositeKey_Id_FooCompositeKey_Version"" ON ""FooRelationshipAFooCompositeKeys"" (""FooCompositeKey_Id"", ""FooCompositeKey_Version"");
CREATE INDEX ""IX_FooRelationshipBFooCompositeKey_FooRelationshipB_Id"" ON ""FooRelationshipBFooCompositeKeys"" (""FooRelationshipB_Id"");
-CREATE INDEX ""IX_FooRelationshipBFooCompositeKey_FooCompositeKey_Id_FooCompositeKey_Version"" ON ""FooRelationshipBFooCompositeKeys"" (""FooCompositeKey_Id"", ""FooCompositeKey_Version"");";
+CREATE INDEX ""IX_FooRelationshipBFooCompositeKey_FooCompositeKey_Id_FooCompositeKey_Version"" ON ""FooRelationshipBFooCompositeKeys"" (""FooCompositeKey_Id"", ""FooCompositeKey_Version"");
+";
private static string generatedSql;
// Does not work on the build server. No clue why.
-
+
[TestMethod]
public void SqliteSqlGeneratorTest()
{
diff --git a/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj b/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj
index 149061f..0228e1e 100644
--- a/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj
+++ b/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj
@@ -1,34 +1,24 @@
-
+
- net472
+ net10.0
SQLite.CodeFirst.Test
Contains the Unit Tests for the SQLite.CodeFirst Library.
1.0.0.0
true
..\Shared\SQLite.CodeFirst.StrongNameKey.snk
+
+ Exe
+ true
-
- full
-
-
- pdbonly
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
+
-
\ No newline at end of file
+
diff --git a/SQLite.CodeFirst.Test/TestSetup.cs b/SQLite.CodeFirst.Test/TestSetup.cs
new file mode 100644
index 0000000..e180699
--- /dev/null
+++ b/SQLite.CodeFirst.Test/TestSetup.cs
@@ -0,0 +1,23 @@
+using System.Data.Entity;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SQLite.CodeFirst.NetCore.Console;
+
+namespace SQLite.CodeFirst.Test
+{
+ ///
+ /// Registers the SQLite Entity Framework 6 provider once for the whole test assembly.
+ /// On .NET (Core) there is no app.config based provider discovery, so the code based
+ /// from the demo project is applied before any test runs.
+ /// Setting it explicitly (instead of relying on assembly scanning) makes the registration
+ /// independent of which DbContext the test runner happens to touch first.
+ ///
+ [TestClass]
+ public static class TestSetup
+ {
+ [AssemblyInitialize]
+ public static void Initialize(TestContext context)
+ {
+ DbConfiguration.SetConfiguration(new Configuration());
+ }
+ }
+}
diff --git a/SQLite.CodeFirst.Test/UnitTests/DbInitializers/InitializerDefaultCollationTest.cs b/SQLite.CodeFirst.Test/UnitTests/DbInitializers/InitializerDefaultCollationTest.cs
new file mode 100644
index 0000000..256b323
--- /dev/null
+++ b/SQLite.CodeFirst.Test/UnitTests/DbInitializers/InitializerDefaultCollationTest.cs
@@ -0,0 +1,64 @@
+using System.Data.Entity;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SQLite.CodeFirst.NetCore.Console;
+
+namespace SQLite.CodeFirst.Test.UnitTests.DbInitializers
+{
+ ///
+ /// Verifies that the public initializers forward an explicitly supplied default
+ /// to the base class, which is what feeds it into the SQL generation.
+ /// Without this the documented "default collation" feature is unreachable through the shipped initializers.
+ ///
+ [TestClass]
+ public class InitializerDefaultCollationTest
+ {
+ private static readonly Collation Collation = new Collation(CollationFunction.RTrim);
+
+ private static DbModelBuilder NewModelBuilder()
+ {
+ return new DbModelBuilder();
+ }
+
+ [TestMethod]
+ public void SqliteCreateDatabaseIfNotExists_ForwardsDefaultCollation()
+ {
+ var initializer = new SqliteCreateDatabaseIfNotExists(NewModelBuilder(), Collation);
+ Assert.AreSame(Collation, initializer.DefaultCollation);
+ }
+
+ [TestMethod]
+ public void SqliteCreateDatabaseIfNotExists_WithNullByteFlag_ForwardsDefaultCollation()
+ {
+ var initializer = new SqliteCreateDatabaseIfNotExists(NewModelBuilder(), true, Collation);
+ Assert.AreSame(Collation, initializer.DefaultCollation);
+ }
+
+ [TestMethod]
+ public void SqliteDropCreateDatabaseAlways_ForwardsDefaultCollation()
+ {
+ var initializer = new SqliteDropCreateDatabaseAlways(NewModelBuilder(), Collation);
+ Assert.AreSame(Collation, initializer.DefaultCollation);
+ }
+
+ [TestMethod]
+ public void SqliteDropCreateDatabaseWhenModelChanges_ForwardsDefaultCollation()
+ {
+ var initializer = new SqliteDropCreateDatabaseWhenModelChanges(NewModelBuilder(), Collation);
+ Assert.AreSame(Collation, initializer.DefaultCollation);
+ }
+
+ [TestMethod]
+ public void SqliteDropCreateDatabaseWhenModelChanges_WithHistoryType_ForwardsDefaultCollation()
+ {
+ var initializer = new SqliteDropCreateDatabaseWhenModelChanges(NewModelBuilder(), typeof(History), Collation);
+ Assert.AreSame(Collation, initializer.DefaultCollation);
+ }
+
+ [TestMethod]
+ public void Initializer_WithoutCollation_HasNullDefaultCollation()
+ {
+ var initializer = new SqliteDropCreateDatabaseAlways(NewModelBuilder());
+ Assert.IsNull(initializer.DefaultCollation);
+ }
+ }
+}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/CollateConstraintTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/CollateConstraintTest.cs
index 8697d24..bc02127 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/CollateConstraintTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/CollateConstraintTest.cs
@@ -12,7 +12,7 @@ public void CreateStatement_StatementIsCorrect_NoConstraint()
var collationConstraint = new CollateConstraint();
collationConstraint.CollationFunction = CollationFunction.None;
string output = collationConstraint.CreateStatement();
- Assert.AreEqual(output, "");
+ Assert.AreEqual("", output);
}
[TestMethod]
@@ -21,7 +21,7 @@ public void CreateStatement_StatementIsCorrect_NoCase()
var collationConstraint = new CollateConstraint();
collationConstraint.CollationFunction = CollationFunction.NoCase;
string output = collationConstraint.CreateStatement();
- Assert.AreEqual(output, "COLLATE NOCASE");
+ Assert.AreEqual("COLLATE NOCASE", output);
}
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/ColumnConstraintCollectionTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/ColumnConstraintCollectionTest.cs
index eb6aa47..08def45 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/ColumnConstraintCollectionTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/ColumnConstraintCollectionTest.cs
@@ -18,7 +18,7 @@ public void CreateStatementOneColumnConstraintTest()
columnConstraintMock.Object
});
string output = columnConstraintCollection.CreateStatement();
- Assert.AreEqual(output, "dummy1");
+ Assert.AreEqual("dummy1", output);
}
[TestMethod]
@@ -36,7 +36,7 @@ public void CreateStatementTwoColumnConstraintsTest()
columnConstraintMock2.Object
});
string output = columnConstraintCollection.CreateStatement();
- Assert.AreEqual(output, "dummy1 dummy2");
+ Assert.AreEqual("dummy1 dummy2", output);
}
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/DefaultValueConstraintTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/DefaultValueConstraintTest.cs
index da2a20a..a8b01b1 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/DefaultValueConstraintTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/DefaultValueConstraintTest.cs
@@ -12,7 +12,7 @@ public void CreateStatement_StatementIsCorrect_IntDefault()
var defaultValueConstraint = new DefaultValueConstraint();
defaultValueConstraint.DefaultValue = "0";
string output = defaultValueConstraint.CreateStatement();
- Assert.AreEqual(output, "DEFAULT (0)");
+ Assert.AreEqual("DEFAULT (0)", output);
}
[TestMethod]
@@ -21,7 +21,7 @@ public void CreateStatement_StatementIsCorrect_StringDefault()
var defaultValueConstraint = new DefaultValueConstraint();
defaultValueConstraint.DefaultValue = @"'Something'";
string output = defaultValueConstraint.CreateStatement();
- Assert.AreEqual(output, "DEFAULT ('Something')");
+ Assert.AreEqual("DEFAULT ('Something')", output);
}
[TestMethod]
@@ -30,7 +30,7 @@ public void CreateStatement_StatementIsCorrect_ExpressionDefault()
var defaultValueConstraint = new DefaultValueConstraint();
defaultValueConstraint.DefaultValue = @"datetime('now')";
string output = defaultValueConstraint.CreateStatement();
- Assert.AreEqual(output, "DEFAULT (datetime('now'))");
+ Assert.AreEqual("DEFAULT (datetime('now'))", output);
}
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/MaxLengthConstraint.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/MaxLengthConstraint.cs
index 1df077f..3436359 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/MaxLengthConstraint.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/MaxLengthConstraint.cs
@@ -12,15 +12,14 @@ public void CreateStatementTest()
{
var maxLengthConstraint = new MaxLengthConstraint(12);
string output = maxLengthConstraint.CreateStatement();
- Assert.AreEqual(output, "(12)");
+ Assert.AreEqual("(12)", output);
}
[TestMethod]
- [ExpectedException(typeof(InvalidOperationException))]
public void CreateStatementInvalidParameterTest()
{
var maxLengthConstraint = new MaxLengthConstraint();
- maxLengthConstraint.CreateStatement();
+ Assert.ThrowsExactly(() => maxLengthConstraint.CreateStatement());
}
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/NotNullConstraintTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/NotNullConstraintTest.cs
index 747fdbf..59ea347 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/NotNullConstraintTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/NotNullConstraintTest.cs
@@ -11,7 +11,7 @@ public void CreateStatementTest()
{
var notNullConstraint = new NotNullConstraint();
string output = notNullConstraint.CreateStatement();
- Assert.AreEqual(output, "NOT NULL");
+ Assert.AreEqual("NOT NULL", output);
}
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/PrimaryKeyConstraintTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/PrimaryKeyConstraintTest.cs
index 5d5bc6d..71a9aea 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/PrimaryKeyConstraintTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/PrimaryKeyConstraintTest.cs
@@ -11,7 +11,7 @@ public void CreateStatement_StatementIsCorrect_WithAutoincrement()
{
var primaryKeyConstraint = new PrimaryKeyConstraint { Autoincrement = true };
string output = primaryKeyConstraint.CreateStatement();
- Assert.AreEqual(output, "PRIMARY KEY AUTOINCREMENT");
+ Assert.AreEqual("PRIMARY KEY AUTOINCREMENT", output);
}
[TestMethod]
@@ -19,7 +19,7 @@ public void CreateStatement_StatementIsCorrect()
{
var primaryKeyConstraint = new PrimaryKeyConstraint();
string output = primaryKeyConstraint.CreateStatement();
- Assert.AreEqual(output, "PRIMARY KEY");
+ Assert.AreEqual("PRIMARY KEY", output);
}
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/UniqueConstraintTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/UniqueConstraintTest.cs
index d09e2c5..ece92e1 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/UniqueConstraintTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/UniqueConstraintTest.cs
@@ -12,7 +12,7 @@ public void CreateStatement_StatementIsCorrect_NoConstraint()
var uniqueConstraint = new UniqueConstraint();
uniqueConstraint.OnConflict = OnConflictAction.None;
string output = uniqueConstraint.CreateStatement();
- Assert.AreEqual(output, "UNIQUE");
+ Assert.AreEqual("UNIQUE", output);
}
[TestMethod]
@@ -21,7 +21,7 @@ public void CreateStatement_StatementIsCorrect_WithConstraint()
var uniqueConstraint = new UniqueConstraint();
uniqueConstraint.OnConflict = OnConflictAction.Rollback;
string output = uniqueConstraint.CreateStatement();
- Assert.AreEqual(output, "UNIQUE ON CONFLICT ROLLBACK");
+ Assert.AreEqual("UNIQUE ON CONFLICT ROLLBACK", output);
}
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnStatementCollectionTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnStatementCollectionTest.cs
index eccb3fb..d40913b 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnStatementCollectionTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnStatementCollectionTest.cs
@@ -12,8 +12,8 @@ public void CreateStatementOneEntryTest()
var columnStatementCollection = new ColumnStatementCollection(new[] { CreateStatementMock("dummy1").Object });
string output = columnStatementCollection.CreateStatement();
- Assert.AreEqual(columnStatementCollection.Count, 1);
- Assert.AreEqual(output, "dummy1");
+ Assert.AreEqual(1, columnStatementCollection.Count);
+ Assert.AreEqual("dummy1", output);
}
[TestMethod]
@@ -26,8 +26,8 @@ public void CreateStatementTwoEntryTest()
});
string output = createIndexStatementCollection.CreateStatement();
- Assert.AreEqual(createIndexStatementCollection.Count, 2);
- Assert.AreEqual(output, "dummy1, dummy2");
+ Assert.AreEqual(2, createIndexStatementCollection.Count);
+ Assert.AreEqual("dummy1, dummy2", output);
}
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnStatementTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnStatementTest.cs
index 4d81d75..7643abc 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnStatementTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnStatementTest.cs
@@ -16,12 +16,12 @@ public void CreateStatement()
var columnStatement = new ColumnStatement
{
- ColumnConstraints = columnConstraintsMock.Object,
ColumnName = "dummyColumnName",
- TypeName = "dummyType"
+ TypeName = "dummyType",
+ ColumnConstraints = columnConstraintsMock.Object,
};
string output = columnStatement.CreateStatement();
- Assert.AreEqual(output, "[dummyColumnName] dummyType dummyColumnConstraint");
+ Assert.AreEqual("[dummyColumnName] dummyType dummyColumnConstraint", output);
}
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/CreateDatabaseStatementTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/CreateDatabaseStatementTest.cs
index 33e4f39..888574e 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Statement/CreateDatabaseStatementTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Statement/CreateDatabaseStatementTest.cs
@@ -16,8 +16,8 @@ public void CreateStatementWithOneKeyTest()
};
var createDatabaseStatement = new CreateDatabaseStatement(statements);
- Assert.AreEqual(createDatabaseStatement.Count, 1);
- Assert.AreEqual(createDatabaseStatement.CreateStatement(), "dummy1");
+ Assert.AreEqual(1, createDatabaseStatement.Count);
+ Assert.AreEqual("dummy1", createDatabaseStatement.CreateStatement());
}
[TestMethod]
@@ -30,8 +30,8 @@ public void CreateStatementWithTwoKeyTest()
};
var createDatabaseStatement = new CreateDatabaseStatement(statements);
- Assert.AreEqual(createDatabaseStatement.Count, 2);
- Assert.AreEqual(createDatabaseStatement.CreateStatement(), "dummy1\r\ndummy2");
+ Assert.AreEqual(2, createDatabaseStatement.Count);
+ Assert.AreEqual("dummy1\r\ndummy2", createDatabaseStatement.CreateStatement());
}
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/CreateIndexStatementCollectionTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/CreateIndexStatementCollectionTest.cs
index 1082710..b90626c 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Statement/CreateIndexStatementCollectionTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Statement/CreateIndexStatementCollectionTest.cs
@@ -15,8 +15,8 @@ public void CreateStatementOneEntryTest()
});
string output = createIndexStatementCollection.CreateStatement();
- Assert.AreEqual(createIndexStatementCollection.Count, 1);
- Assert.AreEqual(output, "dummy1");
+ Assert.AreEqual(1, createIndexStatementCollection.Count);
+ Assert.AreEqual("dummy1", output);
}
[TestMethod]
@@ -29,8 +29,8 @@ public void CreateStatementTwoEntryTest()
});
string output = createIndexStatementCollection.CreateStatement();
- Assert.AreEqual(createIndexStatementCollection.Count, 2);
- Assert.AreEqual(output, "dummy1\r\ndummy2");
+ Assert.AreEqual(2, createIndexStatementCollection.Count);
+ Assert.AreEqual("dummy1\r\ndummy2", output);
}
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/CreateTableStatementTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/CreateTableStatementTest.cs
index 31d3817..1d19219 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Statement/CreateTableStatementTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Statement/CreateTableStatementTest.cs
@@ -16,7 +16,7 @@ public void CreateStatementTest()
};
string output = createTableStatement.CreateStatement();
- Assert.AreEqual(output, "CREATE TABLE dummyTable (dummyColumnDefinition);");
+ Assert.AreEqual("CREATE TABLE dummyTable (dummyColumnDefinition);", output);
}
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/ForeignKeyStatementTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/ForeignKeyStatementTest.cs
index 192a6b7..65dd2d7 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Statement/ForeignKeyStatementTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ForeignKeyStatementTest.cs
@@ -19,7 +19,7 @@ public void CreateStatementOneForeignKeyTest()
};
string output = foreignKeyStatement.CreateStatement();
- Assert.AreEqual(output, "FOREIGN KEY (dummyForeignKey1) REFERENCES dummyForeignTable(dummForeignPrimaryKey1)");
+ Assert.AreEqual("FOREIGN KEY ([dummyForeignKey1]) REFERENCES dummyForeignTable([dummForeignPrimaryKey1])", output);
}
[TestMethod]
@@ -34,7 +34,38 @@ public void CreateStatementOneForeignKeyCascadeDeleteTest()
};
string output = foreignKeyStatement.CreateStatement();
- Assert.AreEqual(output, "FOREIGN KEY (dummyForeignKey1) REFERENCES dummyForeignTable(dummForeignPrimaryKey1) ON DELETE CASCADE");
+ Assert.AreEqual("FOREIGN KEY ([dummyForeignKey1]) REFERENCES dummyForeignTable([dummForeignPrimaryKey1]) ON DELETE CASCADE", output);
+ }
+
+ [TestMethod]
+ public void CreateStatementOneForeignKeyCascadeUpdateTest()
+ {
+ var foreignKeyStatement = new ForeignKeyStatement
+ {
+ CascadeUpdate = true,
+ ForeignKey = new List { "dummyForeignKey1" },
+ ForeignPrimaryKey = new List { "dummForeignPrimaryKey1" },
+ ForeignTable = "dummyForeignTable"
+ };
+
+ string output = foreignKeyStatement.CreateStatement();
+ Assert.AreEqual("FOREIGN KEY ([dummyForeignKey1]) REFERENCES dummyForeignTable([dummForeignPrimaryKey1]) ON UPDATE CASCADE", output);
+ }
+
+ [TestMethod]
+ public void CreateStatementOneForeignKeyCascadeDeleteAndUpdateTest()
+ {
+ var foreignKeyStatement = new ForeignKeyStatement
+ {
+ CascadeDelete = true,
+ CascadeUpdate = true,
+ ForeignKey = new List { "dummyForeignKey1" },
+ ForeignPrimaryKey = new List { "dummForeignPrimaryKey1" },
+ ForeignTable = "dummyForeignTable"
+ };
+
+ string output = foreignKeyStatement.CreateStatement();
+ Assert.AreEqual("FOREIGN KEY ([dummyForeignKey1]) REFERENCES dummyForeignTable([dummForeignPrimaryKey1]) ON DELETE CASCADE ON UPDATE CASCADE", output);
}
[TestMethod]
@@ -49,7 +80,7 @@ public void CreateStatementTwoForeignKeyTest()
};
string output = foreignKeyStatement.CreateStatement();
- Assert.AreEqual(output, "FOREIGN KEY (dummyForeignKey1, dummyForeignKey2) REFERENCES dummyForeignTable(dummForeignPrimaryKey1)");
+ Assert.AreEqual("FOREIGN KEY ([dummyForeignKey1], [dummyForeignKey2]) REFERENCES dummyForeignTable([dummForeignPrimaryKey1])", output);
}
[TestMethod]
@@ -64,7 +95,7 @@ public void CreateStatementTwoForeignKeyTwoPrimaryKeyTest()
};
string output = foreignKeyStatement.CreateStatement();
- Assert.AreEqual(output, "FOREIGN KEY (dummyForeignKey1, dummyForeignKey2) REFERENCES dummyForeignTable(dummForeignPrimaryKey1, dummForeignPrimaryKey2)");
+ Assert.AreEqual("FOREIGN KEY ([dummyForeignKey1], [dummyForeignKey2]) REFERENCES dummyForeignTable([dummForeignPrimaryKey1], [dummForeignPrimaryKey2])", output);
}
[TestMethod]
@@ -79,7 +110,7 @@ public void CreateStatementOneForeignKeyTwoPrimaryKeyTest()
};
string output = foreignKeyStatement.CreateStatement();
- Assert.AreEqual(output, "FOREIGN KEY (dummyForeignKey1) REFERENCES dummyForeignTable(dummForeignPrimaryKey1, dummForeignPrimaryKey2)");
+ Assert.AreEqual("FOREIGN KEY ([dummyForeignKey1]) REFERENCES dummyForeignTable([dummForeignPrimaryKey1], [dummForeignPrimaryKey2])", output);
}
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Statement/PrimaryKeyStatementTest.cs b/SQLite.CodeFirst.Test/UnitTests/Statement/PrimaryKeyStatementTest.cs
index 7ece059..b83d2c1 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Statement/PrimaryKeyStatementTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Statement/PrimaryKeyStatementTest.cs
@@ -13,8 +13,8 @@ public void CreateStatementWithOneKeyTest()
const string keyMember1 = "keyMember1";
var primaryKeyStatement = new CompositePrimaryKeyStatement(new List { keyMember1 });
- Assert.AreEqual(primaryKeyStatement.Count, 1);
- Assert.AreEqual(primaryKeyStatement.CreateStatement(), "PRIMARY KEY(keyMember1)");
+ Assert.AreEqual(1, primaryKeyStatement.Count);
+ Assert.AreEqual("PRIMARY KEY([keyMember1])", primaryKeyStatement.CreateStatement());
}
[TestMethod]
@@ -24,8 +24,8 @@ public void CreateStatementWithTwoKeyTest()
const string keyMember2 = "keyMember2";
var primaryKeyStatement = new CompositePrimaryKeyStatement(new List { keyMember1, keyMember2 });
- Assert.AreEqual(primaryKeyStatement.Count, 2);
- Assert.AreEqual(primaryKeyStatement.CreateStatement(), "PRIMARY KEY(keyMember1, keyMember2)");
+ Assert.AreEqual(2, primaryKeyStatement.Count);
+ Assert.AreEqual("PRIMARY KEY([keyMember1], [keyMember2])", primaryKeyStatement.CreateStatement());
}
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Utility/ConnectionStringParserTest.cs b/SQLite.CodeFirst.Test/UnitTests/Utility/ConnectionStringParserTest.cs
index 998f5c8..d6e6238 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Utility/ConnectionStringParserTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Utility/ConnectionStringParserTest.cs
@@ -106,5 +106,33 @@ public void GetDataSource_ReturnsCorrectDataSource_RemovesQuotationMark()
// Assert
Assert.AreEqual(@".\db\footballDb\footballDb.sqlite", result);
}
+
+ [TestMethod]
+ public void GetDataSource_ReturnsCorrectDataSource_WhenValueContainsEqualsSign()
+ {
+ // Arrange
+ string connectionString = @"data source=.\db\foo=bar\footballDb.sqlite;foreign keys=true";
+
+
+ // Act
+ string result = ConnectionStringParser.GetDataSource(connectionString);
+
+ // Assert
+ Assert.AreEqual(@".\db\foo=bar\footballDb.sqlite", result);
+ }
+
+ [TestMethod]
+ public void GetDataSource_ReturnsLastValue_WhenKeyIsRepeated()
+ {
+ // Arrange
+ string connectionString = @"data source=first.sqlite;data source=second.sqlite";
+
+
+ // Act
+ string result = ConnectionStringParser.GetDataSource(connectionString);
+
+ // Assert
+ Assert.AreEqual(@"second.sqlite", result);
+ }
}
}
diff --git a/SQLite.CodeFirst.Test/UnitTests/Utility/HistoryEntityTypeValidatorTest.cs b/SQLite.CodeFirst.Test/UnitTests/Utility/HistoryEntityTypeValidatorTest.cs
index 61fcfb2..590c735 100644
--- a/SQLite.CodeFirst.Test/UnitTests/Utility/HistoryEntityTypeValidatorTest.cs
+++ b/SQLite.CodeFirst.Test/UnitTests/Utility/HistoryEntityTypeValidatorTest.cs
@@ -8,17 +8,17 @@ namespace SQLite.CodeFirst.Test.UnitTests.Utility
public class HistoryEntityTypeValidatorTest
{
[TestMethod]
- [ExpectedException(typeof(InvalidOperationException))]
public void EnsureValidTypeNotIHistory()
{
- HistoryEntityTypeValidator.EnsureValidType(typeof(InvalidFakeHistoryType1));
+ Assert.ThrowsExactly(
+ () => HistoryEntityTypeValidator.EnsureValidType(typeof(InvalidFakeHistoryType1)));
}
[TestMethod]
- [ExpectedException(typeof(InvalidOperationException))]
public void EnsureValidTypeNoParamLessCtorTest()
{
- HistoryEntityTypeValidator.EnsureValidType(typeof(InvalidFakeHistoryType2));
+ Assert.ThrowsExactly(
+ () => HistoryEntityTypeValidator.EnsureValidType(typeof(InvalidFakeHistoryType2)));
}
[TestMethod]
diff --git a/SQLite.CodeFirst.Test/UnitTests/Utility/HistoryRecordSelectorTest.cs b/SQLite.CodeFirst.Test/UnitTests/Utility/HistoryRecordSelectorTest.cs
new file mode 100644
index 0000000..b53a240
--- /dev/null
+++ b/SQLite.CodeFirst.Test/UnitTests/Utility/HistoryRecordSelectorTest.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using SQLite.CodeFirst.Utility;
+
+namespace SQLite.CodeFirst.Test.UnitTests.Utility
+{
+ [TestClass]
+ public class HistoryRecordSelectorTest
+ {
+ private static IHistory Record(string context)
+ {
+ return new History { Context = context, Hash = "hash-" + context };
+ }
+
+ [TestMethod]
+ public void SelectForContext_ReturnsRecordForGivenContext()
+ {
+ var records = new List { Record("ContextA"), Record("ContextB") };
+
+ IHistory result = HistoryRecordSelector.SelectForContext(records, "ContextB");
+
+ Assert.IsNotNull(result);
+ Assert.AreEqual("ContextB", result.Context);
+ }
+
+ [TestMethod]
+ public void SelectForContext_ReturnsNull_WhenNoRecordMatches()
+ {
+ var records = new List { Record("ContextA") };
+
+ IHistory result = HistoryRecordSelector.SelectForContext(records, "ContextB");
+
+ Assert.IsNull(result);
+ }
+
+ [TestMethod]
+ public void SelectForContext_ReturnsNull_WhenEmpty()
+ {
+ IHistory result = HistoryRecordSelector.SelectForContext(new List(), "ContextA");
+
+ Assert.IsNull(result);
+ }
+
+ [TestMethod]
+ public void SelectForContext_Throws_WhenMultipleRecordsShareContext()
+ {
+ // One record per context is an invariant maintained by SaveHistory. If it is ever
+ // violated, surfacing it is better than silently picking an arbitrary record.
+ var records = new List { Record("ContextA"), Record("ContextA") };
+
+ Assert.ThrowsExactly(
+ () => HistoryRecordSelector.SelectForContext(records, "ContextA"));
+ }
+ }
+}
diff --git a/SQLite.CodeFirst.Test/app.config b/SQLite.CodeFirst.Test/app.config
deleted file mode 100644
index 2f401ce..0000000
--- a/SQLite.CodeFirst.Test/app.config
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/SQLite.CodeFirst.Test/packages.lock.json b/SQLite.CodeFirst.Test/packages.lock.json
new file mode 100644
index 0000000..2dccd5e
--- /dev/null
+++ b/SQLite.CodeFirst.Test/packages.lock.json
@@ -0,0 +1,256 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net10.0": {
+ "Microsoft.Testing.Extensions.TrxReport": {
+ "type": "Direct",
+ "requested": "[2.2.3, )",
+ "resolved": "2.2.3",
+ "contentHash": "9Hot3ty5ZVWHrW40k2NPfD0dCaPwIxj7j7VjujNYwpYkYw9AdbejPHjGNkL/gvUWorauJf5IkeDoUeIbS7LuUg==",
+ "dependencies": {
+ "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.3",
+ "Microsoft.Testing.Platform": "2.2.3"
+ }
+ },
+ "Moq": {
+ "type": "Direct",
+ "requested": "[4.20.72, )",
+ "resolved": "4.20.72",
+ "contentHash": "EA55cjyNn8eTNWrgrdZJH5QLFp2L43oxl1tlkoYUKIE9pRwL784OWiTXeCV5ApS+AMYEAlt7Fo03A2XfouvHmQ==",
+ "dependencies": {
+ "Castle.Core": "5.1.1"
+ }
+ },
+ "MSTest.TestAdapter": {
+ "type": "Direct",
+ "requested": "[4.2.3, )",
+ "resolved": "4.2.3",
+ "contentHash": "oVV/luk0bBghnVvLaw8MwFlD7It0Cx9P2nKobeqIafmTQqFFWY69Wo801dxjeNaLzO/o9WQ84WSK84gXJuubhg==",
+ "dependencies": {
+ "MSTest.TestFramework": "4.2.3",
+ "Microsoft.Testing.Extensions.VSTestBridge": "2.2.3",
+ "Microsoft.Testing.Platform.MSBuild": "2.2.3"
+ }
+ },
+ "MSTest.TestFramework": {
+ "type": "Direct",
+ "requested": "[4.2.3, )",
+ "resolved": "4.2.3",
+ "contentHash": "9zzij59YLh+tf+FRLNqhzHjmdspR91bol+jdQxLlxxTGMAML6LDbvuyXKMGdcrE84+QpKkk6KjTVBC5PBGrDeA==",
+ "dependencies": {
+ "MSTest.Analyzers": "4.2.3"
+ }
+ },
+ "Castle.Core": {
+ "type": "Transitive",
+ "resolved": "5.1.1",
+ "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==",
+ "dependencies": {
+ "System.Diagnostics.EventLog": "6.0.0"
+ }
+ },
+ "EntityFramework": {
+ "type": "Transitive",
+ "resolved": "6.5.2",
+ "contentHash": "8iOcnaKcgkWh35s8EhknnENNwwqvrBJ7jeNbQITbGo1mIeIOhGPfVdNOXZ3Y496T6e6wfXvz0WeXlgMdcgVYSA==",
+ "dependencies": {
+ "System.CodeDom": "6.0.0",
+ "System.ComponentModel.Annotations": "5.0.0",
+ "System.Configuration.ConfigurationManager": "6.0.1",
+ "System.Data.SqlClient": "4.8.6"
+ }
+ },
+ "Microsoft.ApplicationInsights": {
+ "type": "Transitive",
+ "resolved": "2.23.0",
+ "contentHash": "nWArUZTdU7iqZLycLKWe0TDms48KKGE6pONH2terYNa8REXiqixrMOkf1sk5DHGMaUTqONU2YkS4SAXBhLStgw=="
+ },
+ "Microsoft.Testing.Extensions.Telemetry": {
+ "type": "Transitive",
+ "resolved": "2.2.3",
+ "contentHash": "mLdW+JOR3kXYGTdgR/qc/UZBA0r+eCR2k6bUxTcuDj5w9WdIQ7Lol5MBUU7YOSGd9bs9bvhSYWAptgz0YtQqCA==",
+ "dependencies": {
+ "Microsoft.ApplicationInsights": "2.23.0",
+ "Microsoft.Testing.Platform": "2.2.3"
+ }
+ },
+ "Microsoft.Testing.Extensions.TrxReport.Abstractions": {
+ "type": "Transitive",
+ "resolved": "2.2.3",
+ "contentHash": "hntvxJEkmUAx6C2xXc/PO38DqEQl4rimzOgSvTR1hAMruMid7R4RcXOrzzF33J66gKaN7jRaQ0TMW/nNfaV9jw==",
+ "dependencies": {
+ "Microsoft.Testing.Platform": "2.2.3"
+ }
+ },
+ "Microsoft.Testing.Extensions.VSTestBridge": {
+ "type": "Transitive",
+ "resolved": "2.2.3",
+ "contentHash": "7WlJISO8QKUK+d+WhgnANwy4ACwUrvICnviY/mthPwjZ2gVeDaSUAeBnMy2cxfzZgm8VATtGUDbYzUxsgV2CyQ==",
+ "dependencies": {
+ "Microsoft.TestPlatform.ObjectModel": "18.3.0",
+ "Microsoft.Testing.Extensions.Telemetry": "2.2.3",
+ "Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.3",
+ "Microsoft.Testing.Platform": "2.2.3"
+ }
+ },
+ "Microsoft.Testing.Platform": {
+ "type": "Transitive",
+ "resolved": "2.2.3",
+ "contentHash": "LhM1/Qoi8Ams5QcD4r3f09CSOono9iQr3NEJQItFtyzWB55nWTgEOsVqXqMWWWIwk3nkPqc+XfnlJmp8xUI5fg=="
+ },
+ "Microsoft.Testing.Platform.MSBuild": {
+ "type": "Transitive",
+ "resolved": "2.2.3",
+ "contentHash": "Q22jJYJLx4srTinsAuoCskqmzjrBJC8YeGJMHHIcrf1dQeHoEZ7wsqDzTlENkMoke2qfufF7U+9u58nlZunH/Q==",
+ "dependencies": {
+ "Microsoft.Testing.Platform": "2.2.3"
+ }
+ },
+ "Microsoft.TestPlatform.ObjectModel": {
+ "type": "Transitive",
+ "resolved": "18.3.0",
+ "contentHash": "AEIEX2aWdPO9XbtR96eBaJxmXRD9vaI9uQ1T/JbPEKlTAZwYx0ZrMzKyULMdh/HH9Sg03kXCoN7LszQ90o6nPQ=="
+ },
+ "Microsoft.Win32.SystemEvents": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A=="
+ },
+ "MSTest.Analyzers": {
+ "type": "Transitive",
+ "resolved": "4.2.3",
+ "contentHash": "dxOZt8/LWuiox7rugInJoIa5Mmu3pBmXdfaoZOx/mxx8+sUFFpjBXPlWXQXGeWzpkVPNC3x1Jf7rt2h2Zjyvvg=="
+ },
+ "runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.7.0",
+ "contentHash": "9kyFSIdN3T0qjDQ2R0HRXYIhS3l5psBzQi6qqhdLz+SzFyEy4sVxNOke+yyYv8Cu8rPER12c3RDjLT8wF3WBYQ==",
+ "dependencies": {
+ "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
+ "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
+ "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0"
+ }
+ },
+ "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg=="
+ },
+ "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ=="
+ },
+ "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA=="
+ },
+ "Stub.System.Data.SQLite.Core.NetStandard": {
+ "type": "Transitive",
+ "resolved": "1.0.119",
+ "contentHash": "dI7ngiCNgdm+n00nQvFTa+LbHvE9MIQXwMSLRzJI/KAJ7G1WmCachsvfE1CD6xvb3OXJvYYEfv3+S/LHyhN0Rg=="
+ },
+ "System.CodeDom": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA=="
+ },
+ "System.ComponentModel.Annotations": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg=="
+ },
+ "System.Configuration.ConfigurationManager": {
+ "type": "Transitive",
+ "resolved": "6.0.1",
+ "contentHash": "jXw9MlUu/kRfEU0WyTptAVueupqIeE3/rl0EZDMlf8pcvJnitQ8HeVEp69rZdaStXwTV72boi/Bhw8lOeO+U2w==",
+ "dependencies": {
+ "System.Security.Cryptography.ProtectedData": "6.0.0",
+ "System.Security.Permissions": "6.0.0"
+ }
+ },
+ "System.Data.SqlClient": {
+ "type": "Transitive",
+ "resolved": "4.8.6",
+ "contentHash": "2Ij/LCaTQRyAi5lAv7UUTV9R2FobC8xN9mE0fXBZohum/xLl8IZVmE98Rq5ugQHjCgTBRKqpXRb4ORulRdA6Ig==",
+ "dependencies": {
+ "runtime.native.System.Data.SqlClient.sni": "4.7.0"
+ }
+ },
+ "System.Data.SQLite": {
+ "type": "Transitive",
+ "resolved": "1.0.119",
+ "contentHash": "JSOJpnBf9goMnxGTJFGCmm6AffxgtpuXNXV5YvWO8UNC2zwd12qkUe5lAbnY+2ohIkIukgIjbvR1RA/sWILv3w==",
+ "dependencies": {
+ "System.Data.SQLite.Core": "[1.0.119]",
+ "System.Data.SQLite.EF6": "[1.0.119]"
+ }
+ },
+ "System.Data.SQLite.Core": {
+ "type": "Transitive",
+ "resolved": "1.0.119",
+ "contentHash": "bhQB8HVtRA+OOYw8UTD1F1kU+nGJ0/OZvH1JmlVUI4bGvgVEWeX1NcHjA765NvUoRVuCPlt8PrEpZ1thSsk1jg==",
+ "dependencies": {
+ "Stub.System.Data.SQLite.Core.NetStandard": "[1.0.119]"
+ }
+ },
+ "System.Data.SQLite.EF6": {
+ "type": "Transitive",
+ "resolved": "1.0.119",
+ "contentHash": "BwwgCSeA80gsxdXtU7IQEBrN9kQXWQrD11hNYOJZbXBBI1C4r7hA4QhBAalO1nzijXikthGRUADIEMI3nlucLA==",
+ "dependencies": {
+ "EntityFramework": "6.4.4"
+ }
+ },
+ "System.Diagnostics.EventLog": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw=="
+ },
+ "System.Drawing.Common": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==",
+ "dependencies": {
+ "Microsoft.Win32.SystemEvents": "6.0.0"
+ }
+ },
+ "System.Security.Cryptography.ProtectedData": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ=="
+ },
+ "System.Security.Permissions": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==",
+ "dependencies": {
+ "System.Windows.Extensions": "6.0.0"
+ }
+ },
+ "System.Windows.Extensions": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==",
+ "dependencies": {
+ "System.Drawing.Common": "6.0.0"
+ }
+ },
+ "sqlite.codefirst": {
+ "type": "Project",
+ "dependencies": {
+ "EntityFramework": "[6.5.2, )"
+ }
+ },
+ "sqlite.codefirst.netcore.console": {
+ "type": "Project",
+ "dependencies": {
+ "SQLite.CodeFirst": "[1.0.0, )",
+ "System.Data.SQLite": "[1.0.119, )",
+ "System.Data.SQLite.EF6": "[1.0.119, )"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst.sln b/SQLite.CodeFirst.sln
deleted file mode 100644
index c1ff1b3..0000000
--- a/SQLite.CodeFirst.sln
+++ /dev/null
@@ -1,48 +0,0 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.28010.2050
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SQLite.CodeFirst", "SQLite.CodeFirst\SQLite.CodeFirst.csproj", "{50A32FE4-0E13-4213-A373-72523CDF34D9}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SQLite.CodeFirst.Console", "SQLite.CodeFirst.Console\SQLite.CodeFirst.Console.csproj", "{DEDABD86-6EA0-4673-A858-A4F71958F51D}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{7031BD3C-AE76-43CD-91B6-B6BCD823968C}"
- ProjectSection(SolutionItems) = preProject
- ci_appveyor.yml = ci_appveyor.yml
- release_appveyor.yml = release_appveyor.yml
- EndProjectSection
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SQLite.CodeFirst.Test", "SQLite.CodeFirst.Test\SQLite.CodeFirst.Test.csproj", "{E542F38D-852E-489D-81C2-BF333503E10F}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1F6D4E0E-5D07-4889-961A-A6AFC0ECD7C7}"
- ProjectSection(SolutionItems) = preProject
- Shared\SQLite.CodeFirst.ruleset = Shared\SQLite.CodeFirst.ruleset
- Shared\SQLite.CodeFirst.StrongNameKey.snk = Shared\SQLite.CodeFirst.StrongNameKey.snk
- EndProjectSection
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release|Any CPU.Build.0 = Release|Any CPU
- {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release|Any CPU.Build.0 = Release|Any CPU
- {E542F38D-852E-489D-81C2-BF333503E10F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {E542F38D-852E-489D-81C2-BF333503E10F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {E542F38D-852E-489D-81C2-BF333503E10F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {E542F38D-852E-489D-81C2-BF333503E10F}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {D21CDFB4-5695-4346-AB51-48C80AED4418}
- EndGlobalSection
-EndGlobal
diff --git a/SQLite.CodeFirst.slnx b/SQLite.CodeFirst.slnx
new file mode 100644
index 0000000..894535d
--- /dev/null
+++ b/SQLite.CodeFirst.slnx
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SQLite.CodeFirst/Internal/Builder/ColumnStatementCollectionBuilder.cs b/SQLite.CodeFirst/Internal/Builder/ColumnStatementCollectionBuilder.cs
index 93043c2..3fba1bb 100644
--- a/SQLite.CodeFirst/Internal/Builder/ColumnStatementCollectionBuilder.cs
+++ b/SQLite.CodeFirst/Internal/Builder/ColumnStatementCollectionBuilder.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Data.Entity.Core.Metadata.Edm;
using System.Linq;
using SQLite.CodeFirst.Extensions;
@@ -11,11 +12,13 @@ internal class ColumnStatementCollectionBuilder : IStatementBuilder properties;
private readonly IEnumerable keyMembers;
+ private readonly Collation defaultCollation;
- public ColumnStatementCollectionBuilder(IEnumerable properties, IEnumerable keyMembers)
+ public ColumnStatementCollectionBuilder(IEnumerable properties, IEnumerable keyMembers, Collation defaultCollation)
{
this.properties = properties;
this.keyMembers = keyMembers;
+ this.defaultCollation = defaultCollation;
}
public ColumnStatementCollection BuildStatement()
@@ -39,7 +42,7 @@ private IEnumerable CreateColumnStatements()
AdjustDatatypeForAutogenerationIfNecessary(property, columnStatement);
AddNullConstraintIfNecessary(property, columnStatement);
AddUniqueConstraintIfNecessary(property, columnStatement);
- AddCollationConstraintIfNecessary(property, columnStatement);
+ AddCollationConstraintIfNecessary(property, columnStatement, defaultCollation);
AddPrimaryKeyConstraintAndAdjustTypeIfNecessary(property, columnStatement);
AddDefaultValueConstraintIfNecessary(property, columnStatement);
@@ -73,12 +76,25 @@ private static void AddNullConstraintIfNecessary(EdmProperty property, ColumnSta
}
}
- private static void AddCollationConstraintIfNecessary(EdmProperty property, ColumnStatement columnStatement)
+ private static void AddCollationConstraintIfNecessary(EdmProperty property, ColumnStatement columnStatement, Collation defaultCollation)
{
- var value = property.GetCustomAnnotation();
- if (value != null)
+ var collateAttribute = property.GetCustomAnnotation();
+ if (property.PrimitiveType.PrimitiveTypeKind == PrimitiveTypeKind.String)
+ {
+ // The column is a string type. Check if we have an explicit or default collation.
+ // If we have both, the explicitly chosen collation takes precedence.
+ var value = collateAttribute == null ? defaultCollation : collateAttribute.Collation;
+ if (value != null)
+ {
+ columnStatement.ColumnConstraints.Add(new CollateConstraint { CollationFunction = value.Function, CustomCollationFunction = value.CustomFunction });
+ }
+ }
+ else if (collateAttribute != null)
{
- columnStatement.ColumnConstraints.Add(new CollateConstraint { CollationFunction = value.Collation, CustomCollationFunction = value.Function });
+ // Only string columns can be explicitly decorated with CollateAttribute.
+ var name = $"{property.DeclaringType.Name}.{property.Name}";
+ var errorMessage = $"CollateAttribute cannot be used on non-string property: {name} (underlying type is {property.PrimitiveType.PrimitiveTypeKind})";
+ throw new InvalidOperationException(errorMessage);
}
}
diff --git a/SQLite.CodeFirst/Internal/Builder/CreateDatabaseStatementBuilder.cs b/SQLite.CodeFirst/Internal/Builder/CreateDatabaseStatementBuilder.cs
index 65862a2..ea87cca 100644
--- a/SQLite.CodeFirst/Internal/Builder/CreateDatabaseStatementBuilder.cs
+++ b/SQLite.CodeFirst/Internal/Builder/CreateDatabaseStatementBuilder.cs
@@ -9,10 +9,12 @@ namespace SQLite.CodeFirst.Builder
internal class CreateDatabaseStatementBuilder : IStatementBuilder
{
private readonly EdmModel edmModel;
+ private readonly Collation defaultCollation;
- public CreateDatabaseStatementBuilder(EdmModel edmModel)
+ public CreateDatabaseStatementBuilder(EdmModel edmModel, Collation defaultCollation)
{
this.edmModel = edmModel;
+ this.defaultCollation = defaultCollation;
}
public CreateDatabaseStatement BuildStatement()
@@ -30,7 +32,7 @@ private IEnumerable GetCreateTableStatements()
foreach (var entitySet in edmModel.Container.EntitySets)
{
- var tableStatementBuilder = new CreateTableStatementBuilder(entitySet, associationTypeContainer);
+ var tableStatementBuilder = new CreateTableStatementBuilder(entitySet, associationTypeContainer, defaultCollation);
yield return tableStatementBuilder.BuildStatement();
}
}
diff --git a/SQLite.CodeFirst/Internal/Builder/CreateTableStatementBuilder.cs b/SQLite.CodeFirst/Internal/Builder/CreateTableStatementBuilder.cs
index b86f701..ac3198b 100644
--- a/SQLite.CodeFirst/Internal/Builder/CreateTableStatementBuilder.cs
+++ b/SQLite.CodeFirst/Internal/Builder/CreateTableStatementBuilder.cs
@@ -12,11 +12,13 @@ internal class CreateTableStatementBuilder : IStatementBuilder();
diff --git a/SQLite.CodeFirst/Internal/Builder/ForeignKeyStatementBuilder.cs b/SQLite.CodeFirst/Internal/Builder/ForeignKeyStatementBuilder.cs
index e5fd81a..0dd595c 100644
--- a/SQLite.CodeFirst/Internal/Builder/ForeignKeyStatementBuilder.cs
+++ b/SQLite.CodeFirst/Internal/Builder/ForeignKeyStatementBuilder.cs
@@ -29,7 +29,8 @@ private IEnumerable GetForeignKeyStatements()
ForeignKey = associationType.ForeignKey,
ForeignTable = associationType.FromTableName,
ForeignPrimaryKey = associationType.ForeignPrimaryKey,
- CascadeDelete = associationType.CascadeDelete
+ CascadeDelete = associationType.CascadeDelete,
+ CascadeUpdate = associationType.CascadeUpdate
};
}
}
diff --git a/SQLite.CodeFirst/Internal/Builder/NameCreators/ColumnNameCreator.cs b/SQLite.CodeFirst/Internal/Builder/NameCreators/ColumnNameCreator.cs
new file mode 100644
index 0000000..c533dfd
--- /dev/null
+++ b/SQLite.CodeFirst/Internal/Builder/NameCreators/ColumnNameCreator.cs
@@ -0,0 +1,12 @@
+using System.Globalization;
+
+namespace SQLite.CodeFirst.Builder.NameCreators
+{
+ internal static class ColumnNameCreator
+ {
+ public static string EscapeName(string columnName)
+ {
+ return string.Format(CultureInfo.InvariantCulture, "[{0}]", columnName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/SQLite.CodeFirst/Internal/Convention/SqliteForeignKeyIndexConvention.cs b/SQLite.CodeFirst/Internal/Convention/SqliteForeignKeyIndexConvention.cs
index e10205e..e5d24ac 100644
--- a/SQLite.CodeFirst/Internal/Convention/SqliteForeignKeyIndexConvention.cs
+++ b/SQLite.CodeFirst/Internal/Convention/SqliteForeignKeyIndexConvention.cs
@@ -74,16 +74,16 @@ public virtual void Apply(AssociationType item, DbModel model)
private static IndexAnnotation CreateIndexAnnotation(string tableName, string propertyName, int count)
{
- var indexName = IndexNameCreator.CreateName(tableName, propertyName);
-
- // If there are two Indicies on the same property, the count is added.
- // In SQLite an Index name must be global unique.
- // To be honest, it should never happen. But because its possible by using the API, it should be covered.
- if (count > 0)
- {
- indexName = String.Format(CultureInfo.InvariantCulture, "{0}_{1}", indexName, count);
- }
+ // If there are two indices on the same property, the count is appended.
+ // In SQLite an index name must be globally unique.
+ // To be honest, it should never happen. But because it is possible via the API, it is covered.
+ // The suffix is folded in before escaping so it stays inside the quoted identifier;
+ // IndexNameCreator.CreateName returns an already-escaped name.
+ string uniquePropertyName = count > 0
+ ? String.Format(CultureInfo.InvariantCulture, "{0}_{1}", propertyName, count)
+ : propertyName;
+ var indexName = IndexNameCreator.CreateName(tableName, uniquePropertyName);
var indexAttribute = new IndexAttribute(indexName);
return new IndexAnnotation(indexAttribute);
}
diff --git a/SQLite.CodeFirst/Internal/Statement/ColumnStatement.cs b/SQLite.CodeFirst/Internal/Statement/ColumnStatement.cs
index bf4f91d..d55b091 100644
--- a/SQLite.CodeFirst/Internal/Statement/ColumnStatement.cs
+++ b/SQLite.CodeFirst/Internal/Statement/ColumnStatement.cs
@@ -1,11 +1,12 @@
using System.Text;
+using SQLite.CodeFirst.Builder.NameCreators;
using SQLite.CodeFirst.Statement.ColumnConstraint;
namespace SQLite.CodeFirst.Statement
{
internal class ColumnStatement : IStatement
{
- private const string Template = "[{column-name}] {type-name} {column-constraint}";
+ private const string Template = "{column-name} {type-name} {column-constraint}";
public string ColumnName { get; set; }
public string TypeName { get; set; }
@@ -15,7 +16,7 @@ public string CreateStatement()
{
var sb = new StringBuilder(Template);
- sb.Replace("{column-name}", ColumnName);
+ sb.Replace("{column-name}", ColumnNameCreator.EscapeName(ColumnName));
sb.Replace("{type-name}", TypeName);
sb.Replace("{column-constraint}", ColumnConstraints.CreateStatement());
diff --git a/SQLite.CodeFirst/Internal/Statement/CompositePrimaryKeyStatement.cs b/SQLite.CodeFirst/Internal/Statement/CompositePrimaryKeyStatement.cs
index f2d0374..196f305 100644
--- a/SQLite.CodeFirst/Internal/Statement/CompositePrimaryKeyStatement.cs
+++ b/SQLite.CodeFirst/Internal/Statement/CompositePrimaryKeyStatement.cs
@@ -1,13 +1,14 @@
-using System;
+using SQLite.CodeFirst.Builder.NameCreators;
+using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Linq;
namespace SQLite.CodeFirst.Statement
{
internal class CompositePrimaryKeyStatement : Collection, IStatement
{
private const string Template = "PRIMARY KEY({primary-keys})";
- private const string PrimaryKeyColumnNameSeperator = ", ";
public CompositePrimaryKeyStatement(IEnumerable keyMembers)
{
@@ -19,7 +20,7 @@ public CompositePrimaryKeyStatement(IEnumerable keyMembers)
public string CreateStatement()
{
- string primaryKeys = String.Join(PrimaryKeyColumnNameSeperator, this);
+ string primaryKeys = String.Join(", ", this.Select(c => ColumnNameCreator.EscapeName(c)));
return Template.Replace("{primary-keys}", primaryKeys);
}
}
diff --git a/SQLite.CodeFirst/Internal/Statement/ForeignKeyStatement.cs b/SQLite.CodeFirst/Internal/Statement/ForeignKeyStatement.cs
index 19b61b4..6b862a7 100644
--- a/SQLite.CodeFirst/Internal/Statement/ForeignKeyStatement.cs
+++ b/SQLite.CodeFirst/Internal/Statement/ForeignKeyStatement.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+using SQLite.CodeFirst.Builder.NameCreators;
+using System.Collections.Generic;
+using System.Linq;
using System.Text;
namespace SQLite.CodeFirst.Statement
@@ -7,24 +9,28 @@ internal class ForeignKeyStatement : IStatement
{
private const string Template = "FOREIGN KEY ({foreign-key}) REFERENCES {referenced-table}({referenced-id})";
private const string CascadeDeleteStatement = "ON DELETE CASCADE";
+ private const string CascadeUpdateStatement = "ON UPDATE CASCADE";
public IEnumerable ForeignKey { get; set; }
public string ForeignTable { get; set; }
public IEnumerable ForeignPrimaryKey { get; set; }
public bool CascadeDelete { get; set; }
+ public bool CascadeUpdate { get; set; }
public string CreateStatement()
{
var sb = new StringBuilder(Template);
-
- sb.Replace("{foreign-key}", string.Join(", ", ForeignKey));
+ sb.Replace("{foreign-key}", string.Join(", ", ForeignKey.Select(c => ColumnNameCreator.EscapeName(c))));
sb.Replace("{referenced-table}", ForeignTable);
- sb.Replace("{referenced-id}", string.Join(", ", ForeignPrimaryKey));
-
+ sb.Replace("{referenced-id}", string.Join(", ", ForeignPrimaryKey.Select(c => ColumnNameCreator.EscapeName(c))));
if (CascadeDelete)
{
sb.Append(" " + CascadeDeleteStatement);
}
+ if (CascadeUpdate)
+ {
+ sb.Append(" " + CascadeUpdateStatement);
+ }
return sb.ToString();
}
diff --git a/SQLite.CodeFirst/Internal/Utility/AssociationTypeContainer.cs b/SQLite.CodeFirst/Internal/Utility/AssociationTypeContainer.cs
index e86d20f..c22d1a9 100644
--- a/SQLite.CodeFirst/Internal/Utility/AssociationTypeContainer.cs
+++ b/SQLite.CodeFirst/Internal/Utility/AssociationTypeContainer.cs
@@ -10,7 +10,9 @@ internal class AssociationTypeContainer
public AssociationTypeContainer(IEnumerable associationTypes, EntityContainer container)
{
- sqliteAssociationTypes = associationTypes.Select(associationType => new SqliteAssociationType(associationType, container));
+ // Materialize once. GetAssociationTypes is called per entity set, so a deferred query
+ // would rebuild every SqliteAssociationType (and its entity-set lookups) on each call.
+ sqliteAssociationTypes = associationTypes.Select(associationType => new SqliteAssociationType(associationType, container)).ToList();
}
public IEnumerable GetAssociationTypes(string entitySetName)
diff --git a/SQLite.CodeFirst/Internal/Utility/ConnectionStringParser.cs b/SQLite.CodeFirst/Internal/Utility/ConnectionStringParser.cs
index 02c2c21..93592c6 100644
--- a/SQLite.CodeFirst/Internal/Utility/ConnectionStringParser.cs
+++ b/SQLite.CodeFirst/Internal/Utility/ConnectionStringParser.cs
@@ -21,7 +21,7 @@ public static string GetDataSource(string connectionString)
IDictionary strings = ParseConnectionString(connectionString);
if (strings.ContainsKey(DataSourceToken))
{
- var path = ExpandDataDirectory(ParseConnectionString(connectionString)[DataSourceToken]);
+ var path = ExpandDataDirectory(strings[DataSourceToken]);
return path.Trim('"');
}
@@ -42,10 +42,13 @@ private static IDictionary ParseConnectionString(string connecti
IDictionary keyValuePairDictionary = new Dictionary();
foreach (var keyValuePair in keyValuePairs)
{
- string[] keyValue = keyValuePair.Split(KeyValueSeperator);
+ // Split on the first '=' only so a value that itself contains '=' is preserved.
+ // Input: "data source=C:\a=b.sqlite" -> key="data source", value="C:\a=b.sqlite"
+ string[] keyValue = keyValuePair.Split(new[] { KeyValueSeperator }, 2, StringSplitOptions.None);
if (keyValue.Length >= 2)
{
- keyValuePairDictionary.Add(keyValue[KeyPosition].Trim().ToLower(CultureInfo.InvariantCulture), keyValue[ValuePosition]);
+ // Indexer assignment (last value wins) so a repeated key does not throw.
+ keyValuePairDictionary[keyValue[KeyPosition].Trim().ToLower(CultureInfo.InvariantCulture)] = keyValue[ValuePosition];
}
}
diff --git a/SQLite.CodeFirst/Internal/Utility/HashCreator.cs b/SQLite.CodeFirst/Internal/Utility/HashCreator.cs
index bd16c54..f8732a6 100644
--- a/SQLite.CodeFirst/Internal/Utility/HashCreator.cs
+++ b/SQLite.CodeFirst/Internal/Utility/HashCreator.cs
@@ -9,7 +9,7 @@ internal static class HashCreator
public static string CreateHash(string data)
{
byte[] dataBytes = Encoding.ASCII.GetBytes(data);
- using (SHA512 sha512 = new SHA512Managed())
+ using (SHA512 sha512 = SHA512.Create())
{
byte[] hashBytes = sha512.ComputeHash(dataBytes);
string hash = Convert.ToBase64String(hashBytes);
diff --git a/SQLite.CodeFirst/Internal/Utility/HistoryRecordSelector.cs b/SQLite.CodeFirst/Internal/Utility/HistoryRecordSelector.cs
new file mode 100644
index 0000000..9621370
--- /dev/null
+++ b/SQLite.CodeFirst/Internal/Utility/HistoryRecordSelector.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace SQLite.CodeFirst.Utility
+{
+ ///
+ /// Selects the history record that belongs to a specific context from the history table.
+ /// A database can be shared by multiple contexts, so the lookup must be scoped by the context
+ /// key. Selecting without that scope would return more than one record on a shared database and
+ /// make the underlying throw.
+ ///
+ internal static class HistoryRecordSelector
+ {
+ public static IHistory SelectForContext(IEnumerable records, string contextKey)
+ {
+ return records.SingleOrDefault(record => record.Context == contextKey);
+ }
+ }
+}
diff --git a/SQLite.CodeFirst/Internal/Utility/SqliteAssociationType.cs b/SQLite.CodeFirst/Internal/Utility/SqliteAssociationType.cs
index 6ab4f49..7532614 100644
--- a/SQLite.CodeFirst/Internal/Utility/SqliteAssociationType.cs
+++ b/SQLite.CodeFirst/Internal/Utility/SqliteAssociationType.cs
@@ -2,6 +2,7 @@
using System.Data.Entity.Core.Metadata.Edm;
using System.Linq;
using SQLite.CodeFirst.Builder.NameCreators;
+using SQLite.CodeFirst.Extensions;
namespace SQLite.CodeFirst.Utility
{
@@ -30,6 +31,13 @@ public SqliteAssociationType(AssociationType associationType, EntityContainer co
ForeignKey = associationType.Constraint.ToProperties.Select(x => x.Name);
ForeignPrimaryKey = associationType.Constraint.FromProperties.Select(x => x.Name);
CascadeDelete = associationType.Constraint.FromRole.DeleteBehavior == OperationAction.Cascade;
+
+ // EF has no notion of update-cascade, so it is opt-in via the CascadeOnUpdateAttribute placed on the
+ // dependent foreign key property. The attribute is registered as a column annotation and therefore
+ // available on the store model foreign key columns (the constraint's ToProperties).
+ CascadeUpdate = associationType.Constraint.ToProperties
+ .Select(property => property.GetCustomAnnotation())
+ .Any(attribute => attribute != null && attribute.CanCascade);
}
private static bool IsSelfReferencing(AssociationType associationType)
@@ -47,5 +55,6 @@ private static bool IsSelfReferencing(AssociationType associationType)
public string ToTableName { get; }
public IEnumerable ForeignPrimaryKey { get; }
public bool CascadeDelete { get; }
+ public bool CascadeUpdate { get; }
}
}
\ No newline at end of file
diff --git a/SQLite.CodeFirst/Public/Attributes/CascadeOnUpdateAttribute.cs b/SQLite.CodeFirst/Public/Attributes/CascadeOnUpdateAttribute.cs
new file mode 100644
index 0000000..5870ccf
--- /dev/null
+++ b/SQLite.CodeFirst/Public/Attributes/CascadeOnUpdateAttribute.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace SQLite.CodeFirst
+{
+ ///
+ /// Adds 'ON UPDATE CASCADE' to the foreign key constraint of the relationship the decorated property belongs to.
+ /// Entity Framework has no concept of update-cascade (it assumes primary keys are immutable), so neither the
+ /// nor the fluent API can express it.
+ /// This opt-in attribute is the only way to emit the keyword.
+ /// Place it on the dependent foreign key property (e.g. the 'TeamId' property), not on the navigation property (e.g. 'Team').
+ /// Requires an explicit foreign key property; relationships with a shadow foreign key column cannot be decorated.
+ ///
+ [AttributeUsage(AttributeTargets.Property)]
+ public sealed class CascadeOnUpdateAttribute : Attribute
+ {
+ public CascadeOnUpdateAttribute()
+ {
+ CanCascade = true;
+ }
+
+ public CascadeOnUpdateAttribute(bool canCascade)
+ {
+ CanCascade = canCascade;
+ }
+
+ public bool CanCascade { get; }
+ }
+}
diff --git a/SQLite.CodeFirst/Public/Attributes/CollateAttribute.cs b/SQLite.CodeFirst/Public/Attributes/CollateAttribute.cs
index 2dfc272..636965a 100644
--- a/SQLite.CodeFirst/Public/Attributes/CollateAttribute.cs
+++ b/SQLite.CodeFirst/Public/Attributes/CollateAttribute.cs
@@ -12,39 +12,19 @@ public sealed class CollateAttribute : Attribute
{
public CollateAttribute()
{
- Collation = CollationFunction.None;
+ Collation = new Collation();
}
- public CollateAttribute(CollationFunction collation)
+ public CollateAttribute(CollationFunction function)
{
- if (collation == CollationFunction.Custom)
- {
- throw new ArgumentException("If the collation is set to CollationFunction.Custom a function must be specified.", nameof(collation));
- }
-
- Collation = collation;
+ Collation = new Collation(function);
}
- public CollateAttribute(CollationFunction collation, string function)
- {
- if (collation != CollationFunction.Custom && !string.IsNullOrEmpty(function))
- {
- throw new ArgumentException("If the collation is not set to CollationFunction.Custom a function must not be specified.", nameof(function));
- }
-
- if (collation == CollationFunction.Custom && string.IsNullOrEmpty(function))
- {
- throw new ArgumentException("If the collation is set to CollationFunction.Custom a function must be specified.", nameof(function));
- }
- Collation = collation;
- Function = function;
+ public CollateAttribute(CollationFunction function, string customFunction)
+ {
+ Collation = new Collation(function, customFunction);
}
- public CollationFunction Collation { get; }
-
- ///
- /// The name of the custom collating function to use (CollationFunction.Custom).
- ///
- public string Function { get; }
+ public Collation Collation { get; }
}
}
\ No newline at end of file
diff --git a/SQLite.CodeFirst/Public/Collation.cs b/SQLite.CodeFirst/Public/Collation.cs
new file mode 100644
index 0000000..0695e73
--- /dev/null
+++ b/SQLite.CodeFirst/Public/Collation.cs
@@ -0,0 +1,46 @@
+using System;
+
+namespace SQLite.CodeFirst
+{
+ ///
+ /// This class can be used to specify the default collation for the database. Explicit Collate attributes will take precendence.
+ /// When SQLite compares two strings, it uses a collating sequence or collating function (two words for the same thing)
+ /// to determine which string is greater or if the two strings are equal. SQLite has three built-in collating functions (see ).
+ /// Set to and specify the name using the function parameter.
+ ///
+ public class Collation
+ {
+ public Collation()
+ : this(CollationFunction.None)
+ {
+ }
+
+ public Collation(CollationFunction function)
+ : this(function, null)
+ {
+ }
+
+ public Collation(CollationFunction function, string customFunction)
+ {
+ if (function != CollationFunction.Custom && !string.IsNullOrEmpty(customFunction))
+ {
+ throw new ArgumentException("If the collation is not set to CollationFunction.Custom a function must not be specified.", nameof(function));
+ }
+
+ if (function == CollationFunction.Custom && string.IsNullOrEmpty(customFunction))
+ {
+ throw new ArgumentException("If the collation is set to CollationFunction.Custom a function must be specified.", nameof(function));
+ }
+
+ CustomFunction = customFunction;
+ Function = function;
+ }
+
+ public CollationFunction Function { get; set; }
+
+ ///
+ /// The name of the custom collating function to use (CollationFunction.Custom).
+ ///
+ public string CustomFunction { get; set; }
+ }
+}
diff --git a/SQLite.CodeFirst/Public/Attributes/CollationFunction.cs b/SQLite.CodeFirst/Public/CollationFunction.cs
similarity index 95%
rename from SQLite.CodeFirst/Public/Attributes/CollationFunction.cs
rename to SQLite.CodeFirst/Public/CollationFunction.cs
index 1bb7f30..6bd054d 100644
--- a/SQLite.CodeFirst/Public/Attributes/CollationFunction.cs
+++ b/SQLite.CodeFirst/Public/CollationFunction.cs
@@ -2,7 +2,7 @@
{
///
/// The collation function to use for this column.
- /// Is used together with the .
+ /// Is used together with the , and when setting a default collation for the database.
///
public enum CollationFunction
{
diff --git a/SQLite.CodeFirst/Public/DbInitializers/SqliteCreateDatabaseIfNotExists.cs b/SQLite.CodeFirst/Public/DbInitializers/SqliteCreateDatabaseIfNotExists.cs
index c572be6..6bfd7dd 100644
--- a/SQLite.CodeFirst/Public/DbInitializers/SqliteCreateDatabaseIfNotExists.cs
+++ b/SQLite.CodeFirst/Public/DbInitializers/SqliteCreateDatabaseIfNotExists.cs
@@ -19,7 +19,16 @@ public class SqliteCreateDatabaseIfNotExists : SqliteInitializerBase
/// The model builder.
public SqliteCreateDatabaseIfNotExists(DbModelBuilder modelBuilder)
- : this(modelBuilder, false)
+ : this(modelBuilder, false, null)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The model builder.
+ /// The default collation applied to all string columns. Explicit s take precedence.
+ public SqliteCreateDatabaseIfNotExists(DbModelBuilder modelBuilder, Collation defaultCollation)
+ : this(modelBuilder, false, defaultCollation)
{ }
///
@@ -28,7 +37,17 @@ public SqliteCreateDatabaseIfNotExists(DbModelBuilder modelBuilder)
/// The model builder.
/// if set to true a null byte database file is treated like the database does not exist.
public SqliteCreateDatabaseIfNotExists(DbModelBuilder modelBuilder, bool nullByteFileMeansNotExisting)
- : base(modelBuilder)
+ : this(modelBuilder, nullByteFileMeansNotExisting, null)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The model builder.
+ /// if set to true a null byte database file is treated like the database does not exist.
+ /// The default collation applied to all string columns. Explicit s take precedence.
+ public SqliteCreateDatabaseIfNotExists(DbModelBuilder modelBuilder, bool nullByteFileMeansNotExisting, Collation defaultCollation)
+ : base(modelBuilder, defaultCollation)
{
this.nullByteFileMeansNotExisting = nullByteFileMeansNotExisting;
}
diff --git a/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseAlways.cs b/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseAlways.cs
index 3154835..fb73aee 100644
--- a/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseAlways.cs
+++ b/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseAlways.cs
@@ -17,7 +17,16 @@ public class SqliteDropCreateDatabaseAlways : SqliteInitializerBase
/// The model builder.
public SqliteDropCreateDatabaseAlways(DbModelBuilder modelBuilder)
- : base(modelBuilder)
+ : this(modelBuilder, null)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The model builder.
+ /// The default collation applied to all string columns. Explicit s take precedence.
+ public SqliteDropCreateDatabaseAlways(DbModelBuilder modelBuilder, Collation defaultCollation)
+ : base(modelBuilder, defaultCollation)
{ }
///
diff --git a/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseWhenModelChanges.cs b/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseWhenModelChanges.cs
index f47feea..dbe050c 100644
--- a/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseWhenModelChanges.cs
+++ b/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseWhenModelChanges.cs
@@ -34,11 +34,17 @@ public class SqliteDropCreateDatabaseWhenModelChanges : SqliteInitiali
///
/// The model builder.
public SqliteDropCreateDatabaseWhenModelChanges(DbModelBuilder modelBuilder)
- : base(modelBuilder)
- {
- historyEntityType = typeof(History);
- ConfigureHistoryEntity();
- }
+ : this(modelBuilder, typeof(History), null)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The model builder.
+ /// The default collation applied to all string columns. Explicit s take precedence.
+ public SqliteDropCreateDatabaseWhenModelChanges(DbModelBuilder modelBuilder, Collation defaultCollation)
+ : this(modelBuilder, typeof(History), defaultCollation)
+ { }
///
/// Initializes a new instance of the class.
@@ -46,7 +52,17 @@ public SqliteDropCreateDatabaseWhenModelChanges(DbModelBuilder modelBuilder)
/// The model builder.
/// Type of the history entity (must implement and provide an parameterless constructor).
public SqliteDropCreateDatabaseWhenModelChanges(DbModelBuilder modelBuilder, Type historyEntityType)
- : base(modelBuilder)
+ : this(modelBuilder, historyEntityType, null)
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The model builder.
+ /// Type of the history entity (must implement and provide an parameterless constructor).
+ /// The default collation applied to all string columns. Explicit s take precedence.
+ public SqliteDropCreateDatabaseWhenModelChanges(DbModelBuilder modelBuilder, Type historyEntityType, Collation defaultCollation)
+ : base(modelBuilder, defaultCollation)
{
this.historyEntityType = historyEntityType;
ConfigureHistoryEntity();
@@ -61,16 +77,16 @@ protected void ConfigureHistoryEntity()
///
/// Initialize the database for the given context.
- /// Generates the SQLite-DDL from the model and executs it against the database.
+ /// Generates the SQLite-DDL from the model and executes it against the database.
/// After that the method is executed.
/// All actions are be executed in transactions.
///
/// The context.
public override void InitializeDatabase(TContext context)
{
- string databseFilePath = GetDatabasePathFromContext(context);
+ string databaseFilePath = GetDatabasePathFromContext(context);
- bool dbExists = InMemoryAwareFile.Exists(databseFilePath);
+ bool dbExists = InMemoryAwareFile.Exists(databaseFilePath);
if (dbExists)
{
if (IsSameModel(context))
@@ -78,10 +94,11 @@ public override void InitializeDatabase(TContext context)
return;
}
- FileAttributes? attributes = InMemoryAwareFile.GetFileAttributes(databseFilePath);
- DeleteDatabase(context, databseFilePath);
+ FileAttributes? attributes = InMemoryAwareFile.GetFileAttributes(databaseFilePath);
+ CloseDatabase(context);
+ DeleteDatabase(context, databaseFilePath);
base.InitializeDatabase(context);
- InMemoryAwareFile.SetFileAttributes(databseFilePath, attributes);
+ InMemoryAwareFile.SetFileAttributes(databaseFilePath, attributes);
SaveHistory(context);
}
else
@@ -91,13 +108,22 @@ public override void InitializeDatabase(TContext context)
}
}
+ ///
+ /// Called to drop/remove Database file from disk.
+ ///
+ /// The context.
+ /// Filename of Database to be removed.
+ protected virtual void DeleteDatabase(TContext context, string databaseFilePath)
+ {
+ InMemoryAwareFile.Delete(databaseFilePath);
+ }
+
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect", Justification = "Required.")]
- private static void DeleteDatabase(TContext context, string databseFilePath)
+ private static void CloseDatabase(TContext context)
{
context.Database.Connection.Close();
GC.Collect();
GC.WaitForPendingFinalizers();
- InMemoryAwareFile.Delete(databseFilePath);
}
private void SaveHistory(TContext context)
@@ -149,7 +175,11 @@ private IHistory GetHistoryRecord(TContext context)
// in order to be supported by .NET 4.0.
DbQuery dbQuery = context.Set(historyEntityType).AsNoTracking();
IEnumerable records = Enumerable.Cast(dbQuery);
- return records.SingleOrDefault();
+
+ // A database can be shared by several contexts, so the record must be looked up by the
+ // context key that SaveHistory writes. An unscoped lookup would match every context's
+ // record and throw on a shared history table.
+ return HistoryRecordSelector.SelectForContext(records, context.GetType().FullName);
}
private string GetHashFromModel(DbConnection connection)
@@ -162,7 +192,7 @@ private string GetHashFromModel(DbConnection connection)
private string GetSqlFromModel(DbConnection connection)
{
var model = ModelBuilder.Build(connection);
- var sqliteSqlGenerator = new SqliteSqlGenerator();
+ var sqliteSqlGenerator = new SqliteSqlGenerator(DefaultCollation);
return sqliteSqlGenerator.Generate(model.StoreModel);
}
}
diff --git a/SQLite.CodeFirst/Public/DbInitializers/SqliteInitializerBase.cs b/SQLite.CodeFirst/Public/DbInitializers/SqliteInitializerBase.cs
index a84cdf8..4e20b33 100644
--- a/SQLite.CodeFirst/Public/DbInitializers/SqliteInitializerBase.cs
+++ b/SQLite.CodeFirst/Public/DbInitializers/SqliteInitializerBase.cs
@@ -24,9 +24,10 @@ namespace SQLite.CodeFirst
public abstract class SqliteInitializerBase : IDatabaseInitializer
where TContext : DbContext
{
- protected SqliteInitializerBase(DbModelBuilder modelBuilder)
+ protected SqliteInitializerBase(DbModelBuilder modelBuilder, Collation defaultCollation = null)
{
ModelBuilder = modelBuilder ?? throw new ArgumentNullException(nameof(modelBuilder));
+ DefaultCollation = defaultCollation;
// This convention will crash the SQLite Provider before "InitializeDatabase" gets called.
// See https://github.com/msallin/SQLiteCodeFirst/issues/7 for details.
@@ -39,6 +40,7 @@ protected SqliteInitializerBase(DbModelBuilder modelBuilder)
modelBuilder.RegisterAttributeAsColumnAnnotation();
modelBuilder.RegisterAttributeAsColumnAnnotation();
modelBuilder.RegisterAttributeAsColumnAnnotation();
+ modelBuilder.RegisterAttributeAsColumnAnnotation();
// By default there is a 'ForeignKeyIndexConvention' but it can be removed.
// And there is no "Contains" and no way to enumerate the ConventionsCollection.
@@ -55,6 +57,8 @@ protected SqliteInitializerBase(DbModelBuilder modelBuilder)
}
}
+ public Collation DefaultCollation { get; }
+
protected DbModelBuilder ModelBuilder { get; }
///
@@ -71,7 +75,7 @@ public virtual void InitializeDatabase(TContext context)
string dbFile = GetDatabasePathFromContext(context);
InMemoryAwareFile.CreateDirectory(dbFile);
- var sqliteDatabaseCreator = new SqliteDatabaseCreator();
+ var sqliteDatabaseCreator = new SqliteDatabaseCreator(DefaultCollation);
sqliteDatabaseCreator.Create(context.Database, model);
Seed(context);
diff --git a/SQLite.CodeFirst/Public/SqliteDatabaseCreator.cs b/SQLite.CodeFirst/Public/SqliteDatabaseCreator.cs
index 4fd325e..0626c24 100644
--- a/SQLite.CodeFirst/Public/SqliteDatabaseCreator.cs
+++ b/SQLite.CodeFirst/Public/SqliteDatabaseCreator.cs
@@ -16,6 +16,13 @@ namespace SQLite.CodeFirst
///
public class SqliteDatabaseCreator : IDatabaseCreator
{
+ public SqliteDatabaseCreator(Collation defaultCollation = null)
+ {
+ DefaultCollation = defaultCollation;
+ }
+
+ public Collation DefaultCollation { get; }
+
///
/// Creates the SQLite-Database.
///
@@ -24,7 +31,7 @@ public void Create(Database db, DbModel model)
if (db == null) throw new ArgumentNullException("db");
if (model == null) throw new ArgumentNullException("model");
- var sqliteSqlGenerator = new SqliteSqlGenerator();
+ var sqliteSqlGenerator = new SqliteSqlGenerator(DefaultCollation);
string sql = sqliteSqlGenerator.Generate(model.StoreModel);
Debug.Write(sql);
db.ExecuteSqlCommand(TransactionalBehavior.EnsureTransaction, sql);
diff --git a/SQLite.CodeFirst/Public/SqliteSqlGenerator.cs b/SQLite.CodeFirst/Public/SqliteSqlGenerator.cs
index d0f248a..944115e 100644
--- a/SQLite.CodeFirst/Public/SqliteSqlGenerator.cs
+++ b/SQLite.CodeFirst/Public/SqliteSqlGenerator.cs
@@ -9,12 +9,19 @@ namespace SQLite.CodeFirst
///
public class SqliteSqlGenerator : ISqlGenerator
{
+ public SqliteSqlGenerator(Collation defaultCollation = null)
+ {
+ DefaultCollation = defaultCollation;
+ }
+
+ public Collation DefaultCollation { get; }
+
///
/// Generates the SQL statement, based on the .
///
public string Generate(EdmModel storeModel)
{
- IStatementBuilder statementBuilder = new CreateDatabaseStatementBuilder(storeModel);
+ IStatementBuilder statementBuilder = new CreateDatabaseStatementBuilder(storeModel, DefaultCollation);
IStatement statement = statementBuilder.BuildStatement();
return statement.CreateStatement();
}
diff --git a/SQLite.CodeFirst/SQLite.CodeFirst.csproj b/SQLite.CodeFirst/SQLite.CodeFirst.csproj
index 21ebe59..a576f4f 100644
--- a/SQLite.CodeFirst/SQLite.CodeFirst.csproj
+++ b/SQLite.CodeFirst/SQLite.CodeFirst.csproj
@@ -1,16 +1,17 @@
- net40;net45
+ net40;net45;netstandard2.1
Marc Sallin
-
Creates a SQLite Database from Code, using Entity Framework CodeFirst. This Project ships several IDbInitializer which creates a new SQLite Database, based on your model/code.
https://github.com/msallin/SQLiteCodeFirst
https://github.com/msallin/SQLiteCodeFirst
GitHub
true
- https://github.com/msallin/SQLiteCodeFirst/blob/master/LICENSE
+ true
+ snupkg
+ Apache-2.0
false
Copyright (C) Marc Sallin
SQLite EntityFramework EF CodeFirst
@@ -20,7 +21,7 @@
-
+
diff --git a/SQLite.CodeFirst/packages.lock.json b/SQLite.CodeFirst/packages.lock.json
new file mode 100644
index 0000000..9d27514
--- /dev/null
+++ b/SQLite.CodeFirst/packages.lock.json
@@ -0,0 +1,208 @@
+{
+ "version": 1,
+ "dependencies": {
+ ".NETFramework,Version=v4.0": {
+ "EntityFramework": {
+ "type": "Direct",
+ "requested": "[6.5.2, )",
+ "resolved": "6.5.2",
+ "contentHash": "8iOcnaKcgkWh35s8EhknnENNwwqvrBJ7jeNbQITbGo1mIeIOhGPfVdNOXZ3Y496T6e6wfXvz0WeXlgMdcgVYSA=="
+ },
+ "Microsoft.NETFramework.ReferenceAssemblies": {
+ "type": "Direct",
+ "requested": "[1.0.3, )",
+ "resolved": "1.0.3",
+ "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
+ "dependencies": {
+ "Microsoft.NETFramework.ReferenceAssemblies.net40": "1.0.3"
+ }
+ },
+ "Microsoft.NETFramework.ReferenceAssemblies.net40": {
+ "type": "Transitive",
+ "resolved": "1.0.3",
+ "contentHash": "3ctXnCpHdoYJNH9ATfXKwckkkdHvHc1Xls12QB9kf1tjh3b+VzxtiwpkZN4GxOawakfH6CJAkqhlDKSiz6Ujbg=="
+ }
+ },
+ ".NETFramework,Version=v4.5": {
+ "EntityFramework": {
+ "type": "Direct",
+ "requested": "[6.5.2, )",
+ "resolved": "6.5.2",
+ "contentHash": "8iOcnaKcgkWh35s8EhknnENNwwqvrBJ7jeNbQITbGo1mIeIOhGPfVdNOXZ3Y496T6e6wfXvz0WeXlgMdcgVYSA=="
+ },
+ "Microsoft.NETFramework.ReferenceAssemblies": {
+ "type": "Direct",
+ "requested": "[1.0.3, )",
+ "resolved": "1.0.3",
+ "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
+ "dependencies": {
+ "Microsoft.NETFramework.ReferenceAssemblies.net45": "1.0.3"
+ }
+ },
+ "Microsoft.NETFramework.ReferenceAssemblies.net45": {
+ "type": "Transitive",
+ "resolved": "1.0.3",
+ "contentHash": "dcSLNuUX2rfZejsyta2EWZ1W5U6ucbFt697lRg1qiTlTM5ZlYv4uAvuxE6ROy6xLWWhLhOaReCDxkhxcajRYtQ=="
+ }
+ },
+ ".NETStandard,Version=v2.1": {
+ "EntityFramework": {
+ "type": "Direct",
+ "requested": "[6.5.2, )",
+ "resolved": "6.5.2",
+ "contentHash": "8iOcnaKcgkWh35s8EhknnENNwwqvrBJ7jeNbQITbGo1mIeIOhGPfVdNOXZ3Y496T6e6wfXvz0WeXlgMdcgVYSA==",
+ "dependencies": {
+ "Microsoft.CSharp": "4.7.0",
+ "System.CodeDom": "6.0.0",
+ "System.ComponentModel.Annotations": "5.0.0",
+ "System.Configuration.ConfigurationManager": "6.0.1",
+ "System.Data.SqlClient": "4.8.6"
+ }
+ },
+ "Microsoft.CSharp": {
+ "type": "Transitive",
+ "resolved": "4.7.0",
+ "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA=="
+ },
+ "Microsoft.Win32.Registry": {
+ "type": "Transitive",
+ "resolved": "4.7.0",
+ "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==",
+ "dependencies": {
+ "System.Buffers": "4.5.0",
+ "System.Memory": "4.5.3",
+ "System.Security.AccessControl": "4.7.0",
+ "System.Security.Principal.Windows": "4.7.0"
+ }
+ },
+ "runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.7.0",
+ "contentHash": "9kyFSIdN3T0qjDQ2R0HRXYIhS3l5psBzQi6qqhdLz+SzFyEy4sVxNOke+yyYv8Cu8rPER12c3RDjLT8wF3WBYQ==",
+ "dependencies": {
+ "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
+ "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": "4.4.0",
+ "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": "4.4.0"
+ }
+ },
+ "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "LbrynESTp3bm5O/+jGL8v0Qg5SJlTV08lpIpFesXjF6uGNMWqFnUQbYBJwZTeua6E/Y7FIM1C54Ey1btLWupdg=="
+ },
+ "runtime.win-x64.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "38ugOfkYJqJoX9g6EYRlZB5U2ZJH51UP8ptxZgdpS07FgOEToV+lS11ouNK2PM12Pr6X/PpT5jK82G3DwH/SxQ=="
+ },
+ "runtime.win-x86.runtime.native.System.Data.SqlClient.sni": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA=="
+ },
+ "System.Buffers": {
+ "type": "Transitive",
+ "resolved": "4.5.1",
+ "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
+ },
+ "System.CodeDom": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA=="
+ },
+ "System.ComponentModel.Annotations": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg=="
+ },
+ "System.Configuration.ConfigurationManager": {
+ "type": "Transitive",
+ "resolved": "6.0.1",
+ "contentHash": "jXw9MlUu/kRfEU0WyTptAVueupqIeE3/rl0EZDMlf8pcvJnitQ8HeVEp69rZdaStXwTV72boi/Bhw8lOeO+U2w==",
+ "dependencies": {
+ "System.Security.Cryptography.ProtectedData": "6.0.0",
+ "System.Security.Permissions": "6.0.0"
+ }
+ },
+ "System.Data.SqlClient": {
+ "type": "Transitive",
+ "resolved": "4.8.6",
+ "contentHash": "2Ij/LCaTQRyAi5lAv7UUTV9R2FobC8xN9mE0fXBZohum/xLl8IZVmE98Rq5ugQHjCgTBRKqpXRb4ORulRdA6Ig==",
+ "dependencies": {
+ "Microsoft.Win32.Registry": "4.7.0",
+ "System.Buffers": "4.5.1",
+ "System.Diagnostics.DiagnosticSource": "4.7.0",
+ "System.Memory": "4.5.4",
+ "System.Security.Principal.Windows": "4.7.0",
+ "System.Text.Encoding.CodePages": "4.7.0",
+ "runtime.native.System.Data.SqlClient.sni": "4.7.0"
+ }
+ },
+ "System.Diagnostics.DiagnosticSource": {
+ "type": "Transitive",
+ "resolved": "4.7.0",
+ "contentHash": "oJjw3uFuVDJiJNbCD8HB4a2p3NYLdt1fiT5OGsPLw+WTOuG0KpP4OXelMmmVKpClueMsit6xOlzy4wNKQFiBLg==",
+ "dependencies": {
+ "System.Memory": "4.5.3"
+ }
+ },
+ "System.Memory": {
+ "type": "Transitive",
+ "resolved": "4.5.4",
+ "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==",
+ "dependencies": {
+ "System.Buffers": "4.5.1",
+ "System.Numerics.Vectors": "4.4.0",
+ "System.Runtime.CompilerServices.Unsafe": "4.5.3"
+ }
+ },
+ "System.Numerics.Vectors": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ=="
+ },
+ "System.Runtime.CompilerServices.Unsafe": {
+ "type": "Transitive",
+ "resolved": "4.7.0",
+ "contentHash": "IpU1lcHz8/09yDr9N+Juc7SCgNUz+RohkCQI+KsWKR67XxpFr8Z6c8t1iENCXZuRuNCc4HBwme/MDHNVCwyAKg=="
+ },
+ "System.Security.AccessControl": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==",
+ "dependencies": {
+ "System.Security.Principal.Windows": "5.0.0"
+ }
+ },
+ "System.Security.Cryptography.ProtectedData": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ==",
+ "dependencies": {
+ "System.Memory": "4.5.4"
+ }
+ },
+ "System.Security.Permissions": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==",
+ "dependencies": {
+ "System.Security.AccessControl": "6.0.0"
+ }
+ },
+ "System.Security.Principal.Windows": {
+ "type": "Transitive",
+ "resolved": "5.0.0",
+ "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA=="
+ },
+ "System.Text.Encoding.CodePages": {
+ "type": "Transitive",
+ "resolved": "4.7.0",
+ "contentHash": "aeu4FlaUTemuT1qOd1MyU4T516QR4Fy+9yDbwWMPHOHy7U8FD6SgTzdZFO7gHcfAPHtECqInbwklVvUK4RHcNg==",
+ "dependencies": {
+ "System.Runtime.CompilerServices.Unsafe": "4.7.0"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ci_appveyor.yml b/ci_appveyor.yml
deleted file mode 100644
index 3a96f64..0000000
--- a/ci_appveyor.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-version: CI_{branch}_{build}
-image: Visual Studio 2017
-skip_tags: true
-configuration:
-- Release
-- Debug
-platform: Any CPU
-before_build:
-- ps: dotnet --version
-- ps: dotnet restore
-build:
- project: SQLite.CodeFirst.sln
- parallel: true
- verbosity: normal
-artifacts:
-- path: SQLite.CodeFirst\bin\Debug\**\SQLite.CodeFirst.dll
- name: Debug
-- path: SQLite.CodeFirst\bin\Release\**\SQLite.CodeFirst.dll
- name: Release
-- path: SQLite.CodeFirst\bin\Release\*.nupkg
- name: NuPkgDebug
-- path: SQLite.CodeFirst\bin\Debug\*.nupkg
- name: NuPkgRelease
\ No newline at end of file
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..f272fe7
--- /dev/null
+++ b/global.json
@@ -0,0 +1,9 @@
+{
+ "sdk": {
+ "version": "10.0.301",
+ "rollForward": "latestMinor"
+ },
+ "test": {
+ "runner": "Microsoft.Testing.Platform"
+ }
+}
diff --git a/release_appveyor.yml b/release_appveyor.yml
deleted file mode 100644
index 17a1e42..0000000
--- a/release_appveyor.yml
+++ /dev/null
@@ -1,44 +0,0 @@
-version: 1.5.2.{build}
-image: Visual Studio 2017
-branches:
- only:
- - master
-skip_tags: true
-platform: Any CPU
-configuration: Release
-dotnet_csproj:
- patch: true
- file: '**\*.csproj'
- version: '{version}'
- package_version: '{version}'
- assembly_version: '{version}'
- file_version: '{version}'
- informational_version: '{version}'
-before_build:
-- ps: dotnet --version
-- ps: dotnet restore
-build:
- project: SQLite.CodeFirst.sln
- parallel: true
- verbosity: normal
-artifacts:
-- path: SQLite.CodeFirst\bin
- name: Assemblies
-- path: SQLite.CodeFirst\bin\Release\*.nupkg
- name: NuPkg
-deploy:
-- provider: NuGet
- api_key:
- secure: QmbFnerlfTAFUZpnaPgVDywMH4fF8rVakefmqvhu3qm9SpuDlLGB9S4HwtdE3Nep
- on:
- branch: master
-- provider: GitHub
- tag: v$(appveyor_build_version)
- release: v$(appveyor_build_version)
- description: https://www.nuget.org/packages/SQLite.CodeFirst/
- auth_token:
- secure: e3cqaFy9PzI9TAdZJBIDy97Bfbwa7j0EXe2yw7Ev9aJXK0Q+3mnULqb1VU4P7BWR
- artifact: Assemblies,NuPkg
- draft: true
- on:
- branch: master
\ No newline at end of file