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/.gitignore b/.gitignore index c8add9d..1ae906a 100644 --- a/.gitignore +++ b/.gitignore @@ -183,3 +183,4 @@ FakesAssemblies/ # Custom *.GhostDoc.xml +/.vs/* diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config index 67f8ea0..3f0e003 100644 --- a/.nuget/NuGet.Config +++ b/.nuget/NuGet.Config @@ -1,6 +1,6 @@  - - - + + + \ No newline at end of file diff --git a/.nuget/NuGet.exe b/.nuget/NuGet.exe deleted file mode 100644 index 8dd7e45..0000000 Binary files a/.nuget/NuGet.exe and /dev/null differ diff --git a/.nuget/NuGet.targets b/.nuget/NuGet.targets deleted file mode 100644 index 3f8c37b..0000000 --- a/.nuget/NuGet.targets +++ /dev/null @@ -1,144 +0,0 @@ - - - - $(MSBuildProjectDirectory)\..\ - - - false - - - false - - - true - - - false - - - - - - - - - - - $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) - - - - - $(SolutionDir).nuget - - - - $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config - $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config - - - - $(MSBuildProjectDirectory)\packages.config - $(PackagesProjectConfig) - - - - - $(NuGetToolsPath)\NuGet.exe - @(PackageSource) - - "$(NuGetExePath)" - mono --runtime=v4.0.30319 "$(NuGetExePath)" - - $(TargetDir.Trim('\\')) - - -RequireConsent - -NonInteractive - - "$(SolutionDir) " - "$(SolutionDir)" - - - $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) - $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols - - - - RestorePackages; - $(BuildDependsOn); - - - - - $(BuildDependsOn); - BuildPackage; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/BuildAllConfigurations.proj b/BuildAllConfigurations.proj deleted file mode 100644 index 3a31715..0000000 --- a/BuildAllConfigurations.proj +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - %(PlatformList.Identity) - - - - - \ No newline at end of file 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/LICENSE b/LICENSE index e06d208..44b57ce 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright 2015 Marc Sallin Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Lib/Moq.dll b/Lib/Moq.dll deleted file mode 100644 index f5b1545..0000000 Binary files a/Lib/Moq.dll and /dev/null differ diff --git a/README.md b/README.md index 7678a77..a6b9367 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,61 @@ # SQLite CodeFirst -**Release Build** [![Build status](https://ci.appveyor.com/api/projects/status/2qavdqctw0ehscm6/branch/master?svg=true)](https://ci.appveyor.com/project/msallin/sqlitecodefirst-nv6vn/branch/master) - -**CI Build** [![Build status](https://ci.appveyor.com/api/projects/status/oc1miog385h801qe?svg=true)](https://ci.appveyor.com/project/msallin/sqlitecodefirst) +[![CI](https://github.com/msallin/SQLiteCodeFirst/actions/workflows/ci.yml/badge.svg)](https://github.com/msallin/SQLiteCodeFirst/actions/workflows/ci.yml) +[![CD](https://github.com/msallin/SQLiteCodeFirst/actions/workflows/cd.yml/badge.svg)](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 PayPal donate button + 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) +- 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.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 @@ -51,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); @@ -64,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 { @@ -85,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 new file mode 100644 index 0000000..2301500 --- /dev/null +++ b/SQLite.CodeFirst.Console/Entity/FooCompositeKey.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +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; } + + [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.Console/Entity/FooRelationshipA.cs b/SQLite.CodeFirst.Console/Entity/FooRelationshipA.cs new file mode 100644 index 0000000..38baaf2 --- /dev/null +++ b/SQLite.CodeFirst.Console/Entity/FooRelationshipA.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace SQLite.CodeFirst.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.Console/Entity/FooRelationshipB.cs b/SQLite.CodeFirst.Console/Entity/FooRelationshipB.cs new file mode 100644 index 0000000..271d8f8 --- /dev/null +++ b/SQLite.CodeFirst.Console/Entity/FooRelationshipB.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace SQLite.CodeFirst.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.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/Entity/Person.cs b/SQLite.CodeFirst.Console/Entity/Person.cs index 2ebad5c..8bc807b 100644 --- a/SQLite.CodeFirst.Console/Entity/Person.cs +++ b/SQLite.CodeFirst.Console/Entity/Person.cs @@ -1,4 +1,6 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace SQLite.CodeFirst.Console.Entity { @@ -18,5 +20,9 @@ public abstract class Person : IEntity [Required] public string City { get; set; } + + [DatabaseGenerated(DatabaseGeneratedOption.Computed)] + [SqlDefaultValue(DefaultValue = "DATETIME('now')")] + public DateTime CreatedUtc { get; set; } } } diff --git a/SQLite.CodeFirst.Console/Entity/Stadion.cs b/SQLite.CodeFirst.Console/Entity/Stadion.cs index f5d9804..d77dd2b 100644 --- a/SQLite.CodeFirst.Console/Entity/Stadion.cs +++ b/SQLite.CodeFirst.Console/Entity/Stadion.cs @@ -18,5 +18,9 @@ public class Stadion [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.Console/ModelConfiguration.cs b/SQLite.CodeFirst.Console/ModelConfiguration.cs index e4815a1..30703ad 100644 --- a/SQLite.CodeFirst.Console/ModelConfiguration.cs +++ b/SQLite.CodeFirst.Console/ModelConfiguration.cs @@ -12,6 +12,7 @@ public static void Configure(DbModelBuilder modelBuilder) ConfigureCoachEntity(modelBuilder); ConfigurePlayerEntity(modelBuilder); ConfigureSelfReferencingEntities(modelBuilder); + ConfigureCompositeKeyEntities(modelBuilder); } private static void ConfigureTeamEntity(DbModelBuilder modelBuilder) @@ -54,5 +55,12 @@ private static void ConfigureSelfReferencingEntities(DbModelBuilder modelBuilder modelBuilder.Entity(); modelBuilder.Entity(); } + + private static void ConfigureCompositeKeyEntities(DbModelBuilder modelBuilder) + { + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + } } } diff --git a/SQLite.CodeFirst.Console/Program.cs b/SQLite.CodeFirst.Console/Program.cs index 1b8e81e..d3913c4 100644 --- a/SQLite.CodeFirst.Console/Program.cs +++ b/SQLite.CodeFirst.Console/Program.cs @@ -131,6 +131,7 @@ private static void DisplaySeededData(DbContext context) 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(); } } diff --git a/SQLite.CodeFirst.Console/Properties/AssemblyInfo.cs b/SQLite.CodeFirst.Console/Properties/AssemblyInfo.cs deleted file mode 100644 index b9a7413..0000000 --- a/SQLite.CodeFirst.Console/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyTitle("SQLite.CodeFirst")] -[assembly: AssemblyDescription("A console application which demonstrates how to use SQLite.CodeFirst.")] - -// Will be replaced by the build server -// But do not put them in a shared file, because this will break the version patching. -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersion("1.0.0.0")] diff --git a/SQLite.CodeFirst.Console/SQLite.CodeFirst.Console.csproj b/SQLite.CodeFirst.Console/SQLite.CodeFirst.Console.csproj index b70e772..95f93db 100644 --- a/SQLite.CodeFirst.Console/SQLite.CodeFirst.Console.csproj +++ b/SQLite.CodeFirst.Console/SQLite.CodeFirst.Console.csproj @@ -1,118 +1,24 @@ - - - + - Debug - AnyCPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D} Exe - Properties - SQLite.CodeFirst.Console - SQLite.CodeFirst.Console - v4.5.2 - 512 - true - - - ..\ - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - + net48 + SQLite.CodeFirst + A console application which demonstrates how to use SQLite.CodeFirst. + 1.0.0.0 AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - true - - ..\Shared\SQLite.CodeFirst.StrongNameKey.snk - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - - + + + - - - ..\packages\System.Data.SQLite.Core.1.0.105.2\lib\net451\System.Data.SQLite.dll - - - ..\packages\System.Data.SQLite.EF6.1.0.105.2\lib\net451\System.Data.SQLite.EF6.dll - - - ..\packages\System.Data.SQLite.Linq.1.0.105.2\lib\net451\System.Data.SQLite.Linq.dll - - - - - - - - Properties\AssemblySharedInfo.cs - - - - - - - - - - - - - - - - - - - - - - {50A32FE4-0E13-4213-A373-72523CDF34D9} - SQLite.CodeFirst - + - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - \ No newline at end of file diff --git a/SQLite.CodeFirst.Console/packages.config b/SQLite.CodeFirst.Console/packages.config deleted file mode 100644 index 0e43dd0..0000000 --- a/SQLite.CodeFirst.Console/packages.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file 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 new file mode 100644 index 0000000..d5ef6ab --- /dev/null +++ b/SQLite.CodeFirst.Test/IntegrationTests/InMemoryDbCreationTest.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +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 InMemoryDbCreationTest : InMemoryDbTest + { + [TestMethod] + public void CreateInMemoryDatabaseTest() + { + using (var context = GetDbContext()) + { + context.Set().Add(new Team + { + Name = "New", + Coach = new Coach + { + City = "New", + FirstName = "New", + LastName = "New", + Street = "New" + }, + Players = new List + { + new Player + { + City = "New", + FirstName = "New", + LastName = "New", + Street = "New", + Number = 1 + }, + new Player + { + City = "New", + FirstName = "New", + LastName = "New", + Street = "New", + Number = 2 + } + }, + Stadion = new Stadion + { + Name = "New", + City = "New", + Street = "New" + } + }); + + context.SaveChanges(); + } + + using (var context = GetDbContext()) + { + Assert.AreEqual(1, context.Set().Count()); + } + } + } +} diff --git a/SQLite.CodeFirst.Test/IntegrationTests/InMemoryDbTest.cs b/SQLite.CodeFirst.Test/IntegrationTests/InMemoryDbTest.cs new file mode 100644 index 0000000..314b760 --- /dev/null +++ b/SQLite.CodeFirst.Test/IntegrationTests/InMemoryDbTest.cs @@ -0,0 +1,48 @@ +using System; +using System.Data.Common; +using System.Data.Entity; +using System.Data.SQLite; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace SQLite.CodeFirst.Test.IntegrationTests +{ + /// + /// Inherit this class to create a test which uses a SQLite in memory database. + /// This class provides the necessary logic to run multiple tests again the in memory database in a row. + /// + public abstract class InMemoryDbTest + where TDbContext : DbContext + { + private bool dbInitialized; + + protected DbConnection Connection { get; private set; } + + protected DbContext GetDbContext() + { + TDbContext context = (TDbContext)Activator.CreateInstance(typeof(TDbContext), Connection, false); + if (!dbInitialized) + { + context.Database.Initialize(true); + dbInitialized = true; + } + return context; + } + + [TestInitialize] + public void Initialize() + { + Connection = new SQLiteConnection("data source=:memory:"); + + // This is important! Else the in memory database will not work. + Connection.Open(); + + dbInitialized = false; + } + + [TestCleanup] + public void Cleanup() + { + Connection.Dispose(); + } + } +} diff --git a/SQLite.CodeFirst.Test/IntegrationTests/MemoryDbTest.cs b/SQLite.CodeFirst.Test/IntegrationTests/MemoryDbTest.cs deleted file mode 100644 index 19d7096..0000000 --- a/SQLite.CodeFirst.Test/IntegrationTests/MemoryDbTest.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Collections.Generic; -using System.Data.Common; -using System.Data.SQLite; -using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using SQLite.CodeFirst.Console; -using SQLite.CodeFirst.Console.Entity; - -namespace SQLite.CodeFirst.Test.IntegrationTests -{ - [TestClass] - public class MemoryDbTest - { - [TestMethod] - public void CreateInMemoryDatabaseTest() - { - using (DbConnection connection = new SQLiteConnection("data source=:memory:")) - { - // This is important! Else the in memory database will not work. - connection.Open(); - - using (var context = new FootballDbContext(connection, false)) - { - context.Set().Add(new Team - { - Name = "New", - Coach = new Coach - { - City = "New", - FirstName = "New", - LastName = "New", - Street = "New" - }, - Players = new List - { - new Player - { - City = "New", - FirstName = "New", - LastName = "New", - Street = "New", - Number = 1 - }, - new Player - { - City = "New", - FirstName = "New", - LastName = "New", - Street = "New", - Number = 2 - } - }, - Stadion = new Stadion - { - Name = "New", - City = "New", - Street = "New" - } - }); - - context.SaveChanges(); - } - - using (var context = new FootballDbContext(connection, false)) - { - Assert.AreEqual(1, context.Set().Count()); - } - } - } - } -} \ No newline at end of file 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 e146364..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,34 +13,46 @@ 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 ""Coaches"" ([Id] INTEGER PRIMARY KEY, [FirstName] nvarchar (50) COLLATE NOCASE, [LastName] nvarchar (50), [Street] nvarchar (100), [City] nvarchar NOT NULL); -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, [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, [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 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 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 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 ""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 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. - [Ignore] + [TestMethod] public void SqliteSqlGeneratorTest() { - using (DbConnection connection = new SQLiteConnection("data source=:memory:")) + using (DbConnection connection = new SQLiteConnection("FullUri=file::memory:")) { // This is important! Else the in memory database will not work. connection.Open(); @@ -51,7 +62,7 @@ public void SqliteSqlGeneratorTest() // ReSharper disable once UnusedVariable Player fo = context.Set().FirstOrDefault(); - Assert.IsTrue(string.Equals(RemoveLineEndings(ReferenceSql), RemoveLineEndings(generatedSql))); + Assert.AreEqual(RemoveLineEndings(ReferenceSql), RemoveLineEndings(generatedSql)); } } } diff --git a/SQLite.CodeFirst.Test/Properties/AssemblyInfo.cs b/SQLite.CodeFirst.Test/Properties/AssemblyInfo.cs deleted file mode 100644 index d167e9f..0000000 --- a/SQLite.CodeFirst.Test/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyTitle("SQLite.CodeFirst.Test")] -[assembly: AssemblyDescription("Contains the Unit Tests for the SQLite.CodeFirst Library.")] - -// Will be replaced by the build server -// But do not put them in a shared file, because this will break the version patching. -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersion("1.0.0.0")] diff --git a/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj b/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj index ceff749..0228e1e 100644 --- a/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj +++ b/SQLite.CodeFirst.Test/SQLite.CodeFirst.Test.csproj @@ -1,166 +1,24 @@ - - - - Debug - AnyCPU - {E542F38D-852E-489D-81C2-BF333503E10F} - Library - Properties - SQLite.CodeFirst.Test - SQLite.CodeFirst.Test - v4.5.2 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - + + 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 - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - True - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - True - - - ..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll - True - False - ..\Lib\Moq.dll - - - - - - ..\packages\System.Data.SQLite.Core.1.0.105.2\lib\net451\System.Data.SQLite.dll - - - ..\packages\System.Data.SQLite.EF6.1.0.105.2\lib\net451\System.Data.SQLite.EF6.dll - - - ..\packages\System.Data.SQLite.Linq.1.0.105.2\lib\net451\System.Data.SQLite.Linq.dll - - - - - - - - - - - - False - - - - - - - Properties\AssemblySharedInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {dedabd86-6ea0-4673-a858-a4f71958f51d} - SQLite.CodeFirst.Console - - - {50a32fe4-0e13-4213-a373-72523cdf34d9} - SQLite.CodeFirst - + + + + + - - + + - - - - - False - - - False - - - False - - - False - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ 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 new file mode 100644 index 0000000..a8b01b1 --- /dev/null +++ b/SQLite.CodeFirst.Test/UnitTests/Statement/ColumnConstraint/DefaultValueConstraintTest.cs @@ -0,0 +1,36 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SQLite.CodeFirst.Statement.ColumnConstraint; + +namespace SQLite.CodeFirst.Test.UnitTests.Statement.ColumnConstraint +{ + [TestClass] + public class DefaultValueConstraintTest + { + [TestMethod] + public void CreateStatement_StatementIsCorrect_IntDefault() + { + var defaultValueConstraint = new DefaultValueConstraint(); + defaultValueConstraint.DefaultValue = "0"; + string output = defaultValueConstraint.CreateStatement(); + Assert.AreEqual("DEFAULT (0)", output); + } + + [TestMethod] + public void CreateStatement_StatementIsCorrect_StringDefault() + { + var defaultValueConstraint = new DefaultValueConstraint(); + defaultValueConstraint.DefaultValue = @"'Something'"; + string output = defaultValueConstraint.CreateStatement(); + Assert.AreEqual("DEFAULT ('Something')", output); + } + + [TestMethod] + public void CreateStatement_StatementIsCorrect_ExpressionDefault() + { + var defaultValueConstraint = new DefaultValueConstraint(); + defaultValueConstraint.DefaultValue = @"datetime('now')"; + string output = defaultValueConstraint.CreateStatement(); + 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.config b/SQLite.CodeFirst.Test/packages.config deleted file mode 100644 index 0e43dd0..0000000 --- a/SQLite.CodeFirst.Test/packages.config +++ /dev/null @@ -1,8 +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 c98e539..0000000 --- a/SQLite.CodeFirst.sln +++ /dev/null @@ -1,87 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite.CodeFirst", "SQLite.CodeFirst\SQLite.CodeFirst.csproj", "{50A32FE4-0E13-4213-A373-72523CDF34D9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite.CodeFirst.Console", "SQLite.CodeFirst.Console\SQLite.CodeFirst.Console.csproj", "{DEDABD86-6EA0-4673-A858-A4F71958F51D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{01B97A84-5983-45C5-ACF8-168E56DF297C}" - ProjectSection(SolutionItems) = preProject - .nuget\NuGet.Config = .nuget\NuGet.Config - .nuget\NuGet.exe = .nuget\NuGet.exe - .nuget\NuGet.targets = .nuget\NuGet.targets - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{7031BD3C-AE76-43CD-91B6-B6BCD823968C}" - ProjectSection(SolutionItems) = preProject - BuildAllConfigurations.proj = BuildAllConfigurations.proj - release_appveyor.yml = release_appveyor.yml - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLite.CodeFirst.Test", "SQLite.CodeFirst.Test\SQLite.CodeFirst.Test.csproj", "{E542F38D-852E-489D-81C2-BF333503E10F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lib", "Lib", "{436039AE-ADD6-48A5-81B9-C413E13344A9}" - ProjectSection(SolutionItems) = preProject - Lib\Moq.dll = Lib\Moq.dll - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1F6D4E0E-5D07-4889-961A-A6AFC0ECD7C7}" - ProjectSection(SolutionItems) = preProject - Shared\AssemblySharedInfo.cs = Shared\AssemblySharedInfo.cs - 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 - Debug-40|Any CPU = Debug-40|Any CPU - Debug-45|Any CPU = Debug-45|Any CPU - Release|Any CPU = Release|Any CPU - Release-40|Any CPU = Release-40|Any CPU - Release-45|Any CPU = Release-45|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug|Any CPU.ActiveCfg = Debug-45|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug|Any CPU.Build.0 = Debug-45|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug-40|Any CPU.ActiveCfg = Debug-40|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug-40|Any CPU.Build.0 = Debug-40|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug-45|Any CPU.ActiveCfg = Debug-45|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Debug-45|Any CPU.Build.0 = Debug-45|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release|Any CPU.ActiveCfg = Release-45|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release|Any CPU.Build.0 = Release-45|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release-40|Any CPU.ActiveCfg = Release-40|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release-40|Any CPU.Build.0 = Release-40|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release-45|Any CPU.ActiveCfg = Release-45|Any CPU - {50A32FE4-0E13-4213-A373-72523CDF34D9}.Release-45|Any CPU.Build.0 = Release-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug|Any CPU.ActiveCfg = Debug-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug|Any CPU.Build.0 = Debug-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug-40|Any CPU.ActiveCfg = Debug-40|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug-40|Any CPU.Build.0 = Debug-40|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug-45|Any CPU.ActiveCfg = Debug-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Debug-45|Any CPU.Build.0 = Debug-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release|Any CPU.ActiveCfg = Release-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release|Any CPU.Build.0 = Release-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release-40|Any CPU.ActiveCfg = Release-40|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release-40|Any CPU.Build.0 = Release-40|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release-45|Any CPU.ActiveCfg = Release-45|Any CPU - {DEDABD86-6EA0-4673-A858-A4F71958F51D}.Release-45|Any CPU.Build.0 = Release-45|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}.Debug-40|Any CPU.ActiveCfg = Debug|Any CPU - {E542F38D-852E-489D-81C2-BF333503E10F}.Debug-40|Any CPU.Build.0 = Debug|Any CPU - {E542F38D-852E-489D-81C2-BF333503E10F}.Debug-45|Any CPU.ActiveCfg = Debug|Any CPU - {E542F38D-852E-489D-81C2-BF333503E10F}.Debug-45|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 - {E542F38D-852E-489D-81C2-BF333503E10F}.Release-40|Any CPU.ActiveCfg = Release|Any CPU - {E542F38D-852E-489D-81C2-BF333503E10F}.Release-40|Any CPU.Build.0 = Release|Any CPU - {E542F38D-852E-489D-81C2-BF333503E10F}.Release-45|Any CPU.ActiveCfg = Release|Any CPU - {E542F38D-852E-489D-81C2-BF333503E10F}.Release-45|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - 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/Shared/AssemblySharedInfo.cs b/SQLite.CodeFirst/Internal/AssemblyInfo.cs similarity index 59% rename from Shared/AssemblySharedInfo.cs rename to SQLite.CodeFirst/Internal/AssemblyInfo.cs index d3d6117..6de9d4c 100644 --- a/Shared/AssemblySharedInfo.cs +++ b/SQLite.CodeFirst/Internal/AssemblyInfo.cs @@ -1,18 +1,3 @@ -using System; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: AssemblyProduct("SQLite.CodeFirst")] -[assembly: AssemblyCopyright("Copyright © Marc Sallin")] -[assembly: AssemblyCompany("Marc Sallin")] - +using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("SQLite.CodeFirst.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e35b9d3bf8a8ebcbbc7414b81cc803f1e43b24996e5af0d552c54743651fc328a50977e6e3c10b31c26165a74f3abbd9fdf2b785b8b9ba48568914aec8fea586a4ffa287c9dc5bd2b6367a3cb48e840baab417e89b27b95fa7ac5bc5b926cf9c45a76b064307845c71e9161bdb6eb7f96a6bf24ee6db96f7a4b71b7408938fae", AllInternalsVisible = true)] -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] - -[assembly:CLSCompliant(true)] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] \ No newline at end of file diff --git a/SQLite.CodeFirst/Internal/Builder/ColumnStatementCollectionBuilder.cs b/SQLite.CodeFirst/Internal/Builder/ColumnStatementCollectionBuilder.cs index bd2fb31..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,8 +42,9 @@ 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); yield return columnStatement; } @@ -72,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) { - columnStatement.ColumnConstraints.Add(new CollateConstraint { CollationFunction = value.Collation }); + // 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) + { + // 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); } } @@ -90,6 +107,15 @@ private static void AddUniqueConstraintIfNecessary(EdmProperty property, ColumnS } } + private static void AddDefaultValueConstraintIfNecessary(EdmProperty property, ColumnStatement columnStatement) + { + var value = property.GetCustomAnnotation(); + if (value != null) + { + columnStatement.ColumnConstraints.Add(new DefaultValueConstraint { DefaultValue = value.DefaultValue }); + } + } + private void AddPrimaryKeyConstraintAndAdjustTypeIfNecessary(EdmProperty property, ColumnStatement columnStatement) { // Only handle a single primary key this way. 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 9e907ad..e5d24ac 100644 --- a/SQLite.CodeFirst/Internal/Convention/SqliteForeignKeyIndexConvention.cs +++ b/SQLite.CodeFirst/Internal/Convention/SqliteForeignKeyIndexConvention.cs @@ -46,7 +46,11 @@ public virtual void Apply(AssociationType item, DbModel model) } string tableName = item.Constraint.ToRole.GetEntityType().GetTableName(); - string propertyName = edmProperty.Name; + + // Special treatment for composite primary keys + string propertyName = item.Constraint.FromProperties.Count > 1 + ? String.Join("_", item.Constraint.ToProperties) + : edmProperty.Name; // The original attribute is removed. The none-ForeignKeyIndicies will be remained and readded without any modification // and the foreignKeyIncidies will be readded with the correct name. @@ -70,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/ColumnConstraint/CollateConstraint.cs b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/CollateConstraint.cs index 2fb664f..74068be 100644 --- a/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/CollateConstraint.cs +++ b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/CollateConstraint.cs @@ -8,6 +8,8 @@ internal class CollateConstraint : IColumnConstraint public CollationFunction CollationFunction { get; set; } + public string CustomCollationFunction { get; set; } + public string CreateStatement() { if (CollationFunction == CollationFunction.None) @@ -17,8 +19,9 @@ public string CreateStatement() var sb = new StringBuilder(Template); - sb.Replace("{collation-name}", CollationFunction.ToString().ToUpperInvariant()); - + string name = CollationFunction == CollationFunction.Custom ? CustomCollationFunction : CollationFunction.ToString().ToUpperInvariant(); + sb.Replace("{collation-name}", name); + return sb.ToString().Trim(); } } diff --git a/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/DefaultValueConstraint.cs b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/DefaultValueConstraint.cs new file mode 100644 index 0000000..9c41c22 --- /dev/null +++ b/SQLite.CodeFirst/Internal/Statement/ColumnConstraint/DefaultValueConstraint.cs @@ -0,0 +1,20 @@ +using System.Text; + +namespace SQLite.CodeFirst.Statement.ColumnConstraint +{ + internal class DefaultValueConstraint : IColumnConstraint + { + private const string Template = "DEFAULT ({defaultValue})"; + + public string DefaultValue { get; set; } + + public string CreateStatement() + { + var sb = new StringBuilder(Template); + + sb.Replace("{defaultValue}", DefaultValue); + + return sb.ToString().Trim(); + } + } +} 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/CreateIndexStatement.cs b/SQLite.CodeFirst/Internal/Statement/CreateIndexStatement.cs index b153ad1..8eb4e63 100644 --- a/SQLite.CodeFirst/Internal/Statement/CreateIndexStatement.cs +++ b/SQLite.CodeFirst/Internal/Statement/CreateIndexStatement.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using SQLite.CodeFirst.Builder.NameCreators; namespace SQLite.CodeFirst.Statement { @@ -23,7 +24,7 @@ public string CreateStatement() stringBuilder.Replace("{index-name}", Name); stringBuilder.Replace("{table-name}", Table); - IEnumerable orderedColumnNames = Columns.OrderBy(c => c.Order).Select(c => c.Name); + IEnumerable orderedColumnNames = Columns.OrderBy(c => c.Order).Select(c => c.Name).Select(NameCreator.EscapeName); string columnDefinition = String.Join(ColumnNameSeperator, orderedColumnNames); stringBuilder.Replace("{column-def}", columnDefinition); 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 9865919..93592c6 100644 --- a/SQLite.CodeFirst/Internal/Utility/ConnectionStringParser.cs +++ b/SQLite.CodeFirst/Internal/Utility/ConnectionStringParser.cs @@ -17,10 +17,20 @@ internal static class ConnectionStringParser public static string GetDataSource(string connectionString) { - var path = ExpandDataDirectory(ParseConnectionString(connectionString)[DataSourceToken]); - // remove quotation mark if exists - path = path.Trim('"'); - return path; + // If the datasource token does not exists this is a FullUri connection string. + IDictionary strings = ParseConnectionString(connectionString); + if (strings.ContainsKey(DataSourceToken)) + { + var path = ExpandDataDirectory(strings[DataSourceToken]); + return path.Trim('"'); + } + + // TODO: Implement FullUri parsing. + if (connectionString.Contains(":memory:")) + { + return ":memory:"; + } + throw new NotSupportedException("FullUri format is currently only supported for :memory:."); } [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "ToUppercase makes no sense.")] @@ -32,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/InMemoryAwareFile.cs b/SQLite.CodeFirst/Internal/Utility/InMemoryAwareFile.cs index ccda4c0..d04c21c 100644 --- a/SQLite.CodeFirst/Internal/Utility/InMemoryAwareFile.cs +++ b/SQLite.CodeFirst/Internal/Utility/InMemoryAwareFile.cs @@ -5,6 +5,24 @@ namespace SQLite.CodeFirst.Utility { internal class InMemoryAwareFile { + public static FileAttributes? GetFileAttributes(string path) + { + if (IsInMemoryPath(path)) + { + return null; + } + return File.GetAttributes(path); + } + + public static void SetFileAttributes(string path, FileAttributes? fileAttributes) + { + if (IsInMemoryPath(path) || fileAttributes == null) + { + return; + } + File.SetAttributes(path, fileAttributes.Value); + } + public static bool Exists(string path) { if (IsInMemoryPath(path)) 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/Properties/AssemblyInfo.cs b/SQLite.CodeFirst/Properties/AssemblyInfo.cs deleted file mode 100644 index 970acd0..0000000 --- a/SQLite.CodeFirst/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyTitle("SQLite.CodeFirst")] -[assembly: AssemblyDescription( - "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.")] - -// Will be replaced by the build server -// But do not put them in a shared file, because this will break the version patching. -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersion("1.0.0.0")] 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 187efcf..636965a 100644 --- a/SQLite.CodeFirst/Public/Attributes/CollateAttribute.cs +++ b/SQLite.CodeFirst/Public/Attributes/CollateAttribute.cs @@ -5,20 +5,26 @@ namespace SQLite.CodeFirst /// /// 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 ). + /// It is possible to specify a custom collating function. Set to and specify the name using the function parameter. /// [AttributeUsage(AttributeTargets.Property)] public sealed class CollateAttribute : Attribute { public CollateAttribute() { - Collation = CollationFunction.None; + Collation = new Collation(); } - public CollateAttribute(CollationFunction collation) + public CollateAttribute(CollationFunction function) { - Collation = collation; + Collation = new Collation(function); } - public CollationFunction Collation { get; private set; } + public CollateAttribute(CollationFunction function, string customFunction) + { + Collation = new Collation(function, customFunction); + } + + public Collation Collation { get; } } } \ No newline at end of file diff --git a/SQLite.CodeFirst/Public/Attributes/SqlDefaultValueAttribute.cs b/SQLite.CodeFirst/Public/Attributes/SqlDefaultValueAttribute.cs new file mode 100644 index 0000000..6c45780 --- /dev/null +++ b/SQLite.CodeFirst/Public/Attributes/SqlDefaultValueAttribute.cs @@ -0,0 +1,16 @@ +using System; + +namespace SQLite.CodeFirst +{ + /// + /// Decorate an column with this attribute to create a "DEFAULT {defaultvalue}". + /// + /// https://www.sqlite.org/lang_createtable.html [05.10.2017] + /// + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class SqlDefaultValueAttribute : Attribute + { + public string DefaultValue { get; set; } + } +} \ 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 77% rename from SQLite.CodeFirst/Public/Attributes/CollationFunction.cs rename to SQLite.CodeFirst/Public/CollationFunction.cs index 10a2519..6bd054d 100644 --- a/SQLite.CodeFirst/Public/Attributes/CollationFunction.cs +++ b/SQLite.CodeFirst/Public/CollationFunction.cs @@ -2,15 +2,15 @@ { /// /// 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 { None, /// - /// Compares string data using memcmp(), regardless of text encoding. - /// + /// The same as binary, except that trailing space characters are ignored. + /// RTrim, /// @@ -21,8 +21,13 @@ public enum CollationFunction NoCase, /// - /// The same as binary, except that trailing space characters are ignored. + /// Compares string data using memcmp(), regardless of text encoding. + /// + Binary, + + /// + /// An application can register additional collating functions using the sqlite3_create_collation() interface. /// - Binary + Custom } } \ No newline at end of file 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 4ee736f..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) { } /// @@ -34,10 +43,15 @@ public override void InitializeDatabase(TContext context) bool exists = InMemoryAwareFile.Exists(databseFilePath); if (exists) { + FileAttributes? attributes = InMemoryAwareFile.GetFileAttributes(databseFilePath); InMemoryAwareFile.Delete(databseFilePath); + base.InitializeDatabase(context); + InMemoryAwareFile.SetFileAttributes(databseFilePath, attributes); + } + else + { + base.InitializeDatabase(context); } - - base.InitializeDatabase(context); } } } diff --git a/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseWhenModelChanges.cs b/SQLite.CodeFirst/Public/DbInitializers/SqliteDropCreateDatabaseWhenModelChanges.cs index 17cea25..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 = File.Exists(databseFilePath); + bool dbExists = InMemoryAwareFile.Exists(databaseFilePath); if (dbExists) { if (IsSameModel(context)) @@ -78,8 +94,11 @@ public override void InitializeDatabase(TContext context) return; } - DeleteDatabase(context, databseFilePath); + FileAttributes? attributes = InMemoryAwareFile.GetFileAttributes(databaseFilePath); + CloseDatabase(context); + DeleteDatabase(context, databaseFilePath); base.InitializeDatabase(context); + InMemoryAwareFile.SetFileAttributes(databaseFilePath, attributes); SaveHistory(context); } else @@ -89,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) @@ -147,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) @@ -160,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 21a159a..4e20b33 100644 --- a/SQLite.CodeFirst/Public/DbInitializers/SqliteInitializerBase.cs +++ b/SQLite.CodeFirst/Public/DbInitializers/SqliteInitializerBase.cs @@ -24,14 +24,10 @@ namespace SQLite.CodeFirst public abstract class SqliteInitializerBase : IDatabaseInitializer where TContext : DbContext { - protected SqliteInitializerBase(DbModelBuilder modelBuilder) + protected SqliteInitializerBase(DbModelBuilder modelBuilder, Collation defaultCollation = null) { - if (modelBuilder == null) - { - throw new ArgumentNullException("modelBuilder"); - } - - ModelBuilder = modelBuilder; + 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. @@ -43,6 +39,8 @@ protected SqliteInitializerBase(DbModelBuilder modelBuilder) modelBuilder.RegisterAttributeAsColumnAnnotation(); 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. @@ -59,6 +57,8 @@ protected SqliteInitializerBase(DbModelBuilder modelBuilder) } } + public Collation DefaultCollation { get; } + protected DbModelBuilder ModelBuilder { get; } /// @@ -75,35 +75,11 @@ public virtual void InitializeDatabase(TContext context) string dbFile = GetDatabasePathFromContext(context); InMemoryAwareFile.CreateDirectory(dbFile); - using (DbContextTransaction transaction = context.Database.BeginTransaction()) - { - try - { - var sqliteDatabaseCreator = new SqliteDatabaseCreator(); - sqliteDatabaseCreator.Create(context.Database, model); - transaction.Commit(); - } - catch (Exception) - { - transaction.Rollback(); - throw; - } - } + var sqliteDatabaseCreator = new SqliteDatabaseCreator(DefaultCollation); + sqliteDatabaseCreator.Create(context.Database, model); - using (DbContextTransaction transaction = context.Database.BeginTransaction()) - { - try - { - Seed(context); - context.SaveChanges(); - transaction.Commit(); - } - catch (Exception) - { - transaction.Rollback(); - throw; - } - } + Seed(context); + context.SaveChanges(); } /// diff --git a/SQLite.CodeFirst/Public/SqliteDatabaseCreator.cs b/SQLite.CodeFirst/Public/SqliteDatabaseCreator.cs index c77c656..0626c24 100644 --- a/SQLite.CodeFirst/Public/SqliteDatabaseCreator.cs +++ b/SQLite.CodeFirst/Public/SqliteDatabaseCreator.cs @@ -8,9 +8,21 @@ namespace SQLite.CodeFirst /// /// Creates a SQLite-Database based on a Entity Framework and . /// This creator can be used standalone or within an initializer. + /// + /// The generated DDL-SQL will be executed together as one statement. + /// If there is a open transaction on the Database, the statement will be executed within this transaction. + /// Otherwise it will be executed within a own transaction. In anyway the atomicity is guaranteed. + /// /// public class SqliteDatabaseCreator : IDatabaseCreator { + public SqliteDatabaseCreator(Collation defaultCollation = null) + { + DefaultCollation = defaultCollation; + } + + public Collation DefaultCollation { get; } + /// /// Creates the SQLite-Database. /// @@ -19,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 caa20fc..a576f4f 100644 --- a/SQLite.CodeFirst/SQLite.CodeFirst.csproj +++ b/SQLite.CodeFirst/SQLite.CodeFirst.csproj @@ -1,159 +1,27 @@ - - - - - Debug-45 - AnyCPU - {50A32FE4-0E13-4213-A373-72523CDF34D9} - Library - Properties - SQLite.CodeFirst - SQLite.CodeFirst - 512 - true - - - ..\ - true - - - - - AnyCPU - true - DEBUG;TRACE - full - prompt - ..\Shared\SQLite.CodeFirst.ruleset - true - false - - - v4.0 - bin\Debug\net40 - - - bin\Debug\net45 - v4.5 - - - - - AnyCPU - TRACE - true - pdbonly - prompt - ..\Shared\SQLite.CodeFirst.ruleset - true - true - - - bin\Release\net40 - v4.0 - - - bin\Release\net45 - v4.5 - - - - - + + + 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 + true + snupkg + Apache-2.0 + false + Copyright (C) Marc Sallin + SQLite EntityFramework EF CodeFirst + true true - - ..\Shared\SQLite.CodeFirst.StrongNameKey.snk - - - - ..\packages\EntityFramework.6.1.3\lib\net40\EntityFramework.dll - - - - - ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - - - - - - - - - - - Properties\AssemblySharedInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file + + diff --git a/SQLite.CodeFirst/SQLite.CodeFirst.csproj.DotSettings b/SQLite.CodeFirst/SQLite.CodeFirst.csproj.DotSettings deleted file mode 100644 index 7a5425b..0000000 --- a/SQLite.CodeFirst/SQLite.CodeFirst.csproj.DotSettings +++ /dev/null @@ -1,9 +0,0 @@ - - True - True - True - True - True - True - True - True \ No newline at end of file diff --git a/SQLite.CodeFirst/SQLite.CodeFirst.csproj.nuspec b/SQLite.CodeFirst/SQLite.CodeFirst.csproj.nuspec deleted file mode 100644 index 6236557..0000000 --- a/SQLite.CodeFirst/SQLite.CodeFirst.csproj.nuspec +++ /dev/null @@ -1,32 +0,0 @@ - - - - $id$ - $version$ - $author$ - $author$ - https://github.com/msallin/SQLiteCodeFirst/blob/master/LICENSE - https://github.com/msallin/SQLiteCodeFirst - false - $description$ - Creates a SQLite Database from Code, using Entity Framework CodeFirst. - - Copyright (C) Marc Sallin - SQLite EntityFramework EF CodeFirst - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/SQLite.CodeFirst/packages.config b/SQLite.CodeFirst/packages.config deleted file mode 100644 index cb3371a..0000000 --- a/SQLite.CodeFirst/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file 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/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 a1d69d3..0000000 --- a/release_appveyor.yml +++ /dev/null @@ -1,49 +0,0 @@ -version: 1.3.2.{build} -branches: - only: - - master -skip_tags: true -platform: Any CPU -assembly_info: - patch: true - file: '**\AssemblyInfo.*' - assembly_version: '{version}' - assembly_file_version: '{version}' - assembly_informational_version: '{version}' -build: - project: BuildAllConfigurations.proj - verbosity: normal -after_build: -- ps: >- - cd $env:APPVEYOR_BUILD_FOLDER - - cd '.nuget' - - ./nuget.exe pack '..\SQLite.CodeFirst\SQLite.CodeFirst.csproj' -Properties -Symbols -OutputDirectory '..\SQLite.CodeFirst\bin' -artifacts: -- path: SQLite.CodeFirst\bin - name: Assemblies -- path: SQLite.CodeFirst\bin\*.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 -notifications: -- provider: Webhook - url: https://zapier.com/hooks/catch/b3cbz0/ - on_build_success: true - on_build_failure: true - on_build_status_changed: false \ No newline at end of file