diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..e69de29b diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..176a458f --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9e5be7d2..005582e1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,41 +1,41 @@ -name: CI - -on: - push - -env: - UseSqlServerContainer: true - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - - name: install .NET Core 8/9 SDKs - uses: actions/setup-dotnet@v2 - with: - include-prerelease: false - dotnet-version: | - 8.0.x - 9.0.x - - - name: checkout repository - uses: actions/checkout@v2 - - - name: dotnet restore - run: dotnet restore - - - name: build - run: dotnet build --configuration Release --no-restore - - - name: test - run: dotnet test --configuration Release --no-build - - - name: test results - uses: EnricoMi/publish-unit-test-result-action@v2 - id: test-results - if: always() - with: - check_name: tests results - trx_files: "**/test-results/**/*.trx" +name: CI + +on: + push + +env: + UseSqlServerContainer: true + +jobs: + build: + runs-on: ubuntu-latest + + steps: + + - name: install .NET Core 8/9 SDKs + uses: actions/setup-dotnet@v2 + with: + include-prerelease: false + dotnet-version: | + 8.0.x + 9.0.x + + - name: checkout repository + uses: actions/checkout@v2 + + - name: dotnet restore + run: dotnet restore + + - name: build + run: dotnet build --configuration Release --no-restore + + - name: test + run: dotnet test --configuration Release --no-build + + - name: test results + uses: EnricoMi/publish-unit-test-result-action@v2 + id: test-results + if: always() + with: + check_name: tests results + trx_files: "**/test-results/**/*.trx" diff --git a/Directory.Build.props b/Directory.Build.props index 1ce6b889..7e39fc69 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,30 +1,30 @@ - - - - (c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved. - 9.0.1 - Pawel Gerr - true - https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore - icon.png - LICENSE.md - git - https://pawelgerr@dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_git/Thinktecture.EntityFrameworkCore - false - Thinktecture - net8.0 - 13.0 - enable - $(NoWarn);CA1303;MSB3884; - enable - true - - trx%3bLogFileName=$(MSBuildProjectName).trx - $(MSBuildThisFileDirectory)test-results/$(TargetFramework) - - - - - - - + + + + (c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved. + 9.0.1 + Pawel Gerr + true + https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore + icon.png + LICENSE.md + git + https://pawelgerr@dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_git/Thinktecture.EntityFrameworkCore + false + Thinktecture + net8.0 + 13.0 + enable + $(NoWarn);CA1303;MSB3884; + enable + true + + trx%3bLogFileName=$(MSBuildProjectName).trx + $(MSBuildThisFileDirectory)test-results/$(TargetFramework) + + + + + + + diff --git a/Readme.md b/Readme.md index 6108816d..71e410b7 100644 --- a/Readme.md +++ b/Readme.md @@ -1,46 +1,46 @@ -# Thinktecture.EntityFrameworkCore - -[![Build Status](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_apis/build/status/Thinktecture.EntityFrameworkCore/Thinktecture.EntityFrameworkCore%20CI?branchName=master)](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_build/latest?definitionId=4&branchName=master) -![NuGet Downloads](https://img.shields.io/nuget/dt/Thinktecture.EntityFrameworkCore.Relational) - - -[![Thinktecture.EntityFrameworkCore.Relational](https://img.shields.io/nuget/vpre/Thinktecture.EntityFrameworkCore.Relational.svg?label=Thinktecture.EntityFrameworkCore.Relational&maxAge=3600)](https://www.nuget.org/packages/Thinktecture.EntityFrameworkCore.Relational/) -[![Thinktecture.EntityFrameworkCore.SqlServer](https://img.shields.io/nuget/vpre/Thinktecture.EntityFrameworkCore.SqlServer.svg?label=Thinktecture.EntityFrameworkCore.SqlServer&maxAge=3600)](https://www.nuget.org/packages/Thinktecture.EntityFrameworkCore.SqlServer/) -[![Thinktecture.EntityFrameworkCore.SqlServer.Testing](https://img.shields.io/nuget/vpre/Thinktecture.EntityFrameworkCore.SqlServer.Testing.svg?label=Thinktecture.EntityFrameworkCore.SqlServer.Testing&maxAge=3600)](https://www.nuget.org/packages/Thinktecture.EntityFrameworkCore.SqlServer.Testing/) -[![Thinktecture.EntityFrameworkCore.Sqlite](https://img.shields.io/nuget/vpre/Thinktecture.EntityFrameworkCore.Sqlite.svg?label=Thinktecture.EntityFrameworkCore.Sqlite&maxAge=3600)](https://www.nuget.org/packages/Thinktecture.EntityFrameworkCore.Sqlite/) -[![Thinktecture.EntityFrameworkCore.Sqlite.Testing](https://img.shields.io/nuget/vpre/Thinktecture.EntityFrameworkCore.Sqlite.Testing.svg?label=Thinktecture.EntityFrameworkCore.Sqlite.Testing&maxAge=3600)](https://www.nuget.org/packages/Thinktecture.EntityFrameworkCore.Sqlite.Testing/) - -These libraries extend [Entity Framework Core](https://docs.microsoft.com/en-us/ef/core/) by a few features to make it easier to work with EF and for easier integration testing or to get more performance in some special cases. - -The code and the documentation can be found on [Thinktecture.EntityFrameworkCore](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore) - -Use the [repo on GitHub](https://github.com/PawelGerr/Thinktecture.EntityFrameworkCore) to create issues and feature requests. - -## Performance -* [Temp-Tables](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/2/Temp-Tables) -* [Bulk-Insert](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/65/Bulk-Insert) -* [Bulk-Update](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/67/Bulk-Update) -* [Bulk-Upsert (Insert-or-Update)](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/69/Bulk-Upsert-(Insert-or-Update)) -* [Truncate Tables](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/64/Truncate-Tables) - -## Features -* [Collection Parameters (temp-tables *light*)](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/109/Collection-Parameters-(temp-tables-light)) (SQL Server) -* [Window Functions Support (RowNumber, Sum, Average, Min, Max)](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki?wikiVersion=GBwikiMaster&pagePath=/Features/Window%20Functions%20Support%20(RowNumber%2C%20Sum%2C%20Average%2C%20Min%2C%20Max)&pageId=14&_a=edit) -* [Nested (virtual) Transactions](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/40/Nested-(virtual)-Transactions) -* [Table Hints](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/71/Table-Hints-(SQL-Server)) (SQL Server) -* [Queries accross multiple databases](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/43/Queries-accross-multiple-databases) (SQL Server) -* [Changing default schema at runtime](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/6/Changing-default-schema-at-runtime) -* [If-Exists / If-Not-Exists checks in migrations](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/7/If-(Not-)Exists-checks-in-migrations) (SQL Server) - -## Convenience -* [Extension method LeftJoin](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/4/Extension-method-LeftJoin) -* [Migrations: include-columns](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/9/Migrations-Include-columns) (SQL Server) -* [Migrations: identity column](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/28/Migrations-Identity-column) (SQL Server) -* [Migrations: (non-)clustered PK](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/29/Migrations-(Non-)Clustered-PK) (SQL Server) - -## Integration Testing -* [Isolation of tests](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/12/Isolation-of-tests) (SQL Server, SQLite) - -## Extensibility -* [Adding custom IRelationalTypeMappingSourcePlugin](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/26/Adding-custom-IRelationalTypeMappingSourcePlugin) -* [Adding custom IEvaluatableExpressionFilter](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/31/Adding-custom-IEvaluatableExpressionFilter) +# Thinktecture.EntityFrameworkCore + +[![Build Status](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_apis/build/status/Thinktecture.EntityFrameworkCore/Thinktecture.EntityFrameworkCore%20CI?branchName=master)](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_build/latest?definitionId=4&branchName=master) +![NuGet Downloads](https://img.shields.io/nuget/dt/Thinktecture.EntityFrameworkCore.Relational) + + +[![Thinktecture.EntityFrameworkCore.Relational](https://img.shields.io/nuget/vpre/Thinktecture.EntityFrameworkCore.Relational.svg?label=Thinktecture.EntityFrameworkCore.Relational&maxAge=3600)](https://www.nuget.org/packages/Thinktecture.EntityFrameworkCore.Relational/) +[![Thinktecture.EntityFrameworkCore.SqlServer](https://img.shields.io/nuget/vpre/Thinktecture.EntityFrameworkCore.SqlServer.svg?label=Thinktecture.EntityFrameworkCore.SqlServer&maxAge=3600)](https://www.nuget.org/packages/Thinktecture.EntityFrameworkCore.SqlServer/) +[![Thinktecture.EntityFrameworkCore.SqlServer.Testing](https://img.shields.io/nuget/vpre/Thinktecture.EntityFrameworkCore.SqlServer.Testing.svg?label=Thinktecture.EntityFrameworkCore.SqlServer.Testing&maxAge=3600)](https://www.nuget.org/packages/Thinktecture.EntityFrameworkCore.SqlServer.Testing/) +[![Thinktecture.EntityFrameworkCore.Sqlite](https://img.shields.io/nuget/vpre/Thinktecture.EntityFrameworkCore.Sqlite.svg?label=Thinktecture.EntityFrameworkCore.Sqlite&maxAge=3600)](https://www.nuget.org/packages/Thinktecture.EntityFrameworkCore.Sqlite/) +[![Thinktecture.EntityFrameworkCore.Sqlite.Testing](https://img.shields.io/nuget/vpre/Thinktecture.EntityFrameworkCore.Sqlite.Testing.svg?label=Thinktecture.EntityFrameworkCore.Sqlite.Testing&maxAge=3600)](https://www.nuget.org/packages/Thinktecture.EntityFrameworkCore.Sqlite.Testing/) + +These libraries extend [Entity Framework Core](https://docs.microsoft.com/en-us/ef/core/) by a few features to make it easier to work with EF and for easier integration testing or to get more performance in some special cases. + +The code and the documentation can be found on [Thinktecture.EntityFrameworkCore](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore) + +Use the [repo on GitHub](https://github.com/PawelGerr/Thinktecture.EntityFrameworkCore) to create issues and feature requests. + +## Performance +* [Temp-Tables](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/2/Temp-Tables) +* [Bulk-Insert](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/65/Bulk-Insert) +* [Bulk-Update](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/67/Bulk-Update) +* [Bulk-Upsert (Insert-or-Update)](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/69/Bulk-Upsert-(Insert-or-Update)) +* [Truncate Tables](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/64/Truncate-Tables) + +## Features +* [Collection Parameters (temp-tables *light*)](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/109/Collection-Parameters-(temp-tables-light)) (SQL Server) +* [Window Functions Support (RowNumber, Sum, Average, Min, Max)](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki?wikiVersion=GBwikiMaster&pagePath=/Features/Window%20Functions%20Support%20(RowNumber%2C%20Sum%2C%20Average%2C%20Min%2C%20Max)&pageId=14&_a=edit) +* [Nested (virtual) Transactions](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/40/Nested-(virtual)-Transactions) +* [Table Hints](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/71/Table-Hints-(SQL-Server)) (SQL Server) +* [Queries accross multiple databases](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/43/Queries-accross-multiple-databases) (SQL Server) +* [Changing default schema at runtime](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/6/Changing-default-schema-at-runtime) +* [If-Exists / If-Not-Exists checks in migrations](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/7/If-(Not-)Exists-checks-in-migrations) (SQL Server) + +## Convenience +* [Extension method LeftJoin](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/4/Extension-method-LeftJoin) +* [Migrations: include-columns](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/9/Migrations-Include-columns) (SQL Server) +* [Migrations: identity column](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/28/Migrations-Identity-column) (SQL Server) +* [Migrations: (non-)clustered PK](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/29/Migrations-(Non-)Clustered-PK) (SQL Server) + +## Integration Testing +* [Isolation of tests](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/12/Isolation-of-tests) (SQL Server, SQLite) + +## Extensibility +* [Adding custom IRelationalTypeMappingSourcePlugin](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/26/Adding-custom-IRelationalTypeMappingSourcePlugin) +* [Adding custom IEvaluatableExpressionFilter](https://dev.azure.com/pawelgerr/Thinktecture.EntityFrameworkCore/_wiki/wikis/Thinktecture.EntityFrameworkCore.wiki/31/Adding-custom-IEvaluatableExpressionFilter) diff --git a/Thinktecture.EntityFrameworkCore.sln b/Thinktecture.EntityFrameworkCore.sln index 67da4f28..6993774d 100644 --- a/Thinktecture.EntityFrameworkCore.sln +++ b/Thinktecture.EntityFrameworkCore.sln @@ -1,153 +1,155 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{564262E5-94E5-4B32-8B0E-444D454DD35F}" -ProjectSection(SolutionItems) = preProject - src\Directory.Build.props = src\Directory.Build.props -EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FC71B400-ACDA-4BBE-BCC1-D1CFB4EDEFE3}" -ProjectSection(SolutionItems) = preProject - tests\Directory.Build.props = tests\Directory.Build.props -EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{30567A9D-BDC2-49C1-B0B4-91D37FB5CCC1}" - ProjectSection(SolutionItems) = preProject - samples\Directory.Build.props = samples\Directory.Build.props - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Relational", "src\Thinktecture.EntityFrameworkCore.Relational\Thinktecture.EntityFrameworkCore.Relational.csproj", "{B43A04CC-FB33-4B06-AC9B-BBDE177EF0E4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.SqlServer", "src\Thinktecture.EntityFrameworkCore.SqlServer\Thinktecture.EntityFrameworkCore.SqlServer.csproj", "{FCB9BC17-69FE-4CB1-A8E8-665316B68B30}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{2CFD0F6C-599B-41DA-A754-03A202B68D12}" -ProjectSection(SolutionItems) = preProject - ci\ci.ps1 = ci\ci.ps1 - azure-pipelines.yml = azure-pipelines.yml - LICENSE.md = LICENSE.md - README.md = README.md - Directory.Build.props = Directory.Build.props - .gitignore = .gitignore - icon.png = icon.png - .github\workflows\main.yml = .github\workflows\main.yml - global.json = global.json - Directory.Packages.props = Directory.Packages.props -EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.SqlServer.Testing", "src\Thinktecture.EntityFrameworkCore.SqlServer.Testing\Thinktecture.EntityFrameworkCore.SqlServer.Testing.csproj", "{4A68E5B4-B0C9-4076-875A-C88AFAC5EB5F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Relational.Tests", "tests\Thinktecture.EntityFrameworkCore.Relational.Tests\Thinktecture.EntityFrameworkCore.Relational.Tests.csproj", "{629B6159-5050-4510-A24A-51630CD46854}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.SqlServer.Tests", "tests\Thinktecture.EntityFrameworkCore.SqlServer.Tests\Thinktecture.EntityFrameworkCore.SqlServer.Tests.csproj", "{B6A31BA8-19A3-4563-BD4E-313C950A012F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.SqlServer.Samples", "samples\Thinktecture.EntityFrameworkCore.SqlServer.Samples\Thinktecture.EntityFrameworkCore.SqlServer.Samples.csproj", "{D2A55DD1-3642-4355-9E36-599238C80C14}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Sqlite", "src\Thinktecture.EntityFrameworkCore.Sqlite\Thinktecture.EntityFrameworkCore.Sqlite.csproj", "{5734E627-253B-4A5A-9AF9-937C9CFCE3D6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.BulkOperations", "src\Thinktecture.EntityFrameworkCore.BulkOperations\Thinktecture.EntityFrameworkCore.BulkOperations.csproj", "{441D3FD0-CACF-45E5-A15B-A59FCACB94C5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Sqlite.Samples", "samples\Thinktecture.EntityFrameworkCore.Sqlite.Samples\Thinktecture.EntityFrameworkCore.Sqlite.Samples.csproj", "{F273CC1E-A91E-4166-9810-2F56106512F8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Sqlite.Tests", "tests\Thinktecture.EntityFrameworkCore.Sqlite.Tests\Thinktecture.EntityFrameworkCore.Sqlite.Tests.csproj", "{392C12CE-E21E-43C2-9718-964EAE5F2AA0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Testing", "src\Thinktecture.EntityFrameworkCore.Testing\Thinktecture.EntityFrameworkCore.Testing.csproj", "{BEF60B0C-F3D0-4FB3-B975-63AA20A5AC59}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Sqlite.Testing", "src\Thinktecture.EntityFrameworkCore.Sqlite.Testing\Thinktecture.EntityFrameworkCore.Sqlite.Testing.csproj", "{C553EA83-3793-4B28-99BB-66A148564746}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Testing.Tests", "tests\Thinktecture.EntityFrameworkCore.Testing.Tests\Thinktecture.EntityFrameworkCore.Testing.Tests.csproj", "{E2A920F7-ED3D-4C73-8207-8C578A9B8AD8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.BulkOperations.Tests", "tests\Thinktecture.EntityFrameworkCore.BulkOperations.Tests\Thinktecture.EntityFrameworkCore.BulkOperations.Tests.csproj", "{8B96D351-8A49-41C2-ABC8-5F9DC047C774}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.TestHelpers", "tests\Thinktecture.EntityFrameworkCore.TestHelpers\Thinktecture.EntityFrameworkCore.TestHelpers.csproj", "{5A64640B-7EF4-43EE-BC65-09960A0B52E1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Benchmarks", "samples\Thinktecture.EntityFrameworkCore.Benchmarks\Thinktecture.EntityFrameworkCore.Benchmarks.csproj", "{A279120C-4417-4C8B-9BEA-5C505DDFFB8D}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {B43A04CC-FB33-4B06-AC9B-BBDE177EF0E4} = {564262E5-94E5-4B32-8B0E-444D454DD35F} - {FCB9BC17-69FE-4CB1-A8E8-665316B68B30} = {564262E5-94E5-4B32-8B0E-444D454DD35F} - {4A68E5B4-B0C9-4076-875A-C88AFAC5EB5F} = {564262E5-94E5-4B32-8B0E-444D454DD35F} - {629B6159-5050-4510-A24A-51630CD46854} = {FC71B400-ACDA-4BBE-BCC1-D1CFB4EDEFE3} - {B6A31BA8-19A3-4563-BD4E-313C950A012F} = {FC71B400-ACDA-4BBE-BCC1-D1CFB4EDEFE3} - {D2A55DD1-3642-4355-9E36-599238C80C14} = {30567A9D-BDC2-49C1-B0B4-91D37FB5CCC1} - {5734E627-253B-4A5A-9AF9-937C9CFCE3D6} = {564262E5-94E5-4B32-8B0E-444D454DD35F} - {441D3FD0-CACF-45E5-A15B-A59FCACB94C5} = {564262E5-94E5-4B32-8B0E-444D454DD35F} - {F273CC1E-A91E-4166-9810-2F56106512F8} = {30567A9D-BDC2-49C1-B0B4-91D37FB5CCC1} - {392C12CE-E21E-43C2-9718-964EAE5F2AA0} = {FC71B400-ACDA-4BBE-BCC1-D1CFB4EDEFE3} - {BEF60B0C-F3D0-4FB3-B975-63AA20A5AC59} = {564262E5-94E5-4B32-8B0E-444D454DD35F} - {C553EA83-3793-4B28-99BB-66A148564746} = {564262E5-94E5-4B32-8B0E-444D454DD35F} - {E2A920F7-ED3D-4C73-8207-8C578A9B8AD8} = {FC71B400-ACDA-4BBE-BCC1-D1CFB4EDEFE3} - {8B96D351-8A49-41C2-ABC8-5F9DC047C774} = {FC71B400-ACDA-4BBE-BCC1-D1CFB4EDEFE3} - {5A64640B-7EF4-43EE-BC65-09960A0B52E1} = {FC71B400-ACDA-4BBE-BCC1-D1CFB4EDEFE3} - {A279120C-4417-4C8B-9BEA-5C505DDFFB8D} = {30567A9D-BDC2-49C1-B0B4-91D37FB5CCC1} - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B43A04CC-FB33-4B06-AC9B-BBDE177EF0E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B43A04CC-FB33-4B06-AC9B-BBDE177EF0E4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B43A04CC-FB33-4B06-AC9B-BBDE177EF0E4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B43A04CC-FB33-4B06-AC9B-BBDE177EF0E4}.Release|Any CPU.Build.0 = Release|Any CPU - {FCB9BC17-69FE-4CB1-A8E8-665316B68B30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FCB9BC17-69FE-4CB1-A8E8-665316B68B30}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FCB9BC17-69FE-4CB1-A8E8-665316B68B30}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FCB9BC17-69FE-4CB1-A8E8-665316B68B30}.Release|Any CPU.Build.0 = Release|Any CPU - {4A68E5B4-B0C9-4076-875A-C88AFAC5EB5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A68E5B4-B0C9-4076-875A-C88AFAC5EB5F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A68E5B4-B0C9-4076-875A-C88AFAC5EB5F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A68E5B4-B0C9-4076-875A-C88AFAC5EB5F}.Release|Any CPU.Build.0 = Release|Any CPU - {629B6159-5050-4510-A24A-51630CD46854}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {629B6159-5050-4510-A24A-51630CD46854}.Debug|Any CPU.Build.0 = Debug|Any CPU - {629B6159-5050-4510-A24A-51630CD46854}.Release|Any CPU.ActiveCfg = Release|Any CPU - {629B6159-5050-4510-A24A-51630CD46854}.Release|Any CPU.Build.0 = Release|Any CPU - {B6A31BA8-19A3-4563-BD4E-313C950A012F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6A31BA8-19A3-4563-BD4E-313C950A012F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6A31BA8-19A3-4563-BD4E-313C950A012F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B6A31BA8-19A3-4563-BD4E-313C950A012F}.Release|Any CPU.Build.0 = Release|Any CPU - {D2A55DD1-3642-4355-9E36-599238C80C14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D2A55DD1-3642-4355-9E36-599238C80C14}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D2A55DD1-3642-4355-9E36-599238C80C14}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D2A55DD1-3642-4355-9E36-599238C80C14}.Release|Any CPU.Build.0 = Release|Any CPU - {5734E627-253B-4A5A-9AF9-937C9CFCE3D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5734E627-253B-4A5A-9AF9-937C9CFCE3D6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5734E627-253B-4A5A-9AF9-937C9CFCE3D6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5734E627-253B-4A5A-9AF9-937C9CFCE3D6}.Release|Any CPU.Build.0 = Release|Any CPU - {441D3FD0-CACF-45E5-A15B-A59FCACB94C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {441D3FD0-CACF-45E5-A15B-A59FCACB94C5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {441D3FD0-CACF-45E5-A15B-A59FCACB94C5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {441D3FD0-CACF-45E5-A15B-A59FCACB94C5}.Release|Any CPU.Build.0 = Release|Any CPU - {F273CC1E-A91E-4166-9810-2F56106512F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F273CC1E-A91E-4166-9810-2F56106512F8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F273CC1E-A91E-4166-9810-2F56106512F8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F273CC1E-A91E-4166-9810-2F56106512F8}.Release|Any CPU.Build.0 = Release|Any CPU - {392C12CE-E21E-43C2-9718-964EAE5F2AA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {392C12CE-E21E-43C2-9718-964EAE5F2AA0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {392C12CE-E21E-43C2-9718-964EAE5F2AA0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {392C12CE-E21E-43C2-9718-964EAE5F2AA0}.Release|Any CPU.Build.0 = Release|Any CPU - {BEF60B0C-F3D0-4FB3-B975-63AA20A5AC59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BEF60B0C-F3D0-4FB3-B975-63AA20A5AC59}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BEF60B0C-F3D0-4FB3-B975-63AA20A5AC59}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BEF60B0C-F3D0-4FB3-B975-63AA20A5AC59}.Release|Any CPU.Build.0 = Release|Any CPU - {C553EA83-3793-4B28-99BB-66A148564746}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C553EA83-3793-4B28-99BB-66A148564746}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C553EA83-3793-4B28-99BB-66A148564746}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C553EA83-3793-4B28-99BB-66A148564746}.Release|Any CPU.Build.0 = Release|Any CPU - {E2A920F7-ED3D-4C73-8207-8C578A9B8AD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E2A920F7-ED3D-4C73-8207-8C578A9B8AD8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E2A920F7-ED3D-4C73-8207-8C578A9B8AD8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E2A920F7-ED3D-4C73-8207-8C578A9B8AD8}.Release|Any CPU.Build.0 = Release|Any CPU - {8B96D351-8A49-41C2-ABC8-5F9DC047C774}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8B96D351-8A49-41C2-ABC8-5F9DC047C774}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B96D351-8A49-41C2-ABC8-5F9DC047C774}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8B96D351-8A49-41C2-ABC8-5F9DC047C774}.Release|Any CPU.Build.0 = Release|Any CPU - {5A64640B-7EF4-43EE-BC65-09960A0B52E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5A64640B-7EF4-43EE-BC65-09960A0B52E1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5A64640B-7EF4-43EE-BC65-09960A0B52E1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5A64640B-7EF4-43EE-BC65-09960A0B52E1}.Release|Any CPU.Build.0 = Release|Any CPU - {A279120C-4417-4C8B-9BEA-5C505DDFFB8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A279120C-4417-4C8B-9BEA-5C505DDFFB8D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A279120C-4417-4C8B-9BEA-5C505DDFFB8D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A279120C-4417-4C8B-9BEA-5C505DDFFB8D}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{564262E5-94E5-4B32-8B0E-444D454DD35F}" +ProjectSection(SolutionItems) = preProject + src\Directory.Build.props = src\Directory.Build.props +EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FC71B400-ACDA-4BBE-BCC1-D1CFB4EDEFE3}" +ProjectSection(SolutionItems) = preProject + tests\Directory.Build.props = tests\Directory.Build.props +EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{30567A9D-BDC2-49C1-B0B4-91D37FB5CCC1}" + ProjectSection(SolutionItems) = preProject + samples\Directory.Build.props = samples\Directory.Build.props + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Relational", "src\Thinktecture.EntityFrameworkCore.Relational\Thinktecture.EntityFrameworkCore.Relational.csproj", "{B43A04CC-FB33-4B06-AC9B-BBDE177EF0E4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.SqlServer", "src\Thinktecture.EntityFrameworkCore.SqlServer\Thinktecture.EntityFrameworkCore.SqlServer.csproj", "{FCB9BC17-69FE-4CB1-A8E8-665316B68B30}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{2CFD0F6C-599B-41DA-A754-03A202B68D12}" +ProjectSection(SolutionItems) = preProject + ci\ci.ps1 = ci\ci.ps1 + azure-pipelines.yml = azure-pipelines.yml + LICENSE.md = LICENSE.md + README.md = README.md + Directory.Build.props = Directory.Build.props + .gitignore = .gitignore + icon.png = icon.png + .github\workflows\main.yml = .github\workflows\main.yml + global.json = global.json + Directory.Packages.props = Directory.Packages.props + .editorconfig = .editorconfig + .gitattributes = .gitattributes +EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.SqlServer.Testing", "src\Thinktecture.EntityFrameworkCore.SqlServer.Testing\Thinktecture.EntityFrameworkCore.SqlServer.Testing.csproj", "{4A68E5B4-B0C9-4076-875A-C88AFAC5EB5F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Relational.Tests", "tests\Thinktecture.EntityFrameworkCore.Relational.Tests\Thinktecture.EntityFrameworkCore.Relational.Tests.csproj", "{629B6159-5050-4510-A24A-51630CD46854}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.SqlServer.Tests", "tests\Thinktecture.EntityFrameworkCore.SqlServer.Tests\Thinktecture.EntityFrameworkCore.SqlServer.Tests.csproj", "{B6A31BA8-19A3-4563-BD4E-313C950A012F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.SqlServer.Samples", "samples\Thinktecture.EntityFrameworkCore.SqlServer.Samples\Thinktecture.EntityFrameworkCore.SqlServer.Samples.csproj", "{D2A55DD1-3642-4355-9E36-599238C80C14}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Sqlite", "src\Thinktecture.EntityFrameworkCore.Sqlite\Thinktecture.EntityFrameworkCore.Sqlite.csproj", "{5734E627-253B-4A5A-9AF9-937C9CFCE3D6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.BulkOperations", "src\Thinktecture.EntityFrameworkCore.BulkOperations\Thinktecture.EntityFrameworkCore.BulkOperations.csproj", "{441D3FD0-CACF-45E5-A15B-A59FCACB94C5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Sqlite.Samples", "samples\Thinktecture.EntityFrameworkCore.Sqlite.Samples\Thinktecture.EntityFrameworkCore.Sqlite.Samples.csproj", "{F273CC1E-A91E-4166-9810-2F56106512F8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Sqlite.Tests", "tests\Thinktecture.EntityFrameworkCore.Sqlite.Tests\Thinktecture.EntityFrameworkCore.Sqlite.Tests.csproj", "{392C12CE-E21E-43C2-9718-964EAE5F2AA0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Testing", "src\Thinktecture.EntityFrameworkCore.Testing\Thinktecture.EntityFrameworkCore.Testing.csproj", "{BEF60B0C-F3D0-4FB3-B975-63AA20A5AC59}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Sqlite.Testing", "src\Thinktecture.EntityFrameworkCore.Sqlite.Testing\Thinktecture.EntityFrameworkCore.Sqlite.Testing.csproj", "{C553EA83-3793-4B28-99BB-66A148564746}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Testing.Tests", "tests\Thinktecture.EntityFrameworkCore.Testing.Tests\Thinktecture.EntityFrameworkCore.Testing.Tests.csproj", "{E2A920F7-ED3D-4C73-8207-8C578A9B8AD8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.BulkOperations.Tests", "tests\Thinktecture.EntityFrameworkCore.BulkOperations.Tests\Thinktecture.EntityFrameworkCore.BulkOperations.Tests.csproj", "{8B96D351-8A49-41C2-ABC8-5F9DC047C774}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.TestHelpers", "tests\Thinktecture.EntityFrameworkCore.TestHelpers\Thinktecture.EntityFrameworkCore.TestHelpers.csproj", "{5A64640B-7EF4-43EE-BC65-09960A0B52E1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thinktecture.EntityFrameworkCore.Benchmarks", "samples\Thinktecture.EntityFrameworkCore.Benchmarks\Thinktecture.EntityFrameworkCore.Benchmarks.csproj", "{A279120C-4417-4C8B-9BEA-5C505DDFFB8D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {B43A04CC-FB33-4B06-AC9B-BBDE177EF0E4} = {564262E5-94E5-4B32-8B0E-444D454DD35F} + {FCB9BC17-69FE-4CB1-A8E8-665316B68B30} = {564262E5-94E5-4B32-8B0E-444D454DD35F} + {4A68E5B4-B0C9-4076-875A-C88AFAC5EB5F} = {564262E5-94E5-4B32-8B0E-444D454DD35F} + {629B6159-5050-4510-A24A-51630CD46854} = {FC71B400-ACDA-4BBE-BCC1-D1CFB4EDEFE3} + {B6A31BA8-19A3-4563-BD4E-313C950A012F} = {FC71B400-ACDA-4BBE-BCC1-D1CFB4EDEFE3} + {D2A55DD1-3642-4355-9E36-599238C80C14} = {30567A9D-BDC2-49C1-B0B4-91D37FB5CCC1} + {5734E627-253B-4A5A-9AF9-937C9CFCE3D6} = {564262E5-94E5-4B32-8B0E-444D454DD35F} + {441D3FD0-CACF-45E5-A15B-A59FCACB94C5} = {564262E5-94E5-4B32-8B0E-444D454DD35F} + {F273CC1E-A91E-4166-9810-2F56106512F8} = {30567A9D-BDC2-49C1-B0B4-91D37FB5CCC1} + {392C12CE-E21E-43C2-9718-964EAE5F2AA0} = {FC71B400-ACDA-4BBE-BCC1-D1CFB4EDEFE3} + {BEF60B0C-F3D0-4FB3-B975-63AA20A5AC59} = {564262E5-94E5-4B32-8B0E-444D454DD35F} + {C553EA83-3793-4B28-99BB-66A148564746} = {564262E5-94E5-4B32-8B0E-444D454DD35F} + {E2A920F7-ED3D-4C73-8207-8C578A9B8AD8} = {FC71B400-ACDA-4BBE-BCC1-D1CFB4EDEFE3} + {8B96D351-8A49-41C2-ABC8-5F9DC047C774} = {FC71B400-ACDA-4BBE-BCC1-D1CFB4EDEFE3} + {5A64640B-7EF4-43EE-BC65-09960A0B52E1} = {FC71B400-ACDA-4BBE-BCC1-D1CFB4EDEFE3} + {A279120C-4417-4C8B-9BEA-5C505DDFFB8D} = {30567A9D-BDC2-49C1-B0B4-91D37FB5CCC1} + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B43A04CC-FB33-4B06-AC9B-BBDE177EF0E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B43A04CC-FB33-4B06-AC9B-BBDE177EF0E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B43A04CC-FB33-4B06-AC9B-BBDE177EF0E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B43A04CC-FB33-4B06-AC9B-BBDE177EF0E4}.Release|Any CPU.Build.0 = Release|Any CPU + {FCB9BC17-69FE-4CB1-A8E8-665316B68B30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCB9BC17-69FE-4CB1-A8E8-665316B68B30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCB9BC17-69FE-4CB1-A8E8-665316B68B30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCB9BC17-69FE-4CB1-A8E8-665316B68B30}.Release|Any CPU.Build.0 = Release|Any CPU + {4A68E5B4-B0C9-4076-875A-C88AFAC5EB5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A68E5B4-B0C9-4076-875A-C88AFAC5EB5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A68E5B4-B0C9-4076-875A-C88AFAC5EB5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A68E5B4-B0C9-4076-875A-C88AFAC5EB5F}.Release|Any CPU.Build.0 = Release|Any CPU + {629B6159-5050-4510-A24A-51630CD46854}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {629B6159-5050-4510-A24A-51630CD46854}.Debug|Any CPU.Build.0 = Debug|Any CPU + {629B6159-5050-4510-A24A-51630CD46854}.Release|Any CPU.ActiveCfg = Release|Any CPU + {629B6159-5050-4510-A24A-51630CD46854}.Release|Any CPU.Build.0 = Release|Any CPU + {B6A31BA8-19A3-4563-BD4E-313C950A012F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6A31BA8-19A3-4563-BD4E-313C950A012F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6A31BA8-19A3-4563-BD4E-313C950A012F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6A31BA8-19A3-4563-BD4E-313C950A012F}.Release|Any CPU.Build.0 = Release|Any CPU + {D2A55DD1-3642-4355-9E36-599238C80C14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D2A55DD1-3642-4355-9E36-599238C80C14}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D2A55DD1-3642-4355-9E36-599238C80C14}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D2A55DD1-3642-4355-9E36-599238C80C14}.Release|Any CPU.Build.0 = Release|Any CPU + {5734E627-253B-4A5A-9AF9-937C9CFCE3D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5734E627-253B-4A5A-9AF9-937C9CFCE3D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5734E627-253B-4A5A-9AF9-937C9CFCE3D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5734E627-253B-4A5A-9AF9-937C9CFCE3D6}.Release|Any CPU.Build.0 = Release|Any CPU + {441D3FD0-CACF-45E5-A15B-A59FCACB94C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {441D3FD0-CACF-45E5-A15B-A59FCACB94C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {441D3FD0-CACF-45E5-A15B-A59FCACB94C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {441D3FD0-CACF-45E5-A15B-A59FCACB94C5}.Release|Any CPU.Build.0 = Release|Any CPU + {F273CC1E-A91E-4166-9810-2F56106512F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F273CC1E-A91E-4166-9810-2F56106512F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F273CC1E-A91E-4166-9810-2F56106512F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F273CC1E-A91E-4166-9810-2F56106512F8}.Release|Any CPU.Build.0 = Release|Any CPU + {392C12CE-E21E-43C2-9718-964EAE5F2AA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {392C12CE-E21E-43C2-9718-964EAE5F2AA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {392C12CE-E21E-43C2-9718-964EAE5F2AA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {392C12CE-E21E-43C2-9718-964EAE5F2AA0}.Release|Any CPU.Build.0 = Release|Any CPU + {BEF60B0C-F3D0-4FB3-B975-63AA20A5AC59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BEF60B0C-F3D0-4FB3-B975-63AA20A5AC59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BEF60B0C-F3D0-4FB3-B975-63AA20A5AC59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BEF60B0C-F3D0-4FB3-B975-63AA20A5AC59}.Release|Any CPU.Build.0 = Release|Any CPU + {C553EA83-3793-4B28-99BB-66A148564746}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C553EA83-3793-4B28-99BB-66A148564746}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C553EA83-3793-4B28-99BB-66A148564746}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C553EA83-3793-4B28-99BB-66A148564746}.Release|Any CPU.Build.0 = Release|Any CPU + {E2A920F7-ED3D-4C73-8207-8C578A9B8AD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2A920F7-ED3D-4C73-8207-8C578A9B8AD8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2A920F7-ED3D-4C73-8207-8C578A9B8AD8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2A920F7-ED3D-4C73-8207-8C578A9B8AD8}.Release|Any CPU.Build.0 = Release|Any CPU + {8B96D351-8A49-41C2-ABC8-5F9DC047C774}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B96D351-8A49-41C2-ABC8-5F9DC047C774}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B96D351-8A49-41C2-ABC8-5F9DC047C774}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B96D351-8A49-41C2-ABC8-5F9DC047C774}.Release|Any CPU.Build.0 = Release|Any CPU + {5A64640B-7EF4-43EE-BC65-09960A0B52E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A64640B-7EF4-43EE-BC65-09960A0B52E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A64640B-7EF4-43EE-BC65-09960A0B52E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A64640B-7EF4-43EE-BC65-09960A0B52E1}.Release|Any CPU.Build.0 = Release|Any CPU + {A279120C-4417-4C8B-9BEA-5C505DDFFB8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A279120C-4417-4C8B-9BEA-5C505DDFFB8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A279120C-4417-4C8B-9BEA-5C505DDFFB8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A279120C-4417-4C8B-9BEA-5C505DDFFB8D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props index c36d432a..e53df4d3 100644 --- a/samples/Directory.Build.props +++ b/samples/Directory.Build.props @@ -1,14 +1,14 @@ - - - - $([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../')) - false - - - - - - net9.0 - - - + + + + $([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../')) + false + + + + + + net9.0 + + + diff --git a/samples/Thinktecture.EntityFrameworkCore.Benchmarks/Benchmarking/ReferenceEqualityValueComparer.cs b/samples/Thinktecture.EntityFrameworkCore.Benchmarks/Benchmarking/ReferenceEqualityValueComparer.cs index 169a2648..a01232a6 100644 --- a/samples/Thinktecture.EntityFrameworkCore.Benchmarks/Benchmarking/ReferenceEqualityValueComparer.cs +++ b/samples/Thinktecture.EntityFrameworkCore.Benchmarks/Benchmarking/ReferenceEqualityValueComparer.cs @@ -1,98 +1,98 @@ -using BenchmarkDotNet.Attributes; -using Microsoft.Extensions.DependencyInjection; -using Thinktecture.Database; - -namespace Thinktecture.Benchmarking; - -[MemoryDiagnoser] -public class ReferenceEqualityValueComparer : IDisposable -{ - private BenchmarkContext? _benchmarkContext; - private IServiceScope? _scope; - private SqlServerBenchmarkDbContext? _sqlServerDbContext; - - private const int _BYTES_LENGTH = 1024; - - private int _counter; - private readonly byte[] _bytesBestCase = new byte[_BYTES_LENGTH]; - private readonly byte[] _bytesWorstCase = new byte[_BYTES_LENGTH]; - - private List _entitiesWithDefaultComparer = null!; - private List _entitiesWithCustomComparer = null!; - - [GlobalSetup] - public void Initialize() - { - _benchmarkContext = new BenchmarkContext(); - _scope = _benchmarkContext.RootServiceProvider.CreateScope(); - _sqlServerDbContext = _scope.ServiceProvider.GetRequiredService(); - - _sqlServerDbContext.Database.EnsureDeleted(); - _sqlServerDbContext.Database.EnsureCreated(); - - _sqlServerDbContext.EntitiesWithByteArray.ExecuteDelete(); - _sqlServerDbContext.EntitiesWithByteArrayAndValueComparer.ExecuteDelete(); - - var bytes = new byte[_BYTES_LENGTH]; - - for (var i = 0; i < 10_000; i++) - { - var id = new Guid($"66AFED1B-92EA-4483-BF4F-{i.ToString("X").PadLeft(12, '0')}"); - - _sqlServerDbContext.EntitiesWithByteArray.Add(new EntityWithByteArray(id, bytes)); - _sqlServerDbContext.EntitiesWithByteArrayAndValueComparer.Add(new EntityWithByteArrayAndValueComparer(id, bytes)); - } - - _sqlServerDbContext.SaveChanges(); - _sqlServerDbContext.ChangeTracker.Clear(); - } - - [GlobalCleanup] - public void Dispose() - { - _scope?.Dispose(); - _benchmarkContext?.Dispose(); - } - - [IterationSetup] - public void IterationSetup() - { - _sqlServerDbContext!.ChangeTracker.Clear(); - _entitiesWithDefaultComparer = _sqlServerDbContext.EntitiesWithByteArray.ToList(); - _entitiesWithCustomComparer = _sqlServerDbContext.EntitiesWithByteArrayAndValueComparer.ToList(); - - _bytesBestCase[0] = _bytesWorstCase[^1] = (byte)(++_counter % Byte.MaxValue); - } - - [Benchmark] - public async Task Default_BestCase() - { - _entitiesWithDefaultComparer.ForEach(e => e.Bytes = _bytesBestCase); - - await _sqlServerDbContext!.SaveChangesAsync(); - } - - [Benchmark] - public async Task Default_WorstCase() - { - _entitiesWithDefaultComparer.ForEach(e => e.Bytes = _bytesWorstCase); - - await _sqlServerDbContext!.SaveChangesAsync(); - } - - [Benchmark] - public async Task ReferenceEquality_BestCase() - { - _entitiesWithCustomComparer.ForEach(e => e.Bytes = _bytesBestCase); - - await _sqlServerDbContext!.SaveChangesAsync(); - } - - [Benchmark] - public async Task ReferenceEquality_WorstCase() - { - _entitiesWithCustomComparer.ForEach(e => e.Bytes = _bytesWorstCase); - - await _sqlServerDbContext!.SaveChangesAsync(); - } -} +using BenchmarkDotNet.Attributes; +using Microsoft.Extensions.DependencyInjection; +using Thinktecture.Database; + +namespace Thinktecture.Benchmarking; + +[MemoryDiagnoser] +public class ReferenceEqualityValueComparer : IDisposable +{ + private BenchmarkContext? _benchmarkContext; + private IServiceScope? _scope; + private SqlServerBenchmarkDbContext? _sqlServerDbContext; + + private const int _BYTES_LENGTH = 1024; + + private int _counter; + private readonly byte[] _bytesBestCase = new byte[_BYTES_LENGTH]; + private readonly byte[] _bytesWorstCase = new byte[_BYTES_LENGTH]; + + private List _entitiesWithDefaultComparer = null!; + private List _entitiesWithCustomComparer = null!; + + [GlobalSetup] + public void Initialize() + { + _benchmarkContext = new BenchmarkContext(); + _scope = _benchmarkContext.RootServiceProvider.CreateScope(); + _sqlServerDbContext = _scope.ServiceProvider.GetRequiredService(); + + _sqlServerDbContext.Database.EnsureDeleted(); + _sqlServerDbContext.Database.EnsureCreated(); + + _sqlServerDbContext.EntitiesWithByteArray.ExecuteDelete(); + _sqlServerDbContext.EntitiesWithByteArrayAndValueComparer.ExecuteDelete(); + + var bytes = new byte[_BYTES_LENGTH]; + + for (var i = 0; i < 10_000; i++) + { + var id = new Guid($"66AFED1B-92EA-4483-BF4F-{i.ToString("X").PadLeft(12, '0')}"); + + _sqlServerDbContext.EntitiesWithByteArray.Add(new EntityWithByteArray(id, bytes)); + _sqlServerDbContext.EntitiesWithByteArrayAndValueComparer.Add(new EntityWithByteArrayAndValueComparer(id, bytes)); + } + + _sqlServerDbContext.SaveChanges(); + _sqlServerDbContext.ChangeTracker.Clear(); + } + + [GlobalCleanup] + public void Dispose() + { + _scope?.Dispose(); + _benchmarkContext?.Dispose(); + } + + [IterationSetup] + public void IterationSetup() + { + _sqlServerDbContext!.ChangeTracker.Clear(); + _entitiesWithDefaultComparer = _sqlServerDbContext.EntitiesWithByteArray.ToList(); + _entitiesWithCustomComparer = _sqlServerDbContext.EntitiesWithByteArrayAndValueComparer.ToList(); + + _bytesBestCase[0] = _bytesWorstCase[^1] = (byte)(++_counter % Byte.MaxValue); + } + + [Benchmark] + public async Task Default_BestCase() + { + _entitiesWithDefaultComparer.ForEach(e => e.Bytes = _bytesBestCase); + + await _sqlServerDbContext!.SaveChangesAsync(); + } + + [Benchmark] + public async Task Default_WorstCase() + { + _entitiesWithDefaultComparer.ForEach(e => e.Bytes = _bytesWorstCase); + + await _sqlServerDbContext!.SaveChangesAsync(); + } + + [Benchmark] + public async Task ReferenceEquality_BestCase() + { + _entitiesWithCustomComparer.ForEach(e => e.Bytes = _bytesBestCase); + + await _sqlServerDbContext!.SaveChangesAsync(); + } + + [Benchmark] + public async Task ReferenceEquality_WorstCase() + { + _entitiesWithCustomComparer.ForEach(e => e.Bytes = _bytesWorstCase); + + await _sqlServerDbContext!.SaveChangesAsync(); + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/CurrentTenant.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/CurrentTenant.cs index 69745358..9952cfb1 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/CurrentTenant.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/CurrentTenant.cs @@ -1,6 +1,6 @@ -namespace Thinktecture; - -public static class CurrentTenant -{ - public static string? Value { get; set; } +namespace Thinktecture; + +public static class CurrentTenant +{ + public static string? Value { get; set; } } \ No newline at end of file diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/Customer.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/Customer.cs index 237ba3f0..92b6f5dc 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/Customer.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/Customer.cs @@ -1,36 +1,36 @@ -namespace Thinktecture.Database; - -public class Customer -{ - public Guid Id { get; private set; } - public string FirstName { get; private set; } - public string LastName { get; private set; } - public long RowVersion { get; private set; } - - private List? _orders; - - // ReSharper disable once UnusedMember.Global - public List Orders - { - get => _orders ??= new List(); - set => _orders = value; - } - -#nullable disable - private Customer() - { - } -#nullable enable - - public Customer(Guid id, string firstName, string lastName) - { - Id = id; - FirstName = firstName; - LastName = lastName; - } - - public override string ToString() - { - return $"{{ CustomerId='{Id}', FirstName='{FirstName}', LastName='{LastName}', RowVersion={RowVersion} }}"; - } -} +namespace Thinktecture.Database; + +public class Customer +{ + public Guid Id { get; private set; } + public string FirstName { get; private set; } + public string LastName { get; private set; } + public long RowVersion { get; private set; } + + private List? _orders; + + // ReSharper disable once UnusedMember.Global + public List Orders + { + get => _orders ??= new List(); + set => _orders = value; + } + +#nullable disable + private Customer() + { + } +#nullable enable + + public Customer(Guid id, string firstName, string lastName) + { + Id = id; + FirstName = firstName; + LastName = lastName; + } + + public override string ToString() + { + return $"{{ CustomerId='{Id}', FirstName='{FirstName}', LastName='{LastName}', RowVersion={RowVersion} }}"; + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/DemoDbContext.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/DemoDbContext.cs index 99d83f30..c7316735 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/DemoDbContext.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/DemoDbContext.cs @@ -1,53 +1,53 @@ -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.EntityFrameworkCore; - -namespace Thinktecture.Database; - -public class DemoDbContext : DbContext, IDbDefaultSchema -{ - /// - public string? Schema { get; } - -#nullable disable - public DbSet Customers { get; set; } - public DbSet Products { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } -#nullable enable - - public DemoDbContext(DbContextOptions options, IDbDefaultSchema? schema = null) - : base(options) - { - Schema = schema?.Schema; - } - - /// - protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) - { - configurationBuilder.Properties(builder => builder - .HavePrecision(18, 5)); - } - - /// - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.ConfigureTempTable(); - modelBuilder.ConfigureTempTable(); - - modelBuilder.ConfigureComplexCollectionParameter(); - - modelBuilder.Entity(builder => - { - builder.Property(c => c.FirstName).HasMaxLength(100); - builder.Property(c => c.LastName).HasMaxLength(100); - - builder.Property(c => c.RowVersion) - .IsRowVersion() - .HasConversion(new NumberToBytesConverter()); - }); - - modelBuilder.Entity().HasKey(i => new { i.OrderId, i.ProductId }); - } -} +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.EntityFrameworkCore; + +namespace Thinktecture.Database; + +public class DemoDbContext : DbContext, IDbDefaultSchema +{ + /// + public string? Schema { get; } + +#nullable disable + public DbSet Customers { get; set; } + public DbSet Products { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } +#nullable enable + + public DemoDbContext(DbContextOptions options, IDbDefaultSchema? schema = null) + : base(options) + { + Schema = schema?.Schema; + } + + /// + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + configurationBuilder.Properties(builder => builder + .HavePrecision(18, 5)); + } + + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ConfigureTempTable(); + modelBuilder.ConfigureTempTable(); + + modelBuilder.ConfigureComplexCollectionParameter(); + + modelBuilder.Entity(builder => + { + builder.Property(c => c.FirstName).HasMaxLength(100); + builder.Property(c => c.LastName).HasMaxLength(100); + + builder.Property(c => c.RowVersion) + .IsRowVersion() + .HasConversion(new NumberToBytesConverter()); + }); + + modelBuilder.Entity().HasKey(i => new { i.OrderId, i.ProductId }); + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/DemoDbContextDesignTimeDbContextFactory.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/DemoDbContextDesignTimeDbContextFactory.cs index 517ffa53..d993b407 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/DemoDbContextDesignTimeDbContextFactory.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/DemoDbContextDesignTimeDbContextFactory.cs @@ -1,16 +1,16 @@ -using Microsoft.EntityFrameworkCore.Design; - -namespace Thinktecture.Database; - -public class DemoDbContextDesignTimeDbContextFactory : IDesignTimeDbContextFactory -{ - public DemoDbContext CreateDbContext(string[] args) - { - var options = new DbContextOptionsBuilder() - .UseSqlServer(SamplesContext.Instance.ConnectionString) - .AddSchemaRespectingComponents() - .Options; - - return new DemoDbContext(options); - } +using Microsoft.EntityFrameworkCore.Design; + +namespace Thinktecture.Database; + +public class DemoDbContextDesignTimeDbContextFactory : IDesignTimeDbContextFactory +{ + public DemoDbContext CreateDbContext(string[] args) + { + var options = new DbContextOptionsBuilder() + .UseSqlServer(SamplesContext.Instance.ConnectionString) + .AddSchemaRespectingComponents() + .Options; + + return new DemoDbContext(options); + } } \ No newline at end of file diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/Order.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/Order.cs index 47d9d730..6f5f3ecd 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/Order.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/Order.cs @@ -1,22 +1,22 @@ -namespace Thinktecture.Database; - -public class Order -{ - public Guid Id { get; set; } - public DateTime Date { get; set; } - public string? Text { get; set; } - public Guid CustomerId { get; set; } - -#nullable disable - public Customer Customer { get; set; } -#nullable enable - - private List? _orderItems; - - // ReSharper disable once UnusedMember.Global - public List OrderItems - { - get => _orderItems ??= new List(); - set => _orderItems = value; - } -} +namespace Thinktecture.Database; + +public class Order +{ + public Guid Id { get; set; } + public DateTime Date { get; set; } + public string? Text { get; set; } + public Guid CustomerId { get; set; } + +#nullable disable + public Customer Customer { get; set; } +#nullable enable + + private List? _orderItems; + + // ReSharper disable once UnusedMember.Global + public List OrderItems + { + get => _orderItems ??= new List(); + set => _orderItems = value; + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/OrderItem.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/OrderItem.cs index a368d6b0..6d103bb4 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/OrderItem.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/OrderItem.cs @@ -1,13 +1,13 @@ -namespace Thinktecture.Database; - -public class OrderItem -{ - public Guid OrderId { get; set; } - public Guid ProductId { get; set; } - public int Count { get; set; } - -#nullable disable - public Order Order { get; set; } - public Product Product { get; set; } -#nullable enable -} +namespace Thinktecture.Database; + +public class OrderItem +{ + public Guid OrderId { get; set; } + public Guid ProductId { get; set; } + public int Count { get; set; } + +#nullable disable + public Order Order { get; set; } + public Product Product { get; set; } +#nullable enable +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/Product.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/Product.cs index e496d9f8..f7a2ef82 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/Product.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Database/Product.cs @@ -1,15 +1,15 @@ -namespace Thinktecture.Database; - -public class Product -{ - public Guid Id { get; set; } - - private List? _orderItems; - - // ReSharper disable once UnusedMember.Global - public List OrderItems - { - get => _orderItems ??= new List(); - set => _orderItems = value; - } -} +namespace Thinktecture.Database; + +public class Product +{ + public Guid Id { get; set; } + + private List? _orderItems; + + // ReSharper disable once UnusedMember.Global + public List OrderItems + { + get => _orderItems ??= new List(); + set => _orderItems = value; + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/DemoTenantDatabaseProvider.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/DemoTenantDatabaseProvider.cs index e95c78ce..c73bb14c 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/DemoTenantDatabaseProvider.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/DemoTenantDatabaseProvider.cs @@ -1,25 +1,25 @@ -using Thinktecture.EntityFrameworkCore.Query; - -namespace Thinktecture; - -public class DemoTenantDatabaseProvider : ITenantDatabaseProvider -{ - public string? Tenant { get; } - - public DemoTenantDatabaseProvider(string? tenant) - { - Tenant = tenant; - } - - /// - public string? GetDatabaseName(string? schema, string table) - { - if (Tenant == "1") - return "demo"; - - if (Tenant == "2") - return "demo2"; - - return null; - } -} +using Thinktecture.EntityFrameworkCore.Query; + +namespace Thinktecture; + +public class DemoTenantDatabaseProvider : ITenantDatabaseProvider +{ + public string? Tenant { get; } + + public DemoTenantDatabaseProvider(string? tenant) + { + Tenant = tenant; + } + + /// + public string? GetDatabaseName(string? schema, string table) + { + if (Tenant == "1") + return "demo"; + + if (Tenant == "2") + return "demo2"; + + return null; + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/DemoTenantDatabaseProviderFactory.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/DemoTenantDatabaseProviderFactory.cs index 6b1ea75d..f9d3a4b0 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/DemoTenantDatabaseProviderFactory.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/DemoTenantDatabaseProviderFactory.cs @@ -1,12 +1,12 @@ -using Thinktecture.EntityFrameworkCore.Query; - -namespace Thinktecture; - -public class DemoTenantDatabaseProviderFactory : ITenantDatabaseProviderFactory -{ - /// - public ITenantDatabaseProvider Create() - { - return new DemoTenantDatabaseProvider(CurrentTenant.Value); - } +using Thinktecture.EntityFrameworkCore.Query; + +namespace Thinktecture; + +public class DemoTenantDatabaseProviderFactory : ITenantDatabaseProviderFactory +{ + /// + public ITenantDatabaseProvider Create() + { + return new DemoTenantDatabaseProvider(CurrentTenant.Value); + } } \ No newline at end of file diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Extensions/DemoDbContextExtensions.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Extensions/DemoDbContextExtensions.cs index 663c0fb3..cdfe08b1 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Extensions/DemoDbContextExtensions.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Extensions/DemoDbContextExtensions.cs @@ -1,62 +1,62 @@ -using Thinktecture.Database; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -public static class DemoDbContextExtensions -{ - public static async Task EnsureCustomerAsync(this DemoDbContext ctx, Guid id) - { - ArgumentNullException.ThrowIfNull(ctx); - - if (!await ctx.Customers.AnyAsync(c => c.Id == id)) - { - ctx.Customers.Add(new Customer(id, $"First name of '{id}'", $"Last name of '{id}'")); - await ctx.SaveChangesAsync(); - } - - return id; - } - - public static async Task EnsureProductAsync(this DemoDbContext ctx, Guid id) - { - ArgumentNullException.ThrowIfNull(ctx); - - if (!await ctx.Products.AnyAsync(c => c.Id == id)) - { - ctx.Products.Add(new Product { Id = id }); - await ctx.SaveChangesAsync(); - } - - return id; - } - - public static async Task EnsureOrderAsync(this DemoDbContext ctx, Guid id, Guid customerId) - { - ArgumentNullException.ThrowIfNull(ctx); - - if (!await ctx.Orders.AnyAsync(c => c.Id == id)) - { - ctx.Orders.Add(new Order { Id = id, CustomerId = customerId }); - await ctx.SaveChangesAsync(); - } - - return id; - } - - public static async Task EnsureOrderItemAsync(this DemoDbContext ctx, Guid orderId, Guid productId, int count) - { - ArgumentNullException.ThrowIfNull(ctx); - - var orderItem = await ctx.OrderItems.FirstOrDefaultAsync(c => c.OrderId == orderId && c.ProductId == productId); - - if (orderItem == null) - { - orderItem = new OrderItem { OrderId = orderId, ProductId = productId }; - ctx.OrderItems.Add(orderItem); - } - - orderItem.Count = count; - await ctx.SaveChangesAsync(); - } -} +using Thinktecture.Database; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +public static class DemoDbContextExtensions +{ + public static async Task EnsureCustomerAsync(this DemoDbContext ctx, Guid id) + { + ArgumentNullException.ThrowIfNull(ctx); + + if (!await ctx.Customers.AnyAsync(c => c.Id == id)) + { + ctx.Customers.Add(new Customer(id, $"First name of '{id}'", $"Last name of '{id}'")); + await ctx.SaveChangesAsync(); + } + + return id; + } + + public static async Task EnsureProductAsync(this DemoDbContext ctx, Guid id) + { + ArgumentNullException.ThrowIfNull(ctx); + + if (!await ctx.Products.AnyAsync(c => c.Id == id)) + { + ctx.Products.Add(new Product { Id = id }); + await ctx.SaveChangesAsync(); + } + + return id; + } + + public static async Task EnsureOrderAsync(this DemoDbContext ctx, Guid id, Guid customerId) + { + ArgumentNullException.ThrowIfNull(ctx); + + if (!await ctx.Orders.AnyAsync(c => c.Id == id)) + { + ctx.Orders.Add(new Order { Id = id, CustomerId = customerId }); + await ctx.SaveChangesAsync(); + } + + return id; + } + + public static async Task EnsureOrderItemAsync(this DemoDbContext ctx, Guid orderId, Guid productId, int count) + { + ArgumentNullException.ThrowIfNull(ctx); + + var orderItem = await ctx.OrderItems.FirstOrDefaultAsync(c => c.OrderId == orderId && c.ProductId == productId); + + if (orderItem == null) + { + orderItem = new OrderItem { OrderId = orderId, ProductId = productId }; + ctx.OrderItems.Add(orderItem); + } + + orderItem.Count = count; + await ctx.SaveChangesAsync(); + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190509195617_Initial_Migration.Designer.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190509195617_Initial_Migration.Designer.cs index 1643842f..be8b7d88 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190509195617_Initial_Migration.Designer.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190509195617_Initial_Migration.Designer.cs @@ -1,101 +1,101 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.Database; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(DemoDbContext))] - [Migration("20190509195617_Initial_Migration")] - partial class Initial_Migration - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.4-servicing-10062") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.Database.Customer", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate(); - - b.HasKey("Id"); - - b.ToTable("Customers"); - }); - - modelBuilder.Entity("Thinktecture.Database.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("CustomerId"); - - b.HasKey("Id"); - - b.HasIndex("CustomerId"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Thinktecture.Database.OrderItem", b => - { - b.Property("OrderId"); - - b.Property("ProductId"); - - b.Property("Count"); - - b.HasKey("OrderId", "ProductId"); - - b.HasIndex("ProductId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Thinktecture.Database.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.HasKey("Id"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("Thinktecture.Database.Order", b => - { - b.HasOne("Thinktecture.Database.Customer", "Customer") - .WithMany("Orders") - .HasForeignKey("CustomerId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Thinktecture.Database.OrderItem", b => - { - b.HasOne("Thinktecture.Database.Order", "Order") - .WithMany("OrderItems") - .HasForeignKey("OrderId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Thinktecture.Database.Product", "Product") - .WithMany("OrderItems") - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.Database; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(DemoDbContext))] + [Migration("20190509195617_Initial_Migration")] + partial class Initial_Migration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate(); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Thinktecture.Database.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CustomerId"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Thinktecture.Database.OrderItem", b => + { + b.Property("OrderId"); + + b.Property("ProductId"); + + b.Property("Count"); + + b.HasKey("OrderId", "ProductId"); + + b.HasIndex("ProductId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Thinktecture.Database.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Thinktecture.Database.Order", b => + { + b.HasOne("Thinktecture.Database.Customer", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Thinktecture.Database.OrderItem", b => + { + b.HasOne("Thinktecture.Database.Order", "Order") + .WithMany("OrderItems") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Thinktecture.Database.Product", "Product") + .WithMany("OrderItems") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190509195617_Initial_Migration.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190509195617_Initial_Migration.cs index cdadcffa..0791c403 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190509195617_Initial_Migration.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190509195617_Initial_Migration.cs @@ -1,81 +1,81 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - // ReSharper disable once InconsistentNaming - // ReSharper disable once UnusedMember.Global - public partial class Initial_Migration : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable("Customers", - table => new - { - Id = table.Column(), - RowVersion = table.Column(rowVersion: true) - }, - constraints: table => table.PrimaryKey("PK_Customers", x => x.Id)); - - migrationBuilder.CreateTable("Products", - table => new - { - Id = table.Column() - }, - constraints: table => table.PrimaryKey("PK_Products", x => x.Id)); - - migrationBuilder.CreateTable("Orders", - table => new - { - Id = table.Column(), - CustomerId = table.Column() - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - table.ForeignKey( - "FK_Orders_Customers_CustomerId", - x => x.CustomerId, - "Customers", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("OrderItems", - table => new - { - OrderId = table.Column(), - ProductId = table.Column(), - Count = table.Column() - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => new { x.OrderId, x.ProductId }); - table.ForeignKey( - "FK_OrderItems_Orders_OrderId", - x => x.OrderId, - "Orders", - "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - "FK_OrderItems_Products_ProductId", - x => x.ProductId, - "Products", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex("IX_OrderItems_ProductId", "OrderItems", "ProductId") - .IncludeColumns("OrderId", "Count"); - migrationBuilder.CreateIndex("IX_OrderItems_ProductId", "OrderItems", "ProductId").IfNotExists(); - migrationBuilder.CreateIndex("IX_Orders_CustomerId", "Orders", "CustomerId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable("OrderItems"); - migrationBuilder.DropTable("Orders"); - migrationBuilder.DropTable("Products"); - migrationBuilder.DropTable("Customers"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + // ReSharper disable once InconsistentNaming + // ReSharper disable once UnusedMember.Global + public partial class Initial_Migration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable("Customers", + table => new + { + Id = table.Column(), + RowVersion = table.Column(rowVersion: true) + }, + constraints: table => table.PrimaryKey("PK_Customers", x => x.Id)); + + migrationBuilder.CreateTable("Products", + table => new + { + Id = table.Column() + }, + constraints: table => table.PrimaryKey("PK_Products", x => x.Id)); + + migrationBuilder.CreateTable("Orders", + table => new + { + Id = table.Column(), + CustomerId = table.Column() + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + table.ForeignKey( + "FK_Orders_Customers_CustomerId", + x => x.CustomerId, + "Customers", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("OrderItems", + table => new + { + OrderId = table.Column(), + ProductId = table.Column(), + Count = table.Column() + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => new { x.OrderId, x.ProductId }); + table.ForeignKey( + "FK_OrderItems_Orders_OrderId", + x => x.OrderId, + "Orders", + "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + "FK_OrderItems_Products_ProductId", + x => x.ProductId, + "Products", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex("IX_OrderItems_ProductId", "OrderItems", "ProductId") + .IncludeColumns("OrderId", "Count"); + migrationBuilder.CreateIndex("IX_OrderItems_ProductId", "OrderItems", "ProductId").IfNotExists(); + migrationBuilder.CreateIndex("IX_Orders_CustomerId", "Orders", "CustomerId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("OrderItems"); + migrationBuilder.DropTable("Orders"); + migrationBuilder.DropTable("Products"); + migrationBuilder.DropTable("Customers"); + } + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190806183132_Added_Date_and_Text.Designer.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190806183132_Added_Date_and_Text.Designer.cs index 2a4bbc2e..99535dec 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190806183132_Added_Date_and_Text.Designer.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190806183132_Added_Date_and_Text.Designer.cs @@ -1,105 +1,105 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.Database; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(DemoDbContext))] - [Migration("20190806183132_Added_Date_and_Text")] - partial class Added_Date_and_Text - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.Database.Customer", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate(); - - b.HasKey("Id"); - - b.ToTable("Customers"); - }); - - modelBuilder.Entity("Thinktecture.Database.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("CustomerId"); - - b.Property("Date"); - - b.Property("Text"); - - b.HasKey("Id"); - - b.HasIndex("CustomerId"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Thinktecture.Database.OrderItem", b => - { - b.Property("OrderId"); - - b.Property("ProductId"); - - b.Property("Count"); - - b.HasKey("OrderId", "ProductId"); - - b.HasIndex("ProductId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Thinktecture.Database.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.HasKey("Id"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("Thinktecture.Database.Order", b => - { - b.HasOne("Thinktecture.Database.Customer", "Customer") - .WithMany("Orders") - .HasForeignKey("CustomerId") - .OnDelete(DeleteBehavior.Cascade); - }); - - modelBuilder.Entity("Thinktecture.Database.OrderItem", b => - { - b.HasOne("Thinktecture.Database.Order", "Order") - .WithMany("OrderItems") - .HasForeignKey("OrderId") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("Thinktecture.Database.Product", "Product") - .WithMany("OrderItems") - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Cascade); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.Database; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(DemoDbContext))] + [Migration("20190806183132_Added_Date_and_Text")] + partial class Added_Date_and_Text + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate(); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Thinktecture.Database.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CustomerId"); + + b.Property("Date"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Thinktecture.Database.OrderItem", b => + { + b.Property("OrderId"); + + b.Property("ProductId"); + + b.Property("Count"); + + b.HasKey("OrderId", "ProductId"); + + b.HasIndex("ProductId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Thinktecture.Database.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Thinktecture.Database.Order", b => + { + b.HasOne("Thinktecture.Database.Customer", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Thinktecture.Database.OrderItem", b => + { + b.HasOne("Thinktecture.Database.Order", "Order") + .WithMany("OrderItems") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Thinktecture.Database.Product", "Product") + .WithMany("OrderItems") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190806183132_Added_Date_and_Text.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190806183132_Added_Date_and_Text.cs index b0391e83..92a2cab8 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190806183132_Added_Date_and_Text.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20190806183132_Added_Date_and_Text.cs @@ -1,20 +1,20 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - // ReSharper disable once InconsistentNaming - public partial class Added_Date_and_Text : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn("Date", "Orders", nullable: false, defaultValue: DateTime.Now); - migrationBuilder.AddColumn("Text", "Orders", nullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn("Date", "Orders"); - migrationBuilder.DropColumn("Text", "Orders"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + // ReSharper disable once InconsistentNaming + public partial class Added_Date_and_Text : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn("Date", "Orders", nullable: false, defaultValue: DateTime.Now); + migrationBuilder.AddColumn("Text", "Orders", nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn("Date", "Orders"); + migrationBuilder.DropColumn("Text", "Orders"); + } + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20200303150837_FirstAndLastName.Designer.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20200303150837_FirstAndLastName.Designer.cs index e16e9caf..b0b23dc0 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20200303150837_FirstAndLastName.Designer.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20200303150837_FirstAndLastName.Designer.cs @@ -1,128 +1,128 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.Database; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(DemoDbContext))] - [Migration("20200303150837_FirstAndLastName")] - partial class FirstAndLastName - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.1") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.Database.Customer", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("FirstName") - .IsRequired() - .HasColumnType("nvarchar(100)") - .HasMaxLength(100); - - b.Property("LastName") - .IsRequired() - .HasColumnType("nvarchar(100)") - .HasMaxLength(100); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("rowversion"); - - b.HasKey("Id"); - - b.ToTable("Customers"); - }); - - modelBuilder.Entity("Thinktecture.Database.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CustomerId") - .HasColumnType("uniqueidentifier"); - - b.Property("Date") - .HasColumnType("datetime2"); - - b.Property("Text") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.HasIndex("CustomerId"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Thinktecture.Database.OrderItem", b => - { - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); - - b.Property("ProductId") - .HasColumnType("uniqueidentifier"); - - b.Property("Count") - .HasColumnType("int"); - - b.HasKey("OrderId", "ProductId"); - - b.HasIndex("ProductId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Thinktecture.Database.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("Thinktecture.Database.Order", b => - { - b.HasOne("Thinktecture.Database.Customer", "Customer") - .WithMany("Orders") - .HasForeignKey("CustomerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.Database.OrderItem", b => - { - b.HasOne("Thinktecture.Database.Order", "Order") - .WithMany("OrderItems") - .HasForeignKey("OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Thinktecture.Database.Product", "Product") - .WithMany("OrderItems") - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.Database; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(DemoDbContext))] + [Migration("20200303150837_FirstAndLastName")] + partial class FirstAndLastName + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(100)") + .HasMaxLength(100); + + b.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(100)") + .HasMaxLength(100); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Thinktecture.Database.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("Text") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Thinktecture.Database.OrderItem", b => + { + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b.Property("Count") + .HasColumnType("int"); + + b.HasKey("OrderId", "ProductId"); + + b.HasIndex("ProductId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Thinktecture.Database.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Thinktecture.Database.Order", b => + { + b.HasOne("Thinktecture.Database.Customer", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.Database.OrderItem", b => + { + b.HasOne("Thinktecture.Database.Order", "Order") + .WithMany("OrderItems") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Thinktecture.Database.Product", "Product") + .WithMany("OrderItems") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20200303150837_FirstAndLastName.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20200303150837_FirstAndLastName.cs index 54bebea5..11f13950 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20200303150837_FirstAndLastName.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/20200303150837_FirstAndLastName.cs @@ -1,19 +1,19 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - public partial class FirstAndLastName : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn("FirstName", "Customers", maxLength: 100, nullable: false, defaultValue: "First"); - migrationBuilder.AddColumn("LastName", "Customers", maxLength: 100, nullable: false, defaultValue: "Last"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn("FirstName", "Customers"); - migrationBuilder.DropColumn("LastName", "Customers"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + public partial class FirstAndLastName : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn("FirstName", "Customers", maxLength: 100, nullable: false, defaultValue: "First"); + migrationBuilder.AddColumn("LastName", "Customers", maxLength: 100, nullable: false, defaultValue: "Last"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn("FirstName", "Customers"); + migrationBuilder.DropColumn("LastName", "Customers"); + } + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/DemoDbContextModelSnapshot.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/DemoDbContextModelSnapshot.cs index b8ff6a2c..83ee6c5a 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/DemoDbContextModelSnapshot.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Migrations/DemoDbContextModelSnapshot.cs @@ -1,126 +1,126 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.Database; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(DemoDbContext))] - partial class DemoDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.1") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.Database.Customer", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("FirstName") - .IsRequired() - .HasColumnType("nvarchar(100)") - .HasMaxLength(100); - - b.Property("LastName") - .IsRequired() - .HasColumnType("nvarchar(100)") - .HasMaxLength(100); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("rowversion"); - - b.HasKey("Id"); - - b.ToTable("Customers"); - }); - - modelBuilder.Entity("Thinktecture.Database.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("CustomerId") - .HasColumnType("uniqueidentifier"); - - b.Property("Date") - .HasColumnType("datetime2"); - - b.Property("Text") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.HasIndex("CustomerId"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Thinktecture.Database.OrderItem", b => - { - b.Property("OrderId") - .HasColumnType("uniqueidentifier"); - - b.Property("ProductId") - .HasColumnType("uniqueidentifier"); - - b.Property("Count") - .HasColumnType("int"); - - b.HasKey("OrderId", "ProductId"); - - b.HasIndex("ProductId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Thinktecture.Database.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("Thinktecture.Database.Order", b => - { - b.HasOne("Thinktecture.Database.Customer", "Customer") - .WithMany("Orders") - .HasForeignKey("CustomerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.Database.OrderItem", b => - { - b.HasOne("Thinktecture.Database.Order", "Order") - .WithMany("OrderItems") - .HasForeignKey("OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Thinktecture.Database.Product", "Product") - .WithMany("OrderItems") - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.Database; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(DemoDbContext))] + partial class DemoDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(100)") + .HasMaxLength(100); + + b.Property("LastName") + .IsRequired() + .HasColumnType("nvarchar(100)") + .HasMaxLength(100); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Thinktecture.Database.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CustomerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Date") + .HasColumnType("datetime2"); + + b.Property("Text") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Thinktecture.Database.OrderItem", b => + { + b.Property("OrderId") + .HasColumnType("uniqueidentifier"); + + b.Property("ProductId") + .HasColumnType("uniqueidentifier"); + + b.Property("Count") + .HasColumnType("int"); + + b.HasKey("OrderId", "ProductId"); + + b.HasIndex("ProductId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Thinktecture.Database.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Thinktecture.Database.Order", b => + { + b.HasOne("Thinktecture.Database.Customer", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.Database.OrderItem", b => + { + b.HasOne("Thinktecture.Database.Order", "Order") + .WithMany("OrderItems") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Thinktecture.Database.Product", "Product") + .WithMany("OrderItems") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Program.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Program.cs index c2424739..0644e27b 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Program.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Program.cs @@ -1,284 +1,284 @@ -using Microsoft.Extensions.DependencyInjection; -using Thinktecture.Database; - -namespace Thinktecture; - -// ReSharper disable once ClassNeverInstantiated.Global -public class Program -{ - // ReSharper disable once InconsistentNaming - public static async Task Main(string[] args) - { - var sp = SamplesContext.Instance.CreateServiceProvider("demo"); - - using (var scope = sp.CreateScope()) - { - var ctx = scope.ServiceProvider.GetRequiredService(); - await ctx.Database.MigrateAsync(); - - await FetchRowVersionsAsync(ctx); - - var customerId = await ctx.EnsureCustomerAsync(new Guid("11D67C68-6F1A-407B-9BD3-56C84FE15BB1")); - var productId = await ctx.EnsureProductAsync(new Guid("872BCAC2-1A85-4B22-AC0F-7D920563A000")); - var orderId = await ctx.EnsureOrderAsync(new Guid("EC1CBF87-F53F-4EF4-B286-8F5EB0AE810D"), customerId); - await ctx.EnsureOrderItemAsync(orderId, productId, 42); - ctx.ChangeTracker.Clear(); // resetting DbContext, as an alternative to create a new one - - // Bulk insert into "real" tables - await DoBulkInsertAsync(ctx); - ctx.ChangeTracker.Clear(); - - await DoBulkInsertSpecificColumnsAsync(ctx); - ctx.ChangeTracker.Clear(); - - // Bulk update - await DoBulkUpdateAsync(ctx, customerId); - ctx.ChangeTracker.Clear(); - - // Bulk insert or update - await DoBulkInsertOrUpdateAsync(ctx, customerId); - ctx.ChangeTracker.Clear(); - - // Bulk insert into temp tables - await DoBulkInsertIntoTempTableAsync(ctx, new List { customerId }); - ctx.ChangeTracker.Clear(); - - await DoBulkInsertIntoTempTableAsync(ctx, new List<(Guid, Guid)> { (customerId, productId) }); - ctx.ChangeTracker.Clear(); - - await DoBulkInsertIntoTempTableAsync(ctx); - ctx.ChangeTracker.Clear(); - - // Collection parameter - await DoScalarCollectionParameterAsync(ctx, new List { customerId }); - ctx.ChangeTracker.Clear(); - - await DoComplexCollectionParameterAsync(ctx, customerId); - ctx.ChangeTracker.Clear(); - - // LEFT JOIN - await DoLeftJoinAsync(ctx); - ctx.ChangeTracker.Clear(); - - // ROWNUMBER - await DoRowNumberAsync(ctx); - ctx.ChangeTracker.Clear(); - - // Tenant - await DoTenantQueriesAsync(ctx); - ctx.ChangeTracker.Clear(); - - // Nested transactions - await DoNestedTransactionsAsync(ctx, orderId); - ctx.ChangeTracker.Clear(); - } - - Console.WriteLine("Exiting samples..."); - } - - private static async Task DoScalarCollectionParameterAsync(DemoDbContext ctx, List customerIds) - { - var customerIdsQuery = ctx.CreateScalarCollectionParameter(customerIds); - - var customers = await ctx.Customers.Where(c => customerIdsQuery.Contains(c.Id)).ToListAsync(); - - Console.WriteLine($"Found customers: {String.Join(", ", customers.Select(c => c.Id))}"); - } - - private static async Task DoComplexCollectionParameterAsync(DemoDbContext ctx, Guid customerId) - { - var parameters = ctx.CreateComplexCollectionParameter(new[] { new MyParameter(customerId, 42) }); - - var customers = await ctx.Customers.Join(parameters, c => c.Id, t => t.Column1, (c, t) => new { Customer = c, Number = t.Column2 }).ToListAsync(); - - Console.WriteLine($"Found customers: {String.Join(", ", customers.Select(c => c.Customer.Id))}"); - } - - private static async Task DoTenantQueriesAsync(DemoDbContext ctx) - { - await ctx.Customers - .Include(c => c.Orders) - .ToListAsync(); - - try - { - // requires a database with the name "demo" - CurrentTenant.Value = "1"; - - await ctx.Customers - .Include(c => c.Orders) - .ToListAsync(); - - // requires a database with the name "demo2" - CurrentTenant.Value = "2"; - - await ctx.Customers - .Include(c => c.Orders) - .ToListAsync(); - } - catch - { - Console.WriteLine("For this demo we need 2 databases: demo and demo2"); - } - finally - { - CurrentTenant.Value = null; - } - } - - private static async Task DoNestedTransactionsAsync(DemoDbContext ctx, Guid orderId) - { - await using var tx = await ctx.Database.BeginTransactionAsync(); - - await using var innerTx = await ctx.Database.BeginTransactionAsync(); - - var order = await ctx.Orders.FirstAsync(c => c.Id == orderId); - order.Text = $"Changed ({DateTime.Now})"; - - await ctx.SaveChangesAsync(); - - await innerTx.CommitAsync(); - - await tx.CommitAsync(); - } - - private static async Task FetchRowVersionsAsync(DemoDbContext ctx) - { - var minActiveRowVersion = await ctx.GetMinActiveRowVersionAsync(); - Console.WriteLine($"Min active row version: {minActiveRowVersion}"); - - var lastUsedRowVersion = await ctx.GetLastUsedRowVersionAsync(); - Console.WriteLine($"Last used row version: {lastUsedRowVersion}"); - } - - private static async Task DoRowNumberAsync(DemoDbContext ctx) - { - var customers = await ctx.Customers - .Select(c => new - { - c.Id, - FirstName_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.FirstName)), - LastName_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.LastName)), - FirstAndLastName1_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.FirstName + " " + c.LastName)), - FirstAndLastName2_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.FirstName).ThenBy(c.LastName)) - }) - .AsSubQuery() - .OrderBy(c => c.FirstName_RowNumber) - .ThenBy(c => c.LastName_RowNumber) - .ToListAsync(); - - Console.WriteLine($"Found customers: {String.Join(", ", customers.Select(c => $"{{ CustomerId={c.Id}, FirstName_RowNumber={c.FirstName_RowNumber}, LastName_RowNumber={c.LastName_RowNumber}, FirstAndLastName1_RowNumber={c.FirstAndLastName1_RowNumber}, FirstAndLastName2_RowNumber={c.FirstAndLastName2_RowNumber} }}"))}"); - - var latestOrders = await ctx.Orders - .Select(o => new - { - o.Id, - o.CustomerId, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(o.Date)) - }) - // Previous query must be a sub query to access "RowNumber" - .AsSubQuery() - .Where(i => i.RowNumber == 1) - .ToListAsync(); - Console.WriteLine($"Latest orders: {String.Join(", ", latestOrders.Select(o => $"{{ CustomerId={o.CustomerId}, OrderId={o.Id} }}"))}"); - } - - private static async Task DoLeftJoinAsync(DemoDbContext ctx) - { - var customerOrder = await ctx.Customers - .LeftJoin(ctx.Orders, - c => c.Id, - o => o.CustomerId, - result => new { Customer = result.Left, Order = result.Right }) - .ToListAsync(); - - Console.WriteLine($"Found customers: {String.Join(", ", customerOrder.Select(co => $"{{ CustomerId={co.Customer.Id}, OrderId={co.Order?.Id} }}"))}"); - } - - private static async Task DoBulkInsertAsync(DemoDbContext ctx) - { - var id = Guid.NewGuid(); - var customersToInsert = new Customer(id, $"First name of '{id}'", $"Last name of '{id}'"); - await ctx.BulkInsertAsync(new[] { customersToInsert }); - - var insertedCustomer = await ctx.Customers.FirstAsync(c => c.Id == customersToInsert.Id); - - Console.WriteLine($"Inserted customers: {insertedCustomer.Id}"); - } - - private static async Task DoBulkInsertSpecificColumnsAsync(DemoDbContext ctx) - { - var customersToInsert = new Customer(Guid.NewGuid(), "First name", "Last name"); - - // only "Id" is sent to the DB - // alternative ways to specify the column: - // * c => new { c.Id } - // * c => c.Id - // * new SqlServerBulkInsertOptions { PropertiesToInsert = IPropertiesProvider.Include(c => new { c.Id })} - await ctx.BulkInsertAsync(new[] { customersToInsert }, c => new { c.Id }); - - var insertedCustomer = await ctx.Customers.FirstAsync(c => c.Id == customersToInsert.Id); - - Console.WriteLine($"Inserted customers: {insertedCustomer.Id}"); - } - - private static async Task DoBulkInsertOrUpdateAsync(DemoDbContext ctx, Guid customerId) - { - var customer = new Customer(customerId, "First name - DoBulkInsertOrUpdateAsync", "Last name will not be updated"); - var newCustomer = new Customer(Guid.NewGuid(), "First name - DoBulkInsertOrUpdateAsync", "Last name - DoBulkInsertOrUpdateAsync"); - - await ctx.BulkInsertOrUpdateAsync(new[] { newCustomer, customer }, propertiesToUpdate: c => c.FirstName); - - var customers = await ctx.Customers.Where(c => c.Id == customerId || c.Id == newCustomer.Id).ToListAsync(); - - Console.WriteLine($"Updated customer: {customers.Single(c => c.Id == customerId)}"); - Console.WriteLine($"New customer: {customers.Single(c => c.Id == newCustomer.Id)}"); - } - - private static async Task DoBulkUpdateAsync(DemoDbContext ctx, Guid customerId) - { - var customer = new Customer(customerId, "First name - DoBulkUpdateAsync", "Last name will not be updated"); - - await ctx.BulkUpdateAsync(new[] { customer }, c => c.FirstName); - - var updatedCustomer = await ctx.Customers.FirstAsync(c => c.Id == customerId); - - Console.WriteLine($"Updated customer: {updatedCustomer}"); - } - - private static async Task DoBulkInsertIntoTempTableAsync(DemoDbContext ctx, List customerIds) - { - await using var tempTableQuery = await ctx.BulkInsertValuesIntoTempTableAsync(customerIds); - - var customers = await ctx.Customers.Join(tempTableQuery.Query, c => c.Id, t => t, (c, t) => c).ToListAsync(); - Console.WriteLine($"Found customers: {String.Join(", ", customers.Select(c => c.Id))}"); - } - - private static async Task DoBulkInsertIntoTempTableAsync(DemoDbContext ctx, List<(Guid customerId, Guid productId)> tuples) - { - await using var tempTableQuery = await ctx.BulkInsertValuesIntoTempTableAsync(tuples); - - var orderItems = await ctx.OrderItems.Join(tempTableQuery.Query, - i => new { i.Order.CustomerId, i.ProductId }, - t => new { CustomerId = t.Column1, ProductId = t.Column2 }, - (i, t) => i) - .ToListAsync(); - - Console.WriteLine($"Found order items: {String.Join(", ", orderItems.Select(i => $"{{ OrderId={i.OrderId}, ProductId={i.ProductId}, Count={i.Count} }}"))}"); - } - - private static async Task DoBulkInsertIntoTempTableAsync(DemoDbContext ctx) - { - var id = Guid.NewGuid(); - var customersToInsert = new[] - { - new Customer(id, $"First name of '{id}'", $"Last name of '{id}'") - }; - - await using var tempTableQuery = await ctx.BulkInsertIntoTempTableAsync(customersToInsert); - - var tempCustomers = await tempTableQuery.Query.ToListAsync(); - - Console.WriteLine($"Customers in temp table: {String.Join(", ", tempCustomers.Select(c => c.Id))}"); - } -} +using Microsoft.Extensions.DependencyInjection; +using Thinktecture.Database; + +namespace Thinktecture; + +// ReSharper disable once ClassNeverInstantiated.Global +public class Program +{ + // ReSharper disable once InconsistentNaming + public static async Task Main(string[] args) + { + var sp = SamplesContext.Instance.CreateServiceProvider("demo"); + + using (var scope = sp.CreateScope()) + { + var ctx = scope.ServiceProvider.GetRequiredService(); + await ctx.Database.MigrateAsync(); + + await FetchRowVersionsAsync(ctx); + + var customerId = await ctx.EnsureCustomerAsync(new Guid("11D67C68-6F1A-407B-9BD3-56C84FE15BB1")); + var productId = await ctx.EnsureProductAsync(new Guid("872BCAC2-1A85-4B22-AC0F-7D920563A000")); + var orderId = await ctx.EnsureOrderAsync(new Guid("EC1CBF87-F53F-4EF4-B286-8F5EB0AE810D"), customerId); + await ctx.EnsureOrderItemAsync(orderId, productId, 42); + ctx.ChangeTracker.Clear(); // resetting DbContext, as an alternative to create a new one + + // Bulk insert into "real" tables + await DoBulkInsertAsync(ctx); + ctx.ChangeTracker.Clear(); + + await DoBulkInsertSpecificColumnsAsync(ctx); + ctx.ChangeTracker.Clear(); + + // Bulk update + await DoBulkUpdateAsync(ctx, customerId); + ctx.ChangeTracker.Clear(); + + // Bulk insert or update + await DoBulkInsertOrUpdateAsync(ctx, customerId); + ctx.ChangeTracker.Clear(); + + // Bulk insert into temp tables + await DoBulkInsertIntoTempTableAsync(ctx, new List { customerId }); + ctx.ChangeTracker.Clear(); + + await DoBulkInsertIntoTempTableAsync(ctx, new List<(Guid, Guid)> { (customerId, productId) }); + ctx.ChangeTracker.Clear(); + + await DoBulkInsertIntoTempTableAsync(ctx); + ctx.ChangeTracker.Clear(); + + // Collection parameter + await DoScalarCollectionParameterAsync(ctx, new List { customerId }); + ctx.ChangeTracker.Clear(); + + await DoComplexCollectionParameterAsync(ctx, customerId); + ctx.ChangeTracker.Clear(); + + // LEFT JOIN + await DoLeftJoinAsync(ctx); + ctx.ChangeTracker.Clear(); + + // ROWNUMBER + await DoRowNumberAsync(ctx); + ctx.ChangeTracker.Clear(); + + // Tenant + await DoTenantQueriesAsync(ctx); + ctx.ChangeTracker.Clear(); + + // Nested transactions + await DoNestedTransactionsAsync(ctx, orderId); + ctx.ChangeTracker.Clear(); + } + + Console.WriteLine("Exiting samples..."); + } + + private static async Task DoScalarCollectionParameterAsync(DemoDbContext ctx, List customerIds) + { + var customerIdsQuery = ctx.CreateScalarCollectionParameter(customerIds); + + var customers = await ctx.Customers.Where(c => customerIdsQuery.Contains(c.Id)).ToListAsync(); + + Console.WriteLine($"Found customers: {String.Join(", ", customers.Select(c => c.Id))}"); + } + + private static async Task DoComplexCollectionParameterAsync(DemoDbContext ctx, Guid customerId) + { + var parameters = ctx.CreateComplexCollectionParameter(new[] { new MyParameter(customerId, 42) }); + + var customers = await ctx.Customers.Join(parameters, c => c.Id, t => t.Column1, (c, t) => new { Customer = c, Number = t.Column2 }).ToListAsync(); + + Console.WriteLine($"Found customers: {String.Join(", ", customers.Select(c => c.Customer.Id))}"); + } + + private static async Task DoTenantQueriesAsync(DemoDbContext ctx) + { + await ctx.Customers + .Include(c => c.Orders) + .ToListAsync(); + + try + { + // requires a database with the name "demo" + CurrentTenant.Value = "1"; + + await ctx.Customers + .Include(c => c.Orders) + .ToListAsync(); + + // requires a database with the name "demo2" + CurrentTenant.Value = "2"; + + await ctx.Customers + .Include(c => c.Orders) + .ToListAsync(); + } + catch + { + Console.WriteLine("For this demo we need 2 databases: demo and demo2"); + } + finally + { + CurrentTenant.Value = null; + } + } + + private static async Task DoNestedTransactionsAsync(DemoDbContext ctx, Guid orderId) + { + await using var tx = await ctx.Database.BeginTransactionAsync(); + + await using var innerTx = await ctx.Database.BeginTransactionAsync(); + + var order = await ctx.Orders.FirstAsync(c => c.Id == orderId); + order.Text = $"Changed ({DateTime.Now})"; + + await ctx.SaveChangesAsync(); + + await innerTx.CommitAsync(); + + await tx.CommitAsync(); + } + + private static async Task FetchRowVersionsAsync(DemoDbContext ctx) + { + var minActiveRowVersion = await ctx.GetMinActiveRowVersionAsync(); + Console.WriteLine($"Min active row version: {minActiveRowVersion}"); + + var lastUsedRowVersion = await ctx.GetLastUsedRowVersionAsync(); + Console.WriteLine($"Last used row version: {lastUsedRowVersion}"); + } + + private static async Task DoRowNumberAsync(DemoDbContext ctx) + { + var customers = await ctx.Customers + .Select(c => new + { + c.Id, + FirstName_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.FirstName)), + LastName_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.LastName)), + FirstAndLastName1_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.FirstName + " " + c.LastName)), + FirstAndLastName2_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.FirstName).ThenBy(c.LastName)) + }) + .AsSubQuery() + .OrderBy(c => c.FirstName_RowNumber) + .ThenBy(c => c.LastName_RowNumber) + .ToListAsync(); + + Console.WriteLine($"Found customers: {String.Join(", ", customers.Select(c => $"{{ CustomerId={c.Id}, FirstName_RowNumber={c.FirstName_RowNumber}, LastName_RowNumber={c.LastName_RowNumber}, FirstAndLastName1_RowNumber={c.FirstAndLastName1_RowNumber}, FirstAndLastName2_RowNumber={c.FirstAndLastName2_RowNumber} }}"))}"); + + var latestOrders = await ctx.Orders + .Select(o => new + { + o.Id, + o.CustomerId, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(o.Date)) + }) + // Previous query must be a sub query to access "RowNumber" + .AsSubQuery() + .Where(i => i.RowNumber == 1) + .ToListAsync(); + Console.WriteLine($"Latest orders: {String.Join(", ", latestOrders.Select(o => $"{{ CustomerId={o.CustomerId}, OrderId={o.Id} }}"))}"); + } + + private static async Task DoLeftJoinAsync(DemoDbContext ctx) + { + var customerOrder = await ctx.Customers + .LeftJoin(ctx.Orders, + c => c.Id, + o => o.CustomerId, + result => new { Customer = result.Left, Order = result.Right }) + .ToListAsync(); + + Console.WriteLine($"Found customers: {String.Join(", ", customerOrder.Select(co => $"{{ CustomerId={co.Customer.Id}, OrderId={co.Order?.Id} }}"))}"); + } + + private static async Task DoBulkInsertAsync(DemoDbContext ctx) + { + var id = Guid.NewGuid(); + var customersToInsert = new Customer(id, $"First name of '{id}'", $"Last name of '{id}'"); + await ctx.BulkInsertAsync(new[] { customersToInsert }); + + var insertedCustomer = await ctx.Customers.FirstAsync(c => c.Id == customersToInsert.Id); + + Console.WriteLine($"Inserted customers: {insertedCustomer.Id}"); + } + + private static async Task DoBulkInsertSpecificColumnsAsync(DemoDbContext ctx) + { + var customersToInsert = new Customer(Guid.NewGuid(), "First name", "Last name"); + + // only "Id" is sent to the DB + // alternative ways to specify the column: + // * c => new { c.Id } + // * c => c.Id + // * new SqlServerBulkInsertOptions { PropertiesToInsert = IPropertiesProvider.Include(c => new { c.Id })} + await ctx.BulkInsertAsync(new[] { customersToInsert }, c => new { c.Id }); + + var insertedCustomer = await ctx.Customers.FirstAsync(c => c.Id == customersToInsert.Id); + + Console.WriteLine($"Inserted customers: {insertedCustomer.Id}"); + } + + private static async Task DoBulkInsertOrUpdateAsync(DemoDbContext ctx, Guid customerId) + { + var customer = new Customer(customerId, "First name - DoBulkInsertOrUpdateAsync", "Last name will not be updated"); + var newCustomer = new Customer(Guid.NewGuid(), "First name - DoBulkInsertOrUpdateAsync", "Last name - DoBulkInsertOrUpdateAsync"); + + await ctx.BulkInsertOrUpdateAsync(new[] { newCustomer, customer }, propertiesToUpdate: c => c.FirstName); + + var customers = await ctx.Customers.Where(c => c.Id == customerId || c.Id == newCustomer.Id).ToListAsync(); + + Console.WriteLine($"Updated customer: {customers.Single(c => c.Id == customerId)}"); + Console.WriteLine($"New customer: {customers.Single(c => c.Id == newCustomer.Id)}"); + } + + private static async Task DoBulkUpdateAsync(DemoDbContext ctx, Guid customerId) + { + var customer = new Customer(customerId, "First name - DoBulkUpdateAsync", "Last name will not be updated"); + + await ctx.BulkUpdateAsync(new[] { customer }, c => c.FirstName); + + var updatedCustomer = await ctx.Customers.FirstAsync(c => c.Id == customerId); + + Console.WriteLine($"Updated customer: {updatedCustomer}"); + } + + private static async Task DoBulkInsertIntoTempTableAsync(DemoDbContext ctx, List customerIds) + { + await using var tempTableQuery = await ctx.BulkInsertValuesIntoTempTableAsync(customerIds); + + var customers = await ctx.Customers.Join(tempTableQuery.Query, c => c.Id, t => t, (c, t) => c).ToListAsync(); + Console.WriteLine($"Found customers: {String.Join(", ", customers.Select(c => c.Id))}"); + } + + private static async Task DoBulkInsertIntoTempTableAsync(DemoDbContext ctx, List<(Guid customerId, Guid productId)> tuples) + { + await using var tempTableQuery = await ctx.BulkInsertValuesIntoTempTableAsync(tuples); + + var orderItems = await ctx.OrderItems.Join(tempTableQuery.Query, + i => new { i.Order.CustomerId, i.ProductId }, + t => new { CustomerId = t.Column1, ProductId = t.Column2 }, + (i, t) => i) + .ToListAsync(); + + Console.WriteLine($"Found order items: {String.Join(", ", orderItems.Select(i => $"{{ OrderId={i.OrderId}, ProductId={i.ProductId}, Count={i.Count} }}"))}"); + } + + private static async Task DoBulkInsertIntoTempTableAsync(DemoDbContext ctx) + { + var id = Guid.NewGuid(); + var customersToInsert = new[] + { + new Customer(id, $"First name of '{id}'", $"Last name of '{id}'") + }; + + await using var tempTableQuery = await ctx.BulkInsertIntoTempTableAsync(customersToInsert); + + var tempCustomers = await tempTableQuery.Query.ToListAsync(); + + Console.WriteLine($"Customers in temp table: {String.Join(", ", tempCustomers.Select(c => c.Id))}"); + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/SamplesContext.cs b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/SamplesContext.cs index 79b7abc5..8fdccc20 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/SamplesContext.cs +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/SamplesContext.cs @@ -1,80 +1,80 @@ -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Thinktecture.Database; -using Thinktecture.EntityFrameworkCore; - -namespace Thinktecture; - -public class SamplesContext -{ - private readonly ILoggerFactory _loggerFactory; - private static readonly Lazy _lazy = new(CreateTestConfiguration); - - public static SamplesContext Instance => _lazy.Value; - - public IConfiguration Configuration { get; } - - public string ConnectionString => Configuration.GetConnectionString("default") - ?? throw new Exception("No connection string with name 'default' found."); - - private static SamplesContext CreateTestConfiguration() - { - var config = GetConfiguration(); - var loggerFactory = new LoggerBuilder().AddConsole().Services.BuildServiceProvider().GetRequiredService(); - - return new SamplesContext(config, loggerFactory); - } - - public SamplesContext(IConfiguration config, ILoggerFactory loggerFactory) - { - _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); - Configuration = config ?? throw new ArgumentNullException(nameof(config)); - } - - private static IConfiguration GetConfiguration() - { - return new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .Build(); - } - - public IServiceProvider CreateServiceProvider(string? schema = null) - { - var services = new ServiceCollection() - .AddDbContext(builder => builder - .UseSqlServer(ConnectionString, sqlOptions => - { - if (schema != null) - sqlOptions.MigrationsHistoryTable("__EFMigrationsHistory", schema); - - sqlOptions.AddWindowFunctionsSupport() - .AddTenantDatabaseSupport() - .AddBulkOperationSupport() - .AddCollectionParameterSupport() - .UseThinktectureSqlServerMigrationsSqlGenerator(); - }) - .ConfigureWarnings(warningsBuilder => warningsBuilder.Ignore(RelationalEventId.PendingModelChangesWarning)) - .EnableSensitiveDataLogging() - .UseLoggerFactory(_loggerFactory) - .AddSchemaRespectingComponents() - .AddNestedTransactionSupport()); - - if (schema != null) - services.AddSingleton(new DbDefaultSchema(schema)); - - return services.BuildServiceProvider(); - } - - private class LoggerBuilder : ILoggingBuilder - { - public IServiceCollection Services { get; } - - public LoggerBuilder() - { - Services = new ServiceCollection() - .AddLogging(); - } - } -} +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Thinktecture.Database; +using Thinktecture.EntityFrameworkCore; + +namespace Thinktecture; + +public class SamplesContext +{ + private readonly ILoggerFactory _loggerFactory; + private static readonly Lazy _lazy = new(CreateTestConfiguration); + + public static SamplesContext Instance => _lazy.Value; + + public IConfiguration Configuration { get; } + + public string ConnectionString => Configuration.GetConnectionString("default") + ?? throw new Exception("No connection string with name 'default' found."); + + private static SamplesContext CreateTestConfiguration() + { + var config = GetConfiguration(); + var loggerFactory = new LoggerBuilder().AddConsole().Services.BuildServiceProvider().GetRequiredService(); + + return new SamplesContext(config, loggerFactory); + } + + public SamplesContext(IConfiguration config, ILoggerFactory loggerFactory) + { + _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + Configuration = config ?? throw new ArgumentNullException(nameof(config)); + } + + private static IConfiguration GetConfiguration() + { + return new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .Build(); + } + + public IServiceProvider CreateServiceProvider(string? schema = null) + { + var services = new ServiceCollection() + .AddDbContext(builder => builder + .UseSqlServer(ConnectionString, sqlOptions => + { + if (schema != null) + sqlOptions.MigrationsHistoryTable("__EFMigrationsHistory", schema); + + sqlOptions.AddWindowFunctionsSupport() + .AddTenantDatabaseSupport() + .AddBulkOperationSupport() + .AddCollectionParameterSupport() + .UseThinktectureSqlServerMigrationsSqlGenerator(); + }) + .ConfigureWarnings(warningsBuilder => warningsBuilder.Ignore(RelationalEventId.PendingModelChangesWarning)) + .EnableSensitiveDataLogging() + .UseLoggerFactory(_loggerFactory) + .AddSchemaRespectingComponents() + .AddNestedTransactionSupport()); + + if (schema != null) + services.AddSingleton(new DbDefaultSchema(schema)); + + return services.BuildServiceProvider(); + } + + private class LoggerBuilder : ILoggingBuilder + { + public IServiceCollection Services { get; } + + public LoggerBuilder() + { + Services = new ServiceCollection() + .AddLogging(); + } + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples.csproj b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples.csproj index f6f1575f..3ac7921a 100644 --- a/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples.csproj +++ b/samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples.csproj @@ -1,25 +1,25 @@ - - - - Exe - $(NoWarn);CS1591 - false - - - - - - - - - PreserveNewest - - - - - - - - - - + + + + Exe + $(NoWarn);CS1591 + false + + + + + + + + + PreserveNewest + + + + + + + + + + diff --git a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/Customer.cs b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/Customer.cs index 258dfd09..3a6b41c6 100644 --- a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/Customer.cs +++ b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/Customer.cs @@ -1,35 +1,35 @@ -namespace Thinktecture.Database; - -public class Customer -{ - public Guid Id { get; private set; } - public string FirstName { get; private set; } - public string LastName { get; private set; } - - private List? _orders; - - // ReSharper disable once UnusedMember.Global - public List Orders - { - get => _orders ??= new List(); - set => _orders = value; - } - -#nullable disable - private Customer() - { - } -#nullable enable - - public Customer(Guid id, string firstName, string lastName) - { - Id = id; - FirstName = firstName; - LastName = lastName; - } - - public override string ToString() - { - return $"{{ CustomerId='{Id}', FirstName='{FirstName}', LastName='{LastName}' }}"; - } -} +namespace Thinktecture.Database; + +public class Customer +{ + public Guid Id { get; private set; } + public string FirstName { get; private set; } + public string LastName { get; private set; } + + private List? _orders; + + // ReSharper disable once UnusedMember.Global + public List Orders + { + get => _orders ??= new List(); + set => _orders = value; + } + +#nullable disable + private Customer() + { + } +#nullable enable + + public Customer(Guid id, string firstName, string lastName) + { + Id = id; + FirstName = firstName; + LastName = lastName; + } + + public override string ToString() + { + return $"{{ CustomerId='{Id}', FirstName='{FirstName}', LastName='{LastName}' }}"; + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/DemoDbContext.cs b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/DemoDbContext.cs index d405f040..dd83ebff 100644 --- a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/DemoDbContext.cs +++ b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/DemoDbContext.cs @@ -1,30 +1,30 @@ -namespace Thinktecture.Database; - -public class DemoDbContext : DbContext -{ -#nullable disable - public DbSet Customers { get; set; } - public DbSet Products { get; set; } - public DbSet Orders { get; set; } - public DbSet OrderItems { get; set; } -#nullable enable - - public DemoDbContext(DbContextOptions options) - : base(options) - { - } - - /// - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity(builder => - { - builder.Property(c => c.FirstName).HasMaxLength(100); - builder.Property(c => c.LastName).HasMaxLength(100); - }); - - modelBuilder.Entity().HasKey(i => new { i.OrderId, i.ProductId }); - } -} +namespace Thinktecture.Database; + +public class DemoDbContext : DbContext +{ +#nullable disable + public DbSet Customers { get; set; } + public DbSet Products { get; set; } + public DbSet Orders { get; set; } + public DbSet OrderItems { get; set; } +#nullable enable + + public DemoDbContext(DbContextOptions options) + : base(options) + { + } + + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(builder => + { + builder.Property(c => c.FirstName).HasMaxLength(100); + builder.Property(c => c.LastName).HasMaxLength(100); + }); + + modelBuilder.Entity().HasKey(i => new { i.OrderId, i.ProductId }); + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/DemoDbContextDesignTimeDbContextFactory.cs b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/DemoDbContextDesignTimeDbContextFactory.cs index 0c96fc6d..f88607fe 100644 --- a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/DemoDbContextDesignTimeDbContextFactory.cs +++ b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/DemoDbContextDesignTimeDbContextFactory.cs @@ -1,16 +1,16 @@ -using Microsoft.EntityFrameworkCore.Design; - -namespace Thinktecture.Database; - -public class DemoDbContextDesignTimeDbContextFactory : IDesignTimeDbContextFactory -{ - public DemoDbContext CreateDbContext(string[] args) - { - var options = new DbContextOptionsBuilder() - .UseSqlite(SamplesContext.Instance.ConnectionString) - .AddSchemaRespectingComponents() - .Options; - - return new DemoDbContext(options); - } +using Microsoft.EntityFrameworkCore.Design; + +namespace Thinktecture.Database; + +public class DemoDbContextDesignTimeDbContextFactory : IDesignTimeDbContextFactory +{ + public DemoDbContext CreateDbContext(string[] args) + { + var options = new DbContextOptionsBuilder() + .UseSqlite(SamplesContext.Instance.ConnectionString) + .AddSchemaRespectingComponents() + .Options; + + return new DemoDbContext(options); + } } \ No newline at end of file diff --git a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/Order.cs b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/Order.cs index 47d9d730..6f5f3ecd 100644 --- a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/Order.cs +++ b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/Order.cs @@ -1,22 +1,22 @@ -namespace Thinktecture.Database; - -public class Order -{ - public Guid Id { get; set; } - public DateTime Date { get; set; } - public string? Text { get; set; } - public Guid CustomerId { get; set; } - -#nullable disable - public Customer Customer { get; set; } -#nullable enable - - private List? _orderItems; - - // ReSharper disable once UnusedMember.Global - public List OrderItems - { - get => _orderItems ??= new List(); - set => _orderItems = value; - } -} +namespace Thinktecture.Database; + +public class Order +{ + public Guid Id { get; set; } + public DateTime Date { get; set; } + public string? Text { get; set; } + public Guid CustomerId { get; set; } + +#nullable disable + public Customer Customer { get; set; } +#nullable enable + + private List? _orderItems; + + // ReSharper disable once UnusedMember.Global + public List OrderItems + { + get => _orderItems ??= new List(); + set => _orderItems = value; + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/OrderItem.cs b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/OrderItem.cs index a368d6b0..6d103bb4 100644 --- a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/OrderItem.cs +++ b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/OrderItem.cs @@ -1,13 +1,13 @@ -namespace Thinktecture.Database; - -public class OrderItem -{ - public Guid OrderId { get; set; } - public Guid ProductId { get; set; } - public int Count { get; set; } - -#nullable disable - public Order Order { get; set; } - public Product Product { get; set; } -#nullable enable -} +namespace Thinktecture.Database; + +public class OrderItem +{ + public Guid OrderId { get; set; } + public Guid ProductId { get; set; } + public int Count { get; set; } + +#nullable disable + public Order Order { get; set; } + public Product Product { get; set; } +#nullable enable +} diff --git a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/Product.cs b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/Product.cs index e496d9f8..f7a2ef82 100644 --- a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/Product.cs +++ b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Database/Product.cs @@ -1,15 +1,15 @@ -namespace Thinktecture.Database; - -public class Product -{ - public Guid Id { get; set; } - - private List? _orderItems; - - // ReSharper disable once UnusedMember.Global - public List OrderItems - { - get => _orderItems ??= new List(); - set => _orderItems = value; - } -} +namespace Thinktecture.Database; + +public class Product +{ + public Guid Id { get; set; } + + private List? _orderItems; + + // ReSharper disable once UnusedMember.Global + public List OrderItems + { + get => _orderItems ??= new List(); + set => _orderItems = value; + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Extensions/DemoDbContextExtensions.cs b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Extensions/DemoDbContextExtensions.cs index 663c0fb3..cdfe08b1 100644 --- a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Extensions/DemoDbContextExtensions.cs +++ b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Extensions/DemoDbContextExtensions.cs @@ -1,62 +1,62 @@ -using Thinktecture.Database; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -public static class DemoDbContextExtensions -{ - public static async Task EnsureCustomerAsync(this DemoDbContext ctx, Guid id) - { - ArgumentNullException.ThrowIfNull(ctx); - - if (!await ctx.Customers.AnyAsync(c => c.Id == id)) - { - ctx.Customers.Add(new Customer(id, $"First name of '{id}'", $"Last name of '{id}'")); - await ctx.SaveChangesAsync(); - } - - return id; - } - - public static async Task EnsureProductAsync(this DemoDbContext ctx, Guid id) - { - ArgumentNullException.ThrowIfNull(ctx); - - if (!await ctx.Products.AnyAsync(c => c.Id == id)) - { - ctx.Products.Add(new Product { Id = id }); - await ctx.SaveChangesAsync(); - } - - return id; - } - - public static async Task EnsureOrderAsync(this DemoDbContext ctx, Guid id, Guid customerId) - { - ArgumentNullException.ThrowIfNull(ctx); - - if (!await ctx.Orders.AnyAsync(c => c.Id == id)) - { - ctx.Orders.Add(new Order { Id = id, CustomerId = customerId }); - await ctx.SaveChangesAsync(); - } - - return id; - } - - public static async Task EnsureOrderItemAsync(this DemoDbContext ctx, Guid orderId, Guid productId, int count) - { - ArgumentNullException.ThrowIfNull(ctx); - - var orderItem = await ctx.OrderItems.FirstOrDefaultAsync(c => c.OrderId == orderId && c.ProductId == productId); - - if (orderItem == null) - { - orderItem = new OrderItem { OrderId = orderId, ProductId = productId }; - ctx.OrderItems.Add(orderItem); - } - - orderItem.Count = count; - await ctx.SaveChangesAsync(); - } -} +using Thinktecture.Database; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +public static class DemoDbContextExtensions +{ + public static async Task EnsureCustomerAsync(this DemoDbContext ctx, Guid id) + { + ArgumentNullException.ThrowIfNull(ctx); + + if (!await ctx.Customers.AnyAsync(c => c.Id == id)) + { + ctx.Customers.Add(new Customer(id, $"First name of '{id}'", $"Last name of '{id}'")); + await ctx.SaveChangesAsync(); + } + + return id; + } + + public static async Task EnsureProductAsync(this DemoDbContext ctx, Guid id) + { + ArgumentNullException.ThrowIfNull(ctx); + + if (!await ctx.Products.AnyAsync(c => c.Id == id)) + { + ctx.Products.Add(new Product { Id = id }); + await ctx.SaveChangesAsync(); + } + + return id; + } + + public static async Task EnsureOrderAsync(this DemoDbContext ctx, Guid id, Guid customerId) + { + ArgumentNullException.ThrowIfNull(ctx); + + if (!await ctx.Orders.AnyAsync(c => c.Id == id)) + { + ctx.Orders.Add(new Order { Id = id, CustomerId = customerId }); + await ctx.SaveChangesAsync(); + } + + return id; + } + + public static async Task EnsureOrderItemAsync(this DemoDbContext ctx, Guid orderId, Guid productId, int count) + { + ArgumentNullException.ThrowIfNull(ctx); + + var orderItem = await ctx.OrderItems.FirstOrDefaultAsync(c => c.OrderId == orderId && c.ProductId == productId); + + if (orderItem == null) + { + orderItem = new OrderItem { OrderId = orderId, ProductId = productId }; + ctx.OrderItems.Add(orderItem); + } + + orderItem.Count = count; + await ctx.SaveChangesAsync(); + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Migrations/20200602165914_Initial_Migration.Designer.cs b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Migrations/20200602165914_Initial_Migration.Designer.cs index e3470a49..8e423ce4 100644 --- a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Migrations/20200602165914_Initial_Migration.Designer.cs +++ b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Migrations/20200602165914_Initial_Migration.Designer.cs @@ -1,119 +1,119 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.Database; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(DemoDbContext))] - [Migration("20200602165914_Initial_Migration")] - partial class Initial_Migration - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.3"); - - modelBuilder.Entity("Thinktecture.Database.Customer", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("FirstName") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(100); - - b.Property("LastName") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(100); - - b.HasKey("Id"); - - b.ToTable("Customers"); - }); - - modelBuilder.Entity("Thinktecture.Database.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("CustomerId") - .HasColumnType("TEXT"); - - b.Property("Date") - .HasColumnType("TEXT"); - - b.Property("Text") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("CustomerId"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Thinktecture.Database.OrderItem", b => - { - b.Property("OrderId") - .HasColumnType("TEXT"); - - b.Property("ProductId") - .HasColumnType("TEXT"); - - b.Property("Count") - .HasColumnType("INTEGER"); - - b.HasKey("OrderId", "ProductId"); - - b.HasIndex("ProductId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Thinktecture.Database.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("Thinktecture.Database.Order", b => - { - b.HasOne("Thinktecture.Database.Customer", "Customer") - .WithMany("Orders") - .HasForeignKey("CustomerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.Database.OrderItem", b => - { - b.HasOne("Thinktecture.Database.Order", "Order") - .WithMany("OrderItems") - .HasForeignKey("OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Thinktecture.Database.Product", "Product") - .WithMany("OrderItems") - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.Database; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(DemoDbContext))] + [Migration("20200602165914_Initial_Migration")] + partial class Initial_Migration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3"); + + modelBuilder.Entity("Thinktecture.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(100); + + b.Property("LastName") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(100); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Thinktecture.Database.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("Text") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Thinktecture.Database.OrderItem", b => + { + b.Property("OrderId") + .HasColumnType("TEXT"); + + b.Property("ProductId") + .HasColumnType("TEXT"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.HasKey("OrderId", "ProductId"); + + b.HasIndex("ProductId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Thinktecture.Database.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Thinktecture.Database.Order", b => + { + b.HasOne("Thinktecture.Database.Customer", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.Database.OrderItem", b => + { + b.HasOne("Thinktecture.Database.Order", "Order") + .WithMany("OrderItems") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Thinktecture.Database.Product", "Product") + .WithMany("OrderItems") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Migrations/20200602165914_Initial_Migration.cs b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Migrations/20200602165914_Initial_Migration.cs index 5a6c89b6..b8f4de52 100644 --- a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Migrations/20200602165914_Initial_Migration.cs +++ b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Migrations/20200602165914_Initial_Migration.cs @@ -1,82 +1,82 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - // ReSharper disable once InconsistentNaming - // ReSharper disable once UnusedMember.Global - public partial class Initial_Migration : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable("Customers", - table => new - { - Id = table.Column(nullable: false), - FirstName = table.Column(maxLength: 100, nullable: false), - LastName = table.Column(maxLength: 100, nullable: false) - }, - constraints: table => table.PrimaryKey("PK_Customers", x => x.Id)); - - migrationBuilder.CreateTable("Products", - table => new - { - Id = table.Column(nullable: false) - }, - constraints: table => table.PrimaryKey("PK_Products", x => x.Id)); - - migrationBuilder.CreateTable("Orders", - table => new - { - Id = table.Column(nullable: false), - Date = table.Column(nullable: false), - Text = table.Column(nullable: true), - CustomerId = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Orders", x => x.Id); - table.ForeignKey( - "FK_Orders_Customers_CustomerId", - x => x.CustomerId, - "Customers", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("OrderItems", - table => new - { - OrderId = table.Column(nullable: false), - ProductId = table.Column(nullable: false), - Count = table.Column(nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_OrderItems", x => new { x.OrderId, x.ProductId }); - table.ForeignKey( - "FK_OrderItems_Orders_OrderId", - x => x.OrderId, - "Orders", - "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - "FK_OrderItems_Products_ProductId", - x => x.ProductId, - "Products", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex("IX_OrderItems_ProductId", "OrderItems", "ProductId"); - migrationBuilder.CreateIndex("IX_Orders_CustomerId", "Orders", "CustomerId"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable("OrderItems"); - migrationBuilder.DropTable("Orders"); - migrationBuilder.DropTable("Products"); - migrationBuilder.DropTable("Customers"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + // ReSharper disable once InconsistentNaming + // ReSharper disable once UnusedMember.Global + public partial class Initial_Migration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable("Customers", + table => new + { + Id = table.Column(nullable: false), + FirstName = table.Column(maxLength: 100, nullable: false), + LastName = table.Column(maxLength: 100, nullable: false) + }, + constraints: table => table.PrimaryKey("PK_Customers", x => x.Id)); + + migrationBuilder.CreateTable("Products", + table => new + { + Id = table.Column(nullable: false) + }, + constraints: table => table.PrimaryKey("PK_Products", x => x.Id)); + + migrationBuilder.CreateTable("Orders", + table => new + { + Id = table.Column(nullable: false), + Date = table.Column(nullable: false), + Text = table.Column(nullable: true), + CustomerId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Orders", x => x.Id); + table.ForeignKey( + "FK_Orders_Customers_CustomerId", + x => x.CustomerId, + "Customers", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("OrderItems", + table => new + { + OrderId = table.Column(nullable: false), + ProductId = table.Column(nullable: false), + Count = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_OrderItems", x => new { x.OrderId, x.ProductId }); + table.ForeignKey( + "FK_OrderItems_Orders_OrderId", + x => x.OrderId, + "Orders", + "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + "FK_OrderItems_Products_ProductId", + x => x.ProductId, + "Products", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex("IX_OrderItems_ProductId", "OrderItems", "ProductId"); + migrationBuilder.CreateIndex("IX_Orders_CustomerId", "Orders", "CustomerId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("OrderItems"); + migrationBuilder.DropTable("Orders"); + migrationBuilder.DropTable("Products"); + migrationBuilder.DropTable("Customers"); + } + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Migrations/DemoDbContextModelSnapshot.cs b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Migrations/DemoDbContextModelSnapshot.cs index 14a9c0b1..e61b3931 100644 --- a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Migrations/DemoDbContextModelSnapshot.cs +++ b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Migrations/DemoDbContextModelSnapshot.cs @@ -1,117 +1,117 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.Database; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(DemoDbContext))] - partial class DemoDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.3"); - - modelBuilder.Entity("Thinktecture.Database.Customer", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("FirstName") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(100); - - b.Property("LastName") - .IsRequired() - .HasColumnType("TEXT") - .HasMaxLength(100); - - b.HasKey("Id"); - - b.ToTable("Customers"); - }); - - modelBuilder.Entity("Thinktecture.Database.Order", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("CustomerId") - .HasColumnType("TEXT"); - - b.Property("Date") - .HasColumnType("TEXT"); - - b.Property("Text") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("CustomerId"); - - b.ToTable("Orders"); - }); - - modelBuilder.Entity("Thinktecture.Database.OrderItem", b => - { - b.Property("OrderId") - .HasColumnType("TEXT"); - - b.Property("ProductId") - .HasColumnType("TEXT"); - - b.Property("Count") - .HasColumnType("INTEGER"); - - b.HasKey("OrderId", "ProductId"); - - b.HasIndex("ProductId"); - - b.ToTable("OrderItems"); - }); - - modelBuilder.Entity("Thinktecture.Database.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("Thinktecture.Database.Order", b => - { - b.HasOne("Thinktecture.Database.Customer", "Customer") - .WithMany("Orders") - .HasForeignKey("CustomerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.Database.OrderItem", b => - { - b.HasOne("Thinktecture.Database.Order", "Order") - .WithMany("OrderItems") - .HasForeignKey("OrderId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Thinktecture.Database.Product", "Product") - .WithMany("OrderItems") - .HasForeignKey("ProductId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.Database; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(DemoDbContext))] + partial class DemoDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3"); + + modelBuilder.Entity("Thinktecture.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(100); + + b.Property("LastName") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(100); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Thinktecture.Database.Order", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CustomerId") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("Text") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CustomerId"); + + b.ToTable("Orders"); + }); + + modelBuilder.Entity("Thinktecture.Database.OrderItem", b => + { + b.Property("OrderId") + .HasColumnType("TEXT"); + + b.Property("ProductId") + .HasColumnType("TEXT"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.HasKey("OrderId", "ProductId"); + + b.HasIndex("ProductId"); + + b.ToTable("OrderItems"); + }); + + modelBuilder.Entity("Thinktecture.Database.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Products"); + }); + + modelBuilder.Entity("Thinktecture.Database.Order", b => + { + b.HasOne("Thinktecture.Database.Customer", "Customer") + .WithMany("Orders") + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.Database.OrderItem", b => + { + b.HasOne("Thinktecture.Database.Order", "Order") + .WithMany("OrderItems") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Thinktecture.Database.Product", "Product") + .WithMany("OrderItems") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Program.cs b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Program.cs index 65e846d9..66c571a0 100644 --- a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Program.cs +++ b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Program.cs @@ -1,169 +1,169 @@ -using Microsoft.Extensions.DependencyInjection; -using Thinktecture.Database; - -namespace Thinktecture; - -// ReSharper disable once ClassNeverInstantiated.Global -public class Program -{ - // ReSharper disable once InconsistentNaming - public static async Task Main(string[] args) - { - var sp = SamplesContext.Instance.CreateServiceProvider(); - - using (var scope = sp.CreateScope()) - { - var ctx = scope.ServiceProvider.GetRequiredService(); - // have to keep open the connection because we are using an in-memory SQLite database - await ctx.Database.OpenConnectionAsync(); - - try - { - await ctx.Database.MigrateAsync(); - - var customerId = await ctx.EnsureCustomerAsync(new Guid("11D67C68-6F1A-407B-9BD3-56C84FE15BB1")); - var productId = await ctx.EnsureProductAsync(new Guid("872BCAC2-1A85-4B22-AC0F-7D920563A000")); - var orderId = await ctx.EnsureOrderAsync(new Guid("EC1CBF87-F53F-4EF4-B286-8F5EB0AE810D"), customerId); - await ctx.EnsureOrderItemAsync(orderId, productId, 42); - ctx.ChangeTracker.Clear(); // resetting DbContext, as an alternative to create a new one - - // Bulk insert into temp tables - await BulkInsertIntoTempTableAsync(ctx); - ctx.ChangeTracker.Clear(); - - // Bulk insert into "real" tables - await DoBulkInsertAsync(ctx); - ctx.ChangeTracker.Clear(); - - await DoBulkInsertSpecificColumnsAsync(ctx); - ctx.ChangeTracker.Clear(); - - // Bulk update - await DoBulkUpdateAsync(ctx, customerId); - ctx.ChangeTracker.Clear(); - - // Bulk insert or update - await DoBulkInsertOrUpdateAsync(ctx, customerId); - ctx.ChangeTracker.Clear(); - - // LEFT JOIN - await DoLeftJoinAsync(ctx); - ctx.ChangeTracker.Clear(); - - // ROWNUMBER - await DoRowNumberAsync(ctx); - ctx.ChangeTracker.Clear(); - } - finally - { - await ctx.Database.CloseConnectionAsync(); - } - } - - Console.WriteLine("Exiting samples..."); - } - - private static async Task DoRowNumberAsync(DemoDbContext ctx) - { - var customers = await ctx.Customers - .Select(c => new - { - c.Id, - FirstName_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.FirstName)), - LastName_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.LastName)), - FirstAndLastName1_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.FirstName + " " + c.LastName)), - FirstAndLastName2_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.FirstName).ThenBy(c.LastName)) - }) - .AsSubQuery() - .OrderBy(c => c.FirstName_RowNumber) - .ThenBy(c => c.LastName_RowNumber) - .ToListAsync(); - - Console.WriteLine($"Found customers: {String.Join(", ", customers.Select(c => $"{{ CustomerId={c.Id}, FirstName_RowNumber={c.FirstName_RowNumber}, LastName_RowNumber={c.LastName_RowNumber}, FirstAndLastName1_RowNumber={c.FirstAndLastName1_RowNumber}, FirstAndLastName2_RowNumber={c.FirstAndLastName2_RowNumber} }}"))}"); - - var latestOrders = await ctx.Orders - .Select(o => new - { - o.Id, - o.CustomerId, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(o.Date)) - }) - // Previous query must be a sub query to access "RowNumber" - .AsSubQuery() - .Where(i => i.RowNumber == 1) - .ToListAsync(); - Console.WriteLine($"Latest orders: {String.Join(", ", latestOrders.Select(o => $"{{ CustomerId={o.CustomerId}, OrderId={o.Id} }}"))}"); - } - - private static async Task DoLeftJoinAsync(DemoDbContext ctx) - { - var customerOrder = await ctx.Customers - .LeftJoin(ctx.Orders, - c => c.Id, - o => o.CustomerId, - result => new { Customer = result.Left, Order = result.Right }) - .ToListAsync(); - - Console.WriteLine($"Found customers: {String.Join(", ", customerOrder.Select(co => $"{{ CustomerId={co.Customer.Id}, OrderId={co.Order?.Id} }}"))}"); - } - - private static async Task BulkInsertIntoTempTableAsync(DemoDbContext ctx) - { - var customersToInsert = new Customer(Guid.NewGuid(), "First name", "Last name"); - await using var tempTable = await ctx.BulkInsertIntoTempTableAsync(new[] { customersToInsert }); - - var insertedCustomer = await tempTable.Query.FirstAsync(c => c.Id == customersToInsert.Id); - - Console.WriteLine($"Customer from temp table: {insertedCustomer.Id}"); - } - - private static async Task DoBulkInsertAsync(DemoDbContext ctx) - { - var customersToInsert = new Customer(Guid.NewGuid(), "First name", "Last name"); - await ctx.BulkInsertAsync(new[] { customersToInsert }); - - var insertedCustomer = await ctx.Customers.FirstAsync(c => c.Id == customersToInsert.Id); - - Console.WriteLine($"Inserted customer: {insertedCustomer.Id}"); - } - - private static async Task DoBulkInsertSpecificColumnsAsync(DemoDbContext ctx) - { - var customersToInsert = new Customer(Guid.NewGuid(), "First name", "Last name"); - - // only "Id" is sent to the DB - // alternative ways to specify the column: - // * c => new { c.Id } - // * c => c.Id - // * new SqliteBulkInsertOptions { PropertiesToInsert = IPropertiesProvider.Include(c => new { c.Id })} - await ctx.BulkInsertAsync(new[] { customersToInsert }, c => new { c.Id, c.FirstName, c.LastName }); - - var insertedCustomer = await ctx.Customers.FirstAsync(c => c.Id == customersToInsert.Id); - - Console.WriteLine($"Inserted customer: {insertedCustomer.Id}"); - } - - private static async Task DoBulkInsertOrUpdateAsync(DemoDbContext ctx, Guid customerId) - { - var customer = new Customer(customerId, "First name - DoBulkInsertOrUpdateAsync", "Last name will not be updated"); - var newCustomer = new Customer(Guid.NewGuid(), "First name - DoBulkInsertOrUpdateAsync", "Last name - DoBulkInsertOrUpdateAsync"); - - await ctx.BulkInsertOrUpdateAsync(new[] { newCustomer, customer }, propertiesToUpdate: c => c.FirstName); - - var customers = await ctx.Customers.Where(c => c.Id == customerId || c.Id == newCustomer.Id).ToListAsync(); - - Console.WriteLine($"Updated customer: {customers.Single(c => c.Id == customerId)}"); - Console.WriteLine($"New customer: {customers.Single(c => c.Id == newCustomer.Id)}"); - } - - private static async Task DoBulkUpdateAsync(DemoDbContext ctx, Guid customerId) - { - var customer = new Customer(customerId, "First name - DoBulkUpdateAsync", "Last name will not be updated"); - - await ctx.BulkUpdateAsync(new[] { customer }, c => c.FirstName); - - var updatedCustomer = await ctx.Customers.FirstAsync(c => c.Id == customerId); - - Console.WriteLine($"Updated customer: {updatedCustomer}"); - } -} +using Microsoft.Extensions.DependencyInjection; +using Thinktecture.Database; + +namespace Thinktecture; + +// ReSharper disable once ClassNeverInstantiated.Global +public class Program +{ + // ReSharper disable once InconsistentNaming + public static async Task Main(string[] args) + { + var sp = SamplesContext.Instance.CreateServiceProvider(); + + using (var scope = sp.CreateScope()) + { + var ctx = scope.ServiceProvider.GetRequiredService(); + // have to keep open the connection because we are using an in-memory SQLite database + await ctx.Database.OpenConnectionAsync(); + + try + { + await ctx.Database.MigrateAsync(); + + var customerId = await ctx.EnsureCustomerAsync(new Guid("11D67C68-6F1A-407B-9BD3-56C84FE15BB1")); + var productId = await ctx.EnsureProductAsync(new Guid("872BCAC2-1A85-4B22-AC0F-7D920563A000")); + var orderId = await ctx.EnsureOrderAsync(new Guid("EC1CBF87-F53F-4EF4-B286-8F5EB0AE810D"), customerId); + await ctx.EnsureOrderItemAsync(orderId, productId, 42); + ctx.ChangeTracker.Clear(); // resetting DbContext, as an alternative to create a new one + + // Bulk insert into temp tables + await BulkInsertIntoTempTableAsync(ctx); + ctx.ChangeTracker.Clear(); + + // Bulk insert into "real" tables + await DoBulkInsertAsync(ctx); + ctx.ChangeTracker.Clear(); + + await DoBulkInsertSpecificColumnsAsync(ctx); + ctx.ChangeTracker.Clear(); + + // Bulk update + await DoBulkUpdateAsync(ctx, customerId); + ctx.ChangeTracker.Clear(); + + // Bulk insert or update + await DoBulkInsertOrUpdateAsync(ctx, customerId); + ctx.ChangeTracker.Clear(); + + // LEFT JOIN + await DoLeftJoinAsync(ctx); + ctx.ChangeTracker.Clear(); + + // ROWNUMBER + await DoRowNumberAsync(ctx); + ctx.ChangeTracker.Clear(); + } + finally + { + await ctx.Database.CloseConnectionAsync(); + } + } + + Console.WriteLine("Exiting samples..."); + } + + private static async Task DoRowNumberAsync(DemoDbContext ctx) + { + var customers = await ctx.Customers + .Select(c => new + { + c.Id, + FirstName_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.FirstName)), + LastName_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.LastName)), + FirstAndLastName1_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.FirstName + " " + c.LastName)), + FirstAndLastName2_RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(c.FirstName).ThenBy(c.LastName)) + }) + .AsSubQuery() + .OrderBy(c => c.FirstName_RowNumber) + .ThenBy(c => c.LastName_RowNumber) + .ToListAsync(); + + Console.WriteLine($"Found customers: {String.Join(", ", customers.Select(c => $"{{ CustomerId={c.Id}, FirstName_RowNumber={c.FirstName_RowNumber}, LastName_RowNumber={c.LastName_RowNumber}, FirstAndLastName1_RowNumber={c.FirstAndLastName1_RowNumber}, FirstAndLastName2_RowNumber={c.FirstAndLastName2_RowNumber} }}"))}"); + + var latestOrders = await ctx.Orders + .Select(o => new + { + o.Id, + o.CustomerId, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(o.Date)) + }) + // Previous query must be a sub query to access "RowNumber" + .AsSubQuery() + .Where(i => i.RowNumber == 1) + .ToListAsync(); + Console.WriteLine($"Latest orders: {String.Join(", ", latestOrders.Select(o => $"{{ CustomerId={o.CustomerId}, OrderId={o.Id} }}"))}"); + } + + private static async Task DoLeftJoinAsync(DemoDbContext ctx) + { + var customerOrder = await ctx.Customers + .LeftJoin(ctx.Orders, + c => c.Id, + o => o.CustomerId, + result => new { Customer = result.Left, Order = result.Right }) + .ToListAsync(); + + Console.WriteLine($"Found customers: {String.Join(", ", customerOrder.Select(co => $"{{ CustomerId={co.Customer.Id}, OrderId={co.Order?.Id} }}"))}"); + } + + private static async Task BulkInsertIntoTempTableAsync(DemoDbContext ctx) + { + var customersToInsert = new Customer(Guid.NewGuid(), "First name", "Last name"); + await using var tempTable = await ctx.BulkInsertIntoTempTableAsync(new[] { customersToInsert }); + + var insertedCustomer = await tempTable.Query.FirstAsync(c => c.Id == customersToInsert.Id); + + Console.WriteLine($"Customer from temp table: {insertedCustomer.Id}"); + } + + private static async Task DoBulkInsertAsync(DemoDbContext ctx) + { + var customersToInsert = new Customer(Guid.NewGuid(), "First name", "Last name"); + await ctx.BulkInsertAsync(new[] { customersToInsert }); + + var insertedCustomer = await ctx.Customers.FirstAsync(c => c.Id == customersToInsert.Id); + + Console.WriteLine($"Inserted customer: {insertedCustomer.Id}"); + } + + private static async Task DoBulkInsertSpecificColumnsAsync(DemoDbContext ctx) + { + var customersToInsert = new Customer(Guid.NewGuid(), "First name", "Last name"); + + // only "Id" is sent to the DB + // alternative ways to specify the column: + // * c => new { c.Id } + // * c => c.Id + // * new SqliteBulkInsertOptions { PropertiesToInsert = IPropertiesProvider.Include(c => new { c.Id })} + await ctx.BulkInsertAsync(new[] { customersToInsert }, c => new { c.Id, c.FirstName, c.LastName }); + + var insertedCustomer = await ctx.Customers.FirstAsync(c => c.Id == customersToInsert.Id); + + Console.WriteLine($"Inserted customer: {insertedCustomer.Id}"); + } + + private static async Task DoBulkInsertOrUpdateAsync(DemoDbContext ctx, Guid customerId) + { + var customer = new Customer(customerId, "First name - DoBulkInsertOrUpdateAsync", "Last name will not be updated"); + var newCustomer = new Customer(Guid.NewGuid(), "First name - DoBulkInsertOrUpdateAsync", "Last name - DoBulkInsertOrUpdateAsync"); + + await ctx.BulkInsertOrUpdateAsync(new[] { newCustomer, customer }, propertiesToUpdate: c => c.FirstName); + + var customers = await ctx.Customers.Where(c => c.Id == customerId || c.Id == newCustomer.Id).ToListAsync(); + + Console.WriteLine($"Updated customer: {customers.Single(c => c.Id == customerId)}"); + Console.WriteLine($"New customer: {customers.Single(c => c.Id == newCustomer.Id)}"); + } + + private static async Task DoBulkUpdateAsync(DemoDbContext ctx, Guid customerId) + { + var customer = new Customer(customerId, "First name - DoBulkUpdateAsync", "Last name will not be updated"); + + await ctx.BulkUpdateAsync(new[] { customer }, c => c.FirstName); + + var updatedCustomer = await ctx.Customers.FirstAsync(c => c.Id == customerId); + + Console.WriteLine($"Updated customer: {updatedCustomer}"); + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/SamplesContext.cs b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/SamplesContext.cs index fe384c90..5548bc95 100644 --- a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/SamplesContext.cs +++ b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/SamplesContext.cs @@ -1,67 +1,67 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Thinktecture.Database; - -namespace Thinktecture; - -public class SamplesContext -{ - private readonly ILoggerFactory _loggerFactory; - private static readonly Lazy _lazy = new(CreateTestConfiguration); - - public static SamplesContext Instance => _lazy.Value; - - public IConfiguration Configuration { get; } - - public string ConnectionString => Configuration.GetConnectionString("default") - ?? throw new Exception("No connection string with name 'default' found."); - - private static SamplesContext CreateTestConfiguration() - { - var config = GetConfiguration(); - var loggerFactory = new LoggerBuilder().AddConsole().Services.BuildServiceProvider().GetRequiredService(); - - return new SamplesContext(config, loggerFactory); - } - - public SamplesContext(IConfiguration config, ILoggerFactory loggerFactory) - { - _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); - Configuration = config ?? throw new ArgumentNullException(nameof(config)); - } - - private static IConfiguration GetConfiguration() - { - return new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .Build(); - } - - public IServiceProvider CreateServiceProvider() - { - var services = new ServiceCollection() - .AddDbContext(builder => builder - .UseSqlite(ConnectionString, sqlOptions => - { - sqlOptions.AddBulkOperationSupport() - .AddWindowFunctionsSupport(); - }) - .EnableSensitiveDataLogging() - .UseLoggerFactory(_loggerFactory) - .AddNestedTransactionSupport()); - - return services.BuildServiceProvider(); - } - - private class LoggerBuilder : ILoggingBuilder - { - public IServiceCollection Services { get; } - - public LoggerBuilder() - { - Services = new ServiceCollection() - .AddLogging(); - } - } -} +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Thinktecture.Database; + +namespace Thinktecture; + +public class SamplesContext +{ + private readonly ILoggerFactory _loggerFactory; + private static readonly Lazy _lazy = new(CreateTestConfiguration); + + public static SamplesContext Instance => _lazy.Value; + + public IConfiguration Configuration { get; } + + public string ConnectionString => Configuration.GetConnectionString("default") + ?? throw new Exception("No connection string with name 'default' found."); + + private static SamplesContext CreateTestConfiguration() + { + var config = GetConfiguration(); + var loggerFactory = new LoggerBuilder().AddConsole().Services.BuildServiceProvider().GetRequiredService(); + + return new SamplesContext(config, loggerFactory); + } + + public SamplesContext(IConfiguration config, ILoggerFactory loggerFactory) + { + _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + Configuration = config ?? throw new ArgumentNullException(nameof(config)); + } + + private static IConfiguration GetConfiguration() + { + return new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .Build(); + } + + public IServiceProvider CreateServiceProvider() + { + var services = new ServiceCollection() + .AddDbContext(builder => builder + .UseSqlite(ConnectionString, sqlOptions => + { + sqlOptions.AddBulkOperationSupport() + .AddWindowFunctionsSupport(); + }) + .EnableSensitiveDataLogging() + .UseLoggerFactory(_loggerFactory) + .AddNestedTransactionSupport()); + + return services.BuildServiceProvider(); + } + + private class LoggerBuilder : ILoggingBuilder + { + public IServiceCollection Services { get; } + + public LoggerBuilder() + { + Services = new ServiceCollection() + .AddLogging(); + } + } +} diff --git a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples.csproj b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples.csproj index 93cbb61a..4372b935 100644 --- a/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples.csproj +++ b/samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples.csproj @@ -1,25 +1,25 @@ - - - - Exe - $(NoWarn);CS1591 - false - - - - - PreserveNewest - - - - - - - - - - - - - - + + + + Exe + $(NoWarn);CS1591 + false + + + + + PreserveNewest + + + + + + + + + + + + + + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 509b9041..703abd45 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,24 +1,24 @@ - - - - $([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../')) - - - - - - true - true - snupkg - - - - - - - - - - - - + + + + $([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../')) + + + + + + true + true + snupkg + + + + + + + + + + + + diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/DefaultPropertiesEntityPropertiesProvider.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/DefaultPropertiesEntityPropertiesProvider.cs index 08be15fb..2cd3fe2f 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/DefaultPropertiesEntityPropertiesProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/DefaultPropertiesEntityPropertiesProvider.cs @@ -1,54 +1,54 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -internal class DefaultPropertiesEntityPropertiesProvider : IEntityPropertiesProvider -{ - public IReadOnlyList GetPropertiesForTempTable(IEntityType entityType) - { - if (entityType.GetOwnedTypesProperties(null).Any()) - throw new NotSupportedException("Temp tables don't support owned entities."); - - return entityType.GetFlattenedProperties().ToList(); - } - - public IReadOnlyList GetKeyProperties(IEntityType entityType) - { - var pk = entityType.FindPrimaryKey()?.Properties; - - if (pk is null or { Count: 0 }) - throw new InvalidOperationException($"The entity '{entityType.Name}' has no primary key. Please provide key properties to perform JOIN/match on."); - - return pk; - } - - public IReadOnlyList GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes) - { - return DeterminePropertiesWithNavigations(entityType, inlinedOwnTypes, IEntityPropertiesProvider.InsertAndUpdateFilter); - } - - public IReadOnlyList GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes) - { - return DeterminePropertiesWithNavigations(entityType, inlinedOwnTypes, IEntityPropertiesProvider.InsertAndUpdateFilter); - } - - private static IReadOnlyList DeterminePropertiesWithNavigations( - IEntityType entityType, - bool? inlinedOwnTypes, - Func, bool> filter) - { - var properties = entityType.GetFlattenedProperties() - .Where(p => filter(p, Array.Empty())) - .Select(p => new PropertyWithNavigations(p, Array.Empty())) - .ToList(); - - foreach (var navigation in entityType.GetOwnedTypesProperties(inlinedOwnTypes)) - { - var navigations = new[] { navigation }; - properties.AddPropertiesAndOwnedTypesRecursively(navigation.TargetEntityType, navigations, inlinedOwnTypes, filter); - } - - return properties; - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +internal class DefaultPropertiesEntityPropertiesProvider : IEntityPropertiesProvider +{ + public IReadOnlyList GetPropertiesForTempTable(IEntityType entityType) + { + if (entityType.GetOwnedTypesProperties(null).Any()) + throw new NotSupportedException("Temp tables don't support owned entities."); + + return entityType.GetFlattenedProperties().ToList(); + } + + public IReadOnlyList GetKeyProperties(IEntityType entityType) + { + var pk = entityType.FindPrimaryKey()?.Properties; + + if (pk is null or { Count: 0 }) + throw new InvalidOperationException($"The entity '{entityType.Name}' has no primary key. Please provide key properties to perform JOIN/match on."); + + return pk; + } + + public IReadOnlyList GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes) + { + return DeterminePropertiesWithNavigations(entityType, inlinedOwnTypes, IEntityPropertiesProvider.InsertAndUpdateFilter); + } + + public IReadOnlyList GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes) + { + return DeterminePropertiesWithNavigations(entityType, inlinedOwnTypes, IEntityPropertiesProvider.InsertAndUpdateFilter); + } + + private static IReadOnlyList DeterminePropertiesWithNavigations( + IEntityType entityType, + bool? inlinedOwnTypes, + Func, bool> filter) + { + var properties = entityType.GetFlattenedProperties() + .Where(p => filter(p, Array.Empty())) + .Select(p => new PropertyWithNavigations(p, Array.Empty())) + .ToList(); + + foreach (var navigation in entityType.GetOwnedTypesProperties(inlinedOwnTypes)) + { + var navigations = new[] { navigation }; + properties.AddPropertiesAndOwnedTypesRecursively(navigation.TargetEntityType, navigations, inlinedOwnTypes, filter); + } + + return properties; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ExcludingEntityPropertiesProvider.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ExcludingEntityPropertiesProvider.cs index 88684ebc..8f2b02ab 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ExcludingEntityPropertiesProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ExcludingEntityPropertiesProvider.cs @@ -1,56 +1,56 @@ -using System.Reflection; -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -internal sealed class ExcludingEntityPropertiesProvider( - IReadOnlyList members) - : IEntityPropertiesProvider -{ - private (IEntityType Type, IReadOnlyList Properties)? _cache; - - public IReadOnlyList GetPropertiesForTempTable(IEntityType entityType) - { - return Filter(entityType, IEntityPropertiesProvider.Default.GetPropertiesForTempTable(entityType)); - } - - private IReadOnlyList Filter(IEntityType entityType, IReadOnlyList properties) - { - var propertiesToExclude = GetPropertiesToExclude(entityType); - return properties.Where(p => !propertiesToExclude.Contains(p.Property)) - .ToList(); - } - - private IReadOnlyList Filter(IEntityType entityType, IReadOnlyList properties) - { - var propertiesToExclude = GetPropertiesToExclude(entityType); - return properties.Where(p => !propertiesToExclude.Contains(p)) - .ToList(); - } - - private IReadOnlyList GetPropertiesToExclude(IEntityType entityType) - { - var cache = _cache; - - if (cache?.Type != entityType) - _cache = cache = (entityType, members.ConvertToEntityProperties(entityType)); - - return cache.Value.Properties; - } - - public IReadOnlyList GetKeyProperties(IEntityType entityType) - { - return Filter(entityType, IEntityPropertiesProvider.Default.GetKeyProperties(entityType)); - } - - public IReadOnlyList GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes) - { - return Filter(entityType, IEntityPropertiesProvider.Default.GetPropertiesForInsert(entityType, inlinedOwnTypes)); - } - - public IReadOnlyList GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes) - { - return Filter(entityType, IEntityPropertiesProvider.Default.GetPropertiesForUpdate(entityType, inlinedOwnTypes)); - } -} +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +internal sealed class ExcludingEntityPropertiesProvider( + IReadOnlyList members) + : IEntityPropertiesProvider +{ + private (IEntityType Type, IReadOnlyList Properties)? _cache; + + public IReadOnlyList GetPropertiesForTempTable(IEntityType entityType) + { + return Filter(entityType, IEntityPropertiesProvider.Default.GetPropertiesForTempTable(entityType)); + } + + private IReadOnlyList Filter(IEntityType entityType, IReadOnlyList properties) + { + var propertiesToExclude = GetPropertiesToExclude(entityType); + return properties.Where(p => !propertiesToExclude.Contains(p.Property)) + .ToList(); + } + + private IReadOnlyList Filter(IEntityType entityType, IReadOnlyList properties) + { + var propertiesToExclude = GetPropertiesToExclude(entityType); + return properties.Where(p => !propertiesToExclude.Contains(p)) + .ToList(); + } + + private IReadOnlyList GetPropertiesToExclude(IEntityType entityType) + { + var cache = _cache; + + if (cache?.Type != entityType) + _cache = cache = (entityType, members.ConvertToEntityProperties(entityType)); + + return cache.Value.Properties; + } + + public IReadOnlyList GetKeyProperties(IEntityType entityType) + { + return Filter(entityType, IEntityPropertiesProvider.Default.GetKeyProperties(entityType)); + } + + public IReadOnlyList GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes) + { + return Filter(entityType, IEntityPropertiesProvider.Default.GetPropertiesForInsert(entityType, inlinedOwnTypes)); + } + + public IReadOnlyList GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes) + { + return Filter(entityType, IEntityPropertiesProvider.Default.GetPropertiesForUpdate(entityType, inlinedOwnTypes)); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertExecutor.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertExecutor.cs index 57be1cb6..087f8a52 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertExecutor.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertExecutor.cs @@ -1,28 +1,28 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Executes bulk inserts. -/// -public interface IBulkInsertExecutor -{ - /// - /// Creates options with default values. - /// - /// Properties to insert. - /// Options to use with . - IBulkInsertOptions CreateOptions(IEntityPropertiesProvider? propertiesToInsert = null); - - /// - /// Performs bulk insert. - /// - /// Entities to insert. - /// Options. - /// Cancellation token. - /// Entity/query type. - /// - Task BulkInsertAsync( - IEnumerable entities, - IBulkInsertOptions options, - CancellationToken cancellationToken = default) - where T : class; -} +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Executes bulk inserts. +/// +public interface IBulkInsertExecutor +{ + /// + /// Creates options with default values. + /// + /// Properties to insert. + /// Options to use with . + IBulkInsertOptions CreateOptions(IEntityPropertiesProvider? propertiesToInsert = null); + + /// + /// Performs bulk insert. + /// + /// Entities to insert. + /// Options. + /// Cancellation token. + /// Entity/query type. + /// + Task BulkInsertAsync( + IEnumerable entities, + IBulkInsertOptions options, + CancellationToken cancellationToken = default) + where T : class; +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertOptions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertOptions.cs index ea42af02..fba6e599 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertOptions.cs @@ -1,13 +1,13 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Bulk insert options. -/// -public interface IBulkInsertOptions -{ - /// - /// Properties to insert. - /// If the is null then all properties of the entity are going to be inserted. - /// - IEntityPropertiesProvider? PropertiesToInsert { get; } -} +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Bulk insert options. +/// +public interface IBulkInsertOptions +{ + /// + /// Properties to insert. + /// If the is null then all properties of the entity are going to be inserted. + /// + IEntityPropertiesProvider? PropertiesToInsert { get; } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertOrUpdateExecutor.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertOrUpdateExecutor.cs index 0ecb1172..caffbba7 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertOrUpdateExecutor.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertOrUpdateExecutor.cs @@ -1,33 +1,33 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Executes bulk insert or update. -/// -public interface IBulkInsertOrUpdateExecutor -{ - /// - /// Creates options with default values. - /// - /// Provides properties to insert. - /// Provides properties to update. - /// Provides key properties. - /// Options to use with . - IBulkInsertOrUpdateOptions CreateOptions( - IEntityPropertiesProvider? propertiesToInsert = null, - IEntityPropertiesProvider? propertiesToUpdate = null, - IEntityPropertiesProvider? keyProperties = null); - - /// - /// Performs bulk insert or update. - /// - /// Entities to insert or update. - /// Options. - /// Cancellation token. - /// Type of the entities to insert or update. - /// Number of affected rows. - Task BulkInsertOrUpdateAsync( - IEnumerable entities, - IBulkInsertOrUpdateOptions options, - CancellationToken cancellationToken = default) - where T : class; -} +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Executes bulk insert or update. +/// +public interface IBulkInsertOrUpdateExecutor +{ + /// + /// Creates options with default values. + /// + /// Provides properties to insert. + /// Provides properties to update. + /// Provides key properties. + /// Options to use with . + IBulkInsertOrUpdateOptions CreateOptions( + IEntityPropertiesProvider? propertiesToInsert = null, + IEntityPropertiesProvider? propertiesToUpdate = null, + IEntityPropertiesProvider? keyProperties = null); + + /// + /// Performs bulk insert or update. + /// + /// Entities to insert or update. + /// Options. + /// Cancellation token. + /// Type of the entities to insert or update. + /// Number of affected rows. + Task BulkInsertOrUpdateAsync( + IEnumerable entities, + IBulkInsertOrUpdateOptions options, + CancellationToken cancellationToken = default) + where T : class; +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertOrUpdateOptions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertOrUpdateOptions.cs index 6b2a677b..81e944b6 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertOrUpdateOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkInsertOrUpdateOptions.cs @@ -1,25 +1,25 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Options for bulk insert or update. -/// -public interface IBulkInsertOrUpdateOptions -{ - /// - /// Properties to insert. - /// If the is null then all properties of the entity are going to be inserted. - /// - IEntityPropertiesProvider? PropertiesToInsert { get; } - - /// - /// Properties to update. - /// If the is null then all properties of the entity are going to be updated. - /// - IEntityPropertiesProvider? PropertiesToUpdate { get; } - - /// - /// Properties to perform the JOIN/match on. - /// The primary key of the entity is used by default. - /// - IEntityPropertiesProvider? KeyProperties { get; } +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Options for bulk insert or update. +/// +public interface IBulkInsertOrUpdateOptions +{ + /// + /// Properties to insert. + /// If the is null then all properties of the entity are going to be inserted. + /// + IEntityPropertiesProvider? PropertiesToInsert { get; } + + /// + /// Properties to update. + /// If the is null then all properties of the entity are going to be updated. + /// + IEntityPropertiesProvider? PropertiesToUpdate { get; } + + /// + /// Properties to perform the JOIN/match on. + /// The primary key of the entity is used by default. + /// + IEntityPropertiesProvider? KeyProperties { get; } } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkOperationContext.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkOperationContext.cs index baa5653a..fc3aa862 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkOperationContext.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkOperationContext.cs @@ -1,27 +1,27 @@ -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Bulk operation context. -/// -public interface IBulkOperationContext -{ - /// - /// Properties participating in the bulk operation. - /// - IReadOnlyList Properties { get; } - - /// - /// Indication whether there are properties that belongs to a different table. - /// - bool HasExternalProperties { get; } - - /// - /// Creates a new . - /// - /// - /// - /// - IEntityDataReader CreateReader(IEnumerable entities); -} +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Bulk operation context. +/// +public interface IBulkOperationContext +{ + /// + /// Properties participating in the bulk operation. + /// + IReadOnlyList Properties { get; } + + /// + /// Indication whether there are properties that belongs to a different table. + /// + bool HasExternalProperties { get; } + + /// + /// Creates a new . + /// + /// + /// + /// + IEntityDataReader CreateReader(IEnumerable entities); +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkUpdateExecutor.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkUpdateExecutor.cs index 1675d7e4..2dc86e40 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkUpdateExecutor.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkUpdateExecutor.cs @@ -1,29 +1,29 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Executes bulk updates. -/// -public interface IBulkUpdateExecutor -{ - /// - /// Creates options with default values. - /// - /// Provides properties to update. - /// Provides key properties. - /// Options to use with . - IBulkUpdateOptions CreateOptions(IEntityPropertiesProvider? propertiesToUpdate = null, IEntityPropertiesProvider? keyProperties = null); - - /// - /// Performs bulk update. - /// - /// Entities to update. - /// Options. - /// Cancellation token. - /// Type of the entities to update. - /// Number of affected rows. - Task BulkUpdateAsync( - IEnumerable entities, - IBulkUpdateOptions options, - CancellationToken cancellationToken = default) - where T : class; -} +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Executes bulk updates. +/// +public interface IBulkUpdateExecutor +{ + /// + /// Creates options with default values. + /// + /// Provides properties to update. + /// Provides key properties. + /// Options to use with . + IBulkUpdateOptions CreateOptions(IEntityPropertiesProvider? propertiesToUpdate = null, IEntityPropertiesProvider? keyProperties = null); + + /// + /// Performs bulk update. + /// + /// Entities to update. + /// Options. + /// Cancellation token. + /// Type of the entities to update. + /// Number of affected rows. + Task BulkUpdateAsync( + IEnumerable entities, + IBulkUpdateOptions options, + CancellationToken cancellationToken = default) + where T : class; +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkUpdateOptions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkUpdateOptions.cs index 3be6e5a8..53e459ff 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkUpdateOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IBulkUpdateOptions.cs @@ -1,19 +1,19 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Bulk update options. -/// -public interface IBulkUpdateOptions -{ - /// - /// Properties to update. - /// If the is null then all properties of the entity are going to be updated. - /// - IEntityPropertiesProvider? PropertiesToUpdate { get; } - - /// - /// Properties to perform the JOIN/match on. - /// The primary key of the entity is used by default. - /// - IEntityPropertiesProvider? KeyProperties { get; } +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Bulk update options. +/// +public interface IBulkUpdateOptions +{ + /// + /// Properties to update. + /// If the is null then all properties of the entity are going to be updated. + /// + IEntityPropertiesProvider? PropertiesToUpdate { get; } + + /// + /// Properties to perform the JOIN/match on. + /// The primary key of the entity is used by default. + /// + IEntityPropertiesProvider? KeyProperties { get; } } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IEntityPropertiesProvider.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IEntityPropertiesProvider.cs index 9ba5615e..7c0ed2a7 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IEntityPropertiesProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IEntityPropertiesProvider.cs @@ -1,103 +1,103 @@ -using System.Linq.Expressions; -using System.Reflection; -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Provides entity properties to work with. -/// -public interface IEntityPropertiesProvider -{ - /// - /// An with 0 properties. - /// - public static readonly IEntityPropertiesProvider Empty = new IncludingEntityPropertiesProvider(Array.Empty()); - - /// - /// An with all properties of an entity. - /// - public static readonly IEntityPropertiesProvider Default = new DefaultPropertiesEntityPropertiesProvider(); - - /// - /// Creates a new with specified . - /// - /// Members to create the provider for. - /// A new instance of . - public static IEntityPropertiesProvider Include(IReadOnlyList members) - { - return new IncludingEntityPropertiesProvider(members); - } - - /// - /// Extracts members from the provided and creates an - /// which provides the specified members to the callers. - /// - /// Projection to extract the members from. - /// Type of the entity. - /// An instance of containing members extracted from . - /// is null. - /// No members couldn't be extracted. - /// The contains unsupported expressions. - public static IEntityPropertiesProvider Include(Expression> projection) - { - ArgumentNullException.ThrowIfNull(projection); - - var members = projection.ExtractMembers(); - - return members.Count == 0 ? Empty : new IncludingEntityPropertiesProvider(members); - } - - /// - /// Extracts members from the provided and creates an - /// which provides all properties of the corresponding entity besides the specified members. - /// - /// - /// - /// - public static IEntityPropertiesProvider Exclude(Expression> projection) - { - ArgumentNullException.ThrowIfNull(projection); - - var members = projection.ExtractMembers(); - - return members.Count == 0 ? Default : new ExcludingEntityPropertiesProvider(members); - } - - /// - /// Determines properties to include into a temp table into. - /// - /// Entity type. - /// Properties to include into a temp table. - IReadOnlyList GetPropertiesForTempTable(IEntityType entityType); - - /// - /// Determines properties to include into a temp table into. - /// - /// Entity type. - /// Properties to include into a temp table. - IReadOnlyList GetKeyProperties(IEntityType entityType); - - /// - /// Determines properties to insert into a (temp) table. - /// - /// Entity type. - /// Indication whether inlined (true), separated (false) or all owned types to return. - /// Properties to insert into a (temp) table. - IReadOnlyList GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes); - - /// - /// Determines properties to use in update of a table. - /// - /// Entity type. - /// Indication whether inlined (true), separated (false) or all owned types to return. - /// Properties to use in update of a table. - IReadOnlyList GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes); - - internal static bool InsertAndUpdateFilter(IProperty property, IReadOnlyList navigations) - { - return property.GetBeforeSaveBehavior() != PropertySaveBehavior.Ignore && - (navigations.Count == 0 || !navigations[^1].IsInlined() || !property.IsKey()); - } -} +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Provides entity properties to work with. +/// +public interface IEntityPropertiesProvider +{ + /// + /// An with 0 properties. + /// + public static readonly IEntityPropertiesProvider Empty = new IncludingEntityPropertiesProvider(Array.Empty()); + + /// + /// An with all properties of an entity. + /// + public static readonly IEntityPropertiesProvider Default = new DefaultPropertiesEntityPropertiesProvider(); + + /// + /// Creates a new with specified . + /// + /// Members to create the provider for. + /// A new instance of . + public static IEntityPropertiesProvider Include(IReadOnlyList members) + { + return new IncludingEntityPropertiesProvider(members); + } + + /// + /// Extracts members from the provided and creates an + /// which provides the specified members to the callers. + /// + /// Projection to extract the members from. + /// Type of the entity. + /// An instance of containing members extracted from . + /// is null. + /// No members couldn't be extracted. + /// The contains unsupported expressions. + public static IEntityPropertiesProvider Include(Expression> projection) + { + ArgumentNullException.ThrowIfNull(projection); + + var members = projection.ExtractMembers(); + + return members.Count == 0 ? Empty : new IncludingEntityPropertiesProvider(members); + } + + /// + /// Extracts members from the provided and creates an + /// which provides all properties of the corresponding entity besides the specified members. + /// + /// + /// + /// + public static IEntityPropertiesProvider Exclude(Expression> projection) + { + ArgumentNullException.ThrowIfNull(projection); + + var members = projection.ExtractMembers(); + + return members.Count == 0 ? Default : new ExcludingEntityPropertiesProvider(members); + } + + /// + /// Determines properties to include into a temp table into. + /// + /// Entity type. + /// Properties to include into a temp table. + IReadOnlyList GetPropertiesForTempTable(IEntityType entityType); + + /// + /// Determines properties to include into a temp table into. + /// + /// Entity type. + /// Properties to include into a temp table. + IReadOnlyList GetKeyProperties(IEntityType entityType); + + /// + /// Determines properties to insert into a (temp) table. + /// + /// Entity type. + /// Indication whether inlined (true), separated (false) or all owned types to return. + /// Properties to insert into a (temp) table. + IReadOnlyList GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes); + + /// + /// Determines properties to use in update of a table. + /// + /// Entity type. + /// Indication whether inlined (true), separated (false) or all owned types to return. + /// Properties to use in update of a table. + IReadOnlyList GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes); + + internal static bool InsertAndUpdateFilter(IProperty property, IReadOnlyList navigations) + { + return property.GetBeforeSaveBehavior() != PropertySaveBehavior.Ignore && + (navigations.Count == 0 || !navigations[^1].IsInlined() || !property.IsKey()); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IOwnedTypeBulkOperationContext.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IOwnedTypeBulkOperationContext.cs index 806e88b7..a3e28455 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IOwnedTypeBulkOperationContext.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IOwnedTypeBulkOperationContext.cs @@ -1,19 +1,19 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Bulk operation context for an owned type. -/// -public interface IOwnedTypeBulkOperationContext : IBulkOperationContext -{ - /// - /// Type of the owned type. - /// - IEntityType EntityType { get; } - - /// - /// A collection of owned types. - /// - IEnumerable Entities { get; } -} +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Bulk operation context for an owned type. +/// +public interface IOwnedTypeBulkOperationContext : IBulkOperationContext +{ + /// + /// Type of the owned type. + /// + IEntityType EntityType { get; } + + /// + /// A collection of owned types. + /// + IEnumerable Entities { get; } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ITempTableBulkInsertExecutor.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ITempTableBulkInsertExecutor.cs index c8d0537e..1e6ab856 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ITempTableBulkInsertExecutor.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ITempTableBulkInsertExecutor.cs @@ -1,43 +1,43 @@ -using Thinktecture.EntityFrameworkCore.TempTables; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Inserts entities into a temp table. -/// -public interface ITempTableBulkInsertExecutor -{ - /// - /// Creates options with default values. - /// - /// Properties to insert. - /// Options to use with . - ITempTableBulkInsertOptions CreateOptions(IEntityPropertiesProvider? propertiesToInsert = null); - - /// - /// Inserts the provided into a temp table. - /// - /// Entities to insert. - /// Options. - /// Cancellation token. - /// Type of the entities. - /// A query returning the inserted . - Task> BulkInsertIntoTempTableAsync( - IEnumerable entities, - ITempTableBulkInsertOptions options, - CancellationToken cancellationToken = default) - where T : class; - - /// - /// Inserts the provided into a temp table. - /// - /// Values to insert. - /// Options. - /// Cancellation token. - /// Type of the values. - /// A query returning the inserted . - Task> BulkInsertValuesIntoTempTableAsync( - IEnumerable values, - ITempTableBulkInsertOptions options, - CancellationToken cancellationToken); -} +using Thinktecture.EntityFrameworkCore.TempTables; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Inserts entities into a temp table. +/// +public interface ITempTableBulkInsertExecutor +{ + /// + /// Creates options with default values. + /// + /// Properties to insert. + /// Options to use with . + ITempTableBulkInsertOptions CreateOptions(IEntityPropertiesProvider? propertiesToInsert = null); + + /// + /// Inserts the provided into a temp table. + /// + /// Entities to insert. + /// Options. + /// Cancellation token. + /// Type of the entities. + /// A query returning the inserted . + Task> BulkInsertIntoTempTableAsync( + IEnumerable entities, + ITempTableBulkInsertOptions options, + CancellationToken cancellationToken = default) + where T : class; + + /// + /// Inserts the provided into a temp table. + /// + /// Values to insert. + /// Options. + /// Cancellation token. + /// Type of the values. + /// A query returning the inserted . + Task> BulkInsertValuesIntoTempTableAsync( + IEnumerable values, + ITempTableBulkInsertOptions options, + CancellationToken cancellationToken); +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ITempTableBulkInsertOptions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ITempTableBulkInsertOptions.cs index 7ed38742..fb888a56 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ITempTableBulkInsertOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ITempTableBulkInsertOptions.cs @@ -1,42 +1,42 @@ -using Thinktecture.EntityFrameworkCore.TempTables; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Options for bulk insert into a temp table. -/// -public interface ITempTableBulkInsertOptions -{ - /// - /// Drops/truncates the temp table if the table exists already. - /// Default is false. - /// - bool TruncateTableIfExists { get; } - - /// - /// Indication whether to drop the temp table on dispose of . - /// Default is true. - /// - /// - /// Set to false for more performance if the same temp table is re-used very often. - /// Set to true on re-use. - /// - bool DropTableOnDispose { get; } - - /// - /// Provides the name to create a temp table with. - /// The default is the . - /// - ITempTableNameProvider? TableNameProvider { get; } - - /// - /// Provides the corresponding columns if the primary key should be created. - /// The default is . - /// - IPrimaryKeyPropertiesProvider? PrimaryKeyCreation { get; } - - /// - /// Gets properties to insert. - /// - IEntityPropertiesProvider? PropertiesToInsert { get; } -} +using Thinktecture.EntityFrameworkCore.TempTables; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Options for bulk insert into a temp table. +/// +public interface ITempTableBulkInsertOptions +{ + /// + /// Drops/truncates the temp table if the table exists already. + /// Default is false. + /// + bool TruncateTableIfExists { get; } + + /// + /// Indication whether to drop the temp table on dispose of . + /// Default is true. + /// + /// + /// Set to false for more performance if the same temp table is re-used very often. + /// Set to true on re-use. + /// + bool DropTableOnDispose { get; } + + /// + /// Provides the name to create a temp table with. + /// The default is the . + /// + ITempTableNameProvider? TableNameProvider { get; } + + /// + /// Provides the corresponding columns if the primary key should be created. + /// The default is . + /// + IPrimaryKeyPropertiesProvider? PrimaryKeyCreation { get; } + + /// + /// Gets properties to insert. + /// + IEntityPropertiesProvider? PropertiesToInsert { get; } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ITruncateTableExecutor.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ITruncateTableExecutor.cs index fb06e60e..b75c2b6d 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ITruncateTableExecutor.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/ITruncateTableExecutor.cs @@ -1,22 +1,22 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Truncates table. -/// -public interface ITruncateTableExecutor -{ - /// - /// Truncates the table of the entity of type . - /// - /// Cancellation token. - /// Type of the entity to truncate. - Task TruncateTableAsync(CancellationToken cancellationToken = default) - where T : class; - - /// - /// Truncates the table of the entity of type . - /// - /// Type of the entity to truncate. - /// Cancellation token. - Task TruncateTableAsync(Type type, CancellationToken cancellationToken = default); -} +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Truncates table. +/// +public interface ITruncateTableExecutor +{ + /// + /// Truncates the table of the entity of type . + /// + /// Cancellation token. + /// Type of the entity to truncate. + Task TruncateTableAsync(CancellationToken cancellationToken = default) + where T : class; + + /// + /// Truncates the table of the entity of type . + /// + /// Type of the entity to truncate. + /// Cancellation token. + Task TruncateTableAsync(Type type, CancellationToken cancellationToken = default); +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IncludingEntityPropertiesProvider.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IncludingEntityPropertiesProvider.cs index c3f0a9e2..9e81643d 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IncludingEntityPropertiesProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/BulkOperations/IncludingEntityPropertiesProvider.cs @@ -1,43 +1,43 @@ -using System.Reflection; -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -internal sealed class IncludingEntityPropertiesProvider : IEntityPropertiesProvider -{ - private readonly IReadOnlyList _members; - - public IncludingEntityPropertiesProvider(IReadOnlyList members) - { - _members = members ?? throw new ArgumentNullException(nameof(members)); - } - - public IReadOnlyList GetPropertiesForTempTable(IEntityType entityType) - { - return _members.ConvertToEntityProperties(entityType); - } - - public IReadOnlyList GetKeyProperties(IEntityType entityType) - { - return _members.ConvertToEntityProperties(entityType); - } - - public IReadOnlyList GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes) - { - return GetProperties(entityType, inlinedOwnTypes, IEntityPropertiesProvider.InsertAndUpdateFilter); - } - - public IReadOnlyList GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes) - { - return GetProperties(entityType, inlinedOwnTypes, IEntityPropertiesProvider.InsertAndUpdateFilter); - } - - private IReadOnlyList GetProperties( - IEntityType entityType, - bool? inlinedOwnTypes, - Func, bool> filter) - { - return _members.ConvertToEntityPropertiesWithNavigations(entityType, inlinedOwnTypes, filter); - } -} +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +internal sealed class IncludingEntityPropertiesProvider : IEntityPropertiesProvider +{ + private readonly IReadOnlyList _members; + + public IncludingEntityPropertiesProvider(IReadOnlyList members) + { + _members = members ?? throw new ArgumentNullException(nameof(members)); + } + + public IReadOnlyList GetPropertiesForTempTable(IEntityType entityType) + { + return _members.ConvertToEntityProperties(entityType); + } + + public IReadOnlyList GetKeyProperties(IEntityType entityType) + { + return _members.ConvertToEntityProperties(entityType); + } + + public IReadOnlyList GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes) + { + return GetProperties(entityType, inlinedOwnTypes, IEntityPropertiesProvider.InsertAndUpdateFilter); + } + + public IReadOnlyList GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes) + { + return GetProperties(entityType, inlinedOwnTypes, IEntityPropertiesProvider.InsertAndUpdateFilter); + } + + private IReadOnlyList GetProperties( + IEntityType entityType, + bool? inlinedOwnTypes, + Func, bool> filter) + { + return _members.ConvertToEntityPropertiesWithNavigations(entityType, inlinedOwnTypes, filter); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/Internal/ThinktectureBulkOperationsAnnotationNames.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/Internal/ThinktectureBulkOperationsAnnotationNames.cs index ffd02879..cb815c43 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/Internal/ThinktectureBulkOperationsAnnotationNames.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/Internal/ThinktectureBulkOperationsAnnotationNames.cs @@ -1,14 +1,14 @@ -namespace Thinktecture.EntityFrameworkCore.Internal; - -/// -/// Annotation names. -/// -public static class ThinktectureBulkOperationsAnnotationNames -{ - private const string _PREFIX = "Thinktecture:BulkOperations:"; - - /// - /// Annotation name for temp table. - /// - public const string TEMP_TABLE = _PREFIX + "TempTable"; -} +namespace Thinktecture.EntityFrameworkCore.Internal; + +/// +/// Annotation names. +/// +public static class ThinktectureBulkOperationsAnnotationNames +{ + private const string _PREFIX = "Thinktecture:BulkOperations:"; + + /// + /// Annotation name for temp table. + /// + public const string TEMP_TABLE = _PREFIX + "TempTable"; +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/AdaptiveEntityTypeConfigurationPrimaryKeyPropertiesProvider.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/AdaptiveEntityTypeConfigurationPrimaryKeyPropertiesProvider.cs index 57ceac0c..e6125dc9 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/AdaptiveEntityTypeConfigurationPrimaryKeyPropertiesProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/AdaptiveEntityTypeConfigurationPrimaryKeyPropertiesProvider.cs @@ -1,19 +1,19 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -internal sealed class AdaptiveEntityTypeConfigurationPrimaryKeyPropertiesProvider : IPrimaryKeyPropertiesProvider -{ - public IReadOnlyCollection GetPrimaryKeyProperties(IEntityType entityType, IReadOnlyCollection tempTableProperties) - { - if (tempTableProperties.Count == 0) - return Array.Empty(); - - var pk = entityType.FindPrimaryKey()?.Properties; - - if (pk is null or { Count: 0 }) - return Array.Empty(); - - return pk.Intersect(tempTableProperties).ToList(); - } -} +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +internal sealed class AdaptiveEntityTypeConfigurationPrimaryKeyPropertiesProvider : IPrimaryKeyPropertiesProvider +{ + public IReadOnlyCollection GetPrimaryKeyProperties(IEntityType entityType, IReadOnlyCollection tempTableProperties) + { + if (tempTableProperties.Count == 0) + return Array.Empty(); + + var pk = entityType.FindPrimaryKey()?.Properties; + + if (pk is null or { Count: 0 }) + return Array.Empty(); + + return pk.Intersect(tempTableProperties).ToList(); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/AdaptiveForcedPrimaryKeyPropertiesProvider.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/AdaptiveForcedPrimaryKeyPropertiesProvider.cs index 0179cfa5..54be37e6 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/AdaptiveForcedPrimaryKeyPropertiesProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/AdaptiveForcedPrimaryKeyPropertiesProvider.cs @@ -1,19 +1,19 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -internal sealed class AdaptiveForcedPrimaryKeyPropertiesProvider : IPrimaryKeyPropertiesProvider -{ - public IReadOnlyCollection GetPrimaryKeyProperties(IEntityType entityType, IReadOnlyCollection tempTableProperties) - { - if (tempTableProperties.Count == 0) - return Array.Empty(); - - var pk = entityType.FindPrimaryKey()?.Properties; - - if (pk is null or { Count: 0 }) - return tempTableProperties; - - return pk.Intersect(tempTableProperties).ToList(); - } -} +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +internal sealed class AdaptiveForcedPrimaryKeyPropertiesProvider : IPrimaryKeyPropertiesProvider +{ + public IReadOnlyCollection GetPrimaryKeyProperties(IEntityType entityType, IReadOnlyCollection tempTableProperties) + { + if (tempTableProperties.Count == 0) + return Array.Empty(); + + var pk = entityType.FindPrimaryKey()?.Properties; + + if (pk is null or { Count: 0 }) + return tempTableProperties; + + return pk.Intersect(tempTableProperties).ToList(); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/CachedTempTableStatement.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/CachedTempTableStatement.cs index 8ea48545..dd92ed11 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/CachedTempTableStatement.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/CachedTempTableStatement.cs @@ -1,29 +1,29 @@ -using Microsoft.EntityFrameworkCore.Storage; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Cached SQL statement for creation of temp tables. -/// -public class CachedTempTableStatement : ICachedTempTableStatement -{ - private readonly Func _statementProvider; - private readonly T _parameter; - - /// - /// Initializes new instance of - /// - /// - /// - public CachedTempTableStatement(T parameter, Func statementProvider) - { - _statementProvider = statementProvider; - _parameter = parameter; - } - - /// - public string GetSqlStatement(ISqlGenerationHelper sqlGenerationHelper, string tableName) - { - return _statementProvider(sqlGenerationHelper, tableName, _parameter); - } -} +using Microsoft.EntityFrameworkCore.Storage; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Cached SQL statement for creation of temp tables. +/// +public class CachedTempTableStatement : ICachedTempTableStatement +{ + private readonly Func _statementProvider; + private readonly T _parameter; + + /// + /// Initializes new instance of + /// + /// + /// + public CachedTempTableStatement(T parameter, Func statementProvider) + { + _statementProvider = statementProvider; + _parameter = parameter; + } + + /// + public string GetSqlStatement(ISqlGenerationHelper sqlGenerationHelper, string tableName) + { + return _statementProvider(sqlGenerationHelper, tableName, _parameter); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ConfiguredPrimaryKeyPropertiesProvider.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ConfiguredPrimaryKeyPropertiesProvider.cs index f483cf3c..fad1bd23 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ConfiguredPrimaryKeyPropertiesProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ConfiguredPrimaryKeyPropertiesProvider.cs @@ -1,30 +1,30 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -internal sealed class ConfiguredPrimaryKeyPropertiesProvider : IPrimaryKeyPropertiesProvider -{ - public IReadOnlyCollection GetPrimaryKeyProperties(IEntityType entityType, IReadOnlyCollection tempTableProperties) - { - ArgumentNullException.ThrowIfNull(entityType); - ArgumentNullException.ThrowIfNull(tempTableProperties); - - var pk = entityType.FindPrimaryKey()?.Properties; - - if (pk is null or { Count: 0 }) - return Array.Empty(); - - var missingColumns = pk.Except(tempTableProperties); - - if (missingColumns.Any()) - { - throw new ArgumentException($""" - Cannot create PRIMARY KEY because not all key columns are part of the temp table. - You may use other key properties providers like '{nameof(IPrimaryKeyPropertiesProvider)}.{nameof(IPrimaryKeyPropertiesProvider.AdaptiveEntityTypeConfiguration)}' instead of '{nameof(IPrimaryKeyPropertiesProvider)}.{nameof(IPrimaryKeyPropertiesProvider.EntityTypeConfiguration)}' to get different behaviors. - Missing columns: {String.Join(", ", missingColumns.Select(p => p.GetColumnName()))}. - """); - } - - return pk; - } -} +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +internal sealed class ConfiguredPrimaryKeyPropertiesProvider : IPrimaryKeyPropertiesProvider +{ + public IReadOnlyCollection GetPrimaryKeyProperties(IEntityType entityType, IReadOnlyCollection tempTableProperties) + { + ArgumentNullException.ThrowIfNull(entityType); + ArgumentNullException.ThrowIfNull(tempTableProperties); + + var pk = entityType.FindPrimaryKey()?.Properties; + + if (pk is null or { Count: 0 }) + return Array.Empty(); + + var missingColumns = pk.Except(tempTableProperties); + + if (missingColumns.Any()) + { + throw new ArgumentException($""" + Cannot create PRIMARY KEY because not all key columns are part of the temp table. + You may use other key properties providers like '{nameof(IPrimaryKeyPropertiesProvider)}.{nameof(IPrimaryKeyPropertiesProvider.AdaptiveEntityTypeConfiguration)}' instead of '{nameof(IPrimaryKeyPropertiesProvider)}.{nameof(IPrimaryKeyPropertiesProvider.EntityTypeConfiguration)}' to get different behaviors. + Missing columns: {String.Join(", ", missingColumns.Select(p => p.GetColumnName()))}. + """); + } + + return pk; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/DefaultTempTableNameProvider.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/DefaultTempTableNameProvider.cs index 0cbe7d25..def751c3 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/DefaultTempTableNameProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/DefaultTempTableNameProvider.cs @@ -1,25 +1,25 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Use the default name of the entity. -/// -public class DefaultTempTableNameProvider : ITempTableNameProvider -{ - /// - /// An instance of . - /// - public static readonly ITempTableNameProvider Instance = new DefaultTempTableNameProvider(); - - /// - public ITempTableNameLease LeaseName(DbContext ctx, IEntityType entityType) - { - ArgumentNullException.ThrowIfNull(entityType); - - var tableName = entityType.GetTableName() - ?? throw new InvalidOperationException($"The entity '{entityType.Name}' has no table name."); - - return new TempTableName(tableName); - } -} +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Use the default name of the entity. +/// +public class DefaultTempTableNameProvider : ITempTableNameProvider +{ + /// + /// An instance of . + /// + public static readonly ITempTableNameProvider Instance = new DefaultTempTableNameProvider(); + + /// + public ITempTableNameLease LeaseName(DbContext ctx, IEntityType entityType) + { + ArgumentNullException.ThrowIfNull(entityType); + + var tableName = entityType.GetTableName() + ?? throw new InvalidOperationException($"The entity '{entityType.Name}' has no table name."); + + return new TempTableName(tableName); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/IPrimaryKeyPropertiesProvider.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/IPrimaryKeyPropertiesProvider.cs index 41fc8cca..c8a774ae 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/IPrimaryKeyPropertiesProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/IPrimaryKeyPropertiesProvider.cs @@ -1,65 +1,65 @@ -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Provides the properties to be used for creation of the primary key. -/// -public interface IPrimaryKeyPropertiesProvider -{ - /// - /// Provides no properties, i.e. no primary key will be created. - /// - public static readonly IPrimaryKeyPropertiesProvider None = new NoPrimaryKeyPropertiesProvider(); - - /// - /// Provides the primary key properties configured for the corresponding . - /// If the entity is keyless then no primary key is created. - /// - /// Is thrown when not all key properties are part of the current temp table. - public static readonly IPrimaryKeyPropertiesProvider EntityTypeConfiguration = new ConfiguredPrimaryKeyPropertiesProvider(); - - /// - /// Provides the primary key properties configured for the corresponding . - /// If the entity is keyless then no primary key is created. - /// Columns which are not part of the actual temp table are skipped. - /// - public static readonly IPrimaryKeyPropertiesProvider AdaptiveEntityTypeConfiguration = new AdaptiveEntityTypeConfigurationPrimaryKeyPropertiesProvider(); - - /// - /// Provides the primary key properties configured for the corresponding . - /// If the entity is keyless then all its properties are used for creation of the primary key. - /// Properties which are not part of the actual temp table are skipped. - /// - public static readonly IPrimaryKeyPropertiesProvider AdaptiveForced = new AdaptiveForcedPrimaryKeyPropertiesProvider(); - - /// - /// Extracts members from the provided . - /// - /// Projection to extract the members from. - /// Type of the entity. - /// An instance of containing members extracted from . - /// is null. - /// No members couldn't be extracted. - /// The contains unsupported expressions. - public static IPrimaryKeyPropertiesProvider From(Expression> projection) - { - ArgumentNullException.ThrowIfNull(projection); - - var members = projection.ExtractMembers(); - - if (members.Count == 0) - throw new ArgumentException("The provided projection contains no properties."); - - return new KeyPropertiesProvider(members); - } - - /// - /// Gets the primary key properties. - /// - /// Entity type to get the primary key properties for. - /// Actual properties of the temp table. - /// Properties to use for creation of the primary key. - IReadOnlyCollection GetPrimaryKeyProperties(IEntityType entityType, IReadOnlyCollection tempTableProperties); -} +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Provides the properties to be used for creation of the primary key. +/// +public interface IPrimaryKeyPropertiesProvider +{ + /// + /// Provides no properties, i.e. no primary key will be created. + /// + public static readonly IPrimaryKeyPropertiesProvider None = new NoPrimaryKeyPropertiesProvider(); + + /// + /// Provides the primary key properties configured for the corresponding . + /// If the entity is keyless then no primary key is created. + /// + /// Is thrown when not all key properties are part of the current temp table. + public static readonly IPrimaryKeyPropertiesProvider EntityTypeConfiguration = new ConfiguredPrimaryKeyPropertiesProvider(); + + /// + /// Provides the primary key properties configured for the corresponding . + /// If the entity is keyless then no primary key is created. + /// Columns which are not part of the actual temp table are skipped. + /// + public static readonly IPrimaryKeyPropertiesProvider AdaptiveEntityTypeConfiguration = new AdaptiveEntityTypeConfigurationPrimaryKeyPropertiesProvider(); + + /// + /// Provides the primary key properties configured for the corresponding . + /// If the entity is keyless then all its properties are used for creation of the primary key. + /// Properties which are not part of the actual temp table are skipped. + /// + public static readonly IPrimaryKeyPropertiesProvider AdaptiveForced = new AdaptiveForcedPrimaryKeyPropertiesProvider(); + + /// + /// Extracts members from the provided . + /// + /// Projection to extract the members from. + /// Type of the entity. + /// An instance of containing members extracted from . + /// is null. + /// No members couldn't be extracted. + /// The contains unsupported expressions. + public static IPrimaryKeyPropertiesProvider From(Expression> projection) + { + ArgumentNullException.ThrowIfNull(projection); + + var members = projection.ExtractMembers(); + + if (members.Count == 0) + throw new ArgumentException("The provided projection contains no properties."); + + return new KeyPropertiesProvider(members); + } + + /// + /// Gets the primary key properties. + /// + /// Entity type to get the primary key properties for. + /// Actual properties of the temp table. + /// Properties to use for creation of the primary key. + IReadOnlyCollection GetPrimaryKeyProperties(IEntityType entityType, IReadOnlyCollection tempTableProperties); +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableCreationOptions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableCreationOptions.cs index b14192ba..384ef60e 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableCreationOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableCreationOptions.cs @@ -1,46 +1,46 @@ -using Thinktecture.EntityFrameworkCore.BulkOperations; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Options required for creation of a temp table. -/// -public interface ITempTableCreationOptions -{ - /// - /// Truncates/drops the temp table before "creation" if the table exists already. - /// Default is false. - /// - /// - /// If the database supports "truncate" then the table is going to be truncated otherwise the table is dropped. - /// If the property is set to false then the temp table is considered a "new table", i.e. no "EXISTS" checks are made. - /// - bool TruncateTableIfExists { get; } - - /// - /// Provides the name to create a temp table with. - /// - ITempTableNameProvider TableNameProvider { get; } - - /// - /// Provides the corresponding columns if the primary key should be created. - /// The default is . - /// - IPrimaryKeyPropertiesProvider PrimaryKeyCreation { get; } - - /// - /// Properties to create temp table with. - /// If the is null then the tamp table will contain all properties of the entity. - /// - IEntityPropertiesProvider? PropertiesToInclude { get; } - - /// - /// Indication whether to drop the temp table on dispose of . - /// Default is true. - /// - /// - /// Set to false for more performance if the same temp table is re-used very often. - /// Set to true on re-use. - /// - bool DropTableOnDispose { get; } -} +using Thinktecture.EntityFrameworkCore.BulkOperations; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Options required for creation of a temp table. +/// +public interface ITempTableCreationOptions +{ + /// + /// Truncates/drops the temp table before "creation" if the table exists already. + /// Default is false. + /// + /// + /// If the database supports "truncate" then the table is going to be truncated otherwise the table is dropped. + /// If the property is set to false then the temp table is considered a "new table", i.e. no "EXISTS" checks are made. + /// + bool TruncateTableIfExists { get; } + + /// + /// Provides the name to create a temp table with. + /// + ITempTableNameProvider TableNameProvider { get; } + + /// + /// Provides the corresponding columns if the primary key should be created. + /// The default is . + /// + IPrimaryKeyPropertiesProvider PrimaryKeyCreation { get; } + + /// + /// Properties to create temp table with. + /// If the is null then the tamp table will contain all properties of the entity. + /// + IEntityPropertiesProvider? PropertiesToInclude { get; } + + /// + /// Indication whether to drop the temp table on dispose of . + /// Default is true. + /// + /// + /// Set to false for more performance if the same temp table is re-used very often. + /// Set to true on re-use. + /// + bool DropTableOnDispose { get; } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableCreator.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableCreator.cs index 26699fc8..8492e1fb 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableCreator.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableCreator.cs @@ -1,25 +1,25 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Creates temp tables. -/// -public interface ITempTableCreator -{ - /// - /// Creates a temp table. - /// - /// Entity/query type. - /// Options. - /// Cancellation token. - /// A reference to a temp table. - /// - /// is null. - /// - /// The provided type is not known by the current . - Task CreateTempTableAsync( - IEntityType entityType, - ITempTableCreationOptions options, - CancellationToken cancellationToken = default); -} +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Creates temp tables. +/// +public interface ITempTableCreator +{ + /// + /// Creates a temp table. + /// + /// Entity/query type. + /// Options. + /// Cancellation token. + /// A reference to a temp table. + /// + /// is null. + /// + /// The provided type is not known by the current . + Task CreateTempTableAsync( + IEntityType entityType, + ITempTableCreationOptions options, + CancellationToken cancellationToken = default); +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableNameLease.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableNameLease.cs index 2eea84da..fb422919 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableNameLease.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableNameLease.cs @@ -1,13 +1,13 @@ -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Contains the name of the temp table. -/// The instance of a should be disposed of along with the corresponding temp table. -/// -public interface ITempTableNameLease : IDisposable -{ - /// - /// The name of the temp table. - /// - string Name { get; } -} +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Contains the name of the temp table. +/// The instance of a should be disposed of along with the corresponding temp table. +/// +public interface ITempTableNameLease : IDisposable +{ + /// + /// The name of the temp table. + /// + string Name { get; } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableNameProvider.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableNameProvider.cs index ea25a567..1d369086 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableNameProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableNameProvider.cs @@ -1,18 +1,18 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Provides the name of the temp table. -/// -public interface ITempTableNameProvider -{ - /// - /// Leases the name for a temp table for provided . - /// The instance of should be disposed of to free the name for re-use. - /// - /// Database context. - /// Entity type to get the name of temp table for. - /// The name of the temp table. - ITempTableNameLease LeaseName(DbContext ctx, IEntityType entityType); -} +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Provides the name of the temp table. +/// +public interface ITempTableNameProvider +{ + /// + /// Leases the name for a temp table for provided . + /// The instance of should be disposed of to free the name for re-use. + /// + /// Database context. + /// Entity type to get the name of temp table for. + /// The name of the temp table. + ITempTableNameLease LeaseName(DbContext ctx, IEntityType entityType); +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableQuery.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableQuery.cs index 47cb436c..e785226e 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableQuery.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableQuery.cs @@ -1,19 +1,19 @@ -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Represents a query pointing to a temp table. -/// Disposal of this query will delete the corresponding temp table. -/// -/// Type of the query item. -public interface ITempTableQuery : IAsyncDisposable, IDisposable -{ - /// - /// The query itself. - /// - IQueryable Query { get; } - - /// - /// The name of the temp table. - /// - string Name { get; } -} +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Represents a query pointing to a temp table. +/// Disposal of this query will delete the corresponding temp table. +/// +/// Type of the query item. +public interface ITempTableQuery : IAsyncDisposable, IDisposable +{ + /// + /// The query itself. + /// + IQueryable Query { get; } + + /// + /// The name of the temp table. + /// + string Name { get; } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableReference.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableReference.cs index 4cb205e0..07a8031f 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableReference.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ITempTableReference.cs @@ -1,12 +1,12 @@ -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Represents a reference to temp table that should be disposed if not further required. -/// -public interface ITempTableReference : IAsyncDisposable, IDisposable -{ - /// - /// The name of temp table. - /// - string Name { get; } -} +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Represents a reference to temp table that should be disposed if not further required. +/// +public interface ITempTableReference : IAsyncDisposable, IDisposable +{ + /// + /// The name of temp table. + /// + string Name { get; } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/KeyPropertiesProvider.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/KeyPropertiesProvider.cs index f25f3fb4..50c5face 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/KeyPropertiesProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/KeyPropertiesProvider.cs @@ -1,30 +1,30 @@ -using System.Reflection; -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -internal class KeyPropertiesProvider : IPrimaryKeyPropertiesProvider -{ - private readonly IReadOnlyList _members; - - public KeyPropertiesProvider(IReadOnlyList members) - { - _members = members; - } - - public IReadOnlyCollection GetPrimaryKeyProperties(IEntityType entityType, IReadOnlyCollection tempTableProperties) - { - var keyProperties = _members.ConvertToEntityProperties(entityType); - var missingColumns = keyProperties.Except(tempTableProperties); - - if (missingColumns.Any()) - { - throw new ArgumentException($""" - Not all key columns are part of the table. - Missing columns: {String.Join(", ", missingColumns.Select(p => p.GetColumnName()))}. - """); - } - - return keyProperties; - } -} +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +internal class KeyPropertiesProvider : IPrimaryKeyPropertiesProvider +{ + private readonly IReadOnlyList _members; + + public KeyPropertiesProvider(IReadOnlyList members) + { + _members = members; + } + + public IReadOnlyCollection GetPrimaryKeyProperties(IEntityType entityType, IReadOnlyCollection tempTableProperties) + { + var keyProperties = _members.ConvertToEntityProperties(entityType); + var missingColumns = keyProperties.Except(tempTableProperties); + + if (missingColumns.Any()) + { + throw new ArgumentException($""" + Not all key columns are part of the table. + Missing columns: {String.Join(", ", missingColumns.Select(p => p.GetColumnName()))}. + """); + } + + return keyProperties; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/CachedTempTableSuffixes.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/CachedTempTableSuffixes.cs index bc0a2362..67b0af54 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/CachedTempTableSuffixes.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/CachedTempTableSuffixes.cs @@ -1,27 +1,27 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables.NameSuffixing; - -internal class CachedTempTableSuffixes -{ - public Dictionary SuffixLookup { get; } - public int NumberOfConsumers { get; private set; } - - public CachedTempTableSuffixes() - { - SuffixLookup = new Dictionary(); - } - - public void IncrementNumberOfConsumers() - { - NumberOfConsumers++; - } - - public void DecrementNumberOfConsumers() - { - if (NumberOfConsumers == 0) - throw new InvalidOperationException("The number of consumers is 0 already."); - - NumberOfConsumers--; - } -} +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables.NameSuffixing; + +internal class CachedTempTableSuffixes +{ + public Dictionary SuffixLookup { get; } + public int NumberOfConsumers { get; private set; } + + public CachedTempTableSuffixes() + { + SuffixLookup = new Dictionary(); + } + + public void IncrementNumberOfConsumers() + { + NumberOfConsumers++; + } + + public void DecrementNumberOfConsumers() + { + if (NumberOfConsumers == 0) + throw new InvalidOperationException("The number of consumers is 0 already."); + + NumberOfConsumers--; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixCache.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixCache.cs index 157a8166..8dabf4ee 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixCache.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixCache.cs @@ -1,52 +1,52 @@ -using System.Data.Common; -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables.NameSuffixing; - -internal class TempTableSuffixCache -{ - private readonly object _lock; - private readonly Dictionary _globalCache; - - public TempTableSuffixCache() - { - _lock = new object(); - _globalCache = new Dictionary(); - } - - public Dictionary LeaseSuffixLookup( - DbConnection connection) - { - ArgumentNullException.ThrowIfNull(connection); - - lock (_lock) - { - if (!_globalCache.TryGetValue(connection, out var cachedSuffixes)) - { - cachedSuffixes = new CachedTempTableSuffixes(); - _globalCache.Add(connection, cachedSuffixes); - } - - cachedSuffixes.IncrementNumberOfConsumers(); - - return cachedSuffixes.SuffixLookup; - } - } - - public void ReturnSuffixLookup( - DbConnection connection) - { - ArgumentNullException.ThrowIfNull(connection); - - lock (_lock) - { - if (!_globalCache.TryGetValue(connection, out var cachedSuffixes)) - return; - - cachedSuffixes.DecrementNumberOfConsumers(); - - if (cachedSuffixes.NumberOfConsumers == 0) - _globalCache.Remove(connection); - } - } -} +using System.Data.Common; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables.NameSuffixing; + +internal class TempTableSuffixCache +{ + private readonly object _lock; + private readonly Dictionary _globalCache; + + public TempTableSuffixCache() + { + _lock = new object(); + _globalCache = new Dictionary(); + } + + public Dictionary LeaseSuffixLookup( + DbConnection connection) + { + ArgumentNullException.ThrowIfNull(connection); + + lock (_lock) + { + if (!_globalCache.TryGetValue(connection, out var cachedSuffixes)) + { + cachedSuffixes = new CachedTempTableSuffixes(); + _globalCache.Add(connection, cachedSuffixes); + } + + cachedSuffixes.IncrementNumberOfConsumers(); + + return cachedSuffixes.SuffixLookup; + } + } + + public void ReturnSuffixLookup( + DbConnection connection) + { + ArgumentNullException.ThrowIfNull(connection); + + lock (_lock) + { + if (!_globalCache.TryGetValue(connection, out var cachedSuffixes)) + return; + + cachedSuffixes.DecrementNumberOfConsumers(); + + if (cachedSuffixes.NumberOfConsumers == 0) + _globalCache.Remove(connection); + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixLease.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixLease.cs index d51e7e5f..258b869c 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixLease.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixLease.cs @@ -1,18 +1,18 @@ -namespace Thinktecture.EntityFrameworkCore.TempTables.NameSuffixing; - -internal readonly struct TempTableSuffixLease : IDisposable -{ - public int Suffix { get; } - private readonly TempTableSuffixes _suffixes; - - public TempTableSuffixLease(int suffix, TempTableSuffixes suffixes) - { - Suffix = suffix; - _suffixes = suffixes ?? throw new ArgumentNullException(nameof(suffixes)); - } - - public void Dispose() - { - _suffixes.Return(Suffix); - } -} +namespace Thinktecture.EntityFrameworkCore.TempTables.NameSuffixing; + +internal readonly struct TempTableSuffixLease : IDisposable +{ + public int Suffix { get; } + private readonly TempTableSuffixes _suffixes; + + public TempTableSuffixLease(int suffix, TempTableSuffixes suffixes) + { + Suffix = suffix; + _suffixes = suffixes ?? throw new ArgumentNullException(nameof(suffixes)); + } + + public void Dispose() + { + _suffixes.Return(Suffix); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixLeasing.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixLeasing.cs index 4c212b9c..8b31187d 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixLeasing.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixLeasing.cs @@ -1,80 +1,80 @@ -using System.Data.Common; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables.NameSuffixing; - -internal class TempTableSuffixLeasing : IDisposable -{ - private readonly object _lock; - private readonly TempTableSuffixCache _cache; - private readonly ICurrentDbContext _currentDbContext; - private DbConnection? _connection; - - private Dictionary? _lookup; - private bool _idDisposed; - - public TempTableSuffixLeasing( - ICurrentDbContext currentDbContext, - TempTableSuffixCache cache) - { - _cache = cache ?? throw new ArgumentNullException(nameof(cache)); - _currentDbContext = currentDbContext ?? throw new ArgumentNullException(nameof(currentDbContext)); - _lock = new object(); - - // don't fetch the connection and suffix lookup immediately but on first use only - } - - public TempTableSuffixLease Lease(IEntityType entityType) - { - ArgumentNullException.ThrowIfNull(entityType); - - EnsureDisposed(); - - if (_lookup == null) - { - lock (_lock) - { - EnsureDisposed(); - - if (_lookup == null) - { - _connection = _currentDbContext.Context.Database.GetDbConnection(); - _lookup = _cache.LeaseSuffixLookup(_connection); - } - } - } - - if (!_lookup.TryGetValue(entityType, out var suffixes)) - { - suffixes = new TempTableSuffixes(); - _lookup.Add(entityType, suffixes); - } - - return suffixes.Lease(); - } - - private void EnsureDisposed() - { - if (_idDisposed) - throw new ObjectDisposedException(nameof(TempTableSuffixLease)); - } - - public void Dispose() - { - if (_idDisposed) - return; - - _idDisposed = true; - - lock (_lock) - { - if (_connection == null) - return; - - _cache.ReturnSuffixLookup(_connection); - _lookup = null; - _connection = null; - } - } -} +using System.Data.Common; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables.NameSuffixing; + +internal class TempTableSuffixLeasing : IDisposable +{ + private readonly object _lock; + private readonly TempTableSuffixCache _cache; + private readonly ICurrentDbContext _currentDbContext; + private DbConnection? _connection; + + private Dictionary? _lookup; + private bool _idDisposed; + + public TempTableSuffixLeasing( + ICurrentDbContext currentDbContext, + TempTableSuffixCache cache) + { + _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + _currentDbContext = currentDbContext ?? throw new ArgumentNullException(nameof(currentDbContext)); + _lock = new object(); + + // don't fetch the connection and suffix lookup immediately but on first use only + } + + public TempTableSuffixLease Lease(IEntityType entityType) + { + ArgumentNullException.ThrowIfNull(entityType); + + EnsureDisposed(); + + if (_lookup == null) + { + lock (_lock) + { + EnsureDisposed(); + + if (_lookup == null) + { + _connection = _currentDbContext.Context.Database.GetDbConnection(); + _lookup = _cache.LeaseSuffixLookup(_connection); + } + } + } + + if (!_lookup.TryGetValue(entityType, out var suffixes)) + { + suffixes = new TempTableSuffixes(); + _lookup.Add(entityType, suffixes); + } + + return suffixes.Lease(); + } + + private void EnsureDisposed() + { + if (_idDisposed) + throw new ObjectDisposedException(nameof(TempTableSuffixLease)); + } + + public void Dispose() + { + if (_idDisposed) + return; + + _idDisposed = true; + + lock (_lock) + { + if (_connection == null) + return; + + _cache.ReturnSuffixLookup(_connection); + _lookup = null; + _connection = null; + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixes.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixes.cs index 4a7d5f97..d4be4599 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixes.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NameSuffixing/TempTableSuffixes.cs @@ -1,48 +1,48 @@ -namespace Thinktecture.EntityFrameworkCore.TempTables.NameSuffixing; - -internal class TempTableSuffixes -{ - private int _numberOfTempTables; - private SortedSet? _suffixes; - - // perf optimization - private int? _firstSuffix; - - public TempTableSuffixLease Lease() - { - if (_suffixes?.Count > 0) - { - var suffix = _suffixes.First(); - _suffixes.Remove(suffix); - return new TempTableSuffixLease(suffix, this); - } - - if (_firstSuffix.HasValue) - { - var suffix = _firstSuffix.Value; - _firstSuffix = null; - return new TempTableSuffixLease(suffix, this); - } - - _numberOfTempTables++; - return new TempTableSuffixLease(_numberOfTempTables, this); - } - - public void Return(int suffix) - { - if (_suffixes == null) - { - if (!_firstSuffix.HasValue) - { - _firstSuffix = suffix; - return; - } - - _suffixes = new SortedSet { _firstSuffix.Value }; - _firstSuffix = null; - } - - if (!_suffixes.Add(suffix)) - throw new InvalidOperationException($"The suffix '{suffix}' is returned already."); - } -} +namespace Thinktecture.EntityFrameworkCore.TempTables.NameSuffixing; + +internal class TempTableSuffixes +{ + private int _numberOfTempTables; + private SortedSet? _suffixes; + + // perf optimization + private int? _firstSuffix; + + public TempTableSuffixLease Lease() + { + if (_suffixes?.Count > 0) + { + var suffix = _suffixes.First(); + _suffixes.Remove(suffix); + return new TempTableSuffixLease(suffix, this); + } + + if (_firstSuffix.HasValue) + { + var suffix = _firstSuffix.Value; + _firstSuffix = null; + return new TempTableSuffixLease(suffix, this); + } + + _numberOfTempTables++; + return new TempTableSuffixLease(_numberOfTempTables, this); + } + + public void Return(int suffix) + { + if (_suffixes == null) + { + if (!_firstSuffix.HasValue) + { + _firstSuffix = suffix; + return; + } + + _suffixes = new SortedSet { _firstSuffix.Value }; + _firstSuffix = null; + } + + if (!_suffixes.Add(suffix)) + throw new InvalidOperationException($"The suffix '{suffix}' is returned already."); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NoPrimaryKeyPropertiesProvider.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NoPrimaryKeyPropertiesProvider.cs index a4f9fd6f..b62032a2 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NoPrimaryKeyPropertiesProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/NoPrimaryKeyPropertiesProvider.cs @@ -1,14 +1,14 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -internal sealed class NoPrimaryKeyPropertiesProvider : IPrimaryKeyPropertiesProvider -{ - public IReadOnlyCollection GetPrimaryKeyProperties(IEntityType entityType, IReadOnlyCollection tempTableProperties) - { - ArgumentNullException.ThrowIfNull(entityType); - ArgumentNullException.ThrowIfNull(tempTableProperties); - - return Array.Empty(); - } -} +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +internal sealed class NoPrimaryKeyPropertiesProvider : IPrimaryKeyPropertiesProvider +{ + public IReadOnlyCollection GetPrimaryKeyProperties(IEntityType entityType, IReadOnlyCollection tempTableProperties) + { + ArgumentNullException.ThrowIfNull(entityType); + ArgumentNullException.ThrowIfNull(tempTableProperties); + + return Array.Empty(); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ReusingTempTableNameProvider.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ReusingTempTableNameProvider.cs index b98248bd..bbc5372f 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ReusingTempTableNameProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/ReusingTempTableNameProvider.cs @@ -1,58 +1,58 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.EntityFrameworkCore.TempTables.NameSuffixing; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Re-uses the temp table names. -/// -public class ReusingTempTableNameProvider : ITempTableNameProvider -{ - /// - /// An instance of . - /// - public static readonly ITempTableNameProvider Instance = new ReusingTempTableNameProvider(); - - /// - public ITempTableNameLease LeaseName(DbContext ctx, IEntityType entityType) - { - ArgumentNullException.ThrowIfNull(ctx); - ArgumentNullException.ThrowIfNull(entityType); - - var nameLeasing = ctx.GetService(); - var suffixLease = nameLeasing.Lease(entityType); - - try - { - var tableName = entityType.GetTableName(); - tableName = $"{tableName}_{suffixLease.Suffix}"; - - return new TempTableNameLease(tableName, suffixLease); - } - catch - { - suffixLease.Dispose(); - throw; - } - } - - private class TempTableNameLease : ITempTableNameLease - { - private TempTableSuffixLease _suffixLease; - - public string Name { get; } - - public TempTableNameLease(string name, TempTableSuffixLease suffixLease) - { - Name = name ?? throw new ArgumentNullException(nameof(name)); - _suffixLease = suffixLease; - } - - public void Dispose() - { - _suffixLease.Dispose(); - _suffixLease = default; - } - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.EntityFrameworkCore.TempTables.NameSuffixing; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Re-uses the temp table names. +/// +public class ReusingTempTableNameProvider : ITempTableNameProvider +{ + /// + /// An instance of . + /// + public static readonly ITempTableNameProvider Instance = new ReusingTempTableNameProvider(); + + /// + public ITempTableNameLease LeaseName(DbContext ctx, IEntityType entityType) + { + ArgumentNullException.ThrowIfNull(ctx); + ArgumentNullException.ThrowIfNull(entityType); + + var nameLeasing = ctx.GetService(); + var suffixLease = nameLeasing.Lease(entityType); + + try + { + var tableName = entityType.GetTableName(); + tableName = $"{tableName}_{suffixLease.Suffix}"; + + return new TempTableNameLease(tableName, suffixLease); + } + catch + { + suffixLease.Dispose(); + throw; + } + } + + private class TempTableNameLease : ITempTableNameLease + { + private TempTableSuffixLease _suffixLease; + + public string Name { get; } + + public TempTableNameLease(string name, TempTableSuffixLease suffixLease) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + _suffixLease = suffixLease; + } + + public void Dispose() + { + _suffixLease.Dispose(); + _suffixLease = default; + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTable.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTable.cs index 9a1fa8a1..26f979db 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTable.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTable.cs @@ -1,17 +1,17 @@ -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Represents a temp table with 1 column. -/// -/// 1st column. -/// Type of column 1. -public sealed record TempTable(TColumn1 Column1); - -/// -/// Represents a temp table with 2 columns. -/// -/// 1st column. -/// 2nd column. -/// Type of column 1. -/// Type of column 2. -public sealed record TempTable(TColumn1 Column1, TColumn2 Column2); +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Represents a temp table with 1 column. +/// +/// 1st column. +/// Type of column 1. +public sealed record TempTable(TColumn1 Column1); + +/// +/// Represents a temp table with 2 columns. +/// +/// 1st column. +/// 2nd column. +/// Type of column 1. +/// Type of column 2. +public sealed record TempTable(TColumn1 Column1, TColumn2 Column2); diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableCreationOptions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableCreationOptions.cs index c525efba..a761f8db 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableCreationOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableCreationOptions.cs @@ -1,64 +1,64 @@ -using System.Diagnostics.CodeAnalysis; -using Thinktecture.EntityFrameworkCore.BulkOperations; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Options required for creation of a temp table -/// -public class TempTableCreationOptions : ITempTableCreationOptions -{ - /// - public bool TruncateTableIfExists { get; set; } - - private ITempTableNameProvider? _tableNameProvider; - - /// - [AllowNull] - public ITempTableNameProvider TableNameProvider - { - get => _tableNameProvider ?? ReusingTempTableNameProvider.Instance; - set => _tableNameProvider = value; - } - - private IPrimaryKeyPropertiesProvider? _primaryKeyCreation; - - /// - [AllowNull] - public IPrimaryKeyPropertiesProvider PrimaryKeyCreation - { - get => _primaryKeyCreation ?? IPrimaryKeyPropertiesProvider.EntityTypeConfiguration; - set => _primaryKeyCreation = value; - } - - /// - public IEntityPropertiesProvider? PropertiesToInclude { get; set; } - - /// - public bool DropTableOnDispose { get; set; } - - /// - /// Initializes a new instance of . - /// - /// Options to take values over. - public TempTableCreationOptions(ITempTableCreationOptions? options = null) - { - if (options is null) - { - DropTableOnDispose = true; - } - else - { - InitializeFrom(options); - } - } - - private void InitializeFrom(ITempTableCreationOptions options) - { - PrimaryKeyCreation = options.PrimaryKeyCreation; - TableNameProvider = options.TableNameProvider; - TruncateTableIfExists = options.TruncateTableIfExists; - PropertiesToInclude = options.PropertiesToInclude; - DropTableOnDispose = options.DropTableOnDispose; - } -} +using System.Diagnostics.CodeAnalysis; +using Thinktecture.EntityFrameworkCore.BulkOperations; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Options required for creation of a temp table +/// +public class TempTableCreationOptions : ITempTableCreationOptions +{ + /// + public bool TruncateTableIfExists { get; set; } + + private ITempTableNameProvider? _tableNameProvider; + + /// + [AllowNull] + public ITempTableNameProvider TableNameProvider + { + get => _tableNameProvider ?? ReusingTempTableNameProvider.Instance; + set => _tableNameProvider = value; + } + + private IPrimaryKeyPropertiesProvider? _primaryKeyCreation; + + /// + [AllowNull] + public IPrimaryKeyPropertiesProvider PrimaryKeyCreation + { + get => _primaryKeyCreation ?? IPrimaryKeyPropertiesProvider.EntityTypeConfiguration; + set => _primaryKeyCreation = value; + } + + /// + public IEntityPropertiesProvider? PropertiesToInclude { get; set; } + + /// + public bool DropTableOnDispose { get; set; } + + /// + /// Initializes a new instance of . + /// + /// Options to take values over. + public TempTableCreationOptions(ITempTableCreationOptions? options = null) + { + if (options is null) + { + DropTableOnDispose = true; + } + else + { + InitializeFrom(options); + } + } + + private void InitializeFrom(ITempTableCreationOptions options) + { + PrimaryKeyCreation = options.PrimaryKeyCreation; + TableNameProvider = options.TableNameProvider; + TruncateTableIfExists = options.TruncateTableIfExists; + PropertiesToInclude = options.PropertiesToInclude; + DropTableOnDispose = options.DropTableOnDispose; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableName.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableName.cs index b36a616e..ab11e8c7 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableName.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableName.cs @@ -1,16 +1,16 @@ -namespace Thinktecture.EntityFrameworkCore.TempTables; - -internal class TempTableName : ITempTableNameLease -{ - public string Name { get; } - - public TempTableName(string name) - { - Name = name ?? throw new ArgumentNullException(nameof(name)); - } - - public void Dispose() - { - // Nothing to dispose. - } -} +namespace Thinktecture.EntityFrameworkCore.TempTables; + +internal class TempTableName : ITempTableNameLease +{ + public string Name { get; } + + public TempTableName(string name) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + } + + public void Dispose() + { + // Nothing to dispose. + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableQuery.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableQuery.cs index 924f5ffb..c567b8ac 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableQuery.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableQuery.cs @@ -1,58 +1,58 @@ -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Represents a query pointing to a temp table. -/// -/// Type of the query item. -public sealed class TempTableQuery : ITempTableQuery -{ - private ITempTableReference? _tempTableReference; - private IQueryable? _query; - - /// - /// The query itself. - /// - public IQueryable Query => _query ?? throw new ObjectDisposedException(nameof(TempTableQuery)); - - /// - /// The name of the temp table. - /// - public string Name => _tempTableReference?.Name ?? throw new ObjectDisposedException(nameof(TempTableQuery)); - - /// - /// Initializes new instance of . - /// - /// Query. - /// Reference to a temp table. - public TempTableQuery(IQueryable query, ITempTableReference tempTableReference) - { - _query = query ?? throw new ArgumentNullException(nameof(query)); - _tempTableReference = tempTableReference ?? throw new ArgumentNullException(nameof(tempTableReference)); - } - - /// - public void Dispose() - { - var tableRef = _tempTableReference; - - if (tableRef == null) - return; - - tableRef.Dispose(); - _tempTableReference = null; - _query = null; - } - - /// - public async ValueTask DisposeAsync() - { - var tableRef = _tempTableReference; - - if (tableRef == null) - return; - - await tableRef.DisposeAsync().ConfigureAwait(false); - _tempTableReference = null; - _query = null; - } -} +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Represents a query pointing to a temp table. +/// +/// Type of the query item. +public sealed class TempTableQuery : ITempTableQuery +{ + private ITempTableReference? _tempTableReference; + private IQueryable? _query; + + /// + /// The query itself. + /// + public IQueryable Query => _query ?? throw new ObjectDisposedException(nameof(TempTableQuery)); + + /// + /// The name of the temp table. + /// + public string Name => _tempTableReference?.Name ?? throw new ObjectDisposedException(nameof(TempTableQuery)); + + /// + /// Initializes new instance of . + /// + /// Query. + /// Reference to a temp table. + public TempTableQuery(IQueryable query, ITempTableReference tempTableReference) + { + _query = query ?? throw new ArgumentNullException(nameof(query)); + _tempTableReference = tempTableReference ?? throw new ArgumentNullException(nameof(tempTableReference)); + } + + /// + public void Dispose() + { + var tableRef = _tempTableReference; + + if (tableRef == null) + return; + + tableRef.Dispose(); + _tempTableReference = null; + _query = null; + } + + /// + public async ValueTask DisposeAsync() + { + var tableRef = _tempTableReference; + + if (tableRef == null) + return; + + await tableRef.DisposeAsync().ConfigureAwait(false); + _tempTableReference = null; + _query = null; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableStatementCache.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableStatementCache.cs index a2b98b78..9d31e529 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableStatementCache.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/EntityFrameworkCore/TempTables/TempTableStatementCache.cs @@ -1,24 +1,24 @@ -using System.Collections.Concurrent; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Cache for SQL statements for creation of temp tables. -/// -/// The type of the cache key. -public class TempTableStatementCache - where TCacheKey : IEquatable -{ - private readonly ConcurrentDictionary _cache = new(); - - /// - /// Gets cached SQL statement for creation of a temp table or creates a new one and caches it. - /// - /// The cache key. - /// Factory for creation of a new SQL statement for creation of a temp table if the statement for provided is missing. - /// Cached SQL statement. - public ICachedTempTableStatement GetOrAdd(TCacheKey cacheKey, Func factory) - { - return _cache.GetOrAdd(cacheKey, factory); - } -} +using System.Collections.Concurrent; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Cache for SQL statements for creation of temp tables. +/// +/// The type of the cache key. +public class TempTableStatementCache + where TCacheKey : IEquatable +{ + private readonly ConcurrentDictionary _cache = new(); + + /// + /// Gets cached SQL statement for creation of a temp table or creates a new one and caches it. + /// + /// The cache key. + /// Factory for creation of a new SQL statement for creation of a temp table if the statement for provided is missing. + /// Cached SQL statement. + public ICachedTempTableStatement GetOrAdd(TCacheKey cacheKey, Func factory) + { + return _cache.GetOrAdd(cacheKey, factory); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsCollectionExtensions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsCollectionExtensions.cs index f0d0b071..6a2b70cb 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsCollectionExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsCollectionExtensions.cs @@ -1,195 +1,195 @@ -using System.Reflection; -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.EntityFrameworkCore.Data; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extensions for collections. -/// -public static class BulkOperationsCollectionExtensions -{ - internal static IReadOnlyList ConvertToEntityProperties( - this IReadOnlyList members, - IEntityType entityType) - { - if (entityType.GetOwnedTypesProperties(null).Any()) - throw new NotSupportedException($"The entity '{entityType.Name}' must not contain owned entities."); - - var properties = new List(); - - foreach (var memberInfo in members) - { - var property = entityType.FindProperty(memberInfo); - - if (property != null) - { - properties.Add(property); - continue; - } - - var complexProperty = entityType.FindComplexProperty(memberInfo); - - if (complexProperty != null) - { - properties.AddRange(complexProperty.ComplexType.GetFlattenedProperties()); - continue; - } - - throw new InvalidOperationException($"The member '{memberInfo.Name}' of the entity '{entityType.Name}' cannot be written to database."); - } - - return properties; - } - - internal static IReadOnlyList ConvertToEntityPropertiesWithNavigations( - this IReadOnlyList members, - IEntityType entityType, - bool? inlinedOwnTypes, - Func, bool> filter) - { - var properties = new List(); - var navigations = Array.Empty(); - - foreach (var memberInfo in members) - { - var property = entityType.FindProperty(memberInfo); - - if (property != null) - { - if (!filter(property, navigations)) - throw new InvalidOperationException($"The member '{memberInfo.Name}' of the entity '{entityType.Name}' cannot be written to database."); - - properties.Add(new PropertyWithNavigations(property, navigations)); - continue; - } - - var complexProperty = entityType.FindComplexProperty(memberInfo); - - if (complexProperty != null) - { - foreach (var propertyOfComplexType in complexProperty.ComplexType.GetFlattenedProperties()) - { - if (!filter(propertyOfComplexType, navigations)) - throw new InvalidOperationException($"The member '{memberInfo.Name}' of the entity '{entityType.Name}' cannot be written to database."); - - properties.Add(new PropertyWithNavigations(propertyOfComplexType, navigations)); - } - - continue; - } - - var ownedNavi = FindOwnedProperty(entityType, memberInfo); - - if (ownedNavi == null) - throw new Exception($"The member '{memberInfo.Name}' has not been found on entity '{entityType.Name}'."); - - properties.AddPropertiesAndOwnedTypesRecursively(ownedNavi.TargetEntityType, new[] { ownedNavi }, inlinedOwnTypes, filter); - } - - return properties; - } - - private static INavigation? FindOwnedProperty( - IEntityType entityType, - MemberInfo memberInfo) - { - foreach (var ownedTypeNavi in entityType.GetOwnedTypesProperties(null)) // search for all owned properties, i.e., don't use "inlinedOwnTypes" from the caller - { - if (memberInfo.IsEqualTo(ownedTypeNavi.PropertyInfo) || memberInfo.IsEqualTo(ownedTypeNavi.FieldInfo)) - { - if (ownedTypeNavi.IsInlined()) - return ownedTypeNavi; - - throw new NotSupportedException($"Properties of owned types that are saved in a separate table are not supported. Property: {ownedTypeNavi.Name}"); - } - } - - return null; - } - - /// - /// Checks that there are no separate owned types inside a collection property. - /// - /// Properties to check. - /// If a separate owned type is found inside a collection property. - public static void EnsureNoSeparateOwnedTypesInsideCollectionOwnedType(this IReadOnlyList properties) - { - ArgumentNullException.ThrowIfNull(properties); - - foreach (var property in properties) - { - INavigation? collection = null; - - foreach (var navigation in property.Navigations) - { - if (!navigation.IsInlined() && collection is not null) - throw new NotSupportedException($"Non-inlined (i.e. with its own table) nested owned type '{navigation.DeclaringEntityType.Name}.{navigation.Name}' inside another owned type collection '{collection.DeclaringEntityType.Name}.{collection.Name}' is not supported."); - - if (navigation.IsCollection) - collection = navigation; - } - } - } - - /// - /// Separates in own properties and properties belonging to different tables. - /// - /// Properties to separate. - /// A collection of own and "external" properties. - public static (IReadOnlyList sameTable, IReadOnlyList otherTable) SeparateProperties( - this IReadOnlyList properties) - { - var sameTableProperties = new List(); - List? externalProperties = null; - - foreach (var property in properties) - { - if (property.IsInlined) - { - sameTableProperties.Add(property); - } - else - { - externalProperties ??= new List(); - externalProperties.Add(property); - } - } - - return (sameTableProperties, (IReadOnlyList?)externalProperties ?? Array.Empty()); - } - - /// - /// Groups by the owned type property they belong to. - /// - /// Properties to group. - /// A collection of the parent properties. - /// A collection of tuples of the owned type navigation, owned type entities and their properties. - public static IEnumerable<(INavigation, IEnumerable, IReadOnlyList)> GroupExternalProperties( - this IReadOnlyList externalProperties, - IReadOnlyList parentEntities) - { - foreach (var group in externalProperties.Select(p => p.DropUntilSeparateNavigation()) - .GroupBy(t => t, DroppedInlineNavigationsComparer.Instance)) - { - var navigation = group.Key.DroppedNavigations.Last(); - var ownedEntities = group.Key.DroppedNavigations.Aggregate((IEnumerable)parentEntities, ProjectEntities); - var properties = group.Select(g => g.Property).ToList(); - - yield return (navigation, ownedEntities, properties); - } - } - - private static IEnumerable ProjectEntities(IEnumerable ownedEntities, INavigation navigation) - { - var getter = navigation.GetGetter() ?? throw new Exception($"No property-getter for the navigational property '{navigation.ClrType.Name}.{navigation.PropertyInfo?.Name ?? navigation.Name}' found."); - - ownedEntities = ownedEntities.Select(getter.GetClrValue).Where(e => e != null)!; - - if (navigation.IsCollection) - ownedEntities = ownedEntities.SelectMany(c => (IEnumerable)c); - - return ownedEntities; - } -} +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.EntityFrameworkCore.Data; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extensions for collections. +/// +public static class BulkOperationsCollectionExtensions +{ + internal static IReadOnlyList ConvertToEntityProperties( + this IReadOnlyList members, + IEntityType entityType) + { + if (entityType.GetOwnedTypesProperties(null).Any()) + throw new NotSupportedException($"The entity '{entityType.Name}' must not contain owned entities."); + + var properties = new List(); + + foreach (var memberInfo in members) + { + var property = entityType.FindProperty(memberInfo); + + if (property != null) + { + properties.Add(property); + continue; + } + + var complexProperty = entityType.FindComplexProperty(memberInfo); + + if (complexProperty != null) + { + properties.AddRange(complexProperty.ComplexType.GetFlattenedProperties()); + continue; + } + + throw new InvalidOperationException($"The member '{memberInfo.Name}' of the entity '{entityType.Name}' cannot be written to database."); + } + + return properties; + } + + internal static IReadOnlyList ConvertToEntityPropertiesWithNavigations( + this IReadOnlyList members, + IEntityType entityType, + bool? inlinedOwnTypes, + Func, bool> filter) + { + var properties = new List(); + var navigations = Array.Empty(); + + foreach (var memberInfo in members) + { + var property = entityType.FindProperty(memberInfo); + + if (property != null) + { + if (!filter(property, navigations)) + throw new InvalidOperationException($"The member '{memberInfo.Name}' of the entity '{entityType.Name}' cannot be written to database."); + + properties.Add(new PropertyWithNavigations(property, navigations)); + continue; + } + + var complexProperty = entityType.FindComplexProperty(memberInfo); + + if (complexProperty != null) + { + foreach (var propertyOfComplexType in complexProperty.ComplexType.GetFlattenedProperties()) + { + if (!filter(propertyOfComplexType, navigations)) + throw new InvalidOperationException($"The member '{memberInfo.Name}' of the entity '{entityType.Name}' cannot be written to database."); + + properties.Add(new PropertyWithNavigations(propertyOfComplexType, navigations)); + } + + continue; + } + + var ownedNavi = FindOwnedProperty(entityType, memberInfo); + + if (ownedNavi == null) + throw new Exception($"The member '{memberInfo.Name}' has not been found on entity '{entityType.Name}'."); + + properties.AddPropertiesAndOwnedTypesRecursively(ownedNavi.TargetEntityType, new[] { ownedNavi }, inlinedOwnTypes, filter); + } + + return properties; + } + + private static INavigation? FindOwnedProperty( + IEntityType entityType, + MemberInfo memberInfo) + { + foreach (var ownedTypeNavi in entityType.GetOwnedTypesProperties(null)) // search for all owned properties, i.e., don't use "inlinedOwnTypes" from the caller + { + if (memberInfo.IsEqualTo(ownedTypeNavi.PropertyInfo) || memberInfo.IsEqualTo(ownedTypeNavi.FieldInfo)) + { + if (ownedTypeNavi.IsInlined()) + return ownedTypeNavi; + + throw new NotSupportedException($"Properties of owned types that are saved in a separate table are not supported. Property: {ownedTypeNavi.Name}"); + } + } + + return null; + } + + /// + /// Checks that there are no separate owned types inside a collection property. + /// + /// Properties to check. + /// If a separate owned type is found inside a collection property. + public static void EnsureNoSeparateOwnedTypesInsideCollectionOwnedType(this IReadOnlyList properties) + { + ArgumentNullException.ThrowIfNull(properties); + + foreach (var property in properties) + { + INavigation? collection = null; + + foreach (var navigation in property.Navigations) + { + if (!navigation.IsInlined() && collection is not null) + throw new NotSupportedException($"Non-inlined (i.e. with its own table) nested owned type '{navigation.DeclaringEntityType.Name}.{navigation.Name}' inside another owned type collection '{collection.DeclaringEntityType.Name}.{collection.Name}' is not supported."); + + if (navigation.IsCollection) + collection = navigation; + } + } + } + + /// + /// Separates in own properties and properties belonging to different tables. + /// + /// Properties to separate. + /// A collection of own and "external" properties. + public static (IReadOnlyList sameTable, IReadOnlyList otherTable) SeparateProperties( + this IReadOnlyList properties) + { + var sameTableProperties = new List(); + List? externalProperties = null; + + foreach (var property in properties) + { + if (property.IsInlined) + { + sameTableProperties.Add(property); + } + else + { + externalProperties ??= new List(); + externalProperties.Add(property); + } + } + + return (sameTableProperties, (IReadOnlyList?)externalProperties ?? Array.Empty()); + } + + /// + /// Groups by the owned type property they belong to. + /// + /// Properties to group. + /// A collection of the parent properties. + /// A collection of tuples of the owned type navigation, owned type entities and their properties. + public static IEnumerable<(INavigation, IEnumerable, IReadOnlyList)> GroupExternalProperties( + this IReadOnlyList externalProperties, + IReadOnlyList parentEntities) + { + foreach (var group in externalProperties.Select(p => p.DropUntilSeparateNavigation()) + .GroupBy(t => t, DroppedInlineNavigationsComparer.Instance)) + { + var navigation = group.Key.DroppedNavigations.Last(); + var ownedEntities = group.Key.DroppedNavigations.Aggregate((IEnumerable)parentEntities, ProjectEntities); + var properties = group.Select(g => g.Property).ToList(); + + yield return (navigation, ownedEntities, properties); + } + } + + private static IEnumerable ProjectEntities(IEnumerable ownedEntities, INavigation navigation) + { + var getter = navigation.GetGetter() ?? throw new Exception($"No property-getter for the navigational property '{navigation.ClrType.Name}.{navigation.PropertyInfo?.Name ?? navigation.Name}' found."); + + ownedEntities = ownedEntities.Select(getter.GetClrValue).Where(e => e != null)!; + + if (navigation.IsCollection) + ownedEntities = ownedEntities.SelectMany(c => (IEnumerable)c); + + return ownedEntities; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsDbContextExtensions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsDbContextExtensions.cs index f25366bd..ff8ba68e 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsDbContextExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsDbContextExtensions.cs @@ -1,360 +1,360 @@ -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Thinktecture.EntityFrameworkCore.BulkOperations; -using Thinktecture.EntityFrameworkCore.Parameters; -using Thinktecture.EntityFrameworkCore.TempTables; -using Thinktecture.Internal; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extension methods for . -/// -public static class BulkOperationsDbContextExtensions -{ - /// - /// Creates a temp table using custom type ''. - /// - /// Database context to use. - /// Options. - /// Cancellation token. - /// Type of custom temp table. - /// Table name - /// is null. - /// The provided type is not known by the provided . - public static Task CreateTempTableAsync( - this DbContext ctx, - ITempTableCreationOptions options, - CancellationToken cancellationToken = default) - where T : class - { - return ctx.CreateTempTableAsync(typeof(T), options, cancellationToken); - } - - /// - /// Creates a temp table. - /// - /// Database context to use. - /// Type of the entity. - /// Options. - /// Cancellation token. - /// Table name - /// - /// is null - /// - or is null - /// - or is null. - /// - /// The provided type is not known by provided . - public static Task CreateTempTableAsync( - this DbContext ctx, - Type type, - ITempTableCreationOptions options, - CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(ctx); - - var entityType = ctx.Model.GetEntityType(EntityNameProvider.GetTempTableName(type), type); - return ctx.GetService().CreateTempTableAsync(entityType, options, cancellationToken); - } - - /// - /// Copies into a table. - /// - /// Database context. - /// Entities to insert. - /// Properties to insert. If null then all properties are used. - /// Cancellation token. - /// Entity type. - /// or is null. - public static async Task BulkInsertAsync( - this DbContext ctx, - IEnumerable entities, - Expression>? propertiesToInsert = null, - CancellationToken cancellationToken = default) - where T : class - { - var bulkInsertExecutor = ctx.GetService(); - var options = bulkInsertExecutor.CreateOptions(propertiesToInsert is null ? null : IEntityPropertiesProvider.Include(propertiesToInsert)); - - await bulkInsertExecutor.BulkInsertAsync(entities, options, cancellationToken).ConfigureAwait(false); - } - - /// - /// Copies into a table. - /// - /// Database context. - /// Entities to insert. - /// Options. - /// Cancellation token. - /// Entity type. - /// or is null. - public static async Task BulkInsertAsync( - this DbContext ctx, - IEnumerable entities, - IBulkInsertOptions options, - CancellationToken cancellationToken = default) - where T : class - { - await ctx.GetService() - .BulkInsertAsync(entities, options, cancellationToken).ConfigureAwait(false); - } - - /// - /// Updates in the table. - /// - /// Database context. - /// Entities to update. - /// Properties to update. If null then all properties are used. - /// Properties to match on. If null then the primary key of the entity is used. - /// Cancellation token. - /// Number of affected rows. - /// Entity type. - /// or is null. - public static async Task BulkUpdateAsync( - this DbContext ctx, - IEnumerable entities, - Expression>? propertiesToUpdate = null, - Expression>? propertiesToMatchOn = null, - CancellationToken cancellationToken = default) - where T : class - { - var bulkUpdateExecutor = ctx.GetService(); - - var options = bulkUpdateExecutor.CreateOptions(propertiesToUpdate is null ? null : IEntityPropertiesProvider.Include(propertiesToUpdate), - propertiesToMatchOn is null ? null : IEntityPropertiesProvider.Include(propertiesToMatchOn)); - - return await bulkUpdateExecutor.BulkUpdateAsync(entities, options, cancellationToken).ConfigureAwait(false); - } - - /// - /// Updates in the table. - /// - /// Database context. - /// Entities to update. - /// Options. - /// Cancellation token. - /// Number of affected rows. - /// Entity type. - /// or is null. - public static async Task BulkUpdateAsync( - this DbContext ctx, - IEnumerable entities, - IBulkUpdateOptions options, - CancellationToken cancellationToken = default) - where T : class - { - return await ctx.GetService() - .BulkUpdateAsync(entities, options, cancellationToken).ConfigureAwait(false); - } - - /// - /// Updates that are in the table, the rest will be inserted. - /// - /// Database context. - /// Entities to insert or update. - /// Properties to insert. If null then all properties are used. - /// Properties to update. If null then all properties are used. - /// Properties to match on. If null then the primary key of the entity is used. - /// Cancellation token. - /// Number of affected rows. - /// Entity type. - /// or is null. - public static async Task BulkInsertOrUpdateAsync( - this DbContext ctx, - IEnumerable entities, - Expression>? propertiesToInsert = null, - Expression>? propertiesToUpdate = null, - Expression>? propertiesToMatchOn = null, - CancellationToken cancellationToken = default) - where T : class - { - var bulkOperationExecutor = ctx.GetService(); - - var options = bulkOperationExecutor.CreateOptions(propertiesToInsert is null ? null : IEntityPropertiesProvider.Include(propertiesToInsert), - propertiesToUpdate is null ? null : IEntityPropertiesProvider.Include(propertiesToUpdate), - propertiesToMatchOn is null ? null : IEntityPropertiesProvider.Include(propertiesToMatchOn)); - - return await bulkOperationExecutor.BulkInsertOrUpdateAsync(entities, options, cancellationToken).ConfigureAwait(false); - } - - /// - /// Updates that are in the table, the rest will be inserted. - /// - /// Database context. - /// Entities to insert or update. - /// Options. - /// Cancellation token. - /// Number of affected rows. - /// Entity type. - /// or is null. - public static async Task BulkInsertOrUpdateAsync( - this DbContext ctx, - IEnumerable entities, - IBulkInsertOrUpdateOptions options, - CancellationToken cancellationToken = default) - where T : class - { - return await ctx.GetService() - .BulkInsertOrUpdateAsync(entities, options, cancellationToken).ConfigureAwait(false); - } - - /// - /// Copies into a temp table and returns the query for accessing the inserted records. - /// - /// Database context. - /// Values to insert. - /// Options. - /// Cancellation token. - /// Type of the values to insert. - /// A query for accessing the inserted values. - /// or is null. - public static Task> BulkInsertValuesIntoTempTableAsync( - this DbContext ctx, - IEnumerable values, - ITempTableBulkInsertOptions? options = null, - CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(values); - - var executor = ctx.GetService(); - options ??= executor.CreateOptions(); - - return executor.BulkInsertValuesIntoTempTableAsync(values, options, cancellationToken); - } - - /// - /// Copies into a temp table and returns the query for accessing the inserted records. - /// - /// Database context. - /// Values to insert. - /// Options. - /// Cancellation token. - /// Type of the column 1. - /// Type of the column 2. - /// A query for accessing the inserted values. - /// or is null. - public static Task>> BulkInsertValuesIntoTempTableAsync( - this DbContext ctx, - IEnumerable<(TColumn1 column1, TColumn2 column2)> values, - ITempTableBulkInsertOptions? options = null, - CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(values); - - var entities = values.Select(t => new TempTable(t.column1, t.column2)); - - return ctx.BulkInsertIntoTempTableAsync(entities, options, cancellationToken); - } - - /// - /// Copies into a temp table and returns the query for accessing the inserted records. - /// - /// Database context. - /// Entities to insert. - /// Properties to insert. If null then all properties are used. - /// Cancellation token. - /// Entity type. - /// A query for accessing the inserted values. - /// or is null. - public static Task> BulkInsertIntoTempTableAsync( - this DbContext ctx, - IEnumerable entities, - Expression>? propertiesToInsert = null, - CancellationToken cancellationToken = default) - where T : class - { - var executor = ctx.GetService(); - var options = executor.CreateOptions(propertiesToInsert is null ? null : IEntityPropertiesProvider.Include(propertiesToInsert)); - - return executor.BulkInsertIntoTempTableAsync(entities, options, cancellationToken); - } - - /// - /// Copies into a temp table and returns the query for accessing the inserted records. - /// - /// Database context. - /// Entities to insert. - /// Options. - /// Cancellation token. - /// Entity type. - /// A query for accessing the inserted values. - /// or is null. - public static Task> BulkInsertIntoTempTableAsync( - this DbContext ctx, - IEnumerable entities, - ITempTableBulkInsertOptions? options, - CancellationToken cancellationToken = default) - where T : class - { - var executor = ctx.GetService(); - options ??= executor.CreateOptions(); - - return executor.BulkInsertIntoTempTableAsync(entities, options, cancellationToken); - } - - /// - /// Truncates the table of the entity of type . - /// - /// Database context. - /// Cancellation token. - /// Type of the entity to truncate. - public static Task TruncateTableAsync( - this DbContext ctx, - CancellationToken cancellationToken = default) - where T : class - { - return ctx.GetService() - .TruncateTableAsync(cancellationToken); - } - - /// - /// Truncates the table of the entity of type . - /// - /// Database context. - /// Type of the entity to truncate. - /// Cancellation token. - public static Task TruncateTableAsync( - this DbContext ctx, - Type type, - CancellationToken cancellationToken = default) - { - return ctx.GetService() - .TruncateTableAsync(type, cancellationToken); - } - - /// - /// Converts the provided to a "parameter" to be used in queries. - /// - /// An instance of to use the with. - /// A collection of to create a query from. - /// - /// Indication whether the query should apply 'DISTINCT' on . - /// It is highly recommended to set this parameter to true to get better execution plans. - /// - /// Type of the . - /// An giving access to the provided . - public static IQueryable CreateScalarCollectionParameter(this DbContext ctx, IReadOnlyCollection values, bool applyDistinct = true) - { - return ctx.GetService() - .CreateScalarQuery(ctx, values, applyDistinct); - } - - /// - /// Converts the provided to a "parameter" to be used in queries. - /// - /// An instance of to use the with. - /// A collection of to create a query from. - /// - /// Indication whether the query should apply 'DISTINCT' on . - /// It is highly recommended to set this parameter to true to get better execution plans. - /// - /// Type of the . - /// An giving access to the provided . - public static IQueryable CreateComplexCollectionParameter(this DbContext ctx, IReadOnlyCollection objects, bool applyDistinct = true) - where T : class - { - return ctx.GetService() - .CreateComplexQuery(ctx, objects, applyDistinct); - } -} +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Thinktecture.EntityFrameworkCore.BulkOperations; +using Thinktecture.EntityFrameworkCore.Parameters; +using Thinktecture.EntityFrameworkCore.TempTables; +using Thinktecture.Internal; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extension methods for . +/// +public static class BulkOperationsDbContextExtensions +{ + /// + /// Creates a temp table using custom type ''. + /// + /// Database context to use. + /// Options. + /// Cancellation token. + /// Type of custom temp table. + /// Table name + /// is null. + /// The provided type is not known by the provided . + public static Task CreateTempTableAsync( + this DbContext ctx, + ITempTableCreationOptions options, + CancellationToken cancellationToken = default) + where T : class + { + return ctx.CreateTempTableAsync(typeof(T), options, cancellationToken); + } + + /// + /// Creates a temp table. + /// + /// Database context to use. + /// Type of the entity. + /// Options. + /// Cancellation token. + /// Table name + /// + /// is null + /// - or is null + /// - or is null. + /// + /// The provided type is not known by provided . + public static Task CreateTempTableAsync( + this DbContext ctx, + Type type, + ITempTableCreationOptions options, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(ctx); + + var entityType = ctx.Model.GetEntityType(EntityNameProvider.GetTempTableName(type), type); + return ctx.GetService().CreateTempTableAsync(entityType, options, cancellationToken); + } + + /// + /// Copies into a table. + /// + /// Database context. + /// Entities to insert. + /// Properties to insert. If null then all properties are used. + /// Cancellation token. + /// Entity type. + /// or is null. + public static async Task BulkInsertAsync( + this DbContext ctx, + IEnumerable entities, + Expression>? propertiesToInsert = null, + CancellationToken cancellationToken = default) + where T : class + { + var bulkInsertExecutor = ctx.GetService(); + var options = bulkInsertExecutor.CreateOptions(propertiesToInsert is null ? null : IEntityPropertiesProvider.Include(propertiesToInsert)); + + await bulkInsertExecutor.BulkInsertAsync(entities, options, cancellationToken).ConfigureAwait(false); + } + + /// + /// Copies into a table. + /// + /// Database context. + /// Entities to insert. + /// Options. + /// Cancellation token. + /// Entity type. + /// or is null. + public static async Task BulkInsertAsync( + this DbContext ctx, + IEnumerable entities, + IBulkInsertOptions options, + CancellationToken cancellationToken = default) + where T : class + { + await ctx.GetService() + .BulkInsertAsync(entities, options, cancellationToken).ConfigureAwait(false); + } + + /// + /// Updates in the table. + /// + /// Database context. + /// Entities to update. + /// Properties to update. If null then all properties are used. + /// Properties to match on. If null then the primary key of the entity is used. + /// Cancellation token. + /// Number of affected rows. + /// Entity type. + /// or is null. + public static async Task BulkUpdateAsync( + this DbContext ctx, + IEnumerable entities, + Expression>? propertiesToUpdate = null, + Expression>? propertiesToMatchOn = null, + CancellationToken cancellationToken = default) + where T : class + { + var bulkUpdateExecutor = ctx.GetService(); + + var options = bulkUpdateExecutor.CreateOptions(propertiesToUpdate is null ? null : IEntityPropertiesProvider.Include(propertiesToUpdate), + propertiesToMatchOn is null ? null : IEntityPropertiesProvider.Include(propertiesToMatchOn)); + + return await bulkUpdateExecutor.BulkUpdateAsync(entities, options, cancellationToken).ConfigureAwait(false); + } + + /// + /// Updates in the table. + /// + /// Database context. + /// Entities to update. + /// Options. + /// Cancellation token. + /// Number of affected rows. + /// Entity type. + /// or is null. + public static async Task BulkUpdateAsync( + this DbContext ctx, + IEnumerable entities, + IBulkUpdateOptions options, + CancellationToken cancellationToken = default) + where T : class + { + return await ctx.GetService() + .BulkUpdateAsync(entities, options, cancellationToken).ConfigureAwait(false); + } + + /// + /// Updates that are in the table, the rest will be inserted. + /// + /// Database context. + /// Entities to insert or update. + /// Properties to insert. If null then all properties are used. + /// Properties to update. If null then all properties are used. + /// Properties to match on. If null then the primary key of the entity is used. + /// Cancellation token. + /// Number of affected rows. + /// Entity type. + /// or is null. + public static async Task BulkInsertOrUpdateAsync( + this DbContext ctx, + IEnumerable entities, + Expression>? propertiesToInsert = null, + Expression>? propertiesToUpdate = null, + Expression>? propertiesToMatchOn = null, + CancellationToken cancellationToken = default) + where T : class + { + var bulkOperationExecutor = ctx.GetService(); + + var options = bulkOperationExecutor.CreateOptions(propertiesToInsert is null ? null : IEntityPropertiesProvider.Include(propertiesToInsert), + propertiesToUpdate is null ? null : IEntityPropertiesProvider.Include(propertiesToUpdate), + propertiesToMatchOn is null ? null : IEntityPropertiesProvider.Include(propertiesToMatchOn)); + + return await bulkOperationExecutor.BulkInsertOrUpdateAsync(entities, options, cancellationToken).ConfigureAwait(false); + } + + /// + /// Updates that are in the table, the rest will be inserted. + /// + /// Database context. + /// Entities to insert or update. + /// Options. + /// Cancellation token. + /// Number of affected rows. + /// Entity type. + /// or is null. + public static async Task BulkInsertOrUpdateAsync( + this DbContext ctx, + IEnumerable entities, + IBulkInsertOrUpdateOptions options, + CancellationToken cancellationToken = default) + where T : class + { + return await ctx.GetService() + .BulkInsertOrUpdateAsync(entities, options, cancellationToken).ConfigureAwait(false); + } + + /// + /// Copies into a temp table and returns the query for accessing the inserted records. + /// + /// Database context. + /// Values to insert. + /// Options. + /// Cancellation token. + /// Type of the values to insert. + /// A query for accessing the inserted values. + /// or is null. + public static Task> BulkInsertValuesIntoTempTableAsync( + this DbContext ctx, + IEnumerable values, + ITempTableBulkInsertOptions? options = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(values); + + var executor = ctx.GetService(); + options ??= executor.CreateOptions(); + + return executor.BulkInsertValuesIntoTempTableAsync(values, options, cancellationToken); + } + + /// + /// Copies into a temp table and returns the query for accessing the inserted records. + /// + /// Database context. + /// Values to insert. + /// Options. + /// Cancellation token. + /// Type of the column 1. + /// Type of the column 2. + /// A query for accessing the inserted values. + /// or is null. + public static Task>> BulkInsertValuesIntoTempTableAsync( + this DbContext ctx, + IEnumerable<(TColumn1 column1, TColumn2 column2)> values, + ITempTableBulkInsertOptions? options = null, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(values); + + var entities = values.Select(t => new TempTable(t.column1, t.column2)); + + return ctx.BulkInsertIntoTempTableAsync(entities, options, cancellationToken); + } + + /// + /// Copies into a temp table and returns the query for accessing the inserted records. + /// + /// Database context. + /// Entities to insert. + /// Properties to insert. If null then all properties are used. + /// Cancellation token. + /// Entity type. + /// A query for accessing the inserted values. + /// or is null. + public static Task> BulkInsertIntoTempTableAsync( + this DbContext ctx, + IEnumerable entities, + Expression>? propertiesToInsert = null, + CancellationToken cancellationToken = default) + where T : class + { + var executor = ctx.GetService(); + var options = executor.CreateOptions(propertiesToInsert is null ? null : IEntityPropertiesProvider.Include(propertiesToInsert)); + + return executor.BulkInsertIntoTempTableAsync(entities, options, cancellationToken); + } + + /// + /// Copies into a temp table and returns the query for accessing the inserted records. + /// + /// Database context. + /// Entities to insert. + /// Options. + /// Cancellation token. + /// Entity type. + /// A query for accessing the inserted values. + /// or is null. + public static Task> BulkInsertIntoTempTableAsync( + this DbContext ctx, + IEnumerable entities, + ITempTableBulkInsertOptions? options, + CancellationToken cancellationToken = default) + where T : class + { + var executor = ctx.GetService(); + options ??= executor.CreateOptions(); + + return executor.BulkInsertIntoTempTableAsync(entities, options, cancellationToken); + } + + /// + /// Truncates the table of the entity of type . + /// + /// Database context. + /// Cancellation token. + /// Type of the entity to truncate. + public static Task TruncateTableAsync( + this DbContext ctx, + CancellationToken cancellationToken = default) + where T : class + { + return ctx.GetService() + .TruncateTableAsync(cancellationToken); + } + + /// + /// Truncates the table of the entity of type . + /// + /// Database context. + /// Type of the entity to truncate. + /// Cancellation token. + public static Task TruncateTableAsync( + this DbContext ctx, + Type type, + CancellationToken cancellationToken = default) + { + return ctx.GetService() + .TruncateTableAsync(type, cancellationToken); + } + + /// + /// Converts the provided to a "parameter" to be used in queries. + /// + /// An instance of to use the with. + /// A collection of to create a query from. + /// + /// Indication whether the query should apply 'DISTINCT' on . + /// It is highly recommended to set this parameter to true to get better execution plans. + /// + /// Type of the . + /// An giving access to the provided . + public static IQueryable CreateScalarCollectionParameter(this DbContext ctx, IReadOnlyCollection values, bool applyDistinct = true) + { + return ctx.GetService() + .CreateScalarQuery(ctx, values, applyDistinct); + } + + /// + /// Converts the provided to a "parameter" to be used in queries. + /// + /// An instance of to use the with. + /// A collection of to create a query from. + /// + /// Indication whether the query should apply 'DISTINCT' on . + /// It is highly recommended to set this parameter to true to get better execution plans. + /// + /// Type of the . + /// An giving access to the provided . + public static IQueryable CreateComplexCollectionParameter(this DbContext ctx, IReadOnlyCollection objects, bool applyDistinct = true) + where T : class + { + return ctx.GetService() + .CreateComplexQuery(ctx, objects, applyDistinct); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsEntityPropertiesProviderExtensions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsEntityPropertiesProviderExtensions.cs index 919b2b9a..39603293 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsEntityPropertiesProviderExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsEntityPropertiesProviderExtensions.cs @@ -1,102 +1,102 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.EntityFrameworkCore.BulkOperations; -using Thinktecture.EntityFrameworkCore.Data; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extensions for . -/// -public static class BulkOperationsEntityPropertiesProviderExtensions -{ - /// - /// Determines properties to include into a temp table into. - /// - /// Entity properties provider. - /// Entity type. - /// Properties to include into a temp table. - public static IReadOnlyList DeterminePropertiesForTempTable( - this IEntityPropertiesProvider? entityPropertiesProvider, - IEntityType entityType) - { - ArgumentNullException.ThrowIfNull(entityType); - - return (entityPropertiesProvider ?? IEntityPropertiesProvider.Default).GetPropertiesForTempTable(entityType); - } - - /// - /// Determines key properties. - /// - /// Entity properties provider. - /// Entity type. - /// Key properties. - public static IReadOnlyList DetermineKeyProperties( - this IEntityPropertiesProvider? entityPropertiesProvider, - IEntityType entityType) - { - ArgumentNullException.ThrowIfNull(entityType); - - var properties = (entityPropertiesProvider ?? IEntityPropertiesProvider.Default).GetKeyProperties(entityType); - - if (properties is null or { Count: 0 }) - throw new ArgumentException("The number of key properties to perform JOIN/match on cannot be 0."); - - return properties; - } - - /// - /// Determines properties to insert into a (temp) table. - /// - /// Entity properties provider. - /// Entity type. - /// Indication whether inlined (true), separated (false) or all owned types to return. - /// Properties to insert into a (temp) table. - public static IReadOnlyList DeterminePropertiesForInsert( - this IEntityPropertiesProvider? entityPropertiesProvider, - IEntityType entityType, - bool? inlinedOwnTypes) - { - ArgumentNullException.ThrowIfNull(entityType); - - return (entityPropertiesProvider ?? IEntityPropertiesProvider.Default).GetPropertiesForInsert(entityType, inlinedOwnTypes); - } - - /// - /// Determines properties to use in update of a table. - /// - /// Entity properties provider. - /// Entity type. - /// Indication whether inlined (true), separated (false) or all owned types to return. - /// Properties to use in update of a table. - public static IReadOnlyList DeterminePropertiesForUpdate( - this IEntityPropertiesProvider? entityPropertiesProvider, - IEntityType entityType, - bool? inlinedOwnTypes) - { - ArgumentNullException.ThrowIfNull(entityType); - - return (entityPropertiesProvider ?? IEntityPropertiesProvider.Default).GetPropertiesForUpdate(entityType, inlinedOwnTypes); - } - - internal static void AddPropertiesAndOwnedTypesRecursively( - this List properties, - IEntityType entityType, - IReadOnlyList navigations, - bool? inlinedOwnTypes, - Func, bool> filter) - { - foreach (var property in entityType.GetProperties().Where(p => filter(p, navigations))) - { - properties.Add(new PropertyWithNavigations(property, navigations)); - } - - foreach (var ownedTypeNavigation in entityType.GetOwnedTypesProperties(inlinedOwnTypes)) - { - var innerNavigations = navigations.ToList(); - innerNavigations.Add(ownedTypeNavigation); - - properties.AddPropertiesAndOwnedTypesRecursively(ownedTypeNavigation.TargetEntityType, innerNavigations, inlinedOwnTypes, filter); - } - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.EntityFrameworkCore.BulkOperations; +using Thinktecture.EntityFrameworkCore.Data; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extensions for . +/// +public static class BulkOperationsEntityPropertiesProviderExtensions +{ + /// + /// Determines properties to include into a temp table into. + /// + /// Entity properties provider. + /// Entity type. + /// Properties to include into a temp table. + public static IReadOnlyList DeterminePropertiesForTempTable( + this IEntityPropertiesProvider? entityPropertiesProvider, + IEntityType entityType) + { + ArgumentNullException.ThrowIfNull(entityType); + + return (entityPropertiesProvider ?? IEntityPropertiesProvider.Default).GetPropertiesForTempTable(entityType); + } + + /// + /// Determines key properties. + /// + /// Entity properties provider. + /// Entity type. + /// Key properties. + public static IReadOnlyList DetermineKeyProperties( + this IEntityPropertiesProvider? entityPropertiesProvider, + IEntityType entityType) + { + ArgumentNullException.ThrowIfNull(entityType); + + var properties = (entityPropertiesProvider ?? IEntityPropertiesProvider.Default).GetKeyProperties(entityType); + + if (properties is null or { Count: 0 }) + throw new ArgumentException("The number of key properties to perform JOIN/match on cannot be 0."); + + return properties; + } + + /// + /// Determines properties to insert into a (temp) table. + /// + /// Entity properties provider. + /// Entity type. + /// Indication whether inlined (true), separated (false) or all owned types to return. + /// Properties to insert into a (temp) table. + public static IReadOnlyList DeterminePropertiesForInsert( + this IEntityPropertiesProvider? entityPropertiesProvider, + IEntityType entityType, + bool? inlinedOwnTypes) + { + ArgumentNullException.ThrowIfNull(entityType); + + return (entityPropertiesProvider ?? IEntityPropertiesProvider.Default).GetPropertiesForInsert(entityType, inlinedOwnTypes); + } + + /// + /// Determines properties to use in update of a table. + /// + /// Entity properties provider. + /// Entity type. + /// Indication whether inlined (true), separated (false) or all owned types to return. + /// Properties to use in update of a table. + public static IReadOnlyList DeterminePropertiesForUpdate( + this IEntityPropertiesProvider? entityPropertiesProvider, + IEntityType entityType, + bool? inlinedOwnTypes) + { + ArgumentNullException.ThrowIfNull(entityType); + + return (entityPropertiesProvider ?? IEntityPropertiesProvider.Default).GetPropertiesForUpdate(entityType, inlinedOwnTypes); + } + + internal static void AddPropertiesAndOwnedTypesRecursively( + this List properties, + IEntityType entityType, + IReadOnlyList navigations, + bool? inlinedOwnTypes, + Func, bool> filter) + { + foreach (var property in entityType.GetProperties().Where(p => filter(p, navigations))) + { + properties.Add(new PropertyWithNavigations(property, navigations)); + } + + foreach (var ownedTypeNavigation in entityType.GetOwnedTypesProperties(inlinedOwnTypes)) + { + var innerNavigations = navigations.ToList(); + innerNavigations.Add(ownedTypeNavigation); + + properties.AddPropertiesAndOwnedTypesRecursively(ownedTypeNavigation.TargetEntityType, innerNavigations, inlinedOwnTypes, filter); + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsEntityTypeExtensions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsEntityTypeExtensions.cs index d4bf7f77..40fed5cf 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsEntityTypeExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsEntityTypeExtensions.cs @@ -1,29 +1,29 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extension methods for . -/// -public static class BulkOperationsEntityTypeExtensions -{ - /// - /// Fetches owned types of the . - /// - /// Entity type to fetch owned types of. - /// Indication whether to fetch inlined owned types, non-inlined or all of them. - /// Navigations pointing to owned types. - /// - public static IEnumerable GetOwnedTypesProperties( - this IEntityType entityType, - bool? inlinedOwnTypes) - { - ArgumentNullException.ThrowIfNull(entityType); - - return entityType.GetNavigations() - .Where(n => n.ForeignKey.IsOwnership && - n.ForeignKey.PrincipalEntityType == entityType && - (inlinedOwnTypes == null || inlinedOwnTypes == n.IsInlined())); - } -} +using Microsoft.EntityFrameworkCore.Metadata; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extension methods for . +/// +public static class BulkOperationsEntityTypeExtensions +{ + /// + /// Fetches owned types of the . + /// + /// Entity type to fetch owned types of. + /// Indication whether to fetch inlined owned types, non-inlined or all of them. + /// Navigations pointing to owned types. + /// + public static IEnumerable GetOwnedTypesProperties( + this IEntityType entityType, + bool? inlinedOwnTypes) + { + ArgumentNullException.ThrowIfNull(entityType); + + return entityType.GetNavigations() + .Where(n => n.ForeignKey.IsOwnership && + n.ForeignKey.PrincipalEntityType == entityType && + (inlinedOwnTypes == null || inlinedOwnTypes == n.IsInlined())); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsExpressionExtensions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsExpressionExtensions.cs index 9715b23d..d5d9d5ff 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsExpressionExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsExpressionExtensions.cs @@ -1,68 +1,68 @@ -using System.Linq.Expressions; -using System.Reflection; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extensions for . -/// -public static class BulkOperationsExpressionExtensions -{ - internal static IReadOnlyList ExtractMembers(this Expression> projection) - { - ArgumentNullException.ThrowIfNull(projection); - - var members = new List(); - ExtractMembers(members, projection.Parameters[0], projection.Body); - - return members; - } - - private static void ExtractMembers(List members, ParameterExpression paramExpression, Expression body) - { - switch (body.NodeType) - { - case ExpressionType.Convert: - case ExpressionType.Quote: - ExtractMembers(members, paramExpression, ((UnaryExpression)body).Operand); - break; - - // entity => entity.Member; - case ExpressionType.MemberAccess: - members.Add(ExtractMember(paramExpression, (MemberExpression)body)); - break; - - // entity => new { Prop = entity.Member } - case ExpressionType.New: - ExtractMembers(members, paramExpression, (NewExpression)body); - break; - - default: - throw new NotSupportedException($"The expression of type '{body.NodeType}' is not supported. Expression: {body}."); - } - } - - private static void ExtractMembers(List members, ParameterExpression paramExpression, NewExpression newExpression) - { - foreach (var argument in newExpression.Arguments) - { - ExtractMembers(members, paramExpression, argument); - } - } - - // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local - private static MemberInfo ExtractMember(ParameterExpression paramExpression, MemberExpression memberAccess) - { - if (memberAccess.Expression != paramExpression) - throw new NotSupportedException($"Complex projections are not supported. Current expression: {memberAccess}"); - - if (memberAccess.Member is PropertyInfo propertyInfo) - return propertyInfo; - - if (memberAccess.Member is FieldInfo fieldInfo) - return fieldInfo; - - throw new NotSupportedException($"The projection must have properties and fields only. Current expression: {memberAccess}"); - } -} +using System.Linq.Expressions; +using System.Reflection; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extensions for . +/// +public static class BulkOperationsExpressionExtensions +{ + internal static IReadOnlyList ExtractMembers(this Expression> projection) + { + ArgumentNullException.ThrowIfNull(projection); + + var members = new List(); + ExtractMembers(members, projection.Parameters[0], projection.Body); + + return members; + } + + private static void ExtractMembers(List members, ParameterExpression paramExpression, Expression body) + { + switch (body.NodeType) + { + case ExpressionType.Convert: + case ExpressionType.Quote: + ExtractMembers(members, paramExpression, ((UnaryExpression)body).Operand); + break; + + // entity => entity.Member; + case ExpressionType.MemberAccess: + members.Add(ExtractMember(paramExpression, (MemberExpression)body)); + break; + + // entity => new { Prop = entity.Member } + case ExpressionType.New: + ExtractMembers(members, paramExpression, (NewExpression)body); + break; + + default: + throw new NotSupportedException($"The expression of type '{body.NodeType}' is not supported. Expression: {body}."); + } + } + + private static void ExtractMembers(List members, ParameterExpression paramExpression, NewExpression newExpression) + { + foreach (var argument in newExpression.Arguments) + { + ExtractMembers(members, paramExpression, argument); + } + } + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + private static MemberInfo ExtractMember(ParameterExpression paramExpression, MemberExpression memberAccess) + { + if (memberAccess.Expression != paramExpression) + throw new NotSupportedException($"Complex projections are not supported. Current expression: {memberAccess}"); + + if (memberAccess.Member is PropertyInfo propertyInfo) + return propertyInfo; + + if (memberAccess.Member is FieldInfo fieldInfo) + return fieldInfo; + + throw new NotSupportedException($"The projection must have properties and fields only. Current expression: {memberAccess}"); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsModelBuilderExtensions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsModelBuilderExtensions.cs index 2f870792..0f4a8503 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsModelBuilderExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsModelBuilderExtensions.cs @@ -1,192 +1,192 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Thinktecture.EntityFrameworkCore.Parameters; -using Thinktecture.EntityFrameworkCore.TempTables; -using Thinktecture.Internal; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extensions for . -/// -public static class BulkOperationsModelBuilderExtensions -{ - /// - /// Introduces and configures a scalar parameter. - /// - /// A model builder. - /// An action that performs configuration of the entity type. - /// Type of the column. - /// An entity type builder for further configuration. - /// is null. - public static void ConfigureScalarCollectionParameter( - this ModelBuilder modelBuilder, - Action>>? buildAction = null) - { - var builder = modelBuilder.SharedTypeEntity>(EntityNameProvider.GetCollectionParameterName(typeof(T), true)) - .ToTable(typeof(ScalarCollectionParameter).ShortDisplayName(), - static tableBuilder => tableBuilder.ExcludeFromMigrations()) - .HasNoKey(); - - buildAction?.Invoke(builder); - } - - /// - /// Introduces and configures a complex parameter. - /// - /// A model builder. - /// An action that performs configuration of the entity type. - /// Type of the parameter. - /// An entity type builder for further configuration. - /// is null. - public static void ConfigureComplexCollectionParameter( - this ModelBuilder modelBuilder, - Action>? buildAction = null) - where T : class - { - var builder = modelBuilder.SharedTypeEntity(EntityNameProvider.GetCollectionParameterName(typeof(T), false)); - - builder.ToTable(typeof(T).ShortDisplayName(), - static tableBuilder => tableBuilder.ExcludeFromMigrations()) - .HasNoKey(); - - buildAction?.Invoke(builder); - } - - /// - /// Introduces and configures a keyless temp table. - /// - /// A model builder. - /// An action that performs configuration of the entity type. - /// Type of the temp table. - /// An entity type builder for further configuration. - /// is null. - public static void ConfigureTempTableEntity( - this ModelBuilder modelBuilder, - Action>? buildAction) - where T : class - { - ConfigureTempTableEntity(modelBuilder, true, buildAction); - } - - /// - /// Introduces and configures a temp table. - /// - /// A model builder. - /// Indication whether the entity has a key or not. - /// An action that performs configuration of the entity type. - /// Type of the temp table. - /// An entity type builder for further configuration. - /// is null. - public static void ConfigureTempTableEntity( - this ModelBuilder modelBuilder, - bool isKeyless = true, - Action>? buildAction = null) - where T : class - { - var builder = modelBuilder.ConfigureTempTableInternal(isKeyless); - - buildAction?.Invoke(builder); - } - - /// - /// Introduces and configures a keyless temp table. - /// - /// A model builder. - /// An action that performs configuration of the entity type. - /// Type of the column. - /// An entity type builder for further configuration. - /// is null. - public static void ConfigureTempTable( - this ModelBuilder modelBuilder, - Action>>? buildAction) - { - ConfigureTempTable(modelBuilder, true, buildAction); - } - - /// - /// Introduces and configures a temp table. - /// - /// A model builder. - /// Indication whether the entity has a key or not. - /// An action that performs configuration of the entity type. - /// Type of the column. - /// An entity type builder for further configuration. - /// is null. - public static void ConfigureTempTable( - this ModelBuilder modelBuilder, - bool isKeyless = true, - Action>>? buildAction = null) - { - var builder = modelBuilder.ConfigureTempTableInternal>(isKeyless); - - if (!isKeyless) - { - builder.HasKey(t => t.Column1); - builder.Property(t => t.Column1).ValueGeneratedNever(); - } - - buildAction?.Invoke(builder); - } - - /// - /// Introduces and configures a keyless temp table. - /// - /// A model builder. - /// An action that performs configuration of the entity type. - /// Type of the column 1. - /// Type of the column 2. - /// An entity type builder for further configuration. - /// is null. - public static void ConfigureTempTable( - this ModelBuilder modelBuilder, - Action>>? buildAction) - { - ConfigureTempTable(modelBuilder, true, buildAction); - } - - /// - /// Introduces and configures a temp table. - /// - /// A model builder. - /// Indication whether the entity has a key or not. - /// An action that performs configuration of the entity type. - /// Type of the column 1. - /// Type of the column 2. - /// An entity type builder for further configuration. - /// is null. - public static void ConfigureTempTable( - this ModelBuilder modelBuilder, - bool isKeyless = true, - Action>>? buildAction = null) - { - ArgumentNullException.ThrowIfNull(modelBuilder); - - var builder = modelBuilder.ConfigureTempTableInternal>(isKeyless); - - if (!isKeyless) - { - builder.HasKey(t => new { t.Column1, t.Column2 }); - builder.Property(t => t.Column1).ValueGeneratedNever(); - builder.Property(t => t.Column2).ValueGeneratedNever(); - } - - buildAction?.Invoke(builder); - } - - private static EntityTypeBuilder ConfigureTempTableInternal(this ModelBuilder modelBuilder, bool isKeyless) - where T : class - { - ArgumentNullException.ThrowIfNull(modelBuilder); - - var builder = modelBuilder.SharedTypeEntity(EntityNameProvider.GetTempTableName(typeof(T))) - .ToTable($"#{typeof(T).ShortDisplayName()}", - tableBuilder => tableBuilder.ExcludeFromMigrations()); - - if (isKeyless) - builder.HasNoKey(); - - return builder; - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Thinktecture.EntityFrameworkCore.Parameters; +using Thinktecture.EntityFrameworkCore.TempTables; +using Thinktecture.Internal; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extensions for . +/// +public static class BulkOperationsModelBuilderExtensions +{ + /// + /// Introduces and configures a scalar parameter. + /// + /// A model builder. + /// An action that performs configuration of the entity type. + /// Type of the column. + /// An entity type builder for further configuration. + /// is null. + public static void ConfigureScalarCollectionParameter( + this ModelBuilder modelBuilder, + Action>>? buildAction = null) + { + var builder = modelBuilder.SharedTypeEntity>(EntityNameProvider.GetCollectionParameterName(typeof(T), true)) + .ToTable(typeof(ScalarCollectionParameter).ShortDisplayName(), + static tableBuilder => tableBuilder.ExcludeFromMigrations()) + .HasNoKey(); + + buildAction?.Invoke(builder); + } + + /// + /// Introduces and configures a complex parameter. + /// + /// A model builder. + /// An action that performs configuration of the entity type. + /// Type of the parameter. + /// An entity type builder for further configuration. + /// is null. + public static void ConfigureComplexCollectionParameter( + this ModelBuilder modelBuilder, + Action>? buildAction = null) + where T : class + { + var builder = modelBuilder.SharedTypeEntity(EntityNameProvider.GetCollectionParameterName(typeof(T), false)); + + builder.ToTable(typeof(T).ShortDisplayName(), + static tableBuilder => tableBuilder.ExcludeFromMigrations()) + .HasNoKey(); + + buildAction?.Invoke(builder); + } + + /// + /// Introduces and configures a keyless temp table. + /// + /// A model builder. + /// An action that performs configuration of the entity type. + /// Type of the temp table. + /// An entity type builder for further configuration. + /// is null. + public static void ConfigureTempTableEntity( + this ModelBuilder modelBuilder, + Action>? buildAction) + where T : class + { + ConfigureTempTableEntity(modelBuilder, true, buildAction); + } + + /// + /// Introduces and configures a temp table. + /// + /// A model builder. + /// Indication whether the entity has a key or not. + /// An action that performs configuration of the entity type. + /// Type of the temp table. + /// An entity type builder for further configuration. + /// is null. + public static void ConfigureTempTableEntity( + this ModelBuilder modelBuilder, + bool isKeyless = true, + Action>? buildAction = null) + where T : class + { + var builder = modelBuilder.ConfigureTempTableInternal(isKeyless); + + buildAction?.Invoke(builder); + } + + /// + /// Introduces and configures a keyless temp table. + /// + /// A model builder. + /// An action that performs configuration of the entity type. + /// Type of the column. + /// An entity type builder for further configuration. + /// is null. + public static void ConfigureTempTable( + this ModelBuilder modelBuilder, + Action>>? buildAction) + { + ConfigureTempTable(modelBuilder, true, buildAction); + } + + /// + /// Introduces and configures a temp table. + /// + /// A model builder. + /// Indication whether the entity has a key or not. + /// An action that performs configuration of the entity type. + /// Type of the column. + /// An entity type builder for further configuration. + /// is null. + public static void ConfigureTempTable( + this ModelBuilder modelBuilder, + bool isKeyless = true, + Action>>? buildAction = null) + { + var builder = modelBuilder.ConfigureTempTableInternal>(isKeyless); + + if (!isKeyless) + { + builder.HasKey(t => t.Column1); + builder.Property(t => t.Column1).ValueGeneratedNever(); + } + + buildAction?.Invoke(builder); + } + + /// + /// Introduces and configures a keyless temp table. + /// + /// A model builder. + /// An action that performs configuration of the entity type. + /// Type of the column 1. + /// Type of the column 2. + /// An entity type builder for further configuration. + /// is null. + public static void ConfigureTempTable( + this ModelBuilder modelBuilder, + Action>>? buildAction) + { + ConfigureTempTable(modelBuilder, true, buildAction); + } + + /// + /// Introduces and configures a temp table. + /// + /// A model builder. + /// Indication whether the entity has a key or not. + /// An action that performs configuration of the entity type. + /// Type of the column 1. + /// Type of the column 2. + /// An entity type builder for further configuration. + /// is null. + public static void ConfigureTempTable( + this ModelBuilder modelBuilder, + bool isKeyless = true, + Action>>? buildAction = null) + { + ArgumentNullException.ThrowIfNull(modelBuilder); + + var builder = modelBuilder.ConfigureTempTableInternal>(isKeyless); + + if (!isKeyless) + { + builder.HasKey(t => new { t.Column1, t.Column2 }); + builder.Property(t => t.Column1).ValueGeneratedNever(); + builder.Property(t => t.Column2).ValueGeneratedNever(); + } + + buildAction?.Invoke(builder); + } + + private static EntityTypeBuilder ConfigureTempTableInternal(this ModelBuilder modelBuilder, bool isKeyless) + where T : class + { + ArgumentNullException.ThrowIfNull(modelBuilder); + + var builder = modelBuilder.SharedTypeEntity(EntityNameProvider.GetTempTableName(typeof(T))) + .ToTable($"#{typeof(T).ShortDisplayName()}", + tableBuilder => tableBuilder.ExcludeFromMigrations()); + + if (isKeyless) + builder.HasNoKey(); + + return builder; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsRelationalQueryableMethodTranslatingExpressionVisitorExtensions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsRelationalQueryableMethodTranslatingExpressionVisitorExtensions.cs index 7b7f1e55..c1b48466 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsRelationalQueryableMethodTranslatingExpressionVisitorExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsRelationalQueryableMethodTranslatingExpressionVisitorExtensions.cs @@ -1,72 +1,72 @@ -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; -using Thinktecture.EntityFrameworkCore.Internal; -using Thinktecture.Internal; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extensions for . -/// -public static class BulkOperationsRelationalQueryableMethodTranslatingExpressionVisitorExtensions -{ - /// - /// Translates custom methods like . - /// - /// The visitor. - /// Method call to translate. - /// Translated method call if a custom method is found; otherwise null. - /// - /// or is null. - /// - public static Expression? TranslateBulkMethods( - this RelationalQueryableMethodTranslatingExpressionVisitor visitor, - MethodCallExpression methodCallExpression) - { - ArgumentNullException.ThrowIfNull(visitor); - ArgumentNullException.ThrowIfNull(methodCallExpression); - - if (methodCallExpression.Method.DeclaringType == typeof(BulkOperationsDbSetExtensions)) - { - if (methodCallExpression.Method.Name == nameof(BulkOperationsDbSetExtensions.FromTempTable)) - { - var tempTableInfo = ((TempTableInfoExpression)methodCallExpression.Arguments[1]).Value; - var shapedQueryExpression = GetShapedQueryExpression(visitor, methodCallExpression); - - return TranslateFromTempTable(shapedQueryExpression, tempTableInfo); - } - - throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print())); - } - - return null; - } - - private static Expression TranslateFromTempTable( - ShapedQueryExpression shapedQueryExpression, - TempTableInfo tempTableInfo) - { - var tempTableName = tempTableInfo.Name ?? throw new Exception("No temp table name provided."); - - var selectExpression = (SelectExpression)shapedQueryExpression.QueryExpression; - var newSelectExpression = selectExpression.AddAnnotation(new Annotation(ThinktectureBulkOperationsAnnotationNames.TEMP_TABLE, tempTableName)); - - return shapedQueryExpression.Update(newSelectExpression, shapedQueryExpression.ShaperExpression); - } - - private static ShapedQueryExpression GetShapedQueryExpression( - ExpressionVisitor visitor, - MethodCallExpression methodCallExpression) - { - var source = visitor.Visit(methodCallExpression.Arguments[0]); - - if (source is not ShapedQueryExpression shapedQueryExpression) - throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print())); - - return shapedQueryExpression; - } -} +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Thinktecture.EntityFrameworkCore.Internal; +using Thinktecture.Internal; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extensions for . +/// +public static class BulkOperationsRelationalQueryableMethodTranslatingExpressionVisitorExtensions +{ + /// + /// Translates custom methods like . + /// + /// The visitor. + /// Method call to translate. + /// Translated method call if a custom method is found; otherwise null. + /// + /// or is null. + /// + public static Expression? TranslateBulkMethods( + this RelationalQueryableMethodTranslatingExpressionVisitor visitor, + MethodCallExpression methodCallExpression) + { + ArgumentNullException.ThrowIfNull(visitor); + ArgumentNullException.ThrowIfNull(methodCallExpression); + + if (methodCallExpression.Method.DeclaringType == typeof(BulkOperationsDbSetExtensions)) + { + if (methodCallExpression.Method.Name == nameof(BulkOperationsDbSetExtensions.FromTempTable)) + { + var tempTableInfo = ((TempTableInfoExpression)methodCallExpression.Arguments[1]).Value; + var shapedQueryExpression = GetShapedQueryExpression(visitor, methodCallExpression); + + return TranslateFromTempTable(shapedQueryExpression, tempTableInfo); + } + + throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print())); + } + + return null; + } + + private static Expression TranslateFromTempTable( + ShapedQueryExpression shapedQueryExpression, + TempTableInfo tempTableInfo) + { + var tempTableName = tempTableInfo.Name ?? throw new Exception("No temp table name provided."); + + var selectExpression = (SelectExpression)shapedQueryExpression.QueryExpression; + var newSelectExpression = selectExpression.AddAnnotation(new Annotation(ThinktectureBulkOperationsAnnotationNames.TEMP_TABLE, tempTableName)); + + return shapedQueryExpression.Update(newSelectExpression, shapedQueryExpression.ShaperExpression); + } + + private static ShapedQueryExpression GetShapedQueryExpression( + ExpressionVisitor visitor, + MethodCallExpression methodCallExpression) + { + var source = visitor.Visit(methodCallExpression.Arguments[0]); + + if (source is not ShapedQueryExpression shapedQueryExpression) + throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print())); + + return shapedQueryExpression; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsServiceCollectionExtensions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsServiceCollectionExtensions.cs index 8546fbab..368bb24c 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsServiceCollectionExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/BulkOperationsServiceCollectionExtensions.cs @@ -1,24 +1,24 @@ -using Microsoft.Extensions.DependencyInjection; -using Thinktecture.EntityFrameworkCore.TempTables.NameSuffixing; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extensions for -/// -public static class BulkOperationsServiceCollectionExtensions -{ - /// - /// Registers components required for creation of temp tables. - /// - /// Service collection to register the components with. - /// The provided . - public static IServiceCollection AddTempTableSuffixComponents(this IServiceCollection services) - { - services.AddScoped(); - services.AddSingleton(); - - return services; - } -} +using Microsoft.Extensions.DependencyInjection; +using Thinktecture.EntityFrameworkCore.TempTables.NameSuffixing; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extensions for +/// +public static class BulkOperationsServiceCollectionExtensions +{ + /// + /// Registers components required for creation of temp tables. + /// + /// Service collection to register the components with. + /// The provided . + public static IServiceCollection AddTempTableSuffixComponents(this IServiceCollection services) + { + services.AddScoped(); + services.AddSingleton(); + + return services; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/Internal/BulkOperationsDbSetExtensions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/Internal/BulkOperationsDbSetExtensions.cs index beee5d60..43988580 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/Internal/BulkOperationsDbSetExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/Internal/BulkOperationsDbSetExtensions.cs @@ -1,30 +1,30 @@ -using System.Linq.Expressions; -using System.Reflection; - -// ReSharper disable once CheckNamespace -namespace Thinktecture.Internal; - -/// -/// This is an internal API. -/// -public static class BulkOperationsDbSetExtensions -{ - private static readonly MethodInfo _fromTempTable = typeof(BulkOperationsDbSetExtensions).GetMethods(BindingFlags.Public | BindingFlags.Static) - .Single(m => m.Name == nameof(FromTempTable) && m.IsGenericMethod); - - /// - /// This is an internal API. - /// - public static IQueryable FromTempTable( - this IQueryable source, - TempTableInfo info) - { - ArgumentNullException.ThrowIfNull(source); - ArgumentNullException.ThrowIfNull(info); - - var methodInfo = _fromTempTable.MakeGenericMethod(typeof(T)); - var expression = Expression.Call(null, methodInfo, source.Expression, new TempTableInfoExpression(info)); - - return source.Provider.CreateQuery(expression); - } -} +using System.Linq.Expressions; +using System.Reflection; + +// ReSharper disable once CheckNamespace +namespace Thinktecture.Internal; + +/// +/// This is an internal API. +/// +public static class BulkOperationsDbSetExtensions +{ + private static readonly MethodInfo _fromTempTable = typeof(BulkOperationsDbSetExtensions).GetMethods(BindingFlags.Public | BindingFlags.Static) + .Single(m => m.Name == nameof(FromTempTable) && m.IsGenericMethod); + + /// + /// This is an internal API. + /// + public static IQueryable FromTempTable( + this IQueryable source, + TempTableInfo info) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(info); + + var methodInfo = _fromTempTable.MakeGenericMethod(typeof(T)); + var expression = Expression.Call(null, methodInfo, source.Expression, new TempTableInfoExpression(info)); + + return source.Provider.CreateQuery(expression); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/MemberInfoExtensions.cs b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/MemberInfoExtensions.cs index 0ac57bf2..6681fb6b 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/MemberInfoExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Extensions/MemberInfoExtensions.cs @@ -1,15 +1,15 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -namespace Thinktecture; - -internal static class MemberInfoExtensions -{ - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsEqualTo(this MemberInfo member, MemberInfo? other) - { - return other is not null - && member.MetadataToken == other.MetadataToken - && ReferenceEquals(member.Module, other.Module); - } -} +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Thinktecture; + +internal static class MemberInfoExtensions +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsEqualTo(this MemberInfo member, MemberInfo? other) + { + return other is not null + && member.MetadataToken == other.MetadataToken + && ReferenceEquals(member.Module, other.Module); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Thinktecture.EntityFrameworkCore.BulkOperations.csproj b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Thinktecture.EntityFrameworkCore.BulkOperations.csproj index 385cbf99..d45374c4 100644 --- a/src/Thinktecture.EntityFrameworkCore.BulkOperations/Thinktecture.EntityFrameworkCore.BulkOperations.csproj +++ b/src/Thinktecture.EntityFrameworkCore.BulkOperations/Thinktecture.EntityFrameworkCore.BulkOperations.csproj @@ -1,7 +1,7 @@ - - - - - - - + + + + + + + diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/EntityDataReader.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/EntityDataReader.cs index d6d6aa78..12e5e037 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/EntityDataReader.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/EntityDataReader.cs @@ -1,159 +1,159 @@ -using System.Data; -using Microsoft.EntityFrameworkCore.Infrastructure; - -namespace Thinktecture.EntityFrameworkCore.Data; - -/// -/// Data reader for Entity Framework Core entities. -/// -/// Type of the entity. -public sealed class EntityDataReader : IEntityDataReader -{ - private readonly DbContext _ctx; - private readonly IEnumerator _enumerator; - private readonly Func[] _propertyGetterLookup; - private readonly IReadOnlyList? _entities; - private readonly List? _readEntities; - - /// - public IReadOnlyList Properties { get; } - - /// - public int FieldCount => Properties.Count; - - /// - /// Initializes - /// - /// Database context. - /// Property getter cache. - /// Entities to read. - /// Properties to read. - /// Makes sure the method has a collection to return. - public EntityDataReader( - DbContext ctx, - IPropertyGetterCache propertyGetterCache, - IEnumerable entities, - IReadOnlyList properties, - bool ensureReadEntitiesCollection) - { - ArgumentNullException.ThrowIfNull(propertyGetterCache); - ArgumentNullException.ThrowIfNull(entities); - - _ctx = ctx ?? throw new ArgumentNullException(nameof(ctx)); - Properties = properties ?? throw new ArgumentNullException(nameof(properties)); - - if (properties.Count == 0) - throw new ArgumentException("The properties collection cannot be empty.", nameof(properties)); - - _propertyGetterLookup = BuildPropertyGetterLookup(propertyGetterCache, properties); - _enumerator = entities.GetEnumerator(); - - if (ensureReadEntitiesCollection) - { - if (entities is IReadOnlyList entityList) - { - _entities = entityList; - } - else - { - _readEntities = new List(); - } - } - } - - private static Func[] BuildPropertyGetterLookup( - IPropertyGetterCache propertyGetterCache, - IReadOnlyList properties) - { - var lookup = new Func[properties.Count]; - - for (var i = 0; i < properties.Count; i++) - { - lookup[i] = propertyGetterCache.GetPropertyGetter(properties[i]); - } - - return lookup; - } - - /// - public int GetPropertyIndex(PropertyWithNavigations property) - { - for (var i = 0; i < Properties.Count; i++) - { - if (property.Equals(Properties[i])) - return i; - } - - throw new ArgumentException($"The property '{property.Property.Name}' of type '{property.Property.ClrType.ShortDisplayName()}' cannot be read by current reader."); - } - - /// - public IReadOnlyList GetReadEntities() - { - return _entities ?? _readEntities ?? throw new InvalidOperationException("'Read entities' were not requested previously."); - } - - /// -#pragma warning disable 8766 - public object? GetValue(int i) -#pragma warning restore 8766 - { - return _propertyGetterLookup[i](_ctx, _enumerator.Current); - } - - /// - public bool Read() - { - if (!_enumerator.MoveNext()) - return false; - - _readEntities?.Add(_enumerator.Current); - return true; - } - - /// - public bool IsDBNull(int i) - { - // we are reading entities (.NET objects), there must be no properties of type "DBNull". - return false; - } - - /// - public void Dispose() - { - _enumerator.Dispose(); - } - - // The following methods are not needed for bulk insert. - // ReSharper disable ArrangeMethodOrOperatorBody - object IDataRecord.this[int i] => throw new NotSupportedException(); - object IDataRecord.this[string name] => throw new NotSupportedException(); - int IDataReader.Depth => throw new NotSupportedException(); - int IDataReader.RecordsAffected => throw new NotSupportedException(); - bool IDataReader.IsClosed => throw new NotSupportedException(); - void IDataReader.Close() => throw new NotSupportedException(); - string IDataRecord.GetName(int i) => throw new NotSupportedException(); - string IDataRecord.GetDataTypeName(int i) => throw new NotSupportedException(); - Type IDataRecord.GetFieldType(int i) => throw new NotSupportedException(); - int IDataRecord.GetValues(object[] values) => throw new NotSupportedException(); - int IDataRecord.GetOrdinal(string name) => throw new NotSupportedException(); - bool IDataRecord.GetBoolean(int i) => throw new NotSupportedException(); - byte IDataRecord.GetByte(int i) => throw new NotSupportedException(); - long IDataRecord.GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferOffset, int length) => throw new NotSupportedException(); - char IDataRecord.GetChar(int i) => throw new NotSupportedException(); - long IDataRecord.GetChars(int i, long fieldOffset, char[]? buffer, int bufferOffset, int length) => throw new NotSupportedException(); - Guid IDataRecord.GetGuid(int i) => throw new NotSupportedException(); - short IDataRecord.GetInt16(int i) => throw new NotSupportedException(); - int IDataRecord.GetInt32(int i) => throw new NotSupportedException(); - long IDataRecord.GetInt64(int i) => throw new NotSupportedException(); - float IDataRecord.GetFloat(int i) => throw new NotSupportedException(); - double IDataRecord.GetDouble(int i) => throw new NotSupportedException(); - string IDataRecord.GetString(int i) => throw new NotSupportedException(); - decimal IDataRecord.GetDecimal(int i) => throw new NotSupportedException(); - DateTime IDataRecord.GetDateTime(int i) => throw new NotSupportedException(); - IDataReader IDataRecord.GetData(int i) => throw new NotSupportedException(); - DataTable IDataReader.GetSchemaTable() => throw new NotSupportedException(); - - bool IDataReader.NextResult() => throw new NotSupportedException(); - // ReSharper restore ArrangeMethodOrOperatorBody -} +using System.Data; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Thinktecture.EntityFrameworkCore.Data; + +/// +/// Data reader for Entity Framework Core entities. +/// +/// Type of the entity. +public sealed class EntityDataReader : IEntityDataReader +{ + private readonly DbContext _ctx; + private readonly IEnumerator _enumerator; + private readonly Func[] _propertyGetterLookup; + private readonly IReadOnlyList? _entities; + private readonly List? _readEntities; + + /// + public IReadOnlyList Properties { get; } + + /// + public int FieldCount => Properties.Count; + + /// + /// Initializes + /// + /// Database context. + /// Property getter cache. + /// Entities to read. + /// Properties to read. + /// Makes sure the method has a collection to return. + public EntityDataReader( + DbContext ctx, + IPropertyGetterCache propertyGetterCache, + IEnumerable entities, + IReadOnlyList properties, + bool ensureReadEntitiesCollection) + { + ArgumentNullException.ThrowIfNull(propertyGetterCache); + ArgumentNullException.ThrowIfNull(entities); + + _ctx = ctx ?? throw new ArgumentNullException(nameof(ctx)); + Properties = properties ?? throw new ArgumentNullException(nameof(properties)); + + if (properties.Count == 0) + throw new ArgumentException("The properties collection cannot be empty.", nameof(properties)); + + _propertyGetterLookup = BuildPropertyGetterLookup(propertyGetterCache, properties); + _enumerator = entities.GetEnumerator(); + + if (ensureReadEntitiesCollection) + { + if (entities is IReadOnlyList entityList) + { + _entities = entityList; + } + else + { + _readEntities = new List(); + } + } + } + + private static Func[] BuildPropertyGetterLookup( + IPropertyGetterCache propertyGetterCache, + IReadOnlyList properties) + { + var lookup = new Func[properties.Count]; + + for (var i = 0; i < properties.Count; i++) + { + lookup[i] = propertyGetterCache.GetPropertyGetter(properties[i]); + } + + return lookup; + } + + /// + public int GetPropertyIndex(PropertyWithNavigations property) + { + for (var i = 0; i < Properties.Count; i++) + { + if (property.Equals(Properties[i])) + return i; + } + + throw new ArgumentException($"The property '{property.Property.Name}' of type '{property.Property.ClrType.ShortDisplayName()}' cannot be read by current reader."); + } + + /// + public IReadOnlyList GetReadEntities() + { + return _entities ?? _readEntities ?? throw new InvalidOperationException("'Read entities' were not requested previously."); + } + + /// +#pragma warning disable 8766 + public object? GetValue(int i) +#pragma warning restore 8766 + { + return _propertyGetterLookup[i](_ctx, _enumerator.Current); + } + + /// + public bool Read() + { + if (!_enumerator.MoveNext()) + return false; + + _readEntities?.Add(_enumerator.Current); + return true; + } + + /// + public bool IsDBNull(int i) + { + // we are reading entities (.NET objects), there must be no properties of type "DBNull". + return false; + } + + /// + public void Dispose() + { + _enumerator.Dispose(); + } + + // The following methods are not needed for bulk insert. + // ReSharper disable ArrangeMethodOrOperatorBody + object IDataRecord.this[int i] => throw new NotSupportedException(); + object IDataRecord.this[string name] => throw new NotSupportedException(); + int IDataReader.Depth => throw new NotSupportedException(); + int IDataReader.RecordsAffected => throw new NotSupportedException(); + bool IDataReader.IsClosed => throw new NotSupportedException(); + void IDataReader.Close() => throw new NotSupportedException(); + string IDataRecord.GetName(int i) => throw new NotSupportedException(); + string IDataRecord.GetDataTypeName(int i) => throw new NotSupportedException(); + Type IDataRecord.GetFieldType(int i) => throw new NotSupportedException(); + int IDataRecord.GetValues(object[] values) => throw new NotSupportedException(); + int IDataRecord.GetOrdinal(string name) => throw new NotSupportedException(); + bool IDataRecord.GetBoolean(int i) => throw new NotSupportedException(); + byte IDataRecord.GetByte(int i) => throw new NotSupportedException(); + long IDataRecord.GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferOffset, int length) => throw new NotSupportedException(); + char IDataRecord.GetChar(int i) => throw new NotSupportedException(); + long IDataRecord.GetChars(int i, long fieldOffset, char[]? buffer, int bufferOffset, int length) => throw new NotSupportedException(); + Guid IDataRecord.GetGuid(int i) => throw new NotSupportedException(); + short IDataRecord.GetInt16(int i) => throw new NotSupportedException(); + int IDataRecord.GetInt32(int i) => throw new NotSupportedException(); + long IDataRecord.GetInt64(int i) => throw new NotSupportedException(); + float IDataRecord.GetFloat(int i) => throw new NotSupportedException(); + double IDataRecord.GetDouble(int i) => throw new NotSupportedException(); + string IDataRecord.GetString(int i) => throw new NotSupportedException(); + decimal IDataRecord.GetDecimal(int i) => throw new NotSupportedException(); + DateTime IDataRecord.GetDateTime(int i) => throw new NotSupportedException(); + IDataReader IDataRecord.GetData(int i) => throw new NotSupportedException(); + DataTable IDataReader.GetSchemaTable() => throw new NotSupportedException(); + + bool IDataReader.NextResult() => throw new NotSupportedException(); + // ReSharper restore ArrangeMethodOrOperatorBody +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/EntityDataReaderFactory.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/EntityDataReaderFactory.cs index e87a0224..fccd32fb 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/EntityDataReaderFactory.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/EntityDataReaderFactory.cs @@ -1,33 +1,33 @@ -namespace Thinktecture.EntityFrameworkCore.Data; - -/// -/// Factory for . -/// -// ReSharper disable once ClassNeverInstantiated.Global -public sealed class EntityDataReaderFactory : IEntityDataReaderFactory -{ - private readonly IPropertyGetterCache _propertyGetterCache; - - /// - /// Initializes new instance of - /// - /// Property getter cache. - public EntityDataReaderFactory(IPropertyGetterCache propertyGetterCache) - { - _propertyGetterCache = propertyGetterCache ?? throw new ArgumentNullException(nameof(propertyGetterCache)); - } - - /// - public IEntityDataReader Create( - DbContext ctx, - IEnumerable entities, - IReadOnlyList properties, - bool ensureReadEntitiesCollection) - { - ArgumentNullException.ThrowIfNull(ctx); - ArgumentNullException.ThrowIfNull(entities); - ArgumentNullException.ThrowIfNull(properties); - - return new EntityDataReader(ctx, _propertyGetterCache, entities, properties, ensureReadEntitiesCollection); - } -} +namespace Thinktecture.EntityFrameworkCore.Data; + +/// +/// Factory for . +/// +// ReSharper disable once ClassNeverInstantiated.Global +public sealed class EntityDataReaderFactory : IEntityDataReaderFactory +{ + private readonly IPropertyGetterCache _propertyGetterCache; + + /// + /// Initializes new instance of + /// + /// Property getter cache. + public EntityDataReaderFactory(IPropertyGetterCache propertyGetterCache) + { + _propertyGetterCache = propertyGetterCache ?? throw new ArgumentNullException(nameof(propertyGetterCache)); + } + + /// + public IEntityDataReader Create( + DbContext ctx, + IEnumerable entities, + IReadOnlyList properties, + bool ensureReadEntitiesCollection) + { + ArgumentNullException.ThrowIfNull(ctx); + ArgumentNullException.ThrowIfNull(entities); + ArgumentNullException.ThrowIfNull(properties); + + return new EntityDataReader(ctx, _propertyGetterCache, entities, properties, ensureReadEntitiesCollection); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IEntityDataReader.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IEntityDataReader.cs index 2fc1a174..d22e1c72 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IEntityDataReader.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IEntityDataReader.cs @@ -1,36 +1,36 @@ -using System.Data; -using System.Reflection; - -namespace Thinktecture.EntityFrameworkCore.Data; - -/// -/// Data reader to be used for bulk inserts. -/// -public interface IEntityDataReader : IDataReader -{ - /// - /// Gets the properties the reader is created for. - /// - /// A collection of . - IReadOnlyList Properties { get; } - - /// - /// Gets the index of the provided that matches with the one of . - /// - /// Property to get the index for. - /// Index of the property. - int GetPropertyIndex(PropertyWithNavigations property); -} - -/// -/// Data reader to be used for bulk inserts. -/// -public interface IEntityDataReader : IEntityDataReader -{ - /// - /// Gets the entities that are read by now. - /// This method should not be called until the end of the reading by the reader otherwise the returned collection will be incomplete! - /// - /// Read entities. - IReadOnlyList GetReadEntities(); -} +using System.Data; +using System.Reflection; + +namespace Thinktecture.EntityFrameworkCore.Data; + +/// +/// Data reader to be used for bulk inserts. +/// +public interface IEntityDataReader : IDataReader +{ + /// + /// Gets the properties the reader is created for. + /// + /// A collection of . + IReadOnlyList Properties { get; } + + /// + /// Gets the index of the provided that matches with the one of . + /// + /// Property to get the index for. + /// Index of the property. + int GetPropertyIndex(PropertyWithNavigations property); +} + +/// +/// Data reader to be used for bulk inserts. +/// +public interface IEntityDataReader : IEntityDataReader +{ + /// + /// Gets the entities that are read by now. + /// This method should not be called until the end of the reading by the reader otherwise the returned collection will be incomplete! + /// + /// Read entities. + IReadOnlyList GetReadEntities(); +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IEntityDataReaderFactory.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IEntityDataReaderFactory.cs index 84b2c619..1a2f9df8 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IEntityDataReaderFactory.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IEntityDataReaderFactory.cs @@ -1,23 +1,23 @@ -namespace Thinktecture.EntityFrameworkCore.Data; - -/// -/// Factory for creation of . -/// -public interface IEntityDataReaderFactory -{ - /// - /// Creates an for entities of type . - /// The data reader reads the provided only. - /// - /// Database context. - /// Entities to use by the data reader. - /// Properties of the entity of type to generate the data reader for. - /// Makes sure the method has a collection to return. - /// Type of the entity. - /// An instance of . - IEntityDataReader Create( - DbContext ctx, - IEnumerable entities, - IReadOnlyList properties, - bool ensureReadEntitiesCollection); -} +namespace Thinktecture.EntityFrameworkCore.Data; + +/// +/// Factory for creation of . +/// +public interface IEntityDataReaderFactory +{ + /// + /// Creates an for entities of type . + /// The data reader reads the provided only. + /// + /// Database context. + /// Entities to use by the data reader. + /// Properties of the entity of type to generate the data reader for. + /// Makes sure the method has a collection to return. + /// Type of the entity. + /// An instance of . + IEntityDataReader Create( + DbContext ctx, + IEnumerable entities, + IReadOnlyList properties, + bool ensureReadEntitiesCollection); +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IPropertyGetterCache.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IPropertyGetterCache.cs index 35fb2a86..86f8f746 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IPropertyGetterCache.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IPropertyGetterCache.cs @@ -1,15 +1,15 @@ -namespace Thinktecture.EntityFrameworkCore.Data; - -/// -/// Builds and caches property getters. -/// -public interface IPropertyGetterCache -{ - /// - /// Gets a property get for provided . - /// - /// Property to get the getter for. - /// Type of the root entity. - /// Property getter. - Func GetPropertyGetter(PropertyWithNavigations property); -} +namespace Thinktecture.EntityFrameworkCore.Data; + +/// +/// Builds and caches property getters. +/// +public interface IPropertyGetterCache +{ + /// + /// Gets a property get for provided . + /// + /// Property to get the getter for. + /// Type of the root entity. + /// Property getter. + Func GetPropertyGetter(PropertyWithNavigations property); +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IShadowPropertyGetter.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IShadowPropertyGetter.cs index 3eec7c34..4ef14102 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IShadowPropertyGetter.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/IShadowPropertyGetter.cs @@ -1,15 +1,15 @@ -namespace Thinktecture.EntityFrameworkCore.Data; - -/// -/// Getter for a shadow property. -/// -public interface IShadowPropertyGetter -{ - /// - /// Gets the value of the shadow property. - /// - /// Context. - /// Entity - /// The value of the shadow property. - object? GetValue(DbContext ctx, object entity); -} +namespace Thinktecture.EntityFrameworkCore.Data; + +/// +/// Getter for a shadow property. +/// +public interface IShadowPropertyGetter +{ + /// + /// Gets the value of the shadow property. + /// + /// Context. + /// Entity + /// The value of the shadow property. + object? GetValue(DbContext ctx, object entity); +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/PropertyGetterCache.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/PropertyGetterCache.cs index 14311ffd..f16729f3 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/PropertyGetterCache.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/PropertyGetterCache.cs @@ -1,148 +1,148 @@ -using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Microsoft.Extensions.Logging; - -namespace Thinktecture.EntityFrameworkCore.Data; - -/// -/// Builds and caches property getters. -/// -public class PropertyGetterCache : IPropertyGetterCache -{ - private readonly ILogger _logger; - private readonly ConcurrentDictionary _propertyGetterLookup; - - /// - /// Initializes new instance of . - /// - /// Logger factory. - public PropertyGetterCache(ILoggerFactory loggerFactory) - { - _logger = loggerFactory?.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory)); - _propertyGetterLookup = new ConcurrentDictionary(); - } - - /// - public Func GetPropertyGetter(PropertyWithNavigations property) - { - return (Func)_propertyGetterLookup.GetOrAdd(property, BuildPropertyGetter); - } - - private Func BuildPropertyGetter(PropertyWithNavigations cacheKey) - { - var property = cacheKey.Property; - var storeObject = cacheKey.GetStoreObject(); - var hasSqlDefaultValue = property.GetDefaultValueSql(storeObject) != null; - var hasDefaultValue = property.TryGetDefaultValue(storeObject, out _); - - if ((hasSqlDefaultValue || hasDefaultValue) && !property.IsNullable) - { - if (property.ClrType.IsClass) - { - _logger.LogWarning("The corresponding column of '{Entity}.{Property}' has a DEFAULT value constraint in the database and is NOT NULL. Depending on the database vendor the .NET value `null` may lead to an exception because the tool for bulk insert of data may prevent sending `null`s for NOT NULL columns. Use 'PropertiesToInsert/PropertiesToUpdate' on corresponding options to specify properties to insert/update and skip the property so database uses the DEFAULT value.", - property.DeclaringType.ClrType.Name, property.Name); - } - else if (!property.ClrType.IsGenericType || - (!property.ClrType.IsGenericTypeDefinition && property.ClrType.GetGenericTypeDefinition() != typeof(Nullable<>))) - { - _logger.LogWarning("The corresponding column of '{Entity}.{Property}' has a DEFAULT value constraint in the database and is NOT NULL. Depending on the database vendor the \".NET default values\" (`false`, `0`, `00000000-0000-0000-0000-000000000000` etc.) may lead to unexpected results because these values are sent to the database as-is, i.e. the DEFAULT value constraint will NOT be used by database. Use 'PropertiesToInsert/PropertiesToUpdate' on corresponding options to specify properties to insert and skip the property so database uses the DEFAULT value.", - property.DeclaringType.ClrType.Name, property.Name); - } - } - - var getter = BuildGetter(property); - var converter = property.GetValueConverter(); - - if (converter != null) - getter = UseConverter(getter, converter); - - if (cacheKey.Navigations.Count != 0) - { - var naviGetter = BuildNavigationGetter(cacheKey.Navigations); - getter = Combine(naviGetter, getter); - } - - return (Func)(object)getter; - } - - private static Func BuildNavigationGetter(IReadOnlyList navigations) - { - Func? getter = null; - - for (var i = 0; i < navigations.Count; i++) - { - getter = Combine(getter, navigations[i].GetGetter().GetClrValue); - } - - return getter ?? throw new ArgumentException("No navigations provided."); - } - - private static Func Combine(Func? parentGetter, Func getter) - { - if (parentGetter is null) - return getter; - - return e => - { - var childEntity = parentGetter(e); - - return childEntity == null ? null : getter(childEntity); - }; - } - - private static Func Combine(Func naviGetter, Func getter) - { - return (ctx, e) => - { - var childEntity = naviGetter(e); - - return childEntity == null ? null : getter(ctx, childEntity); - }; - } - - private static Func BuildGetter(IProperty property) - { - if (property.IsShadowProperty()) - { - var shadowPropGetter = CreateShadowPropertyGetter(property); - return shadowPropGetter.GetValue; - } - - var getter = property.GetGetter(); - - if (getter == null) - throw new ArgumentException($"The property '{property.Name}' of entity '{property.DeclaringType.Name}' has no property getter."); - - return (_, entity) => getter.GetClrValueUsingContainingEntity(entity); - } - - private static Func UseConverter(Func getter, ValueConverter converter) - { - var convert = converter.ConvertToProvider; - - return (ctx, e) => - { - var value = getter(ctx, e); - - if (value != null) - value = convert(value); - - return value; - }; - } - - [SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] - private static IShadowPropertyGetter CreateShadowPropertyGetter(IProperty property) - { - var currentValueGetter = property.GetPropertyAccessors().CurrentValueGetter; - var shadowPropGetterType = typeof(ShadowPropertyGetter<>).MakeGenericType(property.ClrType); - var shadowPropGetter = Activator.CreateInstance(shadowPropGetterType, currentValueGetter) - ?? throw new Exception($"Could not create shadow property getter of type '{shadowPropGetterType.ShortDisplayName()}'."); - - return (IShadowPropertyGetter)shadowPropGetter; - } -} +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.Extensions.Logging; + +namespace Thinktecture.EntityFrameworkCore.Data; + +/// +/// Builds and caches property getters. +/// +public class PropertyGetterCache : IPropertyGetterCache +{ + private readonly ILogger _logger; + private readonly ConcurrentDictionary _propertyGetterLookup; + + /// + /// Initializes new instance of . + /// + /// Logger factory. + public PropertyGetterCache(ILoggerFactory loggerFactory) + { + _logger = loggerFactory?.CreateLogger() ?? throw new ArgumentNullException(nameof(loggerFactory)); + _propertyGetterLookup = new ConcurrentDictionary(); + } + + /// + public Func GetPropertyGetter(PropertyWithNavigations property) + { + return (Func)_propertyGetterLookup.GetOrAdd(property, BuildPropertyGetter); + } + + private Func BuildPropertyGetter(PropertyWithNavigations cacheKey) + { + var property = cacheKey.Property; + var storeObject = cacheKey.GetStoreObject(); + var hasSqlDefaultValue = property.GetDefaultValueSql(storeObject) != null; + var hasDefaultValue = property.TryGetDefaultValue(storeObject, out _); + + if ((hasSqlDefaultValue || hasDefaultValue) && !property.IsNullable) + { + if (property.ClrType.IsClass) + { + _logger.LogWarning("The corresponding column of '{Entity}.{Property}' has a DEFAULT value constraint in the database and is NOT NULL. Depending on the database vendor the .NET value `null` may lead to an exception because the tool for bulk insert of data may prevent sending `null`s for NOT NULL columns. Use 'PropertiesToInsert/PropertiesToUpdate' on corresponding options to specify properties to insert/update and skip the property so database uses the DEFAULT value.", + property.DeclaringType.ClrType.Name, property.Name); + } + else if (!property.ClrType.IsGenericType || + (!property.ClrType.IsGenericTypeDefinition && property.ClrType.GetGenericTypeDefinition() != typeof(Nullable<>))) + { + _logger.LogWarning("The corresponding column of '{Entity}.{Property}' has a DEFAULT value constraint in the database and is NOT NULL. Depending on the database vendor the \".NET default values\" (`false`, `0`, `00000000-0000-0000-0000-000000000000` etc.) may lead to unexpected results because these values are sent to the database as-is, i.e. the DEFAULT value constraint will NOT be used by database. Use 'PropertiesToInsert/PropertiesToUpdate' on corresponding options to specify properties to insert and skip the property so database uses the DEFAULT value.", + property.DeclaringType.ClrType.Name, property.Name); + } + } + + var getter = BuildGetter(property); + var converter = property.GetValueConverter(); + + if (converter != null) + getter = UseConverter(getter, converter); + + if (cacheKey.Navigations.Count != 0) + { + var naviGetter = BuildNavigationGetter(cacheKey.Navigations); + getter = Combine(naviGetter, getter); + } + + return (Func)(object)getter; + } + + private static Func BuildNavigationGetter(IReadOnlyList navigations) + { + Func? getter = null; + + for (var i = 0; i < navigations.Count; i++) + { + getter = Combine(getter, navigations[i].GetGetter().GetClrValue); + } + + return getter ?? throw new ArgumentException("No navigations provided."); + } + + private static Func Combine(Func? parentGetter, Func getter) + { + if (parentGetter is null) + return getter; + + return e => + { + var childEntity = parentGetter(e); + + return childEntity == null ? null : getter(childEntity); + }; + } + + private static Func Combine(Func naviGetter, Func getter) + { + return (ctx, e) => + { + var childEntity = naviGetter(e); + + return childEntity == null ? null : getter(ctx, childEntity); + }; + } + + private static Func BuildGetter(IProperty property) + { + if (property.IsShadowProperty()) + { + var shadowPropGetter = CreateShadowPropertyGetter(property); + return shadowPropGetter.GetValue; + } + + var getter = property.GetGetter(); + + if (getter == null) + throw new ArgumentException($"The property '{property.Name}' of entity '{property.DeclaringType.Name}' has no property getter."); + + return (_, entity) => getter.GetClrValueUsingContainingEntity(entity); + } + + private static Func UseConverter(Func getter, ValueConverter converter) + { + var convert = converter.ConvertToProvider; + + return (ctx, e) => + { + var value = getter(ctx, e); + + if (value != null) + value = convert(value); + + return value; + }; + } + + [SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] + private static IShadowPropertyGetter CreateShadowPropertyGetter(IProperty property) + { + var currentValueGetter = property.GetPropertyAccessors().CurrentValueGetter; + var shadowPropGetterType = typeof(ShadowPropertyGetter<>).MakeGenericType(property.ClrType); + var shadowPropGetter = Activator.CreateInstance(shadowPropGetterType, currentValueGetter) + ?? throw new Exception($"Could not create shadow property getter of type '{shadowPropGetterType.ShortDisplayName()}'."); + + return (IShadowPropertyGetter)shadowPropGetter; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/PropertyWithNavigations.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/PropertyWithNavigations.cs index b8c52453..ecd70b92 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/PropertyWithNavigations.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/PropertyWithNavigations.cs @@ -1,120 +1,120 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.Data; - -/// -/// Contains the property of an entity and the navigations to reach this property. -/// -public readonly struct PropertyWithNavigations : IEquatable -{ - /// - /// A property of an entity. - /// - public IProperty Property { get; } - - /// - /// Navigations to reach the . - /// - public IReadOnlyList Navigations { get; } - - /// - /// Indication whether this property is persisted in the same table as the parent or in a separate table. - /// - public bool IsInlined { get; } - - /// - /// Initializes a new instance of . - /// - /// A property of an entity. - /// Navigations to reach the . - public PropertyWithNavigations( - IProperty property, - IReadOnlyList navigations) - { - Property = property; - Navigations = navigations; - IsInlined = Navigations.Count == 0 || Navigations.All(n => n.IsInlined()); - } - - /// - public override bool Equals(object? obj) - { - return obj is PropertyWithNavigations other && Equals(other); - } - - /// - public bool Equals(PropertyWithNavigations other) - { - return Property.Equals(other.Property) && Equals(other.Navigations); - } - - private bool Equals(IReadOnlyList other) - { - if (Navigations.Count != other.Count) - return false; - - for (var i = 0; i < Navigations.Count; i++) - { - var navigation = Navigations[i]; - var otherNavi = other[i]; - - if (!navigation.Equals(otherNavi)) - return false; - } - - return true; - } - - /// - public override int GetHashCode() - { - var hashCode = new HashCode(); - hashCode.Add(Property); - Navigations.ComputeHashCode(hashCode); - - return hashCode.ToHashCode(); - } - - /// - public override string ToString() - { - var path = String.Join(".", Navigations.Select(n => n.Name)); - - return String.IsNullOrWhiteSpace(path) ? Property.Name : $"{path}.{Property.Name}"; - } - - /// - /// Drops all leading incl. first non-inlined navigation and creates a new from the rest navigations. - /// - /// Dropped navigations and a new property without the dropped navigations. - /// If the current property has no separate navigations. - public (IReadOnlyList DroppedNavigations, PropertyWithNavigations Property) DropUntilSeparateNavigation() - { - var droppedNavigation = new List(); - - for (var i = 0; i < Navigations.Count; i++) - { - var navigation = Navigations[i]; - - if (navigation.IsInlined()) - { - droppedNavigation.Add(navigation); - } - else - { - droppedNavigation.Add(navigation); - - var separateNavigations = new List(); - - for (var j = i + 1; j < Navigations.Count; j++) - { - separateNavigations.Add(Navigations[j]); - } - - return (droppedNavigation, new PropertyWithNavigations(Property, separateNavigations)); - } - } - - throw new InvalidOperationException("This method must not be called for properties without separate navigations."); - } -} +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.Data; + +/// +/// Contains the property of an entity and the navigations to reach this property. +/// +public readonly struct PropertyWithNavigations : IEquatable +{ + /// + /// A property of an entity. + /// + public IProperty Property { get; } + + /// + /// Navigations to reach the . + /// + public IReadOnlyList Navigations { get; } + + /// + /// Indication whether this property is persisted in the same table as the parent or in a separate table. + /// + public bool IsInlined { get; } + + /// + /// Initializes a new instance of . + /// + /// A property of an entity. + /// Navigations to reach the . + public PropertyWithNavigations( + IProperty property, + IReadOnlyList navigations) + { + Property = property; + Navigations = navigations; + IsInlined = Navigations.Count == 0 || Navigations.All(n => n.IsInlined()); + } + + /// + public override bool Equals(object? obj) + { + return obj is PropertyWithNavigations other && Equals(other); + } + + /// + public bool Equals(PropertyWithNavigations other) + { + return Property.Equals(other.Property) && Equals(other.Navigations); + } + + private bool Equals(IReadOnlyList other) + { + if (Navigations.Count != other.Count) + return false; + + for (var i = 0; i < Navigations.Count; i++) + { + var navigation = Navigations[i]; + var otherNavi = other[i]; + + if (!navigation.Equals(otherNavi)) + return false; + } + + return true; + } + + /// + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(Property); + Navigations.ComputeHashCode(hashCode); + + return hashCode.ToHashCode(); + } + + /// + public override string ToString() + { + var path = String.Join(".", Navigations.Select(n => n.Name)); + + return String.IsNullOrWhiteSpace(path) ? Property.Name : $"{path}.{Property.Name}"; + } + + /// + /// Drops all leading incl. first non-inlined navigation and creates a new from the rest navigations. + /// + /// Dropped navigations and a new property without the dropped navigations. + /// If the current property has no separate navigations. + public (IReadOnlyList DroppedNavigations, PropertyWithNavigations Property) DropUntilSeparateNavigation() + { + var droppedNavigation = new List(); + + for (var i = 0; i < Navigations.Count; i++) + { + var navigation = Navigations[i]; + + if (navigation.IsInlined()) + { + droppedNavigation.Add(navigation); + } + else + { + droppedNavigation.Add(navigation); + + var separateNavigations = new List(); + + for (var j = i + 1; j < Navigations.Count; j++) + { + separateNavigations.Add(Navigations[j]); + } + + return (droppedNavigation, new PropertyWithNavigations(Property, separateNavigations)); + } + } + + throw new InvalidOperationException("This method must not be called for properties without separate navigations."); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/ShadowPropertyGetter.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/ShadowPropertyGetter.cs index b98ab4ca..6b549917 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/ShadowPropertyGetter.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Data/ShadowPropertyGetter.cs @@ -1,34 +1,34 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; -using Microsoft.EntityFrameworkCore.Infrastructure; - -namespace Thinktecture.EntityFrameworkCore.Data; - -/// -/// Getter for a shadow property. -/// -/// Type of the shadow property. -public sealed class ShadowPropertyGetter : IShadowPropertyGetter -{ - private readonly Func _getter; - - /// - /// Initializes new instance of . - /// - /// Current value accessor of the shadow property. - /// is null. - public ShadowPropertyGetter(Delegate getter) - { - _getter = (Func)getter ?? throw new ArgumentNullException(nameof(getter)); - } - - /// - [SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] - public object? GetValue(DbContext context, object entity) - { - var entry = context.Entry(entity); - var internalEntry = ((IInfrastructure)entry).Instance; - - return _getter(internalEntry); - } -} +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Thinktecture.EntityFrameworkCore.Data; + +/// +/// Getter for a shadow property. +/// +/// Type of the shadow property. +public sealed class ShadowPropertyGetter : IShadowPropertyGetter +{ + private readonly Func _getter; + + /// + /// Initializes new instance of . + /// + /// Current value accessor of the shadow property. + /// is null. + public ShadowPropertyGetter(Delegate getter) + { + _getter = (Func)getter ?? throw new ArgumentNullException(nameof(getter)); + } + + /// + [SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] + public object? GetValue(DbContext context, object entity) + { + var entry = context.Entry(entity); + var internalEntry = ((IInfrastructure)entry).Instance; + + return _getter(internalEntry); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/DbDefaultSchema.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/DbDefaultSchema.cs index db7a8326..f5c82c91 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/DbDefaultSchema.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/DbDefaultSchema.cs @@ -1,21 +1,21 @@ -namespace Thinktecture.EntityFrameworkCore; - -/// -/// DB schema. -/// -public sealed class DbDefaultSchema : IDbDefaultSchema -{ - /// - /// Database schema - /// - public string Schema { get; } - - /// - /// Initializes new instance of . - /// - /// - public DbDefaultSchema(string schema) - { - Schema = schema ?? throw new ArgumentNullException(nameof(schema)); - } -} +namespace Thinktecture.EntityFrameworkCore; + +/// +/// DB schema. +/// +public sealed class DbDefaultSchema : IDbDefaultSchema +{ + /// + /// Database schema + /// + public string Schema { get; } + + /// + /// Initializes new instance of . + /// + /// + public DbDefaultSchema(string schema) + { + Schema = schema ?? throw new ArgumentNullException(nameof(schema)); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/IDbDefaultSchema.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/IDbDefaultSchema.cs index 41baf9af..c03d2a22 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/IDbDefaultSchema.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/IDbDefaultSchema.cs @@ -1,12 +1,12 @@ -namespace Thinktecture.EntityFrameworkCore; - -/// -/// Represents a DB schema-containing component. -/// -public interface IDbDefaultSchema -{ - /// - /// Database schema. - /// - string? Schema { get; } +namespace Thinktecture.EntityFrameworkCore; + +/// +/// Represents a DB schema-containing component. +/// +public interface IDbDefaultSchema +{ + /// + /// Database schema. + /// + string? Schema { get; } } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/ITableHint.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/ITableHint.cs index 0f72294d..ef34b886 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/ITableHint.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/ITableHint.cs @@ -1,16 +1,16 @@ -using Microsoft.EntityFrameworkCore.Storage; - -namespace Thinktecture.EntityFrameworkCore; - -/// -/// Represents a table hint. -/// -public interface ITableHint -{ - /// - /// Returns a string representing the table hint. - /// - /// SQL generation helper. - /// Table hint. - string ToString(ISqlGenerationHelper sqlGenerationHelper); -} +using Microsoft.EntityFrameworkCore.Storage; + +namespace Thinktecture.EntityFrameworkCore; + +/// +/// Represents a table hint. +/// +public interface ITableHint +{ + /// + /// Returns a string representing the table hint. + /// + /// SQL generation helper. + /// Table hint. + string ToString(ISqlGenerationHelper sqlGenerationHelper); +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/DbContextOptionsExtensionBase.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/DbContextOptionsExtensionBase.cs index bde6d21b..06df8979 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/DbContextOptionsExtensionBase.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/DbContextOptionsExtensionBase.cs @@ -1,77 +1,77 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture.EntityFrameworkCore.Infrastructure; - -/// -/// Base class for . -/// -public abstract class DbContextOptionsExtensionBase -{ - /// - /// Adds and its dependencies. - /// - /// - protected void AddEntityDataReader(IServiceCollection services) - { - services.TryAddSingleton(); - services.TryAddSingleton(); - } - - /// - /// Gets the lifetime of a Entity Framework Core service. - /// - /// Service to fetch lifetime for. - /// Lifetime of the provided service. - /// If service is not found. - [SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] - protected ServiceLifetime GetLifetime() - { - var serviceType = typeof(TService); - - if (EntityFrameworkRelationalServicesBuilder.RelationalServices.TryGetValue(serviceType, out var serviceCharacteristics) || - EntityFrameworkServicesBuilder.CoreServices.TryGetValue(serviceType, out serviceCharacteristics)) - return serviceCharacteristics.Lifetime; - - throw new InvalidOperationException($"No service characteristics for service '{serviceType.Name}' found."); - } - - /// - /// Adds the implementation of the type - /// if the contains a registration of type , - /// an is thrown otherwise. - /// - /// Service collection. - /// Type of the service. - /// Type of the new implementation. - /// Type of the implementation expected to be in . - /// is null. - /// - /// No registration of service type found in - /// - or the implementation type is not the . - /// - protected void AddWithCheck(IServiceCollection services) - where TImplementation : TService - where TExpectedImplementation : TService - { - ArgumentNullException.ThrowIfNull(services); - - var serviceType = typeof(TService); - var currentDescriptor = services.LastOrDefault(d => d.ServiceType == serviceType); - - if (currentDescriptor is null) - throw new InvalidOperationException($"No registration of the Entity Framework Core service '{serviceType.FullName}' found. Please make sure the database provider is registered first (via 'UseSqlServer' or 'UseSqlite' etc)."); - - var newImplementationType = typeof(TImplementation); - var expectedImplementationType = typeof(TExpectedImplementation); - - if (currentDescriptor.ImplementationType != expectedImplementationType) - throw new InvalidOperationException($"Current registration of the Entity Framework Core service '{serviceType.FullName}' is '{currentDescriptor.ImplementationType?.FullName}' but was expected to be '{expectedImplementationType.FullName}'. Replacing current implementation with '{newImplementationType.FullName}' may lead to unexpected behavior."); - - var lifetime = GetLifetime(); - services.Add(lifetime); - } -} +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture.EntityFrameworkCore.Infrastructure; + +/// +/// Base class for . +/// +public abstract class DbContextOptionsExtensionBase +{ + /// + /// Adds and its dependencies. + /// + /// + protected void AddEntityDataReader(IServiceCollection services) + { + services.TryAddSingleton(); + services.TryAddSingleton(); + } + + /// + /// Gets the lifetime of a Entity Framework Core service. + /// + /// Service to fetch lifetime for. + /// Lifetime of the provided service. + /// If service is not found. + [SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] + protected ServiceLifetime GetLifetime() + { + var serviceType = typeof(TService); + + if (EntityFrameworkRelationalServicesBuilder.RelationalServices.TryGetValue(serviceType, out var serviceCharacteristics) || + EntityFrameworkServicesBuilder.CoreServices.TryGetValue(serviceType, out serviceCharacteristics)) + return serviceCharacteristics.Lifetime; + + throw new InvalidOperationException($"No service characteristics for service '{serviceType.Name}' found."); + } + + /// + /// Adds the implementation of the type + /// if the contains a registration of type , + /// an is thrown otherwise. + /// + /// Service collection. + /// Type of the service. + /// Type of the new implementation. + /// Type of the implementation expected to be in . + /// is null. + /// + /// No registration of service type found in + /// - or the implementation type is not the . + /// + protected void AddWithCheck(IServiceCollection services) + where TImplementation : TService + where TExpectedImplementation : TService + { + ArgumentNullException.ThrowIfNull(services); + + var serviceType = typeof(TService); + var currentDescriptor = services.LastOrDefault(d => d.ServiceType == serviceType); + + if (currentDescriptor is null) + throw new InvalidOperationException($"No registration of the Entity Framework Core service '{serviceType.FullName}' found. Please make sure the database provider is registered first (via 'UseSqlServer' or 'UseSqlite' etc)."); + + var newImplementationType = typeof(TImplementation); + var expectedImplementationType = typeof(TExpectedImplementation); + + if (currentDescriptor.ImplementationType != expectedImplementationType) + throw new InvalidOperationException($"Current registration of the Entity Framework Core service '{serviceType.FullName}' is '{currentDescriptor.ImplementationType?.FullName}' but was expected to be '{expectedImplementationType.FullName}'. Replacing current implementation with '{newImplementationType.FullName}' may lead to unexpected behavior."); + + var lifetime = GetLifetime(); + services.Add(lifetime); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/DefaultSchemaModelCustomizer.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/DefaultSchemaModelCustomizer.cs index 989f6b0a..102cb16e 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/DefaultSchemaModelCustomizer.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/DefaultSchemaModelCustomizer.cs @@ -1,35 +1,35 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; - -namespace Thinktecture.EntityFrameworkCore.Infrastructure; - -/// -/// Sets default database schema. -/// -// ReSharper disable once ClassNeverInstantiated.Global -public sealed class DefaultSchemaModelCustomizer : IModelCustomizer - where TModelCustomizer : class, IModelCustomizer -{ - private readonly TModelCustomizer _modelCustomizer; - - /// - /// Initializes new instance . - /// - /// Inner model customizer. - public DefaultSchemaModelCustomizer(TModelCustomizer modelCustomizer) - { - _modelCustomizer = modelCustomizer ?? throw new ArgumentNullException(nameof(modelCustomizer)); - } - - /// - public void Customize(ModelBuilder modelBuilder, DbContext context) - { - ArgumentNullException.ThrowIfNull(modelBuilder); - ArgumentNullException.ThrowIfNull(context); - - _modelCustomizer.Customize(modelBuilder, context); - - // ReSharper disable once SuspiciousTypeConversion.Global - if (context is IDbDefaultSchema { Schema: { } } schema) - modelBuilder.HasDefaultSchema(schema.Schema); - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Thinktecture.EntityFrameworkCore.Infrastructure; + +/// +/// Sets default database schema. +/// +// ReSharper disable once ClassNeverInstantiated.Global +public sealed class DefaultSchemaModelCustomizer : IModelCustomizer + where TModelCustomizer : class, IModelCustomizer +{ + private readonly TModelCustomizer _modelCustomizer; + + /// + /// Initializes new instance . + /// + /// Inner model customizer. + public DefaultSchemaModelCustomizer(TModelCustomizer modelCustomizer) + { + _modelCustomizer = modelCustomizer ?? throw new ArgumentNullException(nameof(modelCustomizer)); + } + + /// + public void Customize(ModelBuilder modelBuilder, DbContext context) + { + ArgumentNullException.ThrowIfNull(modelBuilder); + ArgumentNullException.ThrowIfNull(context); + + _modelCustomizer.Customize(modelBuilder, context); + + // ReSharper disable once SuspiciousTypeConversion.Global + if (context is IDbDefaultSchema { Schema: { } } schema) + modelBuilder.HasDefaultSchema(schema.Schema); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/DefaultSchemaRespectingModelCacheKeyFactory.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/DefaultSchemaRespectingModelCacheKeyFactory.cs index 3f754b5d..5fc5cc67 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/DefaultSchemaRespectingModelCacheKeyFactory.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/DefaultSchemaRespectingModelCacheKeyFactory.cs @@ -1,42 +1,42 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; - -namespace Thinktecture.EntityFrameworkCore.Infrastructure; - -/// -/// Cache key factory that takes the schema into account. -/// -public sealed class DefaultSchemaRespectingModelCacheKeyFactory : IModelCacheKeyFactory - where TFactory : class, IModelCacheKeyFactory -{ - private readonly TFactory _factory; - - /// - /// Initializes new instance of . - /// - /// Inner factory. - public DefaultSchemaRespectingModelCacheKeyFactory(TFactory factory) - { - _factory = factory ?? throw new ArgumentNullException(nameof(factory)); - } - - /// Gets the model cache key for a given context. - /// The context to get the model cache key for. - /// The created key. - public object Create(DbContext context) - { - return Create(context, false); - } - - /// - public object Create(DbContext context, bool designTime) - { - ArgumentNullException.ThrowIfNull(context); - - var key = _factory.Create(context, designTime); - // ReSharper disable once SuspiciousTypeConversion.Global - var schema = context is IDbDefaultSchema dbSchema ? dbSchema.Schema : null; - - // compiler implements Equals and GetHashCode they way we need - return new { key, schema, designTime }; - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Thinktecture.EntityFrameworkCore.Infrastructure; + +/// +/// Cache key factory that takes the schema into account. +/// +public sealed class DefaultSchemaRespectingModelCacheKeyFactory : IModelCacheKeyFactory + where TFactory : class, IModelCacheKeyFactory +{ + private readonly TFactory _factory; + + /// + /// Initializes new instance of . + /// + /// Inner factory. + public DefaultSchemaRespectingModelCacheKeyFactory(TFactory factory) + { + _factory = factory ?? throw new ArgumentNullException(nameof(factory)); + } + + /// Gets the model cache key for a given context. + /// The context to get the model cache key for. + /// The created key. + public object Create(DbContext context) + { + return Create(context, false); + } + + /// + public object Create(DbContext context, bool designTime) + { + ArgumentNullException.ThrowIfNull(context); + + var key = _factory.Create(context, designTime); + // ReSharper disable once SuspiciousTypeConversion.Global + var schema = context is IDbDefaultSchema dbSchema ? dbSchema.Schema : null; + + // compiler implements Equals and GetHashCode they way we need + return new { key, schema, designTime }; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/IRelationalDbContextComponentDecorator.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/IRelationalDbContextComponentDecorator.cs index 74d83dc3..2c4cf2fa 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/IRelationalDbContextComponentDecorator.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/IRelationalDbContextComponentDecorator.cs @@ -1,17 +1,17 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Thinktecture.EntityFrameworkCore.Infrastructure; - -/// -/// Decorates EF Core components. -/// -public interface IRelationalDbContextComponentDecorator -{ - /// - /// Decorates the service of type with a decorator of type . - /// - /// Service collection. - /// Generic type definition. - /// Service type. - void RegisterDecorator(IServiceCollection services, Type genericDecoratorTypeDefinition); -} +using Microsoft.Extensions.DependencyInjection; + +namespace Thinktecture.EntityFrameworkCore.Infrastructure; + +/// +/// Decorates EF Core components. +/// +public interface IRelationalDbContextComponentDecorator +{ + /// + /// Decorates the service of type with a decorator of type . + /// + /// Service collection. + /// Generic type definition. + /// Service type. + void RegisterDecorator(IServiceCollection services, Type genericDecoratorTypeDefinition); +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/RelationalDbContextComponentDecorator.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/RelationalDbContextComponentDecorator.cs index 89096061..49421ff0 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/RelationalDbContextComponentDecorator.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/RelationalDbContextComponentDecorator.cs @@ -1,49 +1,49 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.Extensions.DependencyInjection; - -namespace Thinktecture.EntityFrameworkCore.Infrastructure; - -/// -/// Decorates EF Core components. -/// -public sealed class RelationalDbContextComponentDecorator : IRelationalDbContextComponentDecorator -{ - /// - public void RegisterDecorator( - IServiceCollection services, - Type genericDecoratorTypeDefinition) - { - ArgumentNullException.ThrowIfNull(services); - ArgumentNullException.ThrowIfNull(genericDecoratorTypeDefinition); - - var (implementationType, lifetime, index) = GetLatestRegistration(services); - - services.Add(ServiceDescriptor.Describe(implementationType, implementationType, lifetime)); // type to decorate - - var decoratorType = genericDecoratorTypeDefinition.MakeGenericType(implementationType); - services[index] = ServiceDescriptor.Describe(typeof(TService), decoratorType, lifetime); - } - - private static (Type implementationType, ServiceLifetime lifetime, int index) GetLatestRegistration(IServiceCollection services) - { - var serviceType = typeof(TService); - - for (var i = services.Count - 1; i >= 0; i--) - { - var service = services[i]; - - if (service.ServiceType != serviceType) - continue; - - if (service.ImplementationType == null) - throw new NotSupportedException($"The registration of the Entity Framework Core service '{serviceType.FullName}' found but the service is not registered 'by type'."); - - if (service.ImplementationType == serviceType) - throw new NotSupportedException($"The implementation type '{service.ImplementationType.ShortDisplayName()}' cannot be the same as the service type '{serviceType.ShortDisplayName()}'."); - - return (service.ImplementationType, service.Lifetime, i); - } - - throw new InvalidOperationException($"No registration of the Entity Framework Core service '{serviceType.FullName}' found. Please make sure the database provider is registered first (via 'UseSqlServer' or 'UseSqlite' etc)."); - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.Extensions.DependencyInjection; + +namespace Thinktecture.EntityFrameworkCore.Infrastructure; + +/// +/// Decorates EF Core components. +/// +public sealed class RelationalDbContextComponentDecorator : IRelationalDbContextComponentDecorator +{ + /// + public void RegisterDecorator( + IServiceCollection services, + Type genericDecoratorTypeDefinition) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(genericDecoratorTypeDefinition); + + var (implementationType, lifetime, index) = GetLatestRegistration(services); + + services.Add(ServiceDescriptor.Describe(implementationType, implementationType, lifetime)); // type to decorate + + var decoratorType = genericDecoratorTypeDefinition.MakeGenericType(implementationType); + services[index] = ServiceDescriptor.Describe(typeof(TService), decoratorType, lifetime); + } + + private static (Type implementationType, ServiceLifetime lifetime, int index) GetLatestRegistration(IServiceCollection services) + { + var serviceType = typeof(TService); + + for (var i = services.Count - 1; i >= 0; i--) + { + var service = services[i]; + + if (service.ServiceType != serviceType) + continue; + + if (service.ImplementationType == null) + throw new NotSupportedException($"The registration of the Entity Framework Core service '{serviceType.FullName}' found but the service is not registered 'by type'."); + + if (service.ImplementationType == serviceType) + throw new NotSupportedException($"The implementation type '{service.ImplementationType.ShortDisplayName()}' cannot be the same as the service type '{serviceType.ShortDisplayName()}'."); + + return (service.ImplementationType, service.Lifetime, i); + } + + throw new InvalidOperationException($"No registration of the Entity Framework Core service '{serviceType.FullName}' found. Please make sure the database provider is registered first (via 'UseSqlServer' or 'UseSqlite' etc)."); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/RelationalDbContextOptionsExtension.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/RelationalDbContextOptionsExtension.cs index 4bb62baf..f3c04fec 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/RelationalDbContextOptionsExtension.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/RelationalDbContextOptionsExtension.cs @@ -1,375 +1,375 @@ -using System.Globalization; -using System.Text; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.ObjectPool; -using Thinktecture.EntityFrameworkCore.Migrations; -using Thinktecture.EntityFrameworkCore.Query; -using Thinktecture.EntityFrameworkCore.Query.ExpressionTranslators; -using Thinktecture.EntityFrameworkCore.Storage; - -namespace Thinktecture.EntityFrameworkCore.Infrastructure; - -/// -/// Extensions for DbContextOptions. -/// -public sealed class RelationalDbContextOptionsExtension : DbContextOptionsExtensionBase, IDbContextOptionsExtension -{ - private const int _DEFAULT_INITIAL_CAPACITY = 300; - private const int _DEFAULT_MAXIMUM_RETAINED_CAPACITY = 4 * 1024; - - private static readonly IRelationalDbContextComponentDecorator _defaultDecorator = new RelationalDbContextComponentDecorator(); - - private readonly List _serviceDescriptors; - private readonly List _evaluatableExpressionFilterPlugins; - private readonly StringBuilderPooledObjectPolicy _stringBuilderPolicy; - - private DbContextOptionsExtensionInfo? _info; - - /// - public DbContextOptionsExtensionInfo Info => _info ??= new RelationalDbContextOptionsExtensionInfo(this); - - /// - /// Adds components so Entity Framework Core can handle changes of the database schema at runtime. - /// - public bool AddSchemaRespectingComponents { get; set; } - - private IRelationalDbContextComponentDecorator? _componentDecorator; - - /// - /// Decorates components. - /// - public IRelationalDbContextComponentDecorator ComponentDecorator - { - get => _componentDecorator ?? _defaultDecorator; - set => _componentDecorator = value; - } - - /// - /// Adds support for nested transactions. - /// - public bool AddNestedTransactionsSupport { get; set; } - - /// - /// Enables and disables support for windows functions like "RowNumber". - /// - public bool AddWindowFunctionsSupport { get; set; } - - /// - /// Enables and disables support for 'tenant database support'. - /// - public bool AddTenantDatabaseSupport { get; set; } - - private bool _useCustomRelationalQueryContextFactory; - - /// - /// A custom factory is registered if true. - /// The factory is required for some features. - /// - public bool UseThinktectureRelationalQueryContextFactory - { - get => _useCustomRelationalQueryContextFactory || AddTenantDatabaseSupport; - set => _useCustomRelationalQueryContextFactory = value; - } - - /// - /// Initializes new instance of . - /// - public RelationalDbContextOptionsExtension() - { - _serviceDescriptors = new List(); - _evaluatableExpressionFilterPlugins = new List(); - _stringBuilderPolicy = new StringBuilderPooledObjectPolicy - { - InitialCapacity = _DEFAULT_INITIAL_CAPACITY, - MaximumRetainedCapacity = _DEFAULT_MAXIMUM_RETAINED_CAPACITY - }; - } - - /// - public void ApplyServices(IServiceCollection services) - { - ArgumentNullException.ThrowIfNull(services); - - services.TryAddSingleton(); - services.AddSingleton(provider => provider.GetRequiredService()); - - services.TryAddSingleton(DummyTenantDatabaseProviderFactory.Instance); - - services.TryAddSingleton(); - services.TryAddSingleton(serviceProvider => - { - var provider = serviceProvider.GetRequiredService(); - return provider.Create(_stringBuilderPolicy); - }); - - services.Add(GetLifetime()); - - if (UseThinktectureRelationalQueryContextFactory) - ComponentDecorator.RegisterDecorator(services, typeof(ThinktectureRelationalQueryContextFactory<>)); - - if (_evaluatableExpressionFilterPlugins.Count > 0) - { - var lifetime = GetLifetime(); - - foreach (var plugin in _evaluatableExpressionFilterPlugins) - { - services.Add(ServiceDescriptor.Describe(typeof(IEvaluatableExpressionFilterPlugin), plugin, lifetime)); - } - } - - if (AddSchemaRespectingComponents) - RegisterDefaultSchemaRespectingComponents(services); - - if (AddNestedTransactionsSupport) - services.Add(ServiceDescriptor.Describe(typeof(IDbContextTransactionManager), typeof(NestedRelationalTransactionManager), GetLifetime())); - - foreach (var descriptor in _serviceDescriptors) - { - services.Add(descriptor); - } - } - - private void RegisterDefaultSchemaRespectingComponents(IServiceCollection services) - { - services.TryAddSingleton(); - - ComponentDecorator.RegisterDecorator(services, typeof(DefaultSchemaRespectingModelCacheKeyFactory<>)); - ComponentDecorator.RegisterDecorator(services, typeof(DefaultSchemaModelCustomizer<>)); - ComponentDecorator.RegisterDecorator(services, typeof(DefaultSchemaRespectingMigrationAssembly<>)); - } - - /// - public void Validate(IDbContextOptions options) - { - if (AddTenantDatabaseSupport && _serviceDescriptors.All(d => d.ServiceType != typeof(ITenantDatabaseProviderFactory))) - throw new InvalidOperationException($"TenantDatabaseSupport is enabled but there is no registration of an implementation of '{nameof(ITenantDatabaseProviderFactory)}'."); - } - - /// - /// Configures the string builder pool. - /// - /// Initial capacity of a new . - /// Instances of with greater capacity are not reused. - /// If or are negative. - public RelationalDbContextOptionsExtension ConfigureStringBuilderPool(int initialCapacity, int maximumRetainedCapacity) - { - if (initialCapacity < 0) - throw new ArgumentOutOfRangeException(nameof(initialCapacity), "Initial capacity cannot be negative."); - - if (maximumRetainedCapacity < 0) - throw new ArgumentOutOfRangeException(nameof(initialCapacity), "Initial capacity cannot be negative."); - - _stringBuilderPolicy.InitialCapacity = initialCapacity; - _stringBuilderPolicy.MaximumRetainedCapacity = maximumRetainedCapacity; - - return this; - } - - /// - /// Adds provided to dependency injection. - /// - /// An implementation of . - /// is null. - public RelationalDbContextOptionsExtension AddRelationalTypeMappingSourcePlugin(Type type) - { - ArgumentNullException.ThrowIfNull(type); - - if (!typeof(IRelationalTypeMappingSourcePlugin).IsAssignableFrom(type)) - throw new ArgumentException($"The provided type '{type.ShortDisplayName()}' must implement '{nameof(IRelationalTypeMappingSourcePlugin)}'.", nameof(type)); - - Register(typeof(IRelationalTypeMappingSourcePlugin), type, ServiceLifetime.Singleton); - - return this; - } - - /// - /// Registers a custom service with internal dependency injection container of Entity Framework Core. - /// - /// Service type. - /// Implementation type. - /// Service lifetime. - /// or is null. - public void Register(Type serviceType, Type implementationType, ServiceLifetime lifetime) - { - ArgumentNullException.ThrowIfNull(serviceType); - - ArgumentNullException.ThrowIfNull(implementationType); - - _serviceDescriptors.Add(ServiceDescriptor.Describe(serviceType, implementationType, lifetime)); - } - - /// - /// Registers a custom service instance with internal dependency injection container of Entity Framework Core. - /// - /// Service type. - /// Implementation instance. - /// or is null. - public void Register(Type serviceType, object implementationInstance) - { - ArgumentNullException.ThrowIfNull(serviceType); - - ArgumentNullException.ThrowIfNull(implementationInstance); - - _serviceDescriptors.Add(ServiceDescriptor.Singleton(serviceType, implementationInstance)); - } - - /// - /// Adds an to the dependency injection. - /// - /// Type of the plugin. - public RelationalDbContextOptionsExtension AddEvaluatableExpressionFilterPlugin() - where T : IEvaluatableExpressionFilterPlugin - { - var type = typeof(T); - - if (!_evaluatableExpressionFilterPlugins.Contains(type)) - _evaluatableExpressionFilterPlugins.Add(type); - - return this; - } - - private class RelationalDbContextOptionsExtensionInfo : DbContextOptionsExtensionInfo - { - private readonly RelationalDbContextOptionsExtension _extension; - - public override bool IsDatabaseProvider => false; - - private string? _logFragment; - - public override string LogFragment => _logFragment ??= CreateLogFragment(); - - private string CreateLogFragment() - { - var sb = new StringBuilder(); - - if (_extension.AddSchemaRespectingComponents) - sb.Append("SchemaRespectingComponents "); - - if (_extension.AddNestedTransactionsSupport) - sb.Append("NestedTransactionsSupport "); - - if (_extension.AddWindowFunctionsSupport) - sb.Append("WindowFunctionsSupport "); - - if (_extension.AddTenantDatabaseSupport) - sb.Append("TenantDatabaseSupport "); - - if (_extension._stringBuilderPolicy.InitialCapacity != _DEFAULT_INITIAL_CAPACITY || _extension._stringBuilderPolicy.MaximumRetainedCapacity != _DEFAULT_MAXIMUM_RETAINED_CAPACITY) - sb.Append("StringBuilderPool(InitialCapacity=").Append(_extension._stringBuilderPolicy.InitialCapacity).Append(", MaximumRetainedCapacity=").Append(_extension._stringBuilderPolicy.MaximumRetainedCapacity).Append(") "); - - return sb.ToString(); - } - - public RelationalDbContextOptionsExtensionInfo(RelationalDbContextOptionsExtension extension) - : base(extension) - { - _extension = extension ?? throw new ArgumentNullException(nameof(extension)); - } - - public override int GetServiceProviderHashCode() - { - var hashCode = new HashCode(); - hashCode.Add(_extension.UseThinktectureRelationalQueryContextFactory); - hashCode.Add(_extension.AddSchemaRespectingComponents); - hashCode.Add(_extension.AddNestedTransactionsSupport); - hashCode.Add(_extension.AddTenantDatabaseSupport); - hashCode.Add(_extension.AddWindowFunctionsSupport); - hashCode.Add(_extension.ComponentDecorator); - hashCode.Add(_extension._stringBuilderPolicy.InitialCapacity); - hashCode.Add(_extension._stringBuilderPolicy.MaximumRetainedCapacity); - - _extension._evaluatableExpressionFilterPlugins.ForEach(type => hashCode.Add(type)); - _extension._serviceDescriptors.ForEach(descriptor => hashCode.Add(GetHashCode(descriptor))); - - // Following switches doesn't add any new components: - // AddCustomRelationalParameterBasedSqlProcessorFactory - - return hashCode.ToHashCode(); - } - - /// - public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) - { - if (other is not RelationalDbContextOptionsExtensionInfo otherRelationalInfo) - return false; - - var areEqual = _extension.UseThinktectureRelationalQueryContextFactory == otherRelationalInfo._extension.UseThinktectureRelationalQueryContextFactory - && _extension.AddSchemaRespectingComponents == otherRelationalInfo._extension.AddSchemaRespectingComponents - && _extension.AddNestedTransactionsSupport == otherRelationalInfo._extension.AddNestedTransactionsSupport - && _extension.AddTenantDatabaseSupport == otherRelationalInfo._extension.AddTenantDatabaseSupport - && _extension.AddWindowFunctionsSupport == otherRelationalInfo._extension.AddWindowFunctionsSupport - && _extension._stringBuilderPolicy.InitialCapacity == otherRelationalInfo._extension._stringBuilderPolicy.InitialCapacity - && _extension._stringBuilderPolicy.MaximumRetainedCapacity == otherRelationalInfo._extension._stringBuilderPolicy.MaximumRetainedCapacity - && _extension.ComponentDecorator.Equals(otherRelationalInfo._extension.ComponentDecorator); - - if (!areEqual) - return false; - - if (_extension._evaluatableExpressionFilterPlugins.Count != otherRelationalInfo._extension._evaluatableExpressionFilterPlugins.Count) - return false; - - if (_extension._evaluatableExpressionFilterPlugins.Except(otherRelationalInfo._extension._evaluatableExpressionFilterPlugins).Any()) - return false; - - if (_extension._serviceDescriptors.Count != otherRelationalInfo._extension._serviceDescriptors.Count) - return false; - - if (!_extension._serviceDescriptors.All(d => otherRelationalInfo._extension._serviceDescriptors.Any(o => AreEqual(d, o)))) - return false; - - return true; - } - - private static bool AreEqual(ServiceDescriptor serviceDescriptor, ServiceDescriptor other) - { - if (serviceDescriptor.Lifetime != other.Lifetime || serviceDescriptor.ServiceType != other.ServiceType) - return false; - - if (serviceDescriptor.ImplementationType is not null) - return serviceDescriptor.ImplementationType == other.ImplementationType; - - if (serviceDescriptor.ImplementationInstance is not null) - return serviceDescriptor.ImplementationInstance.Equals(other.ImplementationInstance); - - throw new NotSupportedException("Implementation factories are not supported."); - } - - private static int GetHashCode(ServiceDescriptor descriptor) - { - int implHashcode; - - if (descriptor.ImplementationType != null) - { - implHashcode = descriptor.ImplementationType.GetHashCode(); - } - else if (descriptor.ImplementationInstance != null) - { - implHashcode = descriptor.ImplementationInstance.GetHashCode(); - } - else - { - throw new NotSupportedException("Implementation factories are not supported."); - } - - return HashCode.Combine(descriptor.Lifetime, descriptor.ServiceType, implHashcode); - } - - public override void PopulateDebugInfo(IDictionary debugInfo) - { - debugInfo["Thinktecture:CustomRelationalQueryContextFactory"] = _extension.UseThinktectureRelationalQueryContextFactory.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:SchemaRespectingComponents"] = _extension.AddSchemaRespectingComponents.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:NestedTransactionsSupport"] = _extension.AddNestedTransactionsSupport.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:WindowFunctionsSupport"] = _extension.AddWindowFunctionsSupport.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:TenantDatabaseSupport"] = _extension.AddTenantDatabaseSupport.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:EvaluatableExpressionFilterPlugins"] = String.Join(", ", _extension._evaluatableExpressionFilterPlugins.Select(t => t.ShortDisplayName())); - debugInfo["Thinktecture:ServiceDescriptors"] = String.Join(", ", _extension._serviceDescriptors); - debugInfo["Thinktecture:StringBuilderPool:InitialCapacity"] = String.Join(", ", _extension._stringBuilderPolicy.InitialCapacity); - debugInfo["Thinktecture:StringBuilderPool:MaximumRetainedCapacity"] = String.Join(", ", _extension._stringBuilderPolicy.MaximumRetainedCapacity); - } - } -} +using System.Globalization; +using System.Text; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.ObjectPool; +using Thinktecture.EntityFrameworkCore.Migrations; +using Thinktecture.EntityFrameworkCore.Query; +using Thinktecture.EntityFrameworkCore.Query.ExpressionTranslators; +using Thinktecture.EntityFrameworkCore.Storage; + +namespace Thinktecture.EntityFrameworkCore.Infrastructure; + +/// +/// Extensions for DbContextOptions. +/// +public sealed class RelationalDbContextOptionsExtension : DbContextOptionsExtensionBase, IDbContextOptionsExtension +{ + private const int _DEFAULT_INITIAL_CAPACITY = 300; + private const int _DEFAULT_MAXIMUM_RETAINED_CAPACITY = 4 * 1024; + + private static readonly IRelationalDbContextComponentDecorator _defaultDecorator = new RelationalDbContextComponentDecorator(); + + private readonly List _serviceDescriptors; + private readonly List _evaluatableExpressionFilterPlugins; + private readonly StringBuilderPooledObjectPolicy _stringBuilderPolicy; + + private DbContextOptionsExtensionInfo? _info; + + /// + public DbContextOptionsExtensionInfo Info => _info ??= new RelationalDbContextOptionsExtensionInfo(this); + + /// + /// Adds components so Entity Framework Core can handle changes of the database schema at runtime. + /// + public bool AddSchemaRespectingComponents { get; set; } + + private IRelationalDbContextComponentDecorator? _componentDecorator; + + /// + /// Decorates components. + /// + public IRelationalDbContextComponentDecorator ComponentDecorator + { + get => _componentDecorator ?? _defaultDecorator; + set => _componentDecorator = value; + } + + /// + /// Adds support for nested transactions. + /// + public bool AddNestedTransactionsSupport { get; set; } + + /// + /// Enables and disables support for windows functions like "RowNumber". + /// + public bool AddWindowFunctionsSupport { get; set; } + + /// + /// Enables and disables support for 'tenant database support'. + /// + public bool AddTenantDatabaseSupport { get; set; } + + private bool _useCustomRelationalQueryContextFactory; + + /// + /// A custom factory is registered if true. + /// The factory is required for some features. + /// + public bool UseThinktectureRelationalQueryContextFactory + { + get => _useCustomRelationalQueryContextFactory || AddTenantDatabaseSupport; + set => _useCustomRelationalQueryContextFactory = value; + } + + /// + /// Initializes new instance of . + /// + public RelationalDbContextOptionsExtension() + { + _serviceDescriptors = new List(); + _evaluatableExpressionFilterPlugins = new List(); + _stringBuilderPolicy = new StringBuilderPooledObjectPolicy + { + InitialCapacity = _DEFAULT_INITIAL_CAPACITY, + MaximumRetainedCapacity = _DEFAULT_MAXIMUM_RETAINED_CAPACITY + }; + } + + /// + public void ApplyServices(IServiceCollection services) + { + ArgumentNullException.ThrowIfNull(services); + + services.TryAddSingleton(); + services.AddSingleton(provider => provider.GetRequiredService()); + + services.TryAddSingleton(DummyTenantDatabaseProviderFactory.Instance); + + services.TryAddSingleton(); + services.TryAddSingleton(serviceProvider => + { + var provider = serviceProvider.GetRequiredService(); + return provider.Create(_stringBuilderPolicy); + }); + + services.Add(GetLifetime()); + + if (UseThinktectureRelationalQueryContextFactory) + ComponentDecorator.RegisterDecorator(services, typeof(ThinktectureRelationalQueryContextFactory<>)); + + if (_evaluatableExpressionFilterPlugins.Count > 0) + { + var lifetime = GetLifetime(); + + foreach (var plugin in _evaluatableExpressionFilterPlugins) + { + services.Add(ServiceDescriptor.Describe(typeof(IEvaluatableExpressionFilterPlugin), plugin, lifetime)); + } + } + + if (AddSchemaRespectingComponents) + RegisterDefaultSchemaRespectingComponents(services); + + if (AddNestedTransactionsSupport) + services.Add(ServiceDescriptor.Describe(typeof(IDbContextTransactionManager), typeof(NestedRelationalTransactionManager), GetLifetime())); + + foreach (var descriptor in _serviceDescriptors) + { + services.Add(descriptor); + } + } + + private void RegisterDefaultSchemaRespectingComponents(IServiceCollection services) + { + services.TryAddSingleton(); + + ComponentDecorator.RegisterDecorator(services, typeof(DefaultSchemaRespectingModelCacheKeyFactory<>)); + ComponentDecorator.RegisterDecorator(services, typeof(DefaultSchemaModelCustomizer<>)); + ComponentDecorator.RegisterDecorator(services, typeof(DefaultSchemaRespectingMigrationAssembly<>)); + } + + /// + public void Validate(IDbContextOptions options) + { + if (AddTenantDatabaseSupport && _serviceDescriptors.All(d => d.ServiceType != typeof(ITenantDatabaseProviderFactory))) + throw new InvalidOperationException($"TenantDatabaseSupport is enabled but there is no registration of an implementation of '{nameof(ITenantDatabaseProviderFactory)}'."); + } + + /// + /// Configures the string builder pool. + /// + /// Initial capacity of a new . + /// Instances of with greater capacity are not reused. + /// If or are negative. + public RelationalDbContextOptionsExtension ConfigureStringBuilderPool(int initialCapacity, int maximumRetainedCapacity) + { + if (initialCapacity < 0) + throw new ArgumentOutOfRangeException(nameof(initialCapacity), "Initial capacity cannot be negative."); + + if (maximumRetainedCapacity < 0) + throw new ArgumentOutOfRangeException(nameof(initialCapacity), "Initial capacity cannot be negative."); + + _stringBuilderPolicy.InitialCapacity = initialCapacity; + _stringBuilderPolicy.MaximumRetainedCapacity = maximumRetainedCapacity; + + return this; + } + + /// + /// Adds provided to dependency injection. + /// + /// An implementation of . + /// is null. + public RelationalDbContextOptionsExtension AddRelationalTypeMappingSourcePlugin(Type type) + { + ArgumentNullException.ThrowIfNull(type); + + if (!typeof(IRelationalTypeMappingSourcePlugin).IsAssignableFrom(type)) + throw new ArgumentException($"The provided type '{type.ShortDisplayName()}' must implement '{nameof(IRelationalTypeMappingSourcePlugin)}'.", nameof(type)); + + Register(typeof(IRelationalTypeMappingSourcePlugin), type, ServiceLifetime.Singleton); + + return this; + } + + /// + /// Registers a custom service with internal dependency injection container of Entity Framework Core. + /// + /// Service type. + /// Implementation type. + /// Service lifetime. + /// or is null. + public void Register(Type serviceType, Type implementationType, ServiceLifetime lifetime) + { + ArgumentNullException.ThrowIfNull(serviceType); + + ArgumentNullException.ThrowIfNull(implementationType); + + _serviceDescriptors.Add(ServiceDescriptor.Describe(serviceType, implementationType, lifetime)); + } + + /// + /// Registers a custom service instance with internal dependency injection container of Entity Framework Core. + /// + /// Service type. + /// Implementation instance. + /// or is null. + public void Register(Type serviceType, object implementationInstance) + { + ArgumentNullException.ThrowIfNull(serviceType); + + ArgumentNullException.ThrowIfNull(implementationInstance); + + _serviceDescriptors.Add(ServiceDescriptor.Singleton(serviceType, implementationInstance)); + } + + /// + /// Adds an to the dependency injection. + /// + /// Type of the plugin. + public RelationalDbContextOptionsExtension AddEvaluatableExpressionFilterPlugin() + where T : IEvaluatableExpressionFilterPlugin + { + var type = typeof(T); + + if (!_evaluatableExpressionFilterPlugins.Contains(type)) + _evaluatableExpressionFilterPlugins.Add(type); + + return this; + } + + private class RelationalDbContextOptionsExtensionInfo : DbContextOptionsExtensionInfo + { + private readonly RelationalDbContextOptionsExtension _extension; + + public override bool IsDatabaseProvider => false; + + private string? _logFragment; + + public override string LogFragment => _logFragment ??= CreateLogFragment(); + + private string CreateLogFragment() + { + var sb = new StringBuilder(); + + if (_extension.AddSchemaRespectingComponents) + sb.Append("SchemaRespectingComponents "); + + if (_extension.AddNestedTransactionsSupport) + sb.Append("NestedTransactionsSupport "); + + if (_extension.AddWindowFunctionsSupport) + sb.Append("WindowFunctionsSupport "); + + if (_extension.AddTenantDatabaseSupport) + sb.Append("TenantDatabaseSupport "); + + if (_extension._stringBuilderPolicy.InitialCapacity != _DEFAULT_INITIAL_CAPACITY || _extension._stringBuilderPolicy.MaximumRetainedCapacity != _DEFAULT_MAXIMUM_RETAINED_CAPACITY) + sb.Append("StringBuilderPool(InitialCapacity=").Append(_extension._stringBuilderPolicy.InitialCapacity).Append(", MaximumRetainedCapacity=").Append(_extension._stringBuilderPolicy.MaximumRetainedCapacity).Append(") "); + + return sb.ToString(); + } + + public RelationalDbContextOptionsExtensionInfo(RelationalDbContextOptionsExtension extension) + : base(extension) + { + _extension = extension ?? throw new ArgumentNullException(nameof(extension)); + } + + public override int GetServiceProviderHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(_extension.UseThinktectureRelationalQueryContextFactory); + hashCode.Add(_extension.AddSchemaRespectingComponents); + hashCode.Add(_extension.AddNestedTransactionsSupport); + hashCode.Add(_extension.AddTenantDatabaseSupport); + hashCode.Add(_extension.AddWindowFunctionsSupport); + hashCode.Add(_extension.ComponentDecorator); + hashCode.Add(_extension._stringBuilderPolicy.InitialCapacity); + hashCode.Add(_extension._stringBuilderPolicy.MaximumRetainedCapacity); + + _extension._evaluatableExpressionFilterPlugins.ForEach(type => hashCode.Add(type)); + _extension._serviceDescriptors.ForEach(descriptor => hashCode.Add(GetHashCode(descriptor))); + + // Following switches doesn't add any new components: + // AddCustomRelationalParameterBasedSqlProcessorFactory + + return hashCode.ToHashCode(); + } + + /// + public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) + { + if (other is not RelationalDbContextOptionsExtensionInfo otherRelationalInfo) + return false; + + var areEqual = _extension.UseThinktectureRelationalQueryContextFactory == otherRelationalInfo._extension.UseThinktectureRelationalQueryContextFactory + && _extension.AddSchemaRespectingComponents == otherRelationalInfo._extension.AddSchemaRespectingComponents + && _extension.AddNestedTransactionsSupport == otherRelationalInfo._extension.AddNestedTransactionsSupport + && _extension.AddTenantDatabaseSupport == otherRelationalInfo._extension.AddTenantDatabaseSupport + && _extension.AddWindowFunctionsSupport == otherRelationalInfo._extension.AddWindowFunctionsSupport + && _extension._stringBuilderPolicy.InitialCapacity == otherRelationalInfo._extension._stringBuilderPolicy.InitialCapacity + && _extension._stringBuilderPolicy.MaximumRetainedCapacity == otherRelationalInfo._extension._stringBuilderPolicy.MaximumRetainedCapacity + && _extension.ComponentDecorator.Equals(otherRelationalInfo._extension.ComponentDecorator); + + if (!areEqual) + return false; + + if (_extension._evaluatableExpressionFilterPlugins.Count != otherRelationalInfo._extension._evaluatableExpressionFilterPlugins.Count) + return false; + + if (_extension._evaluatableExpressionFilterPlugins.Except(otherRelationalInfo._extension._evaluatableExpressionFilterPlugins).Any()) + return false; + + if (_extension._serviceDescriptors.Count != otherRelationalInfo._extension._serviceDescriptors.Count) + return false; + + if (!_extension._serviceDescriptors.All(d => otherRelationalInfo._extension._serviceDescriptors.Any(o => AreEqual(d, o)))) + return false; + + return true; + } + + private static bool AreEqual(ServiceDescriptor serviceDescriptor, ServiceDescriptor other) + { + if (serviceDescriptor.Lifetime != other.Lifetime || serviceDescriptor.ServiceType != other.ServiceType) + return false; + + if (serviceDescriptor.ImplementationType is not null) + return serviceDescriptor.ImplementationType == other.ImplementationType; + + if (serviceDescriptor.ImplementationInstance is not null) + return serviceDescriptor.ImplementationInstance.Equals(other.ImplementationInstance); + + throw new NotSupportedException("Implementation factories are not supported."); + } + + private static int GetHashCode(ServiceDescriptor descriptor) + { + int implHashcode; + + if (descriptor.ImplementationType != null) + { + implHashcode = descriptor.ImplementationType.GetHashCode(); + } + else if (descriptor.ImplementationInstance != null) + { + implHashcode = descriptor.ImplementationInstance.GetHashCode(); + } + else + { + throw new NotSupportedException("Implementation factories are not supported."); + } + + return HashCode.Combine(descriptor.Lifetime, descriptor.ServiceType, implHashcode); + } + + public override void PopulateDebugInfo(IDictionary debugInfo) + { + debugInfo["Thinktecture:CustomRelationalQueryContextFactory"] = _extension.UseThinktectureRelationalQueryContextFactory.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:SchemaRespectingComponents"] = _extension.AddSchemaRespectingComponents.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:NestedTransactionsSupport"] = _extension.AddNestedTransactionsSupport.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:WindowFunctionsSupport"] = _extension.AddWindowFunctionsSupport.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:TenantDatabaseSupport"] = _extension.AddTenantDatabaseSupport.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:EvaluatableExpressionFilterPlugins"] = String.Join(", ", _extension._evaluatableExpressionFilterPlugins.Select(t => t.ShortDisplayName())); + debugInfo["Thinktecture:ServiceDescriptors"] = String.Join(", ", _extension._serviceDescriptors); + debugInfo["Thinktecture:StringBuilderPool:InitialCapacity"] = String.Join(", ", _extension._stringBuilderPolicy.InitialCapacity); + debugInfo["Thinktecture:StringBuilderPool:MaximumRetainedCapacity"] = String.Join(", ", _extension._stringBuilderPolicy.MaximumRetainedCapacity); + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/RelationalDbContextOptionsExtensionOptions.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/RelationalDbContextOptionsExtensionOptions.cs index 216e0d40..0842ed9f 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/RelationalDbContextOptionsExtensionOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Infrastructure/RelationalDbContextOptionsExtensionOptions.cs @@ -1,47 +1,47 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Thinktecture.EntityFrameworkCore.Query.ExpressionTranslators; - -namespace Thinktecture.EntityFrameworkCore.Infrastructure; - -/// -/// Options for the . -/// -public class RelationalDbContextOptionsExtensionOptions : ISingletonOptions -{ - /// - /// Indication whether the support for windows functions is enabled or not. - /// - public bool WindowFunctionsSupportEnabled { get; private set; } - - /// - /// Indication whether the 'tenant database support' is enabled or not. - /// - public bool TenantDatabaseSupportEnabled { get; private set; } - - /// - public void Initialize(IDbContextOptions options) - { - var extension = GetExtension(options); - - WindowFunctionsSupportEnabled = extension.AddWindowFunctionsSupport; - TenantDatabaseSupportEnabled = extension.AddTenantDatabaseSupport; - } - - /// - public void Validate(IDbContextOptions options) - { - var extension = GetExtension(options); - - if (extension.AddWindowFunctionsSupport != WindowFunctionsSupportEnabled) - throw new InvalidOperationException($"The setting '{nameof(RelationalDbContextOptionsExtension.AddWindowFunctionsSupport)}' has been changed."); - - if (extension.AddTenantDatabaseSupport != TenantDatabaseSupportEnabled) - throw new InvalidOperationException($"The setting '{nameof(RelationalDbContextOptionsExtension.AddTenantDatabaseSupport)}' has been changed."); - } - - private static RelationalDbContextOptionsExtension GetExtension(IDbContextOptions options) - { - return options.FindExtension() - ?? throw new InvalidOperationException($"{nameof(RelationalDbContextOptionsExtension)} not found in current '{nameof(IDbContextOptions)}'."); - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; +using Thinktecture.EntityFrameworkCore.Query.ExpressionTranslators; + +namespace Thinktecture.EntityFrameworkCore.Infrastructure; + +/// +/// Options for the . +/// +public class RelationalDbContextOptionsExtensionOptions : ISingletonOptions +{ + /// + /// Indication whether the support for windows functions is enabled or not. + /// + public bool WindowFunctionsSupportEnabled { get; private set; } + + /// + /// Indication whether the 'tenant database support' is enabled or not. + /// + public bool TenantDatabaseSupportEnabled { get; private set; } + + /// + public void Initialize(IDbContextOptions options) + { + var extension = GetExtension(options); + + WindowFunctionsSupportEnabled = extension.AddWindowFunctionsSupport; + TenantDatabaseSupportEnabled = extension.AddTenantDatabaseSupport; + } + + /// + public void Validate(IDbContextOptions options) + { + var extension = GetExtension(options); + + if (extension.AddWindowFunctionsSupport != WindowFunctionsSupportEnabled) + throw new InvalidOperationException($"The setting '{nameof(RelationalDbContextOptionsExtension.AddWindowFunctionsSupport)}' has been changed."); + + if (extension.AddTenantDatabaseSupport != TenantDatabaseSupportEnabled) + throw new InvalidOperationException($"The setting '{nameof(RelationalDbContextOptionsExtension.AddTenantDatabaseSupport)}' has been changed."); + } + + private static RelationalDbContextOptionsExtension GetExtension(IDbContextOptions options) + { + return options.FindExtension() + ?? throw new InvalidOperationException($"{nameof(RelationalDbContextOptionsExtension)} not found in current '{nameof(IDbContextOptions)}'."); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Internal/ThinktectureBulkOperationsAnnotationNames.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Internal/ThinktectureBulkOperationsAnnotationNames.cs index 65c3feb6..4b7305d5 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Internal/ThinktectureBulkOperationsAnnotationNames.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Internal/ThinktectureBulkOperationsAnnotationNames.cs @@ -1,14 +1,14 @@ -namespace Thinktecture.EntityFrameworkCore.Internal; - -/// -/// Annotation names. -/// -public static class ThinktectureRelationalAnnotationNames -{ - private const string _PREFIX = "Thinktecture:Relational:"; - - /// - /// Annotation name for table hints. - /// - public const string TABLE_HINTS = _PREFIX + "TableHints"; -} +namespace Thinktecture.EntityFrameworkCore.Internal; + +/// +/// Annotation names. +/// +public static class ThinktectureRelationalAnnotationNames +{ + private const string _PREFIX = "Thinktecture:Relational:"; + + /// + /// Annotation name for table hints. + /// + public const string TABLE_HINTS = _PREFIX + "TableHints"; +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/LeftJoinResult.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/LeftJoinResult.cs index 73b1c15f..0945daf2 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/LeftJoinResult.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/LeftJoinResult.cs @@ -1,124 +1,124 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Thinktecture.EntityFrameworkCore; - -/// -/// Result of a LEFT JOIN. -/// -/// Type of the entity on the left side of the JOIN. -/// Type of the entity on the right side of the JOIN. -public class LeftJoinResult - where TLeft : notnull -{ - /// - /// Entity on the left side of the JOIN. - /// - [DisallowNull] - public TLeft Left { get; init; } - - /// - /// Entity on the right side of the JOIN. - /// - [MaybeNull, AllowNull] - public TRight Right { get; init; } - -#nullable disable - internal LeftJoinResult() - { - } -#nullable enable -} - -/// -/// Result of a LEFT JOIN. -/// -/// Type of the entity on the left side of the JOIN. -/// Type of the entity on the right side of the JOIN. -/// Type of the entity on the right side of the JOIN. -public class LeftJoinResult : LeftJoinResult - where TLeft : notnull -{ - /// - /// Entity on the right side of the JOIN. - /// - [MaybeNull, AllowNull] - public TRight2 Right2 { get; init; } - -#nullable disable - internal LeftJoinResult() - { - } -#nullable enable -} - -/// -/// Result of a LEFT JOIN. -/// -/// Type of the entity on the left side of the JOIN. -/// Type of the entity on the right side of the JOIN. -/// Type of the entity on the right side of the JOIN. -/// Type of the entity on the right side of the JOIN. -public class LeftJoinResult : LeftJoinResult - where TLeft : notnull -{ - /// - /// Entity on the right side of the JOIN. - /// - [MaybeNull, AllowNull] - public TRight3 Right3 { get; init; } - -#nullable disable - internal LeftJoinResult() - { - } -#nullable enable -} - -/// -/// Result of a LEFT JOIN. -/// -/// Type of the entity on the left side of the JOIN. -/// Type of the entity on the right side of the JOIN. -/// Type of the entity on the right side of the JOIN. -/// Type of the entity on the right side of the JOIN. -/// Type of the entity on the right side of the JOIN. -public class LeftJoinResult : LeftJoinResult - where TLeft : notnull -{ - /// - /// Entity on the right side of the JOIN. - /// - [MaybeNull, AllowNull] - public TRight4 Right4 { get; init; } - -#nullable disable - internal LeftJoinResult() - { - } -#nullable enable -} - -/// -/// Result of a LEFT JOIN. -/// -/// Type of the entity on the left side of the JOIN. -/// Type of the entity on the right side of the JOIN. -/// Type of the entity on the right side of the JOIN. -/// Type of the entity on the right side of the JOIN. -/// Type of the entity on the right side of the JOIN. -/// Type of the entity on the right side of the JOIN. -public class LeftJoinResult : LeftJoinResult - where TLeft : notnull -{ - /// - /// Entity on the right side of the JOIN. - /// - [MaybeNull, AllowNull] - public TRight5 Right5 { get; init; } - -#nullable disable - internal LeftJoinResult() - { - } -#nullable enable +using System.Diagnostics.CodeAnalysis; + +namespace Thinktecture.EntityFrameworkCore; + +/// +/// Result of a LEFT JOIN. +/// +/// Type of the entity on the left side of the JOIN. +/// Type of the entity on the right side of the JOIN. +public class LeftJoinResult + where TLeft : notnull +{ + /// + /// Entity on the left side of the JOIN. + /// + [DisallowNull] + public TLeft Left { get; init; } + + /// + /// Entity on the right side of the JOIN. + /// + [MaybeNull, AllowNull] + public TRight Right { get; init; } + +#nullable disable + internal LeftJoinResult() + { + } +#nullable enable +} + +/// +/// Result of a LEFT JOIN. +/// +/// Type of the entity on the left side of the JOIN. +/// Type of the entity on the right side of the JOIN. +/// Type of the entity on the right side of the JOIN. +public class LeftJoinResult : LeftJoinResult + where TLeft : notnull +{ + /// + /// Entity on the right side of the JOIN. + /// + [MaybeNull, AllowNull] + public TRight2 Right2 { get; init; } + +#nullable disable + internal LeftJoinResult() + { + } +#nullable enable +} + +/// +/// Result of a LEFT JOIN. +/// +/// Type of the entity on the left side of the JOIN. +/// Type of the entity on the right side of the JOIN. +/// Type of the entity on the right side of the JOIN. +/// Type of the entity on the right side of the JOIN. +public class LeftJoinResult : LeftJoinResult + where TLeft : notnull +{ + /// + /// Entity on the right side of the JOIN. + /// + [MaybeNull, AllowNull] + public TRight3 Right3 { get; init; } + +#nullable disable + internal LeftJoinResult() + { + } +#nullable enable +} + +/// +/// Result of a LEFT JOIN. +/// +/// Type of the entity on the left side of the JOIN. +/// Type of the entity on the right side of the JOIN. +/// Type of the entity on the right side of the JOIN. +/// Type of the entity on the right side of the JOIN. +/// Type of the entity on the right side of the JOIN. +public class LeftJoinResult : LeftJoinResult + where TLeft : notnull +{ + /// + /// Entity on the right side of the JOIN. + /// + [MaybeNull, AllowNull] + public TRight4 Right4 { get; init; } + +#nullable disable + internal LeftJoinResult() + { + } +#nullable enable +} + +/// +/// Result of a LEFT JOIN. +/// +/// Type of the entity on the left side of the JOIN. +/// Type of the entity on the right side of the JOIN. +/// Type of the entity on the right side of the JOIN. +/// Type of the entity on the right side of the JOIN. +/// Type of the entity on the right side of the JOIN. +/// Type of the entity on the right side of the JOIN. +public class LeftJoinResult : LeftJoinResult + where TLeft : notnull +{ + /// + /// Entity on the right side of the JOIN. + /// + [MaybeNull, AllowNull] + public TRight5 Right5 { get; init; } + +#nullable disable + internal LeftJoinResult() + { + } +#nullable enable } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssembly.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssembly.cs index 07d1dce9..1009633e 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssembly.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssembly.cs @@ -1,94 +1,94 @@ -using System.Reflection; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.Extensions.DependencyInjection; - -namespace Thinktecture.EntityFrameworkCore.Migrations; - -/// -/// An implementation of that is able to instantiate migrations requiring an . -/// -public sealed class DefaultSchemaRespectingMigrationAssembly : IMigrationsAssembly - where TMigrationsAssembly : class, IMigrationsAssembly -{ - private readonly TMigrationsAssembly _innerMigrationsAssembly; - private readonly IMigrationOperationSchemaSetter _schemaSetter; - private readonly IServiceProvider _serviceProvider; - private readonly DbContext _context; - - /// - public IReadOnlyDictionary Migrations => _innerMigrationsAssembly.Migrations; - - /// - public ModelSnapshot? ModelSnapshot => _innerMigrationsAssembly.ModelSnapshot; - - /// - public Assembly Assembly => _innerMigrationsAssembly.Assembly; - - /// - /// Initializes new instance of . - /// - public DefaultSchemaRespectingMigrationAssembly( - TMigrationsAssembly migrationsAssembly, - IMigrationOperationSchemaSetter schemaSetter, - ICurrentDbContext currentContext, - IServiceProvider serviceProvider) - { - ArgumentNullException.ThrowIfNull(currentContext); - - _innerMigrationsAssembly = migrationsAssembly ?? throw new ArgumentNullException(nameof(migrationsAssembly)); - _schemaSetter = schemaSetter ?? throw new ArgumentNullException(nameof(schemaSetter)); - _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - _context = currentContext.Context ?? throw new ArgumentNullException(nameof(currentContext)); - } - - /// - public string? FindMigrationId(string nameOrId) - { - return _innerMigrationsAssembly.FindMigrationId(nameOrId); - } - - /// - public Migration CreateMigration(TypeInfo migrationClass, string activeProvider) - { - ArgumentNullException.ThrowIfNull(migrationClass); - ArgumentNullException.ThrowIfNull(activeProvider); - - var hasCtorWithDefaultSchema = migrationClass.GetConstructors().Any(c => c.GetParameters().Any(p => p.ParameterType == typeof(IDbDefaultSchema))); - - // ReSharper disable once SuspiciousTypeConversion.Global - if (_context is IDbDefaultSchema schema) - { - var migration = hasCtorWithDefaultSchema ? CreateInstance(migrationClass, schema, activeProvider) : CreateInstance(migrationClass, activeProvider); - - if (schema.Schema is not null) - { - _schemaSetter.SetSchema(migration.UpOperations, schema.Schema); - _schemaSetter.SetSchema(migration.DownOperations, schema.Schema); - } - - return migration; - } - - if (!hasCtorWithDefaultSchema) - return CreateInstance(migrationClass, activeProvider); - - throw new ArgumentException($"For instantiation of default schema respecting migration of type '{migrationClass.Name}' the database context of type '{_context.GetType().ShortDisplayName()}' has to implement the interface '{nameof(IDbDefaultSchema)}'.", nameof(migrationClass)); - } - - private Migration CreateInstance(TypeInfo migrationClass, IDbDefaultSchema schema, string activeProvider) - { - var migration = (Migration)ActivatorUtilities.CreateInstance(_serviceProvider, migrationClass.AsType(), schema); - migration.ActiveProvider = activeProvider; - - return migration; - } - - private Migration CreateInstance(TypeInfo migrationClass, string activeProvider) - { - var migration = (Migration)ActivatorUtilities.CreateInstance(_serviceProvider, migrationClass.AsType()); - migration.ActiveProvider = activeProvider; - - return migration; - } -} +using System.Reflection; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.Extensions.DependencyInjection; + +namespace Thinktecture.EntityFrameworkCore.Migrations; + +/// +/// An implementation of that is able to instantiate migrations requiring an . +/// +public sealed class DefaultSchemaRespectingMigrationAssembly : IMigrationsAssembly + where TMigrationsAssembly : class, IMigrationsAssembly +{ + private readonly TMigrationsAssembly _innerMigrationsAssembly; + private readonly IMigrationOperationSchemaSetter _schemaSetter; + private readonly IServiceProvider _serviceProvider; + private readonly DbContext _context; + + /// + public IReadOnlyDictionary Migrations => _innerMigrationsAssembly.Migrations; + + /// + public ModelSnapshot? ModelSnapshot => _innerMigrationsAssembly.ModelSnapshot; + + /// + public Assembly Assembly => _innerMigrationsAssembly.Assembly; + + /// + /// Initializes new instance of . + /// + public DefaultSchemaRespectingMigrationAssembly( + TMigrationsAssembly migrationsAssembly, + IMigrationOperationSchemaSetter schemaSetter, + ICurrentDbContext currentContext, + IServiceProvider serviceProvider) + { + ArgumentNullException.ThrowIfNull(currentContext); + + _innerMigrationsAssembly = migrationsAssembly ?? throw new ArgumentNullException(nameof(migrationsAssembly)); + _schemaSetter = schemaSetter ?? throw new ArgumentNullException(nameof(schemaSetter)); + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + _context = currentContext.Context ?? throw new ArgumentNullException(nameof(currentContext)); + } + + /// + public string? FindMigrationId(string nameOrId) + { + return _innerMigrationsAssembly.FindMigrationId(nameOrId); + } + + /// + public Migration CreateMigration(TypeInfo migrationClass, string activeProvider) + { + ArgumentNullException.ThrowIfNull(migrationClass); + ArgumentNullException.ThrowIfNull(activeProvider); + + var hasCtorWithDefaultSchema = migrationClass.GetConstructors().Any(c => c.GetParameters().Any(p => p.ParameterType == typeof(IDbDefaultSchema))); + + // ReSharper disable once SuspiciousTypeConversion.Global + if (_context is IDbDefaultSchema schema) + { + var migration = hasCtorWithDefaultSchema ? CreateInstance(migrationClass, schema, activeProvider) : CreateInstance(migrationClass, activeProvider); + + if (schema.Schema is not null) + { + _schemaSetter.SetSchema(migration.UpOperations, schema.Schema); + _schemaSetter.SetSchema(migration.DownOperations, schema.Schema); + } + + return migration; + } + + if (!hasCtorWithDefaultSchema) + return CreateInstance(migrationClass, activeProvider); + + throw new ArgumentException($"For instantiation of default schema respecting migration of type '{migrationClass.Name}' the database context of type '{_context.GetType().ShortDisplayName()}' has to implement the interface '{nameof(IDbDefaultSchema)}'.", nameof(migrationClass)); + } + + private Migration CreateInstance(TypeInfo migrationClass, IDbDefaultSchema schema, string activeProvider) + { + var migration = (Migration)ActivatorUtilities.CreateInstance(_serviceProvider, migrationClass.AsType(), schema); + migration.ActiveProvider = activeProvider; + + return migration; + } + + private Migration CreateInstance(TypeInfo migrationClass, string activeProvider) + { + var migration = (Migration)ActivatorUtilities.CreateInstance(_serviceProvider, migrationClass.AsType()); + migration.ActiveProvider = activeProvider; + + return migration; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Migrations/IMigrationOperationSchemaSetter.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Migrations/IMigrationOperationSchemaSetter.cs index d5ba9945..a948b1db 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Migrations/IMigrationOperationSchemaSetter.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Migrations/IMigrationOperationSchemaSetter.cs @@ -1,16 +1,16 @@ -using Microsoft.EntityFrameworkCore.Migrations.Operations; - -namespace Thinktecture.EntityFrameworkCore.Migrations; - -/// -/// Applies the schema to operations. -/// -public interface IMigrationOperationSchemaSetter -{ - /// - /// Applies the schema to . - /// - /// Operations to apply the schema to. - /// Database schema. - void SetSchema(IReadOnlyList operations, string schema); -} +using Microsoft.EntityFrameworkCore.Migrations.Operations; + +namespace Thinktecture.EntityFrameworkCore.Migrations; + +/// +/// Applies the schema to operations. +/// +public interface IMigrationOperationSchemaSetter +{ + /// + /// Applies the schema to . + /// + /// Operations to apply the schema to. + /// Database schema. + void SetSchema(IReadOnlyList operations, string schema); +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Migrations/MigrationOperationSchemaSetter.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Migrations/MigrationOperationSchemaSetter.cs index a73e15df..9e22d512 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Migrations/MigrationOperationSchemaSetter.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Migrations/MigrationOperationSchemaSetter.cs @@ -1,104 +1,104 @@ -using Microsoft.EntityFrameworkCore.Migrations.Operations; - -namespace Thinktecture.EntityFrameworkCore.Migrations; - -/// -/// Applies the schema to operations. -/// -public class MigrationOperationSchemaSetter : IMigrationOperationSchemaSetter -{ - /// - public void SetSchema(IReadOnlyList operations, string schema) - { - ArgumentNullException.ThrowIfNull(operations); - ArgumentNullException.ThrowIfNull(schema); - - foreach (var operation in operations) - { - SetSchema(operation, schema); - } - } - - private void SetSchema(MigrationOperation operation, string schema) - { - ArgumentNullException.ThrowIfNull(operation); - - switch (operation) - { - case CreateTableOperation createTable: - SetSchema(createTable, schema); - break; - case RenameTableOperation renameTable: - SetSchema(renameTable, schema); - break; - case AddForeignKeyOperation addForeignKey: - SetSchema(addForeignKey, schema); - break; - default: - var opType = operation.GetType(); - SetSchema(operation, opType, "Schema", schema); - SetSchema(operation, opType, "PrincipalSchema", schema); - break; - } - } - - /// - /// Sets the schema. - /// - /// Migration operation. - /// Schema. - protected virtual void SetSchema(CreateTableOperation op, string schema) - { - ArgumentNullException.ThrowIfNull(op); - - op.Schema = schema; - - if (op.PrimaryKey is not null) - SetSchema(op.PrimaryKey, schema); - - foreach (var column in op.Columns) - { - SetSchema(column, schema); - } - - foreach (var key in op.ForeignKeys) - { - SetSchema(key, schema); - } - - foreach (var key in op.UniqueConstraints) - { - SetSchema(key, schema); - } - } - - private static void SetSchema(RenameTableOperation op, string schema) - { - ArgumentNullException.ThrowIfNull(op); - - if (op.Schema == null) - op.Schema = schema; - - if (op.NewSchema == null) - op.NewSchema = schema; - } - - private static void SetSchema(AddForeignKeyOperation op, string schema) - { - ArgumentNullException.ThrowIfNull(op); - - if (op.Schema == null) - op.Schema = schema; - - if (op.PrincipalSchema == null) - op.PrincipalSchema = schema; - } - - private static void SetSchema(MigrationOperation operation, Type opType, string propertyName, string schema) - { - var propInfo = opType.GetProperty(propertyName); - - if (propInfo != null && propInfo.GetValue(operation) == null) - propInfo.SetValue(operation, schema); - } -} +using Microsoft.EntityFrameworkCore.Migrations.Operations; + +namespace Thinktecture.EntityFrameworkCore.Migrations; + +/// +/// Applies the schema to operations. +/// +public class MigrationOperationSchemaSetter : IMigrationOperationSchemaSetter +{ + /// + public void SetSchema(IReadOnlyList operations, string schema) + { + ArgumentNullException.ThrowIfNull(operations); + ArgumentNullException.ThrowIfNull(schema); + + foreach (var operation in operations) + { + SetSchema(operation, schema); + } + } + + private void SetSchema(MigrationOperation operation, string schema) + { + ArgumentNullException.ThrowIfNull(operation); + + switch (operation) + { + case CreateTableOperation createTable: + SetSchema(createTable, schema); + break; + case RenameTableOperation renameTable: + SetSchema(renameTable, schema); + break; + case AddForeignKeyOperation addForeignKey: + SetSchema(addForeignKey, schema); + break; + default: + var opType = operation.GetType(); + SetSchema(operation, opType, "Schema", schema); + SetSchema(operation, opType, "PrincipalSchema", schema); + break; + } + } + + /// + /// Sets the schema. + /// + /// Migration operation. + /// Schema. + protected virtual void SetSchema(CreateTableOperation op, string schema) + { + ArgumentNullException.ThrowIfNull(op); + + op.Schema = schema; + + if (op.PrimaryKey is not null) + SetSchema(op.PrimaryKey, schema); + + foreach (var column in op.Columns) + { + SetSchema(column, schema); + } + + foreach (var key in op.ForeignKeys) + { + SetSchema(key, schema); + } + + foreach (var key in op.UniqueConstraints) + { + SetSchema(key, schema); + } + } + + private static void SetSchema(RenameTableOperation op, string schema) + { + ArgumentNullException.ThrowIfNull(op); + + if (op.Schema == null) + op.Schema = schema; + + if (op.NewSchema == null) + op.NewSchema = schema; + } + + private static void SetSchema(AddForeignKeyOperation op, string schema) + { + ArgumentNullException.ThrowIfNull(op); + + if (op.Schema == null) + op.Schema = schema; + + if (op.PrincipalSchema == null) + op.PrincipalSchema = schema; + } + + private static void SetSchema(MigrationOperation operation, Type opType, string propertyName, string schema) + { + var propInfo = opType.GetProperty(propertyName); + + if (propInfo != null && propInfo.GetValue(operation) == null) + propInfo.SetValue(operation, schema); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/DummyTenantDatabaseProviderFactory.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/DummyTenantDatabaseProviderFactory.cs index cf2ba11a..565dd695 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/DummyTenantDatabaseProviderFactory.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/DummyTenantDatabaseProviderFactory.cs @@ -1,27 +1,27 @@ -namespace Thinktecture.EntityFrameworkCore.Query; - -// ReSharper disable once ClassNeverInstantiated.Global -internal sealed class DummyTenantDatabaseProviderFactory : ITenantDatabaseProviderFactory -{ - public static readonly DummyTenantDatabaseProviderFactory Instance = new(); - - private static readonly DummyTenantDatabaseProvider _provider = new(); - - /// - public ITenantDatabaseProvider Create() - { - return _provider; - } - - private class DummyTenantDatabaseProvider : ITenantDatabaseProvider - { - /// - public string? Tenant => null; - - /// - public string? GetDatabaseName(string? schema, string table) - { - return null; - } - } +namespace Thinktecture.EntityFrameworkCore.Query; + +// ReSharper disable once ClassNeverInstantiated.Global +internal sealed class DummyTenantDatabaseProviderFactory : ITenantDatabaseProviderFactory +{ + public static readonly DummyTenantDatabaseProviderFactory Instance = new(); + + private static readonly DummyTenantDatabaseProvider _provider = new(); + + /// + public ITenantDatabaseProvider Create() + { + return _provider; + } + + private class DummyTenantDatabaseProvider : ITenantDatabaseProvider + { + /// + public string? Tenant => null; + + /// + public string? GetDatabaseName(string? schema, string table) + { + return null; + } + } } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ExpressionTranslators/RelationalMethodCallTranslatorPlugin.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ExpressionTranslators/RelationalMethodCallTranslatorPlugin.cs index 00912d1d..3aec81d3 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ExpressionTranslators/RelationalMethodCallTranslatorPlugin.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ExpressionTranslators/RelationalMethodCallTranslatorPlugin.cs @@ -1,28 +1,28 @@ -using Microsoft.EntityFrameworkCore.Query; -using Thinktecture.EntityFrameworkCore.Infrastructure; - -namespace Thinktecture.EntityFrameworkCore.Query.ExpressionTranslators; - -/// -/// Plugin registering method translators. -/// -public sealed class RelationalMethodCallTranslatorPlugin : IMethodCallTranslatorPlugin -{ - /// - public IEnumerable Translators { get; } - - /// - /// Initializes new instance of . - /// - public RelationalMethodCallTranslatorPlugin(RelationalDbContextOptionsExtensionOptions options, ISqlExpressionFactory sqlExpressionFactory) - { - ArgumentNullException.ThrowIfNull(options); - - var translators = new List(); - - if (options.WindowFunctionsSupportEnabled) - translators.Add(new RelationalDbFunctionsTranslator(sqlExpressionFactory)); - - Translators = translators; - } -} +using Microsoft.EntityFrameworkCore.Query; +using Thinktecture.EntityFrameworkCore.Infrastructure; + +namespace Thinktecture.EntityFrameworkCore.Query.ExpressionTranslators; + +/// +/// Plugin registering method translators. +/// +public sealed class RelationalMethodCallTranslatorPlugin : IMethodCallTranslatorPlugin +{ + /// + public IEnumerable Translators { get; } + + /// + /// Initializes new instance of . + /// + public RelationalMethodCallTranslatorPlugin(RelationalDbContextOptionsExtensionOptions options, ISqlExpressionFactory sqlExpressionFactory) + { + ArgumentNullException.ThrowIfNull(options); + + var translators = new List(); + + if (options.WindowFunctionsSupportEnabled) + translators.Add(new RelationalDbFunctionsTranslator(sqlExpressionFactory)); + + Translators = translators; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ITenantDatabaseProvider.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ITenantDatabaseProvider.cs index d75158d8..fec5c690 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ITenantDatabaseProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ITenantDatabaseProvider.cs @@ -1,20 +1,20 @@ -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -/// Provides the database name of the provided -/// -public interface ITenantDatabaseProvider -{ - /// - /// Gets the current tenant. - /// - string? Tenant { get; } - - /// - /// Gets the database name for provided and . - /// - /// Schema of the table. - /// The table name. - /// The database name. - string? GetDatabaseName(string? schema, string table); +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +/// Provides the database name of the provided +/// +public interface ITenantDatabaseProvider +{ + /// + /// Gets the current tenant. + /// + string? Tenant { get; } + + /// + /// Gets the database name for provided and . + /// + /// Schema of the table. + /// The table name. + /// The database name. + string? GetDatabaseName(string? schema, string table); } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ITenantDatabaseProviderFactory.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ITenantDatabaseProviderFactory.cs index 829e3f36..881a6a2f 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ITenantDatabaseProviderFactory.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ITenantDatabaseProviderFactory.cs @@ -1,13 +1,13 @@ -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -/// Factory for creation of . -/// -public interface ITenantDatabaseProviderFactory -{ - /// - /// Creates an instance of . - /// - /// An instance of . - ITenantDatabaseProvider Create(); +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +/// Factory for creation of . +/// +public interface ITenantDatabaseProviderFactory +{ + /// + /// Creates an instance of . + /// + /// An instance of . + ITenantDatabaseProvider Create(); } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/SqlExpressions/INotNullableSqlExpression.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/SqlExpressions/INotNullableSqlExpression.cs index 4b39a15e..eca20bd9 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/SqlExpressions/INotNullableSqlExpression.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/SqlExpressions/INotNullableSqlExpression.cs @@ -1,8 +1,8 @@ -namespace Thinktecture.EntityFrameworkCore.Query.SqlExpressions; - -/// -/// Marker interface which represents a not-nullable expression. -/// -public interface INotNullableSqlExpression -{ +namespace Thinktecture.EntityFrameworkCore.Query.SqlExpressions; + +/// +/// Marker interface which represents a not-nullable expression. +/// +public interface INotNullableSqlExpression +{ } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ThinktectureRelationalQueryContextFactory.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ThinktectureRelationalQueryContextFactory.cs index 49cf8aad..03184f30 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ThinktectureRelationalQueryContextFactory.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ThinktectureRelationalQueryContextFactory.cs @@ -1,55 +1,55 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Query.Internal; -using Thinktecture.EntityFrameworkCore.Infrastructure; - -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -public class ThinktectureRelationalQueryContextFactory : IQueryContextFactory - where TFactory : IQueryContextFactory -{ - private const string _TENANT_PARAM_PREFIX = "97B894F9-5CDA-4B43-B7AC-6BCCE58FC19E"; - - private readonly TFactory _factory; - private readonly RelationalDbContextOptionsExtensionOptions _relationalOptions; - private readonly ITenantDatabaseProviderFactory _tenantDatabaseProviderFactory; - - /// - /// Initializes new instance of . - /// - /// Inner factory. - /// Options extensions. - /// Tenant provider. - public ThinktectureRelationalQueryContextFactory( - TFactory factory, - RelationalDbContextOptionsExtensionOptions relationalOptions, - ITenantDatabaseProviderFactory tenantDatabaseProviderFactory) - { - _factory = factory ?? throw new ArgumentNullException(nameof(factory)); - _relationalOptions = relationalOptions ?? throw new ArgumentNullException(nameof(relationalOptions)); - _tenantDatabaseProviderFactory = tenantDatabaseProviderFactory ?? throw new ArgumentNullException(nameof(tenantDatabaseProviderFactory)); - } - - /// - public QueryContext Create() - { - var ctx = _factory.Create(); - - if (_relationalOptions.TenantDatabaseSupportEnabled) - AddTenantParameter(ctx); - - return ctx; - } - - /// - /// Adds a parameter so EF query cache treats queries for different tenants as different queries. - /// - /// Query context. - [SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] - private void AddTenantParameter(IParameterValues ctx) - { - var tenantDatabaseProvider = _tenantDatabaseProviderFactory.Create(); - ctx.AddParameter($"{_TENANT_PARAM_PREFIX}|{tenantDatabaseProvider.Tenant}", null); - } -} +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.Internal; +using Thinktecture.EntityFrameworkCore.Infrastructure; + +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +public class ThinktectureRelationalQueryContextFactory : IQueryContextFactory + where TFactory : IQueryContextFactory +{ + private const string _TENANT_PARAM_PREFIX = "97B894F9-5CDA-4B43-B7AC-6BCCE58FC19E"; + + private readonly TFactory _factory; + private readonly RelationalDbContextOptionsExtensionOptions _relationalOptions; + private readonly ITenantDatabaseProviderFactory _tenantDatabaseProviderFactory; + + /// + /// Initializes new instance of . + /// + /// Inner factory. + /// Options extensions. + /// Tenant provider. + public ThinktectureRelationalQueryContextFactory( + TFactory factory, + RelationalDbContextOptionsExtensionOptions relationalOptions, + ITenantDatabaseProviderFactory tenantDatabaseProviderFactory) + { + _factory = factory ?? throw new ArgumentNullException(nameof(factory)); + _relationalOptions = relationalOptions ?? throw new ArgumentNullException(nameof(relationalOptions)); + _tenantDatabaseProviderFactory = tenantDatabaseProviderFactory ?? throw new ArgumentNullException(nameof(tenantDatabaseProviderFactory)); + } + + /// + public QueryContext Create() + { + var ctx = _factory.Create(); + + if (_relationalOptions.TenantDatabaseSupportEnabled) + AddTenantParameter(ctx); + + return ctx; + } + + /// + /// Adds a parameter so EF query cache treats queries for different tenants as different queries. + /// + /// Query context. + [SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] + private void AddTenantParameter(IParameterValues ctx) + { + var tenantDatabaseProvider = _tenantDatabaseProviderFactory.Create(); + ctx.AddParameter($"{_TENANT_PARAM_PREFIX}|{tenantDatabaseProvider.Tenant}", null); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ThinktectureSqlNullabilityProcessor.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ThinktectureSqlNullabilityProcessor.cs index 82507666..8b043b8f 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ThinktectureSqlNullabilityProcessor.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Query/ThinktectureSqlNullabilityProcessor.cs @@ -1,31 +1,31 @@ -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; -using Thinktecture.EntityFrameworkCore.Query.SqlExpressions; - -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -/// Extends . -/// -public class ThinktectureSqlNullabilityProcessor : SqlNullabilityProcessor -{ - /// - public ThinktectureSqlNullabilityProcessor( - RelationalParameterBasedSqlProcessorDependencies dependencies, - RelationalParameterBasedSqlProcessorParameters parameters) - : base(dependencies, parameters) - { - } - - /// - protected override SqlExpression VisitCustomSqlExpression(SqlExpression sqlExpression, bool allowOptimizedExpansion, out bool nullable) - { - if (sqlExpression is INotNullableSqlExpression) - { - nullable = false; - return sqlExpression; - } - - return base.VisitCustomSqlExpression(sqlExpression, allowOptimizedExpansion, out nullable); - } -} +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Thinktecture.EntityFrameworkCore.Query.SqlExpressions; + +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +/// Extends . +/// +public class ThinktectureSqlNullabilityProcessor : SqlNullabilityProcessor +{ + /// + public ThinktectureSqlNullabilityProcessor( + RelationalParameterBasedSqlProcessorDependencies dependencies, + RelationalParameterBasedSqlProcessorParameters parameters) + : base(dependencies, parameters) + { + } + + /// + protected override SqlExpression VisitCustomSqlExpression(SqlExpression sqlExpression, bool allowOptimizedExpansion, out bool nullable) + { + if (sqlExpression is INotNullableSqlExpression) + { + nullable = false; + return sqlExpression; + } + + return base.VisitCustomSqlExpression(sqlExpression, allowOptimizedExpansion, out nullable); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/RelationalDbLoggerCategory.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/RelationalDbLoggerCategory.cs index a1f2abc8..d257b1e2 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/RelationalDbLoggerCategory.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/RelationalDbLoggerCategory.cs @@ -1,16 +1,16 @@ -using Microsoft.EntityFrameworkCore.Diagnostics; - -namespace Thinktecture.EntityFrameworkCore; - -/// -/// Logger category. -/// -public static class RelationalDbLoggerCategory -{ - /// - /// Logger category for nested transactions. - /// - public class NestedTransaction : LoggerCategory - { - } -} +using Microsoft.EntityFrameworkCore.Diagnostics; + +namespace Thinktecture.EntityFrameworkCore; + +/// +/// Logger category. +/// +public static class RelationalDbLoggerCategory +{ + /// + /// Logger category for nested transactions. + /// + public class NestedTransaction : LoggerCategory + { + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/ChildNestedDbContextTransaction.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/ChildNestedDbContextTransaction.cs index 447af82b..9506e12a 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/ChildNestedDbContextTransaction.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/ChildNestedDbContextTransaction.cs @@ -1,75 +1,75 @@ -using System.Data.Common; -using Microsoft.EntityFrameworkCore.Diagnostics; - -namespace Thinktecture.EntityFrameworkCore.Storage; - -/// -/// A child transaction. -/// -public class ChildNestedDbContextTransaction : NestedDbContextTransaction -{ - private readonly NestedDbContextTransaction _parent; - - /// - protected override string TransactionTypeName => "child transaction"; - - /// - public override bool SupportsSavepoints => _parent.SupportsSavepoints; - - /// - /// Initializes new instance of . - /// - /// Logger. - /// Nested transaction manager. - /// Parent transaction. - public ChildNestedDbContextTransaction( - IDiagnosticsLogger logger, - NestedRelationalTransactionManager nestedTransactionManager, - NestedDbContextTransaction parent) - : base(logger, nestedTransactionManager, Guid.NewGuid()) - { - _parent = parent ?? throw new ArgumentNullException(nameof(parent)); - } - - /// - protected internal override DbTransaction GetUnderlyingTransaction() - { - return _parent.GetUnderlyingTransaction(); - } - - /// - public override void CreateSavepoint(string name) - { - _parent.CreateSavepoint(name); - } - - /// - public override Task CreateSavepointAsync(string name, CancellationToken cancellationToken = default) - { - return _parent.CreateSavepointAsync(name, cancellationToken); - } - - /// - public override void RollbackToSavepoint(string name) - { - _parent.RollbackToSavepoint(name); - } - - /// - public override Task RollbackToSavepointAsync(string name, CancellationToken cancellationToken = default) - { - return _parent.RollbackToSavepointAsync(name, cancellationToken); - } - - /// - public override void ReleaseSavepoint(string name) - { - _parent.ReleaseSavepoint(name); - } - - /// - public override Task ReleaseSavepointAsync(string name, CancellationToken cancellationToken = default) - { - return _parent.ReleaseSavepointAsync(name, cancellationToken); - } -} +using System.Data.Common; +using Microsoft.EntityFrameworkCore.Diagnostics; + +namespace Thinktecture.EntityFrameworkCore.Storage; + +/// +/// A child transaction. +/// +public class ChildNestedDbContextTransaction : NestedDbContextTransaction +{ + private readonly NestedDbContextTransaction _parent; + + /// + protected override string TransactionTypeName => "child transaction"; + + /// + public override bool SupportsSavepoints => _parent.SupportsSavepoints; + + /// + /// Initializes new instance of . + /// + /// Logger. + /// Nested transaction manager. + /// Parent transaction. + public ChildNestedDbContextTransaction( + IDiagnosticsLogger logger, + NestedRelationalTransactionManager nestedTransactionManager, + NestedDbContextTransaction parent) + : base(logger, nestedTransactionManager, Guid.NewGuid()) + { + _parent = parent ?? throw new ArgumentNullException(nameof(parent)); + } + + /// + protected internal override DbTransaction GetUnderlyingTransaction() + { + return _parent.GetUnderlyingTransaction(); + } + + /// + public override void CreateSavepoint(string name) + { + _parent.CreateSavepoint(name); + } + + /// + public override Task CreateSavepointAsync(string name, CancellationToken cancellationToken = default) + { + return _parent.CreateSavepointAsync(name, cancellationToken); + } + + /// + public override void RollbackToSavepoint(string name) + { + _parent.RollbackToSavepoint(name); + } + + /// + public override Task RollbackToSavepointAsync(string name, CancellationToken cancellationToken = default) + { + return _parent.RollbackToSavepointAsync(name, cancellationToken); + } + + /// + public override void ReleaseSavepoint(string name) + { + _parent.ReleaseSavepoint(name); + } + + /// + public override Task ReleaseSavepointAsync(string name, CancellationToken cancellationToken = default) + { + return _parent.ReleaseSavepointAsync(name, cancellationToken); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/NestedDbContextTransaction.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/NestedDbContextTransaction.cs index c9b8f33f..6df94b2f 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/NestedDbContextTransaction.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/NestedDbContextTransaction.cs @@ -1,328 +1,328 @@ -using System.Data.Common; -using System.Transactions; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.Logging; -using IsolationLevel = System.Data.IsolationLevel; - -namespace Thinktecture.EntityFrameworkCore.Storage; - -/// -/// A nested transaction. -/// -public abstract class NestedDbContextTransaction : IDbContextTransaction, IInfrastructure -{ - private bool _isCommitted; - private bool _isRolledBack; - private bool _isDisposed; - - private Stack? _children; - - /// - /// Friendly transaction type name. - /// - protected abstract string TransactionTypeName { get; } - - /// - /// Gets the indication whether the transaction is completed, i.e. committed/rolled back, or not. - /// - protected bool IsCompleted => _isCommitted || _isRolledBack; - - /// - /// Nested transaction manager. - /// - protected NestedRelationalTransactionManager NestedTransactionManager { get; private set; } - - /// - /// Logger. - /// - protected IDiagnosticsLogger DiagnosticsLogger { get; } - - /// - public Guid TransactionId { get; } - - /// - DbTransaction IInfrastructure.Instance => GetUnderlyingTransaction(); - - /// - /// Gets the underlying . - /// - /// The underlying . - protected internal abstract DbTransaction GetUnderlyingTransaction(); - - /// - /// Initializes new instance of . - /// - /// Logger. - /// Nested transaction manager. - /// The transaction id. - protected NestedDbContextTransaction( - IDiagnosticsLogger logger, - NestedRelationalTransactionManager nestedTransactionManager, - Guid transactionId) - { - DiagnosticsLogger = logger ?? throw new ArgumentNullException(nameof(logger)); - NestedTransactionManager = nestedTransactionManager ?? throw new ArgumentNullException(nameof(nestedTransactionManager)); - TransactionId = transactionId; - } - - /// - /// Start a new child transaction. - /// - /// A child transaction. - internal ChildNestedDbContextTransaction BeginTransaction(IsolationLevel? isolationLevel) - { - EnsureUsable(); - - if (isolationLevel.HasValue) - EnsureIsolationLevelCompatible(isolationLevel.Value); - - var child = new ChildNestedDbContextTransaction(DiagnosticsLogger, NestedTransactionManager, this); - - if (_children == null) - _children = new Stack(); - - _children.Push(child); - - return child; - } - - private void EnsureIsolationLevelCompatible(IsolationLevel isolationLevel) - { - var underlyingTransaction = GetUnderlyingTransaction(); - var currentIsolationLevel = underlyingTransaction.IsolationLevel; - - switch (isolationLevel) - { - case IsolationLevel.Unspecified: - throw new ArgumentException($"The isolation level '{IsolationLevel.Unspecified}' is not allowed.", nameof(isolationLevel)); - - case IsolationLevel.Chaos: - if (currentIsolationLevel != IsolationLevel.Chaos) - throw CreateException(isolationLevel, currentIsolationLevel); - - break; - - case IsolationLevel.ReadUncommitted: - if (currentIsolationLevel != IsolationLevel.Serializable && - currentIsolationLevel != IsolationLevel.RepeatableRead && - currentIsolationLevel != IsolationLevel.ReadCommitted && - currentIsolationLevel != IsolationLevel.ReadUncommitted) - throw CreateException(isolationLevel, currentIsolationLevel); - - break; - - case IsolationLevel.ReadCommitted: - if (currentIsolationLevel != IsolationLevel.Serializable && - currentIsolationLevel != IsolationLevel.RepeatableRead && - currentIsolationLevel != IsolationLevel.ReadCommitted) - throw CreateException(isolationLevel, currentIsolationLevel); - - break; - - case IsolationLevel.RepeatableRead: - if (currentIsolationLevel != IsolationLevel.Serializable && - currentIsolationLevel != IsolationLevel.RepeatableRead) - throw CreateException(isolationLevel, currentIsolationLevel); - - break; - - case IsolationLevel.Serializable: - if (currentIsolationLevel != IsolationLevel.Serializable) - throw CreateException(isolationLevel, currentIsolationLevel); - - break; - - case IsolationLevel.Snapshot: - if (currentIsolationLevel != IsolationLevel.Snapshot) - throw CreateException(isolationLevel, currentIsolationLevel); - - break; - - default: - throw new ArgumentOutOfRangeException(nameof(isolationLevel), isolationLevel, $"Unknown {nameof(IsolationLevel)}."); - } - } - - private static InvalidOperationException CreateException( - IsolationLevel newIsolationLevel, - IsolationLevel currentIsolationLevel) - { - return new($"The isolation level '{currentIsolationLevel}' of the parent transaction is not compatible to the provided isolation level '{newIsolationLevel}'."); - } - - /// - public virtual void Commit() - { - EnsureUsable(); - EnsureChildrenCompleted(); - - if (_children?.Any(c => !c._isCommitted) == true) - throw new TransactionAbortedException("The transaction has aborted."); - - _isCommitted = true; - NestedTransactionManager.Remove(this); - - DiagnosticsLogger.Logger.LogInformation("The {TransactionType} with id '{TransactionId}' is committed.", TransactionTypeName, TransactionId); - } - - /// - public virtual async Task CommitAsync(CancellationToken cancellationToken = default) - { - EnsureUsable(); - EnsureChildrenCompleted(); - - if (_children?.Any(c => !c._isCommitted) == true) - throw new TransactionAbortedException("The transaction has aborted."); - - _isCommitted = true; - await NestedTransactionManager.RemoveAsync(this).ConfigureAwait(false); - - DiagnosticsLogger.Logger.LogInformation("The {TransactionType} with id '{TransactionId}' is committed.", TransactionTypeName, TransactionId); - } - - /// - public virtual void Rollback() - { - RollbackInternal(); - NestedTransactionManager.Remove(this); - } - - /// - public virtual async Task RollbackAsync(CancellationToken cancellationToken = default) - { - RollbackInternal(); - await NestedTransactionManager.RemoveAsync(this).ConfigureAwait(false); - } - - private void RollbackInternal() - { - EnsureUsable(); - EnsureChildrenCompleted(); - - _isRolledBack = true; - DiagnosticsLogger.Logger.LogInformation("The {TransactionType} with id '{TransactionId}' is rolled back.", TransactionTypeName, TransactionId); - } - - private void EnsureChildrenCompleted() - { - if (_children?.Any(c => !c.IsCompleted) == true) - throw new InvalidOperationException("Transactions nested incorrectly. At least one of the child transactions is not completed."); - } - - /// - public abstract bool SupportsSavepoints { get; } - - /// - public abstract void CreateSavepoint(string name); - - /// - public abstract Task CreateSavepointAsync(string name, CancellationToken cancellationToken = default); - - /// - public abstract void RollbackToSavepoint(string name); - - /// - public abstract Task RollbackToSavepointAsync(string name, CancellationToken cancellationToken = default); - - /// - public abstract void ReleaseSavepoint(string name); - - /// - public abstract Task ReleaseSavepointAsync(string name, CancellationToken cancellationToken = default); - - /// - public void Dispose() - { - if (_isDisposed) - return; - - DiagnosticsLogger.Logger.LogInformation("Disposing {TransactionType} with id '{TransactionId}'.", TransactionTypeName, TransactionId); - Dispose(true); - - _isDisposed = true; - - GC.SuppressFinalize(this); - } - - /// - public async ValueTask DisposeAsync() - { - if (_isDisposed) - return; - - DiagnosticsLogger.Logger.LogInformation("Disposing {TransactionType} with id '{TransactionId}'.", TransactionTypeName, TransactionId); - await DisposeAsync(true).ConfigureAwait(false); - - _isDisposed = true; - - GC.SuppressFinalize(this); - } - - /// - /// Disposes of current transaction. - /// - /// Indication whether this call is made by the method . - protected virtual void Dispose(bool disposing) - { - if (!disposing) - return; - - DisposeChildren(); - - if (!IsCompleted) - RollbackInternal(); - - NestedTransactionManager.Remove(this); - NestedTransactionManager = null!; - _children = null; - } - - /// - /// Disposes of current transaction. - /// - /// Indication whether this call is made by the method . - protected virtual async ValueTask DisposeAsync(bool disposing) - { - if (!disposing) - return; - - await DisposeChildrenAsync().ConfigureAwait(false); - - if (!IsCompleted) - RollbackInternal(); - - await NestedTransactionManager.RemoveAsync(this).ConfigureAwait(false); - NestedTransactionManager = null!; - _children = null; - } - - private void DisposeChildren() - { - if (_children == null) - return; - - while (_children.Count > 0) - { - _children.Pop().Dispose(); - } - } - - private async ValueTask DisposeChildrenAsync() - { - if (_children == null) - return; - - while (_children.Count > 0) - { - await _children.Pop().DisposeAsync().ConfigureAwait(false); - } - } - - private void EnsureUsable() - { - if (IsCompleted || _isDisposed) - throw new InvalidOperationException($"This {TransactionTypeName} has completed; it is no longer usable."); - } -} +using System.Data.Common; +using System.Transactions; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.Logging; +using IsolationLevel = System.Data.IsolationLevel; + +namespace Thinktecture.EntityFrameworkCore.Storage; + +/// +/// A nested transaction. +/// +public abstract class NestedDbContextTransaction : IDbContextTransaction, IInfrastructure +{ + private bool _isCommitted; + private bool _isRolledBack; + private bool _isDisposed; + + private Stack? _children; + + /// + /// Friendly transaction type name. + /// + protected abstract string TransactionTypeName { get; } + + /// + /// Gets the indication whether the transaction is completed, i.e. committed/rolled back, or not. + /// + protected bool IsCompleted => _isCommitted || _isRolledBack; + + /// + /// Nested transaction manager. + /// + protected NestedRelationalTransactionManager NestedTransactionManager { get; private set; } + + /// + /// Logger. + /// + protected IDiagnosticsLogger DiagnosticsLogger { get; } + + /// + public Guid TransactionId { get; } + + /// + DbTransaction IInfrastructure.Instance => GetUnderlyingTransaction(); + + /// + /// Gets the underlying . + /// + /// The underlying . + protected internal abstract DbTransaction GetUnderlyingTransaction(); + + /// + /// Initializes new instance of . + /// + /// Logger. + /// Nested transaction manager. + /// The transaction id. + protected NestedDbContextTransaction( + IDiagnosticsLogger logger, + NestedRelationalTransactionManager nestedTransactionManager, + Guid transactionId) + { + DiagnosticsLogger = logger ?? throw new ArgumentNullException(nameof(logger)); + NestedTransactionManager = nestedTransactionManager ?? throw new ArgumentNullException(nameof(nestedTransactionManager)); + TransactionId = transactionId; + } + + /// + /// Start a new child transaction. + /// + /// A child transaction. + internal ChildNestedDbContextTransaction BeginTransaction(IsolationLevel? isolationLevel) + { + EnsureUsable(); + + if (isolationLevel.HasValue) + EnsureIsolationLevelCompatible(isolationLevel.Value); + + var child = new ChildNestedDbContextTransaction(DiagnosticsLogger, NestedTransactionManager, this); + + if (_children == null) + _children = new Stack(); + + _children.Push(child); + + return child; + } + + private void EnsureIsolationLevelCompatible(IsolationLevel isolationLevel) + { + var underlyingTransaction = GetUnderlyingTransaction(); + var currentIsolationLevel = underlyingTransaction.IsolationLevel; + + switch (isolationLevel) + { + case IsolationLevel.Unspecified: + throw new ArgumentException($"The isolation level '{IsolationLevel.Unspecified}' is not allowed.", nameof(isolationLevel)); + + case IsolationLevel.Chaos: + if (currentIsolationLevel != IsolationLevel.Chaos) + throw CreateException(isolationLevel, currentIsolationLevel); + + break; + + case IsolationLevel.ReadUncommitted: + if (currentIsolationLevel != IsolationLevel.Serializable && + currentIsolationLevel != IsolationLevel.RepeatableRead && + currentIsolationLevel != IsolationLevel.ReadCommitted && + currentIsolationLevel != IsolationLevel.ReadUncommitted) + throw CreateException(isolationLevel, currentIsolationLevel); + + break; + + case IsolationLevel.ReadCommitted: + if (currentIsolationLevel != IsolationLevel.Serializable && + currentIsolationLevel != IsolationLevel.RepeatableRead && + currentIsolationLevel != IsolationLevel.ReadCommitted) + throw CreateException(isolationLevel, currentIsolationLevel); + + break; + + case IsolationLevel.RepeatableRead: + if (currentIsolationLevel != IsolationLevel.Serializable && + currentIsolationLevel != IsolationLevel.RepeatableRead) + throw CreateException(isolationLevel, currentIsolationLevel); + + break; + + case IsolationLevel.Serializable: + if (currentIsolationLevel != IsolationLevel.Serializable) + throw CreateException(isolationLevel, currentIsolationLevel); + + break; + + case IsolationLevel.Snapshot: + if (currentIsolationLevel != IsolationLevel.Snapshot) + throw CreateException(isolationLevel, currentIsolationLevel); + + break; + + default: + throw new ArgumentOutOfRangeException(nameof(isolationLevel), isolationLevel, $"Unknown {nameof(IsolationLevel)}."); + } + } + + private static InvalidOperationException CreateException( + IsolationLevel newIsolationLevel, + IsolationLevel currentIsolationLevel) + { + return new($"The isolation level '{currentIsolationLevel}' of the parent transaction is not compatible to the provided isolation level '{newIsolationLevel}'."); + } + + /// + public virtual void Commit() + { + EnsureUsable(); + EnsureChildrenCompleted(); + + if (_children?.Any(c => !c._isCommitted) == true) + throw new TransactionAbortedException("The transaction has aborted."); + + _isCommitted = true; + NestedTransactionManager.Remove(this); + + DiagnosticsLogger.Logger.LogInformation("The {TransactionType} with id '{TransactionId}' is committed.", TransactionTypeName, TransactionId); + } + + /// + public virtual async Task CommitAsync(CancellationToken cancellationToken = default) + { + EnsureUsable(); + EnsureChildrenCompleted(); + + if (_children?.Any(c => !c._isCommitted) == true) + throw new TransactionAbortedException("The transaction has aborted."); + + _isCommitted = true; + await NestedTransactionManager.RemoveAsync(this).ConfigureAwait(false); + + DiagnosticsLogger.Logger.LogInformation("The {TransactionType} with id '{TransactionId}' is committed.", TransactionTypeName, TransactionId); + } + + /// + public virtual void Rollback() + { + RollbackInternal(); + NestedTransactionManager.Remove(this); + } + + /// + public virtual async Task RollbackAsync(CancellationToken cancellationToken = default) + { + RollbackInternal(); + await NestedTransactionManager.RemoveAsync(this).ConfigureAwait(false); + } + + private void RollbackInternal() + { + EnsureUsable(); + EnsureChildrenCompleted(); + + _isRolledBack = true; + DiagnosticsLogger.Logger.LogInformation("The {TransactionType} with id '{TransactionId}' is rolled back.", TransactionTypeName, TransactionId); + } + + private void EnsureChildrenCompleted() + { + if (_children?.Any(c => !c.IsCompleted) == true) + throw new InvalidOperationException("Transactions nested incorrectly. At least one of the child transactions is not completed."); + } + + /// + public abstract bool SupportsSavepoints { get; } + + /// + public abstract void CreateSavepoint(string name); + + /// + public abstract Task CreateSavepointAsync(string name, CancellationToken cancellationToken = default); + + /// + public abstract void RollbackToSavepoint(string name); + + /// + public abstract Task RollbackToSavepointAsync(string name, CancellationToken cancellationToken = default); + + /// + public abstract void ReleaseSavepoint(string name); + + /// + public abstract Task ReleaseSavepointAsync(string name, CancellationToken cancellationToken = default); + + /// + public void Dispose() + { + if (_isDisposed) + return; + + DiagnosticsLogger.Logger.LogInformation("Disposing {TransactionType} with id '{TransactionId}'.", TransactionTypeName, TransactionId); + Dispose(true); + + _isDisposed = true; + + GC.SuppressFinalize(this); + } + + /// + public async ValueTask DisposeAsync() + { + if (_isDisposed) + return; + + DiagnosticsLogger.Logger.LogInformation("Disposing {TransactionType} with id '{TransactionId}'.", TransactionTypeName, TransactionId); + await DisposeAsync(true).ConfigureAwait(false); + + _isDisposed = true; + + GC.SuppressFinalize(this); + } + + /// + /// Disposes of current transaction. + /// + /// Indication whether this call is made by the method . + protected virtual void Dispose(bool disposing) + { + if (!disposing) + return; + + DisposeChildren(); + + if (!IsCompleted) + RollbackInternal(); + + NestedTransactionManager.Remove(this); + NestedTransactionManager = null!; + _children = null; + } + + /// + /// Disposes of current transaction. + /// + /// Indication whether this call is made by the method . + protected virtual async ValueTask DisposeAsync(bool disposing) + { + if (!disposing) + return; + + await DisposeChildrenAsync().ConfigureAwait(false); + + if (!IsCompleted) + RollbackInternal(); + + await NestedTransactionManager.RemoveAsync(this).ConfigureAwait(false); + NestedTransactionManager = null!; + _children = null; + } + + private void DisposeChildren() + { + if (_children == null) + return; + + while (_children.Count > 0) + { + _children.Pop().Dispose(); + } + } + + private async ValueTask DisposeChildrenAsync() + { + if (_children == null) + return; + + while (_children.Count > 0) + { + await _children.Pop().DisposeAsync().ConfigureAwait(false); + } + } + + private void EnsureUsable() + { + if (IsCompleted || _isDisposed) + throw new InvalidOperationException($"This {TransactionTypeName} has completed; it is no longer usable."); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/NestedRelationalTransactionManager.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/NestedRelationalTransactionManager.cs index 27c609c1..f3f6fc68 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/NestedRelationalTransactionManager.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/NestedRelationalTransactionManager.cs @@ -1,327 +1,327 @@ -using System.Data.Common; -using System.Transactions; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.Logging; -using IsolationLevel = System.Data.IsolationLevel; - -namespace Thinktecture.EntityFrameworkCore.Storage; - -/// -/// Transaction manager with nested transaction support. -/// -public class NestedRelationalTransactionManager : IRelationalTransactionManager, ITransactionEnlistmentManager -{ - private readonly IDiagnosticsLogger _logger; - private readonly IRelationalConnection _innerManager; - private readonly ITransactionEnlistmentManager? _enlistmentManager; - private readonly Stack _transactions; - - /// - public IDbContextTransaction? CurrentTransaction => CurrentNestedTransaction; - - private NestedDbContextTransaction? CurrentNestedTransaction => _transactions.FirstOrDefault(); - - /// - public Transaction? EnlistedTransaction => (_enlistmentManager ?? throw new NotSupportedException("The current provider doesn't support System.Transaction.")).EnlistedTransaction; - - /// - /// Initializes new instance of . - /// - /// Logger. - /// Current connection which is the "real" transaction manager, i.e. the one of the current database provider. - public NestedRelationalTransactionManager( - IDiagnosticsLogger logger, - IRelationalConnection connection) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _innerManager = connection ?? throw new ArgumentNullException(nameof(connection)); - _enlistmentManager = connection as ITransactionEnlistmentManager; - _transactions = new Stack(); - } - - /// - public void EnlistTransaction(Transaction? transaction) - { - (_enlistmentManager ?? throw new NotSupportedException("The current provider doesn't support System.Transaction.")) - .EnlistTransaction(transaction); - } - - /// - public IDbContextTransaction? UseTransaction( - DbTransaction? transaction) - { - return UseTransactionInternal(transaction, null); - } - - /// - public IDbContextTransaction? UseTransaction(DbTransaction? transaction, Guid transactionId) - { - return UseTransactionInternal(transaction, transactionId); - } - - private IDbContextTransaction? UseTransactionInternal( - DbTransaction? transaction, - Guid? transactionId) - { - if (transaction == null) - { - _logger.Logger.LogInformation($"Setting {nameof(DbTransaction)} to null."); - - if (transactionId.HasValue) - { - _innerManager.UseTransaction(null, transactionId.Value); - } - else - { - _innerManager.UseTransaction(null); - } - - ClearTransactions(); - } - else - { - _logger.Logger.LogInformation($"Setting {nameof(DbTransaction)} to the provided one."); - var tx = transactionId.HasValue - ? _innerManager.UseTransaction(transaction, transactionId.Value) - : _innerManager.UseTransaction(transaction); - - if (tx == null) - { - _logger.Logger.LogWarning("The inner transaction manager returned 'null' although the provided one is not null."); - ClearTransactions(); - } - else - { - _transactions.Push(new RootNestedDbContextTransaction(_logger, this, _innerManager, tx, transactionId)); - } - } - - return CurrentTransaction; - } - - /// - public Task UseTransactionAsync( - DbTransaction? transaction, - CancellationToken cancellationToken = default) - { - return UseTransactionInternalAsync(transaction, null, cancellationToken); - } - - /// - public Task UseTransactionAsync( - DbTransaction? transaction, - Guid transactionId, - CancellationToken cancellationToken = default) - { - return UseTransactionInternalAsync(transaction, transactionId, cancellationToken); - } - - private async Task UseTransactionInternalAsync( - DbTransaction? transaction, - Guid? transactionId, - CancellationToken cancellationToken) - { - if (transaction == null) - { - _logger.Logger.LogInformation($"Setting {nameof(DbTransaction)} to null."); - - if (transactionId.HasValue) - { - await _innerManager.UseTransactionAsync(null, transactionId.Value, cancellationToken).ConfigureAwait(false); - } - else - { - await _innerManager.UseTransactionAsync(null, cancellationToken).ConfigureAwait(false); - } - - ClearTransactions(); - } - else - { - _logger.Logger.LogInformation($"Setting {nameof(DbTransaction)} to the provided one."); - var tx = transactionId.HasValue - ? await _innerManager.UseTransactionAsync(transaction, transactionId.Value, cancellationToken).ConfigureAwait(false) - : await _innerManager.UseTransactionAsync(transaction, cancellationToken).ConfigureAwait(false); - - if (tx == null) - { - _logger.Logger.LogWarning("The inner transaction manager returned 'null' although the provided one is not null."); - ClearTransactions(); - } - else - { - _transactions.Push(new RootNestedDbContextTransaction(_logger, this, _innerManager, tx, transactionId)); - } - } - - return CurrentTransaction; - } - - /// - public void ResetState() - { - _logger.Logger.LogInformation("Resetting inner state."); - _innerManager.ResetState(); - - while (_transactions.Count > 0) - { - _transactions.Pop().Dispose(); - } - } - - /// - public async Task ResetStateAsync(CancellationToken cancellationToken = default) - { - _logger.Logger.LogInformation("Resetting inner state."); - await _innerManager.ResetStateAsync(cancellationToken).ConfigureAwait(false); - - while (_transactions.Count > 0) - { - await _transactions.Pop().DisposeAsync().ConfigureAwait(false); - } - } - - /// - public IDbContextTransaction BeginTransaction() - { - return BeginTransactionInternal(null); - } - - /// - public IDbContextTransaction BeginTransaction(IsolationLevel isolationLevel) - { - return BeginTransactionInternal(isolationLevel); - } - - /// - public Task BeginTransactionAsync(CancellationToken cancellationToken = default) - { - return BeginTransactionInternalAsync(null, cancellationToken); - } - - /// - public Task BeginTransactionAsync(IsolationLevel isolationLevel, CancellationToken cancellationToken = default) - { - return BeginTransactionInternalAsync(isolationLevel, cancellationToken); - } - - private IDbContextTransaction BeginTransactionInternal(IsolationLevel? isolationLevel) - { - var currentTx = CurrentNestedTransaction; - - if (currentTx != null) - { - currentTx = currentTx.BeginTransaction(isolationLevel); - _logger.Logger.LogInformation("Started a child transaction with id '{TransactionId}' using isolation level '{IsolationLevel}'.", currentTx.TransactionId, isolationLevel); - } - else - { - var tx = isolationLevel.HasValue ? _innerManager.BeginTransaction(isolationLevel.Value) : _innerManager.BeginTransaction(); - currentTx = new RootNestedDbContextTransaction(_logger, this, _innerManager, tx, null); - _logger.Logger.LogInformation("Started a root transaction with id '{TransactionId}' using isolation level '{IsolationLevel}'.", currentTx.TransactionId, isolationLevel); - } - - _transactions.Push(currentTx); - - return currentTx; - } - - private async Task BeginTransactionInternalAsync(IsolationLevel? isolationLevel, CancellationToken cancellationToken) - { - var currentTx = CurrentNestedTransaction; - - if (currentTx != null) - { - currentTx = currentTx.BeginTransaction(isolationLevel); - _logger.Logger.LogInformation("Started a child transaction with id '{TransactionId}' using isolation level '{IsolationLevel}'.", currentTx.TransactionId, isolationLevel); - } - else - { - var tx = await (isolationLevel.HasValue - ? _innerManager.BeginTransactionAsync(isolationLevel.Value, cancellationToken) - : _innerManager.BeginTransactionAsync(cancellationToken)) - .ConfigureAwait(false); - currentTx = new RootNestedDbContextTransaction(_logger, this, _innerManager, tx, null); - _logger.Logger.LogInformation("Started a root transaction with id '{TransactionId}' using isolation level '{IsolationLevel}'.", currentTx.TransactionId, isolationLevel); - } - - _transactions.Push(currentTx); - - return currentTx; - } - - /// - public void CommitTransaction() - { - if (_transactions.Count == 0) - throw new InvalidOperationException("The connection does not have any active transactions."); - - _transactions.Pop().Commit(); - } - - /// - public Task CommitTransactionAsync(CancellationToken cancellationToken = default) - { - if (_transactions.Count == 0) - throw new InvalidOperationException("The connection does not have any active transactions."); - - return _transactions.Pop().CommitAsync(cancellationToken); - } - - /// - public void RollbackTransaction() - { - if (_transactions.Count == 0) - throw new InvalidOperationException("The connection does not have any active transactions."); - - _transactions.Pop().Rollback(); - } - - /// - public Task RollbackTransactionAsync(CancellationToken cancellationToken = default) - { - if (_transactions.Count == 0) - throw new InvalidOperationException("The connection does not have any active transactions."); - - return _transactions.Pop().RollbackAsync(cancellationToken); - } - - private void ClearTransactions() - { - _transactions.Clear(); - } - - internal void Remove(NestedDbContextTransaction transaction) - { - if (!_transactions.Contains(transaction)) - return; - - while (_transactions.Count > 0) - { - var tx = _transactions.Pop(); - - if (tx == transaction) - return; - - tx.Dispose(); - } - } - - internal async ValueTask RemoveAsync( - NestedDbContextTransaction transaction) - { - if (!_transactions.Contains(transaction)) - return; - - while (_transactions.Count > 0) - { - var tx = _transactions.Pop(); - - if (tx == transaction) - return; - - await tx.DisposeAsync().ConfigureAwait(false); - } - } -} +using System.Data.Common; +using System.Transactions; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.Logging; +using IsolationLevel = System.Data.IsolationLevel; + +namespace Thinktecture.EntityFrameworkCore.Storage; + +/// +/// Transaction manager with nested transaction support. +/// +public class NestedRelationalTransactionManager : IRelationalTransactionManager, ITransactionEnlistmentManager +{ + private readonly IDiagnosticsLogger _logger; + private readonly IRelationalConnection _innerManager; + private readonly ITransactionEnlistmentManager? _enlistmentManager; + private readonly Stack _transactions; + + /// + public IDbContextTransaction? CurrentTransaction => CurrentNestedTransaction; + + private NestedDbContextTransaction? CurrentNestedTransaction => _transactions.FirstOrDefault(); + + /// + public Transaction? EnlistedTransaction => (_enlistmentManager ?? throw new NotSupportedException("The current provider doesn't support System.Transaction.")).EnlistedTransaction; + + /// + /// Initializes new instance of . + /// + /// Logger. + /// Current connection which is the "real" transaction manager, i.e. the one of the current database provider. + public NestedRelationalTransactionManager( + IDiagnosticsLogger logger, + IRelationalConnection connection) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _innerManager = connection ?? throw new ArgumentNullException(nameof(connection)); + _enlistmentManager = connection as ITransactionEnlistmentManager; + _transactions = new Stack(); + } + + /// + public void EnlistTransaction(Transaction? transaction) + { + (_enlistmentManager ?? throw new NotSupportedException("The current provider doesn't support System.Transaction.")) + .EnlistTransaction(transaction); + } + + /// + public IDbContextTransaction? UseTransaction( + DbTransaction? transaction) + { + return UseTransactionInternal(transaction, null); + } + + /// + public IDbContextTransaction? UseTransaction(DbTransaction? transaction, Guid transactionId) + { + return UseTransactionInternal(transaction, transactionId); + } + + private IDbContextTransaction? UseTransactionInternal( + DbTransaction? transaction, + Guid? transactionId) + { + if (transaction == null) + { + _logger.Logger.LogInformation($"Setting {nameof(DbTransaction)} to null."); + + if (transactionId.HasValue) + { + _innerManager.UseTransaction(null, transactionId.Value); + } + else + { + _innerManager.UseTransaction(null); + } + + ClearTransactions(); + } + else + { + _logger.Logger.LogInformation($"Setting {nameof(DbTransaction)} to the provided one."); + var tx = transactionId.HasValue + ? _innerManager.UseTransaction(transaction, transactionId.Value) + : _innerManager.UseTransaction(transaction); + + if (tx == null) + { + _logger.Logger.LogWarning("The inner transaction manager returned 'null' although the provided one is not null."); + ClearTransactions(); + } + else + { + _transactions.Push(new RootNestedDbContextTransaction(_logger, this, _innerManager, tx, transactionId)); + } + } + + return CurrentTransaction; + } + + /// + public Task UseTransactionAsync( + DbTransaction? transaction, + CancellationToken cancellationToken = default) + { + return UseTransactionInternalAsync(transaction, null, cancellationToken); + } + + /// + public Task UseTransactionAsync( + DbTransaction? transaction, + Guid transactionId, + CancellationToken cancellationToken = default) + { + return UseTransactionInternalAsync(transaction, transactionId, cancellationToken); + } + + private async Task UseTransactionInternalAsync( + DbTransaction? transaction, + Guid? transactionId, + CancellationToken cancellationToken) + { + if (transaction == null) + { + _logger.Logger.LogInformation($"Setting {nameof(DbTransaction)} to null."); + + if (transactionId.HasValue) + { + await _innerManager.UseTransactionAsync(null, transactionId.Value, cancellationToken).ConfigureAwait(false); + } + else + { + await _innerManager.UseTransactionAsync(null, cancellationToken).ConfigureAwait(false); + } + + ClearTransactions(); + } + else + { + _logger.Logger.LogInformation($"Setting {nameof(DbTransaction)} to the provided one."); + var tx = transactionId.HasValue + ? await _innerManager.UseTransactionAsync(transaction, transactionId.Value, cancellationToken).ConfigureAwait(false) + : await _innerManager.UseTransactionAsync(transaction, cancellationToken).ConfigureAwait(false); + + if (tx == null) + { + _logger.Logger.LogWarning("The inner transaction manager returned 'null' although the provided one is not null."); + ClearTransactions(); + } + else + { + _transactions.Push(new RootNestedDbContextTransaction(_logger, this, _innerManager, tx, transactionId)); + } + } + + return CurrentTransaction; + } + + /// + public void ResetState() + { + _logger.Logger.LogInformation("Resetting inner state."); + _innerManager.ResetState(); + + while (_transactions.Count > 0) + { + _transactions.Pop().Dispose(); + } + } + + /// + public async Task ResetStateAsync(CancellationToken cancellationToken = default) + { + _logger.Logger.LogInformation("Resetting inner state."); + await _innerManager.ResetStateAsync(cancellationToken).ConfigureAwait(false); + + while (_transactions.Count > 0) + { + await _transactions.Pop().DisposeAsync().ConfigureAwait(false); + } + } + + /// + public IDbContextTransaction BeginTransaction() + { + return BeginTransactionInternal(null); + } + + /// + public IDbContextTransaction BeginTransaction(IsolationLevel isolationLevel) + { + return BeginTransactionInternal(isolationLevel); + } + + /// + public Task BeginTransactionAsync(CancellationToken cancellationToken = default) + { + return BeginTransactionInternalAsync(null, cancellationToken); + } + + /// + public Task BeginTransactionAsync(IsolationLevel isolationLevel, CancellationToken cancellationToken = default) + { + return BeginTransactionInternalAsync(isolationLevel, cancellationToken); + } + + private IDbContextTransaction BeginTransactionInternal(IsolationLevel? isolationLevel) + { + var currentTx = CurrentNestedTransaction; + + if (currentTx != null) + { + currentTx = currentTx.BeginTransaction(isolationLevel); + _logger.Logger.LogInformation("Started a child transaction with id '{TransactionId}' using isolation level '{IsolationLevel}'.", currentTx.TransactionId, isolationLevel); + } + else + { + var tx = isolationLevel.HasValue ? _innerManager.BeginTransaction(isolationLevel.Value) : _innerManager.BeginTransaction(); + currentTx = new RootNestedDbContextTransaction(_logger, this, _innerManager, tx, null); + _logger.Logger.LogInformation("Started a root transaction with id '{TransactionId}' using isolation level '{IsolationLevel}'.", currentTx.TransactionId, isolationLevel); + } + + _transactions.Push(currentTx); + + return currentTx; + } + + private async Task BeginTransactionInternalAsync(IsolationLevel? isolationLevel, CancellationToken cancellationToken) + { + var currentTx = CurrentNestedTransaction; + + if (currentTx != null) + { + currentTx = currentTx.BeginTransaction(isolationLevel); + _logger.Logger.LogInformation("Started a child transaction with id '{TransactionId}' using isolation level '{IsolationLevel}'.", currentTx.TransactionId, isolationLevel); + } + else + { + var tx = await (isolationLevel.HasValue + ? _innerManager.BeginTransactionAsync(isolationLevel.Value, cancellationToken) + : _innerManager.BeginTransactionAsync(cancellationToken)) + .ConfigureAwait(false); + currentTx = new RootNestedDbContextTransaction(_logger, this, _innerManager, tx, null); + _logger.Logger.LogInformation("Started a root transaction with id '{TransactionId}' using isolation level '{IsolationLevel}'.", currentTx.TransactionId, isolationLevel); + } + + _transactions.Push(currentTx); + + return currentTx; + } + + /// + public void CommitTransaction() + { + if (_transactions.Count == 0) + throw new InvalidOperationException("The connection does not have any active transactions."); + + _transactions.Pop().Commit(); + } + + /// + public Task CommitTransactionAsync(CancellationToken cancellationToken = default) + { + if (_transactions.Count == 0) + throw new InvalidOperationException("The connection does not have any active transactions."); + + return _transactions.Pop().CommitAsync(cancellationToken); + } + + /// + public void RollbackTransaction() + { + if (_transactions.Count == 0) + throw new InvalidOperationException("The connection does not have any active transactions."); + + _transactions.Pop().Rollback(); + } + + /// + public Task RollbackTransactionAsync(CancellationToken cancellationToken = default) + { + if (_transactions.Count == 0) + throw new InvalidOperationException("The connection does not have any active transactions."); + + return _transactions.Pop().RollbackAsync(cancellationToken); + } + + private void ClearTransactions() + { + _transactions.Clear(); + } + + internal void Remove(NestedDbContextTransaction transaction) + { + if (!_transactions.Contains(transaction)) + return; + + while (_transactions.Count > 0) + { + var tx = _transactions.Pop(); + + if (tx == transaction) + return; + + tx.Dispose(); + } + } + + internal async ValueTask RemoveAsync( + NestedDbContextTransaction transaction) + { + if (!_transactions.Contains(transaction)) + return; + + while (_transactions.Count > 0) + { + var tx = _transactions.Pop(); + + if (tx == transaction) + return; + + await tx.DisposeAsync().ConfigureAwait(false); + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/RootNestedDbContextTransaction.cs b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/RootNestedDbContextTransaction.cs index 3795aa13..8ce57671 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/RootNestedDbContextTransaction.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/EntityFrameworkCore/Storage/RootNestedDbContextTransaction.cs @@ -1,132 +1,132 @@ -using System.Data.Common; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Storage; - -namespace Thinktecture.EntityFrameworkCore.Storage; - -/// -/// A root transaction. -/// -public sealed class RootNestedDbContextTransaction : NestedDbContextTransaction -{ - private readonly IRelationalTransactionManager _innerManager; - private readonly IDbContextTransaction _innerTx; - - /// - protected override string TransactionTypeName => "root transaction"; - - /// - public override bool SupportsSavepoints => _innerTx.SupportsSavepoints; - - /// - /// Initializes new instance of . - /// - /// Logger. - /// Nested transaction manager. - /// Inner transaction manager. - /// The real transaction. - /// The transaction id. - public RootNestedDbContextTransaction( - IDiagnosticsLogger logger, - NestedRelationalTransactionManager nestedTransactionManager, - IRelationalTransactionManager innerManager, - IDbContextTransaction tx, - Guid? transactionId) - : base(logger, nestedTransactionManager, transactionId ?? tx?.TransactionId ?? throw new ArgumentNullException(nameof(tx))) - { - _innerManager = innerManager ?? throw new ArgumentNullException(nameof(innerManager)); - _innerTx = tx; - } - - /// - protected internal override DbTransaction GetUnderlyingTransaction() - { - return _innerTx.GetDbTransaction(); - } - - /// - public override void Commit() - { - base.Commit(); - - _innerManager.CommitTransaction(); - } - - /// - public override async Task CommitAsync(CancellationToken cancellationToken = default) - { - await base.CommitAsync(cancellationToken).ConfigureAwait(false); - - await _innerManager.CommitTransactionAsync(cancellationToken).ConfigureAwait(false); - } - - /// - public override void Rollback() - { - base.Rollback(); - - _innerManager.RollbackTransaction(); - } - - /// - public override async Task RollbackAsync(CancellationToken cancellationToken = default) - { - await base.RollbackAsync(cancellationToken).ConfigureAwait(false); - - await _innerManager.RollbackTransactionAsync(cancellationToken).ConfigureAwait(false); - } - - /// - public override void CreateSavepoint(string name) - { - _innerTx.CreateSavepoint(name); - } - - /// - public override Task CreateSavepointAsync(string name, CancellationToken cancellationToken = default) - { - return _innerTx.CreateSavepointAsync(name, cancellationToken); - } - - /// - public override void RollbackToSavepoint(string name) - { - _innerTx.RollbackToSavepoint(name); - } - - /// - public override Task RollbackToSavepointAsync(string name, CancellationToken cancellationToken = default) - { - return _innerTx.RollbackToSavepointAsync(name, cancellationToken); - } - - /// - public override void ReleaseSavepoint(string name) - { - _innerTx.ReleaseSavepoint(name); - } - - /// - public override Task ReleaseSavepointAsync(string name, CancellationToken cancellationToken = default) - { - return _innerTx.ReleaseSavepointAsync(name, cancellationToken); - } - - /// - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if (disposing) - _innerTx.Dispose(); - } - - /// - protected override async ValueTask DisposeAsync(bool disposing) - { - await base.DisposeAsync(disposing).ConfigureAwait(false); - - if (disposing) - await _innerTx.DisposeAsync().ConfigureAwait(false); - } -} +using System.Data.Common; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Thinktecture.EntityFrameworkCore.Storage; + +/// +/// A root transaction. +/// +public sealed class RootNestedDbContextTransaction : NestedDbContextTransaction +{ + private readonly IRelationalTransactionManager _innerManager; + private readonly IDbContextTransaction _innerTx; + + /// + protected override string TransactionTypeName => "root transaction"; + + /// + public override bool SupportsSavepoints => _innerTx.SupportsSavepoints; + + /// + /// Initializes new instance of . + /// + /// Logger. + /// Nested transaction manager. + /// Inner transaction manager. + /// The real transaction. + /// The transaction id. + public RootNestedDbContextTransaction( + IDiagnosticsLogger logger, + NestedRelationalTransactionManager nestedTransactionManager, + IRelationalTransactionManager innerManager, + IDbContextTransaction tx, + Guid? transactionId) + : base(logger, nestedTransactionManager, transactionId ?? tx?.TransactionId ?? throw new ArgumentNullException(nameof(tx))) + { + _innerManager = innerManager ?? throw new ArgumentNullException(nameof(innerManager)); + _innerTx = tx; + } + + /// + protected internal override DbTransaction GetUnderlyingTransaction() + { + return _innerTx.GetDbTransaction(); + } + + /// + public override void Commit() + { + base.Commit(); + + _innerManager.CommitTransaction(); + } + + /// + public override async Task CommitAsync(CancellationToken cancellationToken = default) + { + await base.CommitAsync(cancellationToken).ConfigureAwait(false); + + await _innerManager.CommitTransactionAsync(cancellationToken).ConfigureAwait(false); + } + + /// + public override void Rollback() + { + base.Rollback(); + + _innerManager.RollbackTransaction(); + } + + /// + public override async Task RollbackAsync(CancellationToken cancellationToken = default) + { + await base.RollbackAsync(cancellationToken).ConfigureAwait(false); + + await _innerManager.RollbackTransactionAsync(cancellationToken).ConfigureAwait(false); + } + + /// + public override void CreateSavepoint(string name) + { + _innerTx.CreateSavepoint(name); + } + + /// + public override Task CreateSavepointAsync(string name, CancellationToken cancellationToken = default) + { + return _innerTx.CreateSavepointAsync(name, cancellationToken); + } + + /// + public override void RollbackToSavepoint(string name) + { + _innerTx.RollbackToSavepoint(name); + } + + /// + public override Task RollbackToSavepointAsync(string name, CancellationToken cancellationToken = default) + { + return _innerTx.RollbackToSavepointAsync(name, cancellationToken); + } + + /// + public override void ReleaseSavepoint(string name) + { + _innerTx.ReleaseSavepoint(name); + } + + /// + public override Task ReleaseSavepointAsync(string name, CancellationToken cancellationToken = default) + { + return _innerTx.ReleaseSavepointAsync(name, cancellationToken); + } + + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + _innerTx.Dispose(); + } + + /// + protected override async ValueTask DisposeAsync(bool disposing) + { + await base.DisposeAsync(disposing).ConfigureAwait(false); + + if (disposing) + await _innerTx.DisposeAsync().ConfigureAwait(false); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/PropertyWithNavigationsExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/PropertyWithNavigationsExtensions.cs index 241d8476..65a029d2 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/PropertyWithNavigationsExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/PropertyWithNavigationsExtensions.cs @@ -1,47 +1,47 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture; - -/// -/// Extension methods for . -/// -public static class PropertyWithNavigationsExtensions -{ - /// - /// Gets the for . - /// - /// Property to get for. - /// The . - /// If no found. - public static StoreObjectIdentifier GetStoreObject(this PropertyWithNavigations property) - { - return StoreObjectIdentifier.Create(property.Property.DeclaringType, StoreObjectType.Table) - ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.Property.DeclaringType.Name}'."); - } - - /// - /// Gets the for . - /// - /// Property to get for. - /// The . - /// If no found. - public static StoreObjectIdentifier GetStoreObject(this IProperty property) - { - return StoreObjectIdentifier.Create(property.DeclaringType, StoreObjectType.Table) - ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.DeclaringType.Name}'."); - } - - /// - /// Gets the column name for . - /// - /// Property to get column name for. - /// Store object. - /// Column name - /// If no column name found. - public static string GetColumnName(this PropertyWithNavigations property, StoreObjectIdentifier storeObject) - { - return property.Property.GetColumnName(storeObject) - ?? throw new Exception($"The property '{property.Property.Name}' has no column name."); - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture; + +/// +/// Extension methods for . +/// +public static class PropertyWithNavigationsExtensions +{ + /// + /// Gets the for . + /// + /// Property to get for. + /// The . + /// If no found. + public static StoreObjectIdentifier GetStoreObject(this PropertyWithNavigations property) + { + return StoreObjectIdentifier.Create(property.Property.DeclaringType, StoreObjectType.Table) + ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.Property.DeclaringType.Name}'."); + } + + /// + /// Gets the for . + /// + /// Property to get for. + /// The . + /// If no found. + public static StoreObjectIdentifier GetStoreObject(this IProperty property) + { + return StoreObjectIdentifier.Create(property.DeclaringType, StoreObjectType.Table) + ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.DeclaringType.Name}'."); + } + + /// + /// Gets the column name for . + /// + /// Property to get column name for. + /// Store object. + /// Column name + /// If no column name found. + public static string GetColumnName(this PropertyWithNavigations property, StoreObjectIdentifier storeObject) + { + return property.Property.GetColumnName(storeObject) + ?? throw new Exception($"The property '{property.Property.Name}' has no column name."); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalCollectionExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalCollectionExtensions.cs index ccedc7a2..7c5c6857 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalCollectionExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalCollectionExtensions.cs @@ -1,22 +1,22 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture; - -/// -/// Extensions for collections. -/// -public static class RelationalCollectionExtensions -{ - /// - /// Computes the hashcode for navigations. - /// - /// Navigations to compute hashcode for. - /// Instance of to use. - public static void ComputeHashCode(this IEnumerable navigations, HashCode hashCode) - { - foreach (var navigation in navigations) - { - hashCode.Add(navigation); - } - } -} +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture; + +/// +/// Extensions for collections. +/// +public static class RelationalCollectionExtensions +{ + /// + /// Computes the hashcode for navigations. + /// + /// Navigations to compute hashcode for. + /// Instance of to use. + public static void ComputeHashCode(this IEnumerable navigations, HashCode hashCode) + { + foreach (var navigation in navigations) + { + hashCode.Add(navigation); + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalDbContextOptionsBuilderExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalDbContextOptionsBuilderExtensions.cs index acf4cb49..3f22b23f 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalDbContextOptionsBuilderExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalDbContextOptionsBuilderExtensions.cs @@ -1,259 +1,259 @@ -using System.Text; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Storage; -using Thinktecture.EntityFrameworkCore.Infrastructure; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extensions for . -/// -public static class RelationalDbContextOptionsBuilderExtensions -{ - /// - /// Add an to dependency injection. - /// - /// Options builder. - /// Type of the context. - /// Type of the plugin implementing . - /// Options builder for chaining. - public static DbContextOptionsBuilder AddRelationalTypeMappingSourcePlugin(this DbContextOptionsBuilder builder) - where TContext : DbContext - where TPlugin : IRelationalTypeMappingSourcePlugin - { - // ReSharper disable once RedundantCast - ((DbContextOptionsBuilder)builder).AddRelationalTypeMappingSourcePlugin(); - return builder; - } - - /// - /// Add an to dependency injection. - /// - /// Options builder. - /// Type of the plugin implementing . - /// Options builder for chaining. - public static DbContextOptionsBuilder AddRelationalTypeMappingSourcePlugin(this DbContextOptionsBuilder builder) - where TPlugin : IRelationalTypeMappingSourcePlugin - { - builder.AddOrUpdateExtension(extension => extension.AddRelationalTypeMappingSourcePlugin(typeof(TPlugin))); - return builder; - } - - /// - /// Adds/replaces components that respect with database schema changes at runtime. - /// - /// Options builder. - /// Indication whether to enable or disable the feature. - /// The provided . - /// is null. - public static DbContextOptionsBuilder AddSchemaRespectingComponents( - this DbContextOptionsBuilder builder, - bool addDefaultSchemaRespectingComponents = true) - where T : DbContext - { - ((DbContextOptionsBuilder)builder).AddSchemaRespectingComponents(addDefaultSchemaRespectingComponents); - return builder; - } - - /// - /// Adds/replaces components that respect database schema changes at runtime. - /// - /// Options builder. - /// Indication whether to enable or disable the feature. - /// The provided . - /// is null. - public static DbContextOptionsBuilder AddSchemaRespectingComponents( - this DbContextOptionsBuilder builder, - bool addDefaultSchemaRespectingComponents = true) - { - builder.AddOrUpdateExtension(extension => - { - extension.AddSchemaRespectingComponents = addDefaultSchemaRespectingComponents; - return extension; - }); - return builder; - } - - /// - /// Adds an . - /// - /// Options builder. - /// Type of the plugin. - /// Type of the plugin. - /// The provided . - /// is null. - public static DbContextOptionsBuilder AddEvaluatableExpressionFilterPlugin(this DbContextOptionsBuilder builder) - where T : DbContext - where TPlugin : IEvaluatableExpressionFilterPlugin - { - builder.AddEvaluatableExpressionFilterPlugin(); - return builder; - } - - /// - /// Adds an . - /// - /// Options builder. - /// Type of the plugin. - /// The provided . - /// is null. - public static DbContextOptionsBuilder AddEvaluatableExpressionFilterPlugin(this DbContextOptionsBuilder builder) - where T : IEvaluatableExpressionFilterPlugin - { - builder.AddOrUpdateExtension(extension => extension.AddEvaluatableExpressionFilterPlugin()); - return builder; - } - - /// - /// Adds support for nested transactions. - /// - /// Options builder. - /// Indication whether to enable or disable the feature. - /// The provided . - /// is null. - public static DbContextOptionsBuilder AddNestedTransactionSupport( - this DbContextOptionsBuilder builder, - bool addNestedTransactionsSupport = true) - where T : DbContext - { - ((DbContextOptionsBuilder)builder).AddNestedTransactionSupport(addNestedTransactionsSupport); - return builder; - } - - /// - /// Adds support for nested transactions. - /// - /// Options builder. - /// Indication whether to enable or disable the feature. - /// The provided . - /// is null. - public static DbContextOptionsBuilder AddNestedTransactionSupport( - this DbContextOptionsBuilder builder, - bool addNestedTransactionsSupport = true) - { - builder.AddOrUpdateExtension(extension => - { - extension.AddNestedTransactionsSupport = addNestedTransactionsSupport; - return extension; - }); - return builder; - } - - /// - /// Configures the string builder pool. - /// - /// Options builder. - /// Initial capacity of a new . - /// Instances of with greater capacity are not reused. - /// The provided . - public static DbContextOptionsBuilder ConfigureStringBuilderPool( - this DbContextOptionsBuilder builder, - int initialCapacity = 300, - int maximumRetainedCapacity = 4096) - where T : DbContext - { - ((DbContextOptionsBuilder)builder).ConfigureStringBuilderPool(initialCapacity, maximumRetainedCapacity); - return builder; - } - - /// - /// Configures the string builder pool. - /// - /// Options builder. - /// Initial capacity of a new . - /// Instances of with greater capacity are not reused. - /// The provided . - public static DbContextOptionsBuilder ConfigureStringBuilderPool( - this DbContextOptionsBuilder builder, - int initialCapacity = 300, - int maximumRetainedCapacity = 4096) - { - builder.AddOrUpdateExtension(extension => extension.ConfigureStringBuilderPool(initialCapacity, maximumRetainedCapacity)); - return builder; - } - - /// - /// Adds an extension of type if it is not added yet. - /// - /// Options builder. - /// Type of the extension. - /// is null. - public static TExtension TryAddExtension(this DbContextOptionsBuilder optionsBuilder) - where TExtension : class, IDbContextOptionsExtension, new() - { - ArgumentNullException.ThrowIfNull(optionsBuilder); - - var extension = optionsBuilder.Options.FindExtension() ?? new TExtension(); - var builder = (IDbContextOptionsBuilderInfrastructure)optionsBuilder; - builder.AddOrUpdateExtension(extension); - - return extension; - } - - /// - /// Adds or updates an extension of type . - /// - /// Options builder. - /// Callback that updates the extension. - /// Indication whether to check whether a database provider is registered already. - /// Type of the extension. - /// - /// is null - /// - or - /// is null. - /// - /// - /// If is true and the database is not registered yet. - /// - public static TExtension AddOrUpdateExtension( - this DbContextOptionsBuilder optionsBuilder, - Func callback, - bool ensureDatabaseProviderRegistered = true) - where TExtension : class, IDbContextOptionsExtension, new() - { - return AddOrUpdateExtension(optionsBuilder, callback, () => new TExtension(), ensureDatabaseProviderRegistered); - } - - /// - /// Adds or updates an extension of type . - /// - /// Options builder. - /// Callback that updates the extension. - /// Factory for creation of new instances of . - /// Indication whether to check whether a database provider is registered already. - /// Type of the extension. - /// - /// is null - /// - or - /// is null. - /// - /// - /// If is true and the database is not registered yet. - /// - public static TExtension AddOrUpdateExtension( - this DbContextOptionsBuilder optionsBuilder, - Func callback, - Func extensionFactory, - bool ensureDatabaseProviderRegistered = true - ) - where TExtension : class, IDbContextOptionsExtension - { - ArgumentNullException.ThrowIfNull(optionsBuilder); - ArgumentNullException.ThrowIfNull(callback); - ArgumentNullException.ThrowIfNull(extensionFactory); - - if (ensureDatabaseProviderRegistered && !optionsBuilder.Options.Extensions.Any(e => e.Info.IsDatabaseProvider)) - throw new InvalidOperationException("Please register the database provider first (via 'UseSqlServer' or 'UseSqlite' etc)."); - - var extension = optionsBuilder.Options.FindExtension() ?? extensionFactory(); - - extension = callback(extension); - - var builder = (IDbContextOptionsBuilderInfrastructure)optionsBuilder; - builder.AddOrUpdateExtension(extension); - - return extension; - } -} +using System.Text; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Storage; +using Thinktecture.EntityFrameworkCore.Infrastructure; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extensions for . +/// +public static class RelationalDbContextOptionsBuilderExtensions +{ + /// + /// Add an to dependency injection. + /// + /// Options builder. + /// Type of the context. + /// Type of the plugin implementing . + /// Options builder for chaining. + public static DbContextOptionsBuilder AddRelationalTypeMappingSourcePlugin(this DbContextOptionsBuilder builder) + where TContext : DbContext + where TPlugin : IRelationalTypeMappingSourcePlugin + { + // ReSharper disable once RedundantCast + ((DbContextOptionsBuilder)builder).AddRelationalTypeMappingSourcePlugin(); + return builder; + } + + /// + /// Add an to dependency injection. + /// + /// Options builder. + /// Type of the plugin implementing . + /// Options builder for chaining. + public static DbContextOptionsBuilder AddRelationalTypeMappingSourcePlugin(this DbContextOptionsBuilder builder) + where TPlugin : IRelationalTypeMappingSourcePlugin + { + builder.AddOrUpdateExtension(extension => extension.AddRelationalTypeMappingSourcePlugin(typeof(TPlugin))); + return builder; + } + + /// + /// Adds/replaces components that respect with database schema changes at runtime. + /// + /// Options builder. + /// Indication whether to enable or disable the feature. + /// The provided . + /// is null. + public static DbContextOptionsBuilder AddSchemaRespectingComponents( + this DbContextOptionsBuilder builder, + bool addDefaultSchemaRespectingComponents = true) + where T : DbContext + { + ((DbContextOptionsBuilder)builder).AddSchemaRespectingComponents(addDefaultSchemaRespectingComponents); + return builder; + } + + /// + /// Adds/replaces components that respect database schema changes at runtime. + /// + /// Options builder. + /// Indication whether to enable or disable the feature. + /// The provided . + /// is null. + public static DbContextOptionsBuilder AddSchemaRespectingComponents( + this DbContextOptionsBuilder builder, + bool addDefaultSchemaRespectingComponents = true) + { + builder.AddOrUpdateExtension(extension => + { + extension.AddSchemaRespectingComponents = addDefaultSchemaRespectingComponents; + return extension; + }); + return builder; + } + + /// + /// Adds an . + /// + /// Options builder. + /// Type of the plugin. + /// Type of the plugin. + /// The provided . + /// is null. + public static DbContextOptionsBuilder AddEvaluatableExpressionFilterPlugin(this DbContextOptionsBuilder builder) + where T : DbContext + where TPlugin : IEvaluatableExpressionFilterPlugin + { + builder.AddEvaluatableExpressionFilterPlugin(); + return builder; + } + + /// + /// Adds an . + /// + /// Options builder. + /// Type of the plugin. + /// The provided . + /// is null. + public static DbContextOptionsBuilder AddEvaluatableExpressionFilterPlugin(this DbContextOptionsBuilder builder) + where T : IEvaluatableExpressionFilterPlugin + { + builder.AddOrUpdateExtension(extension => extension.AddEvaluatableExpressionFilterPlugin()); + return builder; + } + + /// + /// Adds support for nested transactions. + /// + /// Options builder. + /// Indication whether to enable or disable the feature. + /// The provided . + /// is null. + public static DbContextOptionsBuilder AddNestedTransactionSupport( + this DbContextOptionsBuilder builder, + bool addNestedTransactionsSupport = true) + where T : DbContext + { + ((DbContextOptionsBuilder)builder).AddNestedTransactionSupport(addNestedTransactionsSupport); + return builder; + } + + /// + /// Adds support for nested transactions. + /// + /// Options builder. + /// Indication whether to enable or disable the feature. + /// The provided . + /// is null. + public static DbContextOptionsBuilder AddNestedTransactionSupport( + this DbContextOptionsBuilder builder, + bool addNestedTransactionsSupport = true) + { + builder.AddOrUpdateExtension(extension => + { + extension.AddNestedTransactionsSupport = addNestedTransactionsSupport; + return extension; + }); + return builder; + } + + /// + /// Configures the string builder pool. + /// + /// Options builder. + /// Initial capacity of a new . + /// Instances of with greater capacity are not reused. + /// The provided . + public static DbContextOptionsBuilder ConfigureStringBuilderPool( + this DbContextOptionsBuilder builder, + int initialCapacity = 300, + int maximumRetainedCapacity = 4096) + where T : DbContext + { + ((DbContextOptionsBuilder)builder).ConfigureStringBuilderPool(initialCapacity, maximumRetainedCapacity); + return builder; + } + + /// + /// Configures the string builder pool. + /// + /// Options builder. + /// Initial capacity of a new . + /// Instances of with greater capacity are not reused. + /// The provided . + public static DbContextOptionsBuilder ConfigureStringBuilderPool( + this DbContextOptionsBuilder builder, + int initialCapacity = 300, + int maximumRetainedCapacity = 4096) + { + builder.AddOrUpdateExtension(extension => extension.ConfigureStringBuilderPool(initialCapacity, maximumRetainedCapacity)); + return builder; + } + + /// + /// Adds an extension of type if it is not added yet. + /// + /// Options builder. + /// Type of the extension. + /// is null. + public static TExtension TryAddExtension(this DbContextOptionsBuilder optionsBuilder) + where TExtension : class, IDbContextOptionsExtension, new() + { + ArgumentNullException.ThrowIfNull(optionsBuilder); + + var extension = optionsBuilder.Options.FindExtension() ?? new TExtension(); + var builder = (IDbContextOptionsBuilderInfrastructure)optionsBuilder; + builder.AddOrUpdateExtension(extension); + + return extension; + } + + /// + /// Adds or updates an extension of type . + /// + /// Options builder. + /// Callback that updates the extension. + /// Indication whether to check whether a database provider is registered already. + /// Type of the extension. + /// + /// is null + /// - or + /// is null. + /// + /// + /// If is true and the database is not registered yet. + /// + public static TExtension AddOrUpdateExtension( + this DbContextOptionsBuilder optionsBuilder, + Func callback, + bool ensureDatabaseProviderRegistered = true) + where TExtension : class, IDbContextOptionsExtension, new() + { + return AddOrUpdateExtension(optionsBuilder, callback, () => new TExtension(), ensureDatabaseProviderRegistered); + } + + /// + /// Adds or updates an extension of type . + /// + /// Options builder. + /// Callback that updates the extension. + /// Factory for creation of new instances of . + /// Indication whether to check whether a database provider is registered already. + /// Type of the extension. + /// + /// is null + /// - or + /// is null. + /// + /// + /// If is true and the database is not registered yet. + /// + public static TExtension AddOrUpdateExtension( + this DbContextOptionsBuilder optionsBuilder, + Func callback, + Func extensionFactory, + bool ensureDatabaseProviderRegistered = true + ) + where TExtension : class, IDbContextOptionsExtension + { + ArgumentNullException.ThrowIfNull(optionsBuilder); + ArgumentNullException.ThrowIfNull(callback); + ArgumentNullException.ThrowIfNull(extensionFactory); + + if (ensureDatabaseProviderRegistered && !optionsBuilder.Options.Extensions.Any(e => e.Info.IsDatabaseProvider)) + throw new InvalidOperationException("Please register the database provider first (via 'UseSqlServer' or 'UseSqlite' etc)."); + + var extension = optionsBuilder.Options.FindExtension() ?? extensionFactory(); + + extension = callback(extension); + + var builder = (IDbContextOptionsBuilderInfrastructure)optionsBuilder; + builder.AddOrUpdateExtension(extension); + + return extension; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs index 67e9d529..109377d3 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs @@ -1,327 +1,327 @@ -using Thinktecture.EntityFrameworkCore; - -namespace Thinktecture; - -/// -/// Extension methods for . -/// -public static class RelationalDbFunctionsExtensions -{ - // ReSharper disable UnusedParameter.Global - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// An instance of . - /// A column or an object containing columns to order by. - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, T10 partitionByColumn10, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, T10 partitionByColumn10, T11 partitionByColumn11, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, T10 partitionByColumn10, T11 partitionByColumn11, T12 partitionByColumn12, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, T10 partitionByColumn10, T11 partitionByColumn11, T12 partitionByColumn12, T13 partitionByColumn13, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, T10 partitionByColumn10, T11 partitionByColumn11, T12 partitionByColumn12, T13 partitionByColumn13, T14 partitionByColumn14, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, T10 partitionByColumn10, T11 partitionByColumn11, T12 partitionByColumn12, T13 partitionByColumn13, T14 partitionByColumn14, T15 partitionByColumn15, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ROW_NUMBER. - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// - /// Is thrown if executed in-memory. - public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, T10 partitionByColumn10, T11 partitionByColumn11, T12 partitionByColumn12, T13 partitionByColumn13, T14 partitionByColumn14, T15 partitionByColumn15, T16 partitionByColumn16, WindowFunctionOrderByClause orderBy) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ORDER BY clause of a the window function. - /// - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// Is thrown if executed in-memory. - public static WindowFunctionOrderByClause OrderBy(this DbFunctions _, T column) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ORDER BY clause of a the window function. - /// - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// Is thrown if executed in-memory. - public static WindowFunctionOrderByClause OrderByDescending(this DbFunctions _, T column) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ORDER BY clause of a the window function. - /// - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// Is thrown if executed in-memory. - public static WindowFunctionOrderByClause ThenBy(this WindowFunctionOrderByClause clause, T column) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the ORDER BY clause of a the window function. - /// - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// Is thrown if executed in-memory. - public static WindowFunctionOrderByClause ThenByDescending(this WindowFunctionOrderByClause clause, T column) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the PARTITION BY clause of a window function. - /// - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// Is thrown if executed in-memory. - public static WindowFunctionPartitionByClause PartitionBy(this DbFunctions _, T1 column1) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the PARTITION BY clause of a window function. - /// - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// Is thrown if executed in-memory. - public static WindowFunctionPartitionByClause PartitionBy(this DbFunctions _, T1 column1, T2 column2) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the PARTITION BY clause of a window function. - /// - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// Is thrown if executed in-memory. - public static WindowFunctionPartitionByClause PartitionBy(this DbFunctions _, T1 column1, T2 column2, T3 column3) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the PARTITION BY clause of a window function. - /// - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// Is thrown if executed in-memory. - public static WindowFunctionPartitionByClause PartitionBy(this DbFunctions _, T1 column1, T2 column2, T3 column3, T4 column4) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - /// - /// Definition of the PARTITION BY clause of a window function. - /// - /// - /// This method is for use with Entity Framework Core only and has no in-memory implementation. - /// - /// Is thrown if executed in-memory. - public static WindowFunctionPartitionByClause PartitionBy(this DbFunctions _, T1 column1, T2 column2, T3 column3, T4 column4, T5 column5) - { - throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); - } - - // ReSharper restore UnusedParameter.Global -} +using Thinktecture.EntityFrameworkCore; + +namespace Thinktecture; + +/// +/// Extension methods for . +/// +public static class RelationalDbFunctionsExtensions +{ + // ReSharper disable UnusedParameter.Global + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// An instance of . + /// A column or an object containing columns to order by. + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, T10 partitionByColumn10, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, T10 partitionByColumn10, T11 partitionByColumn11, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, T10 partitionByColumn10, T11 partitionByColumn11, T12 partitionByColumn12, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, T10 partitionByColumn10, T11 partitionByColumn11, T12 partitionByColumn12, T13 partitionByColumn13, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, T10 partitionByColumn10, T11 partitionByColumn11, T12 partitionByColumn12, T13 partitionByColumn13, T14 partitionByColumn14, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, T10 partitionByColumn10, T11 partitionByColumn11, T12 partitionByColumn12, T13 partitionByColumn13, T14 partitionByColumn14, T15 partitionByColumn15, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ROW_NUMBER. + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// + /// Is thrown if executed in-memory. + public static long RowNumber(this DbFunctions _, T1 partitionByColumn1, T2 partitionByColumn2, T3 partitionByColumn3, T4 partitionByColumn4, T5 partitionByColumn5, T6 partitionByColumn6, T7 partitionByColumn7, T8 partitionByColumn8, T9 partitionByColumn9, T10 partitionByColumn10, T11 partitionByColumn11, T12 partitionByColumn12, T13 partitionByColumn13, T14 partitionByColumn14, T15 partitionByColumn15, T16 partitionByColumn16, WindowFunctionOrderByClause orderBy) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ORDER BY clause of a the window function. + /// + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// Is thrown if executed in-memory. + public static WindowFunctionOrderByClause OrderBy(this DbFunctions _, T column) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ORDER BY clause of a the window function. + /// + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// Is thrown if executed in-memory. + public static WindowFunctionOrderByClause OrderByDescending(this DbFunctions _, T column) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ORDER BY clause of a the window function. + /// + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// Is thrown if executed in-memory. + public static WindowFunctionOrderByClause ThenBy(this WindowFunctionOrderByClause clause, T column) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the ORDER BY clause of a the window function. + /// + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// Is thrown if executed in-memory. + public static WindowFunctionOrderByClause ThenByDescending(this WindowFunctionOrderByClause clause, T column) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the PARTITION BY clause of a window function. + /// + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// Is thrown if executed in-memory. + public static WindowFunctionPartitionByClause PartitionBy(this DbFunctions _, T1 column1) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the PARTITION BY clause of a window function. + /// + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// Is thrown if executed in-memory. + public static WindowFunctionPartitionByClause PartitionBy(this DbFunctions _, T1 column1, T2 column2) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the PARTITION BY clause of a window function. + /// + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// Is thrown if executed in-memory. + public static WindowFunctionPartitionByClause PartitionBy(this DbFunctions _, T1 column1, T2 column2, T3 column3) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the PARTITION BY clause of a window function. + /// + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// Is thrown if executed in-memory. + public static WindowFunctionPartitionByClause PartitionBy(this DbFunctions _, T1 column1, T2 column2, T3 column3, T4 column4) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + /// + /// Definition of the PARTITION BY clause of a window function. + /// + /// + /// This method is for use with Entity Framework Core only and has no in-memory implementation. + /// + /// Is thrown if executed in-memory. + public static WindowFunctionPartitionByClause PartitionBy(this DbFunctions _, T1 column1, T2 column2, T3 column3, T4 column4, T5 column5) + { + throw new InvalidOperationException("This method is for use with Entity Framework Core only and has no in-memory implementation."); + } + + // ReSharper restore UnusedParameter.Global +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalEntityDataReaderExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalEntityDataReaderExtensions.cs index c83c828d..7da70ba8 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalEntityDataReaderExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalEntityDataReaderExtensions.cs @@ -1,24 +1,24 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture; - -/// -/// Extensions for . -/// -[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] -public static class RelationalEntityDataReaderExtensions -{ - /// - /// Gets the index of the provided . - /// - /// Entity data reader. - /// Property to get the index for. - /// Index of the property. - public static int GetPropertyIndex(this IEntityDataReader reader, IProperty property) - { - return reader.GetPropertyIndex(new PropertyWithNavigations(property, Array.Empty())); - } -} +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture; + +/// +/// Extensions for . +/// +[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] +public static class RelationalEntityDataReaderExtensions +{ + /// + /// Gets the index of the provided . + /// + /// Entity data reader. + /// Property to get the index for. + /// Index of the property. + public static int GetPropertyIndex(this IEntityDataReader reader, IProperty property) + { + return reader.GetPropertyIndex(new PropertyWithNavigations(property, Array.Empty())); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalExpressionExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalExpressionExtensions.cs index 5422361f..cd3e9f0c 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalExpressionExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalExpressionExtensions.cs @@ -1,24 +1,24 @@ -using System.Linq.Expressions; -using Thinktecture.Linq.Expressions; - -namespace Thinktecture; - -/// -/// Extension methods for expressions. -/// -public static class RelationalExpressionExtensions -{ - /// - /// This method is for use with only! - /// The visitor extracts the body from and rebinds it using the . - /// - /// Lambda expression to extract the body from. - /// New parameter to rebind the one of the with. - /// Type of the argument of the . - /// Return type of the . - /// The method is called directly instead being used with . - public static TOut ExtractBody(this Expression> lambda, TIn parameter) - { - throw new InvalidOperationException($"This method is not intended to be used directly but with the '{nameof(ExpressionBodyExtractingVisitor)}' only."); - } -} +using System.Linq.Expressions; +using Thinktecture.Linq.Expressions; + +namespace Thinktecture; + +/// +/// Extension methods for expressions. +/// +public static class RelationalExpressionExtensions +{ + /// + /// This method is for use with only! + /// The visitor extracts the body from and rebinds it using the . + /// + /// Lambda expression to extract the body from. + /// New parameter to rebind the one of the with. + /// Type of the argument of the . + /// Return type of the . + /// The method is called directly instead being used with . + public static TOut ExtractBody(this Expression> lambda, TIn parameter) + { + throw new InvalidOperationException($"This method is not intended to be used directly but with the '{nameof(ExpressionBodyExtractingVisitor)}' only."); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalExpressionVisitorExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalExpressionVisitorExtensions.cs index c5eb0b27..4ad48cc2 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalExpressionVisitorExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalExpressionVisitorExtensions.cs @@ -1,47 +1,47 @@ -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extension methods for . -/// -public static class RelationalExpressionVisitorExtensions -{ - /// - /// Visits a collection of and returns new collection if it least one expression has been changed. - /// Otherwise the provided are returned if there are no changes. - /// - /// Visitor to use. - /// Expressions to visit. - /// - /// New collection with visited expressions if at least one visited expression has been changed; otherwise the provided . - /// - public static IReadOnlyList VisitExpressions(this ExpressionVisitor visitor, IReadOnlyList expressions) - where T : Expression - { - T[]? visitedExpression = null; - - for (var i = 0; i < expressions.Count; i++) - { - var expression = expressions[i]; - var visited = (T)visitor.Visit(expression); - - if (visited != expression && visitedExpression is null) - { - visitedExpression = new T[expressions.Count]; - - for (var j = 0; j < i; j++) - { - visitedExpression[j] = expressions[j]; - } - } - - if (visitedExpression is not null) - visitedExpression[i] = visited; - } - - return visitedExpression ?? expressions; - } -} +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extension methods for . +/// +public static class RelationalExpressionVisitorExtensions +{ + /// + /// Visits a collection of and returns new collection if it least one expression has been changed. + /// Otherwise the provided are returned if there are no changes. + /// + /// Visitor to use. + /// Expressions to visit. + /// + /// New collection with visited expressions if at least one visited expression has been changed; otherwise the provided . + /// + public static IReadOnlyList VisitExpressions(this ExpressionVisitor visitor, IReadOnlyList expressions) + where T : Expression + { + T[]? visitedExpression = null; + + for (var i = 0; i < expressions.Count; i++) + { + var expression = expressions[i]; + var visited = (T)visitor.Visit(expression); + + if (visited != expression && visitedExpression is null) + { + visitedExpression = new T[expressions.Count]; + + for (var j = 0; j < i; j++) + { + visitedExpression[j] = expressions[j]; + } + } + + if (visitedExpression is not null) + visitedExpression[i] = visited; + } + + return visitedExpression ?? expressions; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalModelExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalModelExtensions.cs index 75474a8d..f1b658c5 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalModelExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalModelExtensions.cs @@ -1,67 +1,67 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.EntityFrameworkCore; - -namespace Thinktecture; - -/// -/// Extension methods for . -/// -public static class RelationalModelExtensions -{ - /// - /// Fetches meta data for entity of provided . - /// - /// Model of a database context. - /// Entity type. - /// An instance of type . - /// - /// is null - /// - or - /// is null. - /// - /// The provided type is not known by provided . - public static IEntityType GetEntityType(this IModel model, Type type) - { - ArgumentNullException.ThrowIfNull(model); - ArgumentNullException.ThrowIfNull(type); - - var entityType = model.FindEntityType(type); - - if (entityType == null) - throw new EntityTypeNotFoundException(type); - - return entityType; - } - - /// - /// Fetches meta data for entity of provided . - /// - /// Model of a database context. - /// Entity name. - /// Type of the entity to search for. This type is used only then if the search with the wasn't successful. - /// An instance of type . - /// - /// is null - /// - or - /// is null. - /// - /// The provided type is not known by provided . - public static IEntityType GetEntityType(this IModel model, string name, Type? fallbackEntityType = null) - { - ArgumentNullException.ThrowIfNull(model); - ArgumentNullException.ThrowIfNull(name); - - var entityType = model.FindEntityType(name); - - if (entityType is null && fallbackEntityType is not null) - entityType = model.FindEntityType(fallbackEntityType); - - if (entityType is not null) - return entityType; - - if (fallbackEntityType is not null) - throw new EntityTypeNotFoundException(name, fallbackEntityType); - - throw new EntityTypeNotFoundException(name); - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.EntityFrameworkCore; + +namespace Thinktecture; + +/// +/// Extension methods for . +/// +public static class RelationalModelExtensions +{ + /// + /// Fetches meta data for entity of provided . + /// + /// Model of a database context. + /// Entity type. + /// An instance of type . + /// + /// is null + /// - or + /// is null. + /// + /// The provided type is not known by provided . + public static IEntityType GetEntityType(this IModel model, Type type) + { + ArgumentNullException.ThrowIfNull(model); + ArgumentNullException.ThrowIfNull(type); + + var entityType = model.FindEntityType(type); + + if (entityType == null) + throw new EntityTypeNotFoundException(type); + + return entityType; + } + + /// + /// Fetches meta data for entity of provided . + /// + /// Model of a database context. + /// Entity name. + /// Type of the entity to search for. This type is used only then if the search with the wasn't successful. + /// An instance of type . + /// + /// is null + /// - or + /// is null. + /// + /// The provided type is not known by provided . + public static IEntityType GetEntityType(this IModel model, string name, Type? fallbackEntityType = null) + { + ArgumentNullException.ThrowIfNull(model); + ArgumentNullException.ThrowIfNull(name); + + var entityType = model.FindEntityType(name); + + if (entityType is null && fallbackEntityType is not null) + entityType = model.FindEntityType(fallbackEntityType); + + if (entityType is not null) + return entityType; + + if (fallbackEntityType is not null) + throw new EntityTypeNotFoundException(name, fallbackEntityType); + + throw new EntityTypeNotFoundException(name); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalNavigationExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalNavigationExtensions.cs index 3eea9db4..c5824570 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalNavigationExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalNavigationExtensions.cs @@ -1,30 +1,30 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extension methods for . -/// -public static class RelationalNavigationExtensions -{ - /// - /// Indication whether the owned type is persisted in the same table as the owner. - /// - /// Navigation pointing to the owned type. - /// true if the owned type is persisted in the same table as the owner; otherwise false. - /// - /// is null. - /// - public static bool IsInlined( - this INavigation navigation) - { - ArgumentNullException.ThrowIfNull(navigation); - - var sourceType = navigation.DeclaringEntityType; - var targetType = navigation.TargetEntityType; - - return sourceType.GetSchema() == targetType.GetSchema() && - sourceType.GetTableName() == targetType.GetTableName(); - } -} +using Microsoft.EntityFrameworkCore.Metadata; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extension methods for . +/// +public static class RelationalNavigationExtensions +{ + /// + /// Indication whether the owned type is persisted in the same table as the owner. + /// + /// Navigation pointing to the owned type. + /// true if the owned type is persisted in the same table as the owner; otherwise false. + /// + /// is null. + /// + public static bool IsInlined( + this INavigation navigation) + { + ArgumentNullException.ThrowIfNull(navigation); + + var sourceType = navigation.DeclaringEntityType; + var targetType = navigation.TargetEntityType; + + return sourceType.GetSchema() == targetType.GetSchema() && + sourceType.GetTableName() == targetType.GetTableName(); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalQueryableExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalQueryableExtensions.cs index 6683c1ff..36c950af 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalQueryableExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalQueryableExtensions.cs @@ -1,282 +1,282 @@ -using System.Linq.Expressions; -using System.Reflection; -using Thinktecture.EntityFrameworkCore; -using Thinktecture.Internal; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extensions for . -/// -public static class RelationalQueryableExtensions -{ - private static readonly MethodInfo _asSubQuery = typeof(RelationalQueryableExtensions).GetMethods(BindingFlags.Public | BindingFlags.Static) - .Single(m => m.Name == nameof(AsSubQuery) && m.IsGenericMethod); - - private static readonly MethodInfo _withTableHints = typeof(RelationalQueryableExtensions).GetMethods(BindingFlags.Public | BindingFlags.Static) - .Single(m => m.Name == nameof(WithTableHints) - && m.IsGenericMethod - && m.GetParameters()[1].ParameterType == typeof(IReadOnlyList)); - - /// - /// Adds table hints to a table specified in . - /// - /// Query using a table to apply table hints to. - /// Table hints. - /// Entity type. - /// Query with table hints applied. - public static IQueryable WithTableHints(this IQueryable source, params ITableHint[] hints) - { - return source.WithTableHints((IReadOnlyList)hints); - } - - /// - /// Adds table hints to a table specified in . - /// - /// Query using a table to apply table hints to. - /// Table hints. - /// Entity type. - /// Query with table hints applied. - public static IQueryable WithTableHints(this IQueryable source, IReadOnlyList hints) - { - ArgumentNullException.ThrowIfNull(source); - ArgumentNullException.ThrowIfNull(hints); - - var methodInfo = _withTableHints.MakeGenericMethod(typeof(T)); - var expression = Expression.Call(null, methodInfo, source.Expression, new TableHintsExpression(hints)); - return source.Provider.CreateQuery(expression); - } - - /// - /// Performs a LEFT JOIN. - /// - /// Left side query. - /// Right side query. - /// JOIN key selector for the entity on the left. - /// JOIN key selector for the entity on the right. - /// Type of the entity on the left side. - /// Type of the entity on the right side. - /// Type of the entity on the right side. - /// Type of the entity on the right side. - /// Type of the entity on the right side. - /// Type of the entity on the right side. - /// Type of the JOIN key. - /// An with item type . - /// - /// is null - /// - or is null - /// - or is null - /// - or is null - /// - public static IQueryable> LeftJoin( - this IQueryable> left, - IEnumerable right, - Expression, TKey>> leftKeySelector, - Expression> rightKeySelector) - where TLeft : notnull - { - return LeftJoin(left, right, leftKeySelector, rightKeySelector, - r => new LeftJoinResult - { - Left = r.Left.Left, - Right = r.Left.Right, - Right2 = r.Left.Right2, - Right3 = r.Left.Right3, - Right4 = r.Left.Right4, - Right5 = r.Right - }); - } - - /// - /// Performs a LEFT JOIN. - /// - /// Left side query. - /// Right side query. - /// JOIN key selector for the entity on the left. - /// JOIN key selector for the entity on the right. - /// Type of the entity on the left side. - /// Type of the entity on the right side. - /// Type of the entity on the right side. - /// Type of the entity on the right side. - /// Type of the entity on the right side. - /// Type of the JOIN key. - /// An with item type . - /// - /// is null - /// - or is null - /// - or is null - /// - or is null - /// - public static IQueryable> LeftJoin( - this IQueryable> left, - IEnumerable right, - Expression, TKey>> leftKeySelector, - Expression> rightKeySelector) - where TLeft : notnull - { - return LeftJoin(left, right, leftKeySelector, rightKeySelector, - r => new LeftJoinResult - { - Left = r.Left.Left, - Right = r.Left.Right, - Right2 = r.Left.Right2, - Right3 = r.Left.Right3, - Right4 = r.Right - }); - } - - /// - /// Performs a LEFT JOIN. - /// - /// Left side query. - /// Right side query. - /// JOIN key selector for the entity on the left. - /// JOIN key selector for the entity on the right. - /// Type of the entity on the left side. - /// Type of the entity on the right side. - /// Type of the entity on the right side. - /// Type of the entity on the right side. - /// Type of the JOIN key. - /// An with item type . - /// - /// is null - /// - or is null - /// - or is null - /// - or is null - /// - public static IQueryable> LeftJoin( - this IQueryable> left, - IEnumerable right, - Expression, TKey>> leftKeySelector, - Expression> rightKeySelector) - where TLeft : notnull - { - return LeftJoin(left, right, leftKeySelector, rightKeySelector, - r => new LeftJoinResult - { - Left = r.Left.Left, - Right = r.Left.Right, - Right2 = r.Left.Right2, - Right3 = r.Right - }); - } - - /// - /// Performs a LEFT JOIN. - /// - /// Left side query. - /// Right side query. - /// JOIN key selector for the entity on the left. - /// JOIN key selector for the entity on the right. - /// Type of the entity on the left side. - /// Type of the entity on the right side. - /// Type of the entity on the right side. - /// Type of the JOIN key. - /// An with item type . - /// - /// is null - /// - or is null - /// - or is null - /// - or is null - /// - public static IQueryable> LeftJoin( - this IQueryable> left, - IEnumerable right, - Expression, TKey>> leftKeySelector, - Expression> rightKeySelector) - where TLeft : notnull - { - return LeftJoin(left, right, leftKeySelector, rightKeySelector, - r => new LeftJoinResult - { - Left = r.Left.Left, - Right = r.Left.Right, - Right2 = r.Right - }); - } - - /// - /// Performs a LEFT JOIN. - /// - /// Left side query. - /// Right side query. - /// JOIN key selector for the entity on the left. - /// JOIN key selector for the entity on the right. - /// Type of the entity on the left side. - /// Type of the entity on the right side. - /// Type of the JOIN key. - /// An with item type . - /// - /// is null - /// - or is null - /// - or is null - /// - or is null - /// - public static IQueryable> LeftJoin( - this IQueryable left, - IEnumerable right, - Expression> leftKeySelector, - Expression> rightKeySelector) - where TLeft : notnull - { - return LeftJoin(left, right, leftKeySelector, rightKeySelector, r => r); - } - - /// - /// Performs a LEFT JOIN. - /// - /// Left side query. - /// Right side query. - /// JOIN key selector for the entity on the left. - /// JOIN key selector for the entity on the right. - /// - /// Result selector. - /// Please note that the entity can be null when projecting non-nullable structs (int, bool, etc.). - /// - /// Type of the entity on the left side. - /// Type of the entity on the right side. - /// Type of the JOIN key. - /// Type of the result. - /// An with item type . - /// - /// is null - /// - or is null - /// - or is null - /// - or is null - /// - or is null. - /// - public static IQueryable LeftJoin( - this IQueryable left, - IEnumerable right, - Expression> leftKeySelector, - Expression> rightKeySelector, - Expression, TResult>> resultSelector) - where TLeft : notnull - { - ArgumentNullException.ThrowIfNull(left); - ArgumentNullException.ThrowIfNull(right); - ArgumentNullException.ThrowIfNull(leftKeySelector); - ArgumentNullException.ThrowIfNull(rightKeySelector); - ArgumentNullException.ThrowIfNull(resultSelector); - - return left - .GroupJoin(right, leftKeySelector, rightKeySelector, (o, i) => new { Outer = o, Inner = i }) - .SelectMany(g => g.Inner.DefaultIfEmpty(), (o, i) => new LeftJoinResult { Left = o.Outer, Right = i }) - .Select(resultSelector); - } - - /// - /// Executes provided query as a sub query. - /// - /// Query to execute as as sub query. - /// Type of the entity. - /// Query that will be executed as a sub query. - /// is null. - public static IQueryable AsSubQuery(this IQueryable source) - { - ArgumentNullException.ThrowIfNull(source); - - return source.Provider.CreateQuery(Expression.Call(null, _asSubQuery.MakeGenericMethod(typeof(TEntity)), source.Expression)); - } -} +using System.Linq.Expressions; +using System.Reflection; +using Thinktecture.EntityFrameworkCore; +using Thinktecture.Internal; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extensions for . +/// +public static class RelationalQueryableExtensions +{ + private static readonly MethodInfo _asSubQuery = typeof(RelationalQueryableExtensions).GetMethods(BindingFlags.Public | BindingFlags.Static) + .Single(m => m.Name == nameof(AsSubQuery) && m.IsGenericMethod); + + private static readonly MethodInfo _withTableHints = typeof(RelationalQueryableExtensions).GetMethods(BindingFlags.Public | BindingFlags.Static) + .Single(m => m.Name == nameof(WithTableHints) + && m.IsGenericMethod + && m.GetParameters()[1].ParameterType == typeof(IReadOnlyList)); + + /// + /// Adds table hints to a table specified in . + /// + /// Query using a table to apply table hints to. + /// Table hints. + /// Entity type. + /// Query with table hints applied. + public static IQueryable WithTableHints(this IQueryable source, params ITableHint[] hints) + { + return source.WithTableHints((IReadOnlyList)hints); + } + + /// + /// Adds table hints to a table specified in . + /// + /// Query using a table to apply table hints to. + /// Table hints. + /// Entity type. + /// Query with table hints applied. + public static IQueryable WithTableHints(this IQueryable source, IReadOnlyList hints) + { + ArgumentNullException.ThrowIfNull(source); + ArgumentNullException.ThrowIfNull(hints); + + var methodInfo = _withTableHints.MakeGenericMethod(typeof(T)); + var expression = Expression.Call(null, methodInfo, source.Expression, new TableHintsExpression(hints)); + return source.Provider.CreateQuery(expression); + } + + /// + /// Performs a LEFT JOIN. + /// + /// Left side query. + /// Right side query. + /// JOIN key selector for the entity on the left. + /// JOIN key selector for the entity on the right. + /// Type of the entity on the left side. + /// Type of the entity on the right side. + /// Type of the entity on the right side. + /// Type of the entity on the right side. + /// Type of the entity on the right side. + /// Type of the entity on the right side. + /// Type of the JOIN key. + /// An with item type . + /// + /// is null + /// - or is null + /// - or is null + /// - or is null + /// + public static IQueryable> LeftJoin( + this IQueryable> left, + IEnumerable right, + Expression, TKey>> leftKeySelector, + Expression> rightKeySelector) + where TLeft : notnull + { + return LeftJoin(left, right, leftKeySelector, rightKeySelector, + r => new LeftJoinResult + { + Left = r.Left.Left, + Right = r.Left.Right, + Right2 = r.Left.Right2, + Right3 = r.Left.Right3, + Right4 = r.Left.Right4, + Right5 = r.Right + }); + } + + /// + /// Performs a LEFT JOIN. + /// + /// Left side query. + /// Right side query. + /// JOIN key selector for the entity on the left. + /// JOIN key selector for the entity on the right. + /// Type of the entity on the left side. + /// Type of the entity on the right side. + /// Type of the entity on the right side. + /// Type of the entity on the right side. + /// Type of the entity on the right side. + /// Type of the JOIN key. + /// An with item type . + /// + /// is null + /// - or is null + /// - or is null + /// - or is null + /// + public static IQueryable> LeftJoin( + this IQueryable> left, + IEnumerable right, + Expression, TKey>> leftKeySelector, + Expression> rightKeySelector) + where TLeft : notnull + { + return LeftJoin(left, right, leftKeySelector, rightKeySelector, + r => new LeftJoinResult + { + Left = r.Left.Left, + Right = r.Left.Right, + Right2 = r.Left.Right2, + Right3 = r.Left.Right3, + Right4 = r.Right + }); + } + + /// + /// Performs a LEFT JOIN. + /// + /// Left side query. + /// Right side query. + /// JOIN key selector for the entity on the left. + /// JOIN key selector for the entity on the right. + /// Type of the entity on the left side. + /// Type of the entity on the right side. + /// Type of the entity on the right side. + /// Type of the entity on the right side. + /// Type of the JOIN key. + /// An with item type . + /// + /// is null + /// - or is null + /// - or is null + /// - or is null + /// + public static IQueryable> LeftJoin( + this IQueryable> left, + IEnumerable right, + Expression, TKey>> leftKeySelector, + Expression> rightKeySelector) + where TLeft : notnull + { + return LeftJoin(left, right, leftKeySelector, rightKeySelector, + r => new LeftJoinResult + { + Left = r.Left.Left, + Right = r.Left.Right, + Right2 = r.Left.Right2, + Right3 = r.Right + }); + } + + /// + /// Performs a LEFT JOIN. + /// + /// Left side query. + /// Right side query. + /// JOIN key selector for the entity on the left. + /// JOIN key selector for the entity on the right. + /// Type of the entity on the left side. + /// Type of the entity on the right side. + /// Type of the entity on the right side. + /// Type of the JOIN key. + /// An with item type . + /// + /// is null + /// - or is null + /// - or is null + /// - or is null + /// + public static IQueryable> LeftJoin( + this IQueryable> left, + IEnumerable right, + Expression, TKey>> leftKeySelector, + Expression> rightKeySelector) + where TLeft : notnull + { + return LeftJoin(left, right, leftKeySelector, rightKeySelector, + r => new LeftJoinResult + { + Left = r.Left.Left, + Right = r.Left.Right, + Right2 = r.Right + }); + } + + /// + /// Performs a LEFT JOIN. + /// + /// Left side query. + /// Right side query. + /// JOIN key selector for the entity on the left. + /// JOIN key selector for the entity on the right. + /// Type of the entity on the left side. + /// Type of the entity on the right side. + /// Type of the JOIN key. + /// An with item type . + /// + /// is null + /// - or is null + /// - or is null + /// - or is null + /// + public static IQueryable> LeftJoin( + this IQueryable left, + IEnumerable right, + Expression> leftKeySelector, + Expression> rightKeySelector) + where TLeft : notnull + { + return LeftJoin(left, right, leftKeySelector, rightKeySelector, r => r); + } + + /// + /// Performs a LEFT JOIN. + /// + /// Left side query. + /// Right side query. + /// JOIN key selector for the entity on the left. + /// JOIN key selector for the entity on the right. + /// + /// Result selector. + /// Please note that the entity can be null when projecting non-nullable structs (int, bool, etc.). + /// + /// Type of the entity on the left side. + /// Type of the entity on the right side. + /// Type of the JOIN key. + /// Type of the result. + /// An with item type . + /// + /// is null + /// - or is null + /// - or is null + /// - or is null + /// - or is null. + /// + public static IQueryable LeftJoin( + this IQueryable left, + IEnumerable right, + Expression> leftKeySelector, + Expression> rightKeySelector, + Expression, TResult>> resultSelector) + where TLeft : notnull + { + ArgumentNullException.ThrowIfNull(left); + ArgumentNullException.ThrowIfNull(right); + ArgumentNullException.ThrowIfNull(leftKeySelector); + ArgumentNullException.ThrowIfNull(rightKeySelector); + ArgumentNullException.ThrowIfNull(resultSelector); + + return left + .GroupJoin(right, leftKeySelector, rightKeySelector, (o, i) => new { Outer = o, Inner = i }) + .SelectMany(g => g.Inner.DefaultIfEmpty(), (o, i) => new LeftJoinResult { Left = o.Outer, Right = i }) + .Select(resultSelector); + } + + /// + /// Executes provided query as a sub query. + /// + /// Query to execute as as sub query. + /// Type of the entity. + /// Query that will be executed as a sub query. + /// is null. + public static IQueryable AsSubQuery(this IQueryable source) + { + ArgumentNullException.ThrowIfNull(source); + + return source.Provider.CreateQuery(Expression.Call(null, _asSubQuery.MakeGenericMethod(typeof(TEntity)), source.Expression)); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalQueryableMethodTranslatingExpressionVisitorExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalQueryableMethodTranslatingExpressionVisitorExtensions.cs index c864e7e7..75eea126 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalQueryableMethodTranslatingExpressionVisitorExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalQueryableMethodTranslatingExpressionVisitorExtensions.cs @@ -1,86 +1,86 @@ -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; -using Thinktecture.EntityFrameworkCore; -using Thinktecture.EntityFrameworkCore.Internal; -using Thinktecture.Internal; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extensions for . -/// -public static class RelationalQueryableMethodTranslatingExpressionVisitorExtensions -{ - /// - /// Translates custom methods like . - /// - /// The visitor. - /// Method call to translate. - /// Translated method call if a custom method is found; otherwise null. - /// - /// or is null. - /// - public static Expression? TranslateRelationalMethods( - this RelationalQueryableMethodTranslatingExpressionVisitor visitor, - MethodCallExpression methodCallExpression) - { - ArgumentNullException.ThrowIfNull(visitor); - ArgumentNullException.ThrowIfNull(methodCallExpression); - - if (methodCallExpression.Method.DeclaringType == typeof(RelationalQueryableExtensions)) - { - if (methodCallExpression.Method.Name == nameof(RelationalQueryableExtensions.AsSubQuery)) - { - var expression = visitor.Visit(methodCallExpression.Arguments[0]); - - if (expression is ShapedQueryExpression shapedQueryExpression) - { - ((SelectExpression)shapedQueryExpression.QueryExpression).PushdownIntoSubquery(); - return shapedQueryExpression; - } - } - - if (methodCallExpression.Method.Name == nameof(RelationalQueryableExtensions.WithTableHints)) - return TranslateTableHints(GetShapedQueryExpression(visitor, methodCallExpression), methodCallExpression); - } - - return null; - } - - private static Expression TranslateTableHints( - ShapedQueryExpression shapedQueryExpression, - MethodCallExpression methodCallExpression) - { - var hintArgs = methodCallExpression.Arguments[1]; - var tableHints = hintArgs switch - { - TableHintsExpression tableHintsExpression => tableHintsExpression.Value, - ConstantExpression constantExpression => (IReadOnlyList?)constantExpression.Value ?? throw new Exception("No table hints provided."), - _ => throw new NotSupportedException($"Table hint argument of type '{hintArgs.GetType().FullName}' is not supported.") - }; - - if (tableHints.Count == 0) - return shapedQueryExpression; - - var selectExpression = (SelectExpression)shapedQueryExpression.QueryExpression; - var newSelectExpression = selectExpression.AddAnnotation(new Annotation(ThinktectureRelationalAnnotationNames.TABLE_HINTS, tableHints)); - - return shapedQueryExpression.Update(newSelectExpression, shapedQueryExpression.ShaperExpression); - } - - private static ShapedQueryExpression GetShapedQueryExpression( - ExpressionVisitor visitor, - MethodCallExpression methodCallExpression) - { - var source = visitor.Visit(methodCallExpression.Arguments[0]); - - if (source is not ShapedQueryExpression shapedQueryExpression) - throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print())); - - return shapedQueryExpression; - } -} +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Thinktecture.EntityFrameworkCore; +using Thinktecture.EntityFrameworkCore.Internal; +using Thinktecture.Internal; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extensions for . +/// +public static class RelationalQueryableMethodTranslatingExpressionVisitorExtensions +{ + /// + /// Translates custom methods like . + /// + /// The visitor. + /// Method call to translate. + /// Translated method call if a custom method is found; otherwise null. + /// + /// or is null. + /// + public static Expression? TranslateRelationalMethods( + this RelationalQueryableMethodTranslatingExpressionVisitor visitor, + MethodCallExpression methodCallExpression) + { + ArgumentNullException.ThrowIfNull(visitor); + ArgumentNullException.ThrowIfNull(methodCallExpression); + + if (methodCallExpression.Method.DeclaringType == typeof(RelationalQueryableExtensions)) + { + if (methodCallExpression.Method.Name == nameof(RelationalQueryableExtensions.AsSubQuery)) + { + var expression = visitor.Visit(methodCallExpression.Arguments[0]); + + if (expression is ShapedQueryExpression shapedQueryExpression) + { + ((SelectExpression)shapedQueryExpression.QueryExpression).PushdownIntoSubquery(); + return shapedQueryExpression; + } + } + + if (methodCallExpression.Method.Name == nameof(RelationalQueryableExtensions.WithTableHints)) + return TranslateTableHints(GetShapedQueryExpression(visitor, methodCallExpression), methodCallExpression); + } + + return null; + } + + private static Expression TranslateTableHints( + ShapedQueryExpression shapedQueryExpression, + MethodCallExpression methodCallExpression) + { + var hintArgs = methodCallExpression.Arguments[1]; + var tableHints = hintArgs switch + { + TableHintsExpression tableHintsExpression => tableHintsExpression.Value, + ConstantExpression constantExpression => (IReadOnlyList?)constantExpression.Value ?? throw new Exception("No table hints provided."), + _ => throw new NotSupportedException($"Table hint argument of type '{hintArgs.GetType().FullName}' is not supported.") + }; + + if (tableHints.Count == 0) + return shapedQueryExpression; + + var selectExpression = (SelectExpression)shapedQueryExpression.QueryExpression; + var newSelectExpression = selectExpression.AddAnnotation(new Annotation(ThinktectureRelationalAnnotationNames.TABLE_HINTS, tableHints)); + + return shapedQueryExpression.Update(newSelectExpression, shapedQueryExpression.ShaperExpression); + } + + private static ShapedQueryExpression GetShapedQueryExpression( + ExpressionVisitor visitor, + MethodCallExpression methodCallExpression) + { + var source = visitor.Visit(methodCallExpression.Arguments[0]); + + if (source is not ShapedQueryExpression shapedQueryExpression) + throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print())); + + return shapedQueryExpression; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalSelectExpressionExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalSelectExpressionExtensions.cs index aa637ba9..eb9a36c7 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalSelectExpressionExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalSelectExpressionExtensions.cs @@ -1,57 +1,57 @@ -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; - -namespace Thinktecture; - -/// -/// For internal use only. -/// -public static class RelationalSelectExpressionExtensions -{ - /// - /// For internal use only. - /// - public static SelectExpression AddAnnotation( - this SelectExpression selectExpression, - IAnnotation annotation) - { - ArgumentNullException.ThrowIfNull(selectExpression); - ArgumentNullException.ThrowIfNull(annotation); - - var tables = selectExpression.Tables; - - if (tables.Count == 0) - throw new InvalidOperationException($"No tables found to add annotation '{annotation.Name}' to."); - - if (tables.Count > 1) - throw new InvalidOperationException($"Multiple tables found to add annotation '{annotation.Name}' to. Expressions: {String.Join(", ", tables.Select(t => t.Print()))}"); - - var tableExpressionBase = selectExpression.Tables[0]; - - if (tableExpressionBase is not TableExpression tableExpression) - throw new NotSupportedException($"Annotation '{annotation.Name}' can be applied to tables only but found '{tableExpressionBase.GetType().Name}'. Expression: {tableExpressionBase.Print()}"); - - return (SelectExpression)new AnnotationApplyingExpressionVisitor(tableExpression, annotation) - .Visit(selectExpression)!; - } - - private sealed class AnnotationApplyingExpressionVisitor : ExpressionVisitor - { - private readonly TableExpression _tableExpression; - private readonly IAnnotation _annotation; - - public AnnotationApplyingExpressionVisitor(TableExpression tableExpression, IAnnotation annotation) - { - _tableExpression = tableExpression; - _annotation = annotation; - } - - public override Expression? Visit(Expression? expression) - { - return _tableExpression.Equals(expression) - ? _tableExpression.AddAnnotation(_annotation.Name, _annotation.Value) - : base.Visit(expression); - } - } -} +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +namespace Thinktecture; + +/// +/// For internal use only. +/// +public static class RelationalSelectExpressionExtensions +{ + /// + /// For internal use only. + /// + public static SelectExpression AddAnnotation( + this SelectExpression selectExpression, + IAnnotation annotation) + { + ArgumentNullException.ThrowIfNull(selectExpression); + ArgumentNullException.ThrowIfNull(annotation); + + var tables = selectExpression.Tables; + + if (tables.Count == 0) + throw new InvalidOperationException($"No tables found to add annotation '{annotation.Name}' to."); + + if (tables.Count > 1) + throw new InvalidOperationException($"Multiple tables found to add annotation '{annotation.Name}' to. Expressions: {String.Join(", ", tables.Select(t => t.Print()))}"); + + var tableExpressionBase = selectExpression.Tables[0]; + + if (tableExpressionBase is not TableExpression tableExpression) + throw new NotSupportedException($"Annotation '{annotation.Name}' can be applied to tables only but found '{tableExpressionBase.GetType().Name}'. Expression: {tableExpressionBase.Print()}"); + + return (SelectExpression)new AnnotationApplyingExpressionVisitor(tableExpression, annotation) + .Visit(selectExpression)!; + } + + private sealed class AnnotationApplyingExpressionVisitor : ExpressionVisitor + { + private readonly TableExpression _tableExpression; + private readonly IAnnotation _annotation; + + public AnnotationApplyingExpressionVisitor(TableExpression tableExpression, IAnnotation annotation) + { + _tableExpression = tableExpression; + _annotation = annotation; + } + + public override Expression? Visit(Expression? expression) + { + return _tableExpression.Equals(expression) + ? _tableExpression.AddAnnotation(_annotation.Name, _annotation.Value) + : base.Visit(expression); + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalServiceCollectionExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalServiceCollectionExtensions.cs index ac3d3414..82a480d8 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalServiceCollectionExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Extensions/RelationalServiceCollectionExtensions.cs @@ -1,47 +1,47 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extension methods for . -/// -public static class RelationalServiceCollectionExtensions -{ - /// - /// Adds a service of the type specified in with an - /// implementation type specified in to the - /// if the service type hasn't already been registered. - /// - /// The to add the service to. - /// Lifetime of the service. - /// The type of the service to add. - /// The type of the implementation to use. - /// is null. - public static void TryAdd(this IServiceCollection services, ServiceLifetime lifetime) - where TImplementation : TService - { - ArgumentNullException.ThrowIfNull(services); - - services.TryAdd(ServiceDescriptor.Describe(typeof(TService), typeof(TImplementation), lifetime)); - } - - /// - /// Adds a service of the type specified in with an - /// implementation type specified in to the - /// . - /// - /// The to add the service to. - /// Lifetime of the service. - /// The type of the service to add. - /// The type of the implementation to use. - /// is null. - public static void Add(this IServiceCollection services, ServiceLifetime lifetime) - where TImplementation : TService - { - ArgumentNullException.ThrowIfNull(services); - - services.Add(ServiceDescriptor.Describe(typeof(TService), typeof(TImplementation), lifetime)); - } -} +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extension methods for . +/// +public static class RelationalServiceCollectionExtensions +{ + /// + /// Adds a service of the type specified in with an + /// implementation type specified in to the + /// if the service type hasn't already been registered. + /// + /// The to add the service to. + /// Lifetime of the service. + /// The type of the service to add. + /// The type of the implementation to use. + /// is null. + public static void TryAdd(this IServiceCollection services, ServiceLifetime lifetime) + where TImplementation : TService + { + ArgumentNullException.ThrowIfNull(services); + + services.TryAdd(ServiceDescriptor.Describe(typeof(TService), typeof(TImplementation), lifetime)); + } + + /// + /// Adds a service of the type specified in with an + /// implementation type specified in to the + /// . + /// + /// The to add the service to. + /// Lifetime of the service. + /// The type of the service to add. + /// The type of the implementation to use. + /// is null. + public static void Add(this IServiceCollection services, ServiceLifetime lifetime) + where TImplementation : TService + { + ArgumentNullException.ThrowIfNull(services); + + services.Add(ServiceDescriptor.Describe(typeof(TService), typeof(TImplementation), lifetime)); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Internal/NonEvaluatableConstantExpression.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Internal/NonEvaluatableConstantExpression.cs index 4af1d33e..4530cf94 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Internal/NonEvaluatableConstantExpression.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Internal/NonEvaluatableConstantExpression.cs @@ -1,55 +1,55 @@ -using System.Linq.Expressions; - -namespace Thinktecture.Internal; - -/// -/// For internal use only. -/// -public abstract class NonEvaluatableConstantExpression : Expression, IEquatable - where T : notnull -{ - /// - /// Value. - /// - public T Value { get; } - - /// - public override Type Type { get; } - - /// - public override ExpressionType NodeType => ExpressionType.Extension; - - /// - /// Initializes new instance of - /// - /// - public NonEvaluatableConstantExpression(T value) - { - Value = value; - Type = Value.GetType(); - } - - /// - protected override Expression Accept(ExpressionVisitor visitor) - { - return this; - } - - /// - public override bool Equals(object? obj) - { - return obj != null - && (ReferenceEquals(this, obj) - || (obj is NonEvaluatableConstantExpression other && Equals(other.Value))); - } - - /// - /// Compares with . - /// - /// Other value. - /// Return - public abstract bool Equals(T? otherValue); - - /// - public abstract override int GetHashCode(); -} +using System.Linq.Expressions; + +namespace Thinktecture.Internal; + +/// +/// For internal use only. +/// +public abstract class NonEvaluatableConstantExpression : Expression, IEquatable + where T : notnull +{ + /// + /// Value. + /// + public T Value { get; } + + /// + public override Type Type { get; } + + /// + public override ExpressionType NodeType => ExpressionType.Extension; + + /// + /// Initializes new instance of + /// + /// + public NonEvaluatableConstantExpression(T value) + { + Value = value; + Type = Value.GetType(); + } + + /// + protected override Expression Accept(ExpressionVisitor visitor) + { + return this; + } + + /// + public override bool Equals(object? obj) + { + return obj != null + && (ReferenceEquals(this, obj) + || (obj is NonEvaluatableConstantExpression other && Equals(other.Value))); + } + + /// + /// Compares with . + /// + /// Other value. + /// Return + public abstract bool Equals(T? otherValue); + + /// + public abstract override int GetHashCode(); +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/ExpressionBodyExtractingVisitor.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/ExpressionBodyExtractingVisitor.cs index 81333f20..02ea6ee0 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/ExpressionBodyExtractingVisitor.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/ExpressionBodyExtractingVisitor.cs @@ -1,55 +1,55 @@ -using System.Linq.Expressions; -using System.Reflection; - -namespace Thinktecture.Linq.Expressions; - -/// -/// Searches for occurrences of -/// and replaces them with the body of the provided lambda expression. -/// The references to the original lambda parameter is replaced with the one provided with . -/// -public class ExpressionBodyExtractingVisitor : ExpressionVisitor -{ - private static readonly ExpressionBodyExtractingVisitor _instance = new(); - private static readonly MethodInfo _extractBodyMethod = typeof(RelationalExpressionExtensions).GetMethod(nameof(RelationalExpressionExtensions.ExtractBody), BindingFlags.Static | BindingFlags.Public) - ?? throw new Exception($"Method '{nameof(RelationalExpressionExtensions.ExtractBody)}' not found."); - - /// - /// Rewrites the provided if it contains . - /// - /// Expression to rewrite. - /// The type of the lambda expression. - /// - /// A rewritten if any occurrences of have been found; - /// otherwise the provided is returned. - /// - /// The provided could not be rewritten. - public static T Rewrite(T expression) - where T : LambdaExpression - { - var visitedExpression = _instance.Visit(expression); - - if (visitedExpression is T lambda) - return lambda; - - throw new NotSupportedException($"The provided expression is not supported: {expression}"); - } - - /// - protected override Expression VisitMethodCall(MethodCallExpression node) - { - ArgumentNullException.ThrowIfNull(node); - - if (!node.Method.IsGenericMethod || node.Method.GetGenericMethodDefinition() != _extractBodyMethod) - return base.VisitMethodCall(node); - - var newParameter = node.Arguments[1]; - var lambda = LambdaExpressionSearchingVisitor.GetLambda(node.Arguments[0]); - var oldParameter = lambda.Parameters[0]; - - var expressionReplacer = new ExpressionReplacingVisitor(oldParameter, newParameter); - var expression = expressionReplacer.Visit(lambda.Body); - - return Visit(expression); - } -} +using System.Linq.Expressions; +using System.Reflection; + +namespace Thinktecture.Linq.Expressions; + +/// +/// Searches for occurrences of +/// and replaces them with the body of the provided lambda expression. +/// The references to the original lambda parameter is replaced with the one provided with . +/// +public class ExpressionBodyExtractingVisitor : ExpressionVisitor +{ + private static readonly ExpressionBodyExtractingVisitor _instance = new(); + private static readonly MethodInfo _extractBodyMethod = typeof(RelationalExpressionExtensions).GetMethod(nameof(RelationalExpressionExtensions.ExtractBody), BindingFlags.Static | BindingFlags.Public) + ?? throw new Exception($"Method '{nameof(RelationalExpressionExtensions.ExtractBody)}' not found."); + + /// + /// Rewrites the provided if it contains . + /// + /// Expression to rewrite. + /// The type of the lambda expression. + /// + /// A rewritten if any occurrences of have been found; + /// otherwise the provided is returned. + /// + /// The provided could not be rewritten. + public static T Rewrite(T expression) + where T : LambdaExpression + { + var visitedExpression = _instance.Visit(expression); + + if (visitedExpression is T lambda) + return lambda; + + throw new NotSupportedException($"The provided expression is not supported: {expression}"); + } + + /// + protected override Expression VisitMethodCall(MethodCallExpression node) + { + ArgumentNullException.ThrowIfNull(node); + + if (!node.Method.IsGenericMethod || node.Method.GetGenericMethodDefinition() != _extractBodyMethod) + return base.VisitMethodCall(node); + + var newParameter = node.Arguments[1]; + var lambda = LambdaExpressionSearchingVisitor.GetLambda(node.Arguments[0]); + var oldParameter = lambda.Parameters[0]; + + var expressionReplacer = new ExpressionReplacingVisitor(oldParameter, newParameter); + var expression = expressionReplacer.Visit(lambda.Body); + + return Visit(expression); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/ExpressionReplacingVisitor.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/ExpressionReplacingVisitor.cs index b14f19e3..7e53ef10 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/ExpressionReplacingVisitor.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/ExpressionReplacingVisitor.cs @@ -1,31 +1,31 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; - -namespace Thinktecture.Linq.Expressions; - -/// -/// Replaces one expression with another. -/// -public sealed class ExpressionReplacingVisitor : ExpressionVisitor -{ - private readonly Expression _oldExpression; - private readonly Expression _newExpression; - - /// - /// Initializes . - /// - /// Expression to replace. - /// Expression to replace with. - public ExpressionReplacingVisitor(Expression oldExpression, Expression newExpression) - { - _oldExpression = oldExpression ?? throw new ArgumentNullException(nameof(oldExpression)); - _newExpression = newExpression ?? throw new ArgumentNullException(nameof(newExpression)); - } - - /// - [return: NotNullIfNotNull("node")] - public override Expression? Visit(Expression? node) - { - return node == _oldExpression ? _newExpression : base.Visit(node); - } -} +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; + +namespace Thinktecture.Linq.Expressions; + +/// +/// Replaces one expression with another. +/// +public sealed class ExpressionReplacingVisitor : ExpressionVisitor +{ + private readonly Expression _oldExpression; + private readonly Expression _newExpression; + + /// + /// Initializes . + /// + /// Expression to replace. + /// Expression to replace with. + public ExpressionReplacingVisitor(Expression oldExpression, Expression newExpression) + { + _oldExpression = oldExpression ?? throw new ArgumentNullException(nameof(oldExpression)); + _newExpression = newExpression ?? throw new ArgumentNullException(nameof(newExpression)); + } + + /// + [return: NotNullIfNotNull("node")] + public override Expression? Visit(Expression? node) + { + return node == _oldExpression ? _newExpression : base.Visit(node); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/LambdaExpressionSearchingVisitor.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/LambdaExpressionSearchingVisitor.cs index 8cb6c9ab..1c057027 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/LambdaExpressionSearchingVisitor.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/LambdaExpressionSearchingVisitor.cs @@ -1,131 +1,131 @@ -using System.Linq.Expressions; -using System.Reflection; -using Microsoft.EntityFrameworkCore.Infrastructure; - -namespace Thinktecture.Linq.Expressions; - -/// -/// Visitor for searching a . -/// -public class LambdaExpressionSearchingVisitor : ExpressionVisitor -{ - private static readonly LambdaExpressionSearchingVisitor _instance = new(); - - /// - /// Searches for in provided . - /// - /// Expression to search for lambda in. - /// Found instance of . - /// Provided is null. - /// Provided does not contain a lambda. - public static LambdaExpression GetLambda(Expression expression) - { - ArgumentNullException.ThrowIfNull(expression); - - var foundExpression = _instance.Visit(expression); - - if (foundExpression == null || foundExpression.NodeType != ExpressionType.Lambda) - throw new ArgumentException("The provided expression does not contain nor point to a lambda expression."); - - return (LambdaExpression)foundExpression; - } - - /// - protected override Expression VisitLambda(Expression node) - { - return node; - } - - /// - protected override Expression VisitUnary(UnaryExpression node) - { - ArgumentNullException.ThrowIfNull(node); - - if (node.NodeType is ExpressionType.Convert or ExpressionType.Quote) - return Visit(node.Operand) ?? throw NotSupported(node); - - throw NotSupported(node); - } - - /// - protected override Expression VisitMember(MemberExpression node) - { - ArgumentNullException.ThrowIfNull(node); - - var instanceExpression = node.Expression; - - if (instanceExpression is { NodeType: not ExpressionType.Constant }) - instanceExpression = Visit(instanceExpression); - - if (instanceExpression?.NodeType == ExpressionType.Constant) - { - var instance = ((ConstantExpression)instanceExpression).Value - ?? throw new Exception($"Instance cannot be null. Member: {node.Member}."); - - var value = GetMemberValue(node, instance); - - if (value is Expression exp) - return exp; - - return Expression.Constant(value); - } - - throw new NotSupportedException($"The value of most inner expression must be of type '{ExpressionType.Constant}'. Found expression: {node.GetType().ShortDisplayName()}"); - } - - /// - protected override Expression VisitMethodCall(MethodCallExpression node) - { - ArgumentNullException.ThrowIfNull(node); - - var instanceExpression = Visit(node.Object); - - if (instanceExpression is { NodeType: not ExpressionType.Constant }) - throw new NotSupportedException($"The expression representing the instance to call the method on must be of type '{ExpressionType.Constant}'. Found expression: {instanceExpression.GetType().ShortDisplayName()}"); - - var arguments = node.Arguments.Select(a => - { - var visitedArgument = Visit(a); - - if (visitedArgument is null) - throw new NotSupportedException($"The expressions representing the arguments of the method call could not be translated. Found expression: {a}"); - - if (visitedArgument.NodeType != ExpressionType.Constant) - throw new NotSupportedException($"All expressions representing the arguments of the method call must be of type '{ExpressionType.Constant}'. Found expression: {visitedArgument.GetType().ShortDisplayName()}"); - - return ((ConstantExpression)a).Value; - }) - .ToArray(); - - var value = node.Method.Invoke(((ConstantExpression?)instanceExpression)?.Value, arguments); - - if (value is Expression exp) - return exp; - - return Expression.Constant(value); - } - - private static object? GetMemberValue(MemberExpression memberAccess, object instance) - { - ArgumentNullException.ThrowIfNull(memberAccess); - - switch (memberAccess.Member.MemberType) - { - case MemberTypes.Field: - var fieldInfo = (FieldInfo)memberAccess.Member; - return fieldInfo.GetValue(instance); - - case MemberTypes.Property: - var propertyInfo = (PropertyInfo)memberAccess.Member; - return propertyInfo.GetValue(instance); - - default: - throw new NotSupportedException($"Member type '{memberAccess.Member.MemberType}' is not supported."); - } - } - - private static Exception NotSupported(Expression node) - { - return new NotSupportedException($"Node of type '{node.GetType().ShortDisplayName()}' is not supported."); - } -} +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Thinktecture.Linq.Expressions; + +/// +/// Visitor for searching a . +/// +public class LambdaExpressionSearchingVisitor : ExpressionVisitor +{ + private static readonly LambdaExpressionSearchingVisitor _instance = new(); + + /// + /// Searches for in provided . + /// + /// Expression to search for lambda in. + /// Found instance of . + /// Provided is null. + /// Provided does not contain a lambda. + public static LambdaExpression GetLambda(Expression expression) + { + ArgumentNullException.ThrowIfNull(expression); + + var foundExpression = _instance.Visit(expression); + + if (foundExpression == null || foundExpression.NodeType != ExpressionType.Lambda) + throw new ArgumentException("The provided expression does not contain nor point to a lambda expression."); + + return (LambdaExpression)foundExpression; + } + + /// + protected override Expression VisitLambda(Expression node) + { + return node; + } + + /// + protected override Expression VisitUnary(UnaryExpression node) + { + ArgumentNullException.ThrowIfNull(node); + + if (node.NodeType is ExpressionType.Convert or ExpressionType.Quote) + return Visit(node.Operand) ?? throw NotSupported(node); + + throw NotSupported(node); + } + + /// + protected override Expression VisitMember(MemberExpression node) + { + ArgumentNullException.ThrowIfNull(node); + + var instanceExpression = node.Expression; + + if (instanceExpression is { NodeType: not ExpressionType.Constant }) + instanceExpression = Visit(instanceExpression); + + if (instanceExpression?.NodeType == ExpressionType.Constant) + { + var instance = ((ConstantExpression)instanceExpression).Value + ?? throw new Exception($"Instance cannot be null. Member: {node.Member}."); + + var value = GetMemberValue(node, instance); + + if (value is Expression exp) + return exp; + + return Expression.Constant(value); + } + + throw new NotSupportedException($"The value of most inner expression must be of type '{ExpressionType.Constant}'. Found expression: {node.GetType().ShortDisplayName()}"); + } + + /// + protected override Expression VisitMethodCall(MethodCallExpression node) + { + ArgumentNullException.ThrowIfNull(node); + + var instanceExpression = Visit(node.Object); + + if (instanceExpression is { NodeType: not ExpressionType.Constant }) + throw new NotSupportedException($"The expression representing the instance to call the method on must be of type '{ExpressionType.Constant}'. Found expression: {instanceExpression.GetType().ShortDisplayName()}"); + + var arguments = node.Arguments.Select(a => + { + var visitedArgument = Visit(a); + + if (visitedArgument is null) + throw new NotSupportedException($"The expressions representing the arguments of the method call could not be translated. Found expression: {a}"); + + if (visitedArgument.NodeType != ExpressionType.Constant) + throw new NotSupportedException($"All expressions representing the arguments of the method call must be of type '{ExpressionType.Constant}'. Found expression: {visitedArgument.GetType().ShortDisplayName()}"); + + return ((ConstantExpression)a).Value; + }) + .ToArray(); + + var value = node.Method.Invoke(((ConstantExpression?)instanceExpression)?.Value, arguments); + + if (value is Expression exp) + return exp; + + return Expression.Constant(value); + } + + private static object? GetMemberValue(MemberExpression memberAccess, object instance) + { + ArgumentNullException.ThrowIfNull(memberAccess); + + switch (memberAccess.Member.MemberType) + { + case MemberTypes.Field: + var fieldInfo = (FieldInfo)memberAccess.Member; + return fieldInfo.GetValue(instance); + + case MemberTypes.Property: + var propertyInfo = (PropertyInfo)memberAccess.Member; + return propertyInfo.GetValue(instance); + + default: + throw new NotSupportedException($"Member type '{memberAccess.Member.MemberType}' is not supported."); + } + } + + private static Exception NotSupported(Expression node) + { + return new NotSupportedException($"Node of type '{node.GetType().ShortDisplayName()}' is not supported."); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/RelinqInterfaceMemberAccessVisitor.cs b/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/RelinqInterfaceMemberAccessVisitor.cs index 777c74d8..867a42c7 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/RelinqInterfaceMemberAccessVisitor.cs +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Linq/Expressions/RelinqInterfaceMemberAccessVisitor.cs @@ -1,91 +1,91 @@ -using System.Linq.Expressions; -using System.Reflection; -using Microsoft.EntityFrameworkCore.Infrastructure; - -namespace Thinktecture.Linq.Expressions; - -/// -/// Searches for conversions from a (derived) type to an interface -/// and rewrites member access expressions so the property of the derived type is used instead the one of the interface. -/// -public class RelinqInterfaceMemberAccessVisitor : ExpressionVisitor -{ - private static readonly RelinqInterfaceMemberAccessVisitor _instance = new(); - - /// - /// Rewrites the provided so the property of the concrete implementation type is used instead of an interface. - /// - /// Expression to rewrite. - /// Type of the expression. - /// - /// A rewritten if it contains property-access to an interface; - /// otherwise the provided . - /// - /// - /// The provided could not be rewritten. - /// - public static T Rewrite(T expression) - where T : Expression - { - var visitedExpression = _instance.Visit(expression); - - if (visitedExpression is T exp) - return exp; - - throw new NotSupportedException($"The provided expression could not be rewritten: {expression}"); - } - - /// - protected override Expression VisitMember(MemberExpression node) - { - ArgumentNullException.ThrowIfNull(node); - - if (node.Expression is { NodeType: ExpressionType.Convert }) - { - var conversion = (UnaryExpression)node.Expression; - - if (conversion.Type.IsInterface && conversion.Type.IsAssignableFrom(conversion.Operand.Type)) - { - MemberInfo? member = null; - - if (node.Member.MemberType == MemberTypes.Property) - member = FindProperty(conversion.Operand.Type, conversion.Type, (PropertyInfo)node.Member); - - if (member == null) - throw new MissingMemberException(conversion.Operand.Type.ShortDisplayName(), node.Member.Name); - - var operand = Visit(conversion.Operand); - return Expression.MakeMemberAccess(operand, member); - } - } - - return base.VisitMember(node); - } - - private static MemberInfo? FindProperty(Type dstType, Type interfaceType, PropertyInfo interfaceMember) - { - var map = dstType.GetInterfaceMap(interfaceType); - var targetMethod = FindTargetMethod(map, interfaceMember); - - if (targetMethod != null) - { - return dstType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .FirstOrDefault(p => p.GetMethod == targetMethod || p.SetMethod == targetMethod); - } - - return null; - } - - private static MethodInfo? FindTargetMethod(InterfaceMapping map, PropertyInfo interfaceProperty) - { - for (var i = 0; i < map.InterfaceMethods.Length; i++) - { - var interfaceMethod = map.InterfaceMethods[i]; - - if (interfaceMethod == interfaceProperty.GetMethod || interfaceMethod == interfaceProperty.SetMethod) - return map.TargetMethods[i]; - } - - return null; - } -} +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Thinktecture.Linq.Expressions; + +/// +/// Searches for conversions from a (derived) type to an interface +/// and rewrites member access expressions so the property of the derived type is used instead the one of the interface. +/// +public class RelinqInterfaceMemberAccessVisitor : ExpressionVisitor +{ + private static readonly RelinqInterfaceMemberAccessVisitor _instance = new(); + + /// + /// Rewrites the provided so the property of the concrete implementation type is used instead of an interface. + /// + /// Expression to rewrite. + /// Type of the expression. + /// + /// A rewritten if it contains property-access to an interface; + /// otherwise the provided . + /// + /// + /// The provided could not be rewritten. + /// + public static T Rewrite(T expression) + where T : Expression + { + var visitedExpression = _instance.Visit(expression); + + if (visitedExpression is T exp) + return exp; + + throw new NotSupportedException($"The provided expression could not be rewritten: {expression}"); + } + + /// + protected override Expression VisitMember(MemberExpression node) + { + ArgumentNullException.ThrowIfNull(node); + + if (node.Expression is { NodeType: ExpressionType.Convert }) + { + var conversion = (UnaryExpression)node.Expression; + + if (conversion.Type.IsInterface && conversion.Type.IsAssignableFrom(conversion.Operand.Type)) + { + MemberInfo? member = null; + + if (node.Member.MemberType == MemberTypes.Property) + member = FindProperty(conversion.Operand.Type, conversion.Type, (PropertyInfo)node.Member); + + if (member == null) + throw new MissingMemberException(conversion.Operand.Type.ShortDisplayName(), node.Member.Name); + + var operand = Visit(conversion.Operand); + return Expression.MakeMemberAccess(operand, member); + } + } + + return base.VisitMember(node); + } + + private static MemberInfo? FindProperty(Type dstType, Type interfaceType, PropertyInfo interfaceMember) + { + var map = dstType.GetInterfaceMap(interfaceType); + var targetMethod = FindTargetMethod(map, interfaceMember); + + if (targetMethod != null) + { + return dstType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .FirstOrDefault(p => p.GetMethod == targetMethod || p.SetMethod == targetMethod); + } + + return null; + } + + private static MethodInfo? FindTargetMethod(InterfaceMapping map, PropertyInfo interfaceProperty) + { + for (var i = 0; i < map.InterfaceMethods.Length; i++) + { + var interfaceMethod = map.InterfaceMethods[i]; + + if (interfaceMethod == interfaceProperty.GetMethod || interfaceMethod == interfaceProperty.SetMethod) + return map.TargetMethods[i]; + } + + return null; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Relational/Thinktecture.EntityFrameworkCore.Relational.csproj b/src/Thinktecture.EntityFrameworkCore.Relational/Thinktecture.EntityFrameworkCore.Relational.csproj index e1f1415f..b1fd614c 100644 --- a/src/Thinktecture.EntityFrameworkCore.Relational/Thinktecture.EntityFrameworkCore.Relational.csproj +++ b/src/Thinktecture.EntityFrameworkCore.Relational/Thinktecture.EntityFrameworkCore.Relational.csproj @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/ITestIsolationOptions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/ITestIsolationOptions.cs index d23308af..105b7c99 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/ITestIsolationOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/ITestIsolationOptions.cs @@ -1,362 +1,362 @@ -using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reflection; -using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using Thinktecture.EntityFrameworkCore.Parameters; -using Thinktecture.EntityFrameworkCore.TempTables; - -namespace Thinktecture.EntityFrameworkCore.Testing; - -/// -/// Options for test isolation behavior. -/// -public interface ITestIsolationOptions -{ - /// - /// No test isolation, i.e. no ambient transaction, no unique schema, no cleanup. - /// - public static readonly ITestIsolationOptions None = new NoIsolation(); - - /// - /// Test isolation via ambient transaction. - /// No unique schema, no cleanup. - /// - public static readonly ITestIsolationOptions SharedTablesAmbientTransaction = new ShareTablesIsolation(); - - /// - /// Rollbacks migrations and then deletes database objects (like tables) with a schema used by the tests. - /// No ambient transaction; uses unique schema. - /// - public static readonly ITestIsolationOptions RollbackMigrationsAndCleanup = new RollbackMigrationsAndCleanupDatabase(); - - /// - /// Deletes database objects (like tables) with a schema used by the tests. - /// No ambient transaction; uses unique schema. - /// - public static readonly ITestIsolationOptions CleanupOnly = new CleanupDatabase(); - - /// - /// Deletes all records from the tables using "TRUNCATE". - /// No ambient transaction; no unique schema. - /// - public static readonly ITestIsolationOptions TruncateTables = new TruncateAllTables(); - - /// - /// Deletes all records from the tables using "DELETE". - /// No ambient transaction; no unique schema. - /// - public static ITestIsolationOptions DeleteData( - Predicate? filter = null, - Func, IReadOnlyList>? modifyDeletionOrder = null) - { - return new DeleteAllData(filter is null - ? SkipTempTablesAndCollectionParameters - : entityType => filter(entityType) && SkipTempTablesAndCollectionParameters(entityType), - modifyDeletionOrder); - } - - private static bool SkipTempTablesAndCollectionParameters(IEntityType entityType) - { - return !entityType.ClrType.IsGenericType - || (entityType.ClrType.GetGenericTypeDefinition() != typeof(TempTable<>) - && entityType.ClrType.GetGenericTypeDefinition() != typeof(TempTable<,>) - && entityType.ClrType.GetGenericTypeDefinition() != typeof(ScalarCollectionParameter<>)); - } - - /// - /// Performs custom cleanup. - /// - /// Indicator whether the tables require an unique database schema. - /// Callback that performs the actual cleanup. - /// Type of the - /// - public static ITestIsolationOptions Custom(bool needsUniqueSchema, Func cleanup) - where T : DbContext - { - return new CustomCleanup(needsUniqueSchema, cleanup); - } - - /// - /// Indicator, whether the database needs cleanup. - /// - bool NeedsAmbientTransaction { get; } - - /// - /// Indicator, whether the database needs cleanup. - /// - bool NeedsUniqueSchema { get; } - - /// - /// Indicator, whether the database needs cleanup. - /// - bool NeedsCleanup { get; } - - /// - /// Cleanup of the database. - /// - ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken); - - private class NoIsolation : ITestIsolationOptions - { - public bool NeedsAmbientTransaction => false; - public bool NeedsUniqueSchema => false; - public bool NeedsCleanup => false; - - public ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) - { - return ValueTask.CompletedTask; - } - } - - private class ShareTablesIsolation : ITestIsolationOptions - { - public bool NeedsAmbientTransaction => true; - public bool NeedsUniqueSchema => false; - public bool NeedsCleanup => false; - - public ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) - { - return ValueTask.CompletedTask; - } - } - - private class RollbackMigrationsAndCleanupDatabase : ITestIsolationOptions - { - public bool NeedsAmbientTransaction => false; - public bool NeedsUniqueSchema => true; - public bool NeedsCleanup => true; - - public async ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) - { - await RollbackMigrationsAsync(dbContext, cancellationToken); - await DeleteDatabaseObjectsAsync(dbContext, schema, cancellationToken); - } - } - - private class CleanupDatabase : ITestIsolationOptions - { - public bool NeedsAmbientTransaction => false; - public bool NeedsUniqueSchema => true; - public bool NeedsCleanup => true; - - public async ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) - { - await DeleteDatabaseObjectsAsync(dbContext, schema, cancellationToken); - } - } - - private class CustomCleanup : ITestIsolationOptions - where T : DbContext - { - private readonly Func _cleanup; - - public bool NeedsAmbientTransaction => false; - public bool NeedsUniqueSchema { get; } - public bool NeedsCleanup => true; - - public CustomCleanup(bool needsUniqueSchema, Func cleanup) - { - NeedsUniqueSchema = needsUniqueSchema; - _cleanup = cleanup; - } - - public async ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) - { - await _cleanup((T)dbContext, schema, cancellationToken); - } - } - - private class TruncateAllTables : ITestIsolationOptions - { - public bool NeedsAmbientTransaction => false; - public bool NeedsUniqueSchema => false; - public bool NeedsCleanup => true; - - [SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] - public async ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) - { - foreach (var entityType in dbContext.Model.GetEntityTypesInHierarchicalOrder().Reverse()) - { - if (entityType.GetTableName() is not null) - await dbContext.TruncateTableAsync(entityType.ClrType, cancellationToken); - } - } - } - - private class DeleteAllData : ITestIsolationOptions - { - private static readonly MethodInfo _deleteData = typeof(DeleteAllData).GetMethod(nameof(DeleteDataAsync), BindingFlags.Static | BindingFlags.NonPublic) - ?? throw new Exception($"Method '{nameof(DeleteDataAsync)}' not found."); - - private readonly Predicate _filter; - private readonly Func, IReadOnlyList>? _modifyDeletionOrder; - private readonly ConcurrentDictionary> _deleteDelegatesLookup; - - private IReadOnlyList? _orderedEntities; - - public bool NeedsAmbientTransaction => false; - public bool NeedsUniqueSchema => false; - public bool NeedsCleanup => true; - - public DeleteAllData( - Predicate filter, - Func, IReadOnlyList>? modifyDeletionOrder) - { - _filter = filter; - _modifyDeletionOrder = modifyDeletionOrder; - _deleteDelegatesLookup = new ConcurrentDictionary>(); - } - - [SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] - public async ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) - { - if (_orderedEntities is null) - { - var orderedEntities = dbContext.Model.GetEntityTypesInHierarchicalOrder().Reverse().ToList(); - - _orderedEntities = _modifyDeletionOrder?.Invoke(orderedEntities) ?? orderedEntities; - } - - foreach (var entityType in _orderedEntities) - { - if (entityType.GetTableName() is null || !_filter(entityType)) - continue; - - var delete = _deleteDelegatesLookup.GetOrAdd(entityType, CreateDelegate); - - await delete(dbContext, entityType.Name, cancellationToken); - } - } - - private static Func CreateDelegate(IEntityType type) - { - var ctxParam = Expression.Parameter(typeof(DbContext)); - var nameParam = Expression.Parameter(typeof(string)); - var cancellationTokenParam = Expression.Parameter(typeof(CancellationToken)); - var method = _deleteData.MakeGenericMethod(type.ClrType); - - var call = Expression.Call(method, ctxParam, nameParam, cancellationTokenParam); - - return Expression.Lambda>(call, ctxParam, nameParam, cancellationTokenParam).Compile(); - } - - private static async Task DeleteDataAsync(DbContext dbContext, string name, CancellationToken cancellationToken) - where T : class - { - await dbContext.Set(name).ExecuteDeleteAsync(cancellationToken); - } - } - - /// - /// Rollbacks all migrations. - /// - protected static async Task RollbackMigrationsAsync(DbContext dbContext, CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(dbContext); - - await dbContext.GetService().MigrateAsync("0", cancellationToken); - } - - /// - /// Deletes database objects (like tables) with provided . - /// - protected static async Task DeleteDatabaseObjectsAsync(DbContext ctx, string? schema, CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(ctx); - - if (schema is not null) - { - var sqlHelper = ctx.GetService(); - - await ctx.Database.ExecuteSqlRawAsync(GetSqlForCleanup(), new object[] { new SqlParameter("@schema", schema) }, cancellationToken); - await ctx.Database.ExecuteSqlRawAsync(GetDropSchemaSql(sqlHelper, schema), cancellationToken); - } - } - - private static string GetSqlForCleanup() - { - return """ - DECLARE @crlf NVARCHAR(MAX) = CHAR(13) + CHAR(10); - DECLARE @sql NVARCHAR(MAX); - DECLARE @cursor CURSOR - - -- Drop Constraints - SET @cursor = CURSOR FAST_FORWARD FOR - SELECT DISTINCT sql = 'ALTER TABLE ' + QUOTENAME(tc.TABLE_SCHEMA) + '.' + QUOTENAME(tc.TABLE_NAME) + ' DROP ' + QUOTENAME(rc.CONSTRAINT_NAME) + ';' + @crlf - FROM - INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc - LEFT JOIN - INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc - ON tc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME - WHERE - tc.TABLE_SCHEMA = @schema - - OPEN @cursor FETCH NEXT FROM @cursor INTO @sql - - WHILE (@@FETCH_STATUS = 0) - BEGIN - Exec sp_executesql @sql - FETCH NEXT FROM @cursor INTO @sql - END - - CLOSE @cursor - DEALLOCATE @cursor - - -- Drop Views - SELECT @sql = N''; - SELECT @sql = @sql + 'DROP VIEW ' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) +';' + @crlf - FROM - SYS.VIEWS - WHERE - schema_id = SCHEMA_ID(@schema) - - EXEC(@sql); - - -- Drop Functions - SELECT @sql = N''; - SELECT @sql = @sql + N' DROP FUNCTION ' + QUOTENAME(SCHEMA_NAME(schema_id)) + N'.' + QUOTENAME(name) - FROM sys.objects - WHERE type_desc LIKE '%FUNCTION%' - AND schema_id = SCHEMA_ID(@schema); - - EXEC(@sql); - - -- Disable temporal tables - SELECT @sql = N''; - SELECT @sql = @sql + 'IF OBJECTPROPERTY(OBJECT_ID(''' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) +'''), ''TableTemporalType'') = 2' + @crlf - + ' ALTER TABLE ' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) +' SET (SYSTEM_VERSIONING = OFF);' + @crlf - FROM - SYS.TABLES - WHERE - schema_id = SCHEMA_ID(@schema) - - EXEC(@sql); - - -- Drop tables - SELECT @sql = N''; - SELECT @sql = @sql + 'DROP TABLE ' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) +';' + @crlf - FROM - SYS.TABLES - WHERE - schema_id = SCHEMA_ID(@schema) - - EXEC(@sql); - """; - } - - private static string GetDropSchemaSql(ISqlGenerationHelper sqlHelper, string schema) - { - ArgumentNullException.ThrowIfNull(sqlHelper); - - return $""" - IF SCHEMA_ID('{sqlHelper.DelimitIdentifier(schema)}') IS NOT NULL - DROP SCHEMA {sqlHelper.DelimitIdentifier(schema)}; - """; - } -} +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Thinktecture.EntityFrameworkCore.Parameters; +using Thinktecture.EntityFrameworkCore.TempTables; + +namespace Thinktecture.EntityFrameworkCore.Testing; + +/// +/// Options for test isolation behavior. +/// +public interface ITestIsolationOptions +{ + /// + /// No test isolation, i.e. no ambient transaction, no unique schema, no cleanup. + /// + public static readonly ITestIsolationOptions None = new NoIsolation(); + + /// + /// Test isolation via ambient transaction. + /// No unique schema, no cleanup. + /// + public static readonly ITestIsolationOptions SharedTablesAmbientTransaction = new ShareTablesIsolation(); + + /// + /// Rollbacks migrations and then deletes database objects (like tables) with a schema used by the tests. + /// No ambient transaction; uses unique schema. + /// + public static readonly ITestIsolationOptions RollbackMigrationsAndCleanup = new RollbackMigrationsAndCleanupDatabase(); + + /// + /// Deletes database objects (like tables) with a schema used by the tests. + /// No ambient transaction; uses unique schema. + /// + public static readonly ITestIsolationOptions CleanupOnly = new CleanupDatabase(); + + /// + /// Deletes all records from the tables using "TRUNCATE". + /// No ambient transaction; no unique schema. + /// + public static readonly ITestIsolationOptions TruncateTables = new TruncateAllTables(); + + /// + /// Deletes all records from the tables using "DELETE". + /// No ambient transaction; no unique schema. + /// + public static ITestIsolationOptions DeleteData( + Predicate? filter = null, + Func, IReadOnlyList>? modifyDeletionOrder = null) + { + return new DeleteAllData(filter is null + ? SkipTempTablesAndCollectionParameters + : entityType => filter(entityType) && SkipTempTablesAndCollectionParameters(entityType), + modifyDeletionOrder); + } + + private static bool SkipTempTablesAndCollectionParameters(IEntityType entityType) + { + return !entityType.ClrType.IsGenericType + || (entityType.ClrType.GetGenericTypeDefinition() != typeof(TempTable<>) + && entityType.ClrType.GetGenericTypeDefinition() != typeof(TempTable<,>) + && entityType.ClrType.GetGenericTypeDefinition() != typeof(ScalarCollectionParameter<>)); + } + + /// + /// Performs custom cleanup. + /// + /// Indicator whether the tables require an unique database schema. + /// Callback that performs the actual cleanup. + /// Type of the + /// + public static ITestIsolationOptions Custom(bool needsUniqueSchema, Func cleanup) + where T : DbContext + { + return new CustomCleanup(needsUniqueSchema, cleanup); + } + + /// + /// Indicator, whether the database needs cleanup. + /// + bool NeedsAmbientTransaction { get; } + + /// + /// Indicator, whether the database needs cleanup. + /// + bool NeedsUniqueSchema { get; } + + /// + /// Indicator, whether the database needs cleanup. + /// + bool NeedsCleanup { get; } + + /// + /// Cleanup of the database. + /// + ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken); + + private class NoIsolation : ITestIsolationOptions + { + public bool NeedsAmbientTransaction => false; + public bool NeedsUniqueSchema => false; + public bool NeedsCleanup => false; + + public ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) + { + return ValueTask.CompletedTask; + } + } + + private class ShareTablesIsolation : ITestIsolationOptions + { + public bool NeedsAmbientTransaction => true; + public bool NeedsUniqueSchema => false; + public bool NeedsCleanup => false; + + public ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) + { + return ValueTask.CompletedTask; + } + } + + private class RollbackMigrationsAndCleanupDatabase : ITestIsolationOptions + { + public bool NeedsAmbientTransaction => false; + public bool NeedsUniqueSchema => true; + public bool NeedsCleanup => true; + + public async ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) + { + await RollbackMigrationsAsync(dbContext, cancellationToken); + await DeleteDatabaseObjectsAsync(dbContext, schema, cancellationToken); + } + } + + private class CleanupDatabase : ITestIsolationOptions + { + public bool NeedsAmbientTransaction => false; + public bool NeedsUniqueSchema => true; + public bool NeedsCleanup => true; + + public async ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) + { + await DeleteDatabaseObjectsAsync(dbContext, schema, cancellationToken); + } + } + + private class CustomCleanup : ITestIsolationOptions + where T : DbContext + { + private readonly Func _cleanup; + + public bool NeedsAmbientTransaction => false; + public bool NeedsUniqueSchema { get; } + public bool NeedsCleanup => true; + + public CustomCleanup(bool needsUniqueSchema, Func cleanup) + { + NeedsUniqueSchema = needsUniqueSchema; + _cleanup = cleanup; + } + + public async ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) + { + await _cleanup((T)dbContext, schema, cancellationToken); + } + } + + private class TruncateAllTables : ITestIsolationOptions + { + public bool NeedsAmbientTransaction => false; + public bool NeedsUniqueSchema => false; + public bool NeedsCleanup => true; + + [SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] + public async ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) + { + foreach (var entityType in dbContext.Model.GetEntityTypesInHierarchicalOrder().Reverse()) + { + if (entityType.GetTableName() is not null) + await dbContext.TruncateTableAsync(entityType.ClrType, cancellationToken); + } + } + } + + private class DeleteAllData : ITestIsolationOptions + { + private static readonly MethodInfo _deleteData = typeof(DeleteAllData).GetMethod(nameof(DeleteDataAsync), BindingFlags.Static | BindingFlags.NonPublic) + ?? throw new Exception($"Method '{nameof(DeleteDataAsync)}' not found."); + + private readonly Predicate _filter; + private readonly Func, IReadOnlyList>? _modifyDeletionOrder; + private readonly ConcurrentDictionary> _deleteDelegatesLookup; + + private IReadOnlyList? _orderedEntities; + + public bool NeedsAmbientTransaction => false; + public bool NeedsUniqueSchema => false; + public bool NeedsCleanup => true; + + public DeleteAllData( + Predicate filter, + Func, IReadOnlyList>? modifyDeletionOrder) + { + _filter = filter; + _modifyDeletionOrder = modifyDeletionOrder; + _deleteDelegatesLookup = new ConcurrentDictionary>(); + } + + [SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] + public async ValueTask CleanupAsync(DbContext dbContext, string? schema, CancellationToken cancellationToken) + { + if (_orderedEntities is null) + { + var orderedEntities = dbContext.Model.GetEntityTypesInHierarchicalOrder().Reverse().ToList(); + + _orderedEntities = _modifyDeletionOrder?.Invoke(orderedEntities) ?? orderedEntities; + } + + foreach (var entityType in _orderedEntities) + { + if (entityType.GetTableName() is null || !_filter(entityType)) + continue; + + var delete = _deleteDelegatesLookup.GetOrAdd(entityType, CreateDelegate); + + await delete(dbContext, entityType.Name, cancellationToken); + } + } + + private static Func CreateDelegate(IEntityType type) + { + var ctxParam = Expression.Parameter(typeof(DbContext)); + var nameParam = Expression.Parameter(typeof(string)); + var cancellationTokenParam = Expression.Parameter(typeof(CancellationToken)); + var method = _deleteData.MakeGenericMethod(type.ClrType); + + var call = Expression.Call(method, ctxParam, nameParam, cancellationTokenParam); + + return Expression.Lambda>(call, ctxParam, nameParam, cancellationTokenParam).Compile(); + } + + private static async Task DeleteDataAsync(DbContext dbContext, string name, CancellationToken cancellationToken) + where T : class + { + await dbContext.Set(name).ExecuteDeleteAsync(cancellationToken); + } + } + + /// + /// Rollbacks all migrations. + /// + protected static async Task RollbackMigrationsAsync(DbContext dbContext, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(dbContext); + + await dbContext.GetService().MigrateAsync("0", cancellationToken); + } + + /// + /// Deletes database objects (like tables) with provided . + /// + protected static async Task DeleteDatabaseObjectsAsync(DbContext ctx, string? schema, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(ctx); + + if (schema is not null) + { + var sqlHelper = ctx.GetService(); + + await ctx.Database.ExecuteSqlRawAsync(GetSqlForCleanup(), new object[] { new SqlParameter("@schema", schema) }, cancellationToken); + await ctx.Database.ExecuteSqlRawAsync(GetDropSchemaSql(sqlHelper, schema), cancellationToken); + } + } + + private static string GetSqlForCleanup() + { + return """ + DECLARE @crlf NVARCHAR(MAX) = CHAR(13) + CHAR(10); + DECLARE @sql NVARCHAR(MAX); + DECLARE @cursor CURSOR + + -- Drop Constraints + SET @cursor = CURSOR FAST_FORWARD FOR + SELECT DISTINCT sql = 'ALTER TABLE ' + QUOTENAME(tc.TABLE_SCHEMA) + '.' + QUOTENAME(tc.TABLE_NAME) + ' DROP ' + QUOTENAME(rc.CONSTRAINT_NAME) + ';' + @crlf + FROM + INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc + LEFT JOIN + INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc + ON tc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME + WHERE + tc.TABLE_SCHEMA = @schema + + OPEN @cursor FETCH NEXT FROM @cursor INTO @sql + + WHILE (@@FETCH_STATUS = 0) + BEGIN + Exec sp_executesql @sql + FETCH NEXT FROM @cursor INTO @sql + END + + CLOSE @cursor + DEALLOCATE @cursor + + -- Drop Views + SELECT @sql = N''; + SELECT @sql = @sql + 'DROP VIEW ' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) +';' + @crlf + FROM + SYS.VIEWS + WHERE + schema_id = SCHEMA_ID(@schema) + + EXEC(@sql); + + -- Drop Functions + SELECT @sql = N''; + SELECT @sql = @sql + N' DROP FUNCTION ' + QUOTENAME(SCHEMA_NAME(schema_id)) + N'.' + QUOTENAME(name) + FROM sys.objects + WHERE type_desc LIKE '%FUNCTION%' + AND schema_id = SCHEMA_ID(@schema); + + EXEC(@sql); + + -- Disable temporal tables + SELECT @sql = N''; + SELECT @sql = @sql + 'IF OBJECTPROPERTY(OBJECT_ID(''' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) +'''), ''TableTemporalType'') = 2' + @crlf + + ' ALTER TABLE ' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) +' SET (SYSTEM_VERSIONING = OFF);' + @crlf + FROM + SYS.TABLES + WHERE + schema_id = SCHEMA_ID(@schema) + + EXEC(@sql); + + -- Drop tables + SELECT @sql = N''; + SELECT @sql = @sql + 'DROP TABLE ' + QUOTENAME(SCHEMA_NAME(schema_id)) + '.' + QUOTENAME(name) +';' + @crlf + FROM + SYS.TABLES + WHERE + schema_id = SCHEMA_ID(@schema) + + EXEC(@sql); + """; + } + + private static string GetDropSchemaSql(ISqlGenerationHelper sqlHelper, string schema) + { + ArgumentNullException.ThrowIfNull(sqlHelper); + + return $""" + IF SCHEMA_ID('{sqlHelper.DelimitIdentifier(schema)}') IS NOT NULL + DROP SCHEMA {sqlHelper.DelimitIdentifier(schema)}; + """; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerDbContextIntegrationTests.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerDbContextIntegrationTests.cs index 4f7a4976..3b208ef6 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerDbContextIntegrationTests.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerDbContextIntegrationTests.cs @@ -1,159 +1,159 @@ -using Xunit; -using Xunit.Abstractions; - -namespace Thinktecture.EntityFrameworkCore.Testing; - -/// -/// A base class for integration tests using EF Core along with SQL Server. -/// -/// Type of the database context. -public abstract class SqlServerDbContextIntegrationTests : ITestDbContextProvider, IAsyncLifetime - where T : DbContext -{ - private SqlServerTestDbContextProvider? _testCtxProvider; - - /// - /// Gets the which is created on the first access. - /// - protected SqlServerTestDbContextProvider TestCtxProvider => _testCtxProvider ??= TestCtxProviderBuilder.Build(); - - private readonly SqlServerTestDbContextProviderBuilder _testCtxProviderBuilder; - private bool _isDisposed; - private bool _isProviderConfigured; - - /// - /// Gets the which is created on the first access. - /// - protected SqlServerTestDbContextProviderBuilder TestCtxProviderBuilder - { - get - { - if (!_isProviderConfigured) - { - ConfigureTestDbContextProvider(_testCtxProviderBuilder); - _isProviderConfigured = true; - } - - return _testCtxProviderBuilder; - } - } - - /// - public T ArrangeDbContext => TestCtxProvider.ArrangeDbContext; - - /// - public T ActDbContext => TestCtxProvider.ActDbContext; - - /// - public T AssertDbContext => TestCtxProvider.AssertDbContext; - - /// - /// Initializes new instance of . - /// - /// Connection string to use. - /// Indication whether to create new tables with a new schema or use the existing ones. - /// Output helper to use for logging. - [Obsolete($"Use the overload with '{nameof(ITestIsolationOptions)}' instead.")] - protected SqlServerDbContextIntegrationTests( - string connectionString, - bool useSharedTables = true, - ITestOutputHelper? testOutputHelper = null) - : this(connectionString, - useSharedTables ? ITestIsolationOptions.SharedTablesAmbientTransaction : ITestIsolationOptions.RollbackMigrationsAndCleanup, - testOutputHelper) - { - } - - /// - /// Initializes new instance of . - /// - /// Connection string to use. - /// Test isolation behavior. - /// Output helper to use for logging. - protected SqlServerDbContextIntegrationTests( - string connectionString, - ITestIsolationOptions isolationOptions, - ITestOutputHelper? testOutputHelper = null) - { - _testCtxProviderBuilder = new SqlServerTestDbContextProviderBuilder(connectionString, isolationOptions) - .UseLogging(testOutputHelper); - } - - /// - /// Allows further configuration of the . - /// - /// Builder for further configuration of . - protected virtual void ConfigureTestDbContextProvider(SqlServerTestDbContextProviderBuilder builder) - { - } - - /// - public T CreateDbContext() - { - return TestCtxProvider.CreateDbContext(); - } - - /// - public T CreateDbContext(bool useMasterConnection) - { - return TestCtxProvider.CreateDbContext(useMasterConnection); - } - - /// - public virtual Task InitializeAsync() - { - return Task.CompletedTask; - } - - /// - public void Dispose() - { - if (_isDisposed) - return; - - _isDisposed = true; - - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - async Task IAsyncLifetime.DisposeAsync() - { - await DisposeAsync(); - } - - /// - public async ValueTask DisposeAsync() - { - if (_isDisposed) - return; - - _isDisposed = true; - - await DisposeAsync(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes of managed resources like the . - /// - /// Indication that the method is being called by . - protected virtual void Dispose(bool disposing) - { - DisposeAsync(disposing).AsTask().ConfigureAwait(false).GetAwaiter().GetResult(); - } - - /// - /// Disposes of managed resources like the . - /// - /// Indication that the method is being called by . - protected virtual async ValueTask DisposeAsync(bool disposing) - { - if (!disposing) - return; - - await (_testCtxProvider?.DisposeAsync() ?? ValueTask.CompletedTask); - _testCtxProvider = null; - } -} +using Xunit; +using Xunit.Abstractions; + +namespace Thinktecture.EntityFrameworkCore.Testing; + +/// +/// A base class for integration tests using EF Core along with SQL Server. +/// +/// Type of the database context. +public abstract class SqlServerDbContextIntegrationTests : ITestDbContextProvider, IAsyncLifetime + where T : DbContext +{ + private SqlServerTestDbContextProvider? _testCtxProvider; + + /// + /// Gets the which is created on the first access. + /// + protected SqlServerTestDbContextProvider TestCtxProvider => _testCtxProvider ??= TestCtxProviderBuilder.Build(); + + private readonly SqlServerTestDbContextProviderBuilder _testCtxProviderBuilder; + private bool _isDisposed; + private bool _isProviderConfigured; + + /// + /// Gets the which is created on the first access. + /// + protected SqlServerTestDbContextProviderBuilder TestCtxProviderBuilder + { + get + { + if (!_isProviderConfigured) + { + ConfigureTestDbContextProvider(_testCtxProviderBuilder); + _isProviderConfigured = true; + } + + return _testCtxProviderBuilder; + } + } + + /// + public T ArrangeDbContext => TestCtxProvider.ArrangeDbContext; + + /// + public T ActDbContext => TestCtxProvider.ActDbContext; + + /// + public T AssertDbContext => TestCtxProvider.AssertDbContext; + + /// + /// Initializes new instance of . + /// + /// Connection string to use. + /// Indication whether to create new tables with a new schema or use the existing ones. + /// Output helper to use for logging. + [Obsolete($"Use the overload with '{nameof(ITestIsolationOptions)}' instead.")] + protected SqlServerDbContextIntegrationTests( + string connectionString, + bool useSharedTables = true, + ITestOutputHelper? testOutputHelper = null) + : this(connectionString, + useSharedTables ? ITestIsolationOptions.SharedTablesAmbientTransaction : ITestIsolationOptions.RollbackMigrationsAndCleanup, + testOutputHelper) + { + } + + /// + /// Initializes new instance of . + /// + /// Connection string to use. + /// Test isolation behavior. + /// Output helper to use for logging. + protected SqlServerDbContextIntegrationTests( + string connectionString, + ITestIsolationOptions isolationOptions, + ITestOutputHelper? testOutputHelper = null) + { + _testCtxProviderBuilder = new SqlServerTestDbContextProviderBuilder(connectionString, isolationOptions) + .UseLogging(testOutputHelper); + } + + /// + /// Allows further configuration of the . + /// + /// Builder for further configuration of . + protected virtual void ConfigureTestDbContextProvider(SqlServerTestDbContextProviderBuilder builder) + { + } + + /// + public T CreateDbContext() + { + return TestCtxProvider.CreateDbContext(); + } + + /// + public T CreateDbContext(bool useMasterConnection) + { + return TestCtxProvider.CreateDbContext(useMasterConnection); + } + + /// + public virtual Task InitializeAsync() + { + return Task.CompletedTask; + } + + /// + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + async Task IAsyncLifetime.DisposeAsync() + { + await DisposeAsync(); + } + + /// + public async ValueTask DisposeAsync() + { + if (_isDisposed) + return; + + _isDisposed = true; + + await DisposeAsync(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes of managed resources like the . + /// + /// Indication that the method is being called by . + protected virtual void Dispose(bool disposing) + { + DisposeAsync(disposing).AsTask().ConfigureAwait(false).GetAwaiter().GetResult(); + } + + /// + /// Disposes of managed resources like the . + /// + /// Indication that the method is being called by . + protected virtual async ValueTask DisposeAsync(bool disposing) + { + if (!disposing) + return; + + await (_testCtxProvider?.DisposeAsync() ?? ValueTask.CompletedTask); + _testCtxProvider = null; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerLockTableOptions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerLockTableOptions.cs index 60cdbc4c..a0bed74d 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerLockTableOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerLockTableOptions.cs @@ -1,54 +1,54 @@ -namespace Thinktecture.EntityFrameworkCore.Testing; - -/// -/// Options used for locking the database during migrations and tear down. -/// -public class SqlServerLockTableOptions -{ - /// - /// Indication whether the feature is enabled or not. - /// - public bool IsEnabled { get; } - - /// - /// The name of the table. - /// Default: '__IntegrationTestIsolation' - /// - public string Name { get; set; } - - /// - /// The schema of the table. - /// - public string? Schema { get; set; } - - /// - /// Number of retries for creation of the table. - /// Default: 10 - /// - public int MaxNumberOfLockRetries { get; set; } - - /// - /// Min. delay between retries to create the table. - /// Default: 50ms - /// - public TimeSpan MinRetryDelay { get; set; } - - /// - /// Max. delay between retries to create the table. - /// Default: 300ms - /// - public TimeSpan MaxRetryDelay { get; set; } - - /// - /// Initializes new instance of . - /// - /// Indication whether the feature is enabled or not. - public SqlServerLockTableOptions(bool isEnabled) - { - IsEnabled = isEnabled; - Name = "__IntegrationTestIsolation"; - MaxNumberOfLockRetries = 10; - MinRetryDelay = TimeSpan.FromMilliseconds(50); - MaxRetryDelay = TimeSpan.FromMilliseconds(200); - } -} +namespace Thinktecture.EntityFrameworkCore.Testing; + +/// +/// Options used for locking the database during migrations and tear down. +/// +public class SqlServerLockTableOptions +{ + /// + /// Indication whether the feature is enabled or not. + /// + public bool IsEnabled { get; } + + /// + /// The name of the table. + /// Default: '__IntegrationTestIsolation' + /// + public string Name { get; set; } + + /// + /// The schema of the table. + /// + public string? Schema { get; set; } + + /// + /// Number of retries for creation of the table. + /// Default: 10 + /// + public int MaxNumberOfLockRetries { get; set; } + + /// + /// Min. delay between retries to create the table. + /// Default: 50ms + /// + public TimeSpan MinRetryDelay { get; set; } + + /// + /// Max. delay between retries to create the table. + /// Default: 300ms + /// + public TimeSpan MaxRetryDelay { get; set; } + + /// + /// Initializes new instance of . + /// + /// Indication whether the feature is enabled or not. + public SqlServerLockTableOptions(bool isEnabled) + { + IsEnabled = isEnabled; + Name = "__IntegrationTestIsolation"; + MaxNumberOfLockRetries = 10; + MinRetryDelay = TimeSpan.FromMilliseconds(50); + MaxRetryDelay = TimeSpan.FromMilliseconds(200); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProvider.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProvider.cs index 7e2498b5..e72dbe01 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProvider.cs @@ -1,357 +1,357 @@ -using System.Data; -using System.Data.Common; -using System.Linq.Expressions; -using System.Reflection; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage; -using Thinktecture.Logging; - -namespace Thinktecture.EntityFrameworkCore.Testing; - -/// -/// Provides instances of for testing purposes. -/// -public abstract class SqlServerTestDbContextProvider -{ - private static readonly object _sharedLock = new(); - - /// - /// Provides a lock object for database-wide operations like creation of tables. - /// - /// Current database context. - /// A lock object. - protected virtual object GetSharedLock(DbContext ctx) - { - return _sharedLock; - } -} - -/// -/// Provides instances of for testing purposes. -/// -/// Type of the database context. -public class SqlServerTestDbContextProvider : SqlServerTestDbContextProvider, ITestDbContextProvider - where T : DbContext -{ - private readonly object _instanceWideLock; - private readonly ITestIsolationOptions _isolationOptions; - private readonly DbContextOptions _masterDbContextOptions; - private readonly DbContextOptions _dbContextOptions; - private readonly IMigrationExecutionStrategy _migrationExecutionStrategy; - private readonly DbConnection _masterConnection; - private readonly IReadOnlyList> _contextInitializations; - private readonly Func, IDbDefaultSchema?, T?>? _contextFactory; - private readonly TestingLoggingOptions _testingLoggingOptions; - - private readonly bool _lockTableEnabled; - private readonly string _lockTableName; - private readonly string? _lockTableSchema; - private readonly int _maxNumberOfLockRetries; - private readonly TimeSpan _minRetryDelay; - private readonly TimeSpan _maxRetryDelay; - private readonly Random _random; - - private Func, IDbDefaultSchema?, T>? _defaultContextFactory; - private T? _arrangeDbContext; - private T? _actDbContext; - private T? _assertDbContext; - private IDbContextTransaction? _tx; - private bool _isAtLeastOneContextCreated; - private readonly IsolationLevel _sharedTablesIsolationLevel; - private bool _isDisposed; - - /// - public T ArrangeDbContext => _arrangeDbContext ??= CreateDbContext(true); - - /// - public T ActDbContext => _actDbContext ??= CreateDbContext(true); - - /// - public T AssertDbContext => _assertDbContext ??= CreateDbContext(true); - - /// - /// Default database schema to use. - /// - // ReSharper disable once MemberCanBePrivate.Global - public string? Schema { get; } - - /// - /// Contains executed commands if this feature was activated. - /// - public IReadOnlyCollection? ExecutedCommands { get; } - - /// - /// Log level switch. - /// - public TestingLogLevelSwitch LogLevelSwitch => _testingLoggingOptions.LogLevelSwitch; - - /// - /// Initializes a new instance of - /// - /// Options. - protected internal SqlServerTestDbContextProvider(SqlServerTestDbContextProviderOptions options) - { - ArgumentNullException.ThrowIfNull(options); - - _instanceWideLock = new object(); - Schema = options.Schema; - _sharedTablesIsolationLevel = ValidateIsolationLevel(options.SharedTablesIsolationLevel); - _isolationOptions = options.IsolationOptions; - _masterConnection = options.MasterConnection ?? throw new ArgumentException($"The '{nameof(options.MasterConnection)}' cannot be null.", nameof(options)); - _masterDbContextOptions = options.MasterDbContextOptionsBuilder.Options ?? throw new ArgumentException($"The '{nameof(options.MasterDbContextOptionsBuilder)}' cannot be null.", nameof(options)); - _dbContextOptions = options.DbContextOptionsBuilder.Options ?? throw new ArgumentException($"The '{nameof(options.DbContextOptionsBuilder)}' cannot be null.", nameof(options)); - _migrationExecutionStrategy = options.MigrationExecutionStrategy ?? throw new ArgumentException($"The '{nameof(options.MigrationExecutionStrategy)}' cannot be null.", nameof(options)); - _testingLoggingOptions = options.TestingLoggingOptions ?? throw new ArgumentException($"The '{nameof(options.TestingLoggingOptions)}' cannot be null.", nameof(options)); - _contextInitializations = options.ContextInitializations ?? throw new ArgumentException($"The '{nameof(options.ContextInitializations)}' cannot be null.", nameof(options)); - ExecutedCommands = options.ExecutedCommands; - _contextFactory = options.ContextFactory; - - _random = new Random(); - - _lockTableEnabled = options.LockTable.IsEnabled; - _lockTableName = options.LockTable.Name; - _lockTableSchema = options.LockTable.Schema; - _maxNumberOfLockRetries = options.LockTable.MaxNumberOfLockRetries; - _minRetryDelay = options.LockTable.MinRetryDelay; - _maxRetryDelay = options.LockTable.MaxRetryDelay; - } - - private static IsolationLevel ValidateIsolationLevel(IsolationLevel? isolationLevel) - { - if (!isolationLevel.HasValue) - return IsolationLevel.ReadCommitted; - - if (!Enum.IsDefined(isolationLevel.Value)) - throw new ArgumentException($"The provided isolation level '{isolationLevel}' is invalid.", nameof(isolationLevel)); - - if (isolationLevel < IsolationLevel.ReadCommitted) - throw new ArgumentException($"The isolation level '{isolationLevel}' cannot be less than '{nameof(IsolationLevel.ReadCommitted)}'.", nameof(isolationLevel)); - - return isolationLevel.Value; - } - - /// - public T CreateDbContext() - { - return CreateDbContext(false); - } - - /// - /// Creates a new . - /// - /// - /// Indication whether to use the master connection or a new one. - /// - /// A new instance of . - public T CreateDbContext(bool useMasterConnection) - { - if (!useMasterConnection && _isolationOptions.NeedsAmbientTransaction) - throw new NotSupportedException($"A database transaction cannot be shared among different connections, so the isolation of tests couldn't be guaranteed. Set 'useMasterConnection' to 'true' or 'useSharedTables' to 'false' or use '{nameof(ArrangeDbContext)}/{nameof(ActDbContext)}/{nameof(AssertDbContext)}' which use the same database connection."); - - bool isFirstCtx; - - lock (_instanceWideLock) - { - isFirstCtx = !_isAtLeastOneContextCreated; - _isAtLeastOneContextCreated = true; - } - - var options = useMasterConnection ? _masterDbContextOptions : _dbContextOptions; - var ctx = CreateDbContext(options, Schema is null ? null : new DbDefaultSchema(Schema)); - - foreach (var ctxInit in _contextInitializations) - { - ctxInit(ctx); - } - - if (isFirstCtx) - { - RunMigrations(ctx); - - if (_isolationOptions.NeedsAmbientTransaction) - _tx = BeginTransaction(ctx); - } - else if (_tx != null) - { - ctx.Database.UseTransaction(_tx.GetDbTransaction()); - } - - return ctx; - } - - /// - /// Creates a new instance of the database context. - /// - /// Options to use for creation. - /// Database schema to use. - /// A new instance of the database context. - protected virtual T CreateDbContext(DbContextOptions options, IDbDefaultSchema? schema) - { - var ctx = _contextFactory?.Invoke(options, schema) - ?? (_defaultContextFactory ??= CreateDefaultContextFactory())(options, schema); - - return ctx; - } - - private static Func, IDbDefaultSchema?, T> CreateDefaultContextFactory() - { - var optionsType = typeof(DbContextOptions); - var schemaType = typeof(IDbDefaultSchema); - var optionsParam = Expression.Parameter(optionsType); - var schemaParam = Expression.Parameter(schemaType); - Expression[]? ctorArgs = null; - - var ctor = typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, new[] { optionsType, schemaType }); - - if (ctor is not null) - { - ctorArgs = new Expression[] { optionsParam, schemaParam }; - } - else - { - ctor = typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, new[] { optionsType }); - - if (ctor is not null) - ctorArgs = new Expression[] { optionsParam }; - } - - if (ctor is null || ctorArgs is null) - { - throw new Exception($""" - Could not create an instance of type of '{typeof(T).ShortDisplayName()}' neither using constructor parameters ({typeof(DbContextOptions).ShortDisplayName()} options, {nameof(IDbDefaultSchema)} schema) nor using construct ({typeof(DbContextOptions).ShortDisplayName()} options). - Please provide the corresponding constructor or a custom factory via '{typeof(SqlServerTestDbContextProviderBuilder).ShortDisplayName()}.{nameof(SqlServerTestDbContextProviderBuilder.UseContextFactory)}'. - """); - } - - return Expression.Lambda, IDbDefaultSchema?, T>>(Expression.New(ctor, ctorArgs), optionsParam, schemaParam) - .Compile(); - } - - /// - /// Starts a new transaction. - /// - /// Database context. - /// An instance of . - protected virtual IDbContextTransaction BeginTransaction(T ctx) - { - ArgumentNullException.ThrowIfNull(ctx); - - return ctx.Database.BeginTransaction(_sharedTablesIsolationLevel); - } - - /// - /// Runs migrations for provided . - /// - /// Database context to run migrations for. - /// The provided context is null. - protected virtual void RunMigrations(T ctx) - { - ArgumentNullException.ThrowIfNull(ctx); - - // concurrent execution is not supported by EF migrations - lock (GetSharedLock(ctx)) - { - var logLevel = LogLevelSwitch.MinimumLogLevel; - - try - { - LogLevelSwitch.MinimumLogLevel = _testingLoggingOptions.MigrationLogLevel; - _migrationExecutionStrategy.Migrate(ctx); - } - finally - { - LogLevelSwitch.MinimumLogLevel = logLevel; - } - } - } - - /// - /// Rollbacks transaction if shared tables are used - /// otherwise performs cleanup according to provided . - /// - public void Dispose() - { - if (_isDisposed) - return; - - _isDisposed = true; - - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Rollbacks transaction if shared tables are used - /// otherwise performs cleanup according to provided . - /// - public async ValueTask DisposeAsync() - { - if (_isDisposed) - return; - - _isDisposed = true; - - await DisposeAsync(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes of inner resources. - /// - /// Indication whether this method is being called by the method . - protected virtual void Dispose(bool disposing) - { - DisposeAsync(disposing).AsTask().ConfigureAwait(false).GetAwaiter().GetResult(); - } - - /// - /// Disposes of inner resources. - /// - /// Indication whether this method is being called by the method . - protected virtual async ValueTask DisposeAsync(bool disposing) - { - if (!disposing) - return; - - var isAtLeastOneContextCreated = false; - - lock (_instanceWideLock) - { - if (_isAtLeastOneContextCreated) - { - isAtLeastOneContextCreated = true; - _isAtLeastOneContextCreated = false; - } - } - - if (isAtLeastOneContextCreated) - await DisposeContextsAndRollbackMigrationsAsync(default); - - _masterConnection.Dispose(); - _testingLoggingOptions.Dispose(); - } - - private async Task DisposeContextsAndRollbackMigrationsAsync(CancellationToken cancellationToken) - { - if (_tx is not null) - { - await _tx.RollbackAsync(cancellationToken); - await _tx.DisposeAsync(); - } - - if (_isolationOptions.NeedsCleanup) - { - // Create a new ctx as a last resort to rollback migrations and clean up the database - await using var ctx = _actDbContext ?? _arrangeDbContext ?? _assertDbContext ?? CreateDbContext(_masterDbContextOptions, Schema is null ? null : new DbDefaultSchema(Schema)); - await _isolationOptions.CleanupAsync(ctx, Schema, cancellationToken); - } - - await (_arrangeDbContext?.DisposeAsync() ?? ValueTask.CompletedTask); - await (_actDbContext?.DisposeAsync() ?? ValueTask.CompletedTask); - await (_assertDbContext?.DisposeAsync() ?? ValueTask.CompletedTask); - - _arrangeDbContext = null; - _actDbContext = null; - _assertDbContext = null; - } -} +using System.Data; +using System.Data.Common; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Thinktecture.Logging; + +namespace Thinktecture.EntityFrameworkCore.Testing; + +/// +/// Provides instances of for testing purposes. +/// +public abstract class SqlServerTestDbContextProvider +{ + private static readonly object _sharedLock = new(); + + /// + /// Provides a lock object for database-wide operations like creation of tables. + /// + /// Current database context. + /// A lock object. + protected virtual object GetSharedLock(DbContext ctx) + { + return _sharedLock; + } +} + +/// +/// Provides instances of for testing purposes. +/// +/// Type of the database context. +public class SqlServerTestDbContextProvider : SqlServerTestDbContextProvider, ITestDbContextProvider + where T : DbContext +{ + private readonly object _instanceWideLock; + private readonly ITestIsolationOptions _isolationOptions; + private readonly DbContextOptions _masterDbContextOptions; + private readonly DbContextOptions _dbContextOptions; + private readonly IMigrationExecutionStrategy _migrationExecutionStrategy; + private readonly DbConnection _masterConnection; + private readonly IReadOnlyList> _contextInitializations; + private readonly Func, IDbDefaultSchema?, T?>? _contextFactory; + private readonly TestingLoggingOptions _testingLoggingOptions; + + private readonly bool _lockTableEnabled; + private readonly string _lockTableName; + private readonly string? _lockTableSchema; + private readonly int _maxNumberOfLockRetries; + private readonly TimeSpan _minRetryDelay; + private readonly TimeSpan _maxRetryDelay; + private readonly Random _random; + + private Func, IDbDefaultSchema?, T>? _defaultContextFactory; + private T? _arrangeDbContext; + private T? _actDbContext; + private T? _assertDbContext; + private IDbContextTransaction? _tx; + private bool _isAtLeastOneContextCreated; + private readonly IsolationLevel _sharedTablesIsolationLevel; + private bool _isDisposed; + + /// + public T ArrangeDbContext => _arrangeDbContext ??= CreateDbContext(true); + + /// + public T ActDbContext => _actDbContext ??= CreateDbContext(true); + + /// + public T AssertDbContext => _assertDbContext ??= CreateDbContext(true); + + /// + /// Default database schema to use. + /// + // ReSharper disable once MemberCanBePrivate.Global + public string? Schema { get; } + + /// + /// Contains executed commands if this feature was activated. + /// + public IReadOnlyCollection? ExecutedCommands { get; } + + /// + /// Log level switch. + /// + public TestingLogLevelSwitch LogLevelSwitch => _testingLoggingOptions.LogLevelSwitch; + + /// + /// Initializes a new instance of + /// + /// Options. + protected internal SqlServerTestDbContextProvider(SqlServerTestDbContextProviderOptions options) + { + ArgumentNullException.ThrowIfNull(options); + + _instanceWideLock = new object(); + Schema = options.Schema; + _sharedTablesIsolationLevel = ValidateIsolationLevel(options.SharedTablesIsolationLevel); + _isolationOptions = options.IsolationOptions; + _masterConnection = options.MasterConnection ?? throw new ArgumentException($"The '{nameof(options.MasterConnection)}' cannot be null.", nameof(options)); + _masterDbContextOptions = options.MasterDbContextOptionsBuilder.Options ?? throw new ArgumentException($"The '{nameof(options.MasterDbContextOptionsBuilder)}' cannot be null.", nameof(options)); + _dbContextOptions = options.DbContextOptionsBuilder.Options ?? throw new ArgumentException($"The '{nameof(options.DbContextOptionsBuilder)}' cannot be null.", nameof(options)); + _migrationExecutionStrategy = options.MigrationExecutionStrategy ?? throw new ArgumentException($"The '{nameof(options.MigrationExecutionStrategy)}' cannot be null.", nameof(options)); + _testingLoggingOptions = options.TestingLoggingOptions ?? throw new ArgumentException($"The '{nameof(options.TestingLoggingOptions)}' cannot be null.", nameof(options)); + _contextInitializations = options.ContextInitializations ?? throw new ArgumentException($"The '{nameof(options.ContextInitializations)}' cannot be null.", nameof(options)); + ExecutedCommands = options.ExecutedCommands; + _contextFactory = options.ContextFactory; + + _random = new Random(); + + _lockTableEnabled = options.LockTable.IsEnabled; + _lockTableName = options.LockTable.Name; + _lockTableSchema = options.LockTable.Schema; + _maxNumberOfLockRetries = options.LockTable.MaxNumberOfLockRetries; + _minRetryDelay = options.LockTable.MinRetryDelay; + _maxRetryDelay = options.LockTable.MaxRetryDelay; + } + + private static IsolationLevel ValidateIsolationLevel(IsolationLevel? isolationLevel) + { + if (!isolationLevel.HasValue) + return IsolationLevel.ReadCommitted; + + if (!Enum.IsDefined(isolationLevel.Value)) + throw new ArgumentException($"The provided isolation level '{isolationLevel}' is invalid.", nameof(isolationLevel)); + + if (isolationLevel < IsolationLevel.ReadCommitted) + throw new ArgumentException($"The isolation level '{isolationLevel}' cannot be less than '{nameof(IsolationLevel.ReadCommitted)}'.", nameof(isolationLevel)); + + return isolationLevel.Value; + } + + /// + public T CreateDbContext() + { + return CreateDbContext(false); + } + + /// + /// Creates a new . + /// + /// + /// Indication whether to use the master connection or a new one. + /// + /// A new instance of . + public T CreateDbContext(bool useMasterConnection) + { + if (!useMasterConnection && _isolationOptions.NeedsAmbientTransaction) + throw new NotSupportedException($"A database transaction cannot be shared among different connections, so the isolation of tests couldn't be guaranteed. Set 'useMasterConnection' to 'true' or 'useSharedTables' to 'false' or use '{nameof(ArrangeDbContext)}/{nameof(ActDbContext)}/{nameof(AssertDbContext)}' which use the same database connection."); + + bool isFirstCtx; + + lock (_instanceWideLock) + { + isFirstCtx = !_isAtLeastOneContextCreated; + _isAtLeastOneContextCreated = true; + } + + var options = useMasterConnection ? _masterDbContextOptions : _dbContextOptions; + var ctx = CreateDbContext(options, Schema is null ? null : new DbDefaultSchema(Schema)); + + foreach (var ctxInit in _contextInitializations) + { + ctxInit(ctx); + } + + if (isFirstCtx) + { + RunMigrations(ctx); + + if (_isolationOptions.NeedsAmbientTransaction) + _tx = BeginTransaction(ctx); + } + else if (_tx != null) + { + ctx.Database.UseTransaction(_tx.GetDbTransaction()); + } + + return ctx; + } + + /// + /// Creates a new instance of the database context. + /// + /// Options to use for creation. + /// Database schema to use. + /// A new instance of the database context. + protected virtual T CreateDbContext(DbContextOptions options, IDbDefaultSchema? schema) + { + var ctx = _contextFactory?.Invoke(options, schema) + ?? (_defaultContextFactory ??= CreateDefaultContextFactory())(options, schema); + + return ctx; + } + + private static Func, IDbDefaultSchema?, T> CreateDefaultContextFactory() + { + var optionsType = typeof(DbContextOptions); + var schemaType = typeof(IDbDefaultSchema); + var optionsParam = Expression.Parameter(optionsType); + var schemaParam = Expression.Parameter(schemaType); + Expression[]? ctorArgs = null; + + var ctor = typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, new[] { optionsType, schemaType }); + + if (ctor is not null) + { + ctorArgs = new Expression[] { optionsParam, schemaParam }; + } + else + { + ctor = typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, new[] { optionsType }); + + if (ctor is not null) + ctorArgs = new Expression[] { optionsParam }; + } + + if (ctor is null || ctorArgs is null) + { + throw new Exception($""" + Could not create an instance of type of '{typeof(T).ShortDisplayName()}' neither using constructor parameters ({typeof(DbContextOptions).ShortDisplayName()} options, {nameof(IDbDefaultSchema)} schema) nor using construct ({typeof(DbContextOptions).ShortDisplayName()} options). + Please provide the corresponding constructor or a custom factory via '{typeof(SqlServerTestDbContextProviderBuilder).ShortDisplayName()}.{nameof(SqlServerTestDbContextProviderBuilder.UseContextFactory)}'. + """); + } + + return Expression.Lambda, IDbDefaultSchema?, T>>(Expression.New(ctor, ctorArgs), optionsParam, schemaParam) + .Compile(); + } + + /// + /// Starts a new transaction. + /// + /// Database context. + /// An instance of . + protected virtual IDbContextTransaction BeginTransaction(T ctx) + { + ArgumentNullException.ThrowIfNull(ctx); + + return ctx.Database.BeginTransaction(_sharedTablesIsolationLevel); + } + + /// + /// Runs migrations for provided . + /// + /// Database context to run migrations for. + /// The provided context is null. + protected virtual void RunMigrations(T ctx) + { + ArgumentNullException.ThrowIfNull(ctx); + + // concurrent execution is not supported by EF migrations + lock (GetSharedLock(ctx)) + { + var logLevel = LogLevelSwitch.MinimumLogLevel; + + try + { + LogLevelSwitch.MinimumLogLevel = _testingLoggingOptions.MigrationLogLevel; + _migrationExecutionStrategy.Migrate(ctx); + } + finally + { + LogLevelSwitch.MinimumLogLevel = logLevel; + } + } + } + + /// + /// Rollbacks transaction if shared tables are used + /// otherwise performs cleanup according to provided . + /// + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Rollbacks transaction if shared tables are used + /// otherwise performs cleanup according to provided . + /// + public async ValueTask DisposeAsync() + { + if (_isDisposed) + return; + + _isDisposed = true; + + await DisposeAsync(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes of inner resources. + /// + /// Indication whether this method is being called by the method . + protected virtual void Dispose(bool disposing) + { + DisposeAsync(disposing).AsTask().ConfigureAwait(false).GetAwaiter().GetResult(); + } + + /// + /// Disposes of inner resources. + /// + /// Indication whether this method is being called by the method . + protected virtual async ValueTask DisposeAsync(bool disposing) + { + if (!disposing) + return; + + var isAtLeastOneContextCreated = false; + + lock (_instanceWideLock) + { + if (_isAtLeastOneContextCreated) + { + isAtLeastOneContextCreated = true; + _isAtLeastOneContextCreated = false; + } + } + + if (isAtLeastOneContextCreated) + await DisposeContextsAndRollbackMigrationsAsync(default); + + _masterConnection.Dispose(); + _testingLoggingOptions.Dispose(); + } + + private async Task DisposeContextsAndRollbackMigrationsAsync(CancellationToken cancellationToken) + { + if (_tx is not null) + { + await _tx.RollbackAsync(cancellationToken); + await _tx.DisposeAsync(); + } + + if (_isolationOptions.NeedsCleanup) + { + // Create a new ctx as a last resort to rollback migrations and clean up the database + await using var ctx = _actDbContext ?? _arrangeDbContext ?? _assertDbContext ?? CreateDbContext(_masterDbContextOptions, Schema is null ? null : new DbDefaultSchema(Schema)); + await _isolationOptions.CleanupAsync(ctx, Schema, cancellationToken); + } + + await (_arrangeDbContext?.DisposeAsync() ?? ValueTask.CompletedTask); + await (_actDbContext?.DisposeAsync() ?? ValueTask.CompletedTask); + await (_assertDbContext?.DisposeAsync() ?? ValueTask.CompletedTask); + + _arrangeDbContext = null; + _actDbContext = null; + _assertDbContext = null; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProviderBuilder.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProviderBuilder.cs index 3cb97097..599aa089 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProviderBuilder.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProviderBuilder.cs @@ -1,420 +1,420 @@ -using System.Data; -using System.Data.Common; -using System.Globalization; -using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.Extensions.Logging; -using Thinktecture.EntityFrameworkCore.Migrations; -using Xunit.Abstractions; - -namespace Thinktecture.EntityFrameworkCore.Testing; - -/// -/// Builder for the . -/// -/// Type of the . -public class SqlServerTestDbContextProviderBuilder : TestDbContextProviderBuilder - where T : DbContext -{ - private const string _HISTORY_TABLE_NAME = "__EFMigrationsHistory"; - - private readonly string _connectionString; - private readonly ITestIsolationOptions _isolationOptions; - private readonly List, string?>> _configuresOptionsCollection; - private readonly List> _configuresSqlServerOptionsCollection; - private readonly List> _ctxInitializations; - - private bool _useThinktectureSqlServerMigrationsSqlGenerator = true; - private string? _sharedTablesSchema; - private IsolationLevel? _sharedTablesIsolationLevel; - private SqlServerLockTableOptions? _lockTable; - private Func, IDbDefaultSchema?, T?>? _contextFactory; - private Func, SqlServerTestDbContextProvider?>? _providerFactory; - - /// - /// Initializes new instance of . - /// - /// Connection string to use. - /// Indication whether to create new tables with a new schema or use the existing ones. - [Obsolete($"Use the overload with '{nameof(ITestIsolationOptions)}' instead.")] - public SqlServerTestDbContextProviderBuilder(string connectionString, bool useSharedTables) - : this(connectionString, useSharedTables ? ITestIsolationOptions.SharedTablesAmbientTransaction : ITestIsolationOptions.RollbackMigrationsAndCleanup) - { - } - - /// - /// Initializes new instance of . - /// - /// Connection string to use. - /// Test isolation behavior. - public SqlServerTestDbContextProviderBuilder(string connectionString, ITestIsolationOptions isolationOptions) - { - _connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString)); - _isolationOptions = isolationOptions; - _configuresOptionsCollection = new List, string?>>(); - _configuresSqlServerOptionsCollection = new List>(); - _ctxInitializations = new List>(); - } - - /// - /// Disables locking of the database during migrations and tear down. - /// - /// Current builder for chaining - public SqlServerTestDbContextProviderBuilder DisableLockingDuringDDL() - { - _lockTable = new SqlServerLockTableOptions(false); - - return this; - } - - /// - /// Enables locking of the database during migrations and tear down. - /// This feature is enabled by default. - /// - /// The name of the lock table. - /// The schema of the lock table. - /// Number of retries for creation of the lock table. - /// Minimum delay between retries. - /// Maximum delay between retries. - /// Current builder for chaining - public SqlServerTestDbContextProviderBuilder UseLockingDuringDDL( - string tableName, - string? schema, - int maxNumberOfLockRetries = 10, - TimeSpan? minRetryDelay = null, - TimeSpan? maxRetryDelay = null) - { - _lockTable = new SqlServerLockTableOptions(true) - { - Name = tableName, - Schema = schema, - MaxNumberOfLockRetries = maxNumberOfLockRetries - }; - - if (minRetryDelay.HasValue) - _lockTable.MinRetryDelay = minRetryDelay.Value; - - if (maxRetryDelay.HasValue) - _lockTable.MaxRetryDelay = maxRetryDelay.Value; - - return this; - } - - /// - /// Specifies the isolation level to use with shared tables. - /// Default is . - /// - /// Isolation level to use. - /// Current builder for chaining - public SqlServerTestDbContextProviderBuilder UseSharedTablesIsolationLevel(IsolationLevel sharedTablesIsolationLevel) - { - _sharedTablesIsolationLevel = sharedTablesIsolationLevel; - - return this; - } - - /// - /// Specifies the migration strategy to use. - /// Default is . - /// - /// Migration strategy to use. - /// Current builder for chaining - public new SqlServerTestDbContextProviderBuilder UseMigrationExecutionStrategy(IMigrationExecutionStrategy migrationExecutionStrategy) - { - base.UseMigrationExecutionStrategy(migrationExecutionStrategy); - - return this; - } - - /// - /// Sets the logger factory to be used by EF. - /// - /// Logger factory to use. - /// Enables or disables sensitive data logging. - /// Current builder for chaining. - public new SqlServerTestDbContextProviderBuilder UseLogging( - ILoggerFactory? loggerFactory, - bool enableSensitiveDataLogging = true) - { - base.UseLogging(loggerFactory, enableSensitiveDataLogging); - - return this; - } - - /// - /// Sets output helper to be used by Serilog which is passed to EF. - /// - /// XUnit output. - /// Enables or disables sensitive data logging. - /// The serilog output template. - /// Current builder for chaining. - public new SqlServerTestDbContextProviderBuilder UseLogging( - ITestOutputHelper? testOutputHelper, - bool enableSensitiveDataLogging = true, - string? outputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}") - { - base.UseLogging(testOutputHelper, enableSensitiveDataLogging, outputTemplate); - - return this; - } - - /// - /// Sets the log level during migrations. - /// - /// Minimum log level to use during migrations. - /// Current builder for chaining. - public new SqlServerTestDbContextProviderBuilder UseMigrationLogLevel(LogLevel logLevel) - { - base.UseMigrationLogLevel(logLevel); - - return this; - } - - /// - /// Allows further configuration of the . - /// - /// Callback is called with the current and the database schema. - /// Current builder for chaining. - public SqlServerTestDbContextProviderBuilder ConfigureOptions(Action, string?> configure) - { - ArgumentNullException.ThrowIfNull(configure); - - _configuresOptionsCollection.Add(configure); - - return this; - } - - /// - /// Allows further configuration of the . - /// - /// Callback is called with the current and the database schema. - /// Current builder for chaining. - public SqlServerTestDbContextProviderBuilder ConfigureSqlServerOptions(Action configure) - { - ArgumentNullException.ThrowIfNull(configure); - - _configuresSqlServerOptionsCollection.Add(configure); - - return this; - } - - /// - /// Disables EF model cache. - /// - /// Current builder for chaining. - public new SqlServerTestDbContextProviderBuilder DisableModelCache(bool disableModelCache = true) - { - base.DisableModelCache(disableModelCache); - - return this; - } - - /// - /// Indication whether the should be used or not. - /// - /// - /// Current builder for chaining. - public SqlServerTestDbContextProviderBuilder UseThinktectureSqlServerMigrationsSqlGenerator(bool useThinktectureSqlServerMigrationsSqlGenerator = true) - { - _useThinktectureSqlServerMigrationsSqlGenerator = useThinktectureSqlServerMigrationsSqlGenerator; - - return this; - } - - /// - /// Indication whether collect executed commands or not. - /// - /// Current builder for chaining - public new SqlServerTestDbContextProviderBuilder CollectExecutedCommands(bool collectExecutedCommands = true) - { - base.CollectExecutedCommands(collectExecutedCommands); - - return this; - } - - /// - /// Changes the schema if "useSharedTables" is set to true. - /// Default schema is "tests". - /// - /// Schema to use - /// Current builder for chaining. - public SqlServerTestDbContextProviderBuilder UseSharedTableSchema(string schema) - { - if (String.IsNullOrWhiteSpace(schema)) - throw new ArgumentException("Schema cannot be empty.", nameof(schema)); - - _sharedTablesSchema = schema; - - return this; - } - - /// - /// Provides a callback to execute on every creation of a new . - /// - /// Initialization. - /// Current builder for chaining. - public SqlServerTestDbContextProviderBuilder InitializeContext(Action initialize) - { - ArgumentNullException.ThrowIfNull(initialize); - - _ctxInitializations.Add(initialize); - - return this; - } - - /// - /// Provides a custom factory to create . - /// - /// Factory to create the context of type . - /// Current builder for chaining. - public SqlServerTestDbContextProviderBuilder UseContextFactory(Func, IDbDefaultSchema?, T?>? contextFactory) - { - _contextFactory = contextFactory; - - return this; - } - - /// - /// Delegates the creation of to the provided . - /// - /// Factory to use for creation of . - /// Current builder for chaining. - public SqlServerTestDbContextProviderBuilder UseProviderFactory(Func, SqlServerTestDbContextProvider?>? providerFactory) - { - _providerFactory = providerFactory; - - return this; - } - - /// - /// Gets/generates schema to be used. - /// - /// Indication whether a new schema should be generated or a shared one. - /// A database schema. - [Obsolete($"Use the overload with '{nameof(ITestIsolationOptions)}' instead.")] - protected virtual string? DetermineSchema(bool useSharedTables) - { - return DetermineSchema(useSharedTables ? ITestIsolationOptions.SharedTablesAmbientTransaction : ITestIsolationOptions.RollbackMigrationsAndCleanup); - } - - /// - /// Gets/generates schema to be used. - /// - /// Test isolation behavior. - /// A database schema. - protected virtual string? DetermineSchema(ITestIsolationOptions isolationOptions) - { - return !isolationOptions.NeedsUniqueSchema - ? _sharedTablesSchema - : Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); - } - - /// - /// Creates and configures the - /// - /// Database connection to use. - /// Default database schema to use. - /// An instance of - public virtual DbContextOptionsBuilder CreateOptionsBuilder( - DbConnection? connection, - string? schema) - { - var loggingOptions = CreateLoggingOptions(); - var state = new TestDbContextProviderBuilderState(loggingOptions); - - return CreateOptionsBuilder(state, connection, schema); - } - - /// - /// Creates and configures the - /// - /// Current building state. - /// Database connection to use. - /// Default database schema to use. - /// An instance of - protected virtual DbContextOptionsBuilder CreateOptionsBuilder( - TestDbContextProviderBuilderState state, - DbConnection? connection, - string? schema) - { - var builder = new DbContextOptionsBuilder(); - - if (connection is null) - { - builder.UseSqlServer(_connectionString, optionsBuilder => ConfigureSqlServer(optionsBuilder, schema)); - } - else - { - builder.UseSqlServer(connection, optionsBuilder => ConfigureSqlServer(optionsBuilder, schema)); - } - - if (schema is not null) - builder.AddSchemaRespectingComponents(); - - ApplyDefaultConfiguration(state, builder); - - _configuresOptionsCollection.ForEach(configure => configure(builder, schema)); - - return builder; - } - - /// - /// Configures SQL Server options. - /// - /// A builder for configuration of the options. - /// Schema to use - /// The is null. - protected virtual void ConfigureSqlServer(SqlServerDbContextOptionsBuilder builder, string? schema) - { - ArgumentNullException.ThrowIfNull(builder); - - builder.MigrationsHistoryTable(_HISTORY_TABLE_NAME, schema); - - if (_useThinktectureSqlServerMigrationsSqlGenerator) - builder.UseThinktectureSqlServerMigrationsSqlGenerator(); - - _configuresSqlServerOptionsCollection.ForEach(configure => configure(builder, schema)); - } - - /// - /// Builds the . - /// - /// A new instance of . - public SqlServerTestDbContextProvider Build() - { - // create all dependencies immediately to decouple the provider from this builder - - var schema = DetermineSchema(_isolationOptions); - var masterConnection = new SqlConnection(_connectionString); - - try - { - var loggingOptions = CreateLoggingOptions(); - var state = new TestDbContextProviderBuilderState(loggingOptions); - var masterDbContextOptionsBuilder = CreateOptionsBuilder(state, masterConnection, schema); - var dbContextOptionsBuilder = CreateOptionsBuilder(state, null, schema); - - var options = new SqlServerTestDbContextProviderOptions(masterConnection, - state.MigrationExecutionStrategy ?? IMigrationExecutionStrategy.Migrations, - masterDbContextOptionsBuilder, - dbContextOptionsBuilder, - loggingOptions, - _ctxInitializations.ToList(), - schema) - { - IsolationOptions = _isolationOptions, - ContextFactory = _contextFactory, - ExecutedCommands = state.CommandCapturingInterceptor?.Commands, - SharedTablesIsolationLevel = _sharedTablesIsolationLevel, - LockTable = _lockTable - }; - - return _providerFactory?.Invoke(options) ?? new SqlServerTestDbContextProvider(options); - } - catch - { - masterConnection.Dispose(); - throw; - } - } -} +using System.Data; +using System.Data.Common; +using System.Globalization; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.Extensions.Logging; +using Thinktecture.EntityFrameworkCore.Migrations; +using Xunit.Abstractions; + +namespace Thinktecture.EntityFrameworkCore.Testing; + +/// +/// Builder for the . +/// +/// Type of the . +public class SqlServerTestDbContextProviderBuilder : TestDbContextProviderBuilder + where T : DbContext +{ + private const string _HISTORY_TABLE_NAME = "__EFMigrationsHistory"; + + private readonly string _connectionString; + private readonly ITestIsolationOptions _isolationOptions; + private readonly List, string?>> _configuresOptionsCollection; + private readonly List> _configuresSqlServerOptionsCollection; + private readonly List> _ctxInitializations; + + private bool _useThinktectureSqlServerMigrationsSqlGenerator = true; + private string? _sharedTablesSchema; + private IsolationLevel? _sharedTablesIsolationLevel; + private SqlServerLockTableOptions? _lockTable; + private Func, IDbDefaultSchema?, T?>? _contextFactory; + private Func, SqlServerTestDbContextProvider?>? _providerFactory; + + /// + /// Initializes new instance of . + /// + /// Connection string to use. + /// Indication whether to create new tables with a new schema or use the existing ones. + [Obsolete($"Use the overload with '{nameof(ITestIsolationOptions)}' instead.")] + public SqlServerTestDbContextProviderBuilder(string connectionString, bool useSharedTables) + : this(connectionString, useSharedTables ? ITestIsolationOptions.SharedTablesAmbientTransaction : ITestIsolationOptions.RollbackMigrationsAndCleanup) + { + } + + /// + /// Initializes new instance of . + /// + /// Connection string to use. + /// Test isolation behavior. + public SqlServerTestDbContextProviderBuilder(string connectionString, ITestIsolationOptions isolationOptions) + { + _connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString)); + _isolationOptions = isolationOptions; + _configuresOptionsCollection = new List, string?>>(); + _configuresSqlServerOptionsCollection = new List>(); + _ctxInitializations = new List>(); + } + + /// + /// Disables locking of the database during migrations and tear down. + /// + /// Current builder for chaining + public SqlServerTestDbContextProviderBuilder DisableLockingDuringDDL() + { + _lockTable = new SqlServerLockTableOptions(false); + + return this; + } + + /// + /// Enables locking of the database during migrations and tear down. + /// This feature is enabled by default. + /// + /// The name of the lock table. + /// The schema of the lock table. + /// Number of retries for creation of the lock table. + /// Minimum delay between retries. + /// Maximum delay between retries. + /// Current builder for chaining + public SqlServerTestDbContextProviderBuilder UseLockingDuringDDL( + string tableName, + string? schema, + int maxNumberOfLockRetries = 10, + TimeSpan? minRetryDelay = null, + TimeSpan? maxRetryDelay = null) + { + _lockTable = new SqlServerLockTableOptions(true) + { + Name = tableName, + Schema = schema, + MaxNumberOfLockRetries = maxNumberOfLockRetries + }; + + if (minRetryDelay.HasValue) + _lockTable.MinRetryDelay = minRetryDelay.Value; + + if (maxRetryDelay.HasValue) + _lockTable.MaxRetryDelay = maxRetryDelay.Value; + + return this; + } + + /// + /// Specifies the isolation level to use with shared tables. + /// Default is . + /// + /// Isolation level to use. + /// Current builder for chaining + public SqlServerTestDbContextProviderBuilder UseSharedTablesIsolationLevel(IsolationLevel sharedTablesIsolationLevel) + { + _sharedTablesIsolationLevel = sharedTablesIsolationLevel; + + return this; + } + + /// + /// Specifies the migration strategy to use. + /// Default is . + /// + /// Migration strategy to use. + /// Current builder for chaining + public new SqlServerTestDbContextProviderBuilder UseMigrationExecutionStrategy(IMigrationExecutionStrategy migrationExecutionStrategy) + { + base.UseMigrationExecutionStrategy(migrationExecutionStrategy); + + return this; + } + + /// + /// Sets the logger factory to be used by EF. + /// + /// Logger factory to use. + /// Enables or disables sensitive data logging. + /// Current builder for chaining. + public new SqlServerTestDbContextProviderBuilder UseLogging( + ILoggerFactory? loggerFactory, + bool enableSensitiveDataLogging = true) + { + base.UseLogging(loggerFactory, enableSensitiveDataLogging); + + return this; + } + + /// + /// Sets output helper to be used by Serilog which is passed to EF. + /// + /// XUnit output. + /// Enables or disables sensitive data logging. + /// The serilog output template. + /// Current builder for chaining. + public new SqlServerTestDbContextProviderBuilder UseLogging( + ITestOutputHelper? testOutputHelper, + bool enableSensitiveDataLogging = true, + string? outputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}") + { + base.UseLogging(testOutputHelper, enableSensitiveDataLogging, outputTemplate); + + return this; + } + + /// + /// Sets the log level during migrations. + /// + /// Minimum log level to use during migrations. + /// Current builder for chaining. + public new SqlServerTestDbContextProviderBuilder UseMigrationLogLevel(LogLevel logLevel) + { + base.UseMigrationLogLevel(logLevel); + + return this; + } + + /// + /// Allows further configuration of the . + /// + /// Callback is called with the current and the database schema. + /// Current builder for chaining. + public SqlServerTestDbContextProviderBuilder ConfigureOptions(Action, string?> configure) + { + ArgumentNullException.ThrowIfNull(configure); + + _configuresOptionsCollection.Add(configure); + + return this; + } + + /// + /// Allows further configuration of the . + /// + /// Callback is called with the current and the database schema. + /// Current builder for chaining. + public SqlServerTestDbContextProviderBuilder ConfigureSqlServerOptions(Action configure) + { + ArgumentNullException.ThrowIfNull(configure); + + _configuresSqlServerOptionsCollection.Add(configure); + + return this; + } + + /// + /// Disables EF model cache. + /// + /// Current builder for chaining. + public new SqlServerTestDbContextProviderBuilder DisableModelCache(bool disableModelCache = true) + { + base.DisableModelCache(disableModelCache); + + return this; + } + + /// + /// Indication whether the should be used or not. + /// + /// + /// Current builder for chaining. + public SqlServerTestDbContextProviderBuilder UseThinktectureSqlServerMigrationsSqlGenerator(bool useThinktectureSqlServerMigrationsSqlGenerator = true) + { + _useThinktectureSqlServerMigrationsSqlGenerator = useThinktectureSqlServerMigrationsSqlGenerator; + + return this; + } + + /// + /// Indication whether collect executed commands or not. + /// + /// Current builder for chaining + public new SqlServerTestDbContextProviderBuilder CollectExecutedCommands(bool collectExecutedCommands = true) + { + base.CollectExecutedCommands(collectExecutedCommands); + + return this; + } + + /// + /// Changes the schema if "useSharedTables" is set to true. + /// Default schema is "tests". + /// + /// Schema to use + /// Current builder for chaining. + public SqlServerTestDbContextProviderBuilder UseSharedTableSchema(string schema) + { + if (String.IsNullOrWhiteSpace(schema)) + throw new ArgumentException("Schema cannot be empty.", nameof(schema)); + + _sharedTablesSchema = schema; + + return this; + } + + /// + /// Provides a callback to execute on every creation of a new . + /// + /// Initialization. + /// Current builder for chaining. + public SqlServerTestDbContextProviderBuilder InitializeContext(Action initialize) + { + ArgumentNullException.ThrowIfNull(initialize); + + _ctxInitializations.Add(initialize); + + return this; + } + + /// + /// Provides a custom factory to create . + /// + /// Factory to create the context of type . + /// Current builder for chaining. + public SqlServerTestDbContextProviderBuilder UseContextFactory(Func, IDbDefaultSchema?, T?>? contextFactory) + { + _contextFactory = contextFactory; + + return this; + } + + /// + /// Delegates the creation of to the provided . + /// + /// Factory to use for creation of . + /// Current builder for chaining. + public SqlServerTestDbContextProviderBuilder UseProviderFactory(Func, SqlServerTestDbContextProvider?>? providerFactory) + { + _providerFactory = providerFactory; + + return this; + } + + /// + /// Gets/generates schema to be used. + /// + /// Indication whether a new schema should be generated or a shared one. + /// A database schema. + [Obsolete($"Use the overload with '{nameof(ITestIsolationOptions)}' instead.")] + protected virtual string? DetermineSchema(bool useSharedTables) + { + return DetermineSchema(useSharedTables ? ITestIsolationOptions.SharedTablesAmbientTransaction : ITestIsolationOptions.RollbackMigrationsAndCleanup); + } + + /// + /// Gets/generates schema to be used. + /// + /// Test isolation behavior. + /// A database schema. + protected virtual string? DetermineSchema(ITestIsolationOptions isolationOptions) + { + return !isolationOptions.NeedsUniqueSchema + ? _sharedTablesSchema + : Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); + } + + /// + /// Creates and configures the + /// + /// Database connection to use. + /// Default database schema to use. + /// An instance of + public virtual DbContextOptionsBuilder CreateOptionsBuilder( + DbConnection? connection, + string? schema) + { + var loggingOptions = CreateLoggingOptions(); + var state = new TestDbContextProviderBuilderState(loggingOptions); + + return CreateOptionsBuilder(state, connection, schema); + } + + /// + /// Creates and configures the + /// + /// Current building state. + /// Database connection to use. + /// Default database schema to use. + /// An instance of + protected virtual DbContextOptionsBuilder CreateOptionsBuilder( + TestDbContextProviderBuilderState state, + DbConnection? connection, + string? schema) + { + var builder = new DbContextOptionsBuilder(); + + if (connection is null) + { + builder.UseSqlServer(_connectionString, optionsBuilder => ConfigureSqlServer(optionsBuilder, schema)); + } + else + { + builder.UseSqlServer(connection, optionsBuilder => ConfigureSqlServer(optionsBuilder, schema)); + } + + if (schema is not null) + builder.AddSchemaRespectingComponents(); + + ApplyDefaultConfiguration(state, builder); + + _configuresOptionsCollection.ForEach(configure => configure(builder, schema)); + + return builder; + } + + /// + /// Configures SQL Server options. + /// + /// A builder for configuration of the options. + /// Schema to use + /// The is null. + protected virtual void ConfigureSqlServer(SqlServerDbContextOptionsBuilder builder, string? schema) + { + ArgumentNullException.ThrowIfNull(builder); + + builder.MigrationsHistoryTable(_HISTORY_TABLE_NAME, schema); + + if (_useThinktectureSqlServerMigrationsSqlGenerator) + builder.UseThinktectureSqlServerMigrationsSqlGenerator(); + + _configuresSqlServerOptionsCollection.ForEach(configure => configure(builder, schema)); + } + + /// + /// Builds the . + /// + /// A new instance of . + public SqlServerTestDbContextProvider Build() + { + // create all dependencies immediately to decouple the provider from this builder + + var schema = DetermineSchema(_isolationOptions); + var masterConnection = new SqlConnection(_connectionString); + + try + { + var loggingOptions = CreateLoggingOptions(); + var state = new TestDbContextProviderBuilderState(loggingOptions); + var masterDbContextOptionsBuilder = CreateOptionsBuilder(state, masterConnection, schema); + var dbContextOptionsBuilder = CreateOptionsBuilder(state, null, schema); + + var options = new SqlServerTestDbContextProviderOptions(masterConnection, + state.MigrationExecutionStrategy ?? IMigrationExecutionStrategy.Migrations, + masterDbContextOptionsBuilder, + dbContextOptionsBuilder, + loggingOptions, + _ctxInitializations.ToList(), + schema) + { + IsolationOptions = _isolationOptions, + ContextFactory = _contextFactory, + ExecutedCommands = state.CommandCapturingInterceptor?.Commands, + SharedTablesIsolationLevel = _sharedTablesIsolationLevel, + LockTable = _lockTable + }; + + return _providerFactory?.Invoke(options) ?? new SqlServerTestDbContextProvider(options); + } + catch + { + masterConnection.Dispose(); + throw; + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProviderOptions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProviderOptions.cs index b4fdae55..ac70f305 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProviderOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProviderOptions.cs @@ -1,74 +1,74 @@ -using System.Data; -using System.Diagnostics.CodeAnalysis; -using Microsoft.Data.SqlClient; -using Thinktecture.Logging; - -namespace Thinktecture.EntityFrameworkCore.Testing; - -/// -/// Options for the . -/// -/// -public class SqlServerTestDbContextProviderOptions : TestDbContextProviderOptions - where T : DbContext -{ - /// - /// Master database connection. - /// - public new SqlConnection MasterConnection => (SqlConnection)base.MasterConnection; - - private readonly ITestIsolationOptions? _isolationOptions; - - /// - /// Test isolation behavior. - /// - public ITestIsolationOptions IsolationOptions - { - get => _isolationOptions ?? ITestIsolationOptions.SharedTablesAmbientTransaction; - init => _isolationOptions = value; - } - - /// - /// Default database schema to use. - /// - public string? Schema { get; } - - /// - /// A factory method for creation of contexts of type . - /// - public Func, IDbDefaultSchema?, T?>? ContextFactory { get; init; } - - /// - /// Isolation level to be used with shared tables. - /// Default is . - /// - public IsolationLevel? SharedTablesIsolationLevel { get; init; } - - private SqlServerLockTableOptions? _lockTable; - - /// - /// Options used for locking the database during migrations and tear down. - /// - [AllowNull] - public SqlServerLockTableOptions LockTable - { - get => _lockTable ??= new SqlServerLockTableOptions(true); - init => _lockTable = value; - } - - /// - /// Initializes new instance of . - /// - public SqlServerTestDbContextProviderOptions( - SqlConnection masterConnection, - IMigrationExecutionStrategy migrationExecutionStrategy, - DbContextOptionsBuilder masterDbContextOptionsBuilder, - DbContextOptionsBuilder dbContextOptionsBuilder, - TestingLoggingOptions testingLoggingOptions, - IReadOnlyList> contextInitializations, - string? schema) - : base(masterConnection, migrationExecutionStrategy, masterDbContextOptionsBuilder, dbContextOptionsBuilder, testingLoggingOptions, contextInitializations) - { - Schema = schema; - } -} +using System.Data; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Data.SqlClient; +using Thinktecture.Logging; + +namespace Thinktecture.EntityFrameworkCore.Testing; + +/// +/// Options for the . +/// +/// +public class SqlServerTestDbContextProviderOptions : TestDbContextProviderOptions + where T : DbContext +{ + /// + /// Master database connection. + /// + public new SqlConnection MasterConnection => (SqlConnection)base.MasterConnection; + + private readonly ITestIsolationOptions? _isolationOptions; + + /// + /// Test isolation behavior. + /// + public ITestIsolationOptions IsolationOptions + { + get => _isolationOptions ?? ITestIsolationOptions.SharedTablesAmbientTransaction; + init => _isolationOptions = value; + } + + /// + /// Default database schema to use. + /// + public string? Schema { get; } + + /// + /// A factory method for creation of contexts of type . + /// + public Func, IDbDefaultSchema?, T?>? ContextFactory { get; init; } + + /// + /// Isolation level to be used with shared tables. + /// Default is . + /// + public IsolationLevel? SharedTablesIsolationLevel { get; init; } + + private SqlServerLockTableOptions? _lockTable; + + /// + /// Options used for locking the database during migrations and tear down. + /// + [AllowNull] + public SqlServerLockTableOptions LockTable + { + get => _lockTable ??= new SqlServerLockTableOptions(true); + init => _lockTable = value; + } + + /// + /// Initializes new instance of . + /// + public SqlServerTestDbContextProviderOptions( + SqlConnection masterConnection, + IMigrationExecutionStrategy migrationExecutionStrategy, + DbContextOptionsBuilder masterDbContextOptionsBuilder, + DbContextOptionsBuilder dbContextOptionsBuilder, + TestingLoggingOptions testingLoggingOptions, + IReadOnlyList> contextInitializations, + string? schema) + : base(masterConnection, migrationExecutionStrategy, masterDbContextOptionsBuilder, dbContextOptionsBuilder, testingLoggingOptions, contextInitializations) + { + Schema = schema; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/Thinktecture.EntityFrameworkCore.SqlServer.Testing.csproj b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/Thinktecture.EntityFrameworkCore.SqlServer.Testing.csproj index 0cc4eece..1b2488c1 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/Thinktecture.EntityFrameworkCore.SqlServer.Testing.csproj +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer.Testing/Thinktecture.EntityFrameworkCore.SqlServer.Testing.csproj @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/BulkInsertContext.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/BulkInsertContext.cs index d6f3f89c..8f1f651b 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/BulkInsertContext.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/BulkInsertContext.cs @@ -1,78 +1,78 @@ -using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -internal class BulkInsertContext : ISqlServerBulkOperationContext -{ - private readonly IReadOnlyList _externalProperties; - private readonly DbContext _ctx; - private readonly IEntityDataReaderFactory _readerFactory; - - public SqlConnection Connection { get; } - public SqlTransaction? Transaction { get; } - public IReadOnlyList Properties { get; } - public SqlServerBulkInsertOptions Options { get; } - public bool HasExternalProperties => _externalProperties.Count != 0; - - public IEntityDataReader CreateReader(IEnumerable entities) - { - return _readerFactory.Create(_ctx, entities, Properties, HasExternalProperties); - } - - public BulkInsertContext( - DbContext ctx, - IEntityDataReaderFactory factory, - SqlConnection connection, - SqlTransaction? transaction, - SqlServerBulkInsertOptions options, - IReadOnlyList properties) - { - _ctx = ctx; - _readerFactory = factory; - Connection = connection; - Transaction = transaction; - var (ownProperties, externalProperties) = properties.SeparateProperties(); - Properties = ownProperties; - _externalProperties = externalProperties; - Options = options; - } - - public IReadOnlyList GetChildren(IReadOnlyList entities) - { - if (!HasExternalProperties) - return Array.Empty(); - - var childCtx = new List(); - - foreach (var (navigation, ownedEntities, properties) in _externalProperties.GroupExternalProperties(entities)) - { - var ownedTypeCtx = new OwnedTypeBulkInsertContext(_ctx, _readerFactory, Connection, Transaction, Options, properties, navigation.TargetEntityType, ownedEntities); - childCtx.Add(ownedTypeCtx); - } - - return childCtx; - } - - private class OwnedTypeBulkInsertContext : BulkInsertContext, ISqlServerOwnedTypeBulkOperationContext - { - public IEntityType EntityType { get; } - public IEnumerable Entities { get; } - - public OwnedTypeBulkInsertContext( - DbContext ctx, - IEntityDataReaderFactory factory, - SqlConnection connection, - SqlTransaction? transaction, - SqlServerBulkInsertOptions options, - IReadOnlyList properties, - IEntityType entityType, - IEnumerable entities) - : base(ctx, factory, connection, transaction, options, properties) - { - EntityType = entityType; - Entities = entities; - } - } -} +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +internal class BulkInsertContext : ISqlServerBulkOperationContext +{ + private readonly IReadOnlyList _externalProperties; + private readonly DbContext _ctx; + private readonly IEntityDataReaderFactory _readerFactory; + + public SqlConnection Connection { get; } + public SqlTransaction? Transaction { get; } + public IReadOnlyList Properties { get; } + public SqlServerBulkInsertOptions Options { get; } + public bool HasExternalProperties => _externalProperties.Count != 0; + + public IEntityDataReader CreateReader(IEnumerable entities) + { + return _readerFactory.Create(_ctx, entities, Properties, HasExternalProperties); + } + + public BulkInsertContext( + DbContext ctx, + IEntityDataReaderFactory factory, + SqlConnection connection, + SqlTransaction? transaction, + SqlServerBulkInsertOptions options, + IReadOnlyList properties) + { + _ctx = ctx; + _readerFactory = factory; + Connection = connection; + Transaction = transaction; + var (ownProperties, externalProperties) = properties.SeparateProperties(); + Properties = ownProperties; + _externalProperties = externalProperties; + Options = options; + } + + public IReadOnlyList GetChildren(IReadOnlyList entities) + { + if (!HasExternalProperties) + return Array.Empty(); + + var childCtx = new List(); + + foreach (var (navigation, ownedEntities, properties) in _externalProperties.GroupExternalProperties(entities)) + { + var ownedTypeCtx = new OwnedTypeBulkInsertContext(_ctx, _readerFactory, Connection, Transaction, Options, properties, navigation.TargetEntityType, ownedEntities); + childCtx.Add(ownedTypeCtx); + } + + return childCtx; + } + + private class OwnedTypeBulkInsertContext : BulkInsertContext, ISqlServerOwnedTypeBulkOperationContext + { + public IEntityType EntityType { get; } + public IEnumerable Entities { get; } + + public OwnedTypeBulkInsertContext( + DbContext ctx, + IEntityDataReaderFactory factory, + SqlConnection connection, + SqlTransaction? transaction, + SqlServerBulkInsertOptions options, + IReadOnlyList properties, + IEntityType entityType, + IEnumerable entities) + : base(ctx, factory, connection, transaction, options, properties) + { + EntityType = entityType; + Entities = entities; + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/CompositeTempTableEntityPropertiesProvider.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/CompositeTempTableEntityPropertiesProvider.cs index 280e6466..302a09ab 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/CompositeTempTableEntityPropertiesProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/CompositeTempTableEntityPropertiesProvider.cs @@ -1,134 +1,134 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] -internal abstract class CompositeTempTableEntityPropertiesProvider : IEntityPropertiesProvider -{ - private readonly IEntityPropertiesProvider? _keyPropertiesProvider; - - protected CompositeTempTableEntityPropertiesProvider(IEntityPropertiesProvider? keyPropertiesProvider) - { - _keyPropertiesProvider = keyPropertiesProvider; - } - - public static IEntityPropertiesProvider CreateForInsertOrUpdate( - IEntityPropertiesProvider propertiesToInsert, - IEntityPropertiesProvider propertiesToUpdate, - IEntityPropertiesProvider? keyPropertiesProvider) - { - return new PropertiesProviderForInsertOrUpdate(propertiesToInsert, propertiesToUpdate, keyPropertiesProvider); - } - - public static IEntityPropertiesProvider CreateForUpdate( - IEntityPropertiesProvider propertiesToUpdate, - IEntityPropertiesProvider? keyPropertiesProvider) - { - return new PropertiesProviderForUpdate(propertiesToUpdate, keyPropertiesProvider); - } - - public abstract IReadOnlyList GetPropertiesForTempTable(IEntityType entityType); - public abstract IReadOnlyList GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes); - public abstract IReadOnlyList GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes); - - public IReadOnlyList GetKeyProperties(IEntityType entityType) - { - return _keyPropertiesProvider.DetermineKeyProperties(entityType); - } - - private IReadOnlyList GetProperties( - IEntityType entityType, - IReadOnlyList propertiesToInsert, - IReadOnlyList propertiesToUpdate) - { - return _keyPropertiesProvider.DetermineKeyProperties(entityType) - .Union(propertiesToInsert) - .Union(propertiesToUpdate) - .ToList(); - } - - private IReadOnlyList GetPropertiesWithNavigations( - IEntityType entityType, - IReadOnlyList propertiesToInsert, - IReadOnlyList propertiesToUpdate) - { - return _keyPropertiesProvider.DetermineKeyProperties(entityType).Select(p => new PropertyWithNavigations(p, Array.Empty())) - .Union(propertiesToInsert) - .Union(propertiesToUpdate) - .ToList(); - } - - private class PropertiesProviderForInsertOrUpdate : CompositeTempTableEntityPropertiesProvider - { - private readonly IEntityPropertiesProvider _propertiesToInsert; - private readonly IEntityPropertiesProvider _propertiesToUpdate; - - public PropertiesProviderForInsertOrUpdate( - IEntityPropertiesProvider propertiesToInsert, - IEntityPropertiesProvider propertiesToUpdate, - IEntityPropertiesProvider? keyPropertiesProvider) - : base(keyPropertiesProvider) - { - _propertiesToInsert = propertiesToInsert; - _propertiesToUpdate = propertiesToUpdate; - } - - public override IReadOnlyList GetPropertiesForTempTable(IEntityType entityType) - { - return GetProperties(entityType, - _propertiesToInsert.GetPropertiesForTempTable(entityType), - _propertiesToUpdate.GetPropertiesForTempTable(entityType)); - } - - public override IReadOnlyList GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes) - { - return GetPropertiesWithNavigations(entityType, - _propertiesToInsert.GetPropertiesForInsert(entityType, inlinedOwnTypes), - _propertiesToUpdate.GetPropertiesForInsert(entityType, inlinedOwnTypes)); - } - - public override IReadOnlyList GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes) - { - return GetPropertiesWithNavigations(entityType, - _propertiesToInsert.GetPropertiesForUpdate(entityType, inlinedOwnTypes), - _propertiesToUpdate.GetPropertiesForUpdate(entityType, inlinedOwnTypes)); - } - } - - private class PropertiesProviderForUpdate : CompositeTempTableEntityPropertiesProvider - { - private readonly IEntityPropertiesProvider _propertiesToUpdate; - - public PropertiesProviderForUpdate( - IEntityPropertiesProvider propertiesToUpdate, - IEntityPropertiesProvider? keyPropertiesProvider) - : base(keyPropertiesProvider) - { - _propertiesToUpdate = propertiesToUpdate; - } - - public override IReadOnlyList GetPropertiesForTempTable(IEntityType entityType) - { - return GetProperties(entityType, - Array.Empty(), - _propertiesToUpdate.GetPropertiesForTempTable(entityType)); - } - - public override IReadOnlyList GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes) - { - return GetPropertiesWithNavigations(entityType, - Array.Empty(), - _propertiesToUpdate.GetPropertiesForInsert(entityType, inlinedOwnTypes)); - } - - public override IReadOnlyList GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes) - { - return GetPropertiesWithNavigations(entityType, - Array.Empty(), - _propertiesToUpdate.GetPropertiesForUpdate(entityType, inlinedOwnTypes)); - } - } -} +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] +internal abstract class CompositeTempTableEntityPropertiesProvider : IEntityPropertiesProvider +{ + private readonly IEntityPropertiesProvider? _keyPropertiesProvider; + + protected CompositeTempTableEntityPropertiesProvider(IEntityPropertiesProvider? keyPropertiesProvider) + { + _keyPropertiesProvider = keyPropertiesProvider; + } + + public static IEntityPropertiesProvider CreateForInsertOrUpdate( + IEntityPropertiesProvider propertiesToInsert, + IEntityPropertiesProvider propertiesToUpdate, + IEntityPropertiesProvider? keyPropertiesProvider) + { + return new PropertiesProviderForInsertOrUpdate(propertiesToInsert, propertiesToUpdate, keyPropertiesProvider); + } + + public static IEntityPropertiesProvider CreateForUpdate( + IEntityPropertiesProvider propertiesToUpdate, + IEntityPropertiesProvider? keyPropertiesProvider) + { + return new PropertiesProviderForUpdate(propertiesToUpdate, keyPropertiesProvider); + } + + public abstract IReadOnlyList GetPropertiesForTempTable(IEntityType entityType); + public abstract IReadOnlyList GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes); + public abstract IReadOnlyList GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes); + + public IReadOnlyList GetKeyProperties(IEntityType entityType) + { + return _keyPropertiesProvider.DetermineKeyProperties(entityType); + } + + private IReadOnlyList GetProperties( + IEntityType entityType, + IReadOnlyList propertiesToInsert, + IReadOnlyList propertiesToUpdate) + { + return _keyPropertiesProvider.DetermineKeyProperties(entityType) + .Union(propertiesToInsert) + .Union(propertiesToUpdate) + .ToList(); + } + + private IReadOnlyList GetPropertiesWithNavigations( + IEntityType entityType, + IReadOnlyList propertiesToInsert, + IReadOnlyList propertiesToUpdate) + { + return _keyPropertiesProvider.DetermineKeyProperties(entityType).Select(p => new PropertyWithNavigations(p, Array.Empty())) + .Union(propertiesToInsert) + .Union(propertiesToUpdate) + .ToList(); + } + + private class PropertiesProviderForInsertOrUpdate : CompositeTempTableEntityPropertiesProvider + { + private readonly IEntityPropertiesProvider _propertiesToInsert; + private readonly IEntityPropertiesProvider _propertiesToUpdate; + + public PropertiesProviderForInsertOrUpdate( + IEntityPropertiesProvider propertiesToInsert, + IEntityPropertiesProvider propertiesToUpdate, + IEntityPropertiesProvider? keyPropertiesProvider) + : base(keyPropertiesProvider) + { + _propertiesToInsert = propertiesToInsert; + _propertiesToUpdate = propertiesToUpdate; + } + + public override IReadOnlyList GetPropertiesForTempTable(IEntityType entityType) + { + return GetProperties(entityType, + _propertiesToInsert.GetPropertiesForTempTable(entityType), + _propertiesToUpdate.GetPropertiesForTempTable(entityType)); + } + + public override IReadOnlyList GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes) + { + return GetPropertiesWithNavigations(entityType, + _propertiesToInsert.GetPropertiesForInsert(entityType, inlinedOwnTypes), + _propertiesToUpdate.GetPropertiesForInsert(entityType, inlinedOwnTypes)); + } + + public override IReadOnlyList GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes) + { + return GetPropertiesWithNavigations(entityType, + _propertiesToInsert.GetPropertiesForUpdate(entityType, inlinedOwnTypes), + _propertiesToUpdate.GetPropertiesForUpdate(entityType, inlinedOwnTypes)); + } + } + + private class PropertiesProviderForUpdate : CompositeTempTableEntityPropertiesProvider + { + private readonly IEntityPropertiesProvider _propertiesToUpdate; + + public PropertiesProviderForUpdate( + IEntityPropertiesProvider propertiesToUpdate, + IEntityPropertiesProvider? keyPropertiesProvider) + : base(keyPropertiesProvider) + { + _propertiesToUpdate = propertiesToUpdate; + } + + public override IReadOnlyList GetPropertiesForTempTable(IEntityType entityType) + { + return GetProperties(entityType, + Array.Empty(), + _propertiesToUpdate.GetPropertiesForTempTable(entityType)); + } + + public override IReadOnlyList GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes) + { + return GetPropertiesWithNavigations(entityType, + Array.Empty(), + _propertiesToUpdate.GetPropertiesForInsert(entityType, inlinedOwnTypes)); + } + + public override IReadOnlyList GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes) + { + return GetPropertiesWithNavigations(entityType, + Array.Empty(), + _propertiesToUpdate.GetPropertiesForUpdate(entityType, inlinedOwnTypes)); + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/ISqlServerBulkOperationContext.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/ISqlServerBulkOperationContext.cs index c865443d..ec646d23 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/ISqlServerBulkOperationContext.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/ISqlServerBulkOperationContext.cs @@ -1,12 +1,12 @@ -using Microsoft.Data.SqlClient; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -internal interface ISqlServerBulkOperationContext : IBulkOperationContext -{ - SqlConnection Connection { get; } - SqlTransaction? Transaction { get; } - SqlServerBulkInsertOptions Options { get; } - - IReadOnlyList GetChildren(IReadOnlyList entities); -} +using Microsoft.Data.SqlClient; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +internal interface ISqlServerBulkOperationContext : IBulkOperationContext +{ + SqlConnection Connection { get; } + SqlTransaction? Transaction { get; } + SqlServerBulkInsertOptions Options { get; } + + IReadOnlyList GetChildren(IReadOnlyList entities); +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/ISqlServerOwnedTypeBulkOperationContext.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/ISqlServerOwnedTypeBulkOperationContext.cs index b04a98cc..d694fdf2 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/ISqlServerOwnedTypeBulkOperationContext.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/ISqlServerOwnedTypeBulkOperationContext.cs @@ -1,5 +1,5 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -internal interface ISqlServerOwnedTypeBulkOperationContext : ISqlServerBulkOperationContext, IOwnedTypeBulkOperationContext -{ +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +internal interface ISqlServerOwnedTypeBulkOperationContext : ISqlServerBulkOperationContext, IOwnedTypeBulkOperationContext +{ } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/MomentOfSqlServerPrimaryKeyCreation.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/MomentOfSqlServerPrimaryKeyCreation.cs index 1146fe55..a376b6e5 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/MomentOfSqlServerPrimaryKeyCreation.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/MomentOfSqlServerPrimaryKeyCreation.cs @@ -1,17 +1,17 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Defines when the primary key should be created. -/// -public enum MomentOfSqlServerPrimaryKeyCreation -{ - /// - /// Primary key should be created before bulk insert. - /// - BeforeBulkInsert, - - /// - /// Primary key should be created after bulk insert. - /// - AfterBulkInsert +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Defines when the primary key should be created. +/// +public enum MomentOfSqlServerPrimaryKeyCreation +{ + /// + /// Primary key should be created before bulk insert. + /// + BeforeBulkInsert, + + /// + /// Primary key should be created after bulk insert. + /// + AfterBulkInsert } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkInsertOptions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkInsertOptions.cs index 1c3ea247..25b8833a 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkInsertOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkInsertOptions.cs @@ -1,56 +1,56 @@ -using System.Data; -using Microsoft.Data.SqlClient; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Bulk insert options for SQL Server. -/// -public sealed class SqlServerBulkInsertOptions : IBulkInsertOptions -{ - /// - /// Timeout used by - /// - public TimeSpan? BulkCopyTimeout { get; set; } - - /// - /// Options used by . - /// - public SqlBulkCopyOptions SqlBulkCopyOptions { get; set; } - - /// - /// Batch size used by . - /// - public int? BatchSize { get; set; } - - /// - /// Enables or disables a object to stream data from an object. - /// Default is set to true. - /// - public bool EnableStreaming { get; set; } - - /// - public IEntityPropertiesProvider? PropertiesToInsert { get; set; } - - /// - /// Initializes new instance of . - /// - /// Options to initialize from. - public SqlServerBulkInsertOptions(IBulkInsertOptions? optionsToInitializeFrom = null) - { - EnableStreaming = true; - - if (optionsToInitializeFrom == null) - return; - - PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert; - - if (optionsToInitializeFrom is SqlServerBulkInsertOptions sqlServerOptions) - { - BulkCopyTimeout = sqlServerOptions.BulkCopyTimeout; - SqlBulkCopyOptions = sqlServerOptions.SqlBulkCopyOptions; - BatchSize = sqlServerOptions.BatchSize; - EnableStreaming = sqlServerOptions.EnableStreaming; - } - } -} +using System.Data; +using Microsoft.Data.SqlClient; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Bulk insert options for SQL Server. +/// +public sealed class SqlServerBulkInsertOptions : IBulkInsertOptions +{ + /// + /// Timeout used by + /// + public TimeSpan? BulkCopyTimeout { get; set; } + + /// + /// Options used by . + /// + public SqlBulkCopyOptions SqlBulkCopyOptions { get; set; } + + /// + /// Batch size used by . + /// + public int? BatchSize { get; set; } + + /// + /// Enables or disables a object to stream data from an object. + /// Default is set to true. + /// + public bool EnableStreaming { get; set; } + + /// + public IEntityPropertiesProvider? PropertiesToInsert { get; set; } + + /// + /// Initializes new instance of . + /// + /// Options to initialize from. + public SqlServerBulkInsertOptions(IBulkInsertOptions? optionsToInitializeFrom = null) + { + EnableStreaming = true; + + if (optionsToInitializeFrom == null) + return; + + PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert; + + if (optionsToInitializeFrom is SqlServerBulkInsertOptions sqlServerOptions) + { + BulkCopyTimeout = sqlServerOptions.BulkCopyTimeout; + SqlBulkCopyOptions = sqlServerOptions.SqlBulkCopyOptions; + BatchSize = sqlServerOptions.BatchSize; + EnableStreaming = sqlServerOptions.EnableStreaming; + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkInsertOrUpdateOptions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkInsertOrUpdateOptions.cs index 93495daa..c8dad5d0 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkInsertOrUpdateOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkInsertOrUpdateOptions.cs @@ -1,92 +1,92 @@ -using Microsoft.Data.SqlClient; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Bulk insert or update options for SQL Server. -/// -public sealed class SqlServerBulkInsertOrUpdateOptions : ISqlServerMergeOperationOptions, IBulkInsertOrUpdateOptions -{ - /// - public IEntityPropertiesProvider? PropertiesToInsert { get; set; } - - /// - public IEntityPropertiesProvider? PropertiesToUpdate { get; set; } - - /// - public IEntityPropertiesProvider? KeyProperties { get; set; } - - /// - public List MergeTableHints { get; } - - /// - public SqlServerBulkOperationTempTableOptions TempTableOptions { get; } - - /// - /// Initializes new instance of . - /// - /// Indication, whether the table hint should be used with 'MERGE' or not. - public SqlServerBulkInsertOrUpdateOptions(bool holdLockDuringMerge) - : this(null, holdLockDuringMerge) - { - } - - /// - /// Initializes new instance of . - /// - /// Options to initialize from. - public SqlServerBulkInsertOrUpdateOptions(IBulkInsertOrUpdateOptions? optionsToInitializeFrom = null) - : this(optionsToInitializeFrom, optionsToInitializeFrom is null) - { - } - - private SqlServerBulkInsertOrUpdateOptions( - IBulkInsertOrUpdateOptions? optionsToInitializeFrom, - bool holdLockDuringMerge) - { - if (optionsToInitializeFrom is not null) - { - PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert; - PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate; - KeyProperties = optionsToInitializeFrom.KeyProperties; - } - - if (optionsToInitializeFrom is ISqlServerMergeOperationOptions mergeOptions) - { - TempTableOptions = new SqlServerBulkOperationTempTableOptions(mergeOptions.TempTableOptions); - MergeTableHints = mergeOptions.MergeTableHints.ToList(); - - if (holdLockDuringMerge && !MergeTableHints.Contains(SqlServerTableHintLimited.HoldLock)) - MergeTableHints.Add(SqlServerTableHintLimited.HoldLock); - } - else - { - TempTableOptions = new SqlServerBulkOperationTempTableOptions - { - SqlBulkCopyOptions = SqlBulkCopyOptions.KeepIdentity - }; - MergeTableHints = new List(); - - if (holdLockDuringMerge) - MergeTableHints.Add(SqlServerTableHintLimited.HoldLock); - } - } - - /// - /// Gets the options for bulk insert into a temp table. - /// - public SqlServerTempTableBulkOperationOptions GetTempTableBulkInsertOptions() - { - var options = new SqlServerTempTableBulkInsertOptions - { - PropertiesToInsert = PropertiesToInsert is null || PropertiesToUpdate is null - ? null - : CompositeTempTableEntityPropertiesProvider.CreateForInsertOrUpdate(PropertiesToInsert, PropertiesToUpdate, KeyProperties), - Advanced = { UsePropertiesToInsertForTempTableCreation = true } - }; - - TempTableOptions.Populate(options); - - return options; - } -} +using Microsoft.Data.SqlClient; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Bulk insert or update options for SQL Server. +/// +public sealed class SqlServerBulkInsertOrUpdateOptions : ISqlServerMergeOperationOptions, IBulkInsertOrUpdateOptions +{ + /// + public IEntityPropertiesProvider? PropertiesToInsert { get; set; } + + /// + public IEntityPropertiesProvider? PropertiesToUpdate { get; set; } + + /// + public IEntityPropertiesProvider? KeyProperties { get; set; } + + /// + public List MergeTableHints { get; } + + /// + public SqlServerBulkOperationTempTableOptions TempTableOptions { get; } + + /// + /// Initializes new instance of . + /// + /// Indication, whether the table hint should be used with 'MERGE' or not. + public SqlServerBulkInsertOrUpdateOptions(bool holdLockDuringMerge) + : this(null, holdLockDuringMerge) + { + } + + /// + /// Initializes new instance of . + /// + /// Options to initialize from. + public SqlServerBulkInsertOrUpdateOptions(IBulkInsertOrUpdateOptions? optionsToInitializeFrom = null) + : this(optionsToInitializeFrom, optionsToInitializeFrom is null) + { + } + + private SqlServerBulkInsertOrUpdateOptions( + IBulkInsertOrUpdateOptions? optionsToInitializeFrom, + bool holdLockDuringMerge) + { + if (optionsToInitializeFrom is not null) + { + PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert; + PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate; + KeyProperties = optionsToInitializeFrom.KeyProperties; + } + + if (optionsToInitializeFrom is ISqlServerMergeOperationOptions mergeOptions) + { + TempTableOptions = new SqlServerBulkOperationTempTableOptions(mergeOptions.TempTableOptions); + MergeTableHints = mergeOptions.MergeTableHints.ToList(); + + if (holdLockDuringMerge && !MergeTableHints.Contains(SqlServerTableHintLimited.HoldLock)) + MergeTableHints.Add(SqlServerTableHintLimited.HoldLock); + } + else + { + TempTableOptions = new SqlServerBulkOperationTempTableOptions + { + SqlBulkCopyOptions = SqlBulkCopyOptions.KeepIdentity + }; + MergeTableHints = new List(); + + if (holdLockDuringMerge) + MergeTableHints.Add(SqlServerTableHintLimited.HoldLock); + } + } + + /// + /// Gets the options for bulk insert into a temp table. + /// + public SqlServerTempTableBulkOperationOptions GetTempTableBulkInsertOptions() + { + var options = new SqlServerTempTableBulkInsertOptions + { + PropertiesToInsert = PropertiesToInsert is null || PropertiesToUpdate is null + ? null + : CompositeTempTableEntityPropertiesProvider.CreateForInsertOrUpdate(PropertiesToInsert, PropertiesToUpdate, KeyProperties), + Advanced = { UsePropertiesToInsertForTempTableCreation = true } + }; + + TempTableOptions.Populate(options); + + return options; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutor.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutor.cs index cbb7df35..126e60da 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutor.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutor.cs @@ -1,576 +1,576 @@ -using System.Diagnostics; -using System.Text; -using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.ObjectPool; -using Thinktecture.EntityFrameworkCore.Data; -using Thinktecture.EntityFrameworkCore.TempTables; -using Thinktecture.Internal; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Executes bulk operations. -/// -public sealed class SqlServerBulkOperationExecutor - : IBulkInsertExecutor, ITempTableBulkInsertExecutor, IBulkUpdateExecutor, - IBulkInsertOrUpdateExecutor, ITruncateTableExecutor -{ - private readonly DbContext _ctx; - private readonly IDiagnosticsLogger _logger; - private readonly ISqlGenerationHelper _sqlGenerationHelper; - private readonly ObjectPool _stringBuilderPool; - - private static class EventIds - { - public static readonly EventId Inserting = 0; - public static readonly EventId Inserted = 1; - } - - /// - /// Initializes new instance of . - /// - /// Current database context. - /// Logger. - /// SQL generation helper. - /// String builder pool. - public SqlServerBulkOperationExecutor( - ICurrentDbContext ctx, - IDiagnosticsLogger logger, - ISqlGenerationHelper sqlGenerationHelper, - ObjectPool stringBuilderPool) - { - ArgumentNullException.ThrowIfNull(ctx); - - _ctx = ctx.Context; - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _sqlGenerationHelper = sqlGenerationHelper ?? throw new ArgumentNullException(nameof(sqlGenerationHelper)); - _stringBuilderPool = stringBuilderPool ?? throw new ArgumentNullException(nameof(stringBuilderPool)); - } - - /// - IBulkInsertOptions IBulkInsertExecutor.CreateOptions(IEntityPropertiesProvider? propertiesToInsert) - { - return new SqlServerBulkInsertOptions { PropertiesToInsert = propertiesToInsert }; - } - - /// - ITempTableBulkInsertOptions ITempTableBulkInsertExecutor.CreateOptions(IEntityPropertiesProvider? propertiesToInsert) - { - return new SqlServerTempTableBulkInsertOptions { PropertiesToInsert = propertiesToInsert }; - } - - /// - IBulkUpdateOptions IBulkUpdateExecutor.CreateOptions(IEntityPropertiesProvider? propertiesToUpdate, IEntityPropertiesProvider? keyProperties) - { - return new SqlServerBulkUpdateOptions - { - PropertiesToUpdate = propertiesToUpdate, - KeyProperties = keyProperties - }; - } - - /// - IBulkInsertOrUpdateOptions IBulkInsertOrUpdateExecutor.CreateOptions( - IEntityPropertiesProvider? propertiesToInsert, - IEntityPropertiesProvider? propertiesToUpdate, - IEntityPropertiesProvider? keyProperties) - { - return new SqlServerBulkInsertOrUpdateOptions - { - PropertiesToInsert = propertiesToInsert, - PropertiesToUpdate = propertiesToUpdate, - KeyProperties = keyProperties - }; - } - - /// - public Task BulkInsertAsync( - IEnumerable entities, - IBulkInsertOptions options, - CancellationToken cancellationToken = default) - where T : class - { - var entityType = _ctx.Model.GetEntityType(typeof(T)); - var tableName = entityType.GetTableName() ?? throw new InvalidOperationException($"The entity '{entityType.Name}' has no table name."); - - return BulkInsertAsync(entityType, entities, entityType.GetSchema(), tableName, options, SqlServerBulkOperationContextFactoryForEntities.Instance, cancellationToken); - } - - private async Task BulkInsertAsync( - IEntityType entityType, - IEnumerable entitiesOrValues, - string? schema, - string tableName, - IBulkInsertOptions options, - ISqlServerBulkOperationContextFactory bulkOperationContextFactory, - CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(entitiesOrValues); - ArgumentNullException.ThrowIfNull(tableName); - ArgumentNullException.ThrowIfNull(options); - - if (options is not SqlServerBulkInsertOptions sqlServerOptions) - sqlServerOptions = new SqlServerBulkInsertOptions(options); - - var properties = sqlServerOptions.PropertiesToInsert.DeterminePropertiesForInsert(entityType, null); - properties.EnsureNoSeparateOwnedTypesInsideCollectionOwnedType(); - - var ctx = bulkOperationContextFactory.CreateForBulkInsert(_ctx, sqlServerOptions, properties); - - await BulkInsertAsync(entitiesOrValues, schema, tableName, ctx, cancellationToken); - } - - private async Task BulkInsertAsync( - IEnumerable entitiesOrValues, - string? schema, - string tableName, - ISqlServerBulkOperationContext ctx, - CancellationToken cancellationToken) - { - using var reader = ctx.CreateReader(entitiesOrValues); - using var bulkCopy = CreateSqlBulkCopy(ctx.Connection, ctx.Transaction, schema, tableName, ctx.Options); - - var columns = SetColumnMappings(bulkCopy, reader); - - await _ctx.Database.OpenConnectionAsync(cancellationToken).ConfigureAwait(false); - - try - { - LogInserting(ctx.Options.SqlBulkCopyOptions, bulkCopy, columns); - var stopwatch = Stopwatch.StartNew(); - - await bulkCopy.WriteToServerAsync(reader, cancellationToken).ConfigureAwait(false); - - LogInserted(ctx.Options.SqlBulkCopyOptions, stopwatch.Elapsed, bulkCopy, columns); - - if (ctx.HasExternalProperties) - { - var readEntities = reader.GetReadEntities(); - - if (readEntities.Count != 0) - await BulkInsertSeparatedOwnedEntitiesAsync((IReadOnlyList)readEntities, ctx, cancellationToken); - } - } - finally - { - await _ctx.Database.CloseConnectionAsync().ConfigureAwait(false); - } - } - - private async Task BulkInsertSeparatedOwnedEntitiesAsync( - IReadOnlyList parentEntities, - ISqlServerBulkOperationContext parentBulkOperationContext, - CancellationToken cancellationToken) - { - if (parentEntities.Count == 0) - return; - - foreach (var childContext in parentBulkOperationContext.GetChildren(parentEntities)) - { - var childTableName = childContext.EntityType.GetTableName() ?? throw new InvalidOperationException($"The entity '{childContext.EntityType.Name}' has no table name."); - - await BulkInsertAsync(childContext.Entities, - childContext.EntityType.GetSchema(), - childTableName, - childContext, - cancellationToken).ConfigureAwait(false); - } - } - - private string SetColumnMappings(SqlBulkCopy bulkCopy, IEntityDataReader reader) - { - var columnsSb = _stringBuilderPool.Get(); - - try - { - StoreObjectIdentifier? storeObject = null; - - for (var i = 0; i < reader.Properties.Count; i++) - { - var property = reader.Properties[i]; - - storeObject ??= property.GetStoreObject(); - var columnName = property.GetColumnName(storeObject.Value); - - bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(i, columnName)); - - if (columnsSb.Length > 0) - columnsSb.Append(", "); - - columnsSb.Append(columnName).Append(' ').Append(property.Property.GetColumnType(storeObject.Value)); - } - - return columnsSb.ToString(); - } - finally - { - _stringBuilderPool.Return(columnsSb); - } - } - - private SqlBulkCopy CreateSqlBulkCopy(SqlConnection sqlCon, SqlTransaction? sqlTx, string? schema, string tableName, SqlServerBulkInsertOptions sqlServerOptions) - { - var bulkCopy = new SqlBulkCopy(sqlCon, sqlServerOptions.SqlBulkCopyOptions, sqlTx) - { - DestinationTableName = _sqlGenerationHelper.DelimitIdentifier(tableName, schema), - EnableStreaming = sqlServerOptions.EnableStreaming - }; - - if (sqlServerOptions.BulkCopyTimeout.HasValue) - bulkCopy.BulkCopyTimeout = (int)sqlServerOptions.BulkCopyTimeout.Value.TotalSeconds; - - if (sqlServerOptions.BatchSize.HasValue) - bulkCopy.BatchSize = sqlServerOptions.BatchSize.Value; - - return bulkCopy; - } - - private void LogInserting(SqlBulkCopyOptions options, SqlBulkCopy bulkCopy, string columns) - { - _logger.Logger.LogDebug(EventIds.Inserting, - """ - Executing DbCommand [SqlBulkCopyOptions={SqlBulkCopyOptions}, BulkCopyTimeout={BulkCopyTimeout}, BatchSize={BatchSize}, EnableStreaming={EnableStreaming}] - INSERT BULK {Table} ({Columns}) - """, - options, - bulkCopy.BulkCopyTimeout, - bulkCopy.BatchSize, - bulkCopy.EnableStreaming, - bulkCopy.DestinationTableName, - columns); - } - - private void LogInserted(SqlBulkCopyOptions options, TimeSpan duration, SqlBulkCopy bulkCopy, string columns) - { - _logger.Logger.LogInformation(EventIds.Inserted, - """ - Executed DbCommand ({Duration}ms) [SqlBulkCopyOptions={SqlBulkCopyOptions}, BulkCopyTimeout={BulkCopyTimeout}, BatchSize={BatchSize}, EnableStreaming={EnableStreaming}] - INSERT BULK {Table} ({Columns}) - """, - (long)duration.TotalMilliseconds, - options, - bulkCopy.BulkCopyTimeout, - bulkCopy.BatchSize, - bulkCopy.EnableStreaming, - bulkCopy.DestinationTableName, - columns); - } - - /// - public Task> BulkInsertValuesIntoTempTableAsync( - IEnumerable values, - ITempTableBulkInsertOptions options, - CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(values); - ArgumentNullException.ThrowIfNull(options); - - if (options is not SqlServerTempTableBulkInsertOptions sqlServerOptions) - sqlServerOptions = new SqlServerTempTableBulkInsertOptions(options); - - return BulkInsertIntoTempTableAsync>(values, - sqlServerOptions, - SqlServerBulkOperationContextFactoryForValues.Instance, - query => query.Select(t => t.Column1), - cancellationToken); - } - - /// - public Task> BulkInsertIntoTempTableAsync( - IEnumerable entities, - ITempTableBulkInsertOptions options, - CancellationToken cancellationToken = default) - where T : class - { - ArgumentNullException.ThrowIfNull(entities); - ArgumentNullException.ThrowIfNull(options); - - if (options is not SqlServerTempTableBulkInsertOptions sqlServerOptions) - sqlServerOptions = new SqlServerTempTableBulkInsertOptions(options); - - return BulkInsertIntoTempTableAsync(entities, - sqlServerOptions, - SqlServerBulkOperationContextFactoryForEntities.Instance, - static query => query, - cancellationToken); - } - - private async Task> BulkInsertIntoTempTableAsync( - IEnumerable entitiesOrValues, - SqlServerTempTableBulkInsertOptions options, - ISqlServerBulkOperationContextFactory bulkOperationContextFactory, - Func, IQueryable> projection, - CancellationToken cancellationToken) - where TEntity : class - { - var type = typeof(TEntity); - var entityTypeName = EntityNameProvider.GetTempTableName(type); - var entityType = _ctx.Model.GetEntityType(entityTypeName, type); - - var tempTableCreationOptions = options.GetTempTableCreationOptions(); - var primaryKeyCreation = tempTableCreationOptions.PrimaryKeyCreation; // keep this one in a local variable because we may change it in the next line - - if (options.MomentOfPrimaryKeyCreation == MomentOfSqlServerPrimaryKeyCreation.AfterBulkInsert) - tempTableCreationOptions.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None; - - var tempTableCreator = _ctx.GetService(); - var tempTableReference = await tempTableCreator.CreateTempTableAsync(entityType, tempTableCreationOptions, cancellationToken).ConfigureAwait(false); - - try - { - var bulkInsertOptions = options.GetBulkInsertOptions(); - await BulkInsertAsync(entityType, entitiesOrValues, null, tempTableReference.Name, bulkInsertOptions, bulkOperationContextFactory, cancellationToken).ConfigureAwait(false); - - if (options.MomentOfPrimaryKeyCreation == MomentOfSqlServerPrimaryKeyCreation.AfterBulkInsert) - { - tempTableCreationOptions.PrimaryKeyCreation = primaryKeyCreation; - - var tempTableProperties = tempTableCreationOptions.PropertiesToInclude.DeterminePropertiesForTempTable(entityType); - var keyProperties = tempTableCreationOptions.PrimaryKeyCreation.GetPrimaryKeyProperties(entityType, tempTableProperties); - await tempTableCreator.CreatePrimaryKeyAsync(_ctx, keyProperties, tempTableReference.Name, tempTableCreationOptions.TruncateTableIfExists, cancellationToken).ConfigureAwait(false); - } - - var dbSet = entityType.Name == entityTypeName - ? _ctx.Set(entityTypeName) - : _ctx.Set(); - - var query = dbSet.FromTempTable(new TempTableInfo(tempTableReference.Name)); - - var pk = entityType.FindPrimaryKey(); - - if (pk is not null && pk.Properties.Count != 0) - query = query.AsNoTracking(); - - return new TempTableQuery(projection(query), tempTableReference); - } - catch (Exception) - { - await tempTableReference.DisposeAsync().ConfigureAwait(false); - throw; - } - } - - /// - public async Task TruncateTableAsync(CancellationToken cancellationToken = default) - where T : class - { - await TruncateTableAsync(typeof(T), cancellationToken); - } - - /// - public async Task TruncateTableAsync(Type type, CancellationToken cancellationToken = default) - { - var entityType = _ctx.Model.GetEntityType(type); - var tableName = entityType.GetTableName() ?? throw new InvalidOperationException($"The entity '{entityType.Name}' has no table name."); - - var tableIdentifier = _sqlGenerationHelper.DelimitIdentifier(tableName, entityType.GetSchema()); - var truncateStatement = $"TRUNCATE TABLE {tableIdentifier};"; - - await _ctx.Database.ExecuteSqlRawAsync(truncateStatement, cancellationToken); - } - - /// - public async Task BulkUpdateAsync( - IEnumerable entities, - IBulkUpdateOptions options, - CancellationToken cancellationToken = default) - where T : class - { - ArgumentNullException.ThrowIfNull(entities); - ArgumentNullException.ThrowIfNull(options); - - if (options is not SqlServerBulkUpdateOptions sqlServerOptions) - sqlServerOptions = new SqlServerBulkUpdateOptions(options); - - return await BulkUpdateAsync(entities, sqlServerOptions, cancellationToken); - } - - private async Task BulkUpdateAsync(IEnumerable entities, SqlServerBulkUpdateOptions options, CancellationToken cancellationToken) - where T : class - { - var entityType = _ctx.Model.GetEntityType(typeof(T)); - var propertiesForUpdate = options.PropertiesToUpdate.DeterminePropertiesForUpdate(entityType, true); - - if (propertiesForUpdate.Count == 0) - throw new ArgumentException("The number of properties to update cannot be 0."); - - var tempTableOptions = options.GetTempTableBulkInsertOptions(); - await using var tempTable = await BulkInsertIntoTempTableAsync(entities, tempTableOptions, cancellationToken); - - var mergeStatement = CreateMergeCommand(entityType, tempTable.Name, options, null, propertiesForUpdate, options.KeyProperties.DetermineKeyProperties(entityType)); - - return await _ctx.Database.ExecuteSqlRawAsync(mergeStatement, cancellationToken); - } - - /// - public async Task BulkInsertOrUpdateAsync( - IEnumerable entities, - IBulkInsertOrUpdateOptions options, - CancellationToken cancellationToken = default) - where T : class - { - ArgumentNullException.ThrowIfNull(entities); - ArgumentNullException.ThrowIfNull(options); - - if (options is not SqlServerBulkInsertOrUpdateOptions sqlServerOptions) - sqlServerOptions = new SqlServerBulkInsertOrUpdateOptions(options); - - return await BulkInsertOrUpdateAsync(entities, sqlServerOptions, cancellationToken); - } - - private async Task BulkInsertOrUpdateAsync(IEnumerable entities, SqlServerBulkInsertOrUpdateOptions options, CancellationToken cancellationToken) - where T : class - { - var entityType = _ctx.Model.GetEntityType(typeof(T)); - var propertiesForInsert = options.PropertiesToInsert.DeterminePropertiesForInsert(entityType, true); - var propertiesForUpdate = options.PropertiesToUpdate.DeterminePropertiesForInsert(entityType, true); - - if (propertiesForInsert.Count == 0) - throw new ArgumentException("The number of properties to insert cannot be 0."); - - var tempTableOptions = options.GetTempTableBulkInsertOptions(); - await using var tempTable = await BulkInsertIntoTempTableAsync(entities, tempTableOptions, cancellationToken); - - var mergeStatement = CreateMergeCommand(entityType, tempTable.Name, options, propertiesForInsert, propertiesForUpdate, options.KeyProperties.DetermineKeyProperties(entityType)); - - return await _ctx.Database.ExecuteSqlRawAsync(mergeStatement, cancellationToken); - } - - private string CreateMergeCommand( - IEntityType entityType, - string sourceTempTableName, - T options, - IReadOnlyList? propertiesToInsert, - IReadOnlyList propertiesToUpdate, - IReadOnlyList keyProperties) - where T : ISqlServerMergeOperationOptions - { - var sb = _stringBuilderPool.Get(); - - try - { - var tableName = entityType.GetTableName() ?? throw new Exception($"The entity '{entityType.Name}' has no table name."); - var storeObject = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table) - ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{entityType.Name}'."); - - sb.Append("MERGE INTO ") - .Append(_sqlGenerationHelper.DelimitIdentifier(tableName, entityType.GetSchema())); - - if (options.MergeTableHints.Count != 0) - { - sb.Append(" WITH ("); - - for (var i = 0; i < options.MergeTableHints.Count; i++) - { - if (i != 0) - sb.Append(", "); - - sb.Append(options.MergeTableHints[i]); - } - - sb.Append(")"); - } - - sb.AppendLine(" AS d") - .Append("USING ") - .Append(_sqlGenerationHelper.DelimitIdentifier(sourceTempTableName)) - .Append(" AS s ON "); - - var isFirstIteration = true; - - foreach (var property in keyProperties) - { - if (!isFirstIteration) - sb.AppendLine(" AND "); - - var columnName = property.GetColumnName(storeObject) - ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.DeclaringType.Name}'."); - var escapedColumnName = _sqlGenerationHelper.DelimitIdentifier(columnName); - - sb.Append("(d.").Append(escapedColumnName).Append(" = s.").Append(escapedColumnName); - - if (property.IsNullable) - sb.Append(" OR d.").Append(escapedColumnName).Append(" IS NULL AND s.").Append(escapedColumnName).Append(" IS NULL"); - - sb.Append(")"); - isFirstIteration = false; - } - - isFirstIteration = true; - - foreach (var property in propertiesToUpdate.Select(p => p.Property).Except(keyProperties)) - { - if (!isFirstIteration) - { - sb.Append(", "); - } - else - { - sb.AppendLine() - .AppendLine("WHEN MATCHED THEN") - .Append("\tUPDATE SET "); - } - - var columnName = property.GetColumnName(storeObject) - ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.DeclaringType.Name}'."); - var escapedColumnName = _sqlGenerationHelper.DelimitIdentifier(columnName); - - sb.Append("d.").Append(escapedColumnName).Append(" = s.").Append(escapedColumnName); - isFirstIteration = false; - } - - if (propertiesToInsert is not null) - { - sb.AppendLine() - .AppendLine("WHEN NOT MATCHED THEN") - .Append("\tINSERT ("); - - isFirstIteration = true; - - foreach (var property in propertiesToInsert) - { - if (!isFirstIteration) - sb.Append(", "); - - var columnName = property.GetColumnName(storeObject); - var escapedColumnName = _sqlGenerationHelper.DelimitIdentifier(columnName); - - sb.Append(escapedColumnName); - isFirstIteration = false; - } - - sb.AppendLine(")") - .Append("\tVALUES ("); - - isFirstIteration = true; - - foreach (var property in propertiesToInsert) - { - if (!isFirstIteration) - sb.Append(", "); - - var columnName = property.GetColumnName(storeObject); - var escapedColumnName = _sqlGenerationHelper.DelimitIdentifier(columnName); - - sb.Append("s.").Append(escapedColumnName); - isFirstIteration = false; - } - - sb.Append(")"); - } - - sb.Append(_sqlGenerationHelper.StatementTerminator); - - return sb.ToString(); - } - finally - { - _stringBuilderPool.Return(sb); - } - } -} +using System.Diagnostics; +using System.Text; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; +using Thinktecture.EntityFrameworkCore.Data; +using Thinktecture.EntityFrameworkCore.TempTables; +using Thinktecture.Internal; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Executes bulk operations. +/// +public sealed class SqlServerBulkOperationExecutor + : IBulkInsertExecutor, ITempTableBulkInsertExecutor, IBulkUpdateExecutor, + IBulkInsertOrUpdateExecutor, ITruncateTableExecutor +{ + private readonly DbContext _ctx; + private readonly IDiagnosticsLogger _logger; + private readonly ISqlGenerationHelper _sqlGenerationHelper; + private readonly ObjectPool _stringBuilderPool; + + private static class EventIds + { + public static readonly EventId Inserting = 0; + public static readonly EventId Inserted = 1; + } + + /// + /// Initializes new instance of . + /// + /// Current database context. + /// Logger. + /// SQL generation helper. + /// String builder pool. + public SqlServerBulkOperationExecutor( + ICurrentDbContext ctx, + IDiagnosticsLogger logger, + ISqlGenerationHelper sqlGenerationHelper, + ObjectPool stringBuilderPool) + { + ArgumentNullException.ThrowIfNull(ctx); + + _ctx = ctx.Context; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _sqlGenerationHelper = sqlGenerationHelper ?? throw new ArgumentNullException(nameof(sqlGenerationHelper)); + _stringBuilderPool = stringBuilderPool ?? throw new ArgumentNullException(nameof(stringBuilderPool)); + } + + /// + IBulkInsertOptions IBulkInsertExecutor.CreateOptions(IEntityPropertiesProvider? propertiesToInsert) + { + return new SqlServerBulkInsertOptions { PropertiesToInsert = propertiesToInsert }; + } + + /// + ITempTableBulkInsertOptions ITempTableBulkInsertExecutor.CreateOptions(IEntityPropertiesProvider? propertiesToInsert) + { + return new SqlServerTempTableBulkInsertOptions { PropertiesToInsert = propertiesToInsert }; + } + + /// + IBulkUpdateOptions IBulkUpdateExecutor.CreateOptions(IEntityPropertiesProvider? propertiesToUpdate, IEntityPropertiesProvider? keyProperties) + { + return new SqlServerBulkUpdateOptions + { + PropertiesToUpdate = propertiesToUpdate, + KeyProperties = keyProperties + }; + } + + /// + IBulkInsertOrUpdateOptions IBulkInsertOrUpdateExecutor.CreateOptions( + IEntityPropertiesProvider? propertiesToInsert, + IEntityPropertiesProvider? propertiesToUpdate, + IEntityPropertiesProvider? keyProperties) + { + return new SqlServerBulkInsertOrUpdateOptions + { + PropertiesToInsert = propertiesToInsert, + PropertiesToUpdate = propertiesToUpdate, + KeyProperties = keyProperties + }; + } + + /// + public Task BulkInsertAsync( + IEnumerable entities, + IBulkInsertOptions options, + CancellationToken cancellationToken = default) + where T : class + { + var entityType = _ctx.Model.GetEntityType(typeof(T)); + var tableName = entityType.GetTableName() ?? throw new InvalidOperationException($"The entity '{entityType.Name}' has no table name."); + + return BulkInsertAsync(entityType, entities, entityType.GetSchema(), tableName, options, SqlServerBulkOperationContextFactoryForEntities.Instance, cancellationToken); + } + + private async Task BulkInsertAsync( + IEntityType entityType, + IEnumerable entitiesOrValues, + string? schema, + string tableName, + IBulkInsertOptions options, + ISqlServerBulkOperationContextFactory bulkOperationContextFactory, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(entitiesOrValues); + ArgumentNullException.ThrowIfNull(tableName); + ArgumentNullException.ThrowIfNull(options); + + if (options is not SqlServerBulkInsertOptions sqlServerOptions) + sqlServerOptions = new SqlServerBulkInsertOptions(options); + + var properties = sqlServerOptions.PropertiesToInsert.DeterminePropertiesForInsert(entityType, null); + properties.EnsureNoSeparateOwnedTypesInsideCollectionOwnedType(); + + var ctx = bulkOperationContextFactory.CreateForBulkInsert(_ctx, sqlServerOptions, properties); + + await BulkInsertAsync(entitiesOrValues, schema, tableName, ctx, cancellationToken); + } + + private async Task BulkInsertAsync( + IEnumerable entitiesOrValues, + string? schema, + string tableName, + ISqlServerBulkOperationContext ctx, + CancellationToken cancellationToken) + { + using var reader = ctx.CreateReader(entitiesOrValues); + using var bulkCopy = CreateSqlBulkCopy(ctx.Connection, ctx.Transaction, schema, tableName, ctx.Options); + + var columns = SetColumnMappings(bulkCopy, reader); + + await _ctx.Database.OpenConnectionAsync(cancellationToken).ConfigureAwait(false); + + try + { + LogInserting(ctx.Options.SqlBulkCopyOptions, bulkCopy, columns); + var stopwatch = Stopwatch.StartNew(); + + await bulkCopy.WriteToServerAsync(reader, cancellationToken).ConfigureAwait(false); + + LogInserted(ctx.Options.SqlBulkCopyOptions, stopwatch.Elapsed, bulkCopy, columns); + + if (ctx.HasExternalProperties) + { + var readEntities = reader.GetReadEntities(); + + if (readEntities.Count != 0) + await BulkInsertSeparatedOwnedEntitiesAsync((IReadOnlyList)readEntities, ctx, cancellationToken); + } + } + finally + { + await _ctx.Database.CloseConnectionAsync().ConfigureAwait(false); + } + } + + private async Task BulkInsertSeparatedOwnedEntitiesAsync( + IReadOnlyList parentEntities, + ISqlServerBulkOperationContext parentBulkOperationContext, + CancellationToken cancellationToken) + { + if (parentEntities.Count == 0) + return; + + foreach (var childContext in parentBulkOperationContext.GetChildren(parentEntities)) + { + var childTableName = childContext.EntityType.GetTableName() ?? throw new InvalidOperationException($"The entity '{childContext.EntityType.Name}' has no table name."); + + await BulkInsertAsync(childContext.Entities, + childContext.EntityType.GetSchema(), + childTableName, + childContext, + cancellationToken).ConfigureAwait(false); + } + } + + private string SetColumnMappings(SqlBulkCopy bulkCopy, IEntityDataReader reader) + { + var columnsSb = _stringBuilderPool.Get(); + + try + { + StoreObjectIdentifier? storeObject = null; + + for (var i = 0; i < reader.Properties.Count; i++) + { + var property = reader.Properties[i]; + + storeObject ??= property.GetStoreObject(); + var columnName = property.GetColumnName(storeObject.Value); + + bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(i, columnName)); + + if (columnsSb.Length > 0) + columnsSb.Append(", "); + + columnsSb.Append(columnName).Append(' ').Append(property.Property.GetColumnType(storeObject.Value)); + } + + return columnsSb.ToString(); + } + finally + { + _stringBuilderPool.Return(columnsSb); + } + } + + private SqlBulkCopy CreateSqlBulkCopy(SqlConnection sqlCon, SqlTransaction? sqlTx, string? schema, string tableName, SqlServerBulkInsertOptions sqlServerOptions) + { + var bulkCopy = new SqlBulkCopy(sqlCon, sqlServerOptions.SqlBulkCopyOptions, sqlTx) + { + DestinationTableName = _sqlGenerationHelper.DelimitIdentifier(tableName, schema), + EnableStreaming = sqlServerOptions.EnableStreaming + }; + + if (sqlServerOptions.BulkCopyTimeout.HasValue) + bulkCopy.BulkCopyTimeout = (int)sqlServerOptions.BulkCopyTimeout.Value.TotalSeconds; + + if (sqlServerOptions.BatchSize.HasValue) + bulkCopy.BatchSize = sqlServerOptions.BatchSize.Value; + + return bulkCopy; + } + + private void LogInserting(SqlBulkCopyOptions options, SqlBulkCopy bulkCopy, string columns) + { + _logger.Logger.LogDebug(EventIds.Inserting, + """ + Executing DbCommand [SqlBulkCopyOptions={SqlBulkCopyOptions}, BulkCopyTimeout={BulkCopyTimeout}, BatchSize={BatchSize}, EnableStreaming={EnableStreaming}] + INSERT BULK {Table} ({Columns}) + """, + options, + bulkCopy.BulkCopyTimeout, + bulkCopy.BatchSize, + bulkCopy.EnableStreaming, + bulkCopy.DestinationTableName, + columns); + } + + private void LogInserted(SqlBulkCopyOptions options, TimeSpan duration, SqlBulkCopy bulkCopy, string columns) + { + _logger.Logger.LogInformation(EventIds.Inserted, + """ + Executed DbCommand ({Duration}ms) [SqlBulkCopyOptions={SqlBulkCopyOptions}, BulkCopyTimeout={BulkCopyTimeout}, BatchSize={BatchSize}, EnableStreaming={EnableStreaming}] + INSERT BULK {Table} ({Columns}) + """, + (long)duration.TotalMilliseconds, + options, + bulkCopy.BulkCopyTimeout, + bulkCopy.BatchSize, + bulkCopy.EnableStreaming, + bulkCopy.DestinationTableName, + columns); + } + + /// + public Task> BulkInsertValuesIntoTempTableAsync( + IEnumerable values, + ITempTableBulkInsertOptions options, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(values); + ArgumentNullException.ThrowIfNull(options); + + if (options is not SqlServerTempTableBulkInsertOptions sqlServerOptions) + sqlServerOptions = new SqlServerTempTableBulkInsertOptions(options); + + return BulkInsertIntoTempTableAsync>(values, + sqlServerOptions, + SqlServerBulkOperationContextFactoryForValues.Instance, + query => query.Select(t => t.Column1), + cancellationToken); + } + + /// + public Task> BulkInsertIntoTempTableAsync( + IEnumerable entities, + ITempTableBulkInsertOptions options, + CancellationToken cancellationToken = default) + where T : class + { + ArgumentNullException.ThrowIfNull(entities); + ArgumentNullException.ThrowIfNull(options); + + if (options is not SqlServerTempTableBulkInsertOptions sqlServerOptions) + sqlServerOptions = new SqlServerTempTableBulkInsertOptions(options); + + return BulkInsertIntoTempTableAsync(entities, + sqlServerOptions, + SqlServerBulkOperationContextFactoryForEntities.Instance, + static query => query, + cancellationToken); + } + + private async Task> BulkInsertIntoTempTableAsync( + IEnumerable entitiesOrValues, + SqlServerTempTableBulkInsertOptions options, + ISqlServerBulkOperationContextFactory bulkOperationContextFactory, + Func, IQueryable> projection, + CancellationToken cancellationToken) + where TEntity : class + { + var type = typeof(TEntity); + var entityTypeName = EntityNameProvider.GetTempTableName(type); + var entityType = _ctx.Model.GetEntityType(entityTypeName, type); + + var tempTableCreationOptions = options.GetTempTableCreationOptions(); + var primaryKeyCreation = tempTableCreationOptions.PrimaryKeyCreation; // keep this one in a local variable because we may change it in the next line + + if (options.MomentOfPrimaryKeyCreation == MomentOfSqlServerPrimaryKeyCreation.AfterBulkInsert) + tempTableCreationOptions.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None; + + var tempTableCreator = _ctx.GetService(); + var tempTableReference = await tempTableCreator.CreateTempTableAsync(entityType, tempTableCreationOptions, cancellationToken).ConfigureAwait(false); + + try + { + var bulkInsertOptions = options.GetBulkInsertOptions(); + await BulkInsertAsync(entityType, entitiesOrValues, null, tempTableReference.Name, bulkInsertOptions, bulkOperationContextFactory, cancellationToken).ConfigureAwait(false); + + if (options.MomentOfPrimaryKeyCreation == MomentOfSqlServerPrimaryKeyCreation.AfterBulkInsert) + { + tempTableCreationOptions.PrimaryKeyCreation = primaryKeyCreation; + + var tempTableProperties = tempTableCreationOptions.PropertiesToInclude.DeterminePropertiesForTempTable(entityType); + var keyProperties = tempTableCreationOptions.PrimaryKeyCreation.GetPrimaryKeyProperties(entityType, tempTableProperties); + await tempTableCreator.CreatePrimaryKeyAsync(_ctx, keyProperties, tempTableReference.Name, tempTableCreationOptions.TruncateTableIfExists, cancellationToken).ConfigureAwait(false); + } + + var dbSet = entityType.Name == entityTypeName + ? _ctx.Set(entityTypeName) + : _ctx.Set(); + + var query = dbSet.FromTempTable(new TempTableInfo(tempTableReference.Name)); + + var pk = entityType.FindPrimaryKey(); + + if (pk is not null && pk.Properties.Count != 0) + query = query.AsNoTracking(); + + return new TempTableQuery(projection(query), tempTableReference); + } + catch (Exception) + { + await tempTableReference.DisposeAsync().ConfigureAwait(false); + throw; + } + } + + /// + public async Task TruncateTableAsync(CancellationToken cancellationToken = default) + where T : class + { + await TruncateTableAsync(typeof(T), cancellationToken); + } + + /// + public async Task TruncateTableAsync(Type type, CancellationToken cancellationToken = default) + { + var entityType = _ctx.Model.GetEntityType(type); + var tableName = entityType.GetTableName() ?? throw new InvalidOperationException($"The entity '{entityType.Name}' has no table name."); + + var tableIdentifier = _sqlGenerationHelper.DelimitIdentifier(tableName, entityType.GetSchema()); + var truncateStatement = $"TRUNCATE TABLE {tableIdentifier};"; + + await _ctx.Database.ExecuteSqlRawAsync(truncateStatement, cancellationToken); + } + + /// + public async Task BulkUpdateAsync( + IEnumerable entities, + IBulkUpdateOptions options, + CancellationToken cancellationToken = default) + where T : class + { + ArgumentNullException.ThrowIfNull(entities); + ArgumentNullException.ThrowIfNull(options); + + if (options is not SqlServerBulkUpdateOptions sqlServerOptions) + sqlServerOptions = new SqlServerBulkUpdateOptions(options); + + return await BulkUpdateAsync(entities, sqlServerOptions, cancellationToken); + } + + private async Task BulkUpdateAsync(IEnumerable entities, SqlServerBulkUpdateOptions options, CancellationToken cancellationToken) + where T : class + { + var entityType = _ctx.Model.GetEntityType(typeof(T)); + var propertiesForUpdate = options.PropertiesToUpdate.DeterminePropertiesForUpdate(entityType, true); + + if (propertiesForUpdate.Count == 0) + throw new ArgumentException("The number of properties to update cannot be 0."); + + var tempTableOptions = options.GetTempTableBulkInsertOptions(); + await using var tempTable = await BulkInsertIntoTempTableAsync(entities, tempTableOptions, cancellationToken); + + var mergeStatement = CreateMergeCommand(entityType, tempTable.Name, options, null, propertiesForUpdate, options.KeyProperties.DetermineKeyProperties(entityType)); + + return await _ctx.Database.ExecuteSqlRawAsync(mergeStatement, cancellationToken); + } + + /// + public async Task BulkInsertOrUpdateAsync( + IEnumerable entities, + IBulkInsertOrUpdateOptions options, + CancellationToken cancellationToken = default) + where T : class + { + ArgumentNullException.ThrowIfNull(entities); + ArgumentNullException.ThrowIfNull(options); + + if (options is not SqlServerBulkInsertOrUpdateOptions sqlServerOptions) + sqlServerOptions = new SqlServerBulkInsertOrUpdateOptions(options); + + return await BulkInsertOrUpdateAsync(entities, sqlServerOptions, cancellationToken); + } + + private async Task BulkInsertOrUpdateAsync(IEnumerable entities, SqlServerBulkInsertOrUpdateOptions options, CancellationToken cancellationToken) + where T : class + { + var entityType = _ctx.Model.GetEntityType(typeof(T)); + var propertiesForInsert = options.PropertiesToInsert.DeterminePropertiesForInsert(entityType, true); + var propertiesForUpdate = options.PropertiesToUpdate.DeterminePropertiesForInsert(entityType, true); + + if (propertiesForInsert.Count == 0) + throw new ArgumentException("The number of properties to insert cannot be 0."); + + var tempTableOptions = options.GetTempTableBulkInsertOptions(); + await using var tempTable = await BulkInsertIntoTempTableAsync(entities, tempTableOptions, cancellationToken); + + var mergeStatement = CreateMergeCommand(entityType, tempTable.Name, options, propertiesForInsert, propertiesForUpdate, options.KeyProperties.DetermineKeyProperties(entityType)); + + return await _ctx.Database.ExecuteSqlRawAsync(mergeStatement, cancellationToken); + } + + private string CreateMergeCommand( + IEntityType entityType, + string sourceTempTableName, + T options, + IReadOnlyList? propertiesToInsert, + IReadOnlyList propertiesToUpdate, + IReadOnlyList keyProperties) + where T : ISqlServerMergeOperationOptions + { + var sb = _stringBuilderPool.Get(); + + try + { + var tableName = entityType.GetTableName() ?? throw new Exception($"The entity '{entityType.Name}' has no table name."); + var storeObject = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table) + ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{entityType.Name}'."); + + sb.Append("MERGE INTO ") + .Append(_sqlGenerationHelper.DelimitIdentifier(tableName, entityType.GetSchema())); + + if (options.MergeTableHints.Count != 0) + { + sb.Append(" WITH ("); + + for (var i = 0; i < options.MergeTableHints.Count; i++) + { + if (i != 0) + sb.Append(", "); + + sb.Append(options.MergeTableHints[i]); + } + + sb.Append(")"); + } + + sb.AppendLine(" AS d") + .Append("USING ") + .Append(_sqlGenerationHelper.DelimitIdentifier(sourceTempTableName)) + .Append(" AS s ON "); + + var isFirstIteration = true; + + foreach (var property in keyProperties) + { + if (!isFirstIteration) + sb.AppendLine(" AND "); + + var columnName = property.GetColumnName(storeObject) + ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.DeclaringType.Name}'."); + var escapedColumnName = _sqlGenerationHelper.DelimitIdentifier(columnName); + + sb.Append("(d.").Append(escapedColumnName).Append(" = s.").Append(escapedColumnName); + + if (property.IsNullable) + sb.Append(" OR d.").Append(escapedColumnName).Append(" IS NULL AND s.").Append(escapedColumnName).Append(" IS NULL"); + + sb.Append(")"); + isFirstIteration = false; + } + + isFirstIteration = true; + + foreach (var property in propertiesToUpdate.Select(p => p.Property).Except(keyProperties)) + { + if (!isFirstIteration) + { + sb.Append(", "); + } + else + { + sb.AppendLine() + .AppendLine("WHEN MATCHED THEN") + .Append("\tUPDATE SET "); + } + + var columnName = property.GetColumnName(storeObject) + ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.DeclaringType.Name}'."); + var escapedColumnName = _sqlGenerationHelper.DelimitIdentifier(columnName); + + sb.Append("d.").Append(escapedColumnName).Append(" = s.").Append(escapedColumnName); + isFirstIteration = false; + } + + if (propertiesToInsert is not null) + { + sb.AppendLine() + .AppendLine("WHEN NOT MATCHED THEN") + .Append("\tINSERT ("); + + isFirstIteration = true; + + foreach (var property in propertiesToInsert) + { + if (!isFirstIteration) + sb.Append(", "); + + var columnName = property.GetColumnName(storeObject); + var escapedColumnName = _sqlGenerationHelper.DelimitIdentifier(columnName); + + sb.Append(escapedColumnName); + isFirstIteration = false; + } + + sb.AppendLine(")") + .Append("\tVALUES ("); + + isFirstIteration = true; + + foreach (var property in propertiesToInsert) + { + if (!isFirstIteration) + sb.Append(", "); + + var columnName = property.GetColumnName(storeObject); + var escapedColumnName = _sqlGenerationHelper.DelimitIdentifier(columnName); + + sb.Append("s.").Append(escapedColumnName); + isFirstIteration = false; + } + + sb.Append(")"); + } + + sb.Append(_sqlGenerationHelper.StatementTerminator); + + return sb.ToString(); + } + finally + { + _stringBuilderPool.Return(sb); + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkUpdateOptions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkUpdateOptions.cs index 4c20df4f..c58f5af5 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkUpdateOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerBulkUpdateOptions.cs @@ -1,64 +1,64 @@ -using Microsoft.Data.SqlClient; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Bulk update options for SQL Server. -/// -public sealed class SqlServerBulkUpdateOptions : ISqlServerMergeOperationOptions, IBulkUpdateOptions -{ - /// - public IEntityPropertiesProvider? PropertiesToUpdate { get; set; } - - /// - public IEntityPropertiesProvider? KeyProperties { get; set; } - - /// - public List MergeTableHints { get; } - - /// - public SqlServerBulkOperationTempTableOptions TempTableOptions { get; } - - /// - /// Initializes new instance of . - /// - /// Options to initialize from. - public SqlServerBulkUpdateOptions(IBulkUpdateOptions? optionsToInitializeFrom = null) - { - if (optionsToInitializeFrom is not null) - { - PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate; - KeyProperties = optionsToInitializeFrom.KeyProperties; - } - - if (optionsToInitializeFrom is ISqlServerMergeOperationOptions mergeOptions) - { - TempTableOptions = new SqlServerBulkOperationTempTableOptions(mergeOptions.TempTableOptions); - MergeTableHints = mergeOptions.MergeTableHints.ToList(); - } - else - { - TempTableOptions = new SqlServerBulkOperationTempTableOptions - { - SqlBulkCopyOptions = SqlBulkCopyOptions.KeepIdentity - }; - MergeTableHints = new List { SqlServerTableHintLimited.HoldLock }; - } - } - - /// - /// Gets the options for bulk insert into a temp table. - /// - public SqlServerTempTableBulkOperationOptions GetTempTableBulkInsertOptions() - { - var options = new SqlServerTempTableBulkInsertOptions - { - PropertiesToInsert = PropertiesToUpdate is null ? null : CompositeTempTableEntityPropertiesProvider.CreateForUpdate(PropertiesToUpdate, KeyProperties), - Advanced = { UsePropertiesToInsertForTempTableCreation = true } - }; - - TempTableOptions.Populate(options); - - return options; - } -} +using Microsoft.Data.SqlClient; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Bulk update options for SQL Server. +/// +public sealed class SqlServerBulkUpdateOptions : ISqlServerMergeOperationOptions, IBulkUpdateOptions +{ + /// + public IEntityPropertiesProvider? PropertiesToUpdate { get; set; } + + /// + public IEntityPropertiesProvider? KeyProperties { get; set; } + + /// + public List MergeTableHints { get; } + + /// + public SqlServerBulkOperationTempTableOptions TempTableOptions { get; } + + /// + /// Initializes new instance of . + /// + /// Options to initialize from. + public SqlServerBulkUpdateOptions(IBulkUpdateOptions? optionsToInitializeFrom = null) + { + if (optionsToInitializeFrom is not null) + { + PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate; + KeyProperties = optionsToInitializeFrom.KeyProperties; + } + + if (optionsToInitializeFrom is ISqlServerMergeOperationOptions mergeOptions) + { + TempTableOptions = new SqlServerBulkOperationTempTableOptions(mergeOptions.TempTableOptions); + MergeTableHints = mergeOptions.MergeTableHints.ToList(); + } + else + { + TempTableOptions = new SqlServerBulkOperationTempTableOptions + { + SqlBulkCopyOptions = SqlBulkCopyOptions.KeepIdentity + }; + MergeTableHints = new List { SqlServerTableHintLimited.HoldLock }; + } + } + + /// + /// Gets the options for bulk insert into a temp table. + /// + public SqlServerTempTableBulkOperationOptions GetTempTableBulkInsertOptions() + { + var options = new SqlServerTempTableBulkInsertOptions + { + PropertiesToInsert = PropertiesToUpdate is null ? null : CompositeTempTableEntityPropertiesProvider.CreateForUpdate(PropertiesToUpdate, KeyProperties), + Advanced = { UsePropertiesToInsertForTempTableCreation = true } + }; + + TempTableOptions.Populate(options); + + return options; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerTempTableBulkInsertOptions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerTempTableBulkInsertOptions.cs index 2499cc32..e7cb8240 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerTempTableBulkInsertOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerTempTableBulkInsertOptions.cs @@ -1,16 +1,16 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Options for bulk insert into temp tables. -/// -public sealed class SqlServerTempTableBulkInsertOptions : SqlServerTempTableBulkOperationOptions -{ - /// - /// Initializes new instance of . - /// - /// Options to initialize from. - public SqlServerTempTableBulkInsertOptions(ITempTableBulkInsertOptions? optionsToInitializeFrom = null) - : base(optionsToInitializeFrom) - { - } -} +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Options for bulk insert into temp tables. +/// +public sealed class SqlServerTempTableBulkInsertOptions : SqlServerTempTableBulkOperationOptions +{ + /// + /// Initializes new instance of . + /// + /// Options to initialize from. + public SqlServerTempTableBulkInsertOptions(ITempTableBulkInsertOptions? optionsToInitializeFrom = null) + : base(optionsToInitializeFrom) + { + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerTempTableBulkOperationOptions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerTempTableBulkOperationOptions.cs index 1d6a0743..d34e5a8d 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerTempTableBulkOperationOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/BulkOperations/SqlServerTempTableBulkOperationOptions.cs @@ -1,152 +1,152 @@ -using System.Data; -using Microsoft.Data.SqlClient; -using Thinktecture.EntityFrameworkCore.TempTables; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Options for bulk insert into temp tables. -/// -public abstract class SqlServerTempTableBulkOperationOptions : ITempTableBulkInsertOptions -{ - /// - /// Defines when the primary key should be created. - /// Default is set to . - /// - public MomentOfSqlServerPrimaryKeyCreation MomentOfPrimaryKeyCreation { get; set; } - - /// - public bool TruncateTableIfExists { get; set; } - - /// - public bool DropTableOnDispose { get; set; } - - /// - public ITempTableNameProvider? TableNameProvider { get; set; } - - /// - public IPrimaryKeyPropertiesProvider? PrimaryKeyCreation { get; set; } - - /// - /// Timeout used by - /// - public TimeSpan? BulkCopyTimeout { get; set; } - - /// - /// Options used by . - /// - public SqlBulkCopyOptions SqlBulkCopyOptions { get; set; } - - /// - /// Batch size used by . - /// - public int? BatchSize { get; set; } - - /// - /// Enables or disables a object to stream data from an object. - /// Default is set to true. - /// - public bool EnableStreaming { get; set; } - - /// - public IEntityPropertiesProvider? PropertiesToInsert { get; set; } - - /// - /// Adds "COLLATE database_default" to columns so the collation matches with the one of the user database instead of the master db. - /// - public bool UseDefaultDatabaseCollation { get; set; } - - /// - /// Advanced settings. - /// - public AdvancedSqlServerTempTableBulkOperationOptions Advanced { get; } - - /// - /// Initializes new instance of . - /// - /// Options to initialize from. - protected SqlServerTempTableBulkOperationOptions(ITempTableBulkInsertOptions? optionsToInitializeFrom) - { - Advanced = new AdvancedSqlServerTempTableBulkOperationOptions(); - - if (optionsToInitializeFrom is null) - { - DropTableOnDispose = true; - MomentOfPrimaryKeyCreation = MomentOfSqlServerPrimaryKeyCreation.AfterBulkInsert; - EnableStreaming = true; - } - else - { - TruncateTableIfExists = optionsToInitializeFrom.TruncateTableIfExists; - DropTableOnDispose = optionsToInitializeFrom.DropTableOnDispose; - TableNameProvider = optionsToInitializeFrom.TableNameProvider; - PrimaryKeyCreation = optionsToInitializeFrom.PrimaryKeyCreation; - PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert; - - if (optionsToInitializeFrom is SqlServerTempTableBulkOperationOptions sqlServerOptions) - { - UseDefaultDatabaseCollation = sqlServerOptions.UseDefaultDatabaseCollation; - - BatchSize = sqlServerOptions.BatchSize; - EnableStreaming = sqlServerOptions.EnableStreaming; - BulkCopyTimeout = sqlServerOptions.BulkCopyTimeout; - SqlBulkCopyOptions = sqlServerOptions.SqlBulkCopyOptions; - MomentOfPrimaryKeyCreation = sqlServerOptions.MomentOfPrimaryKeyCreation; - - Advanced.UsePropertiesToInsertForTempTableCreation = sqlServerOptions.Advanced.UsePropertiesToInsertForTempTableCreation; - } - } - } - - /// - /// Gets options for creation of the temp table. - /// - public SqlServerTempTableCreationOptions GetTempTableCreationOptions() - { - return new SqlServerTempTableCreationOptions - { - TruncateTableIfExists = TruncateTableIfExists, - DropTableOnDispose = DropTableOnDispose, - TableNameProvider = TableNameProvider, - PrimaryKeyCreation = PrimaryKeyCreation, - UseDefaultDatabaseCollation = UseDefaultDatabaseCollation, - - PropertiesToInclude = Advanced.UsePropertiesToInsertForTempTableCreation ? PropertiesToInsert : null - }; - } - - /// - /// Gets options for bulk insert. - /// - public SqlServerBulkInsertOptions GetBulkInsertOptions() - { - return new SqlServerBulkInsertOptions - { - BatchSize = BatchSize, - EnableStreaming = EnableStreaming, - BulkCopyTimeout = BulkCopyTimeout, - SqlBulkCopyOptions = SqlBulkCopyOptions, - PropertiesToInsert = PropertiesToInsert - }; - } - - /// - /// Advanced options. - /// - public class AdvancedSqlServerTempTableBulkOperationOptions - { - /// - /// By default all properties of the corresponding entity are used to create the temp table, - /// i.e. the are ignored. - /// This approach ensures that the returned doesn't throw an exception on read. - /// The drawback is that all required (i.e. non-null) properties must be set accordingly and included into . - /// - /// If a use case requires the use of a subset of properties of the corresponding entity then set this setting to true. - /// In this case the temp table is created by using only. - /// The omitted properties must not be selected or used by the returned . - /// - /// Default is false. - /// - public bool UsePropertiesToInsertForTempTableCreation { get; set; } - } -} +using System.Data; +using Microsoft.Data.SqlClient; +using Thinktecture.EntityFrameworkCore.TempTables; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Options for bulk insert into temp tables. +/// +public abstract class SqlServerTempTableBulkOperationOptions : ITempTableBulkInsertOptions +{ + /// + /// Defines when the primary key should be created. + /// Default is set to . + /// + public MomentOfSqlServerPrimaryKeyCreation MomentOfPrimaryKeyCreation { get; set; } + + /// + public bool TruncateTableIfExists { get; set; } + + /// + public bool DropTableOnDispose { get; set; } + + /// + public ITempTableNameProvider? TableNameProvider { get; set; } + + /// + public IPrimaryKeyPropertiesProvider? PrimaryKeyCreation { get; set; } + + /// + /// Timeout used by + /// + public TimeSpan? BulkCopyTimeout { get; set; } + + /// + /// Options used by . + /// + public SqlBulkCopyOptions SqlBulkCopyOptions { get; set; } + + /// + /// Batch size used by . + /// + public int? BatchSize { get; set; } + + /// + /// Enables or disables a object to stream data from an object. + /// Default is set to true. + /// + public bool EnableStreaming { get; set; } + + /// + public IEntityPropertiesProvider? PropertiesToInsert { get; set; } + + /// + /// Adds "COLLATE database_default" to columns so the collation matches with the one of the user database instead of the master db. + /// + public bool UseDefaultDatabaseCollation { get; set; } + + /// + /// Advanced settings. + /// + public AdvancedSqlServerTempTableBulkOperationOptions Advanced { get; } + + /// + /// Initializes new instance of . + /// + /// Options to initialize from. + protected SqlServerTempTableBulkOperationOptions(ITempTableBulkInsertOptions? optionsToInitializeFrom) + { + Advanced = new AdvancedSqlServerTempTableBulkOperationOptions(); + + if (optionsToInitializeFrom is null) + { + DropTableOnDispose = true; + MomentOfPrimaryKeyCreation = MomentOfSqlServerPrimaryKeyCreation.AfterBulkInsert; + EnableStreaming = true; + } + else + { + TruncateTableIfExists = optionsToInitializeFrom.TruncateTableIfExists; + DropTableOnDispose = optionsToInitializeFrom.DropTableOnDispose; + TableNameProvider = optionsToInitializeFrom.TableNameProvider; + PrimaryKeyCreation = optionsToInitializeFrom.PrimaryKeyCreation; + PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert; + + if (optionsToInitializeFrom is SqlServerTempTableBulkOperationOptions sqlServerOptions) + { + UseDefaultDatabaseCollation = sqlServerOptions.UseDefaultDatabaseCollation; + + BatchSize = sqlServerOptions.BatchSize; + EnableStreaming = sqlServerOptions.EnableStreaming; + BulkCopyTimeout = sqlServerOptions.BulkCopyTimeout; + SqlBulkCopyOptions = sqlServerOptions.SqlBulkCopyOptions; + MomentOfPrimaryKeyCreation = sqlServerOptions.MomentOfPrimaryKeyCreation; + + Advanced.UsePropertiesToInsertForTempTableCreation = sqlServerOptions.Advanced.UsePropertiesToInsertForTempTableCreation; + } + } + } + + /// + /// Gets options for creation of the temp table. + /// + public SqlServerTempTableCreationOptions GetTempTableCreationOptions() + { + return new SqlServerTempTableCreationOptions + { + TruncateTableIfExists = TruncateTableIfExists, + DropTableOnDispose = DropTableOnDispose, + TableNameProvider = TableNameProvider, + PrimaryKeyCreation = PrimaryKeyCreation, + UseDefaultDatabaseCollation = UseDefaultDatabaseCollation, + + PropertiesToInclude = Advanced.UsePropertiesToInsertForTempTableCreation ? PropertiesToInsert : null + }; + } + + /// + /// Gets options for bulk insert. + /// + public SqlServerBulkInsertOptions GetBulkInsertOptions() + { + return new SqlServerBulkInsertOptions + { + BatchSize = BatchSize, + EnableStreaming = EnableStreaming, + BulkCopyTimeout = BulkCopyTimeout, + SqlBulkCopyOptions = SqlBulkCopyOptions, + PropertiesToInsert = PropertiesToInsert + }; + } + + /// + /// Advanced options. + /// + public class AdvancedSqlServerTempTableBulkOperationOptions + { + /// + /// By default all properties of the corresponding entity are used to create the temp table, + /// i.e. the are ignored. + /// This approach ensures that the returned doesn't throw an exception on read. + /// The drawback is that all required (i.e. non-null) properties must be set accordingly and included into . + /// + /// If a use case requires the use of a subset of properties of the corresponding entity then set this setting to true. + /// In this case the temp table is created by using only. + /// The omitted properties must not be selected or used by the returned . + /// + /// Default is false. + /// + public bool UsePropertiesToInsertForTempTableCreation { get; set; } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Infrastructure/SqlServerDbContextOptionsExtension.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Infrastructure/SqlServerDbContextOptionsExtension.cs index ba6ca432..2342799a 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Infrastructure/SqlServerDbContextOptionsExtension.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Infrastructure/SqlServerDbContextOptionsExtension.cs @@ -1,323 +1,323 @@ -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Text; -using System.Text.Json; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Thinktecture.EntityFrameworkCore.BulkOperations; -using Thinktecture.EntityFrameworkCore.Migrations; -using Thinktecture.EntityFrameworkCore.Parameters; -using Thinktecture.EntityFrameworkCore.Query; -using Thinktecture.EntityFrameworkCore.Query.ExpressionTranslators; -using Thinktecture.EntityFrameworkCore.TempTables; - -namespace Thinktecture.EntityFrameworkCore.Infrastructure; - -/// -/// Extensions for DbContextOptions. -/// -public sealed class SqlServerDbContextOptionsExtension : DbContextOptionsExtensionBase, IDbContextOptionsExtension -{ - private readonly RelationalDbContextOptionsExtension _relationalOptions; - - private SqlServerDbContextOptionsExtensionInfo? _info; - - /// - public DbContextOptionsExtensionInfo Info => _info ??= new SqlServerDbContextOptionsExtensionInfo(this); - - /// - /// Enables and disables support for "RowNumber". - /// - [Obsolete($"Use '{nameof(AddWindowFunctionsSupport)}'")] - public bool AddRowNumberSupport - { - get => _relationalOptions.AddWindowFunctionsSupport; - set => _relationalOptions.AddWindowFunctionsSupport = value; - } - - /// - /// Enables and disables support for window functions like "RowNumber". - /// - public bool AddWindowFunctionsSupport - { - get => _relationalOptions.AddWindowFunctionsSupport; - set => _relationalOptions.AddWindowFunctionsSupport = value; - } - - /// - /// Enables and disables tenants support. - /// - public bool AddTenantDatabaseSupport - { - get => _relationalOptions.AddTenantDatabaseSupport; - set => _relationalOptions.AddTenantDatabaseSupport = value; - } - - private bool _addCustomQueryableMethodTranslatingExpressionVisitorFactory; - - /// - /// A custom factory is registered if true. - /// The factory is required to be able to translate custom methods like . - /// - public bool AddCustomQueryableMethodTranslatingExpressionVisitorFactory - { - get => _addCustomQueryableMethodTranslatingExpressionVisitorFactory || AddBulkOperationSupport || AddWindowFunctionsSupport || AddTableHintSupport; - set => _addCustomQueryableMethodTranslatingExpressionVisitorFactory = value; - } - - private bool _addCustomRelationalParameterBasedSqlProcessorFactory; - - /// - /// A custom factory is registered if true. - /// The factory is required for some features. - /// - public bool AddCustomRelationalParameterBasedSqlProcessorFactory - { - get => _addCustomRelationalParameterBasedSqlProcessorFactory || AddBulkOperationSupport || AddWindowFunctionsSupport || AddTableHintSupport; - set => _addCustomRelationalParameterBasedSqlProcessorFactory = value; - } - - private bool _addCustomQuerySqlGeneratorFactory; - - /// - /// A custom factory is registered if true. - /// The factory is required for some features like 'tenant database support' or for generation of 'DELETE' statements. - /// - public bool AddCustomQuerySqlGeneratorFactory - { - get => _addCustomQuerySqlGeneratorFactory || AddBulkOperationSupport || AddTenantDatabaseSupport || AddWindowFunctionsSupport || AddTableHintSupport; - set => _addCustomQuerySqlGeneratorFactory = value; - } - - /// - /// Enables and disables support for table hints. - /// - public bool AddTableHintSupport { get; set; } - - /// - /// Enables and disables support for bulk operations and temp tables. - /// - public bool AddBulkOperationSupport { get; set; } - - /// - /// Indication whether to configure temp tables for primitive types. - /// - public bool ConfigureTempTablesForPrimitiveTypes { get; set; } - - private JsonSerializerOptions? _collectionParameterJsonSerializerOptions; - private bool _addCollectionParameterSupport; - internal bool ConfigureCollectionParametersForPrimitiveTypes { get; private set; } - internal bool UseDeferredCollectionParameterSerialization { get; private set; } - - /// - /// Changes the implementation of to . - /// - public bool UseThinktectureSqlServerMigrationsSqlGenerator { get; set; } - - /// - /// Initializes new instance of . - /// - /// An instance of . - public SqlServerDbContextOptionsExtension(RelationalDbContextOptionsExtension relationalOptions) - { - _relationalOptions = relationalOptions ?? throw new ArgumentNullException(nameof(relationalOptions)); - } - - /// - [SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] - public void ApplyServices(IServiceCollection services) - { - services.TryAddSingleton(); - services.AddSingleton(provider => provider.GetRequiredService()); - services.AddSingleton(provider => provider.GetRequiredService()); - - services.Add(GetLifetime()); - - if (AddCustomQueryableMethodTranslatingExpressionVisitorFactory) - AddWithCheck(services); - - if (AddCustomQuerySqlGeneratorFactory) - AddWithCheck(services); - - if (AddCustomRelationalParameterBasedSqlProcessorFactory) - AddWithCheck(services); - - if (AddBulkOperationSupport) - { - services.Add(GetLifetime()); - - services.AddSingleton>(); - services.AddSingleton>(); - services.TryAddScoped(); - services.TryAddScoped(provider => provider.GetRequiredService()); - services.AddTempTableSuffixComponents(); - - AddEntityDataReader(services); - services.TryAddScoped(); - services.TryAddScoped(provider => provider.GetRequiredService()); - services.TryAddScoped(provider => provider.GetRequiredService()); - services.TryAddScoped(provider => provider.GetRequiredService()); - services.TryAddScoped(provider => provider.GetRequiredService()); - services.TryAddScoped(provider => provider.GetRequiredService()); - } - - if (_addCollectionParameterSupport) - { - var jsonSerializerOptions = _collectionParameterJsonSerializerOptions ?? new JsonSerializerOptions(); - - services.AddSingleton(serviceProvider => ActivatorUtilities.CreateInstance(serviceProvider, jsonSerializerOptions)); - services.Add(GetLifetime()); - } - - if (UseThinktectureSqlServerMigrationsSqlGenerator) - AddWithCheck(services); - - if (_relationalOptions.AddSchemaRespectingComponents) - services.AddSingleton(); - - if (AddWindowFunctionsSupport) - { - var lifetime = GetLifetime(); - services.Add(ServiceDescriptor.Describe(typeof(IEvaluatableExpressionFilterPlugin), typeof(WindowFunctionEvaluatableExpressionFilterPlugin), lifetime)); - - } - } - - /// - /// Registers a custom service with internal dependency injection container of Entity Framework Core. - /// - /// Service type. - /// Implementation type. - /// Service lifetime. - /// or is null. - public void Register(Type serviceType, Type implementationType, ServiceLifetime lifetime) - { - _relationalOptions.Register(serviceType, implementationType, lifetime); - } - - /// - /// Registers a custom service instance with internal dependency injection container of Entity Framework Core. - /// - /// Service type. - /// Implementation instance. - /// or is null. - public void Register(Type serviceType, object implementationInstance) - { - _relationalOptions.Register(serviceType, implementationInstance); - } - - /// - /// Enables and disables support for queryable parameters. - /// - public SqlServerDbContextOptionsExtension AddCollectionParameterSupport( - bool addCollectionParameterSupport, - JsonSerializerOptions? jsonSerializerOptions, - bool configureCollectionParametersForPrimitiveTypes, - bool useDeferredSerialization) - { - _addCollectionParameterSupport = addCollectionParameterSupport; - _collectionParameterJsonSerializerOptions = jsonSerializerOptions; - ConfigureCollectionParametersForPrimitiveTypes = addCollectionParameterSupport && configureCollectionParametersForPrimitiveTypes; - UseDeferredCollectionParameterSerialization = addCollectionParameterSupport && useDeferredSerialization; - - return this; - } - - /// - public void Validate(IDbContextOptions options) - { - } - - private class SqlServerDbContextOptionsExtensionInfo : DbContextOptionsExtensionInfo - { - private readonly SqlServerDbContextOptionsExtension _extension; - public override bool IsDatabaseProvider => false; - - private string? _logFragment; - - public override string LogFragment => _logFragment ??= CreateLogFragment(); - - private string CreateLogFragment() - { - var sb = new StringBuilder(); - - if (_extension.AddBulkOperationSupport) - sb.Append("BulkOperationSupport "); - - if (_extension._addCollectionParameterSupport) - sb.Append("CollectionParameterSupport "); - - if (_extension.AddTableHintSupport) - sb.Append("TableHintSupport "); - - if (_extension.UseThinktectureSqlServerMigrationsSqlGenerator) - sb.Append("ThinktectureSqlServerMigrationsSqlGenerator "); - - return sb.ToString(); - } - - /// - public SqlServerDbContextOptionsExtensionInfo(SqlServerDbContextOptionsExtension extension) - : base(extension) - { - _extension = extension ?? throw new ArgumentNullException(nameof(extension)); - } - - /// - public override int GetServiceProviderHashCode() - { - var hashCode = new HashCode(); - - hashCode.Add(_extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory); - hashCode.Add(_extension.AddCustomQuerySqlGeneratorFactory); - hashCode.Add(_extension.AddCustomRelationalParameterBasedSqlProcessorFactory); - hashCode.Add(_extension.AddBulkOperationSupport); - hashCode.Add(_extension.ConfigureTempTablesForPrimitiveTypes); - hashCode.Add(_extension._addCollectionParameterSupport); - hashCode.Add(_extension.ConfigureCollectionParametersForPrimitiveTypes); - hashCode.Add(_extension.UseDeferredCollectionParameterSerialization); - hashCode.Add(_extension._collectionParameterJsonSerializerOptions); - hashCode.Add(_extension.AddTenantDatabaseSupport); - hashCode.Add(_extension.AddTableHintSupport); - hashCode.Add(_extension.UseThinktectureSqlServerMigrationsSqlGenerator); - - return hashCode.ToHashCode(); - } - - /// - public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) - { - return other is SqlServerDbContextOptionsExtensionInfo otherSqlServerInfo - && _extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory == otherSqlServerInfo._extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory - && _extension.AddCustomQuerySqlGeneratorFactory == otherSqlServerInfo._extension.AddCustomQuerySqlGeneratorFactory - && _extension.AddCustomRelationalParameterBasedSqlProcessorFactory == otherSqlServerInfo._extension.AddCustomRelationalParameterBasedSqlProcessorFactory - && _extension.AddBulkOperationSupport == otherSqlServerInfo._extension.AddBulkOperationSupport - && _extension.ConfigureTempTablesForPrimitiveTypes == otherSqlServerInfo._extension.ConfigureTempTablesForPrimitiveTypes - && _extension._addCollectionParameterSupport == otherSqlServerInfo._extension._addCollectionParameterSupport - && _extension.ConfigureCollectionParametersForPrimitiveTypes == otherSqlServerInfo._extension.ConfigureCollectionParametersForPrimitiveTypes - && _extension.UseDeferredCollectionParameterSerialization == otherSqlServerInfo._extension.UseDeferredCollectionParameterSerialization - && _extension._collectionParameterJsonSerializerOptions == otherSqlServerInfo._extension._collectionParameterJsonSerializerOptions - && _extension.AddTenantDatabaseSupport == otherSqlServerInfo._extension.AddTenantDatabaseSupport - && _extension.AddTableHintSupport == otherSqlServerInfo._extension.AddTableHintSupport - && _extension.UseThinktectureSqlServerMigrationsSqlGenerator == otherSqlServerInfo._extension.UseThinktectureSqlServerMigrationsSqlGenerator; - } - - /// - public override void PopulateDebugInfo(IDictionary debugInfo) - { - debugInfo["Thinktecture:CustomQueryableMethodTranslatingExpressionVisitorFactory"] = _extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:CustomQuerySqlGeneratorFactory"] = _extension.AddCustomQuerySqlGeneratorFactory.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:CustomRelationalParameterBasedSqlProcessorFactory"] = _extension.AddCustomRelationalParameterBasedSqlProcessorFactory.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:BulkOperationSupport"] = _extension.AddBulkOperationSupport.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:CollectionParameterSupport"] = _extension._addCollectionParameterSupport.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:TenantDatabaseSupport"] = _extension.AddTenantDatabaseSupport.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:TableHintSupport"] = _extension.AddTableHintSupport.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:UseThinktectureSqlServerMigrationsSqlGenerator"] = _extension.UseThinktectureSqlServerMigrationsSqlGenerator.ToString(CultureInfo.InvariantCulture); - } - } -} +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text; +using System.Text.Json; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Thinktecture.EntityFrameworkCore.BulkOperations; +using Thinktecture.EntityFrameworkCore.Migrations; +using Thinktecture.EntityFrameworkCore.Parameters; +using Thinktecture.EntityFrameworkCore.Query; +using Thinktecture.EntityFrameworkCore.Query.ExpressionTranslators; +using Thinktecture.EntityFrameworkCore.TempTables; + +namespace Thinktecture.EntityFrameworkCore.Infrastructure; + +/// +/// Extensions for DbContextOptions. +/// +public sealed class SqlServerDbContextOptionsExtension : DbContextOptionsExtensionBase, IDbContextOptionsExtension +{ + private readonly RelationalDbContextOptionsExtension _relationalOptions; + + private SqlServerDbContextOptionsExtensionInfo? _info; + + /// + public DbContextOptionsExtensionInfo Info => _info ??= new SqlServerDbContextOptionsExtensionInfo(this); + + /// + /// Enables and disables support for "RowNumber". + /// + [Obsolete($"Use '{nameof(AddWindowFunctionsSupport)}'")] + public bool AddRowNumberSupport + { + get => _relationalOptions.AddWindowFunctionsSupport; + set => _relationalOptions.AddWindowFunctionsSupport = value; + } + + /// + /// Enables and disables support for window functions like "RowNumber". + /// + public bool AddWindowFunctionsSupport + { + get => _relationalOptions.AddWindowFunctionsSupport; + set => _relationalOptions.AddWindowFunctionsSupport = value; + } + + /// + /// Enables and disables tenants support. + /// + public bool AddTenantDatabaseSupport + { + get => _relationalOptions.AddTenantDatabaseSupport; + set => _relationalOptions.AddTenantDatabaseSupport = value; + } + + private bool _addCustomQueryableMethodTranslatingExpressionVisitorFactory; + + /// + /// A custom factory is registered if true. + /// The factory is required to be able to translate custom methods like . + /// + public bool AddCustomQueryableMethodTranslatingExpressionVisitorFactory + { + get => _addCustomQueryableMethodTranslatingExpressionVisitorFactory || AddBulkOperationSupport || AddWindowFunctionsSupport || AddTableHintSupport; + set => _addCustomQueryableMethodTranslatingExpressionVisitorFactory = value; + } + + private bool _addCustomRelationalParameterBasedSqlProcessorFactory; + + /// + /// A custom factory is registered if true. + /// The factory is required for some features. + /// + public bool AddCustomRelationalParameterBasedSqlProcessorFactory + { + get => _addCustomRelationalParameterBasedSqlProcessorFactory || AddBulkOperationSupport || AddWindowFunctionsSupport || AddTableHintSupport; + set => _addCustomRelationalParameterBasedSqlProcessorFactory = value; + } + + private bool _addCustomQuerySqlGeneratorFactory; + + /// + /// A custom factory is registered if true. + /// The factory is required for some features like 'tenant database support' or for generation of 'DELETE' statements. + /// + public bool AddCustomQuerySqlGeneratorFactory + { + get => _addCustomQuerySqlGeneratorFactory || AddBulkOperationSupport || AddTenantDatabaseSupport || AddWindowFunctionsSupport || AddTableHintSupport; + set => _addCustomQuerySqlGeneratorFactory = value; + } + + /// + /// Enables and disables support for table hints. + /// + public bool AddTableHintSupport { get; set; } + + /// + /// Enables and disables support for bulk operations and temp tables. + /// + public bool AddBulkOperationSupport { get; set; } + + /// + /// Indication whether to configure temp tables for primitive types. + /// + public bool ConfigureTempTablesForPrimitiveTypes { get; set; } + + private JsonSerializerOptions? _collectionParameterJsonSerializerOptions; + private bool _addCollectionParameterSupport; + internal bool ConfigureCollectionParametersForPrimitiveTypes { get; private set; } + internal bool UseDeferredCollectionParameterSerialization { get; private set; } + + /// + /// Changes the implementation of to . + /// + public bool UseThinktectureSqlServerMigrationsSqlGenerator { get; set; } + + /// + /// Initializes new instance of . + /// + /// An instance of . + public SqlServerDbContextOptionsExtension(RelationalDbContextOptionsExtension relationalOptions) + { + _relationalOptions = relationalOptions ?? throw new ArgumentNullException(nameof(relationalOptions)); + } + + /// + [SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] + public void ApplyServices(IServiceCollection services) + { + services.TryAddSingleton(); + services.AddSingleton(provider => provider.GetRequiredService()); + services.AddSingleton(provider => provider.GetRequiredService()); + + services.Add(GetLifetime()); + + if (AddCustomQueryableMethodTranslatingExpressionVisitorFactory) + AddWithCheck(services); + + if (AddCustomQuerySqlGeneratorFactory) + AddWithCheck(services); + + if (AddCustomRelationalParameterBasedSqlProcessorFactory) + AddWithCheck(services); + + if (AddBulkOperationSupport) + { + services.Add(GetLifetime()); + + services.AddSingleton>(); + services.AddSingleton>(); + services.TryAddScoped(); + services.TryAddScoped(provider => provider.GetRequiredService()); + services.AddTempTableSuffixComponents(); + + AddEntityDataReader(services); + services.TryAddScoped(); + services.TryAddScoped(provider => provider.GetRequiredService()); + services.TryAddScoped(provider => provider.GetRequiredService()); + services.TryAddScoped(provider => provider.GetRequiredService()); + services.TryAddScoped(provider => provider.GetRequiredService()); + services.TryAddScoped(provider => provider.GetRequiredService()); + } + + if (_addCollectionParameterSupport) + { + var jsonSerializerOptions = _collectionParameterJsonSerializerOptions ?? new JsonSerializerOptions(); + + services.AddSingleton(serviceProvider => ActivatorUtilities.CreateInstance(serviceProvider, jsonSerializerOptions)); + services.Add(GetLifetime()); + } + + if (UseThinktectureSqlServerMigrationsSqlGenerator) + AddWithCheck(services); + + if (_relationalOptions.AddSchemaRespectingComponents) + services.AddSingleton(); + + if (AddWindowFunctionsSupport) + { + var lifetime = GetLifetime(); + services.Add(ServiceDescriptor.Describe(typeof(IEvaluatableExpressionFilterPlugin), typeof(WindowFunctionEvaluatableExpressionFilterPlugin), lifetime)); + + } + } + + /// + /// Registers a custom service with internal dependency injection container of Entity Framework Core. + /// + /// Service type. + /// Implementation type. + /// Service lifetime. + /// or is null. + public void Register(Type serviceType, Type implementationType, ServiceLifetime lifetime) + { + _relationalOptions.Register(serviceType, implementationType, lifetime); + } + + /// + /// Registers a custom service instance with internal dependency injection container of Entity Framework Core. + /// + /// Service type. + /// Implementation instance. + /// or is null. + public void Register(Type serviceType, object implementationInstance) + { + _relationalOptions.Register(serviceType, implementationInstance); + } + + /// + /// Enables and disables support for queryable parameters. + /// + public SqlServerDbContextOptionsExtension AddCollectionParameterSupport( + bool addCollectionParameterSupport, + JsonSerializerOptions? jsonSerializerOptions, + bool configureCollectionParametersForPrimitiveTypes, + bool useDeferredSerialization) + { + _addCollectionParameterSupport = addCollectionParameterSupport; + _collectionParameterJsonSerializerOptions = jsonSerializerOptions; + ConfigureCollectionParametersForPrimitiveTypes = addCollectionParameterSupport && configureCollectionParametersForPrimitiveTypes; + UseDeferredCollectionParameterSerialization = addCollectionParameterSupport && useDeferredSerialization; + + return this; + } + + /// + public void Validate(IDbContextOptions options) + { + } + + private class SqlServerDbContextOptionsExtensionInfo : DbContextOptionsExtensionInfo + { + private readonly SqlServerDbContextOptionsExtension _extension; + public override bool IsDatabaseProvider => false; + + private string? _logFragment; + + public override string LogFragment => _logFragment ??= CreateLogFragment(); + + private string CreateLogFragment() + { + var sb = new StringBuilder(); + + if (_extension.AddBulkOperationSupport) + sb.Append("BulkOperationSupport "); + + if (_extension._addCollectionParameterSupport) + sb.Append("CollectionParameterSupport "); + + if (_extension.AddTableHintSupport) + sb.Append("TableHintSupport "); + + if (_extension.UseThinktectureSqlServerMigrationsSqlGenerator) + sb.Append("ThinktectureSqlServerMigrationsSqlGenerator "); + + return sb.ToString(); + } + + /// + public SqlServerDbContextOptionsExtensionInfo(SqlServerDbContextOptionsExtension extension) + : base(extension) + { + _extension = extension ?? throw new ArgumentNullException(nameof(extension)); + } + + /// + public override int GetServiceProviderHashCode() + { + var hashCode = new HashCode(); + + hashCode.Add(_extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory); + hashCode.Add(_extension.AddCustomQuerySqlGeneratorFactory); + hashCode.Add(_extension.AddCustomRelationalParameterBasedSqlProcessorFactory); + hashCode.Add(_extension.AddBulkOperationSupport); + hashCode.Add(_extension.ConfigureTempTablesForPrimitiveTypes); + hashCode.Add(_extension._addCollectionParameterSupport); + hashCode.Add(_extension.ConfigureCollectionParametersForPrimitiveTypes); + hashCode.Add(_extension.UseDeferredCollectionParameterSerialization); + hashCode.Add(_extension._collectionParameterJsonSerializerOptions); + hashCode.Add(_extension.AddTenantDatabaseSupport); + hashCode.Add(_extension.AddTableHintSupport); + hashCode.Add(_extension.UseThinktectureSqlServerMigrationsSqlGenerator); + + return hashCode.ToHashCode(); + } + + /// + public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) + { + return other is SqlServerDbContextOptionsExtensionInfo otherSqlServerInfo + && _extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory == otherSqlServerInfo._extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory + && _extension.AddCustomQuerySqlGeneratorFactory == otherSqlServerInfo._extension.AddCustomQuerySqlGeneratorFactory + && _extension.AddCustomRelationalParameterBasedSqlProcessorFactory == otherSqlServerInfo._extension.AddCustomRelationalParameterBasedSqlProcessorFactory + && _extension.AddBulkOperationSupport == otherSqlServerInfo._extension.AddBulkOperationSupport + && _extension.ConfigureTempTablesForPrimitiveTypes == otherSqlServerInfo._extension.ConfigureTempTablesForPrimitiveTypes + && _extension._addCollectionParameterSupport == otherSqlServerInfo._extension._addCollectionParameterSupport + && _extension.ConfigureCollectionParametersForPrimitiveTypes == otherSqlServerInfo._extension.ConfigureCollectionParametersForPrimitiveTypes + && _extension.UseDeferredCollectionParameterSerialization == otherSqlServerInfo._extension.UseDeferredCollectionParameterSerialization + && _extension._collectionParameterJsonSerializerOptions == otherSqlServerInfo._extension._collectionParameterJsonSerializerOptions + && _extension.AddTenantDatabaseSupport == otherSqlServerInfo._extension.AddTenantDatabaseSupport + && _extension.AddTableHintSupport == otherSqlServerInfo._extension.AddTableHintSupport + && _extension.UseThinktectureSqlServerMigrationsSqlGenerator == otherSqlServerInfo._extension.UseThinktectureSqlServerMigrationsSqlGenerator; + } + + /// + public override void PopulateDebugInfo(IDictionary debugInfo) + { + debugInfo["Thinktecture:CustomQueryableMethodTranslatingExpressionVisitorFactory"] = _extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:CustomQuerySqlGeneratorFactory"] = _extension.AddCustomQuerySqlGeneratorFactory.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:CustomRelationalParameterBasedSqlProcessorFactory"] = _extension.AddCustomRelationalParameterBasedSqlProcessorFactory.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:BulkOperationSupport"] = _extension.AddBulkOperationSupport.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:CollectionParameterSupport"] = _extension._addCollectionParameterSupport.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:TenantDatabaseSupport"] = _extension.AddTenantDatabaseSupport.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:TableHintSupport"] = _extension.AddTableHintSupport.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:UseThinktectureSqlServerMigrationsSqlGenerator"] = _extension.UseThinktectureSqlServerMigrationsSqlGenerator.ToString(CultureInfo.InvariantCulture); + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Migrations/ThinktectureSqlServerMigrationsSqlGenerator.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Migrations/ThinktectureSqlServerMigrationsSqlGenerator.cs index 1ad39351..b4c4535d 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Migrations/ThinktectureSqlServerMigrationsSqlGenerator.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Migrations/ThinktectureSqlServerMigrationsSqlGenerator.cs @@ -1,282 +1,282 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Migrations.Operations; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.Update; - -namespace Thinktecture.EntityFrameworkCore.Migrations; - -/// -/// Extended migration SQL generator. -/// -public class ThinktectureSqlServerMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator -{ - private bool _closeScopeBeforeEndingStatement; - - /// - /// Initializes . - /// - /// Dependencies. - /// The command batch preparer. - public ThinktectureSqlServerMigrationsSqlGenerator( - MigrationsSqlGeneratorDependencies dependencies, - ICommandBatchPreparer commandBatchPreparer) - : base(dependencies, commandBatchPreparer) - { - } - - /// - protected override void Generate(CreateTableOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate = true) - { - ArgumentNullException.ThrowIfNull(builder); - - if (operation.IfExistsCheckRequired()) - throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfExists)}()' is not allowed with '{operation.GetType().Name}'"); - - if (!operation.IfNotExistsCheckRequired()) - { - base.Generate(operation, model, builder, terminate); - return; - } - - builder.AppendLine($"IF(OBJECT_ID('{DelimitIdentifier(operation.Name, operation.Schema)}') IS NULL)") - .AppendLine("BEGIN"); - - var unterminatingBuilder = new UnterminatingMigrationCommandListBuilder(builder, Dependencies); - - using (builder.Indent()) - { - base.Generate(operation, model, unterminatingBuilder, false); - builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); - } - - builder.AppendLine("END"); - - if (terminate || unterminatingBuilder.SuppressTransaction.HasValue) - builder.EndCommand(unterminatingBuilder.SuppressTransaction ?? false); - } - - /// - protected override void Generate(DropTableOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate = true) - { - ArgumentNullException.ThrowIfNull(builder); - - if (operation.IfNotExistsCheckRequired()) - throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfNotExists)}()' is not allowed with '{operation.GetType().Name}'"); - - if (!operation.IfExistsCheckRequired()) - { - base.Generate(operation, model, builder, terminate); - return; - } - - builder.AppendLine($"IF(OBJECT_ID('{DelimitIdentifier(operation.Name, operation.Schema)}') IS NOT NULL)") - .AppendLine("BEGIN"); - - using (builder.Indent()) - { - base.Generate(operation, model, builder, false); - builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); - } - - builder.AppendLine("END"); - - if (terminate) - builder.EndCommand(); - } - - /// - protected override void Generate(AddColumnOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate) - { - ArgumentNullException.ThrowIfNull(builder); - - if (operation.IfExistsCheckRequired()) - throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfExists)}()' is not allowed with '{operation.GetType().Name}'"); - - if (!operation.IfNotExistsCheckRequired()) - { - base.Generate(operation, model, builder, terminate); - return; - } - - builder.AppendLine($"IF(COL_LENGTH('{DelimitIdentifier(operation.Table, operation.Schema)}', {GenerateSqlLiteral(operation.Name)}) IS NULL)") - .AppendLine("BEGIN"); - - using (builder.Indent()) - { - base.Generate(operation, model, builder, false); - builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); - } - - builder.AppendLine("END"); - - if (!terminate) - return; - - if (operation.Comment != null) - AddDescription(builder, operation.Comment, operation.Schema, operation.Table, operation.Name); - - builder.EndCommand(); - } - - /// - protected override void Generate(DropColumnOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate = true) - { - ArgumentNullException.ThrowIfNull(builder); - - if (operation.IfNotExistsCheckRequired()) - throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfNotExists)}()' is not allowed with '{operation.GetType().Name}'"); - - if (!operation.IfExistsCheckRequired()) - { - base.Generate(operation, model, builder, terminate); - return; - } - - builder.AppendLine($"IF(COL_LENGTH('{DelimitIdentifier(operation.Table, operation.Schema)}', {GenerateSqlLiteral(operation.Name)}) IS NOT NULL)") - .AppendLine("BEGIN"); - - using (builder.Indent()) - { - base.Generate(operation, model, builder, false); - builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); - } - - builder.AppendLine("END"); - - if (terminate) - builder.EndCommand(); - } - - /// - protected override void Generate(CreateIndexOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate = true) - { - ArgumentNullException.ThrowIfNull(builder); - - if (operation.IfExistsCheckRequired()) - throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfExists)}()' is not allowed with '{operation.GetType().Name}'"); - - if (!operation.IfNotExistsCheckRequired()) - { - base.Generate(operation, model, builder, terminate); - return; - } - - builder.AppendLine($"IF(IndexProperty(OBJECT_ID('{DelimitIdentifier(operation.Table, operation.Schema)}'), {GenerateSqlLiteral(operation.Name)}, 'IndexId') IS NULL)") - .AppendLine("BEGIN"); - - using (builder.Indent()) - { - base.Generate(operation, model, builder, false); - builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); - } - - builder.AppendLine("END"); - - if (terminate) - builder.EndCommand(); - } - - /// - protected override void Generate(DropIndexOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate) - { - ArgumentNullException.ThrowIfNull(builder); - - if (operation.IfNotExistsCheckRequired()) - throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfNotExists)}()' is not allowed with '{operation.GetType().Name}'"); - - if (!operation.IfExistsCheckRequired()) - { - base.Generate(operation, model, builder, terminate); - return; - } - - var table = operation.Table ?? throw new InvalidOperationException($"The {nameof(DropIndexOperation)} for index '{operation.Name}' has no table name."); - - builder.AppendLine($"IF(IndexProperty(OBJECT_ID('{DelimitIdentifier(table, operation.Schema)}'), {GenerateSqlLiteral(operation.Name)}, 'IndexId') IS NOT NULL)") - .AppendLine("BEGIN"); - - using (builder.Indent()) - { - base.Generate(operation, model, builder, false); - builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); - } - - builder.AppendLine("END"); - - if (terminate) - builder.EndCommand(); - } - - /// - protected override void Generate(AddUniqueConstraintOperation operation, IModel? model, MigrationCommandListBuilder builder) - { - ArgumentNullException.ThrowIfNull(builder); - - if (operation.IfExistsCheckRequired()) - throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfExists)}()' is not allowed with '{operation.GetType().Name}'"); - - if (!operation.IfNotExistsCheckRequired()) - { - base.Generate(operation, model, builder); - return; - } - - builder.AppendLine($"IF(OBJECT_ID('{DelimitIdentifier(operation.Name)}') IS NULL)") - .AppendLine("BEGIN"); - - _closeScopeBeforeEndingStatement = true; - builder.IncrementIndent(); - - base.Generate(operation, model, builder); - } - - /// - protected override void Generate(DropUniqueConstraintOperation operation, IModel? model, MigrationCommandListBuilder builder) - { - ArgumentNullException.ThrowIfNull(builder); - - if (operation.IfNotExistsCheckRequired()) - throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfNotExists)}()' is not allowed with '{operation.GetType().Name}'"); - - if (!operation.IfExistsCheckRequired()) - { - base.Generate(operation, model, builder); - return; - } - - builder.AppendLine($"IF(OBJECT_ID('{DelimitIdentifier(operation.Name)}') IS NOT NULL)") - .AppendLine("BEGIN"); - - _closeScopeBeforeEndingStatement = true; - builder.IncrementIndent(); - - base.Generate(operation, model, builder); - } - - /// - protected override void EndStatement(MigrationCommandListBuilder builder, bool suppressTransaction = false) - { - ArgumentNullException.ThrowIfNull(builder); - - if (_closeScopeBeforeEndingStatement) - { - _closeScopeBeforeEndingStatement = false; - - builder.DecrementIndent(); - builder.AppendLine("END"); - } - - base.EndStatement(builder, suppressTransaction); - } - - private string GenerateSqlLiteral(string text) - { - return Dependencies.TypeMappingSource.GetMapping(typeof(string)).GenerateSqlLiteral(text); - } - - private string DelimitIdentifier(string name, string? schema = null) - { - return Dependencies.SqlGenerationHelper.DelimitIdentifier(name, schema); - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Update; + +namespace Thinktecture.EntityFrameworkCore.Migrations; + +/// +/// Extended migration SQL generator. +/// +public class ThinktectureSqlServerMigrationsSqlGenerator : SqlServerMigrationsSqlGenerator +{ + private bool _closeScopeBeforeEndingStatement; + + /// + /// Initializes . + /// + /// Dependencies. + /// The command batch preparer. + public ThinktectureSqlServerMigrationsSqlGenerator( + MigrationsSqlGeneratorDependencies dependencies, + ICommandBatchPreparer commandBatchPreparer) + : base(dependencies, commandBatchPreparer) + { + } + + /// + protected override void Generate(CreateTableOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate = true) + { + ArgumentNullException.ThrowIfNull(builder); + + if (operation.IfExistsCheckRequired()) + throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfExists)}()' is not allowed with '{operation.GetType().Name}'"); + + if (!operation.IfNotExistsCheckRequired()) + { + base.Generate(operation, model, builder, terminate); + return; + } + + builder.AppendLine($"IF(OBJECT_ID('{DelimitIdentifier(operation.Name, operation.Schema)}') IS NULL)") + .AppendLine("BEGIN"); + + var unterminatingBuilder = new UnterminatingMigrationCommandListBuilder(builder, Dependencies); + + using (builder.Indent()) + { + base.Generate(operation, model, unterminatingBuilder, false); + builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); + } + + builder.AppendLine("END"); + + if (terminate || unterminatingBuilder.SuppressTransaction.HasValue) + builder.EndCommand(unterminatingBuilder.SuppressTransaction ?? false); + } + + /// + protected override void Generate(DropTableOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate = true) + { + ArgumentNullException.ThrowIfNull(builder); + + if (operation.IfNotExistsCheckRequired()) + throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfNotExists)}()' is not allowed with '{operation.GetType().Name}'"); + + if (!operation.IfExistsCheckRequired()) + { + base.Generate(operation, model, builder, terminate); + return; + } + + builder.AppendLine($"IF(OBJECT_ID('{DelimitIdentifier(operation.Name, operation.Schema)}') IS NOT NULL)") + .AppendLine("BEGIN"); + + using (builder.Indent()) + { + base.Generate(operation, model, builder, false); + builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); + } + + builder.AppendLine("END"); + + if (terminate) + builder.EndCommand(); + } + + /// + protected override void Generate(AddColumnOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate) + { + ArgumentNullException.ThrowIfNull(builder); + + if (operation.IfExistsCheckRequired()) + throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfExists)}()' is not allowed with '{operation.GetType().Name}'"); + + if (!operation.IfNotExistsCheckRequired()) + { + base.Generate(operation, model, builder, terminate); + return; + } + + builder.AppendLine($"IF(COL_LENGTH('{DelimitIdentifier(operation.Table, operation.Schema)}', {GenerateSqlLiteral(operation.Name)}) IS NULL)") + .AppendLine("BEGIN"); + + using (builder.Indent()) + { + base.Generate(operation, model, builder, false); + builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); + } + + builder.AppendLine("END"); + + if (!terminate) + return; + + if (operation.Comment != null) + AddDescription(builder, operation.Comment, operation.Schema, operation.Table, operation.Name); + + builder.EndCommand(); + } + + /// + protected override void Generate(DropColumnOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate = true) + { + ArgumentNullException.ThrowIfNull(builder); + + if (operation.IfNotExistsCheckRequired()) + throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfNotExists)}()' is not allowed with '{operation.GetType().Name}'"); + + if (!operation.IfExistsCheckRequired()) + { + base.Generate(operation, model, builder, terminate); + return; + } + + builder.AppendLine($"IF(COL_LENGTH('{DelimitIdentifier(operation.Table, operation.Schema)}', {GenerateSqlLiteral(operation.Name)}) IS NOT NULL)") + .AppendLine("BEGIN"); + + using (builder.Indent()) + { + base.Generate(operation, model, builder, false); + builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); + } + + builder.AppendLine("END"); + + if (terminate) + builder.EndCommand(); + } + + /// + protected override void Generate(CreateIndexOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate = true) + { + ArgumentNullException.ThrowIfNull(builder); + + if (operation.IfExistsCheckRequired()) + throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfExists)}()' is not allowed with '{operation.GetType().Name}'"); + + if (!operation.IfNotExistsCheckRequired()) + { + base.Generate(operation, model, builder, terminate); + return; + } + + builder.AppendLine($"IF(IndexProperty(OBJECT_ID('{DelimitIdentifier(operation.Table, operation.Schema)}'), {GenerateSqlLiteral(operation.Name)}, 'IndexId') IS NULL)") + .AppendLine("BEGIN"); + + using (builder.Indent()) + { + base.Generate(operation, model, builder, false); + builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); + } + + builder.AppendLine("END"); + + if (terminate) + builder.EndCommand(); + } + + /// + protected override void Generate(DropIndexOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate) + { + ArgumentNullException.ThrowIfNull(builder); + + if (operation.IfNotExistsCheckRequired()) + throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfNotExists)}()' is not allowed with '{operation.GetType().Name}'"); + + if (!operation.IfExistsCheckRequired()) + { + base.Generate(operation, model, builder, terminate); + return; + } + + var table = operation.Table ?? throw new InvalidOperationException($"The {nameof(DropIndexOperation)} for index '{operation.Name}' has no table name."); + + builder.AppendLine($"IF(IndexProperty(OBJECT_ID('{DelimitIdentifier(table, operation.Schema)}'), {GenerateSqlLiteral(operation.Name)}, 'IndexId') IS NOT NULL)") + .AppendLine("BEGIN"); + + using (builder.Indent()) + { + base.Generate(operation, model, builder, false); + builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator); + } + + builder.AppendLine("END"); + + if (terminate) + builder.EndCommand(); + } + + /// + protected override void Generate(AddUniqueConstraintOperation operation, IModel? model, MigrationCommandListBuilder builder) + { + ArgumentNullException.ThrowIfNull(builder); + + if (operation.IfExistsCheckRequired()) + throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfExists)}()' is not allowed with '{operation.GetType().Name}'"); + + if (!operation.IfNotExistsCheckRequired()) + { + base.Generate(operation, model, builder); + return; + } + + builder.AppendLine($"IF(OBJECT_ID('{DelimitIdentifier(operation.Name)}') IS NULL)") + .AppendLine("BEGIN"); + + _closeScopeBeforeEndingStatement = true; + builder.IncrementIndent(); + + base.Generate(operation, model, builder); + } + + /// + protected override void Generate(DropUniqueConstraintOperation operation, IModel? model, MigrationCommandListBuilder builder) + { + ArgumentNullException.ThrowIfNull(builder); + + if (operation.IfNotExistsCheckRequired()) + throw new InvalidOperationException($"The check '{nameof(SqlServerOperationBuilderExtensions.IfNotExists)}()' is not allowed with '{operation.GetType().Name}'"); + + if (!operation.IfExistsCheckRequired()) + { + base.Generate(operation, model, builder); + return; + } + + builder.AppendLine($"IF(OBJECT_ID('{DelimitIdentifier(operation.Name)}') IS NOT NULL)") + .AppendLine("BEGIN"); + + _closeScopeBeforeEndingStatement = true; + builder.IncrementIndent(); + + base.Generate(operation, model, builder); + } + + /// + protected override void EndStatement(MigrationCommandListBuilder builder, bool suppressTransaction = false) + { + ArgumentNullException.ThrowIfNull(builder); + + if (_closeScopeBeforeEndingStatement) + { + _closeScopeBeforeEndingStatement = false; + + builder.DecrementIndent(); + builder.AppendLine("END"); + } + + base.EndStatement(builder, suppressTransaction); + } + + private string GenerateSqlLiteral(string text) + { + return Dependencies.TypeMappingSource.GetMapping(typeof(string)).GenerateSqlLiteral(text); + } + + private string DelimitIdentifier(string name, string? schema = null) + { + return Dependencies.SqlGenerationHelper.DelimitIdentifier(name, schema); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Migrations/UnterminatingMigrationCommandListBuilder.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Migrations/UnterminatingMigrationCommandListBuilder.cs index 47405449..efd1f51a 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Migrations/UnterminatingMigrationCommandListBuilder.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Migrations/UnterminatingMigrationCommandListBuilder.cs @@ -1,76 +1,76 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.EntityFrameworkCore.Migrations; - -/// -/// A hack preventing premature end of command. -/// -internal class UnterminatingMigrationCommandListBuilder : MigrationCommandListBuilder -{ - private readonly MigrationCommandListBuilder _builder; - - public bool? SuppressTransaction { get; private set; } - - public UnterminatingMigrationCommandListBuilder( - MigrationCommandListBuilder builder, - MigrationsSqlGeneratorDependencies dependencies) - : base(dependencies) - { - _builder = builder ?? throw new ArgumentNullException(nameof(builder)); - } - - public override MigrationCommandListBuilder EndCommand(bool suppressTransaction = false) - { - if (SuppressTransaction.HasValue) - throw new NotSupportedException($"Multiple calls to '{nameof(EndCommand)}' detected. The methods '{nameof(SqlServerOperationBuilderExtensions.IfExists)}()'/'{nameof(SqlServerOperationBuilderExtensions.IfNotExists)}()' cannot be used with current migration operation."); - - SuppressTransaction = suppressTransaction; - return this; - } - - public override MigrationCommandListBuilder Append(string o) - { - _builder.Append(o); - return this; - } - - public override IDisposable Indent() - { - return _builder.Indent(); - } - - public override MigrationCommandListBuilder AppendLine() - { - _builder.AppendLine(); - return this; - } - - public override MigrationCommandListBuilder AppendLine(string o) - { - _builder.AppendLine(o); - return this; - } - - public override MigrationCommandListBuilder AppendLines(string o) - { - _builder.AppendLines(o); - return this; - } - - public override MigrationCommandListBuilder DecrementIndent() - { - _builder.DecrementIndent(); - return this; - } - - public override MigrationCommandListBuilder IncrementIndent() - { - _builder.IncrementIndent(); - return this; - } - - public override IReadOnlyList GetCommandList() - { - return _builder.GetCommandList(); - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.EntityFrameworkCore.Migrations; + +/// +/// A hack preventing premature end of command. +/// +internal class UnterminatingMigrationCommandListBuilder : MigrationCommandListBuilder +{ + private readonly MigrationCommandListBuilder _builder; + + public bool? SuppressTransaction { get; private set; } + + public UnterminatingMigrationCommandListBuilder( + MigrationCommandListBuilder builder, + MigrationsSqlGeneratorDependencies dependencies) + : base(dependencies) + { + _builder = builder ?? throw new ArgumentNullException(nameof(builder)); + } + + public override MigrationCommandListBuilder EndCommand(bool suppressTransaction = false) + { + if (SuppressTransaction.HasValue) + throw new NotSupportedException($"Multiple calls to '{nameof(EndCommand)}' detected. The methods '{nameof(SqlServerOperationBuilderExtensions.IfExists)}()'/'{nameof(SqlServerOperationBuilderExtensions.IfNotExists)}()' cannot be used with current migration operation."); + + SuppressTransaction = suppressTransaction; + return this; + } + + public override MigrationCommandListBuilder Append(string o) + { + _builder.Append(o); + return this; + } + + public override IDisposable Indent() + { + return _builder.Indent(); + } + + public override MigrationCommandListBuilder AppendLine() + { + _builder.AppendLine(); + return this; + } + + public override MigrationCommandListBuilder AppendLine(string o) + { + _builder.AppendLine(o); + return this; + } + + public override MigrationCommandListBuilder AppendLines(string o) + { + _builder.AppendLines(o); + return this; + } + + public override MigrationCommandListBuilder DecrementIndent() + { + _builder.DecrementIndent(); + return this; + } + + public override MigrationCommandListBuilder IncrementIndent() + { + _builder.IncrementIndent(); + return this; + } + + public override IReadOnlyList GetCommandList() + { + return _builder.GetCommandList(); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerParameterBasedSqlProcessor.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerParameterBasedSqlProcessor.cs index a3f56a21..547e2d6c 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerParameterBasedSqlProcessor.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerParameterBasedSqlProcessor.cs @@ -1,28 +1,28 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; - -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -[SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] -public class ThinktectureSqlServerParameterBasedSqlProcessor : SqlServerParameterBasedSqlProcessor -{ - /// - public ThinktectureSqlServerParameterBasedSqlProcessor( - RelationalParameterBasedSqlProcessorDependencies dependencies, - RelationalParameterBasedSqlProcessorParameters parameters) - : base(dependencies, parameters) - { - } - - /// - protected override Expression ProcessSqlNullability(Expression selectExpression, IReadOnlyDictionary parametersValues, out bool canCache) - { - ArgumentNullException.ThrowIfNull(selectExpression); - ArgumentNullException.ThrowIfNull(parametersValues); - - return new ThinktectureSqlServerSqlNullabilityProcessor(Dependencies, Parameters).Process(selectExpression, parametersValues, out canCache); - } -} +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; + +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +[SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] +public class ThinktectureSqlServerParameterBasedSqlProcessor : SqlServerParameterBasedSqlProcessor +{ + /// + public ThinktectureSqlServerParameterBasedSqlProcessor( + RelationalParameterBasedSqlProcessorDependencies dependencies, + RelationalParameterBasedSqlProcessorParameters parameters) + : base(dependencies, parameters) + { + } + + /// + protected override Expression ProcessSqlNullability(Expression selectExpression, IReadOnlyDictionary parametersValues, out bool canCache) + { + ArgumentNullException.ThrowIfNull(selectExpression); + ArgumentNullException.ThrowIfNull(parametersValues); + + return new ThinktectureSqlServerSqlNullabilityProcessor(Dependencies, Parameters).Process(selectExpression, parametersValues, out canCache); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerParameterBasedSqlProcessorFactory.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerParameterBasedSqlProcessorFactory.cs index e9ca5de5..873693a6 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerParameterBasedSqlProcessorFactory.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerParameterBasedSqlProcessorFactory.cs @@ -1,25 +1,25 @@ -using Microsoft.EntityFrameworkCore.Query; - -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -public class ThinktectureSqlServerParameterBasedSqlProcessorFactory : IRelationalParameterBasedSqlProcessorFactory -{ - private readonly RelationalParameterBasedSqlProcessorDependencies _dependencies; - - /// - /// Initializes . - /// - /// Dependencies. - public ThinktectureSqlServerParameterBasedSqlProcessorFactory( - RelationalParameterBasedSqlProcessorDependencies dependencies) - { - _dependencies = dependencies; - } - - /// - public RelationalParameterBasedSqlProcessor Create(RelationalParameterBasedSqlProcessorParameters parameters) - { - return new ThinktectureSqlServerParameterBasedSqlProcessor(_dependencies, parameters); - } -} +using Microsoft.EntityFrameworkCore.Query; + +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +public class ThinktectureSqlServerParameterBasedSqlProcessorFactory : IRelationalParameterBasedSqlProcessorFactory +{ + private readonly RelationalParameterBasedSqlProcessorDependencies _dependencies; + + /// + /// Initializes . + /// + /// Dependencies. + public ThinktectureSqlServerParameterBasedSqlProcessorFactory( + RelationalParameterBasedSqlProcessorDependencies dependencies) + { + _dependencies = dependencies; + } + + /// + public RelationalParameterBasedSqlProcessor Create(RelationalParameterBasedSqlProcessorParameters parameters) + { + return new ThinktectureSqlServerParameterBasedSqlProcessor(_dependencies, parameters); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQuerySqlGenerator.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQuerySqlGenerator.cs index 42f3d9bf..c5bada2a 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQuerySqlGenerator.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQuerySqlGenerator.cs @@ -1,146 +1,146 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; -using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; -using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; -using Microsoft.EntityFrameworkCore.Storage; -using Thinktecture.EntityFrameworkCore.Internal; -using Thinktecture.EntityFrameworkCore.Query.SqlExpressions; - -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -[SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] -public class ThinktectureSqlServerQuerySqlGenerator : SqlServerQuerySqlGenerator -{ - private readonly ITenantDatabaseProvider _databaseProvider; - - /// - public ThinktectureSqlServerQuerySqlGenerator( - QuerySqlGeneratorDependencies dependencies, - IRelationalTypeMappingSource typeMappingSource, - ISqlServerSingletonOptions sqlServerSingletonOptions, - ITenantDatabaseProvider databaseProvider) - : base(dependencies, typeMappingSource, sqlServerSingletonOptions) - { - _databaseProvider = databaseProvider ?? throw new ArgumentNullException(nameof(databaseProvider)); - } - - /// - protected override Expression VisitExtension(Expression extensionExpression) - { - switch (extensionExpression) - { - case WindowFunctionExpression windowFunctionExpression: - return VisitWindowFunction(windowFunctionExpression); - default: - return base.VisitExtension(extensionExpression); - } - } - - private Expression VisitWindowFunction(WindowFunctionExpression windowFunctionExpression) - { - Sql.Append(windowFunctionExpression.Name).Append(" ("); - - for (var i = 0; i < windowFunctionExpression.Arguments.Count; i++) - { - if (i != 0) - Sql.Append(", "); - - var argument = windowFunctionExpression.Arguments[i]; - Visit(argument); - } - - if (windowFunctionExpression.Arguments.Count == 0 && windowFunctionExpression.UseAsteriskWhenNoArguments) - Sql.Append("*"); - - Sql.Append(") OVER ("); - - if (windowFunctionExpression.Partitions.Count != 0) - { - Sql.Append("PARTITION BY "); - - for (var i = 0; i < windowFunctionExpression.Partitions.Count; i++) - { - if (i != 0) - Sql.Append(", "); - - var partition = windowFunctionExpression.Partitions[i]; - Visit(partition); - } - } - - if (windowFunctionExpression.Orderings.Count != 0) - { - Sql.Append(" ORDER BY "); - - for (var i = 0; i < windowFunctionExpression.Orderings.Count; i++) - { - if (i != 0) - Sql.Append(", "); - - var ordering = windowFunctionExpression.Orderings[i]; - VisitOrdering(ordering); - } - } - - Sql.Append(")"); - - return windowFunctionExpression; - } - - /// - protected override Expression VisitTable(TableExpression tableExpression) - { - ArgumentNullException.ThrowIfNull(tableExpression); - - var tableHints = tableExpression.FindAnnotation(ThinktectureRelationalAnnotationNames.TABLE_HINTS)?.Value as IReadOnlyList; - - var visitedExpression = VisitTableOrTempTable(tableExpression); - - if (tableHints?.Count > 0) - { - Sql.Append(" ").Append("WITH ("); - - for (var i = 0; i < tableHints.Count; i++) - { - if (i != 0) - Sql.Append(", "); - - Sql.Append(tableHints[i].ToString(Dependencies.SqlGenerationHelper)); - } - - Sql.Append(")"); - } - - return visitedExpression; - } - - private Expression VisitTableOrTempTable(TableExpression tableExpression) - { - var tempTable = tableExpression.FindAnnotation(ThinktectureBulkOperationsAnnotationNames.TEMP_TABLE); - - if (tempTable is not null) - { - var tempTableName = (string?)tempTable.Value ?? throw new Exception("Temp table name cannot be null."); - Sql.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(tempTableName)) - .Append(AliasSeparator) - .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(tableExpression.Alias)); - - return tableExpression; - } - else - { - var databaseName = _databaseProvider.GetDatabaseName(tableExpression.Schema, tableExpression.Name); - - if (!String.IsNullOrWhiteSpace(databaseName)) - { - Sql.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(databaseName)) - .Append(String.IsNullOrWhiteSpace(tableExpression.Schema) ? ".." : "."); - } - - return base.VisitTable(tableExpression); - } - } -} +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; +using Microsoft.EntityFrameworkCore.Storage; +using Thinktecture.EntityFrameworkCore.Internal; +using Thinktecture.EntityFrameworkCore.Query.SqlExpressions; + +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +[SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] +public class ThinktectureSqlServerQuerySqlGenerator : SqlServerQuerySqlGenerator +{ + private readonly ITenantDatabaseProvider _databaseProvider; + + /// + public ThinktectureSqlServerQuerySqlGenerator( + QuerySqlGeneratorDependencies dependencies, + IRelationalTypeMappingSource typeMappingSource, + ISqlServerSingletonOptions sqlServerSingletonOptions, + ITenantDatabaseProvider databaseProvider) + : base(dependencies, typeMappingSource, sqlServerSingletonOptions) + { + _databaseProvider = databaseProvider ?? throw new ArgumentNullException(nameof(databaseProvider)); + } + + /// + protected override Expression VisitExtension(Expression extensionExpression) + { + switch (extensionExpression) + { + case WindowFunctionExpression windowFunctionExpression: + return VisitWindowFunction(windowFunctionExpression); + default: + return base.VisitExtension(extensionExpression); + } + } + + private Expression VisitWindowFunction(WindowFunctionExpression windowFunctionExpression) + { + Sql.Append(windowFunctionExpression.Name).Append(" ("); + + for (var i = 0; i < windowFunctionExpression.Arguments.Count; i++) + { + if (i != 0) + Sql.Append(", "); + + var argument = windowFunctionExpression.Arguments[i]; + Visit(argument); + } + + if (windowFunctionExpression.Arguments.Count == 0 && windowFunctionExpression.UseAsteriskWhenNoArguments) + Sql.Append("*"); + + Sql.Append(") OVER ("); + + if (windowFunctionExpression.Partitions.Count != 0) + { + Sql.Append("PARTITION BY "); + + for (var i = 0; i < windowFunctionExpression.Partitions.Count; i++) + { + if (i != 0) + Sql.Append(", "); + + var partition = windowFunctionExpression.Partitions[i]; + Visit(partition); + } + } + + if (windowFunctionExpression.Orderings.Count != 0) + { + Sql.Append(" ORDER BY "); + + for (var i = 0; i < windowFunctionExpression.Orderings.Count; i++) + { + if (i != 0) + Sql.Append(", "); + + var ordering = windowFunctionExpression.Orderings[i]; + VisitOrdering(ordering); + } + } + + Sql.Append(")"); + + return windowFunctionExpression; + } + + /// + protected override Expression VisitTable(TableExpression tableExpression) + { + ArgumentNullException.ThrowIfNull(tableExpression); + + var tableHints = tableExpression.FindAnnotation(ThinktectureRelationalAnnotationNames.TABLE_HINTS)?.Value as IReadOnlyList; + + var visitedExpression = VisitTableOrTempTable(tableExpression); + + if (tableHints?.Count > 0) + { + Sql.Append(" ").Append("WITH ("); + + for (var i = 0; i < tableHints.Count; i++) + { + if (i != 0) + Sql.Append(", "); + + Sql.Append(tableHints[i].ToString(Dependencies.SqlGenerationHelper)); + } + + Sql.Append(")"); + } + + return visitedExpression; + } + + private Expression VisitTableOrTempTable(TableExpression tableExpression) + { + var tempTable = tableExpression.FindAnnotation(ThinktectureBulkOperationsAnnotationNames.TEMP_TABLE); + + if (tempTable is not null) + { + var tempTableName = (string?)tempTable.Value ?? throw new Exception("Temp table name cannot be null."); + Sql.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(tempTableName)) + .Append(AliasSeparator) + .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(tableExpression.Alias)); + + return tableExpression; + } + else + { + var databaseName = _databaseProvider.GetDatabaseName(tableExpression.Schema, tableExpression.Name); + + if (!String.IsNullOrWhiteSpace(databaseName)) + { + Sql.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(databaseName)) + .Append(String.IsNullOrWhiteSpace(tableExpression.Schema) ? ".." : "."); + } + + return base.VisitTable(tableExpression); + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQuerySqlGeneratorFactory.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQuerySqlGeneratorFactory.cs index 9ad8a173..e6335741 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQuerySqlGeneratorFactory.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQuerySqlGeneratorFactory.cs @@ -1,41 +1,41 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; -using Microsoft.EntityFrameworkCore.Storage; - -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] -public class ThinktectureSqlServerQuerySqlGeneratorFactory : IQuerySqlGeneratorFactory -{ - private readonly QuerySqlGeneratorDependencies _dependencies; - private readonly IRelationalTypeMappingSource _typeMappingSource; - private readonly ISqlServerSingletonOptions _sqlServerSingletonOptions; - private readonly ITenantDatabaseProviderFactory _databaseProviderFactory; - - /// - /// Initializes new instance of . - /// - /// Dependencies. - /// Type mapping source. - /// Options. - /// Factory. - public ThinktectureSqlServerQuerySqlGeneratorFactory( - QuerySqlGeneratorDependencies dependencies, - IRelationalTypeMappingSource typeMappingSource, - ISqlServerSingletonOptions sqlServerSingletonOptions, - ITenantDatabaseProviderFactory databaseProviderFactory) - { - _dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies)); - _typeMappingSource = typeMappingSource ?? throw new ArgumentNullException(nameof(typeMappingSource)); - _sqlServerSingletonOptions = sqlServerSingletonOptions; - _databaseProviderFactory = databaseProviderFactory ?? throw new ArgumentNullException(nameof(databaseProviderFactory)); - } - - /// - public QuerySqlGenerator Create() - { - return new ThinktectureSqlServerQuerySqlGenerator(_dependencies, _typeMappingSource, _sqlServerSingletonOptions, _databaseProviderFactory.Create()); - } -} +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] +public class ThinktectureSqlServerQuerySqlGeneratorFactory : IQuerySqlGeneratorFactory +{ + private readonly QuerySqlGeneratorDependencies _dependencies; + private readonly IRelationalTypeMappingSource _typeMappingSource; + private readonly ISqlServerSingletonOptions _sqlServerSingletonOptions; + private readonly ITenantDatabaseProviderFactory _databaseProviderFactory; + + /// + /// Initializes new instance of . + /// + /// Dependencies. + /// Type mapping source. + /// Options. + /// Factory. + public ThinktectureSqlServerQuerySqlGeneratorFactory( + QuerySqlGeneratorDependencies dependencies, + IRelationalTypeMappingSource typeMappingSource, + ISqlServerSingletonOptions sqlServerSingletonOptions, + ITenantDatabaseProviderFactory databaseProviderFactory) + { + _dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies)); + _typeMappingSource = typeMappingSource ?? throw new ArgumentNullException(nameof(typeMappingSource)); + _sqlServerSingletonOptions = sqlServerSingletonOptions; + _databaseProviderFactory = databaseProviderFactory ?? throw new ArgumentNullException(nameof(databaseProviderFactory)); + } + + /// + public QuerySqlGenerator Create() + { + return new ThinktectureSqlServerQuerySqlGenerator(_dependencies, _typeMappingSource, _sqlServerSingletonOptions, _databaseProviderFactory.Create()); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor.cs index f62e6276..b1d02fcd 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor.cs @@ -1,46 +1,46 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; -using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; - -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -/// Extends the capabilities of . -/// -[SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] -public class ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor - : SqlServerQueryableMethodTranslatingExpressionVisitor -{ - /// - public ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor( - QueryableMethodTranslatingExpressionVisitorDependencies dependencies, - RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies, - SqlServerQueryCompilationContext queryCompilationContext, - ISqlServerSingletonOptions sqlServerSingletonOptions) - : base(dependencies, relationalDependencies, queryCompilationContext, sqlServerSingletonOptions) - { - } - - /// - protected ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor( - ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor parentVisitor) - : base(parentVisitor) - { - } - - /// - protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVisitor() - { - return new ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor(this); - } - - /// - protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) - { - return this.TranslateRelationalMethods(methodCallExpression) ?? - this.TranslateBulkMethods(methodCallExpression) ?? - base.VisitMethodCall(methodCallExpression); - } -} +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; + +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +/// Extends the capabilities of . +/// +[SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] +public class ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor + : SqlServerQueryableMethodTranslatingExpressionVisitor +{ + /// + public ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor( + QueryableMethodTranslatingExpressionVisitorDependencies dependencies, + RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies, + SqlServerQueryCompilationContext queryCompilationContext, + ISqlServerSingletonOptions sqlServerSingletonOptions) + : base(dependencies, relationalDependencies, queryCompilationContext, sqlServerSingletonOptions) + { + } + + /// + protected ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor( + ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor parentVisitor) + : base(parentVisitor) + { + } + + /// + protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVisitor() + { + return new ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor(this); + } + + /// + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) + { + return this.TranslateRelationalMethods(methodCallExpression) ?? + this.TranslateBulkMethods(methodCallExpression) ?? + base.VisitMethodCall(methodCallExpression); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitorFactory.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitorFactory.cs index 88bc89c9..85ed9f75 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitorFactory.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/Query/ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitorFactory.cs @@ -1,40 +1,40 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; -using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; - -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -/// Factory for creation of the . -/// -[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] -public sealed class ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitorFactory - : IQueryableMethodTranslatingExpressionVisitorFactory -{ - private readonly QueryableMethodTranslatingExpressionVisitorDependencies _dependencies; - private readonly RelationalQueryableMethodTranslatingExpressionVisitorDependencies _relationalDependencies; - private readonly ISqlServerSingletonOptions _sqlServerSingletonOptions; - - /// - /// Initializes new instance of . - /// - /// Dependencies. - /// Relational dependencies. - /// Options. - public ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitorFactory( - QueryableMethodTranslatingExpressionVisitorDependencies dependencies, - RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies, - ISqlServerSingletonOptions sqlServerSingletonOptions) - { - _dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies)); - _relationalDependencies = relationalDependencies ?? throw new ArgumentNullException(nameof(relationalDependencies)); - _sqlServerSingletonOptions = sqlServerSingletonOptions; - } - - /// - public QueryableMethodTranslatingExpressionVisitor Create(QueryCompilationContext queryCompilationContext) - { - return new ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor(_dependencies, _relationalDependencies, (SqlServerQueryCompilationContext)queryCompilationContext, _sqlServerSingletonOptions); - } -} +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; + +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +/// Factory for creation of the . +/// +[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] +public sealed class ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitorFactory + : IQueryableMethodTranslatingExpressionVisitorFactory +{ + private readonly QueryableMethodTranslatingExpressionVisitorDependencies _dependencies; + private readonly RelationalQueryableMethodTranslatingExpressionVisitorDependencies _relationalDependencies; + private readonly ISqlServerSingletonOptions _sqlServerSingletonOptions; + + /// + /// Initializes new instance of . + /// + /// Dependencies. + /// Relational dependencies. + /// Options. + public ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitorFactory( + QueryableMethodTranslatingExpressionVisitorDependencies dependencies, + RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies, + ISqlServerSingletonOptions sqlServerSingletonOptions) + { + _dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies)); + _relationalDependencies = relationalDependencies ?? throw new ArgumentNullException(nameof(relationalDependencies)); + _sqlServerSingletonOptions = sqlServerSingletonOptions; + } + + /// + public QueryableMethodTranslatingExpressionVisitor Create(QueryCompilationContext queryCompilationContext) + { + return new ThinktectureSqlServerQueryableMethodTranslatingExpressionVisitor(_dependencies, _relationalDependencies, (SqlServerQueryCompilationContext)queryCompilationContext, _sqlServerSingletonOptions); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/SqlServerDbLoggerCategory.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/SqlServerDbLoggerCategory.cs index e02069f8..a5e0c55f 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/SqlServerDbLoggerCategory.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/SqlServerDbLoggerCategory.cs @@ -1,16 +1,16 @@ -using Microsoft.EntityFrameworkCore.Diagnostics; - -namespace Thinktecture.EntityFrameworkCore; - -/// -/// Logger category. -/// -public static class SqlServerDbLoggerCategory -{ - /// - /// Logger category for bulk operations. - /// - public class BulkOperation : LoggerCategory - { - } -} +using Microsoft.EntityFrameworkCore.Diagnostics; + +namespace Thinktecture.EntityFrameworkCore; + +/// +/// Logger category. +/// +public static class SqlServerDbLoggerCategory +{ + /// + /// Logger category for bulk operations. + /// + public class BulkOperation : LoggerCategory + { + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/SqlServerTableHint.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/SqlServerTableHint.cs index 3c930cc8..9a9f122b 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/SqlServerTableHint.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/SqlServerTableHint.cs @@ -1,197 +1,197 @@ -using Microsoft.EntityFrameworkCore.Storage; - -namespace Thinktecture.EntityFrameworkCore; - -/// -/// SQL Server table hints. -/// -public class SqlServerTableHint : ITableHint, IEquatable -{ - /// - /// NOEXPAND - /// - public static readonly SqlServerTableHint NoExpand = new("NOEXPAND"); - - /// - /// FORCESCAN - /// - public static readonly SqlServerTableHint ForceScan = new("FORCESCAN"); - - /// - /// FORCESEEK - /// - public static readonly SqlServerTableHint ForceSeek = new("FORCESEEK"); - - /// - /// HOLDLOCK - /// - public static readonly SqlServerTableHint HoldLock = new("HOLDLOCK"); - - /// - /// NOLOCK - /// - public static readonly SqlServerTableHint NoLock = new("NOLOCK"); - - /// - /// NOWAIT - /// - public static readonly SqlServerTableHint NoWait = new("NOWAIT"); - - /// - /// PAGLOCK - /// - public static readonly SqlServerTableHint PagLock = new("PAGLOCK"); - - /// - /// READCOMMITTED - /// - public static readonly SqlServerTableHint ReadCommitted = new("READCOMMITTED"); - - /// - /// READCOMMITTEDLOCK - /// - public static readonly SqlServerTableHint ReadCommittedLock = new("READCOMMITTEDLOCK"); - - /// - /// READPAST - /// - public static readonly SqlServerTableHint ReadPast = new("READPAST"); - - /// - /// READUNCOMMITTED - /// - public static readonly SqlServerTableHint ReadUncommitted = new("READUNCOMMITTED"); - - /// - /// REPEATABLEREAD - /// - public static readonly SqlServerTableHint RepeatableRead = new("REPEATABLEREAD"); - - /// - /// ROWLOCK - /// - public static readonly SqlServerTableHint RowLock = new("ROWLOCK"); - - /// - /// SERIALIZABLE - /// - public static readonly SqlServerTableHint Serializable = new("SERIALIZABLE"); - - /// - /// SNAPSHOT - /// - public static readonly SqlServerTableHint Snapshot = new("SNAPSHOT"); - - /// - /// TABLOCK - /// - public static readonly SqlServerTableHint TabLock = new("TABLOCK"); - - /// - /// TABLOCKX - /// - public static readonly SqlServerTableHint TabLockx = new("TABLOCKX"); - - /// - /// UPDLOCK - /// - public static readonly SqlServerTableHint UpdLock = new("UPDLOCK"); - - /// - /// XLOCK - /// - public static readonly SqlServerTableHint XLock = new("XLOCK"); - - /// - /// SPATIAL_WINDOW_MAX_CELLS - /// - public static SqlServerTableHint Spatial_Window_Max_Cells(int value) - { - return new($"SPATIAL_WINDOW_MAX_CELLS = {value}"); - } - - /// - /// INDEX(name) - /// - public static SqlServerTableHint Index(string name) - { - return new IndexTableHint(name); - } - - private readonly string _value; - - private SqlServerTableHint(string value) - { - _value = value ?? throw new ArgumentNullException(nameof(value)); - } - - /// - public override bool Equals(object? obj) - { - return ReferenceEquals(this, obj) || (obj is SqlServerTableHint other && Equals(other)); - } - - /// - public bool Equals(SqlServerTableHint? other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - - return _value == other._value; - } - - /// - public override int GetHashCode() - { - return _value.GetHashCode(); - } - - /// - public override string ToString() - { - return _value; - } - - /// - public virtual string ToString(ISqlGenerationHelper sqlGenerationHelper) - { - return _value; - } - - private sealed class IndexTableHint : SqlServerTableHint, IEquatable - { - private readonly string _name; - - public IndexTableHint(string name) - : base($"INDEX({name})") - { - _name = name; - } - - public override bool Equals(object? obj) - { - return ReferenceEquals(this, obj) || (obj is IndexTableHint other && Equals(other)); - } - - public bool Equals(IndexTableHint? other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - return base.Equals(other) && _name == other._name; - } - - public override int GetHashCode() - { - return HashCode.Combine(base.GetHashCode(), _name); - } - - public override string ToString(ISqlGenerationHelper sqlGenerationHelper) - { - return $"INDEX({sqlGenerationHelper.DelimitIdentifier(_name)})"; - } - } -} +using Microsoft.EntityFrameworkCore.Storage; + +namespace Thinktecture.EntityFrameworkCore; + +/// +/// SQL Server table hints. +/// +public class SqlServerTableHint : ITableHint, IEquatable +{ + /// + /// NOEXPAND + /// + public static readonly SqlServerTableHint NoExpand = new("NOEXPAND"); + + /// + /// FORCESCAN + /// + public static readonly SqlServerTableHint ForceScan = new("FORCESCAN"); + + /// + /// FORCESEEK + /// + public static readonly SqlServerTableHint ForceSeek = new("FORCESEEK"); + + /// + /// HOLDLOCK + /// + public static readonly SqlServerTableHint HoldLock = new("HOLDLOCK"); + + /// + /// NOLOCK + /// + public static readonly SqlServerTableHint NoLock = new("NOLOCK"); + + /// + /// NOWAIT + /// + public static readonly SqlServerTableHint NoWait = new("NOWAIT"); + + /// + /// PAGLOCK + /// + public static readonly SqlServerTableHint PagLock = new("PAGLOCK"); + + /// + /// READCOMMITTED + /// + public static readonly SqlServerTableHint ReadCommitted = new("READCOMMITTED"); + + /// + /// READCOMMITTEDLOCK + /// + public static readonly SqlServerTableHint ReadCommittedLock = new("READCOMMITTEDLOCK"); + + /// + /// READPAST + /// + public static readonly SqlServerTableHint ReadPast = new("READPAST"); + + /// + /// READUNCOMMITTED + /// + public static readonly SqlServerTableHint ReadUncommitted = new("READUNCOMMITTED"); + + /// + /// REPEATABLEREAD + /// + public static readonly SqlServerTableHint RepeatableRead = new("REPEATABLEREAD"); + + /// + /// ROWLOCK + /// + public static readonly SqlServerTableHint RowLock = new("ROWLOCK"); + + /// + /// SERIALIZABLE + /// + public static readonly SqlServerTableHint Serializable = new("SERIALIZABLE"); + + /// + /// SNAPSHOT + /// + public static readonly SqlServerTableHint Snapshot = new("SNAPSHOT"); + + /// + /// TABLOCK + /// + public static readonly SqlServerTableHint TabLock = new("TABLOCK"); + + /// + /// TABLOCKX + /// + public static readonly SqlServerTableHint TabLockx = new("TABLOCKX"); + + /// + /// UPDLOCK + /// + public static readonly SqlServerTableHint UpdLock = new("UPDLOCK"); + + /// + /// XLOCK + /// + public static readonly SqlServerTableHint XLock = new("XLOCK"); + + /// + /// SPATIAL_WINDOW_MAX_CELLS + /// + public static SqlServerTableHint Spatial_Window_Max_Cells(int value) + { + return new($"SPATIAL_WINDOW_MAX_CELLS = {value}"); + } + + /// + /// INDEX(name) + /// + public static SqlServerTableHint Index(string name) + { + return new IndexTableHint(name); + } + + private readonly string _value; + + private SqlServerTableHint(string value) + { + _value = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + public override bool Equals(object? obj) + { + return ReferenceEquals(this, obj) || (obj is SqlServerTableHint other && Equals(other)); + } + + /// + public bool Equals(SqlServerTableHint? other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return _value == other._value; + } + + /// + public override int GetHashCode() + { + return _value.GetHashCode(); + } + + /// + public override string ToString() + { + return _value; + } + + /// + public virtual string ToString(ISqlGenerationHelper sqlGenerationHelper) + { + return _value; + } + + private sealed class IndexTableHint : SqlServerTableHint, IEquatable + { + private readonly string _name; + + public IndexTableHint(string name) + : base($"INDEX({name})") + { + _name = name; + } + + public override bool Equals(object? obj) + { + return ReferenceEquals(this, obj) || (obj is IndexTableHint other && Equals(other)); + } + + public bool Equals(IndexTableHint? other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + return base.Equals(other) && _name == other._name; + } + + public override int GetHashCode() + { + return HashCode.Combine(base.GetHashCode(), _name); + } + + public override string ToString(ISqlGenerationHelper sqlGenerationHelper) + { + return $"INDEX({sqlGenerationHelper.DelimitIdentifier(_name)})"; + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/SqlServerTableHintLimited.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/SqlServerTableHintLimited.cs index 9552389b..477e0abf 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/SqlServerTableHintLimited.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/SqlServerTableHintLimited.cs @@ -1,144 +1,144 @@ -namespace Thinktecture.EntityFrameworkCore; - -/// -/// SQL Server table hints. -/// -public class SqlServerTableHintLimited : IEquatable -{ - /// - /// KEEPIDENTITY - /// - public static readonly SqlServerTableHintLimited KeepIdentity = new("KEEPIDENTITY"); - - /// - /// KEEPDEFAULTS - /// - public static readonly SqlServerTableHintLimited KeepDefaults = new("KEEPDEFAULTS"); - - /// - /// HOLDLOCK - /// - public static readonly SqlServerTableHintLimited HoldLock = new("HOLDLOCK"); - - /// - /// IGNORE_CONSTRAINTS - /// - public static readonly SqlServerTableHintLimited IgnoreConstraints = new("IGNORE_CONSTRAINTS"); - - /// - /// IGNORE_TRIGGERS - /// - public static readonly SqlServerTableHintLimited IgnoreTriggers = new("IGNORE_TRIGGERS"); - - /// - /// NOLOCK - /// - public static readonly SqlServerTableHintLimited NoLock = new("NOLOCK"); - - /// - /// NOWAIT - /// - public static readonly SqlServerTableHintLimited NoWait = new("NOWAIT"); - - /// - /// PAGLOCK - /// - public static readonly SqlServerTableHintLimited PagLock = new("PAGLOCK"); - - /// - /// READCOMMITTED - /// - public static readonly SqlServerTableHintLimited ReadCommitted = new("READCOMMITTED"); - - /// - /// READCOMMITTEDLOCK - /// - public static readonly SqlServerTableHintLimited ReadCommittedLock = new("READCOMMITTEDLOCK"); - - /// - /// READPAST - /// - public static readonly SqlServerTableHintLimited ReadPast = new("READPAST"); - - /// - /// REPEATABLEREAD - /// - public static readonly SqlServerTableHintLimited RepeatableRead = new("REPEATABLEREAD"); - - /// - /// ROWLOCK - /// - public static readonly SqlServerTableHintLimited RowLock = new("ROWLOCK"); - - /// - /// SERIALIZABLE - /// - public static readonly SqlServerTableHintLimited Serializable = new("SERIALIZABLE"); - - /// - /// SNAPSHOT - /// - public static readonly SqlServerTableHintLimited Snapshot = new("SNAPSHOT"); - - /// - /// TABLOCK - /// - public static readonly SqlServerTableHintLimited TabLock = new("TABLOCK"); - - /// - /// TABLOCKX - /// - public static readonly SqlServerTableHintLimited TabLockx = new("TABLOCKX"); - - /// - /// UPDLOCK - /// - public static readonly SqlServerTableHintLimited UpdLock = new("UPDLOCK"); - - /// - /// XLOCK - /// - public static readonly SqlServerTableHintLimited XLock = new("XLOCK"); - - private readonly string _value; - - private SqlServerTableHintLimited(string value) - { - _value = value ?? throw new ArgumentNullException(nameof(value)); - } - - /// - public override bool Equals(object? obj) - { - if (ReferenceEquals(null, obj)) - return false; - if (ReferenceEquals(this, obj)) - return true; - if (obj.GetType() != this.GetType()) - return false; - return Equals((SqlServerTableHintLimited)obj); - } - - /// - public bool Equals(SqlServerTableHintLimited? other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - - return _value == other._value; - } - - /// - public override int GetHashCode() - { - return _value.GetHashCode(); - } - - /// - public override string ToString() - { - return _value; - } -} +namespace Thinktecture.EntityFrameworkCore; + +/// +/// SQL Server table hints. +/// +public class SqlServerTableHintLimited : IEquatable +{ + /// + /// KEEPIDENTITY + /// + public static readonly SqlServerTableHintLimited KeepIdentity = new("KEEPIDENTITY"); + + /// + /// KEEPDEFAULTS + /// + public static readonly SqlServerTableHintLimited KeepDefaults = new("KEEPDEFAULTS"); + + /// + /// HOLDLOCK + /// + public static readonly SqlServerTableHintLimited HoldLock = new("HOLDLOCK"); + + /// + /// IGNORE_CONSTRAINTS + /// + public static readonly SqlServerTableHintLimited IgnoreConstraints = new("IGNORE_CONSTRAINTS"); + + /// + /// IGNORE_TRIGGERS + /// + public static readonly SqlServerTableHintLimited IgnoreTriggers = new("IGNORE_TRIGGERS"); + + /// + /// NOLOCK + /// + public static readonly SqlServerTableHintLimited NoLock = new("NOLOCK"); + + /// + /// NOWAIT + /// + public static readonly SqlServerTableHintLimited NoWait = new("NOWAIT"); + + /// + /// PAGLOCK + /// + public static readonly SqlServerTableHintLimited PagLock = new("PAGLOCK"); + + /// + /// READCOMMITTED + /// + public static readonly SqlServerTableHintLimited ReadCommitted = new("READCOMMITTED"); + + /// + /// READCOMMITTEDLOCK + /// + public static readonly SqlServerTableHintLimited ReadCommittedLock = new("READCOMMITTEDLOCK"); + + /// + /// READPAST + /// + public static readonly SqlServerTableHintLimited ReadPast = new("READPAST"); + + /// + /// REPEATABLEREAD + /// + public static readonly SqlServerTableHintLimited RepeatableRead = new("REPEATABLEREAD"); + + /// + /// ROWLOCK + /// + public static readonly SqlServerTableHintLimited RowLock = new("ROWLOCK"); + + /// + /// SERIALIZABLE + /// + public static readonly SqlServerTableHintLimited Serializable = new("SERIALIZABLE"); + + /// + /// SNAPSHOT + /// + public static readonly SqlServerTableHintLimited Snapshot = new("SNAPSHOT"); + + /// + /// TABLOCK + /// + public static readonly SqlServerTableHintLimited TabLock = new("TABLOCK"); + + /// + /// TABLOCKX + /// + public static readonly SqlServerTableHintLimited TabLockx = new("TABLOCKX"); + + /// + /// UPDLOCK + /// + public static readonly SqlServerTableHintLimited UpdLock = new("UPDLOCK"); + + /// + /// XLOCK + /// + public static readonly SqlServerTableHintLimited XLock = new("XLOCK"); + + private readonly string _value; + + private SqlServerTableHintLimited(string value) + { + _value = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj.GetType() != this.GetType()) + return false; + return Equals((SqlServerTableHintLimited)obj); + } + + /// + public bool Equals(SqlServerTableHintLimited? other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return _value == other._value; + } + + /// + public override int GetHashCode() + { + return _value.GetHashCode(); + } + + /// + public override string ToString() + { + return _value; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/ISqlServerTempTableCreator.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/ISqlServerTempTableCreator.cs index ee3ff304..7e1395d8 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/ISqlServerTempTableCreator.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/ISqlServerTempTableCreator.cs @@ -1,35 +1,35 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Creates temp tables for SQL server. -/// -public interface ISqlServerTempTableCreator : ITempTableCreator -{ - /// - /// Creates a temp table. - /// - /// Entity/query type. - /// Options. - /// Cancellation token. - /// A reference to a temp table. - /// - /// is null. - /// - /// The provided type is not known by the current . - Task CreateTempTableAsync( - IEntityType entityType, - SqlServerTempTableCreationOptions options, - CancellationToken cancellationToken = default); - - /// - /// Creates a primary key in a temp table with provided . - /// - /// Database context to use. - /// Properties which should be part of the primary key. - /// Table name to create the primary key in. - /// If true then the primary key is not going to be created if it exists already. - /// Cancellation token. - Task CreatePrimaryKeyAsync(DbContext ctx, IReadOnlyCollection keyProperties, string tableName, bool checkForExistence = false, CancellationToken cancellationToken = default); -} +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Creates temp tables for SQL server. +/// +public interface ISqlServerTempTableCreator : ITempTableCreator +{ + /// + /// Creates a temp table. + /// + /// Entity/query type. + /// Options. + /// Cancellation token. + /// A reference to a temp table. + /// + /// is null. + /// + /// The provided type is not known by the current . + Task CreateTempTableAsync( + IEntityType entityType, + SqlServerTempTableCreationOptions options, + CancellationToken cancellationToken = default); + + /// + /// Creates a primary key in a temp table with provided . + /// + /// Database context to use. + /// Properties which should be part of the primary key. + /// Table name to create the primary key in. + /// If true then the primary key is not going to be created if it exists already. + /// Cancellation token. + Task CreatePrimaryKeyAsync(DbContext ctx, IReadOnlyCollection keyProperties, string tableName, bool checkForExistence = false, CancellationToken cancellationToken = default); +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableCreationOptions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableCreationOptions.cs index a67eb8ca..34bd16d3 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableCreationOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableCreationOptions.cs @@ -1,22 +1,22 @@ -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Options required for creation of a temp table. -/// -public class SqlServerTempTableCreationOptions : TempTableCreationOptions -{ - /// - /// Adds "COLLATE database_default" to columns so the collation matches with the one of the user database instead of the master db. - /// - public bool UseDefaultDatabaseCollation { get; set; } - - /// - /// Initializes new instance of . - /// - public SqlServerTempTableCreationOptions(ITempTableCreationOptions? optionsToInitializeFrom = null) - : base(optionsToInitializeFrom) - { - if (optionsToInitializeFrom is SqlServerTempTableCreationOptions sqlServerOptions) - UseDefaultDatabaseCollation = sqlServerOptions.UseDefaultDatabaseCollation; - } -} +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Options required for creation of a temp table. +/// +public class SqlServerTempTableCreationOptions : TempTableCreationOptions +{ + /// + /// Adds "COLLATE database_default" to columns so the collation matches with the one of the user database instead of the master db. + /// + public bool UseDefaultDatabaseCollation { get; set; } + + /// + /// Initializes new instance of . + /// + public SqlServerTempTableCreationOptions(ITempTableCreationOptions? optionsToInitializeFrom = null) + : base(optionsToInitializeFrom) + { + if (optionsToInitializeFrom is SqlServerTempTableCreationOptions sqlServerOptions) + UseDefaultDatabaseCollation = sqlServerOptions.UseDefaultDatabaseCollation; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableCreator.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableCreator.cs index cf79cc20..5820e307 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableCreator.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableCreator.cs @@ -1,332 +1,332 @@ -using System.Diagnostics.CodeAnalysis; -using System.Text; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.ObjectPool; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Creates temp tables. -/// -public sealed class SqlServerTempTableCreator : ISqlServerTempTableCreator -{ - private static readonly string[] _stringColumnTypes = { "char", "varchar", "text", "nchar", "nvarchar", "ntext" }; - - private readonly DbContext _ctx; - private readonly IDiagnosticsLogger _logger; - private readonly ISqlGenerationHelper _sqlGenerationHelper; - private readonly IRelationalTypeMappingSource _typeMappingSource; - private readonly TempTableStatementCache _tempTableCache; - private readonly TempTableStatementCache _primaryKeyCache; - private readonly ObjectPool _stringBuilderPool; - - /// - /// Initializes . - /// - /// Current database context. - /// Logger. - /// SQL generation helper. - /// Type mappings. - /// SQL statement cache. - /// Cache for primary keys. - /// String builder pool. - public SqlServerTempTableCreator( - ICurrentDbContext ctx, - IDiagnosticsLogger logger, - ISqlGenerationHelper sqlGenerationHelper, - IRelationalTypeMappingSource typeMappingSource, - TempTableStatementCache cache, - TempTableStatementCache primaryKeyCache, - ObjectPool stringBuilderPool) - { - ArgumentNullException.ThrowIfNull(ctx); - - _ctx = ctx.Context ?? throw new ArgumentNullException(nameof(sqlGenerationHelper)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _sqlGenerationHelper = sqlGenerationHelper ?? throw new ArgumentNullException(nameof(sqlGenerationHelper)); - _typeMappingSource = typeMappingSource ?? throw new ArgumentNullException(nameof(typeMappingSource)); - _tempTableCache = cache ?? throw new ArgumentNullException(nameof(cache)); - _primaryKeyCache = primaryKeyCache ?? throw new ArgumentNullException(nameof(primaryKeyCache)); - _stringBuilderPool = stringBuilderPool ?? throw new ArgumentNullException(nameof(stringBuilderPool)); - } - - /// - public Task CreateTempTableAsync( - IEntityType entityType, - ITempTableCreationOptions options, - CancellationToken cancellationToken = default) - { - if (options is not SqlServerTempTableCreationOptions sqlServerOptions) - sqlServerOptions = new SqlServerTempTableCreationOptions(options); - - return CreateTempTableAsync(entityType, sqlServerOptions, cancellationToken); - } - - /// - public async Task CreateTempTableAsync( - IEntityType entityType, - SqlServerTempTableCreationOptions options, - CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(entityType); - ArgumentNullException.ThrowIfNull(options); - - var (nameLease, tableName) = GetTableName(entityType, options.TableNameProvider); - - try - { - var sql = GetTempTableCreationSql(entityType, tableName, options); - - await _ctx.Database.OpenConnectionAsync(cancellationToken).ConfigureAwait(false); - - try - { - await _ctx.Database.ExecuteSqlRawAsync(sql, cancellationToken).ConfigureAwait(false); - } - catch (Exception) - { - await _ctx.Database.CloseConnectionAsync().ConfigureAwait(false); - throw; - } - - return new SqlServerTempTableReference(_logger, _sqlGenerationHelper, tableName, _ctx.Database, nameLease, options.DropTableOnDispose); - } - catch (Exception) - { - nameLease.Dispose(); - throw; - } - } - - private (ITempTableNameLease nameLease, string tableName) GetTableName( - IEntityType entityType, - ITempTableNameProvider nameProvider) - { - ArgumentNullException.ThrowIfNull(nameProvider); - - var nameLease = nameProvider.LeaseName(_ctx, entityType); - var name = nameLease.Name; - - if (!name.StartsWith("#", StringComparison.Ordinal)) - name = $"#{name}"; - - return (nameLease, name); - } - - /// - public async Task CreatePrimaryKeyAsync( - DbContext ctx, - IReadOnlyCollection keyProperties, - string tableName, - bool checkForExistence = false, - CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(ctx); - ArgumentNullException.ThrowIfNull(keyProperties); - ArgumentNullException.ThrowIfNull(tableName); - - if (keyProperties.Count == 0) - return; - - var cachedStatement = _primaryKeyCache.GetOrAdd(new SqlServerTempTablePrimaryKeyCacheKey(keyProperties, checkForExistence), CreatePrimaryKeyStatement); - var sql = cachedStatement.GetSqlStatement(_sqlGenerationHelper, tableName); - - await ctx.Database.ExecuteSqlRawAsync(sql, cancellationToken).ConfigureAwait(false); - } - - private ICachedTempTableStatement CreatePrimaryKeyStatement(SqlServerTempTablePrimaryKeyCacheKey cacheKey) - { - var columnNames = cacheKey.KeyProperties.Select(p => - { - var storeObject = p.GetStoreObject(); - return p.GetColumnName(storeObject); - }); - - var commaSeparatedColumns = String.Join(", ", columnNames); - - if (cacheKey.CheckForExistence) - { - return new CachedTempTableStatement(commaSeparatedColumns, - static (sqlGenerationHelper, name, commaSeparatedColumns) => - $""" - IF(NOT EXISTS (SELECT * FROM tempdb.INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'PRIMARY KEY' AND OBJECT_ID(TABLE_CATALOG + '..' + TABLE_NAME) = OBJECT_ID('tempdb..{name}'))) - BEGIN - ALTER TABLE {sqlGenerationHelper.DelimitIdentifier(name)} - ADD CONSTRAINT {sqlGenerationHelper.DelimitIdentifier($"PK_{name}_{Guid.NewGuid():N}")} PRIMARY KEY CLUSTERED ({commaSeparatedColumns}); - END - """); - } - - return new CachedTempTableStatement(commaSeparatedColumns, static (sqlGenerationHelper, name, commaSeparatedColumns) => - $""" - ALTER TABLE {sqlGenerationHelper.DelimitIdentifier(name)} - ADD CONSTRAINT {sqlGenerationHelper.DelimitIdentifier($"PK_{name}_{Guid.NewGuid():N}")} PRIMARY KEY CLUSTERED ({commaSeparatedColumns}); - """); - } - - private string GetTempTableCreationSql(IEntityType entityType, string tableName, SqlServerTempTableCreationOptions options) - { - ArgumentNullException.ThrowIfNull(tableName); - - var cachedStatement = _tempTableCache.GetOrAdd(new SqlServerTempTableCreatorCacheKey(options, entityType), CreateCachedStatement); - - return cachedStatement.GetSqlStatement(_sqlGenerationHelper, tableName); - } - - private ICachedTempTableStatement CreateCachedStatement(SqlServerTempTableCreatorCacheKey cacheKey) - { - var columnDefinitions = GetColumnsDefinitions(cacheKey); - - if (!cacheKey.TruncateTableIfExists) - { - return new CachedTempTableStatement(columnDefinitions, - static (sqlGenerationHelper, name, columnDefinitions) => - $""" - CREATE TABLE {sqlGenerationHelper.DelimitIdentifier(name)} - ( - {columnDefinitions} - ); - """); - } - - return new CachedTempTableStatement(columnDefinitions, - static (sqlGenerationHelper, name, columnDefinitions) => - { - var escapedTableName = sqlGenerationHelper.DelimitIdentifier(name); - - return $""" - IF(OBJECT_ID('tempdb..{name}') IS NOT NULL) - TRUNCATE TABLE {escapedTableName}; - ELSE - BEGIN - CREATE TABLE {escapedTableName} - ( - {columnDefinitions} - ); - END - """; - }); - } - - private string GetColumnsDefinitions(SqlServerTempTableCreatorCacheKey options) - { - var sb = _stringBuilderPool.Get(); - - try - { - StoreObjectIdentifier? storeObject = null; - IEntityType? designTimeEntityType = null; - - var isFirst = true; - - foreach (var property in options.Properties) - { - if (!isFirst) - sb.AppendLine(","); - - storeObject ??= property.GetStoreObject(); - - var columnType = property.GetColumnType(storeObject.Value); - var columnName = property.GetColumnName(storeObject.Value) - ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.DeclaringType.Name}'."); - - sb.Append('\t') - .Append(_sqlGenerationHelper.DelimitIdentifier(columnName)).Append(' ') - .Append(columnType); - - if (_stringColumnTypes.Any(t => columnType.StartsWith(t, StringComparison.OrdinalIgnoreCase))) - { - // Collation information is not available from the runtime model, so we need to fetch it from the design time model - designTimeEntityType ??= _ctx.GetService().Model - .GetEntityType(property.DeclaringType.Name); - - var collation = designTimeEntityType.GetProperty(property.Name) - .GetCollation(storeObject.Value); - - if (options.UseDefaultDatabaseCollation && String.IsNullOrWhiteSpace(collation)) - collation = "database_default"; - - if (!String.IsNullOrWhiteSpace(collation)) - sb.Append(" COLLATE ").Append(collation); - } - - sb.Append(property.IsNullable ? " NULL" : " NOT NULL"); - - if (IsIdentityColumn(property)) - sb.Append(" IDENTITY"); - - var defaultValueSql = property.GetDefaultValueSql(storeObject.Value); - - if (!String.IsNullOrWhiteSpace(defaultValueSql)) - { - sb.Append(" DEFAULT (").Append(defaultValueSql).Append(')'); - } - else if (property.TryGetDefaultValue(storeObject.Value, out var defaultValue) && defaultValue is not null) - { - var converter = property.GetValueConverter(); - - if (converter is not null) - defaultValue = converter.ConvertToProvider(defaultValue); - - if (defaultValue is not null) - { - var mappingForValue = _typeMappingSource.FindMapping(defaultValue.GetType(), columnType) - ?? _typeMappingSource.GetMappingForValue(defaultValue); - - sb.Append(" DEFAULT ").Append(mappingForValue.GenerateSqlLiteral(defaultValue)); - } - } - - isFirst = false; - } - - CreatePkClause(options.PrimaryKeys, sb); - - return sb.ToString(); - } - finally - { - _stringBuilderPool.Return(sb); - } - } - - private void CreatePkClause( - IReadOnlyCollection keyProperties, - StringBuilder sb) - { - if (keyProperties.Count <= 0) - return; - - var columnNames = keyProperties.Select(p => - { - var storeObject = p.GetStoreObject(); - return p.GetColumnName(storeObject) - ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{p.DeclaringType.Name}'."); - }); - - sb.AppendLine(","); - sb.Append("\t\tPRIMARY KEY ("); - var isFirst = true; - - foreach (var columnName in columnNames) - { - if (!isFirst) - sb.Append(", "); - - sb.Append(_sqlGenerationHelper.DelimitIdentifier(columnName)); - isFirst = false; - } - - sb.Append(')'); - } - - [SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] - private static bool IsIdentityColumn(IProperty property) - { - return SqlServerValueGenerationStrategy.IdentityColumn.Equals(property.FindAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy)?.Value); - } -} +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.ObjectPool; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Creates temp tables. +/// +public sealed class SqlServerTempTableCreator : ISqlServerTempTableCreator +{ + private static readonly string[] _stringColumnTypes = { "char", "varchar", "text", "nchar", "nvarchar", "ntext" }; + + private readonly DbContext _ctx; + private readonly IDiagnosticsLogger _logger; + private readonly ISqlGenerationHelper _sqlGenerationHelper; + private readonly IRelationalTypeMappingSource _typeMappingSource; + private readonly TempTableStatementCache _tempTableCache; + private readonly TempTableStatementCache _primaryKeyCache; + private readonly ObjectPool _stringBuilderPool; + + /// + /// Initializes . + /// + /// Current database context. + /// Logger. + /// SQL generation helper. + /// Type mappings. + /// SQL statement cache. + /// Cache for primary keys. + /// String builder pool. + public SqlServerTempTableCreator( + ICurrentDbContext ctx, + IDiagnosticsLogger logger, + ISqlGenerationHelper sqlGenerationHelper, + IRelationalTypeMappingSource typeMappingSource, + TempTableStatementCache cache, + TempTableStatementCache primaryKeyCache, + ObjectPool stringBuilderPool) + { + ArgumentNullException.ThrowIfNull(ctx); + + _ctx = ctx.Context ?? throw new ArgumentNullException(nameof(sqlGenerationHelper)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _sqlGenerationHelper = sqlGenerationHelper ?? throw new ArgumentNullException(nameof(sqlGenerationHelper)); + _typeMappingSource = typeMappingSource ?? throw new ArgumentNullException(nameof(typeMappingSource)); + _tempTableCache = cache ?? throw new ArgumentNullException(nameof(cache)); + _primaryKeyCache = primaryKeyCache ?? throw new ArgumentNullException(nameof(primaryKeyCache)); + _stringBuilderPool = stringBuilderPool ?? throw new ArgumentNullException(nameof(stringBuilderPool)); + } + + /// + public Task CreateTempTableAsync( + IEntityType entityType, + ITempTableCreationOptions options, + CancellationToken cancellationToken = default) + { + if (options is not SqlServerTempTableCreationOptions sqlServerOptions) + sqlServerOptions = new SqlServerTempTableCreationOptions(options); + + return CreateTempTableAsync(entityType, sqlServerOptions, cancellationToken); + } + + /// + public async Task CreateTempTableAsync( + IEntityType entityType, + SqlServerTempTableCreationOptions options, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(entityType); + ArgumentNullException.ThrowIfNull(options); + + var (nameLease, tableName) = GetTableName(entityType, options.TableNameProvider); + + try + { + var sql = GetTempTableCreationSql(entityType, tableName, options); + + await _ctx.Database.OpenConnectionAsync(cancellationToken).ConfigureAwait(false); + + try + { + await _ctx.Database.ExecuteSqlRawAsync(sql, cancellationToken).ConfigureAwait(false); + } + catch (Exception) + { + await _ctx.Database.CloseConnectionAsync().ConfigureAwait(false); + throw; + } + + return new SqlServerTempTableReference(_logger, _sqlGenerationHelper, tableName, _ctx.Database, nameLease, options.DropTableOnDispose); + } + catch (Exception) + { + nameLease.Dispose(); + throw; + } + } + + private (ITempTableNameLease nameLease, string tableName) GetTableName( + IEntityType entityType, + ITempTableNameProvider nameProvider) + { + ArgumentNullException.ThrowIfNull(nameProvider); + + var nameLease = nameProvider.LeaseName(_ctx, entityType); + var name = nameLease.Name; + + if (!name.StartsWith("#", StringComparison.Ordinal)) + name = $"#{name}"; + + return (nameLease, name); + } + + /// + public async Task CreatePrimaryKeyAsync( + DbContext ctx, + IReadOnlyCollection keyProperties, + string tableName, + bool checkForExistence = false, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(ctx); + ArgumentNullException.ThrowIfNull(keyProperties); + ArgumentNullException.ThrowIfNull(tableName); + + if (keyProperties.Count == 0) + return; + + var cachedStatement = _primaryKeyCache.GetOrAdd(new SqlServerTempTablePrimaryKeyCacheKey(keyProperties, checkForExistence), CreatePrimaryKeyStatement); + var sql = cachedStatement.GetSqlStatement(_sqlGenerationHelper, tableName); + + await ctx.Database.ExecuteSqlRawAsync(sql, cancellationToken).ConfigureAwait(false); + } + + private ICachedTempTableStatement CreatePrimaryKeyStatement(SqlServerTempTablePrimaryKeyCacheKey cacheKey) + { + var columnNames = cacheKey.KeyProperties.Select(p => + { + var storeObject = p.GetStoreObject(); + return p.GetColumnName(storeObject); + }); + + var commaSeparatedColumns = String.Join(", ", columnNames); + + if (cacheKey.CheckForExistence) + { + return new CachedTempTableStatement(commaSeparatedColumns, + static (sqlGenerationHelper, name, commaSeparatedColumns) => + $""" + IF(NOT EXISTS (SELECT * FROM tempdb.INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'PRIMARY KEY' AND OBJECT_ID(TABLE_CATALOG + '..' + TABLE_NAME) = OBJECT_ID('tempdb..{name}'))) + BEGIN + ALTER TABLE {sqlGenerationHelper.DelimitIdentifier(name)} + ADD CONSTRAINT {sqlGenerationHelper.DelimitIdentifier($"PK_{name}_{Guid.NewGuid():N}")} PRIMARY KEY CLUSTERED ({commaSeparatedColumns}); + END + """); + } + + return new CachedTempTableStatement(commaSeparatedColumns, static (sqlGenerationHelper, name, commaSeparatedColumns) => + $""" + ALTER TABLE {sqlGenerationHelper.DelimitIdentifier(name)} + ADD CONSTRAINT {sqlGenerationHelper.DelimitIdentifier($"PK_{name}_{Guid.NewGuid():N}")} PRIMARY KEY CLUSTERED ({commaSeparatedColumns}); + """); + } + + private string GetTempTableCreationSql(IEntityType entityType, string tableName, SqlServerTempTableCreationOptions options) + { + ArgumentNullException.ThrowIfNull(tableName); + + var cachedStatement = _tempTableCache.GetOrAdd(new SqlServerTempTableCreatorCacheKey(options, entityType), CreateCachedStatement); + + return cachedStatement.GetSqlStatement(_sqlGenerationHelper, tableName); + } + + private ICachedTempTableStatement CreateCachedStatement(SqlServerTempTableCreatorCacheKey cacheKey) + { + var columnDefinitions = GetColumnsDefinitions(cacheKey); + + if (!cacheKey.TruncateTableIfExists) + { + return new CachedTempTableStatement(columnDefinitions, + static (sqlGenerationHelper, name, columnDefinitions) => + $""" + CREATE TABLE {sqlGenerationHelper.DelimitIdentifier(name)} + ( + {columnDefinitions} + ); + """); + } + + return new CachedTempTableStatement(columnDefinitions, + static (sqlGenerationHelper, name, columnDefinitions) => + { + var escapedTableName = sqlGenerationHelper.DelimitIdentifier(name); + + return $""" + IF(OBJECT_ID('tempdb..{name}') IS NOT NULL) + TRUNCATE TABLE {escapedTableName}; + ELSE + BEGIN + CREATE TABLE {escapedTableName} + ( + {columnDefinitions} + ); + END + """; + }); + } + + private string GetColumnsDefinitions(SqlServerTempTableCreatorCacheKey options) + { + var sb = _stringBuilderPool.Get(); + + try + { + StoreObjectIdentifier? storeObject = null; + IEntityType? designTimeEntityType = null; + + var isFirst = true; + + foreach (var property in options.Properties) + { + if (!isFirst) + sb.AppendLine(","); + + storeObject ??= property.GetStoreObject(); + + var columnType = property.GetColumnType(storeObject.Value); + var columnName = property.GetColumnName(storeObject.Value) + ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.DeclaringType.Name}'."); + + sb.Append('\t') + .Append(_sqlGenerationHelper.DelimitIdentifier(columnName)).Append(' ') + .Append(columnType); + + if (_stringColumnTypes.Any(t => columnType.StartsWith(t, StringComparison.OrdinalIgnoreCase))) + { + // Collation information is not available from the runtime model, so we need to fetch it from the design time model + designTimeEntityType ??= _ctx.GetService().Model + .GetEntityType(property.DeclaringType.Name); + + var collation = designTimeEntityType.GetProperty(property.Name) + .GetCollation(storeObject.Value); + + if (options.UseDefaultDatabaseCollation && String.IsNullOrWhiteSpace(collation)) + collation = "database_default"; + + if (!String.IsNullOrWhiteSpace(collation)) + sb.Append(" COLLATE ").Append(collation); + } + + sb.Append(property.IsNullable ? " NULL" : " NOT NULL"); + + if (IsIdentityColumn(property)) + sb.Append(" IDENTITY"); + + var defaultValueSql = property.GetDefaultValueSql(storeObject.Value); + + if (!String.IsNullOrWhiteSpace(defaultValueSql)) + { + sb.Append(" DEFAULT (").Append(defaultValueSql).Append(')'); + } + else if (property.TryGetDefaultValue(storeObject.Value, out var defaultValue) && defaultValue is not null) + { + var converter = property.GetValueConverter(); + + if (converter is not null) + defaultValue = converter.ConvertToProvider(defaultValue); + + if (defaultValue is not null) + { + var mappingForValue = _typeMappingSource.FindMapping(defaultValue.GetType(), columnType) + ?? _typeMappingSource.GetMappingForValue(defaultValue); + + sb.Append(" DEFAULT ").Append(mappingForValue.GenerateSqlLiteral(defaultValue)); + } + } + + isFirst = false; + } + + CreatePkClause(options.PrimaryKeys, sb); + + return sb.ToString(); + } + finally + { + _stringBuilderPool.Return(sb); + } + } + + private void CreatePkClause( + IReadOnlyCollection keyProperties, + StringBuilder sb) + { + if (keyProperties.Count <= 0) + return; + + var columnNames = keyProperties.Select(p => + { + var storeObject = p.GetStoreObject(); + return p.GetColumnName(storeObject) + ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{p.DeclaringType.Name}'."); + }); + + sb.AppendLine(","); + sb.Append("\t\tPRIMARY KEY ("); + var isFirst = true; + + foreach (var columnName in columnNames) + { + if (!isFirst) + sb.Append(", "); + + sb.Append(_sqlGenerationHelper.DelimitIdentifier(columnName)); + isFirst = false; + } + + sb.Append(')'); + } + + [SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] + private static bool IsIdentityColumn(IProperty property) + { + return SqlServerValueGenerationStrategy.IdentityColumn.Equals(property.FindAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy)?.Value); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableCreatorCacheKey.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableCreatorCacheKey.cs index 0aa6419a..026735dc 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableCreatorCacheKey.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableCreatorCacheKey.cs @@ -1,69 +1,69 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Cache key for . -/// -public readonly struct SqlServerTempTableCreatorCacheKey - : IEquatable -{ - /// - public bool TruncateTableIfExists { get; } - - /// - public bool UseDefaultDatabaseCollation { get; } - - /// - /// Properties to create temp table with. - /// - public IReadOnlyList Properties { get; } - - /// - /// Properties the primary key should be created with. - /// - public IReadOnlyCollection PrimaryKeys { get; } - - /// - /// Initializes new instance of . - /// - /// Options. - /// Entity type. - public SqlServerTempTableCreatorCacheKey( - SqlServerTempTableCreationOptions options, - IEntityType entityType) - { - TruncateTableIfExists = options.TruncateTableIfExists; - UseDefaultDatabaseCollation = options.UseDefaultDatabaseCollation; - Properties = options.PropertiesToInclude.DeterminePropertiesForTempTable(entityType); - PrimaryKeys = options.PrimaryKeyCreation.GetPrimaryKeyProperties(entityType, Properties); - } - - /// - public bool Equals(SqlServerTempTableCreatorCacheKey other) - { - return TruncateTableIfExists == other.TruncateTableIfExists && - UseDefaultDatabaseCollation == other.UseDefaultDatabaseCollation && - Properties.AreEqual(other.Properties) && - PrimaryKeys.AreEqual(other.PrimaryKeys); - } - - /// - public override bool Equals(object? obj) - { - return obj is SqlServerTempTableCreatorCacheKey other && Equals(other); - } - - /// - public override int GetHashCode() - { - var hashCode = new HashCode(); - hashCode.Add(TruncateTableIfExists); - hashCode.Add(UseDefaultDatabaseCollation); - - Properties.ComputeHashCode(hashCode); - PrimaryKeys.ComputeHashCode(hashCode); - - return hashCode.ToHashCode(); - } -} +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Cache key for . +/// +public readonly struct SqlServerTempTableCreatorCacheKey + : IEquatable +{ + /// + public bool TruncateTableIfExists { get; } + + /// + public bool UseDefaultDatabaseCollation { get; } + + /// + /// Properties to create temp table with. + /// + public IReadOnlyList Properties { get; } + + /// + /// Properties the primary key should be created with. + /// + public IReadOnlyCollection PrimaryKeys { get; } + + /// + /// Initializes new instance of . + /// + /// Options. + /// Entity type. + public SqlServerTempTableCreatorCacheKey( + SqlServerTempTableCreationOptions options, + IEntityType entityType) + { + TruncateTableIfExists = options.TruncateTableIfExists; + UseDefaultDatabaseCollation = options.UseDefaultDatabaseCollation; + Properties = options.PropertiesToInclude.DeterminePropertiesForTempTable(entityType); + PrimaryKeys = options.PrimaryKeyCreation.GetPrimaryKeyProperties(entityType, Properties); + } + + /// + public bool Equals(SqlServerTempTableCreatorCacheKey other) + { + return TruncateTableIfExists == other.TruncateTableIfExists && + UseDefaultDatabaseCollation == other.UseDefaultDatabaseCollation && + Properties.AreEqual(other.Properties) && + PrimaryKeys.AreEqual(other.PrimaryKeys); + } + + /// + public override bool Equals(object? obj) + { + return obj is SqlServerTempTableCreatorCacheKey other && Equals(other); + } + + /// + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(TruncateTableIfExists); + hashCode.Add(UseDefaultDatabaseCollation); + + Properties.ComputeHashCode(hashCode); + PrimaryKeys.ComputeHashCode(hashCode); + + return hashCode.ToHashCode(); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTablePrimaryKeyCacheKey.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTablePrimaryKeyCacheKey.cs index 483ea0d3..7d6983c8 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTablePrimaryKeyCacheKey.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTablePrimaryKeyCacheKey.cs @@ -1,58 +1,58 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Cache key for . -/// -public readonly struct SqlServerTempTablePrimaryKeyCacheKey - : IEquatable - -{ - /// - /// Properties to create the primary key with. - /// - public IReadOnlyCollection KeyProperties { get; } - - /// - /// Indication whether to check for existence of PK. - /// - public bool CheckForExistence { get; } - - /// - /// Initializes new instance of . - /// - /// Properties to create the primary key with. - /// Indication whether to check for existence of PK. - public SqlServerTempTablePrimaryKeyCacheKey( - IReadOnlyCollection keyProperties, - bool checkForExistence) - { - KeyProperties = keyProperties; - CheckForExistence = checkForExistence; - } - - /// - public bool Equals(SqlServerTempTablePrimaryKeyCacheKey other) - { - return CheckForExistence == other.CheckForExistence && - KeyProperties.AreEqual(other.KeyProperties); - } - - /// - public override bool Equals(object? obj) - { - return obj is SqlServerTempTablePrimaryKeyCacheKey other && Equals(other); - } - - /// - public override int GetHashCode() - { - var hashCode = new HashCode(); - hashCode.Add(CheckForExistence); - - KeyProperties.ComputeHashCode(hashCode); - - return hashCode.ToHashCode(); - } -} +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Cache key for . +/// +public readonly struct SqlServerTempTablePrimaryKeyCacheKey + : IEquatable + +{ + /// + /// Properties to create the primary key with. + /// + public IReadOnlyCollection KeyProperties { get; } + + /// + /// Indication whether to check for existence of PK. + /// + public bool CheckForExistence { get; } + + /// + /// Initializes new instance of . + /// + /// Properties to create the primary key with. + /// Indication whether to check for existence of PK. + public SqlServerTempTablePrimaryKeyCacheKey( + IReadOnlyCollection keyProperties, + bool checkForExistence) + { + KeyProperties = keyProperties; + CheckForExistence = checkForExistence; + } + + /// + public bool Equals(SqlServerTempTablePrimaryKeyCacheKey other) + { + return CheckForExistence == other.CheckForExistence && + KeyProperties.AreEqual(other.KeyProperties); + } + + /// + public override bool Equals(object? obj) + { + return obj is SqlServerTempTablePrimaryKeyCacheKey other && Equals(other); + } + + /// + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(CheckForExistence); + + KeyProperties.ComputeHashCode(hashCode); + + return hashCode.ToHashCode(); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableReference.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableReference.cs index 3fd845a1..23538b66 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableReference.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/EntityFrameworkCore/TempTables/SqlServerTempTableReference.cs @@ -1,136 +1,136 @@ -using System.Data; -using System.Data.Common; -using System.Diagnostics.CodeAnalysis; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.Logging; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// A reference to SQL Server temp table. -/// -public sealed class SqlServerTempTableReference : ITempTableReference -{ - private readonly IDiagnosticsLogger _logger; - private readonly ISqlGenerationHelper _sqlGenerationHelper; - private readonly DatabaseFacade _database; - private readonly ITempTableNameLease _nameLease; - private readonly bool _dropTableOnDispose; - - private bool _isDisposed; - - /// - public string Name { get; } - - /// - /// Initializes new instance of . - /// - /// Logger - /// SQL generation helper. - /// The name of the temp table. - /// Database facade. - /// Leased table name that will be disposed along with the temp table. - /// Indication whether to drop the temp table on dispose or not. - public SqlServerTempTableReference(IDiagnosticsLogger logger, - ISqlGenerationHelper sqlGenerationHelper, - string tableName, - DatabaseFacade database, - ITempTableNameLease nameLease, - bool dropTableOnDispose) - { - Name = tableName ?? throw new ArgumentNullException(nameof(tableName)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _sqlGenerationHelper = sqlGenerationHelper ?? throw new ArgumentNullException(nameof(sqlGenerationHelper)); - _database = database ?? throw new ArgumentNullException(nameof(database)); - _nameLease = nameLease ?? throw new ArgumentNullException(nameof(nameLease)); - _dropTableOnDispose = dropTableOnDispose; - } - - /// - public void Dispose() - { - if (_isDisposed) - return; - - _isDisposed = true; - - try - { - using var command = TryCreateCleanupCommand(); - - if (command is null) - return; - - command.ExecuteNonQuery(); - - _database.CloseConnection(); - } - catch (ObjectDisposedException ex) - { - _logger.Logger.LogWarning(ex, $"Trying to dispose of the temp table reference '{Name}' after the corresponding DbContext has been disposed."); - } - finally - { - _nameLease.Dispose(); - } - } - - /// - public async ValueTask DisposeAsync() - { - if (_isDisposed) - return; - - _isDisposed = true; - - try - { - await using var command = TryCreateCleanupCommand(); - - if (command is null) - return; - - await command.ExecuteNonQueryAsync(); - - await _database.CloseConnectionAsync().ConfigureAwait(false); - } - catch (ObjectDisposedException ex) - { - _logger.Logger.LogWarning(ex, $"Trying to dispose of the temp table reference '{Name}' after the corresponding DbContext has been disposed."); - } - finally - { - _nameLease.Dispose(); - } - } - - private DbCommand? TryCreateCleanupCommand() - { - DbCommand? command = null; - - try - { - if (!_dropTableOnDispose) - return null; - - var connection = _database.GetDbConnection(); - - if (connection.State != ConnectionState.Open) - return null; - - command = connection.CreateCommand(); - command.CommandText = $""" - IF(OBJECT_ID('tempdb..{Name}') IS NOT NULL) - DROP TABLE {_sqlGenerationHelper.DelimitIdentifier(Name)}; - """; - return command; - } - catch - { - command?.Dispose(); - throw; - } - } -} +using System.Data; +using System.Data.Common; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.Logging; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// A reference to SQL Server temp table. +/// +public sealed class SqlServerTempTableReference : ITempTableReference +{ + private readonly IDiagnosticsLogger _logger; + private readonly ISqlGenerationHelper _sqlGenerationHelper; + private readonly DatabaseFacade _database; + private readonly ITempTableNameLease _nameLease; + private readonly bool _dropTableOnDispose; + + private bool _isDisposed; + + /// + public string Name { get; } + + /// + /// Initializes new instance of . + /// + /// Logger + /// SQL generation helper. + /// The name of the temp table. + /// Database facade. + /// Leased table name that will be disposed along with the temp table. + /// Indication whether to drop the temp table on dispose or not. + public SqlServerTempTableReference(IDiagnosticsLogger logger, + ISqlGenerationHelper sqlGenerationHelper, + string tableName, + DatabaseFacade database, + ITempTableNameLease nameLease, + bool dropTableOnDispose) + { + Name = tableName ?? throw new ArgumentNullException(nameof(tableName)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _sqlGenerationHelper = sqlGenerationHelper ?? throw new ArgumentNullException(nameof(sqlGenerationHelper)); + _database = database ?? throw new ArgumentNullException(nameof(database)); + _nameLease = nameLease ?? throw new ArgumentNullException(nameof(nameLease)); + _dropTableOnDispose = dropTableOnDispose; + } + + /// + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + + try + { + using var command = TryCreateCleanupCommand(); + + if (command is null) + return; + + command.ExecuteNonQuery(); + + _database.CloseConnection(); + } + catch (ObjectDisposedException ex) + { + _logger.Logger.LogWarning(ex, $"Trying to dispose of the temp table reference '{Name}' after the corresponding DbContext has been disposed."); + } + finally + { + _nameLease.Dispose(); + } + } + + /// + public async ValueTask DisposeAsync() + { + if (_isDisposed) + return; + + _isDisposed = true; + + try + { + await using var command = TryCreateCleanupCommand(); + + if (command is null) + return; + + await command.ExecuteNonQueryAsync(); + + await _database.CloseConnectionAsync().ConfigureAwait(false); + } + catch (ObjectDisposedException ex) + { + _logger.Logger.LogWarning(ex, $"Trying to dispose of the temp table reference '{Name}' after the corresponding DbContext has been disposed."); + } + finally + { + _nameLease.Dispose(); + } + } + + private DbCommand? TryCreateCleanupCommand() + { + DbCommand? command = null; + + try + { + if (!_dropTableOnDispose) + return null; + + var connection = _database.GetDbConnection(); + + if (connection.State != ConnectionState.Open) + return null; + + command = connection.CreateCommand(); + command.CommandText = $""" + IF(OBJECT_ID('tempdb..{Name}') IS NOT NULL) + DROP TABLE {_sqlGenerationHelper.DelimitIdentifier(Name)}; + """; + return command; + } + catch + { + command?.Dispose(); + throw; + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerCollectionExtensions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerCollectionExtensions.cs index 25913960..dd7432c3 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerCollectionExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerCollectionExtensions.cs @@ -1,59 +1,59 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture; - -internal static class SqlServerCollectionExtensions -{ - public static void ComputeHashCode(this IEnumerable properties, HashCode hashCode) - { - foreach (var property in properties) - { - hashCode.Add(property); - } - } - - public static void ComputeHashCode(this IEnumerable properties, HashCode hashCode) - { - foreach (var property in properties) - { - hashCode.Add(property); - } - } - - public static bool AreEqual(this IReadOnlyCollection collection, IReadOnlyCollection other) - { - if (collection.Count != other.Count) - return false; - - using var otherEnumerator = other.GetEnumerator(); - - foreach (var item in collection) - { - otherEnumerator.MoveNext(); - - if (!item.Equals(otherEnumerator.Current)) - return false; - } - - return true; - } - - public static bool AreEqual(this IReadOnlyCollection collection, IReadOnlyCollection other) - { - if (collection.Count != other.Count) - return false; - - using var otherEnumerator = other.GetEnumerator(); - - foreach (var item in collection) - { - otherEnumerator.MoveNext(); - - if (!item.Equals(otherEnumerator.Current)) - return false; - } - - return true; - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture; + +internal static class SqlServerCollectionExtensions +{ + public static void ComputeHashCode(this IEnumerable properties, HashCode hashCode) + { + foreach (var property in properties) + { + hashCode.Add(property); + } + } + + public static void ComputeHashCode(this IEnumerable properties, HashCode hashCode) + { + foreach (var property in properties) + { + hashCode.Add(property); + } + } + + public static bool AreEqual(this IReadOnlyCollection collection, IReadOnlyCollection other) + { + if (collection.Count != other.Count) + return false; + + using var otherEnumerator = other.GetEnumerator(); + + foreach (var item in collection) + { + otherEnumerator.MoveNext(); + + if (!item.Equals(otherEnumerator.Current)) + return false; + } + + return true; + } + + public static bool AreEqual(this IReadOnlyCollection collection, IReadOnlyCollection other) + { + if (collection.Count != other.Count) + return false; + + using var otherEnumerator = other.GetEnumerator(); + + foreach (var item in collection) + { + otherEnumerator.MoveNext(); + + if (!item.Equals(otherEnumerator.Current)) + return false; + } + + return true; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerDbContextExtensions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerDbContextExtensions.cs index caaac98f..80ad5478 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerDbContextExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerDbContextExtensions.cs @@ -1,62 +1,62 @@ -using System.Text; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extension methods for . -/// -public static class SqlServerDbContextExtensions -{ - private static readonly NumberToBytesConverter _rowVersionConverter = new(); - - /// - /// Fetches MIN_ACTIVE_ROWVERSION from SQL Server. - /// - /// Database context to use. - /// Cancellation token. - /// The result of MIN_ACTIVE_ROWVERSION call. - /// is null. - public static Task GetMinActiveRowVersionAsync(this DbContext ctx, CancellationToken cancellationToken = default) - { - return GetRowVersionAsync(ctx, "MIN_ACTIVE_ROWVERSION()", cancellationToken); - } - - /// - /// Fetches @@DBTS from SQL Server. - /// - /// Database context to use. - /// Cancellation token. - /// The result of @@DBTS call. - /// is null. - public static Task GetLastUsedRowVersionAsync(this DbContext ctx, CancellationToken cancellationToken = default) - { - return GetRowVersionAsync(ctx, "@@DBTS", cancellationToken); - } - - private static async Task GetRowVersionAsync(DbContext ctx, string dbFunction, CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(ctx); - - await using var command = ctx.Database.GetDbConnection().CreateCommand(); - - command.Transaction = ctx.Database.CurrentTransaction?.GetDbTransaction(); - command.CommandText = $"SELECT {dbFunction};"; - - await ctx.Database.OpenConnectionAsync(cancellationToken).ConfigureAwait(false); - - try - { - var result = await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false); - var bytes = (byte[])result!; - - return (long)_rowVersionConverter.ConvertFromProvider(bytes)!; - } - finally - { - await ctx.Database.CloseConnectionAsync().ConfigureAwait(false); - } - } -} +using System.Text; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extension methods for . +/// +public static class SqlServerDbContextExtensions +{ + private static readonly NumberToBytesConverter _rowVersionConverter = new(); + + /// + /// Fetches MIN_ACTIVE_ROWVERSION from SQL Server. + /// + /// Database context to use. + /// Cancellation token. + /// The result of MIN_ACTIVE_ROWVERSION call. + /// is null. + public static Task GetMinActiveRowVersionAsync(this DbContext ctx, CancellationToken cancellationToken = default) + { + return GetRowVersionAsync(ctx, "MIN_ACTIVE_ROWVERSION()", cancellationToken); + } + + /// + /// Fetches @@DBTS from SQL Server. + /// + /// Database context to use. + /// Cancellation token. + /// The result of @@DBTS call. + /// is null. + public static Task GetLastUsedRowVersionAsync(this DbContext ctx, CancellationToken cancellationToken = default) + { + return GetRowVersionAsync(ctx, "@@DBTS", cancellationToken); + } + + private static async Task GetRowVersionAsync(DbContext ctx, string dbFunction, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(ctx); + + await using var command = ctx.Database.GetDbConnection().CreateCommand(); + + command.Transaction = ctx.Database.CurrentTransaction?.GetDbTransaction(); + command.CommandText = $"SELECT {dbFunction};"; + + await ctx.Database.OpenConnectionAsync(cancellationToken).ConfigureAwait(false); + + try + { + var result = await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false); + var bytes = (byte[])result!; + + return (long)_rowVersionConverter.ConvertFromProvider(bytes)!; + } + finally + { + await ctx.Database.CloseConnectionAsync().ConfigureAwait(false); + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs index 251594fb..dca0ca52 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs @@ -1,185 +1,185 @@ -using System.Text.Json; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.Extensions.DependencyInjection; -using Thinktecture.EntityFrameworkCore.Infrastructure; -using Thinktecture.EntityFrameworkCore.Migrations; -using Thinktecture.EntityFrameworkCore.Query; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extensions for . -/// -public static class SqlServerDbContextOptionsBuilderExtensions -{ - /// - /// Adds support for bulk operations and temp tables. - /// - /// SQL Server options builder. - /// Indication whether to enable or disable the feature. - /// Indication whether to configure temp tables for primitive types. - /// Provided . - public static SqlServerDbContextOptionsBuilder AddBulkOperationSupport( - this SqlServerDbContextOptionsBuilder sqlServerOptionsBuilder, - bool addBulkOperationSupport = true, - bool configureTempTablesForPrimitiveTypes = true) - { - return AddOrUpdateExtension(sqlServerOptionsBuilder, extension => - { - extension.AddBulkOperationSupport = addBulkOperationSupport; - extension.ConfigureTempTablesForPrimitiveTypes = addBulkOperationSupport && configureTempTablesForPrimitiveTypes; - return extension; - }); - } - - /// - /// Adds support for queryable parameters. - /// - /// SQL Server options builder. - /// JSON serialization options. - /// Indication whether to enable or disable the feature. - /// Indication whether to configure collection parameters for primitive types. - /// - /// If true then the provided collection will be serialized when the query is executed. - /// If false then the collection is going to be serialized when "collection parameter" is created, - /// i.e. when calling pr . - /// Default is false. - /// - /// Provided . - public static SqlServerDbContextOptionsBuilder AddCollectionParameterSupport( - this SqlServerDbContextOptionsBuilder sqlServerOptionsBuilder, - JsonSerializerOptions? jsonSerializerOptions = null, - bool addCollectionParameterSupport = true, - bool configureCollectionParametersForPrimitiveTypes = true, - bool useDeferredSerialization = false) - { - return AddOrUpdateExtension(sqlServerOptionsBuilder, extension => extension.AddCollectionParameterSupport(addCollectionParameterSupport, jsonSerializerOptions, configureCollectionParametersForPrimitiveTypes, useDeferredSerialization)); - } - - /// - /// Adds custom factory required for translation of custom methods like . - /// - /// Options builder. - /// Indication whether to add a custom factory. - /// Provided . - public static SqlServerDbContextOptionsBuilder AddCustomQueryableMethodTranslatingExpressionVisitorFactory( - this SqlServerDbContextOptionsBuilder builder, - bool addCustomQueryableMethodTranslatingExpressionVisitorFactory = true) - { - builder.AddOrUpdateExtension(extension => - { - extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory = addCustomQueryableMethodTranslatingExpressionVisitorFactory; - return extension; - }); - return builder; - } - - /// - /// Adds support for "RowNumber". - /// - /// Options builder. - /// Indication whether to enable or disable the feature. - /// Provided . - [Obsolete($"Use '{nameof(AddWindowFunctionsSupport)}' instead.")] - public static SqlServerDbContextOptionsBuilder AddRowNumberSupport( - this SqlServerDbContextOptionsBuilder builder, - bool addRowNumberSupport = true) - { - builder.AddOrUpdateExtension(extension => - { - extension.AddWindowFunctionsSupport = addRowNumberSupport; - return extension; - }); - return builder; - } - - /// - /// Adds support for window functions like "RowNumber". - /// - /// Options builder. - /// Indication whether to enable or disable the feature. - /// Provided . - public static SqlServerDbContextOptionsBuilder AddWindowFunctionsSupport( - this SqlServerDbContextOptionsBuilder builder, - bool addWindowFunctionsSupport = true) - { - builder.AddOrUpdateExtension(extension => - { - extension.AddWindowFunctionsSupport = addWindowFunctionsSupport; - return extension; - }); - return builder; - } - - /// - /// Adds support for "Table Hints". - /// - /// Options builder. - /// Indication whether to enable or disable the feature. - /// Provided . - public static SqlServerDbContextOptionsBuilder AddTableHintSupport( - this SqlServerDbContextOptionsBuilder builder, - bool addTableHintSupport = true) - { - builder.AddOrUpdateExtension(extension => - { - extension.AddTableHintSupport = addTableHintSupport; - return extension; - }); - return builder; - } - - /// - /// Adds 'tenant database support'. - /// - /// Options builder. - /// Indication whether to enable or disable the feature. - /// The lifetime of the provided . - /// Provided . - public static SqlServerDbContextOptionsBuilder AddTenantDatabaseSupport( - this SqlServerDbContextOptionsBuilder builder, - bool addTenantSupport = true, - ServiceLifetime databaseProviderLifetime = ServiceLifetime.Singleton) - where TTenantDatabaseProviderFactory : ITenantDatabaseProviderFactory - { - builder.AddOrUpdateExtension(extension => - { - extension.AddTenantDatabaseSupport = addTenantSupport; - extension.Register(typeof(ITenantDatabaseProviderFactory), typeof(TTenantDatabaseProviderFactory), databaseProviderLifetime); - - return extension; - }); - return builder; - } - - /// - /// Changes the implementation of to . - /// - /// SQL Server options builder. - /// Indication whether to enable or disable the feature. - /// Provided . - public static SqlServerDbContextOptionsBuilder UseThinktectureSqlServerMigrationsSqlGenerator( - this SqlServerDbContextOptionsBuilder sqlServerOptionsBuilder, - bool useSqlGenerator = true) - { - return AddOrUpdateExtension(sqlServerOptionsBuilder, extension => - { - extension.UseThinktectureSqlServerMigrationsSqlGenerator = useSqlGenerator; - return extension; - }); - } - - private static SqlServerDbContextOptionsBuilder AddOrUpdateExtension(this SqlServerDbContextOptionsBuilder sqlServerOptionsBuilder, - Func callback) - { - ArgumentNullException.ThrowIfNull(sqlServerOptionsBuilder); - - var infrastructure = (IRelationalDbContextOptionsBuilderInfrastructure)sqlServerOptionsBuilder; - var relationalOptions = infrastructure.OptionsBuilder.TryAddExtension(); - infrastructure.OptionsBuilder.AddOrUpdateExtension(callback, () => new SqlServerDbContextOptionsExtension(relationalOptions)); - - return sqlServerOptionsBuilder; - } -} +using System.Text.Json; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.Extensions.DependencyInjection; +using Thinktecture.EntityFrameworkCore.Infrastructure; +using Thinktecture.EntityFrameworkCore.Migrations; +using Thinktecture.EntityFrameworkCore.Query; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extensions for . +/// +public static class SqlServerDbContextOptionsBuilderExtensions +{ + /// + /// Adds support for bulk operations and temp tables. + /// + /// SQL Server options builder. + /// Indication whether to enable or disable the feature. + /// Indication whether to configure temp tables for primitive types. + /// Provided . + public static SqlServerDbContextOptionsBuilder AddBulkOperationSupport( + this SqlServerDbContextOptionsBuilder sqlServerOptionsBuilder, + bool addBulkOperationSupport = true, + bool configureTempTablesForPrimitiveTypes = true) + { + return AddOrUpdateExtension(sqlServerOptionsBuilder, extension => + { + extension.AddBulkOperationSupport = addBulkOperationSupport; + extension.ConfigureTempTablesForPrimitiveTypes = addBulkOperationSupport && configureTempTablesForPrimitiveTypes; + return extension; + }); + } + + /// + /// Adds support for queryable parameters. + /// + /// SQL Server options builder. + /// JSON serialization options. + /// Indication whether to enable or disable the feature. + /// Indication whether to configure collection parameters for primitive types. + /// + /// If true then the provided collection will be serialized when the query is executed. + /// If false then the collection is going to be serialized when "collection parameter" is created, + /// i.e. when calling pr . + /// Default is false. + /// + /// Provided . + public static SqlServerDbContextOptionsBuilder AddCollectionParameterSupport( + this SqlServerDbContextOptionsBuilder sqlServerOptionsBuilder, + JsonSerializerOptions? jsonSerializerOptions = null, + bool addCollectionParameterSupport = true, + bool configureCollectionParametersForPrimitiveTypes = true, + bool useDeferredSerialization = false) + { + return AddOrUpdateExtension(sqlServerOptionsBuilder, extension => extension.AddCollectionParameterSupport(addCollectionParameterSupport, jsonSerializerOptions, configureCollectionParametersForPrimitiveTypes, useDeferredSerialization)); + } + + /// + /// Adds custom factory required for translation of custom methods like . + /// + /// Options builder. + /// Indication whether to add a custom factory. + /// Provided . + public static SqlServerDbContextOptionsBuilder AddCustomQueryableMethodTranslatingExpressionVisitorFactory( + this SqlServerDbContextOptionsBuilder builder, + bool addCustomQueryableMethodTranslatingExpressionVisitorFactory = true) + { + builder.AddOrUpdateExtension(extension => + { + extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory = addCustomQueryableMethodTranslatingExpressionVisitorFactory; + return extension; + }); + return builder; + } + + /// + /// Adds support for "RowNumber". + /// + /// Options builder. + /// Indication whether to enable or disable the feature. + /// Provided . + [Obsolete($"Use '{nameof(AddWindowFunctionsSupport)}' instead.")] + public static SqlServerDbContextOptionsBuilder AddRowNumberSupport( + this SqlServerDbContextOptionsBuilder builder, + bool addRowNumberSupport = true) + { + builder.AddOrUpdateExtension(extension => + { + extension.AddWindowFunctionsSupport = addRowNumberSupport; + return extension; + }); + return builder; + } + + /// + /// Adds support for window functions like "RowNumber". + /// + /// Options builder. + /// Indication whether to enable or disable the feature. + /// Provided . + public static SqlServerDbContextOptionsBuilder AddWindowFunctionsSupport( + this SqlServerDbContextOptionsBuilder builder, + bool addWindowFunctionsSupport = true) + { + builder.AddOrUpdateExtension(extension => + { + extension.AddWindowFunctionsSupport = addWindowFunctionsSupport; + return extension; + }); + return builder; + } + + /// + /// Adds support for "Table Hints". + /// + /// Options builder. + /// Indication whether to enable or disable the feature. + /// Provided . + public static SqlServerDbContextOptionsBuilder AddTableHintSupport( + this SqlServerDbContextOptionsBuilder builder, + bool addTableHintSupport = true) + { + builder.AddOrUpdateExtension(extension => + { + extension.AddTableHintSupport = addTableHintSupport; + return extension; + }); + return builder; + } + + /// + /// Adds 'tenant database support'. + /// + /// Options builder. + /// Indication whether to enable or disable the feature. + /// The lifetime of the provided . + /// Provided . + public static SqlServerDbContextOptionsBuilder AddTenantDatabaseSupport( + this SqlServerDbContextOptionsBuilder builder, + bool addTenantSupport = true, + ServiceLifetime databaseProviderLifetime = ServiceLifetime.Singleton) + where TTenantDatabaseProviderFactory : ITenantDatabaseProviderFactory + { + builder.AddOrUpdateExtension(extension => + { + extension.AddTenantDatabaseSupport = addTenantSupport; + extension.Register(typeof(ITenantDatabaseProviderFactory), typeof(TTenantDatabaseProviderFactory), databaseProviderLifetime); + + return extension; + }); + return builder; + } + + /// + /// Changes the implementation of to . + /// + /// SQL Server options builder. + /// Indication whether to enable or disable the feature. + /// Provided . + public static SqlServerDbContextOptionsBuilder UseThinktectureSqlServerMigrationsSqlGenerator( + this SqlServerDbContextOptionsBuilder sqlServerOptionsBuilder, + bool useSqlGenerator = true) + { + return AddOrUpdateExtension(sqlServerOptionsBuilder, extension => + { + extension.UseThinktectureSqlServerMigrationsSqlGenerator = useSqlGenerator; + return extension; + }); + } + + private static SqlServerDbContextOptionsBuilder AddOrUpdateExtension(this SqlServerDbContextOptionsBuilder sqlServerOptionsBuilder, + Func callback) + { + ArgumentNullException.ThrowIfNull(sqlServerOptionsBuilder); + + var infrastructure = (IRelationalDbContextOptionsBuilderInfrastructure)sqlServerOptionsBuilder; + var relationalOptions = infrastructure.OptionsBuilder.TryAddExtension(); + infrastructure.OptionsBuilder.AddOrUpdateExtension(callback, () => new SqlServerDbContextOptionsExtension(relationalOptions)); + + return sqlServerOptionsBuilder; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerMigrationOperationExtensions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerMigrationOperationExtensions.cs index 2779673e..b0a28dd9 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerMigrationOperationExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerMigrationOperationExtensions.cs @@ -1,45 +1,45 @@ -using Microsoft.EntityFrameworkCore.Migrations.Operations; -using Thinktecture.EntityFrameworkCore.Migrations; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extensions for . -/// -public static class SqlServerMigrationOperationExtensions -{ - /// - /// Gets an indication whether the "IfNotExists" check is required. - /// - /// - /// The must be used so the annotations have some effect! - /// Use the extension method "" to change the Migration SQL generator. - /// - /// Operation to check for the flag. - /// true if the check is required; otherwise false. - /// is null. - public static bool IfNotExistsCheckRequired(this MigrationOperation operation) - { - ArgumentNullException.ThrowIfNull(operation); - - return operation[SqlServerOperationBuilderExtensions.IfNotExistsKey] is bool ifNotExists && ifNotExists; - } - - /// - /// Gets an indication whether the "IfExists" check is required. - /// - /// - /// The must be used so the annotations have some effect! - /// Use the extension method "" to change the Migration SQL generator. - /// - /// Operation to check for the flag. - /// true if the check is required; otherwise false. - /// is null. - public static bool IfExistsCheckRequired(this MigrationOperation operation) - { - ArgumentNullException.ThrowIfNull(operation); - - return operation[SqlServerOperationBuilderExtensions.IfExistsKey] is bool ifNotExists && ifNotExists; - } -} +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Thinktecture.EntityFrameworkCore.Migrations; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extensions for . +/// +public static class SqlServerMigrationOperationExtensions +{ + /// + /// Gets an indication whether the "IfNotExists" check is required. + /// + /// + /// The must be used so the annotations have some effect! + /// Use the extension method "" to change the Migration SQL generator. + /// + /// Operation to check for the flag. + /// true if the check is required; otherwise false. + /// is null. + public static bool IfNotExistsCheckRequired(this MigrationOperation operation) + { + ArgumentNullException.ThrowIfNull(operation); + + return operation[SqlServerOperationBuilderExtensions.IfNotExistsKey] is bool ifNotExists && ifNotExists; + } + + /// + /// Gets an indication whether the "IfExists" check is required. + /// + /// + /// The must be used so the annotations have some effect! + /// Use the extension method "" to change the Migration SQL generator. + /// + /// Operation to check for the flag. + /// true if the check is required; otherwise false. + /// is null. + public static bool IfExistsCheckRequired(this MigrationOperation operation) + { + ArgumentNullException.ThrowIfNull(operation); + + return operation[SqlServerOperationBuilderExtensions.IfExistsKey] is bool ifNotExists && ifNotExists; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerOperationBuilderExtensions.cs b/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerOperationBuilderExtensions.cs index a153faa8..40efb41c 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerOperationBuilderExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/Extensions/SqlServerOperationBuilderExtensions.cs @@ -1,115 +1,115 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations.Operations; -using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; -using Thinktecture.EntityFrameworkCore.Migrations; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extensions for . -/// -public static class SqlServerOperationBuilderExtensions -{ - /// - /// Annotation key for "IfNotExists". - /// - // ReSharper disable once ConvertToConstant.Global - public static readonly string IfNotExistsKey = "Thinktecture:OperationBuilderExtensions:IfNotExists"; - - /// - /// Annotation key for "IfExists". - /// - // ReSharper disable once ConvertToConstant.Global - public static readonly string IfExistsKey = "Thinktecture:OperationBuilderExtensions:IfExists"; - - /// - /// Flags the migration that it should be executed if corresponding entity (table, index, etc.) does not exist yet. - /// - /// - /// The must be used so the annotations have some effect! - /// Use the extension method "" to change the Migration SQL generator. - /// - /// An operation builder. - /// Type of the migration operation. - /// Operation builder is null. - public static void IfNotExists(this OperationBuilder builder) - where T : MigrationOperation - { - ArgumentNullException.ThrowIfNull(builder); - - builder.Annotation(IfNotExistsKey, true); - } - - /// - /// Flags the migration that it should be executed if corresponding entity (table, index, etc.) exists. - /// - /// - /// The must be used so the annotations have some effect! - /// Use the extension method "" to change the Migration SQL generator. - /// - /// An operation builder. - /// Type of the migration operation. - /// Operation builder is null. - public static void IfExists(this OperationBuilder builder) - where T : MigrationOperation - { - ArgumentNullException.ThrowIfNull(builder); - - builder.Annotation(IfExistsKey, true); - } - - /// - /// Specifies "include columns" - /// - /// Operation to specify the include columns for. - /// Names of the columns. - /// The provided . - /// - /// is null. - /// - or collection is null. - /// - /// collection is empty. - public static OperationBuilder IncludeColumns(this OperationBuilder operation, params string[] columns) - { - ArgumentNullException.ThrowIfNull(operation); - ArgumentNullException.ThrowIfNull(columns); - if (columns.Length == 0) - throw new ArgumentException("There must be at least one column in provided collection.", nameof(columns)); - - operation.Annotation("SqlServer:Include", columns); - - return operation; - } - - /// - /// Specifies the column as an "identity column". - /// - /// Operation - /// The provided . - /// The provided is null. - public static OperationBuilder AsIdentityColumn(this OperationBuilder operation) - { - ArgumentNullException.ThrowIfNull(operation); - - operation.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - return operation; - } - - /// - /// Sets the PK as clustered/non-clustered. - /// - /// Operation - /// Indication whether the PK should be clustered or not. - /// The provided . - /// The is null. - public static OperationBuilder IsClustered(this OperationBuilder operation, bool isClustered = true) - { - ArgumentNullException.ThrowIfNull(operation); - - operation.Annotation("SqlServer:Clustered", isClustered); - - return operation; - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders; +using Thinktecture.EntityFrameworkCore.Migrations; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extensions for . +/// +public static class SqlServerOperationBuilderExtensions +{ + /// + /// Annotation key for "IfNotExists". + /// + // ReSharper disable once ConvertToConstant.Global + public static readonly string IfNotExistsKey = "Thinktecture:OperationBuilderExtensions:IfNotExists"; + + /// + /// Annotation key for "IfExists". + /// + // ReSharper disable once ConvertToConstant.Global + public static readonly string IfExistsKey = "Thinktecture:OperationBuilderExtensions:IfExists"; + + /// + /// Flags the migration that it should be executed if corresponding entity (table, index, etc.) does not exist yet. + /// + /// + /// The must be used so the annotations have some effect! + /// Use the extension method "" to change the Migration SQL generator. + /// + /// An operation builder. + /// Type of the migration operation. + /// Operation builder is null. + public static void IfNotExists(this OperationBuilder builder) + where T : MigrationOperation + { + ArgumentNullException.ThrowIfNull(builder); + + builder.Annotation(IfNotExistsKey, true); + } + + /// + /// Flags the migration that it should be executed if corresponding entity (table, index, etc.) exists. + /// + /// + /// The must be used so the annotations have some effect! + /// Use the extension method "" to change the Migration SQL generator. + /// + /// An operation builder. + /// Type of the migration operation. + /// Operation builder is null. + public static void IfExists(this OperationBuilder builder) + where T : MigrationOperation + { + ArgumentNullException.ThrowIfNull(builder); + + builder.Annotation(IfExistsKey, true); + } + + /// + /// Specifies "include columns" + /// + /// Operation to specify the include columns for. + /// Names of the columns. + /// The provided . + /// + /// is null. + /// - or collection is null. + /// + /// collection is empty. + public static OperationBuilder IncludeColumns(this OperationBuilder operation, params string[] columns) + { + ArgumentNullException.ThrowIfNull(operation); + ArgumentNullException.ThrowIfNull(columns); + if (columns.Length == 0) + throw new ArgumentException("There must be at least one column in provided collection.", nameof(columns)); + + operation.Annotation("SqlServer:Include", columns); + + return operation; + } + + /// + /// Specifies the column as an "identity column". + /// + /// Operation + /// The provided . + /// The provided is null. + public static OperationBuilder AsIdentityColumn(this OperationBuilder operation) + { + ArgumentNullException.ThrowIfNull(operation); + + operation.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + return operation; + } + + /// + /// Sets the PK as clustered/non-clustered. + /// + /// Operation + /// Indication whether the PK should be clustered or not. + /// The provided . + /// The is null. + public static OperationBuilder IsClustered(this OperationBuilder operation, bool isClustered = true) + { + ArgumentNullException.ThrowIfNull(operation); + + operation.Annotation("SqlServer:Clustered", isClustered); + + return operation; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.SqlServer/Thinktecture.EntityFrameworkCore.SqlServer.csproj b/src/Thinktecture.EntityFrameworkCore.SqlServer/Thinktecture.EntityFrameworkCore.SqlServer.csproj index 51b30b63..3c97755e 100644 --- a/src/Thinktecture.EntityFrameworkCore.SqlServer/Thinktecture.EntityFrameworkCore.SqlServer.csproj +++ b/src/Thinktecture.EntityFrameworkCore.SqlServer/Thinktecture.EntityFrameworkCore.SqlServer.csproj @@ -1,12 +1,12 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProviderOptions.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProviderOptions.cs index 5756aa53..ced14606 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProviderOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqlServerTestDbContextProviderOptions.cs @@ -1,43 +1,43 @@ -using Microsoft.Data.Sqlite; -using Thinktecture.Logging; - -namespace Thinktecture.EntityFrameworkCore.Testing; - -/// -/// Options for the . -/// -/// -public class SqliteTestDbContextProviderOptions : TestDbContextProviderOptions - where T : DbContext -{ - /// - /// Master database connection. - /// - public new SqliteConnection MasterConnection => (SqliteConnection)base.MasterConnection; - - /// - /// Indication whether the master connection is externally owned. - /// - public bool IsExternallyOwnedMasterConnection { get; } - - /// - /// A factory method for creation of contexts of type . - /// - public Func, T?>? ContextFactory { get; init; } - - /// - /// Initializes new instance of - /// - public SqliteTestDbContextProviderOptions( - SqliteConnection masterConnection, - bool isExternallyOwnedMasterConnection, - IMigrationExecutionStrategy migrationExecutionStrategy, - DbContextOptionsBuilder masterDbContextOptionsBuilder, - DbContextOptionsBuilder dbContextOptionsBuilder, - TestingLoggingOptions testingLoggingOptions, - IReadOnlyList> contextInitializations) - : base(masterConnection, migrationExecutionStrategy, masterDbContextOptionsBuilder, dbContextOptionsBuilder, testingLoggingOptions, contextInitializations) - { - IsExternallyOwnedMasterConnection = isExternallyOwnedMasterConnection; - } -} +using Microsoft.Data.Sqlite; +using Thinktecture.Logging; + +namespace Thinktecture.EntityFrameworkCore.Testing; + +/// +/// Options for the . +/// +/// +public class SqliteTestDbContextProviderOptions : TestDbContextProviderOptions + where T : DbContext +{ + /// + /// Master database connection. + /// + public new SqliteConnection MasterConnection => (SqliteConnection)base.MasterConnection; + + /// + /// Indication whether the master connection is externally owned. + /// + public bool IsExternallyOwnedMasterConnection { get; } + + /// + /// A factory method for creation of contexts of type . + /// + public Func, T?>? ContextFactory { get; init; } + + /// + /// Initializes new instance of + /// + public SqliteTestDbContextProviderOptions( + SqliteConnection masterConnection, + bool isExternallyOwnedMasterConnection, + IMigrationExecutionStrategy migrationExecutionStrategy, + DbContextOptionsBuilder masterDbContextOptionsBuilder, + DbContextOptionsBuilder dbContextOptionsBuilder, + TestingLoggingOptions testingLoggingOptions, + IReadOnlyList> contextInitializations) + : base(masterConnection, migrationExecutionStrategy, masterDbContextOptionsBuilder, dbContextOptionsBuilder, testingLoggingOptions, contextInitializations) + { + IsExternallyOwnedMasterConnection = isExternallyOwnedMasterConnection; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteDbContextIntegrationTests.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteDbContextIntegrationTests.cs index 22257b5e..736a66c5 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteDbContextIntegrationTests.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteDbContextIntegrationTests.cs @@ -1,141 +1,141 @@ -using Xunit; -using Xunit.Abstractions; - -namespace Thinktecture.EntityFrameworkCore.Testing; - -/// -/// A base class for integration tests using EF Core along with SQLite. -/// -/// Type of the database context. -// ReSharper disable once UnusedMember.Global -public abstract class SqliteDbContextIntegrationTests : ITestDbContextProvider, IAsyncLifetime - where T : DbContext -{ - private SqliteTestDbContextProvider? _testCtxProvider; - - /// - /// Gets the which is created on the first access. - /// - protected SqliteTestDbContextProvider TestCtxProvider => _testCtxProvider ??= TestCtxProviderBuilder.Build(); - - private bool _isProviderConfigured; - private readonly SqliteTestDbContextProviderBuilder _testCtxProviderBuilder; - private bool _isDisposed; - - /// - /// Gets the which is created on the first access. - /// - protected SqliteTestDbContextProviderBuilder TestCtxProviderBuilder - { - get - { - if (!_isProviderConfigured) - { - ConfigureTestDbContextProvider(_testCtxProviderBuilder); - _isProviderConfigured = true; - } - - return _testCtxProviderBuilder; - } - } - - /// - public T ArrangeDbContext => TestCtxProvider.ArrangeDbContext; - - /// - public T ActDbContext => TestCtxProvider.ActDbContext; - - /// - public T AssertDbContext => TestCtxProvider.AssertDbContext; - - /// - /// Initializes a new instance of - /// - /// Output helper to use for logging. - protected SqliteDbContextIntegrationTests(ITestOutputHelper? testOutputHelper = null) - { - _testCtxProviderBuilder = new SqliteTestDbContextProviderBuilder() - .UseLogging(testOutputHelper); - } - - /// - /// Allows further configuration of the . - /// - /// Builder for further configuration of . - protected virtual void ConfigureTestDbContextProvider(SqliteTestDbContextProviderBuilder builder) - { - } - - /// - public T CreateDbContext() - { - return TestCtxProvider.CreateDbContext(); - } - - /// - public T CreateDbContext(bool useMasterConnection) - { - return TestCtxProvider.CreateDbContext(useMasterConnection); - } - - /// - public virtual Task InitializeAsync() - { - return Task.CompletedTask; - } - - /// - /// Rollbacks transaction if shared tables are used - /// otherwise the migrations are rolled back and all tables, functions, views and the newly generated schema are deleted. - /// - public void Dispose() - { - if (_isDisposed) - return; - - _isDisposed = true; - - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - async Task IAsyncLifetime.DisposeAsync() - { - await DisposeAsync(); - } - - /// - public async ValueTask DisposeAsync() - { - if (_isDisposed) - return; - - _isDisposed = true; - - await DisposeAsync(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes of inner resources. - /// - /// Indication whether this method is being called by the method . - protected virtual void Dispose(bool disposing) - { - DisposeAsync(disposing).AsTask().GetAwaiter().GetResult(); - } - - /// - /// Disposes of inner resources. - /// - /// Indication whether this method is being called by the method . - protected virtual async ValueTask DisposeAsync(bool disposing) - { - if (!disposing) - return; - - await (_testCtxProvider?.DisposeAsync() ?? ValueTask.CompletedTask); - _testCtxProvider = null; - } -} +using Xunit; +using Xunit.Abstractions; + +namespace Thinktecture.EntityFrameworkCore.Testing; + +/// +/// A base class for integration tests using EF Core along with SQLite. +/// +/// Type of the database context. +// ReSharper disable once UnusedMember.Global +public abstract class SqliteDbContextIntegrationTests : ITestDbContextProvider, IAsyncLifetime + where T : DbContext +{ + private SqliteTestDbContextProvider? _testCtxProvider; + + /// + /// Gets the which is created on the first access. + /// + protected SqliteTestDbContextProvider TestCtxProvider => _testCtxProvider ??= TestCtxProviderBuilder.Build(); + + private bool _isProviderConfigured; + private readonly SqliteTestDbContextProviderBuilder _testCtxProviderBuilder; + private bool _isDisposed; + + /// + /// Gets the which is created on the first access. + /// + protected SqliteTestDbContextProviderBuilder TestCtxProviderBuilder + { + get + { + if (!_isProviderConfigured) + { + ConfigureTestDbContextProvider(_testCtxProviderBuilder); + _isProviderConfigured = true; + } + + return _testCtxProviderBuilder; + } + } + + /// + public T ArrangeDbContext => TestCtxProvider.ArrangeDbContext; + + /// + public T ActDbContext => TestCtxProvider.ActDbContext; + + /// + public T AssertDbContext => TestCtxProvider.AssertDbContext; + + /// + /// Initializes a new instance of + /// + /// Output helper to use for logging. + protected SqliteDbContextIntegrationTests(ITestOutputHelper? testOutputHelper = null) + { + _testCtxProviderBuilder = new SqliteTestDbContextProviderBuilder() + .UseLogging(testOutputHelper); + } + + /// + /// Allows further configuration of the . + /// + /// Builder for further configuration of . + protected virtual void ConfigureTestDbContextProvider(SqliteTestDbContextProviderBuilder builder) + { + } + + /// + public T CreateDbContext() + { + return TestCtxProvider.CreateDbContext(); + } + + /// + public T CreateDbContext(bool useMasterConnection) + { + return TestCtxProvider.CreateDbContext(useMasterConnection); + } + + /// + public virtual Task InitializeAsync() + { + return Task.CompletedTask; + } + + /// + /// Rollbacks transaction if shared tables are used + /// otherwise the migrations are rolled back and all tables, functions, views and the newly generated schema are deleted. + /// + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + async Task IAsyncLifetime.DisposeAsync() + { + await DisposeAsync(); + } + + /// + public async ValueTask DisposeAsync() + { + if (_isDisposed) + return; + + _isDisposed = true; + + await DisposeAsync(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes of inner resources. + /// + /// Indication whether this method is being called by the method . + protected virtual void Dispose(bool disposing) + { + DisposeAsync(disposing).AsTask().GetAwaiter().GetResult(); + } + + /// + /// Disposes of inner resources. + /// + /// Indication whether this method is being called by the method . + protected virtual async ValueTask DisposeAsync(bool disposing) + { + if (!disposing) + return; + + await (_testCtxProvider?.DisposeAsync() ?? ValueTask.CompletedTask); + _testCtxProvider = null; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteTestDbContextProvider.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteTestDbContextProvider.cs index 593838d0..5e061bb7 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteTestDbContextProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteTestDbContextProvider.cs @@ -1,235 +1,235 @@ -using System.Data; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Thinktecture.Logging; - -namespace Thinktecture.EntityFrameworkCore.Testing; - -/// -/// Provides instances of for testing purposes. -/// -/// Type of the database context. -public class SqliteTestDbContextProvider : ITestDbContextProvider - where T : DbContext -{ - private readonly object _lock = new(); - - private readonly DbContextOptions _masterDbContextOptions; - private readonly DbContextOptions _dbContextOptions; - private readonly IMigrationExecutionStrategy _migrationExecutionStrategy; - private readonly bool _isExternallyOwnedMasterConnection; - private readonly IReadOnlyList> _contextInitializations; - private readonly Func, T?>? _contextFactory; - - private T? _arrangeDbContext; - private T? _actDbContext; - private T? _assertDbContext; - private bool _isAtLeastOneContextCreated; - private readonly TestingLoggingOptions _testingLoggingOptions; - private bool _isDisposed; - - /// - /// A database connection which kept open during the test, so the in-memory database is not deleted. - /// - public SqliteConnection MasterConnection { get; } - - /// - public T ArrangeDbContext => _arrangeDbContext ??= CreateDbContext(true); - - /// - public T ActDbContext => _actDbContext ??= CreateDbContext(true); - - /// - public T AssertDbContext => _assertDbContext ??= CreateDbContext(true); - - /// - /// Contains executed commands if this feature was activated. - /// - public IReadOnlyCollection? ExecutedCommands { get; } - - /// - /// Log level switch. - /// - public TestingLogLevelSwitch LogLevelSwitch => _testingLoggingOptions.LogLevelSwitch; - - /// - /// The connection string. - /// - public string ConnectionString => MasterConnection.ConnectionString; - - /// - /// Initializes a new instance of - /// - /// Options. - protected internal SqliteTestDbContextProvider(SqliteTestDbContextProviderOptions options) - { - ArgumentNullException.ThrowIfNull(options); - - MasterConnection = options.MasterConnection ?? throw new ArgumentException($"The '{nameof(options.MasterConnection)}' cannot be null.", nameof(options)); - _isExternallyOwnedMasterConnection = options.IsExternallyOwnedMasterConnection; - _masterDbContextOptions = options.MasterDbContextOptionsBuilder.Options ?? throw new ArgumentException($"The '{nameof(options.MasterDbContextOptionsBuilder)}' cannot be null.", nameof(options)); - _dbContextOptions = options.DbContextOptionsBuilder.Options ?? throw new ArgumentException($"The '{nameof(options.DbContextOptionsBuilder)}' cannot be null.", nameof(options)); - _migrationExecutionStrategy = options.MigrationExecutionStrategy ?? throw new ArgumentException($"The '{nameof(options.MigrationExecutionStrategy)}' cannot be null.", nameof(options)); - _testingLoggingOptions = options.TestingLoggingOptions ?? throw new ArgumentException($"The '{nameof(options.TestingLoggingOptions)}' cannot be null.", nameof(options)); - _contextInitializations = options.ContextInitializations ?? throw new ArgumentException($"The '{nameof(options.ContextInitializations)}' cannot be null.", nameof(options)); - ExecutedCommands = options.ExecutedCommands; - _contextFactory = options.ContextFactory; - } - - /// - public T CreateDbContext() - { - return CreateDbContext(false); - } - - /// - public T CreateDbContext(bool useMasterConnection) - { - bool isFirstCtx; - - lock (_lock) - { - isFirstCtx = !_isAtLeastOneContextCreated; - _isAtLeastOneContextCreated = true; - } - - var options = useMasterConnection ? _masterDbContextOptions : _dbContextOptions; - var ctx = CreateDbContext(options); - - foreach (var ctxInit in _contextInitializations) - { - ctxInit(ctx); - } - - if (isFirstCtx) - { - if (MasterConnection.State != ConnectionState.Open) - { - if (_isExternallyOwnedMasterConnection) - throw new InvalidOperationException("Externally owned connections must be opened by the owner."); - - MasterConnection.Open(); - } - - RunMigrations(ctx); - } - - return ctx; - } - - /// - /// Creates a new instance of the database context. - /// - /// Options to use for creation. - /// A new instance of the database context. - protected virtual T CreateDbContext(DbContextOptions options) - { - var ctx = _contextFactory?.Invoke(options); - - if (ctx is not null) - return ctx; - - ctx = (T)(Activator.CreateInstance(typeof(T), options) - ?? throw new Exception($""" - Could not create an instance of type of '{typeof(T).ShortDisplayName()}' using constructor parameters ({typeof(DbContextOptions).ShortDisplayName()} options). - Please provide the corresponding constructor or a custom factory via '{typeof(SqliteTestDbContextProviderBuilder).ShortDisplayName()}.{nameof(SqliteTestDbContextProviderBuilder.UseContextFactory)}'. - """)); - - return ctx; - } - - /// - /// Runs migrations for provided . - /// - /// Database context to run migrations for. - /// The provided context is null. - protected virtual void RunMigrations(T ctx) - { - ArgumentNullException.ThrowIfNull(ctx); - - // concurrent execution is not supported by EF migrations - lock (_lock) - { - var logLevel = LogLevelSwitch.MinimumLogLevel; - - try - { - LogLevelSwitch.MinimumLogLevel = _testingLoggingOptions.MigrationLogLevel; - - _migrationExecutionStrategy.Migrate(ctx); - } - finally - { - LogLevelSwitch.MinimumLogLevel = logLevel; - } - } - } - - /// - /// Rollbacks transaction if shared tables are used - /// otherwise the migrations are rolled back and all tables, functions, views and the newly generated schema are deleted. - /// - public void Dispose() - { - if (_isDisposed) - return; - - _isDisposed = true; - - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - public async ValueTask DisposeAsync() - { - if (_isDisposed) - return; - - _isDisposed = true; - - await DisposeAsync(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes of inner resources. - /// - /// Indication whether this method is being called by the method . - protected virtual void Dispose(bool disposing) - { - DisposeAsync(disposing).AsTask().ConfigureAwait(false).GetAwaiter().GetResult(); - } - - /// - /// Disposes of inner resources. - /// - /// Indication whether this method is being called by the method . - protected virtual async ValueTask DisposeAsync(bool disposing) - { - if (!disposing) - return; - - if (_isAtLeastOneContextCreated) - { - await DisposeContextsAsync(); - _isAtLeastOneContextCreated = false; - } - - if (!_isExternallyOwnedMasterConnection) - await MasterConnection.DisposeAsync(); - - _testingLoggingOptions.Dispose(); - } - - private async ValueTask DisposeContextsAsync() - { - await (_arrangeDbContext?.DisposeAsync() ?? ValueTask.CompletedTask); - await (_actDbContext?.DisposeAsync() ?? ValueTask.CompletedTask); - await (_assertDbContext?.DisposeAsync() ?? ValueTask.CompletedTask); - - _arrangeDbContext = null; - _actDbContext = null; - _assertDbContext = null; - } -} +using System.Data; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Thinktecture.Logging; + +namespace Thinktecture.EntityFrameworkCore.Testing; + +/// +/// Provides instances of for testing purposes. +/// +/// Type of the database context. +public class SqliteTestDbContextProvider : ITestDbContextProvider + where T : DbContext +{ + private readonly object _lock = new(); + + private readonly DbContextOptions _masterDbContextOptions; + private readonly DbContextOptions _dbContextOptions; + private readonly IMigrationExecutionStrategy _migrationExecutionStrategy; + private readonly bool _isExternallyOwnedMasterConnection; + private readonly IReadOnlyList> _contextInitializations; + private readonly Func, T?>? _contextFactory; + + private T? _arrangeDbContext; + private T? _actDbContext; + private T? _assertDbContext; + private bool _isAtLeastOneContextCreated; + private readonly TestingLoggingOptions _testingLoggingOptions; + private bool _isDisposed; + + /// + /// A database connection which kept open during the test, so the in-memory database is not deleted. + /// + public SqliteConnection MasterConnection { get; } + + /// + public T ArrangeDbContext => _arrangeDbContext ??= CreateDbContext(true); + + /// + public T ActDbContext => _actDbContext ??= CreateDbContext(true); + + /// + public T AssertDbContext => _assertDbContext ??= CreateDbContext(true); + + /// + /// Contains executed commands if this feature was activated. + /// + public IReadOnlyCollection? ExecutedCommands { get; } + + /// + /// Log level switch. + /// + public TestingLogLevelSwitch LogLevelSwitch => _testingLoggingOptions.LogLevelSwitch; + + /// + /// The connection string. + /// + public string ConnectionString => MasterConnection.ConnectionString; + + /// + /// Initializes a new instance of + /// + /// Options. + protected internal SqliteTestDbContextProvider(SqliteTestDbContextProviderOptions options) + { + ArgumentNullException.ThrowIfNull(options); + + MasterConnection = options.MasterConnection ?? throw new ArgumentException($"The '{nameof(options.MasterConnection)}' cannot be null.", nameof(options)); + _isExternallyOwnedMasterConnection = options.IsExternallyOwnedMasterConnection; + _masterDbContextOptions = options.MasterDbContextOptionsBuilder.Options ?? throw new ArgumentException($"The '{nameof(options.MasterDbContextOptionsBuilder)}' cannot be null.", nameof(options)); + _dbContextOptions = options.DbContextOptionsBuilder.Options ?? throw new ArgumentException($"The '{nameof(options.DbContextOptionsBuilder)}' cannot be null.", nameof(options)); + _migrationExecutionStrategy = options.MigrationExecutionStrategy ?? throw new ArgumentException($"The '{nameof(options.MigrationExecutionStrategy)}' cannot be null.", nameof(options)); + _testingLoggingOptions = options.TestingLoggingOptions ?? throw new ArgumentException($"The '{nameof(options.TestingLoggingOptions)}' cannot be null.", nameof(options)); + _contextInitializations = options.ContextInitializations ?? throw new ArgumentException($"The '{nameof(options.ContextInitializations)}' cannot be null.", nameof(options)); + ExecutedCommands = options.ExecutedCommands; + _contextFactory = options.ContextFactory; + } + + /// + public T CreateDbContext() + { + return CreateDbContext(false); + } + + /// + public T CreateDbContext(bool useMasterConnection) + { + bool isFirstCtx; + + lock (_lock) + { + isFirstCtx = !_isAtLeastOneContextCreated; + _isAtLeastOneContextCreated = true; + } + + var options = useMasterConnection ? _masterDbContextOptions : _dbContextOptions; + var ctx = CreateDbContext(options); + + foreach (var ctxInit in _contextInitializations) + { + ctxInit(ctx); + } + + if (isFirstCtx) + { + if (MasterConnection.State != ConnectionState.Open) + { + if (_isExternallyOwnedMasterConnection) + throw new InvalidOperationException("Externally owned connections must be opened by the owner."); + + MasterConnection.Open(); + } + + RunMigrations(ctx); + } + + return ctx; + } + + /// + /// Creates a new instance of the database context. + /// + /// Options to use for creation. + /// A new instance of the database context. + protected virtual T CreateDbContext(DbContextOptions options) + { + var ctx = _contextFactory?.Invoke(options); + + if (ctx is not null) + return ctx; + + ctx = (T)(Activator.CreateInstance(typeof(T), options) + ?? throw new Exception($""" + Could not create an instance of type of '{typeof(T).ShortDisplayName()}' using constructor parameters ({typeof(DbContextOptions).ShortDisplayName()} options). + Please provide the corresponding constructor or a custom factory via '{typeof(SqliteTestDbContextProviderBuilder).ShortDisplayName()}.{nameof(SqliteTestDbContextProviderBuilder.UseContextFactory)}'. + """)); + + return ctx; + } + + /// + /// Runs migrations for provided . + /// + /// Database context to run migrations for. + /// The provided context is null. + protected virtual void RunMigrations(T ctx) + { + ArgumentNullException.ThrowIfNull(ctx); + + // concurrent execution is not supported by EF migrations + lock (_lock) + { + var logLevel = LogLevelSwitch.MinimumLogLevel; + + try + { + LogLevelSwitch.MinimumLogLevel = _testingLoggingOptions.MigrationLogLevel; + + _migrationExecutionStrategy.Migrate(ctx); + } + finally + { + LogLevelSwitch.MinimumLogLevel = logLevel; + } + } + } + + /// + /// Rollbacks transaction if shared tables are used + /// otherwise the migrations are rolled back and all tables, functions, views and the newly generated schema are deleted. + /// + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + public async ValueTask DisposeAsync() + { + if (_isDisposed) + return; + + _isDisposed = true; + + await DisposeAsync(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes of inner resources. + /// + /// Indication whether this method is being called by the method . + protected virtual void Dispose(bool disposing) + { + DisposeAsync(disposing).AsTask().ConfigureAwait(false).GetAwaiter().GetResult(); + } + + /// + /// Disposes of inner resources. + /// + /// Indication whether this method is being called by the method . + protected virtual async ValueTask DisposeAsync(bool disposing) + { + if (!disposing) + return; + + if (_isAtLeastOneContextCreated) + { + await DisposeContextsAsync(); + _isAtLeastOneContextCreated = false; + } + + if (!_isExternallyOwnedMasterConnection) + await MasterConnection.DisposeAsync(); + + _testingLoggingOptions.Dispose(); + } + + private async ValueTask DisposeContextsAsync() + { + await (_arrangeDbContext?.DisposeAsync() ?? ValueTask.CompletedTask); + await (_actDbContext?.DisposeAsync() ?? ValueTask.CompletedTask); + await (_assertDbContext?.DisposeAsync() ?? ValueTask.CompletedTask); + + _arrangeDbContext = null; + _actDbContext = null; + _assertDbContext = null; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteTestDbContextProviderBuilder.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteTestDbContextProviderBuilder.cs index 2e169ed2..241f681d 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteTestDbContextProviderBuilder.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteTestDbContextProviderBuilder.cs @@ -1,310 +1,310 @@ -using System.Data.Common; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.Extensions.Logging; -using Thinktecture.Logging; -using Xunit.Abstractions; - -namespace Thinktecture.EntityFrameworkCore.Testing; - -/// -/// Builder for the . -/// -/// Type of the . -public class SqliteTestDbContextProviderBuilder : TestDbContextProviderBuilder - where T : DbContext -{ - private readonly List>> _configuresOptionsCollection; - private readonly List> _configuresSqliteOptionsCollection; - private readonly List> _ctxInitializations; - - private Func, T?>? _contextFactory; - private Func, SqliteTestDbContextProvider?>? _providerFactory; - - /// - /// Initializes new instance of . - /// - public SqliteTestDbContextProviderBuilder() - { - _configuresOptionsCollection = new List>>(); - _configuresSqliteOptionsCollection = new List>(); - _ctxInitializations = new List>(); - } - - /// - /// Specifies the migration strategy to use. - /// Default is . - /// - /// Migration strategy to use. - /// Current builder for chaining - public new SqliteTestDbContextProviderBuilder UseMigrationExecutionStrategy(IMigrationExecutionStrategy migrationExecutionStrategy) - { - base.UseMigrationExecutionStrategy(migrationExecutionStrategy); - - return this; - } - - /// - /// Sets the logger factory to be used by EF. - /// - /// Logger factory to use. - /// Enables or disables sensitive data logging. - /// Current builder for chaining. - public new SqliteTestDbContextProviderBuilder UseLogging( - ILoggerFactory? loggerFactory, - bool enableSensitiveDataLogging = true) - { - base.UseLogging(loggerFactory, enableSensitiveDataLogging); - - return this; - } - - /// - /// Sets output helper to be used by Serilog which is passed to EF. - /// - /// XUnit output. - /// Enables or disables sensitive data logging. - /// The serilog output template. - /// Current builder for chaining. - public new SqliteTestDbContextProviderBuilder UseLogging( - ITestOutputHelper? testOutputHelper, - bool enableSensitiveDataLogging = true, - string? outputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}") - { - base.UseLogging(testOutputHelper, enableSensitiveDataLogging, outputTemplate); - - return this; - } - - /// - /// Sets the log level during migrations. - /// - /// Minimum log level to use during migrations. - /// Current builder for chaining. - public new SqliteTestDbContextProviderBuilder UseMigrationLogLevel(LogLevel logLevel) - { - base.UseMigrationLogLevel(logLevel); - - return this; - } - - /// - /// Allows further configuration of the . - /// - /// Callback is called the current and the database schema. - /// Current builder for chaining. - public SqliteTestDbContextProviderBuilder ConfigureOptions(Action> configure) - { - ArgumentNullException.ThrowIfNull(configure); - - _configuresOptionsCollection.Add(configure); - - return this; - } - - /// - /// Allows further configuration of the . - /// - /// Callback is called with the current . - /// Current builder for chaining. - public SqliteTestDbContextProviderBuilder ConfigureSqliteOptions(Action configure) - { - ArgumentNullException.ThrowIfNull(configure); - - _configuresSqliteOptionsCollection.Add(configure); - - return this; - } - - /// - /// Disables EF model cache. - /// - /// Current builder for chaining. - public new SqliteTestDbContextProviderBuilder DisableModelCache(bool disableModelCache = true) - { - base.DisableModelCache(disableModelCache); - - return this; - } - - /// - /// Indication whether collect executed commands or not. - /// - /// Current builder for chaining - public new SqliteTestDbContextProviderBuilder CollectExecutedCommands(bool collectExecutedCommands = true) - { - base.CollectExecutedCommands(collectExecutedCommands); - - return this; - } - - /// - /// Provides a callback to execute on every creation of a new . - /// - /// Initialization. - /// Current builder for chaining. - public SqliteTestDbContextProviderBuilder InitializeContext(Action initialize) - { - ArgumentNullException.ThrowIfNull(initialize); - - _ctxInitializations.Add(initialize); - - return this; - } - - /// - /// Provides a custom factory to create . - /// - /// Factory to create the context of type . - /// Current builder for chaining. - public SqliteTestDbContextProviderBuilder UseContextFactory(Func, T?>? contextFactory) - { - _contextFactory = contextFactory; - - return this; - } - - /// - /// Delegates the creation of to the provided . - /// - /// Factory to use for creation of . - /// Current builder for chaining. - public SqliteTestDbContextProviderBuilder UseProviderFactory(Func, SqliteTestDbContextProvider?>? providerFactory) - { - _providerFactory = providerFactory; - - return this; - } - - /// - /// Creates and configures the - /// - /// Database connection to use. - /// Connection string to use. - /// An instance of - public virtual DbContextOptionsBuilder CreateOptionsBuilder( - DbConnection? connection, - string connectionString) - { - var loggingOptions = CreateLoggingOptions(); - var state = new TestDbContextProviderBuilderState(loggingOptions); - - return CreateOptionsBuilder(state, connection, connectionString); - } - - /// - /// Creates and configures the - /// - /// Current building state. - /// Database connection to use. - /// Connection string to use. - /// An instance of - protected virtual DbContextOptionsBuilder CreateOptionsBuilder( - TestDbContextProviderBuilderState state, - DbConnection? connection, - string connectionString) - { - var builder = new DbContextOptionsBuilder(); - - if (connection is null) - { - builder.UseSqlite(connectionString, ConfigureSqlite); - } - else - { - builder.UseSqlite(connection, ConfigureSqlite); - } - - ApplyDefaultConfiguration(state, builder); - - _configuresOptionsCollection.ForEach(configure => configure(builder)); - - return builder; - } - - /// - /// Configures SQLite options. - /// - /// A builder for configuration of the options. - /// The is null. - protected virtual void ConfigureSqlite(SqliteDbContextOptionsBuilder builder) - { - ArgumentNullException.ThrowIfNull(builder); - - _configuresSqliteOptionsCollection.ForEach(configure => configure(builder)); - } - - /// - /// Builds the . - /// - /// A new instance of . - public SqliteTestDbContextProvider Build() - { - return BuildInternal(); - } - - internal SqliteTestDbContextProvider Build(SqliteConnection masterConnection) - { - return BuildInternal(masterConnection); - } - - internal SqliteTestDbContextProvider Build( - TestingLoggingOptions loggingOptions, - IMigrationExecutionStrategy migrationStrategy) - { - return BuildInternal(null, loggingOptions, migrationStrategy); - } - - private SqliteTestDbContextProvider BuildInternal( - SqliteConnection? masterConnection = null, - TestingLoggingOptions? loggingOptions = null, - IMigrationExecutionStrategy? migrationStrategy = null) - { - var isExternallyOwnedConnection = masterConnection is not null; - - try - { - masterConnection ??= new SqliteConnection(CreateRandomConnectionString()); - loggingOptions ??= CreateLoggingOptions(); - - var state = new TestDbContextProviderBuilderState(loggingOptions) { MigrationExecutionStrategy = migrationStrategy }; - var masterDbContextOptionsBuilder = CreateOptionsBuilder(state, masterConnection, masterConnection.ConnectionString); - var dbContextOptionsBuilder = CreateOptionsBuilder(state, null, masterConnection.ConnectionString); - - var options = new SqliteTestDbContextProviderOptions(masterConnection, - isExternallyOwnedConnection, - state.MigrationExecutionStrategy ?? IMigrationExecutionStrategy.Migrations, - masterDbContextOptionsBuilder, - dbContextOptionsBuilder, - state.LoggingOptions, - _ctxInitializations.ToList()) - { - ContextFactory = _contextFactory, - ExecutedCommands = state.CommandCapturingInterceptor?.Commands - }; - - return _providerFactory?.Invoke(options) ?? new SqliteTestDbContextProvider(options); - } - catch - { - if (!isExternallyOwnedConnection) - masterConnection?.Dispose(); - - throw; - } - } - - /// - /// Builds the . - /// - /// A new instance of . - public SqliteTestDbContextProviderFactory BuildFactory() - { - return new SqliteTestDbContextProviderFactory(this); - } - - internal static string CreateRandomConnectionString() - { - return $"Data Source=InMemory{Guid.NewGuid():N};Mode=Memory;Cache=Shared"; - } -} +using System.Data.Common; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.Extensions.Logging; +using Thinktecture.Logging; +using Xunit.Abstractions; + +namespace Thinktecture.EntityFrameworkCore.Testing; + +/// +/// Builder for the . +/// +/// Type of the . +public class SqliteTestDbContextProviderBuilder : TestDbContextProviderBuilder + where T : DbContext +{ + private readonly List>> _configuresOptionsCollection; + private readonly List> _configuresSqliteOptionsCollection; + private readonly List> _ctxInitializations; + + private Func, T?>? _contextFactory; + private Func, SqliteTestDbContextProvider?>? _providerFactory; + + /// + /// Initializes new instance of . + /// + public SqliteTestDbContextProviderBuilder() + { + _configuresOptionsCollection = new List>>(); + _configuresSqliteOptionsCollection = new List>(); + _ctxInitializations = new List>(); + } + + /// + /// Specifies the migration strategy to use. + /// Default is . + /// + /// Migration strategy to use. + /// Current builder for chaining + public new SqliteTestDbContextProviderBuilder UseMigrationExecutionStrategy(IMigrationExecutionStrategy migrationExecutionStrategy) + { + base.UseMigrationExecutionStrategy(migrationExecutionStrategy); + + return this; + } + + /// + /// Sets the logger factory to be used by EF. + /// + /// Logger factory to use. + /// Enables or disables sensitive data logging. + /// Current builder for chaining. + public new SqliteTestDbContextProviderBuilder UseLogging( + ILoggerFactory? loggerFactory, + bool enableSensitiveDataLogging = true) + { + base.UseLogging(loggerFactory, enableSensitiveDataLogging); + + return this; + } + + /// + /// Sets output helper to be used by Serilog which is passed to EF. + /// + /// XUnit output. + /// Enables or disables sensitive data logging. + /// The serilog output template. + /// Current builder for chaining. + public new SqliteTestDbContextProviderBuilder UseLogging( + ITestOutputHelper? testOutputHelper, + bool enableSensitiveDataLogging = true, + string? outputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}") + { + base.UseLogging(testOutputHelper, enableSensitiveDataLogging, outputTemplate); + + return this; + } + + /// + /// Sets the log level during migrations. + /// + /// Minimum log level to use during migrations. + /// Current builder for chaining. + public new SqliteTestDbContextProviderBuilder UseMigrationLogLevel(LogLevel logLevel) + { + base.UseMigrationLogLevel(logLevel); + + return this; + } + + /// + /// Allows further configuration of the . + /// + /// Callback is called the current and the database schema. + /// Current builder for chaining. + public SqliteTestDbContextProviderBuilder ConfigureOptions(Action> configure) + { + ArgumentNullException.ThrowIfNull(configure); + + _configuresOptionsCollection.Add(configure); + + return this; + } + + /// + /// Allows further configuration of the . + /// + /// Callback is called with the current . + /// Current builder for chaining. + public SqliteTestDbContextProviderBuilder ConfigureSqliteOptions(Action configure) + { + ArgumentNullException.ThrowIfNull(configure); + + _configuresSqliteOptionsCollection.Add(configure); + + return this; + } + + /// + /// Disables EF model cache. + /// + /// Current builder for chaining. + public new SqliteTestDbContextProviderBuilder DisableModelCache(bool disableModelCache = true) + { + base.DisableModelCache(disableModelCache); + + return this; + } + + /// + /// Indication whether collect executed commands or not. + /// + /// Current builder for chaining + public new SqliteTestDbContextProviderBuilder CollectExecutedCommands(bool collectExecutedCommands = true) + { + base.CollectExecutedCommands(collectExecutedCommands); + + return this; + } + + /// + /// Provides a callback to execute on every creation of a new . + /// + /// Initialization. + /// Current builder for chaining. + public SqliteTestDbContextProviderBuilder InitializeContext(Action initialize) + { + ArgumentNullException.ThrowIfNull(initialize); + + _ctxInitializations.Add(initialize); + + return this; + } + + /// + /// Provides a custom factory to create . + /// + /// Factory to create the context of type . + /// Current builder for chaining. + public SqliteTestDbContextProviderBuilder UseContextFactory(Func, T?>? contextFactory) + { + _contextFactory = contextFactory; + + return this; + } + + /// + /// Delegates the creation of to the provided . + /// + /// Factory to use for creation of . + /// Current builder for chaining. + public SqliteTestDbContextProviderBuilder UseProviderFactory(Func, SqliteTestDbContextProvider?>? providerFactory) + { + _providerFactory = providerFactory; + + return this; + } + + /// + /// Creates and configures the + /// + /// Database connection to use. + /// Connection string to use. + /// An instance of + public virtual DbContextOptionsBuilder CreateOptionsBuilder( + DbConnection? connection, + string connectionString) + { + var loggingOptions = CreateLoggingOptions(); + var state = new TestDbContextProviderBuilderState(loggingOptions); + + return CreateOptionsBuilder(state, connection, connectionString); + } + + /// + /// Creates and configures the + /// + /// Current building state. + /// Database connection to use. + /// Connection string to use. + /// An instance of + protected virtual DbContextOptionsBuilder CreateOptionsBuilder( + TestDbContextProviderBuilderState state, + DbConnection? connection, + string connectionString) + { + var builder = new DbContextOptionsBuilder(); + + if (connection is null) + { + builder.UseSqlite(connectionString, ConfigureSqlite); + } + else + { + builder.UseSqlite(connection, ConfigureSqlite); + } + + ApplyDefaultConfiguration(state, builder); + + _configuresOptionsCollection.ForEach(configure => configure(builder)); + + return builder; + } + + /// + /// Configures SQLite options. + /// + /// A builder for configuration of the options. + /// The is null. + protected virtual void ConfigureSqlite(SqliteDbContextOptionsBuilder builder) + { + ArgumentNullException.ThrowIfNull(builder); + + _configuresSqliteOptionsCollection.ForEach(configure => configure(builder)); + } + + /// + /// Builds the . + /// + /// A new instance of . + public SqliteTestDbContextProvider Build() + { + return BuildInternal(); + } + + internal SqliteTestDbContextProvider Build(SqliteConnection masterConnection) + { + return BuildInternal(masterConnection); + } + + internal SqliteTestDbContextProvider Build( + TestingLoggingOptions loggingOptions, + IMigrationExecutionStrategy migrationStrategy) + { + return BuildInternal(null, loggingOptions, migrationStrategy); + } + + private SqliteTestDbContextProvider BuildInternal( + SqliteConnection? masterConnection = null, + TestingLoggingOptions? loggingOptions = null, + IMigrationExecutionStrategy? migrationStrategy = null) + { + var isExternallyOwnedConnection = masterConnection is not null; + + try + { + masterConnection ??= new SqliteConnection(CreateRandomConnectionString()); + loggingOptions ??= CreateLoggingOptions(); + + var state = new TestDbContextProviderBuilderState(loggingOptions) { MigrationExecutionStrategy = migrationStrategy }; + var masterDbContextOptionsBuilder = CreateOptionsBuilder(state, masterConnection, masterConnection.ConnectionString); + var dbContextOptionsBuilder = CreateOptionsBuilder(state, null, masterConnection.ConnectionString); + + var options = new SqliteTestDbContextProviderOptions(masterConnection, + isExternallyOwnedConnection, + state.MigrationExecutionStrategy ?? IMigrationExecutionStrategy.Migrations, + masterDbContextOptionsBuilder, + dbContextOptionsBuilder, + state.LoggingOptions, + _ctxInitializations.ToList()) + { + ContextFactory = _contextFactory, + ExecutedCommands = state.CommandCapturingInterceptor?.Commands + }; + + return _providerFactory?.Invoke(options) ?? new SqliteTestDbContextProvider(options); + } + catch + { + if (!isExternallyOwnedConnection) + masterConnection?.Dispose(); + + throw; + } + } + + /// + /// Builds the . + /// + /// A new instance of . + public SqliteTestDbContextProviderFactory BuildFactory() + { + return new SqliteTestDbContextProviderFactory(this); + } + + internal static string CreateRandomConnectionString() + { + return $"Data Source=InMemory{Guid.NewGuid():N};Mode=Memory;Cache=Shared"; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteTestDbContextProviderFactory.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteTestDbContextProviderFactory.cs index 18888fcb..8da93e84 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteTestDbContextProviderFactory.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/EntityFrameworkCore/Testing/SqliteTestDbContextProviderFactory.cs @@ -1,134 +1,134 @@ -using Microsoft.Data.Sqlite; -using Microsoft.Extensions.Logging; -using Thinktecture.Logging; -using Xunit; -using Xunit.Abstractions; - -namespace Thinktecture.EntityFrameworkCore.Testing; - -/// -/// A factory for creation of . -/// -/// Type of the . -public class SqliteTestDbContextProviderFactory : IAsyncLifetime, IAsyncDisposable, IDisposable - where T : DbContext -{ - private readonly SqliteTestDbContextProviderBuilder _builder; - private readonly SqliteConnection _masterConnection; - - private bool _isInitialized; - - /// - /// Creates new factory. - /// - /// Context builder for creation of new instances of . - public SqliteTestDbContextProviderFactory(SqliteTestDbContextProviderBuilder builder) - { - _builder = builder; - _masterConnection = new SqliteConnection(SqliteTestDbContextProviderBuilder.CreateRandomConnectionString()); - } - - /// - /// Creates a new . - /// - /// Output helper to use. - /// Execution strategy to use. - /// A new instance of . - /// If the factory is not initialized. - public SqliteTestDbContextProvider Create( - ITestOutputHelper testOutputHelper, - IMigrationExecutionStrategy? migrationStrategy = null) - { - return Create(_builder.CreateLoggingOptions(testOutputHelper.ToLoggerFactory(), null), migrationStrategy); - } - - /// - /// Creates a new . - /// - /// Logging factory to use. - /// Execution strategy to use. - /// A new instance of . - /// If the factory is not initialized. - public SqliteTestDbContextProvider Create( - ILoggerFactory loggerFactory, - IMigrationExecutionStrategy? migrationStrategy = null) - { - return Create(_builder.CreateLoggingOptions(loggerFactory, null), migrationStrategy); - } - - /// - /// Creates a new . - /// - /// Logger to use. - /// Execution strategy to use. - /// A new instance of . - /// If the factory is not initialized. - public SqliteTestDbContextProvider Create( - Serilog.ILogger logger, - IMigrationExecutionStrategy? migrationStrategy = null) - { - return Create(_builder.CreateLoggingOptions(null, logger), migrationStrategy); - } - - /// - /// Creates a new . - /// - /// Logging options to use. - /// Execution strategy to use. - /// A new instance of . - /// If the factory is not initialized. - public SqliteTestDbContextProvider Create( - TestingLoggingOptions? loggingOptions = null, - IMigrationExecutionStrategy? migrationStrategy = null) - { - if (!_isInitialized) - throw new InvalidOperationException("The factory is not initialized yet."); - - loggingOptions ??= TestingLoggingOptions.Empty; - - var provider = _builder.Build(loggingOptions, migrationStrategy ?? IMigrationExecutionStrategy.NoMigration); - provider.MasterConnection.Open(); - - _masterConnection.BackupDatabase(provider.MasterConnection, "main", "main"); - - return provider; - } - - /// - /// Initializes the factory. - /// - public void Initialize() - { - InitializeAsync().GetAwaiter().GetResult(); - } - - /// - /// Initializes the factory. - /// - public async Task InitializeAsync() - { - await _masterConnection.OpenAsync(); - - await using var initializationProvider = _builder.Build(_masterConnection); - await using var ctx = initializationProvider.CreateDbContext(true); // runs migrations - - _isInitialized = true; - } - - /// - public async ValueTask DisposeAsync() - { - await _masterConnection.DisposeAsync(); - } - - async Task IAsyncLifetime.DisposeAsync() - { - await _masterConnection.DisposeAsync(); - } - - /// - public void Dispose() - { - _masterConnection.Dispose(); - } -} +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.Logging; +using Thinktecture.Logging; +using Xunit; +using Xunit.Abstractions; + +namespace Thinktecture.EntityFrameworkCore.Testing; + +/// +/// A factory for creation of . +/// +/// Type of the . +public class SqliteTestDbContextProviderFactory : IAsyncLifetime, IAsyncDisposable, IDisposable + where T : DbContext +{ + private readonly SqliteTestDbContextProviderBuilder _builder; + private readonly SqliteConnection _masterConnection; + + private bool _isInitialized; + + /// + /// Creates new factory. + /// + /// Context builder for creation of new instances of . + public SqliteTestDbContextProviderFactory(SqliteTestDbContextProviderBuilder builder) + { + _builder = builder; + _masterConnection = new SqliteConnection(SqliteTestDbContextProviderBuilder.CreateRandomConnectionString()); + } + + /// + /// Creates a new . + /// + /// Output helper to use. + /// Execution strategy to use. + /// A new instance of . + /// If the factory is not initialized. + public SqliteTestDbContextProvider Create( + ITestOutputHelper testOutputHelper, + IMigrationExecutionStrategy? migrationStrategy = null) + { + return Create(_builder.CreateLoggingOptions(testOutputHelper.ToLoggerFactory(), null), migrationStrategy); + } + + /// + /// Creates a new . + /// + /// Logging factory to use. + /// Execution strategy to use. + /// A new instance of . + /// If the factory is not initialized. + public SqliteTestDbContextProvider Create( + ILoggerFactory loggerFactory, + IMigrationExecutionStrategy? migrationStrategy = null) + { + return Create(_builder.CreateLoggingOptions(loggerFactory, null), migrationStrategy); + } + + /// + /// Creates a new . + /// + /// Logger to use. + /// Execution strategy to use. + /// A new instance of . + /// If the factory is not initialized. + public SqliteTestDbContextProvider Create( + Serilog.ILogger logger, + IMigrationExecutionStrategy? migrationStrategy = null) + { + return Create(_builder.CreateLoggingOptions(null, logger), migrationStrategy); + } + + /// + /// Creates a new . + /// + /// Logging options to use. + /// Execution strategy to use. + /// A new instance of . + /// If the factory is not initialized. + public SqliteTestDbContextProvider Create( + TestingLoggingOptions? loggingOptions = null, + IMigrationExecutionStrategy? migrationStrategy = null) + { + if (!_isInitialized) + throw new InvalidOperationException("The factory is not initialized yet."); + + loggingOptions ??= TestingLoggingOptions.Empty; + + var provider = _builder.Build(loggingOptions, migrationStrategy ?? IMigrationExecutionStrategy.NoMigration); + provider.MasterConnection.Open(); + + _masterConnection.BackupDatabase(provider.MasterConnection, "main", "main"); + + return provider; + } + + /// + /// Initializes the factory. + /// + public void Initialize() + { + InitializeAsync().GetAwaiter().GetResult(); + } + + /// + /// Initializes the factory. + /// + public async Task InitializeAsync() + { + await _masterConnection.OpenAsync(); + + await using var initializationProvider = _builder.Build(_masterConnection); + await using var ctx = initializationProvider.CreateDbContext(true); // runs migrations + + _isInitialized = true; + } + + /// + public async ValueTask DisposeAsync() + { + await _masterConnection.DisposeAsync(); + } + + async Task IAsyncLifetime.DisposeAsync() + { + await _masterConnection.DisposeAsync(); + } + + /// + public void Dispose() + { + _masterConnection.Dispose(); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/Thinktecture.EntityFrameworkCore.Sqlite.Testing.csproj b/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/Thinktecture.EntityFrameworkCore.Sqlite.Testing.csproj index f584e572..b9b7e494 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/Thinktecture.EntityFrameworkCore.Sqlite.Testing.csproj +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite.Testing/Thinktecture.EntityFrameworkCore.Sqlite.Testing.csproj @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/BulkInsertContext.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/BulkInsertContext.cs index ad0a1d62..bd06ac90 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/BulkInsertContext.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/BulkInsertContext.cs @@ -1,83 +1,83 @@ -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -internal class BulkInsertContext : ISqliteBulkOperationContext -{ - private readonly DbContext _ctx; - private readonly IReadOnlyList _externalProperties; - private readonly IEntityDataReaderFactory _readerFactory; - - public IReadOnlyList Properties { get; } - public SqliteConnection Connection { get; } - public SqliteBulkInsertOptions Options { get; } - - public bool HasExternalProperties => _externalProperties.Count != 0; - - /// - public IEntityDataReader CreateReader(IEnumerable entities) - { - return _readerFactory.Create(_ctx, entities, Properties, HasExternalProperties); - } - - public SqliteAutoIncrementBehavior AutoIncrementBehavior => Options.AutoIncrementBehavior; - - public BulkInsertContext( - DbContext ctx, - IEntityDataReaderFactory factory, - SqliteConnection sqlCon, - SqliteBulkInsertOptions options, - IReadOnlyList properties) - { - _ctx = ctx; - _readerFactory = factory; - Connection = sqlCon; - var (ownProperties, externalProperties) = properties.SeparateProperties(); - Properties = ownProperties; - _externalProperties = externalProperties; - Options = options; - } - - public SqliteCommandBuilder CreateCommandBuilder() - { - return SqliteCommandBuilder.Insert(Properties); - } - - public IReadOnlyList GetChildren(IReadOnlyList entities) - { - if (!HasExternalProperties) - return Array.Empty(); - - var childCtx = new List(); - - foreach (var (navigation, ownedEntities, properties) in _externalProperties.GroupExternalProperties(entities)) - { - var ownedTypeCtx = new OwnedTypeBulkInsertContext(_ctx, _readerFactory, Connection, Options, properties, navigation.TargetEntityType, ownedEntities); - childCtx.Add(ownedTypeCtx); - } - - return childCtx; - } - - private class OwnedTypeBulkInsertContext : BulkInsertContext, ISqliteOwnedTypeBulkOperationContext - { - public IEntityType EntityType { get; } - public IEnumerable Entities { get; } - - public OwnedTypeBulkInsertContext( - DbContext ctx, - IEntityDataReaderFactory factory, - SqliteConnection sqlCon, - SqliteBulkInsertOptions options, - IReadOnlyList properties, - IEntityType entityType, - IEnumerable entities) - : base(ctx, factory, sqlCon, options, properties) - { - EntityType = entityType; - Entities = entities; - } - } -} +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +internal class BulkInsertContext : ISqliteBulkOperationContext +{ + private readonly DbContext _ctx; + private readonly IReadOnlyList _externalProperties; + private readonly IEntityDataReaderFactory _readerFactory; + + public IReadOnlyList Properties { get; } + public SqliteConnection Connection { get; } + public SqliteBulkInsertOptions Options { get; } + + public bool HasExternalProperties => _externalProperties.Count != 0; + + /// + public IEntityDataReader CreateReader(IEnumerable entities) + { + return _readerFactory.Create(_ctx, entities, Properties, HasExternalProperties); + } + + public SqliteAutoIncrementBehavior AutoIncrementBehavior => Options.AutoIncrementBehavior; + + public BulkInsertContext( + DbContext ctx, + IEntityDataReaderFactory factory, + SqliteConnection sqlCon, + SqliteBulkInsertOptions options, + IReadOnlyList properties) + { + _ctx = ctx; + _readerFactory = factory; + Connection = sqlCon; + var (ownProperties, externalProperties) = properties.SeparateProperties(); + Properties = ownProperties; + _externalProperties = externalProperties; + Options = options; + } + + public SqliteCommandBuilder CreateCommandBuilder() + { + return SqliteCommandBuilder.Insert(Properties); + } + + public IReadOnlyList GetChildren(IReadOnlyList entities) + { + if (!HasExternalProperties) + return Array.Empty(); + + var childCtx = new List(); + + foreach (var (navigation, ownedEntities, properties) in _externalProperties.GroupExternalProperties(entities)) + { + var ownedTypeCtx = new OwnedTypeBulkInsertContext(_ctx, _readerFactory, Connection, Options, properties, navigation.TargetEntityType, ownedEntities); + childCtx.Add(ownedTypeCtx); + } + + return childCtx; + } + + private class OwnedTypeBulkInsertContext : BulkInsertContext, ISqliteOwnedTypeBulkOperationContext + { + public IEntityType EntityType { get; } + public IEnumerable Entities { get; } + + public OwnedTypeBulkInsertContext( + DbContext ctx, + IEntityDataReaderFactory factory, + SqliteConnection sqlCon, + SqliteBulkInsertOptions options, + IReadOnlyList properties, + IEntityType entityType, + IEnumerable entities) + : base(ctx, factory, sqlCon, options, properties) + { + EntityType = entityType; + Entities = entities; + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/BulkInsertOrUpdateContext.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/BulkInsertOrUpdateContext.cs index 7b593c54..01e33abe 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/BulkInsertOrUpdateContext.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/BulkInsertOrUpdateContext.cs @@ -1,126 +1,126 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] -internal class BulkInsertOrUpdateContext : ISqliteBulkOperationContext -{ - private readonly IReadOnlyList _keyProperties; - private readonly IReadOnlyList _propertiesToInsert; - private readonly IReadOnlyList _propertiesToUpdate; - private readonly IReadOnlyList _externalPropertiesToInsert; - private readonly IReadOnlyList _externalPropertiesToUpdate; - private readonly DbContext _ctx; - private readonly IEntityDataReaderFactory _readerFactory; - - public IReadOnlyList Properties { get; } - public bool HasExternalProperties => _externalPropertiesToInsert.Count != 0 || _externalPropertiesToUpdate.Count != 0; - - /// - public IEntityDataReader CreateReader(IEnumerable entities) - { - return _readerFactory.Create(_ctx, entities, Properties, HasExternalProperties); - } - - public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; } - public SqliteConnection Connection { get; } - - public BulkInsertOrUpdateContext( - DbContext ctx, - IEntityDataReaderFactory factory, - SqliteConnection connection, - IReadOnlyList keyProperties, - IReadOnlyList propertiesToInsert, - IReadOnlyList propertiesForUpdate, - SqliteAutoIncrementBehavior autoIncrementBehavior) - { - _ctx = ctx; - _readerFactory = factory; - Connection = connection; - _keyProperties = keyProperties; - AutoIncrementBehavior = autoIncrementBehavior; - - var (ownPropertiesToInsert, externalPropertiesToInsert) = propertiesToInsert.SeparateProperties(); - _propertiesToInsert = ownPropertiesToInsert; - _externalPropertiesToInsert = externalPropertiesToInsert; - - var (ownPropertiesToUpdate, externalPropertiesToUpdate) = propertiesForUpdate.SeparateProperties(); - _propertiesToUpdate = ownPropertiesToUpdate; - _externalPropertiesToUpdate = externalPropertiesToUpdate; - - Properties = ownPropertiesToInsert.Union(ownPropertiesToUpdate).Union(keyProperties.Select(p => new PropertyWithNavigations(p, Array.Empty()))).ToList(); - } - - /// - public SqliteCommandBuilder CreateCommandBuilder() - { - return SqliteCommandBuilder.InsertOrUpdate(_propertiesToInsert, _propertiesToUpdate, _keyProperties); - } - - public IReadOnlyList GetChildren(IReadOnlyList entities) - { - if (!HasExternalProperties) - return Array.Empty(); - - var childCtx = new List(); - - var propertiesToUpdateData = _externalPropertiesToUpdate.GroupExternalProperties(entities).ToList(); - - foreach (var (navigation, ownedEntities, propertiesToInsert) in _externalPropertiesToInsert.GroupExternalProperties(entities)) - { - var propertiesToUpdateTuple = propertiesToUpdateData.FirstOrDefault(d => d.Item1 == navigation); - var propertiesToUpdate = propertiesToUpdateTuple.Item3; - - if (propertiesToUpdate is null || propertiesToUpdate.Count == 0) - throw new Exception($"The owned type property '{navigation.DeclaringEntityType.Name}.{navigation.Name}' is selected for bulk-insert-or-update but there are no properties for performing the update."); - - propertiesToUpdateData.Remove(propertiesToUpdateTuple); - - var ownedTypeCtx = new OwnedTypeBulkInsertOrUpdateContext(_ctx, _readerFactory, Connection, propertiesToInsert, propertiesToUpdate, navigation.TargetEntityType, ownedEntities, AutoIncrementBehavior); - childCtx.Add(ownedTypeCtx); - } - - if (propertiesToUpdateData.Count != 0) - { - var updateWithoutInsert = propertiesToUpdateData[0].Item1; - throw new Exception($"{propertiesToUpdateData.Count} owned type property/properties including '{updateWithoutInsert.DeclaringEntityType.Name}.{updateWithoutInsert.Name}' is selected for bulk-insert-or-update but there are no properties for performing the insert."); - } - - return childCtx; - } - - private class OwnedTypeBulkInsertOrUpdateContext : BulkInsertOrUpdateContext, ISqliteOwnedTypeBulkOperationContext - { - public IEntityType EntityType { get; } - public IEnumerable Entities { get; } - - public OwnedTypeBulkInsertOrUpdateContext( - DbContext ctx, - IEntityDataReaderFactory factory, - SqliteConnection sqlCon, - IReadOnlyList propertiesToInsert, - IReadOnlyList propertiesToUpdate, - IEntityType entityType, - IEnumerable entities, - SqliteAutoIncrementBehavior autoIncrementBehavior) - : base(ctx, factory, sqlCon, GetKeyProperties(entityType), propertiesToInsert, propertiesToUpdate, autoIncrementBehavior) - { - EntityType = entityType; - Entities = entities; - } - - private static IReadOnlyList GetKeyProperties(IEntityType entityType) - { - var properties = entityType.FindPrimaryKey()?.Properties; - - if (properties is null || properties.Count == 0) - throw new Exception($"The entity type '{entityType.Name}' needs a primary key to be able to perform bulk-insert-or-update."); - - return properties; - } - } -} +using System.Diagnostics.CodeAnalysis; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] +internal class BulkInsertOrUpdateContext : ISqliteBulkOperationContext +{ + private readonly IReadOnlyList _keyProperties; + private readonly IReadOnlyList _propertiesToInsert; + private readonly IReadOnlyList _propertiesToUpdate; + private readonly IReadOnlyList _externalPropertiesToInsert; + private readonly IReadOnlyList _externalPropertiesToUpdate; + private readonly DbContext _ctx; + private readonly IEntityDataReaderFactory _readerFactory; + + public IReadOnlyList Properties { get; } + public bool HasExternalProperties => _externalPropertiesToInsert.Count != 0 || _externalPropertiesToUpdate.Count != 0; + + /// + public IEntityDataReader CreateReader(IEnumerable entities) + { + return _readerFactory.Create(_ctx, entities, Properties, HasExternalProperties); + } + + public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; } + public SqliteConnection Connection { get; } + + public BulkInsertOrUpdateContext( + DbContext ctx, + IEntityDataReaderFactory factory, + SqliteConnection connection, + IReadOnlyList keyProperties, + IReadOnlyList propertiesToInsert, + IReadOnlyList propertiesForUpdate, + SqliteAutoIncrementBehavior autoIncrementBehavior) + { + _ctx = ctx; + _readerFactory = factory; + Connection = connection; + _keyProperties = keyProperties; + AutoIncrementBehavior = autoIncrementBehavior; + + var (ownPropertiesToInsert, externalPropertiesToInsert) = propertiesToInsert.SeparateProperties(); + _propertiesToInsert = ownPropertiesToInsert; + _externalPropertiesToInsert = externalPropertiesToInsert; + + var (ownPropertiesToUpdate, externalPropertiesToUpdate) = propertiesForUpdate.SeparateProperties(); + _propertiesToUpdate = ownPropertiesToUpdate; + _externalPropertiesToUpdate = externalPropertiesToUpdate; + + Properties = ownPropertiesToInsert.Union(ownPropertiesToUpdate).Union(keyProperties.Select(p => new PropertyWithNavigations(p, Array.Empty()))).ToList(); + } + + /// + public SqliteCommandBuilder CreateCommandBuilder() + { + return SqliteCommandBuilder.InsertOrUpdate(_propertiesToInsert, _propertiesToUpdate, _keyProperties); + } + + public IReadOnlyList GetChildren(IReadOnlyList entities) + { + if (!HasExternalProperties) + return Array.Empty(); + + var childCtx = new List(); + + var propertiesToUpdateData = _externalPropertiesToUpdate.GroupExternalProperties(entities).ToList(); + + foreach (var (navigation, ownedEntities, propertiesToInsert) in _externalPropertiesToInsert.GroupExternalProperties(entities)) + { + var propertiesToUpdateTuple = propertiesToUpdateData.FirstOrDefault(d => d.Item1 == navigation); + var propertiesToUpdate = propertiesToUpdateTuple.Item3; + + if (propertiesToUpdate is null || propertiesToUpdate.Count == 0) + throw new Exception($"The owned type property '{navigation.DeclaringEntityType.Name}.{navigation.Name}' is selected for bulk-insert-or-update but there are no properties for performing the update."); + + propertiesToUpdateData.Remove(propertiesToUpdateTuple); + + var ownedTypeCtx = new OwnedTypeBulkInsertOrUpdateContext(_ctx, _readerFactory, Connection, propertiesToInsert, propertiesToUpdate, navigation.TargetEntityType, ownedEntities, AutoIncrementBehavior); + childCtx.Add(ownedTypeCtx); + } + + if (propertiesToUpdateData.Count != 0) + { + var updateWithoutInsert = propertiesToUpdateData[0].Item1; + throw new Exception($"{propertiesToUpdateData.Count} owned type property/properties including '{updateWithoutInsert.DeclaringEntityType.Name}.{updateWithoutInsert.Name}' is selected for bulk-insert-or-update but there are no properties for performing the insert."); + } + + return childCtx; + } + + private class OwnedTypeBulkInsertOrUpdateContext : BulkInsertOrUpdateContext, ISqliteOwnedTypeBulkOperationContext + { + public IEntityType EntityType { get; } + public IEnumerable Entities { get; } + + public OwnedTypeBulkInsertOrUpdateContext( + DbContext ctx, + IEntityDataReaderFactory factory, + SqliteConnection sqlCon, + IReadOnlyList propertiesToInsert, + IReadOnlyList propertiesToUpdate, + IEntityType entityType, + IEnumerable entities, + SqliteAutoIncrementBehavior autoIncrementBehavior) + : base(ctx, factory, sqlCon, GetKeyProperties(entityType), propertiesToInsert, propertiesToUpdate, autoIncrementBehavior) + { + EntityType = entityType; + Entities = entities; + } + + private static IReadOnlyList GetKeyProperties(IEntityType entityType) + { + var properties = entityType.FindPrimaryKey()?.Properties; + + if (properties is null || properties.Count == 0) + throw new Exception($"The entity type '{entityType.Name}' needs a primary key to be able to perform bulk-insert-or-update."); + + return properties; + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/BulkUpdateContext.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/BulkUpdateContext.cs index dd720127..54ba9f54 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/BulkUpdateContext.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/BulkUpdateContext.cs @@ -1,102 +1,102 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] -internal class BulkUpdateContext : ISqliteBulkOperationContext -{ - private readonly IReadOnlyList _keyProperties; - private readonly IReadOnlyList _propertiesToUpdate; - private readonly IReadOnlyList _externalProperties; - private readonly DbContext _ctx; - private readonly IEntityDataReaderFactory _readerFactory; - - public IReadOnlyList Properties { get; } - public SqliteConnection Connection { get; } - - public bool HasExternalProperties => _externalProperties.Count != 0; - - /// - public IEntityDataReader CreateReader(IEnumerable entities) - { - return _readerFactory.Create(_ctx, entities, Properties, HasExternalProperties); - } - - public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; } - - public BulkUpdateContext( - DbContext ctx, - IEntityDataReaderFactory factory, - SqliteConnection connection, - IReadOnlyList keyProperties, - IReadOnlyList propertiesForUpdate, - SqliteAutoIncrementBehavior autoIncrementBehavior) - { - _ctx = ctx; - _readerFactory = factory; - Connection = connection; - _keyProperties = keyProperties; - var (ownProperties, externalProperties) = propertiesForUpdate.SeparateProperties(); - _propertiesToUpdate = ownProperties; - _externalProperties = externalProperties; - Properties = ownProperties.Union(keyProperties.Select(p => new PropertyWithNavigations(p, Array.Empty()))).ToList(); - AutoIncrementBehavior = autoIncrementBehavior; - } - - /// - public SqliteCommandBuilder CreateCommandBuilder() - { - return SqliteCommandBuilder.Update(_propertiesToUpdate, _keyProperties); - } - - /// - public IReadOnlyList GetChildren(IReadOnlyList entities) - { - if (!HasExternalProperties) - return Array.Empty(); - - var childCtx = new List(); - - foreach (var (navigation, ownedEntities, properties) in _externalProperties.GroupExternalProperties(entities)) - { - var ownedTypeCtx = new OwnedTypeBulkUpdateContext(_ctx, _readerFactory, Connection, properties, navigation.TargetEntityType, ownedEntities, AutoIncrementBehavior); - childCtx.Add(ownedTypeCtx); - } - - return childCtx; - } - - private class OwnedTypeBulkUpdateContext : BulkUpdateContext, ISqliteOwnedTypeBulkOperationContext - { - public IEntityType EntityType { get; } - public IEnumerable Entities { get; } - - public OwnedTypeBulkUpdateContext( - DbContext ctx, - IEntityDataReaderFactory factory, - SqliteConnection sqlCon, - IReadOnlyList properties, - IEntityType entityType, - IEnumerable entities, - SqliteAutoIncrementBehavior autoIncrementBehavior) - : base(ctx, factory, sqlCon, GetKeyProperties(entityType), properties, autoIncrementBehavior) - { - EntityType = entityType; - Entities = entities; - } - - private static IReadOnlyList GetKeyProperties(IEntityType entityType) - { - var properties = entityType.FindPrimaryKey()?.Properties; - - if (properties is null || properties.Count == 0) - throw new Exception($"The entity type '{entityType.Name}' needs a primary key to be able to perform bulk-update."); - - return properties; - } - } -} +using System.Diagnostics.CodeAnalysis; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] +internal class BulkUpdateContext : ISqliteBulkOperationContext +{ + private readonly IReadOnlyList _keyProperties; + private readonly IReadOnlyList _propertiesToUpdate; + private readonly IReadOnlyList _externalProperties; + private readonly DbContext _ctx; + private readonly IEntityDataReaderFactory _readerFactory; + + public IReadOnlyList Properties { get; } + public SqliteConnection Connection { get; } + + public bool HasExternalProperties => _externalProperties.Count != 0; + + /// + public IEntityDataReader CreateReader(IEnumerable entities) + { + return _readerFactory.Create(_ctx, entities, Properties, HasExternalProperties); + } + + public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; } + + public BulkUpdateContext( + DbContext ctx, + IEntityDataReaderFactory factory, + SqliteConnection connection, + IReadOnlyList keyProperties, + IReadOnlyList propertiesForUpdate, + SqliteAutoIncrementBehavior autoIncrementBehavior) + { + _ctx = ctx; + _readerFactory = factory; + Connection = connection; + _keyProperties = keyProperties; + var (ownProperties, externalProperties) = propertiesForUpdate.SeparateProperties(); + _propertiesToUpdate = ownProperties; + _externalProperties = externalProperties; + Properties = ownProperties.Union(keyProperties.Select(p => new PropertyWithNavigations(p, Array.Empty()))).ToList(); + AutoIncrementBehavior = autoIncrementBehavior; + } + + /// + public SqliteCommandBuilder CreateCommandBuilder() + { + return SqliteCommandBuilder.Update(_propertiesToUpdate, _keyProperties); + } + + /// + public IReadOnlyList GetChildren(IReadOnlyList entities) + { + if (!HasExternalProperties) + return Array.Empty(); + + var childCtx = new List(); + + foreach (var (navigation, ownedEntities, properties) in _externalProperties.GroupExternalProperties(entities)) + { + var ownedTypeCtx = new OwnedTypeBulkUpdateContext(_ctx, _readerFactory, Connection, properties, navigation.TargetEntityType, ownedEntities, AutoIncrementBehavior); + childCtx.Add(ownedTypeCtx); + } + + return childCtx; + } + + private class OwnedTypeBulkUpdateContext : BulkUpdateContext, ISqliteOwnedTypeBulkOperationContext + { + public IEntityType EntityType { get; } + public IEnumerable Entities { get; } + + public OwnedTypeBulkUpdateContext( + DbContext ctx, + IEntityDataReaderFactory factory, + SqliteConnection sqlCon, + IReadOnlyList properties, + IEntityType entityType, + IEnumerable entities, + SqliteAutoIncrementBehavior autoIncrementBehavior) + : base(ctx, factory, sqlCon, GetKeyProperties(entityType), properties, autoIncrementBehavior) + { + EntityType = entityType; + Entities = entities; + } + + private static IReadOnlyList GetKeyProperties(IEntityType entityType) + { + var properties = entityType.FindPrimaryKey()?.Properties; + + if (properties is null || properties.Count == 0) + throw new Exception($"The entity type '{entityType.Name}' needs a primary key to be able to perform bulk-update."); + + return properties; + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/ISqliteBulkInsertOrUpdateOptions.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/ISqliteBulkInsertOrUpdateOptions.cs index f950a5ab..36f4ffa5 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/ISqliteBulkInsertOrUpdateOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/ISqliteBulkInsertOrUpdateOptions.cs @@ -1,13 +1,13 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Options for SQLite. -/// -public interface ISqliteBulkInsertOrUpdateOptions : IBulkInsertOrUpdateOptions -{ - /// - /// Behavior for auto-increment columns. - /// Default is - /// - SqliteAutoIncrementBehavior AutoIncrementBehavior { get; } +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Options for SQLite. +/// +public interface ISqliteBulkInsertOrUpdateOptions : IBulkInsertOrUpdateOptions +{ + /// + /// Behavior for auto-increment columns. + /// Default is + /// + SqliteAutoIncrementBehavior AutoIncrementBehavior { get; } } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/ISqliteBulkOperationContext.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/ISqliteBulkOperationContext.cs index e9ea350a..80de1fa7 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/ISqliteBulkOperationContext.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/ISqliteBulkOperationContext.cs @@ -1,12 +1,12 @@ -using Microsoft.Data.Sqlite; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -internal interface ISqliteBulkOperationContext : IBulkOperationContext -{ - SqliteAutoIncrementBehavior AutoIncrementBehavior { get; } - SqliteConnection Connection { get; } - SqliteCommandBuilder CreateCommandBuilder(); - - IReadOnlyList GetChildren(IReadOnlyList entities); -} +using Microsoft.Data.Sqlite; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +internal interface ISqliteBulkOperationContext : IBulkOperationContext +{ + SqliteAutoIncrementBehavior AutoIncrementBehavior { get; } + SqliteConnection Connection { get; } + SqliteCommandBuilder CreateCommandBuilder(); + + IReadOnlyList GetChildren(IReadOnlyList entities); +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/ISqliteOwnedTypeBulkOperationContext.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/ISqliteOwnedTypeBulkOperationContext.cs index ac4aa6d7..b3ca291f 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/ISqliteOwnedTypeBulkOperationContext.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/ISqliteOwnedTypeBulkOperationContext.cs @@ -1,5 +1,5 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -internal interface ISqliteOwnedTypeBulkOperationContext : ISqliteBulkOperationContext, IOwnedTypeBulkOperationContext -{ +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +internal interface ISqliteOwnedTypeBulkOperationContext : ISqliteBulkOperationContext, IOwnedTypeBulkOperationContext +{ } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteAutoIncrementBehavior.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteAutoIncrementBehavior.cs index a3b16c28..58fd4fc1 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteAutoIncrementBehavior.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteAutoIncrementBehavior.cs @@ -1,17 +1,17 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Defines behavior from auto-increment columns. -/// -public enum SqliteAutoIncrementBehavior -{ - /// - /// Sends the value NULL instead of 0 to the database. - /// - SetZeroToNull, - - /// - /// Sends the value as is to the database. - /// - KeepValueAsIs +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Defines behavior from auto-increment columns. +/// +public enum SqliteAutoIncrementBehavior +{ + /// + /// Sends the value NULL instead of 0 to the database. + /// + SetZeroToNull, + + /// + /// Sends the value as is to the database. + /// + KeepValueAsIs } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkInsertOptions.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkInsertOptions.cs index 0e7a73df..99715167 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkInsertOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkInsertOptions.cs @@ -1,33 +1,33 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Bulk insert options for SQLite. -/// -public sealed class SqliteBulkInsertOptions : IBulkInsertOptions -{ - /// - public IEntityPropertiesProvider? PropertiesToInsert { get; set; } - - /// - /// Behavior for auto-increment columns. - /// Default is - /// - public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; set; } - - /// - /// Initializes new instance of . - /// - /// Options to initialize from. - public SqliteBulkInsertOptions(IBulkInsertOptions? optionsToInitializeFrom = null) - { - AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull; - - if (optionsToInitializeFrom is not null) - { - PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert; - - if (optionsToInitializeFrom is SqliteBulkInsertOptions sqliteOptions) - AutoIncrementBehavior = sqliteOptions.AutoIncrementBehavior; - } - } -} +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Bulk insert options for SQLite. +/// +public sealed class SqliteBulkInsertOptions : IBulkInsertOptions +{ + /// + public IEntityPropertiesProvider? PropertiesToInsert { get; set; } + + /// + /// Behavior for auto-increment columns. + /// Default is + /// + public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; set; } + + /// + /// Initializes new instance of . + /// + /// Options to initialize from. + public SqliteBulkInsertOptions(IBulkInsertOptions? optionsToInitializeFrom = null) + { + AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull; + + if (optionsToInitializeFrom is not null) + { + PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert; + + if (optionsToInitializeFrom is SqliteBulkInsertOptions sqliteOptions) + AutoIncrementBehavior = sqliteOptions.AutoIncrementBehavior; + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkInsertOrUpdateOptions.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkInsertOrUpdateOptions.cs index 0964d1e4..d1dd99ce 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkInsertOrUpdateOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkInsertOrUpdateOptions.cs @@ -1,38 +1,38 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Options for SQLite. -/// -public class SqliteBulkInsertOrUpdateOptions : ISqliteBulkInsertOrUpdateOptions -{ - /// - public IEntityPropertiesProvider? PropertiesToInsert { get; set; } - - /// - public IEntityPropertiesProvider? PropertiesToUpdate { get; set; } - - /// - public IEntityPropertiesProvider? KeyProperties { get; set; } - - /// - public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; set; } - - /// - /// Initializes new instance of . - /// - /// Options to initialize from. - public SqliteBulkInsertOrUpdateOptions(IBulkInsertOrUpdateOptions? optionsToInitializeFrom = null) - { - AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull; - - if (optionsToInitializeFrom is not null) - { - PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert; - PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate; - KeyProperties = optionsToInitializeFrom.KeyProperties; - - if (optionsToInitializeFrom is ISqliteBulkInsertOrUpdateOptions sqliteOptions) - AutoIncrementBehavior = sqliteOptions.AutoIncrementBehavior; - } - } -} +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Options for SQLite. +/// +public class SqliteBulkInsertOrUpdateOptions : ISqliteBulkInsertOrUpdateOptions +{ + /// + public IEntityPropertiesProvider? PropertiesToInsert { get; set; } + + /// + public IEntityPropertiesProvider? PropertiesToUpdate { get; set; } + + /// + public IEntityPropertiesProvider? KeyProperties { get; set; } + + /// + public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; set; } + + /// + /// Initializes new instance of . + /// + /// Options to initialize from. + public SqliteBulkInsertOrUpdateOptions(IBulkInsertOrUpdateOptions? optionsToInitializeFrom = null) + { + AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull; + + if (optionsToInitializeFrom is not null) + { + PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert; + PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate; + KeyProperties = optionsToInitializeFrom.KeyProperties; + + if (optionsToInitializeFrom is ISqliteBulkInsertOrUpdateOptions sqliteOptions) + AutoIncrementBehavior = sqliteOptions.AutoIncrementBehavior; + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutor.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutor.cs index 4d686b80..62ac7b8d 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutor.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutor.cs @@ -1,428 +1,428 @@ -using System.Diagnostics; -using System.Text; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.ObjectPool; -using Thinktecture.EntityFrameworkCore.Data; -using Thinktecture.EntityFrameworkCore.TempTables; -using Thinktecture.Internal; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Executes bulk operations. -/// -// ReSharper disable once ClassNeverInstantiated.Global -public sealed class SqliteBulkOperationExecutor - : IBulkInsertExecutor, ITempTableBulkInsertExecutor, IBulkUpdateExecutor, - IBulkInsertOrUpdateExecutor, ITruncateTableExecutor -{ - private readonly DbContext _ctx; - private readonly IDiagnosticsLogger _logger; - private readonly ISqlGenerationHelper _sqlGenerationHelper; - private readonly ObjectPool _stringBuilderPool; - - private static class EventIds - { - public static readonly EventId Started = 0; - public static readonly EventId Finished = 1; - } - - /// - /// Initializes new instance of . - /// - /// Current database context. - /// Logger. - /// SQL generation helper. - /// String builder pool. - public SqliteBulkOperationExecutor( - ICurrentDbContext ctx, - IDiagnosticsLogger logger, - ISqlGenerationHelper sqlGenerationHelper, - ObjectPool stringBuilderPool) - { - ArgumentNullException.ThrowIfNull(ctx); - - _ctx = ctx.Context ?? throw new ArgumentNullException(nameof(ctx)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _sqlGenerationHelper = sqlGenerationHelper ?? throw new ArgumentNullException(nameof(sqlGenerationHelper)); - _stringBuilderPool = stringBuilderPool ?? throw new ArgumentNullException(nameof(stringBuilderPool)); - } - - /// - IBulkInsertOptions IBulkInsertExecutor.CreateOptions(IEntityPropertiesProvider? propertiesToInsert) - { - return new SqliteBulkInsertOptions { PropertiesToInsert = propertiesToInsert }; - } - - /// - ITempTableBulkInsertOptions ITempTableBulkInsertExecutor.CreateOptions(IEntityPropertiesProvider? propertiesToInsert) - { - return new SqliteTempTableBulkInsertOptions { PropertiesToInsert = propertiesToInsert }; - } - - /// - IBulkUpdateOptions IBulkUpdateExecutor.CreateOptions(IEntityPropertiesProvider? propertiesToUpdate, IEntityPropertiesProvider? keyProperties) - { - return new SqliteBulkUpdateOptions - { - PropertiesToUpdate = propertiesToUpdate, - KeyProperties = keyProperties - }; - } - - /// - IBulkInsertOrUpdateOptions IBulkInsertOrUpdateExecutor.CreateOptions( - IEntityPropertiesProvider? propertiesToInsert, - IEntityPropertiesProvider? propertiesToUpdate, - IEntityPropertiesProvider? keyProperties) - { - return new SqliteBulkInsertOrUpdateOptions - { - PropertiesToInsert = propertiesToInsert, - PropertiesToUpdate = propertiesToUpdate, - KeyProperties = keyProperties - }; - } - - /// - public async Task BulkInsertAsync( - IEnumerable entities, - IBulkInsertOptions options, - CancellationToken cancellationToken = default) - where T : class - { - ArgumentNullException.ThrowIfNull(entities); - ArgumentNullException.ThrowIfNull(options); - - var entityType = _ctx.Model.GetEntityType(typeof(T)); - var tableName = entityType.GetTableName() ?? throw new InvalidOperationException($"The entity '{entityType.Name}' has no table name."); - - if (options is not SqliteBulkInsertOptions sqliteOptions) - sqliteOptions = new SqliteBulkInsertOptions(options); - - await BulkInsertAsync(entityType, - entities, - entityType.GetSchema(), - tableName, - sqliteOptions, - SqliteBulkOperationContextFactoryForEntities.Instance, - cancellationToken); - } - - private async Task BulkInsertAsync( - IEntityType entityType, - IEnumerable entitiesOrValues, - string? schema, - string tableName, - SqliteBulkInsertOptions options, - ISqliteBulkOperationContextFactory bulkOperationContextFactory, - CancellationToken cancellationToken) - { - var properties = options.PropertiesToInsert.DeterminePropertiesForInsert(entityType, null); - properties.EnsureNoSeparateOwnedTypesInsideCollectionOwnedType(); - - var ctx = bulkOperationContextFactory.CreateForBulkInsert(_ctx, options, properties); - - await ExecuteBulkOperationAsync(entitiesOrValues, schema, tableName, ctx, cancellationToken); - } - - /// - public async Task BulkUpdateAsync( - IEnumerable entities, - IBulkUpdateOptions options, - CancellationToken cancellationToken = default) - where T : class - { - ArgumentNullException.ThrowIfNull(entities); - ArgumentNullException.ThrowIfNull(options); - - var entityType = _ctx.Model.GetEntityType(typeof(T)); - - if (options is not SqliteBulkUpdateOptions sqliteOptions) - sqliteOptions = new SqliteBulkUpdateOptions(options); - - var ctx = new BulkUpdateContext(_ctx, - _ctx.GetService(), - (SqliteConnection)_ctx.Database.GetDbConnection(), - options.KeyProperties.DetermineKeyProperties(entityType), - options.PropertiesToUpdate.DeterminePropertiesForUpdate(entityType, null), - sqliteOptions.AutoIncrementBehavior); - var tableName = entityType.GetTableName() - ?? throw new Exception($"The entity '{entityType.Name}' has no table name."); - - return await ExecuteBulkOperationAsync(entities, entityType.GetSchema(), tableName, ctx, cancellationToken); - } - - /// - public async Task BulkInsertOrUpdateAsync( - IEnumerable entities, - IBulkInsertOrUpdateOptions options, - CancellationToken cancellationToken = default) - where T : class - { - ArgumentNullException.ThrowIfNull(entities); - ArgumentNullException.ThrowIfNull(options); - - if (!(options is ISqliteBulkInsertOrUpdateOptions sqliteOptions)) - sqliteOptions = new SqliteBulkInsertOrUpdateOptions(options); - - var entityType = _ctx.Model.GetEntityType(typeof(T)); - var ctx = new BulkInsertOrUpdateContext(_ctx, - _ctx.GetService(), - (SqliteConnection)_ctx.Database.GetDbConnection(), - sqliteOptions.KeyProperties.DetermineKeyProperties(entityType), - sqliteOptions.PropertiesToInsert.DeterminePropertiesForInsert(entityType, null), - sqliteOptions.PropertiesToUpdate.DeterminePropertiesForUpdate(entityType, true), - sqliteOptions.AutoIncrementBehavior); - var tableName = entityType.GetTableName() - ?? throw new Exception($"The entity '{entityType.Name}' has no table name."); - - return await ExecuteBulkOperationAsync(entities, entityType.GetSchema(), tableName, ctx, cancellationToken); - } - - private async Task ExecuteBulkOperationAsync( - IEnumerable entitiesOrValues, - string? schema, - string tableName, - ISqliteBulkOperationContext bulkOperationContext, - CancellationToken cancellationToken) - { - await _ctx.Database.OpenConnectionAsync(cancellationToken).ConfigureAwait(false); - - try - { - var tableIdentifier = _sqlGenerationHelper.DelimitIdentifier(tableName, schema); - - using var reader = bulkOperationContext.CreateReader(entitiesOrValues); - var numberOfAffectedRows = await ExecuteBulkOperationAsync(reader, bulkOperationContext, tableIdentifier, cancellationToken).ConfigureAwait(false); - - if (bulkOperationContext.HasExternalProperties) - { - var readEntities = reader.GetReadEntities(); - numberOfAffectedRows += await ExecuteBulkOperationForSeparatedOwnedEntitiesAsync((IReadOnlyList)readEntities, bulkOperationContext, cancellationToken); - } - - return numberOfAffectedRows; - } - finally - { - await _ctx.Database.CloseConnectionAsync().ConfigureAwait(false); - } - } - - private async Task ExecuteBulkOperationForSeparatedOwnedEntitiesAsync( - IReadOnlyList parentEntities, - ISqliteBulkOperationContext parentBulkOperationContext, - CancellationToken cancellationToken) - { - if (parentEntities.Count == 0) - return 0; - - var numberOfAffectedRows = 0; - - foreach (var childContext in parentBulkOperationContext.GetChildren(parentEntities)) - { - var childTableName = childContext.EntityType.GetTableName() - ?? throw new InvalidOperationException($"The entity '{childContext.EntityType.Name}' has no table name."); - - numberOfAffectedRows += await ExecuteBulkOperationAsync(childContext.Entities, - childContext.EntityType.GetSchema(), - childTableName, - childContext, - cancellationToken).ConfigureAwait(false); - } - - return numberOfAffectedRows; - } - - private async Task ExecuteBulkOperationAsync( - IEntityDataReader reader, - ISqliteBulkOperationContext bulkOperationContext, - string tableIdentifier, - CancellationToken cancellationToken) - { - await using var command = bulkOperationContext.Connection.CreateCommand(); - - command.CommandText = bulkOperationContext.CreateCommandBuilder().GetStatement(_sqlGenerationHelper, _stringBuilderPool, reader, tableIdentifier); - var parameterInfos = CreateParameters(reader, command); - - try - { - command.Prepare(); - } - catch (SqliteException ex) - { - throw new InvalidOperationException($"Error during bulk operation on table '{tableIdentifier}'. See inner exception for more details.", ex); - } - - LogBulkOperationStart(command.CommandText); - var stopwatch = Stopwatch.StartNew(); - var numberOfAffectedRows = 0; - - while (reader.Read()) - { - for (var i = 0; i < reader.FieldCount; i++) - { - var paramInfo = parameterInfos[i]; - var value = reader.GetValue(i); - - if (bulkOperationContext.AutoIncrementBehavior == SqliteAutoIncrementBehavior.SetZeroToNull && paramInfo.IsAutoIncrementColumn && 0.Equals(value)) - value = null; - - paramInfo.Parameter.Value = value ?? DBNull.Value; - } - - numberOfAffectedRows += await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); - } - - stopwatch.Stop(); - LogBulkOperationEnd(command.CommandText, stopwatch.Elapsed); - - return numberOfAffectedRows; - } - - private static ParameterInfo[] CreateParameters(IEntityDataReader reader, SqliteCommand command) - { - var parameters = new ParameterInfo[reader.Properties.Count]; - - for (var i = 0; i < reader.Properties.Count; i++) - { - var property = reader.Properties[i]; - - var parameter = command.CreateParameter(); - parameter.ParameterName = $"$p{i}"; - parameters[i] = new ParameterInfo(parameter, property.Property.IsAutoIncrement()); - command.Parameters.Add(parameter); - } - - return parameters; - } - - private void LogBulkOperationStart(string statement) - { - _logger.Logger.LogDebug(EventIds.Started, - """ - Executing DbCommand - {Statement} - """, - statement); - } - - private void LogBulkOperationEnd(string statement, TimeSpan duration) - { - _logger.Logger.LogInformation(EventIds.Finished, - """ - Executed DbCommand ({Duration}ms) - {Statement} - """, - (long)duration.TotalMilliseconds, - statement); - } - - /// - public Task> BulkInsertValuesIntoTempTableAsync( - IEnumerable values, - ITempTableBulkInsertOptions options, - CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(values); - ArgumentNullException.ThrowIfNull(options); - - if (options is not SqliteTempTableBulkInsertOptions sqliteOptions) - sqliteOptions = new SqliteTempTableBulkInsertOptions(options); - - return BulkInsertIntoTempTableAsync>(values, - sqliteOptions, - SqliteBulkOperationContextFactoryForValues.Instance, - static query => query.Select(t => t.Column1), - cancellationToken); - } - - /// - public Task> BulkInsertIntoTempTableAsync( - IEnumerable entities, - ITempTableBulkInsertOptions options, - CancellationToken cancellationToken = default) - where T : class - { - ArgumentNullException.ThrowIfNull(entities); - ArgumentNullException.ThrowIfNull(options); - - if (options is not SqliteTempTableBulkInsertOptions sqliteOptions) - sqliteOptions = new SqliteTempTableBulkInsertOptions(options); - - return BulkInsertIntoTempTableAsync(entities, - sqliteOptions, - SqliteBulkOperationContextFactoryForEntities.Instance, - static query => query, - cancellationToken); - } - - private async Task> BulkInsertIntoTempTableAsync( - IEnumerable entitiesOrValues, - SqliteTempTableBulkInsertOptions options, - ISqliteBulkOperationContextFactory bulkOperationContextFactory, - Func, IQueryable> projection, - CancellationToken cancellationToken) - where TEntity : class - { - var type = typeof(TEntity); - var entityTypeName = EntityNameProvider.GetTempTableName(type); - var entityType = _ctx.Model.GetEntityType(entityTypeName, type); - - var tempTableCreator = _ctx.GetService(); - var tempTableCreationOptions = options.GetTempTableCreationOptions(); - var tempTableReference = await tempTableCreator.CreateTempTableAsync(entityType, tempTableCreationOptions, cancellationToken).ConfigureAwait(false); - - try - { - var bulkInsertOptions = options.GetBulkInsertOptions(); - await BulkInsertAsync(entityType, entitiesOrValues, null, tempTableReference.Name, bulkInsertOptions, bulkOperationContextFactory, cancellationToken).ConfigureAwait(false); - - var dbSet = entityType.Name == entityTypeName - ? _ctx.Set(entityTypeName) - : _ctx.Set(); - - var query = dbSet.FromTempTable(new TempTableInfo(tempTableReference.Name)); - - var pk = entityType.FindPrimaryKey(); - - if (pk is not null && pk.Properties.Count != 0) - query = query.AsNoTracking(); - - return new TempTableQuery(projection(query), tempTableReference); - } - catch (Exception) - { - await tempTableReference.DisposeAsync().ConfigureAwait(false); - throw; - } - } - - /// - public async Task TruncateTableAsync(CancellationToken cancellationToken = default) - where T : class - { - await TruncateTableAsync(typeof(T), cancellationToken); - } - - /// - public async Task TruncateTableAsync(Type type, CancellationToken cancellationToken = default) - { - var entityType = _ctx.Model.GetEntityType(type); - var tableName = entityType.GetTableName() - ?? throw new InvalidOperationException($"The entity '{entityType.Name}' has no table name."); - - var tableIdentifier = _sqlGenerationHelper.DelimitIdentifier(tableName, entityType.GetSchema()); - var truncateStatement = $"DELETE FROM {tableIdentifier};"; - - await _ctx.Database.ExecuteSqlRawAsync(truncateStatement, cancellationToken); - } - - private readonly record struct ParameterInfo(SqliteParameter Parameter, bool IsAutoIncrementColumn); -} +using System.Diagnostics; +using System.Text; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; +using Thinktecture.EntityFrameworkCore.Data; +using Thinktecture.EntityFrameworkCore.TempTables; +using Thinktecture.Internal; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Executes bulk operations. +/// +// ReSharper disable once ClassNeverInstantiated.Global +public sealed class SqliteBulkOperationExecutor + : IBulkInsertExecutor, ITempTableBulkInsertExecutor, IBulkUpdateExecutor, + IBulkInsertOrUpdateExecutor, ITruncateTableExecutor +{ + private readonly DbContext _ctx; + private readonly IDiagnosticsLogger _logger; + private readonly ISqlGenerationHelper _sqlGenerationHelper; + private readonly ObjectPool _stringBuilderPool; + + private static class EventIds + { + public static readonly EventId Started = 0; + public static readonly EventId Finished = 1; + } + + /// + /// Initializes new instance of . + /// + /// Current database context. + /// Logger. + /// SQL generation helper. + /// String builder pool. + public SqliteBulkOperationExecutor( + ICurrentDbContext ctx, + IDiagnosticsLogger logger, + ISqlGenerationHelper sqlGenerationHelper, + ObjectPool stringBuilderPool) + { + ArgumentNullException.ThrowIfNull(ctx); + + _ctx = ctx.Context ?? throw new ArgumentNullException(nameof(ctx)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _sqlGenerationHelper = sqlGenerationHelper ?? throw new ArgumentNullException(nameof(sqlGenerationHelper)); + _stringBuilderPool = stringBuilderPool ?? throw new ArgumentNullException(nameof(stringBuilderPool)); + } + + /// + IBulkInsertOptions IBulkInsertExecutor.CreateOptions(IEntityPropertiesProvider? propertiesToInsert) + { + return new SqliteBulkInsertOptions { PropertiesToInsert = propertiesToInsert }; + } + + /// + ITempTableBulkInsertOptions ITempTableBulkInsertExecutor.CreateOptions(IEntityPropertiesProvider? propertiesToInsert) + { + return new SqliteTempTableBulkInsertOptions { PropertiesToInsert = propertiesToInsert }; + } + + /// + IBulkUpdateOptions IBulkUpdateExecutor.CreateOptions(IEntityPropertiesProvider? propertiesToUpdate, IEntityPropertiesProvider? keyProperties) + { + return new SqliteBulkUpdateOptions + { + PropertiesToUpdate = propertiesToUpdate, + KeyProperties = keyProperties + }; + } + + /// + IBulkInsertOrUpdateOptions IBulkInsertOrUpdateExecutor.CreateOptions( + IEntityPropertiesProvider? propertiesToInsert, + IEntityPropertiesProvider? propertiesToUpdate, + IEntityPropertiesProvider? keyProperties) + { + return new SqliteBulkInsertOrUpdateOptions + { + PropertiesToInsert = propertiesToInsert, + PropertiesToUpdate = propertiesToUpdate, + KeyProperties = keyProperties + }; + } + + /// + public async Task BulkInsertAsync( + IEnumerable entities, + IBulkInsertOptions options, + CancellationToken cancellationToken = default) + where T : class + { + ArgumentNullException.ThrowIfNull(entities); + ArgumentNullException.ThrowIfNull(options); + + var entityType = _ctx.Model.GetEntityType(typeof(T)); + var tableName = entityType.GetTableName() ?? throw new InvalidOperationException($"The entity '{entityType.Name}' has no table name."); + + if (options is not SqliteBulkInsertOptions sqliteOptions) + sqliteOptions = new SqliteBulkInsertOptions(options); + + await BulkInsertAsync(entityType, + entities, + entityType.GetSchema(), + tableName, + sqliteOptions, + SqliteBulkOperationContextFactoryForEntities.Instance, + cancellationToken); + } + + private async Task BulkInsertAsync( + IEntityType entityType, + IEnumerable entitiesOrValues, + string? schema, + string tableName, + SqliteBulkInsertOptions options, + ISqliteBulkOperationContextFactory bulkOperationContextFactory, + CancellationToken cancellationToken) + { + var properties = options.PropertiesToInsert.DeterminePropertiesForInsert(entityType, null); + properties.EnsureNoSeparateOwnedTypesInsideCollectionOwnedType(); + + var ctx = bulkOperationContextFactory.CreateForBulkInsert(_ctx, options, properties); + + await ExecuteBulkOperationAsync(entitiesOrValues, schema, tableName, ctx, cancellationToken); + } + + /// + public async Task BulkUpdateAsync( + IEnumerable entities, + IBulkUpdateOptions options, + CancellationToken cancellationToken = default) + where T : class + { + ArgumentNullException.ThrowIfNull(entities); + ArgumentNullException.ThrowIfNull(options); + + var entityType = _ctx.Model.GetEntityType(typeof(T)); + + if (options is not SqliteBulkUpdateOptions sqliteOptions) + sqliteOptions = new SqliteBulkUpdateOptions(options); + + var ctx = new BulkUpdateContext(_ctx, + _ctx.GetService(), + (SqliteConnection)_ctx.Database.GetDbConnection(), + options.KeyProperties.DetermineKeyProperties(entityType), + options.PropertiesToUpdate.DeterminePropertiesForUpdate(entityType, null), + sqliteOptions.AutoIncrementBehavior); + var tableName = entityType.GetTableName() + ?? throw new Exception($"The entity '{entityType.Name}' has no table name."); + + return await ExecuteBulkOperationAsync(entities, entityType.GetSchema(), tableName, ctx, cancellationToken); + } + + /// + public async Task BulkInsertOrUpdateAsync( + IEnumerable entities, + IBulkInsertOrUpdateOptions options, + CancellationToken cancellationToken = default) + where T : class + { + ArgumentNullException.ThrowIfNull(entities); + ArgumentNullException.ThrowIfNull(options); + + if (!(options is ISqliteBulkInsertOrUpdateOptions sqliteOptions)) + sqliteOptions = new SqliteBulkInsertOrUpdateOptions(options); + + var entityType = _ctx.Model.GetEntityType(typeof(T)); + var ctx = new BulkInsertOrUpdateContext(_ctx, + _ctx.GetService(), + (SqliteConnection)_ctx.Database.GetDbConnection(), + sqliteOptions.KeyProperties.DetermineKeyProperties(entityType), + sqliteOptions.PropertiesToInsert.DeterminePropertiesForInsert(entityType, null), + sqliteOptions.PropertiesToUpdate.DeterminePropertiesForUpdate(entityType, true), + sqliteOptions.AutoIncrementBehavior); + var tableName = entityType.GetTableName() + ?? throw new Exception($"The entity '{entityType.Name}' has no table name."); + + return await ExecuteBulkOperationAsync(entities, entityType.GetSchema(), tableName, ctx, cancellationToken); + } + + private async Task ExecuteBulkOperationAsync( + IEnumerable entitiesOrValues, + string? schema, + string tableName, + ISqliteBulkOperationContext bulkOperationContext, + CancellationToken cancellationToken) + { + await _ctx.Database.OpenConnectionAsync(cancellationToken).ConfigureAwait(false); + + try + { + var tableIdentifier = _sqlGenerationHelper.DelimitIdentifier(tableName, schema); + + using var reader = bulkOperationContext.CreateReader(entitiesOrValues); + var numberOfAffectedRows = await ExecuteBulkOperationAsync(reader, bulkOperationContext, tableIdentifier, cancellationToken).ConfigureAwait(false); + + if (bulkOperationContext.HasExternalProperties) + { + var readEntities = reader.GetReadEntities(); + numberOfAffectedRows += await ExecuteBulkOperationForSeparatedOwnedEntitiesAsync((IReadOnlyList)readEntities, bulkOperationContext, cancellationToken); + } + + return numberOfAffectedRows; + } + finally + { + await _ctx.Database.CloseConnectionAsync().ConfigureAwait(false); + } + } + + private async Task ExecuteBulkOperationForSeparatedOwnedEntitiesAsync( + IReadOnlyList parentEntities, + ISqliteBulkOperationContext parentBulkOperationContext, + CancellationToken cancellationToken) + { + if (parentEntities.Count == 0) + return 0; + + var numberOfAffectedRows = 0; + + foreach (var childContext in parentBulkOperationContext.GetChildren(parentEntities)) + { + var childTableName = childContext.EntityType.GetTableName() + ?? throw new InvalidOperationException($"The entity '{childContext.EntityType.Name}' has no table name."); + + numberOfAffectedRows += await ExecuteBulkOperationAsync(childContext.Entities, + childContext.EntityType.GetSchema(), + childTableName, + childContext, + cancellationToken).ConfigureAwait(false); + } + + return numberOfAffectedRows; + } + + private async Task ExecuteBulkOperationAsync( + IEntityDataReader reader, + ISqliteBulkOperationContext bulkOperationContext, + string tableIdentifier, + CancellationToken cancellationToken) + { + await using var command = bulkOperationContext.Connection.CreateCommand(); + + command.CommandText = bulkOperationContext.CreateCommandBuilder().GetStatement(_sqlGenerationHelper, _stringBuilderPool, reader, tableIdentifier); + var parameterInfos = CreateParameters(reader, command); + + try + { + command.Prepare(); + } + catch (SqliteException ex) + { + throw new InvalidOperationException($"Error during bulk operation on table '{tableIdentifier}'. See inner exception for more details.", ex); + } + + LogBulkOperationStart(command.CommandText); + var stopwatch = Stopwatch.StartNew(); + var numberOfAffectedRows = 0; + + while (reader.Read()) + { + for (var i = 0; i < reader.FieldCount; i++) + { + var paramInfo = parameterInfos[i]; + var value = reader.GetValue(i); + + if (bulkOperationContext.AutoIncrementBehavior == SqliteAutoIncrementBehavior.SetZeroToNull && paramInfo.IsAutoIncrementColumn && 0.Equals(value)) + value = null; + + paramInfo.Parameter.Value = value ?? DBNull.Value; + } + + numberOfAffectedRows += await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); + } + + stopwatch.Stop(); + LogBulkOperationEnd(command.CommandText, stopwatch.Elapsed); + + return numberOfAffectedRows; + } + + private static ParameterInfo[] CreateParameters(IEntityDataReader reader, SqliteCommand command) + { + var parameters = new ParameterInfo[reader.Properties.Count]; + + for (var i = 0; i < reader.Properties.Count; i++) + { + var property = reader.Properties[i]; + + var parameter = command.CreateParameter(); + parameter.ParameterName = $"$p{i}"; + parameters[i] = new ParameterInfo(parameter, property.Property.IsAutoIncrement()); + command.Parameters.Add(parameter); + } + + return parameters; + } + + private void LogBulkOperationStart(string statement) + { + _logger.Logger.LogDebug(EventIds.Started, + """ + Executing DbCommand + {Statement} + """, + statement); + } + + private void LogBulkOperationEnd(string statement, TimeSpan duration) + { + _logger.Logger.LogInformation(EventIds.Finished, + """ + Executed DbCommand ({Duration}ms) + {Statement} + """, + (long)duration.TotalMilliseconds, + statement); + } + + /// + public Task> BulkInsertValuesIntoTempTableAsync( + IEnumerable values, + ITempTableBulkInsertOptions options, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(values); + ArgumentNullException.ThrowIfNull(options); + + if (options is not SqliteTempTableBulkInsertOptions sqliteOptions) + sqliteOptions = new SqliteTempTableBulkInsertOptions(options); + + return BulkInsertIntoTempTableAsync>(values, + sqliteOptions, + SqliteBulkOperationContextFactoryForValues.Instance, + static query => query.Select(t => t.Column1), + cancellationToken); + } + + /// + public Task> BulkInsertIntoTempTableAsync( + IEnumerable entities, + ITempTableBulkInsertOptions options, + CancellationToken cancellationToken = default) + where T : class + { + ArgumentNullException.ThrowIfNull(entities); + ArgumentNullException.ThrowIfNull(options); + + if (options is not SqliteTempTableBulkInsertOptions sqliteOptions) + sqliteOptions = new SqliteTempTableBulkInsertOptions(options); + + return BulkInsertIntoTempTableAsync(entities, + sqliteOptions, + SqliteBulkOperationContextFactoryForEntities.Instance, + static query => query, + cancellationToken); + } + + private async Task> BulkInsertIntoTempTableAsync( + IEnumerable entitiesOrValues, + SqliteTempTableBulkInsertOptions options, + ISqliteBulkOperationContextFactory bulkOperationContextFactory, + Func, IQueryable> projection, + CancellationToken cancellationToken) + where TEntity : class + { + var type = typeof(TEntity); + var entityTypeName = EntityNameProvider.GetTempTableName(type); + var entityType = _ctx.Model.GetEntityType(entityTypeName, type); + + var tempTableCreator = _ctx.GetService(); + var tempTableCreationOptions = options.GetTempTableCreationOptions(); + var tempTableReference = await tempTableCreator.CreateTempTableAsync(entityType, tempTableCreationOptions, cancellationToken).ConfigureAwait(false); + + try + { + var bulkInsertOptions = options.GetBulkInsertOptions(); + await BulkInsertAsync(entityType, entitiesOrValues, null, tempTableReference.Name, bulkInsertOptions, bulkOperationContextFactory, cancellationToken).ConfigureAwait(false); + + var dbSet = entityType.Name == entityTypeName + ? _ctx.Set(entityTypeName) + : _ctx.Set(); + + var query = dbSet.FromTempTable(new TempTableInfo(tempTableReference.Name)); + + var pk = entityType.FindPrimaryKey(); + + if (pk is not null && pk.Properties.Count != 0) + query = query.AsNoTracking(); + + return new TempTableQuery(projection(query), tempTableReference); + } + catch (Exception) + { + await tempTableReference.DisposeAsync().ConfigureAwait(false); + throw; + } + } + + /// + public async Task TruncateTableAsync(CancellationToken cancellationToken = default) + where T : class + { + await TruncateTableAsync(typeof(T), cancellationToken); + } + + /// + public async Task TruncateTableAsync(Type type, CancellationToken cancellationToken = default) + { + var entityType = _ctx.Model.GetEntityType(type); + var tableName = entityType.GetTableName() + ?? throw new InvalidOperationException($"The entity '{entityType.Name}' has no table name."); + + var tableIdentifier = _sqlGenerationHelper.DelimitIdentifier(tableName, entityType.GetSchema()); + var truncateStatement = $"DELETE FROM {tableIdentifier};"; + + await _ctx.Database.ExecuteSqlRawAsync(truncateStatement, cancellationToken); + } + + private readonly record struct ParameterInfo(SqliteParameter Parameter, bool IsAutoIncrementColumn); +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkUpdateOptions.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkUpdateOptions.cs index 74061594..371403b7 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkUpdateOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteBulkUpdateOptions.cs @@ -1,37 +1,37 @@ -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Bulk update options for SQLite. -/// -public class SqliteBulkUpdateOptions : IBulkUpdateOptions -{ - /// - public IEntityPropertiesProvider? PropertiesToUpdate { get; set; } - - /// - public IEntityPropertiesProvider? KeyProperties { get; set; } - - /// - /// Behavior for auto-increment columns. - /// Default is - /// - public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; set; } - - /// - /// Initializes new instance of . - /// - /// Options to initialize from. - public SqliteBulkUpdateOptions(IBulkUpdateOptions? optionsToInitializeFrom = null) - { - AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull; - - if (optionsToInitializeFrom is not null) - { - PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate; - KeyProperties = optionsToInitializeFrom.KeyProperties; - - if (optionsToInitializeFrom is SqliteBulkUpdateOptions sqliteOptions) - AutoIncrementBehavior = sqliteOptions.AutoIncrementBehavior; - } - } -} +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Bulk update options for SQLite. +/// +public class SqliteBulkUpdateOptions : IBulkUpdateOptions +{ + /// + public IEntityPropertiesProvider? PropertiesToUpdate { get; set; } + + /// + public IEntityPropertiesProvider? KeyProperties { get; set; } + + /// + /// Behavior for auto-increment columns. + /// Default is + /// + public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; set; } + + /// + /// Initializes new instance of . + /// + /// Options to initialize from. + public SqliteBulkUpdateOptions(IBulkUpdateOptions? optionsToInitializeFrom = null) + { + AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull; + + if (optionsToInitializeFrom is not null) + { + PropertiesToUpdate = optionsToInitializeFrom.PropertiesToUpdate; + KeyProperties = optionsToInitializeFrom.KeyProperties; + + if (optionsToInitializeFrom is SqliteBulkUpdateOptions sqliteOptions) + AutoIncrementBehavior = sqliteOptions.AutoIncrementBehavior; + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteCommandBuilder.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteCommandBuilder.cs index 357b77de..f0d3a24c 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteCommandBuilder.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteCommandBuilder.cs @@ -1,269 +1,269 @@ -using System.Text; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.ObjectPool; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -internal abstract class SqliteCommandBuilder -{ - public static SqliteCommandBuilder Insert(IReadOnlyList propertiesToInsert) - { - return new SqliteInsertBuilder(propertiesToInsert); - } - - public static SqliteCommandBuilder Update(IReadOnlyList propertiesToUpdate, IReadOnlyList keyProperties) - { - return new SqliteUpdateBuilder(propertiesToUpdate, keyProperties); - } - - public static SqliteCommandBuilder InsertOrUpdate(IReadOnlyList propertiesToInsert, IReadOnlyList propertiesToUpdate, IReadOnlyList keyProperties) - { - return new SqliteInsertOrUpdateBuilder(propertiesToInsert, propertiesToUpdate, keyProperties); - } - - private static void GenerateInsertStatement( - StringBuilder sb, - ISqlGenerationHelper sqlGenerationHelper, - IEntityDataReader reader, - string tableIdentifier, - IReadOnlyList propertiesToInsert) - { - sb.Append("INSERT INTO ").Append(tableIdentifier).Append('('); - StoreObjectIdentifier? storeObject = null; - - for (var i = 0; i < propertiesToInsert.Count; i++) - { - if (i > 0) - sb.Append(", "); - - var property = propertiesToInsert[i]; - storeObject ??= property.GetStoreObject(); - var columnName = property.GetColumnName(storeObject.Value); - - sb.Append(sqlGenerationHelper.DelimitIdentifier(columnName)); - } - - sb.AppendLine(")") - .Append("VALUES ("); - - for (var i = 0; i < propertiesToInsert.Count; i++) - { - var index = reader.GetPropertyIndex(propertiesToInsert[i]); - - if (i > 0) - sb.Append(", "); - - sb.Append("$p").Append(index); - } - - sb.Append(")"); - } - - public abstract string GetStatement( - ISqlGenerationHelper sqlGenerationHelper, - ObjectPool stringBuilderPool, - IEntityDataReader reader, - string tableIdentifier); - - private class SqliteInsertBuilder : SqliteCommandBuilder - { - private readonly IReadOnlyList _propertiesToInsert; - - public SqliteInsertBuilder(IReadOnlyList propertiesToInsert) - { - _propertiesToInsert = propertiesToInsert; - } - - /// - public override string GetStatement( - ISqlGenerationHelper sqlGenerationHelper, - ObjectPool stringBuilderPool, - IEntityDataReader reader, - string tableIdentifier) - { - var sb = stringBuilderPool.Get(); - - try - { - GenerateInsertStatement(sb, sqlGenerationHelper, reader, tableIdentifier, _propertiesToInsert); - - sb.Append(sqlGenerationHelper.StatementTerminator); - - return sb.ToString(); - } - finally - { - stringBuilderPool.Return(sb); - } - } - } - - private class SqliteUpdateBuilder : SqliteCommandBuilder - { - private readonly IReadOnlyList _propertiesToUpdate; - private readonly IReadOnlyList _keyProperties; - - public SqliteUpdateBuilder( - IReadOnlyList propertiesToUpdate, - IReadOnlyList keyProperties) - { - _propertiesToUpdate = propertiesToUpdate; - _keyProperties = keyProperties; - } - - public override string GetStatement( - ISqlGenerationHelper sqlGenerationHelper, - ObjectPool stringBuilderPool, - IEntityDataReader reader, - string tableIdentifier) - { - var sb = stringBuilderPool.Get(); - - try - { - sb.Append("UPDATE ").AppendLine(tableIdentifier); - - GenerateSetAndWhereClause(sb, sqlGenerationHelper, reader, _propertiesToUpdate, _keyProperties); - - sb.Append(sqlGenerationHelper.StatementTerminator); - - return sb.ToString(); - } - finally - { - stringBuilderPool.Return(sb); - } - } - - public static bool GenerateSetAndWhereClause( - StringBuilder sb, - ISqlGenerationHelper sqlGenerationHelper, - IEntityDataReader reader, - IReadOnlyList propertiesToUpdate, - IReadOnlyList keyProperties) - { - StoreObjectIdentifier? storeObject = null; - var isFirst = true; - - foreach (var property in propertiesToUpdate.Where(p => p.Navigations.Count > 0 || !keyProperties.Contains(p.Property))) - { - if (!isFirst) - { - sb.Append(", "); - } - else - { - sb.Append("SET "); - } - - storeObject ??= property.GetStoreObject(); - var columnName = property.GetColumnName(storeObject.Value); - - sb.Append(sqlGenerationHelper.DelimitIdentifier(columnName)) - .Append(" = $p").Append(reader.GetPropertyIndex(property)); - - isFirst = false; - } - - // if no properies to update return - if (isFirst) - return false; - - sb.Append(" WHERE "); - - for (var i = 0; i < keyProperties.Count; i++) - { - if (i > 0) - sb.Append(" AND "); - - var property = keyProperties[i]; - var index = reader.GetPropertyIndex(property); - storeObject ??= property.GetStoreObject(); - var columnName = property.GetColumnName(storeObject.Value) - ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.DeclaringType.Name}'."); - var escapedColumnName = sqlGenerationHelper.DelimitIdentifier(columnName); - - sb.Append("(").Append(escapedColumnName).Append(" = $p").Append(index); - - if (property.IsNullable) - sb.Append(" OR ").Append(escapedColumnName).Append(" IS NULL AND $p").Append(index).Append(" IS NULL"); - - sb.Append(")"); - } - - return true; - } - } - - private class SqliteInsertOrUpdateBuilder : SqliteCommandBuilder - { - private readonly IReadOnlyList _propertiesToInsert; - private readonly IReadOnlyList _propertiesToUpdate; - private readonly IReadOnlyList _keyProperties; - - public SqliteInsertOrUpdateBuilder( - IReadOnlyList propertiesToInsert, - IReadOnlyList propertiesToUpdate, - IReadOnlyList keyProperties) - { - _propertiesToInsert = propertiesToInsert; - _propertiesToUpdate = propertiesToUpdate; - _keyProperties = keyProperties; - } - - public override string GetStatement( - ISqlGenerationHelper sqlGenerationHelper, - ObjectPool stringBuilderPool, - IEntityDataReader reader, - string tableIdentifier) - { - var sb = stringBuilderPool.Get(); - - try - { - GenerateInsertStatement(sb, sqlGenerationHelper, reader, tableIdentifier, _propertiesToInsert.Union(_keyProperties.Select(p => new PropertyWithNavigations(p, Array.Empty()))).ToList()); - - sb.AppendLine() - .Append("\tON CONFLICT("); - - StoreObjectIdentifier? storeObject = null; - - for (var i = 0; i < _keyProperties.Count; i++) - { - if (i > 0) - sb.Append(", "); - - var property = _keyProperties[i]; - storeObject ??= property.GetStoreObject(); - var columnName = property.GetColumnName(storeObject.Value) - ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.DeclaringType.Name}'."); - - var escapedColumnName = sqlGenerationHelper.DelimitIdentifier(columnName); - sb.Append(escapedColumnName); - } - - sb.AppendLine(") DO"); - var sbIndexBeforeUpdate = sb.Length; - - sb.Append("\tUPDATE "); - var hasPropertiesToUpdate = SqliteUpdateBuilder.GenerateSetAndWhereClause(sb, sqlGenerationHelper, reader, _propertiesToUpdate, _keyProperties); - - if (!hasPropertiesToUpdate) - { - sb.Length = sbIndexBeforeUpdate; - sb.Append("\tNOTHING"); - } - - sb.Append(sqlGenerationHelper.StatementTerminator); - - return sb.ToString(); - } - finally - { - stringBuilderPool.Return(sb); - } - } - } -} +using System.Text; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.ObjectPool; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +internal abstract class SqliteCommandBuilder +{ + public static SqliteCommandBuilder Insert(IReadOnlyList propertiesToInsert) + { + return new SqliteInsertBuilder(propertiesToInsert); + } + + public static SqliteCommandBuilder Update(IReadOnlyList propertiesToUpdate, IReadOnlyList keyProperties) + { + return new SqliteUpdateBuilder(propertiesToUpdate, keyProperties); + } + + public static SqliteCommandBuilder InsertOrUpdate(IReadOnlyList propertiesToInsert, IReadOnlyList propertiesToUpdate, IReadOnlyList keyProperties) + { + return new SqliteInsertOrUpdateBuilder(propertiesToInsert, propertiesToUpdate, keyProperties); + } + + private static void GenerateInsertStatement( + StringBuilder sb, + ISqlGenerationHelper sqlGenerationHelper, + IEntityDataReader reader, + string tableIdentifier, + IReadOnlyList propertiesToInsert) + { + sb.Append("INSERT INTO ").Append(tableIdentifier).Append('('); + StoreObjectIdentifier? storeObject = null; + + for (var i = 0; i < propertiesToInsert.Count; i++) + { + if (i > 0) + sb.Append(", "); + + var property = propertiesToInsert[i]; + storeObject ??= property.GetStoreObject(); + var columnName = property.GetColumnName(storeObject.Value); + + sb.Append(sqlGenerationHelper.DelimitIdentifier(columnName)); + } + + sb.AppendLine(")") + .Append("VALUES ("); + + for (var i = 0; i < propertiesToInsert.Count; i++) + { + var index = reader.GetPropertyIndex(propertiesToInsert[i]); + + if (i > 0) + sb.Append(", "); + + sb.Append("$p").Append(index); + } + + sb.Append(")"); + } + + public abstract string GetStatement( + ISqlGenerationHelper sqlGenerationHelper, + ObjectPool stringBuilderPool, + IEntityDataReader reader, + string tableIdentifier); + + private class SqliteInsertBuilder : SqliteCommandBuilder + { + private readonly IReadOnlyList _propertiesToInsert; + + public SqliteInsertBuilder(IReadOnlyList propertiesToInsert) + { + _propertiesToInsert = propertiesToInsert; + } + + /// + public override string GetStatement( + ISqlGenerationHelper sqlGenerationHelper, + ObjectPool stringBuilderPool, + IEntityDataReader reader, + string tableIdentifier) + { + var sb = stringBuilderPool.Get(); + + try + { + GenerateInsertStatement(sb, sqlGenerationHelper, reader, tableIdentifier, _propertiesToInsert); + + sb.Append(sqlGenerationHelper.StatementTerminator); + + return sb.ToString(); + } + finally + { + stringBuilderPool.Return(sb); + } + } + } + + private class SqliteUpdateBuilder : SqliteCommandBuilder + { + private readonly IReadOnlyList _propertiesToUpdate; + private readonly IReadOnlyList _keyProperties; + + public SqliteUpdateBuilder( + IReadOnlyList propertiesToUpdate, + IReadOnlyList keyProperties) + { + _propertiesToUpdate = propertiesToUpdate; + _keyProperties = keyProperties; + } + + public override string GetStatement( + ISqlGenerationHelper sqlGenerationHelper, + ObjectPool stringBuilderPool, + IEntityDataReader reader, + string tableIdentifier) + { + var sb = stringBuilderPool.Get(); + + try + { + sb.Append("UPDATE ").AppendLine(tableIdentifier); + + GenerateSetAndWhereClause(sb, sqlGenerationHelper, reader, _propertiesToUpdate, _keyProperties); + + sb.Append(sqlGenerationHelper.StatementTerminator); + + return sb.ToString(); + } + finally + { + stringBuilderPool.Return(sb); + } + } + + public static bool GenerateSetAndWhereClause( + StringBuilder sb, + ISqlGenerationHelper sqlGenerationHelper, + IEntityDataReader reader, + IReadOnlyList propertiesToUpdate, + IReadOnlyList keyProperties) + { + StoreObjectIdentifier? storeObject = null; + var isFirst = true; + + foreach (var property in propertiesToUpdate.Where(p => p.Navigations.Count > 0 || !keyProperties.Contains(p.Property))) + { + if (!isFirst) + { + sb.Append(", "); + } + else + { + sb.Append("SET "); + } + + storeObject ??= property.GetStoreObject(); + var columnName = property.GetColumnName(storeObject.Value); + + sb.Append(sqlGenerationHelper.DelimitIdentifier(columnName)) + .Append(" = $p").Append(reader.GetPropertyIndex(property)); + + isFirst = false; + } + + // if no properies to update return + if (isFirst) + return false; + + sb.Append(" WHERE "); + + for (var i = 0; i < keyProperties.Count; i++) + { + if (i > 0) + sb.Append(" AND "); + + var property = keyProperties[i]; + var index = reader.GetPropertyIndex(property); + storeObject ??= property.GetStoreObject(); + var columnName = property.GetColumnName(storeObject.Value) + ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.DeclaringType.Name}'."); + var escapedColumnName = sqlGenerationHelper.DelimitIdentifier(columnName); + + sb.Append("(").Append(escapedColumnName).Append(" = $p").Append(index); + + if (property.IsNullable) + sb.Append(" OR ").Append(escapedColumnName).Append(" IS NULL AND $p").Append(index).Append(" IS NULL"); + + sb.Append(")"); + } + + return true; + } + } + + private class SqliteInsertOrUpdateBuilder : SqliteCommandBuilder + { + private readonly IReadOnlyList _propertiesToInsert; + private readonly IReadOnlyList _propertiesToUpdate; + private readonly IReadOnlyList _keyProperties; + + public SqliteInsertOrUpdateBuilder( + IReadOnlyList propertiesToInsert, + IReadOnlyList propertiesToUpdate, + IReadOnlyList keyProperties) + { + _propertiesToInsert = propertiesToInsert; + _propertiesToUpdate = propertiesToUpdate; + _keyProperties = keyProperties; + } + + public override string GetStatement( + ISqlGenerationHelper sqlGenerationHelper, + ObjectPool stringBuilderPool, + IEntityDataReader reader, + string tableIdentifier) + { + var sb = stringBuilderPool.Get(); + + try + { + GenerateInsertStatement(sb, sqlGenerationHelper, reader, tableIdentifier, _propertiesToInsert.Union(_keyProperties.Select(p => new PropertyWithNavigations(p, Array.Empty()))).ToList()); + + sb.AppendLine() + .Append("\tON CONFLICT("); + + StoreObjectIdentifier? storeObject = null; + + for (var i = 0; i < _keyProperties.Count; i++) + { + if (i > 0) + sb.Append(", "); + + var property = _keyProperties[i]; + storeObject ??= property.GetStoreObject(); + var columnName = property.GetColumnName(storeObject.Value) + ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.DeclaringType.Name}'."); + + var escapedColumnName = sqlGenerationHelper.DelimitIdentifier(columnName); + sb.Append(escapedColumnName); + } + + sb.AppendLine(") DO"); + var sbIndexBeforeUpdate = sb.Length; + + sb.Append("\tUPDATE "); + var hasPropertiesToUpdate = SqliteUpdateBuilder.GenerateSetAndWhereClause(sb, sqlGenerationHelper, reader, _propertiesToUpdate, _keyProperties); + + if (!hasPropertiesToUpdate) + { + sb.Length = sbIndexBeforeUpdate; + sb.Append("\tNOTHING"); + } + + sb.Append(sqlGenerationHelper.StatementTerminator); + + return sb.ToString(); + } + finally + { + stringBuilderPool.Return(sb); + } + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteTempTableBulkInsertOptions.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteTempTableBulkInsertOptions.cs index dd7f0a31..b539b5a0 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteTempTableBulkInsertOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/BulkOperations/SqliteTempTableBulkInsertOptions.cs @@ -1,111 +1,111 @@ -using System.Linq; -using Thinktecture.EntityFrameworkCore.TempTables; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations; - -/// -/// Bulk insert options for SQLite. -/// -public sealed class SqliteTempTableBulkInsertOptions : ITempTableBulkInsertOptions -{ - /// - /// Behavior for auto-increment columns. - /// Default is - /// - public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; set; } - - /// - public bool TruncateTableIfExists { get; set; } - - /// - public bool DropTableOnDispose { get; set; } - - /// - public ITempTableNameProvider? TableNameProvider { get; set; } - - /// - public IPrimaryKeyPropertiesProvider? PrimaryKeyCreation { get; set; } - - /// - public IEntityPropertiesProvider? PropertiesToInsert { get; set; } - - /// - /// Advanced settings. - /// - public AdvancedSqliteTempTableBulkInsertOptions Advanced { get; } - - /// - /// Initializes new instance of . - /// - public SqliteTempTableBulkInsertOptions(ITempTableBulkInsertOptions? optionsToInitializeFrom = null) - { - Advanced = new AdvancedSqliteTempTableBulkInsertOptions(); - - if (optionsToInitializeFrom is null) - { - DropTableOnDispose = true; - AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull; - } - else - { - TruncateTableIfExists = optionsToInitializeFrom.TruncateTableIfExists; - DropTableOnDispose = optionsToInitializeFrom.DropTableOnDispose; - TableNameProvider = optionsToInitializeFrom.TableNameProvider; - PrimaryKeyCreation = optionsToInitializeFrom.PrimaryKeyCreation; - PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert; - - if (optionsToInitializeFrom is SqliteTempTableBulkInsertOptions sqliteOptions) - { - AutoIncrementBehavior = sqliteOptions.AutoIncrementBehavior; - Advanced.UsePropertiesToInsertForTempTableCreation = sqliteOptions.Advanced.UsePropertiesToInsertForTempTableCreation; - } - } - } - - /// - /// Gets options for creation of the temp table. - /// - public TempTableCreationOptions GetTempTableCreationOptions() - { - return new TempTableCreationOptions - { - TruncateTableIfExists = TruncateTableIfExists, - DropTableOnDispose = DropTableOnDispose, - TableNameProvider = TableNameProvider, - PrimaryKeyCreation = PrimaryKeyCreation, - PropertiesToInclude = Advanced.UsePropertiesToInsertForTempTableCreation ? PropertiesToInsert : null - }; - } - - /// - /// Get options for bulk insert. - /// - public SqliteBulkInsertOptions GetBulkInsertOptions() - { - return new SqliteBulkInsertOptions - { - AutoIncrementBehavior = AutoIncrementBehavior, - PropertiesToInsert = PropertiesToInsert - }; - } - - /// - /// Advanced options. - /// - public class AdvancedSqliteTempTableBulkInsertOptions - { - /// - /// By default all properties of the corresponding entity are used to create the temp table, - /// i.e. the are ignored. - /// This approach ensures that the returned doesn't throw an exception on read. - /// The drawback is that all required (i.e. non-null) properties must be set accordingly and included into . - /// - /// If a use case requires the use of a subset of properties of the corresponding entity then set this setting to true. - /// In this case the temp table is created by using only. - /// The omitted properties must not be selected or used by the returned . - /// - /// Default is false. - /// - public bool UsePropertiesToInsertForTempTableCreation { get; set; } - } -} +using System.Linq; +using Thinktecture.EntityFrameworkCore.TempTables; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations; + +/// +/// Bulk insert options for SQLite. +/// +public sealed class SqliteTempTableBulkInsertOptions : ITempTableBulkInsertOptions +{ + /// + /// Behavior for auto-increment columns. + /// Default is + /// + public SqliteAutoIncrementBehavior AutoIncrementBehavior { get; set; } + + /// + public bool TruncateTableIfExists { get; set; } + + /// + public bool DropTableOnDispose { get; set; } + + /// + public ITempTableNameProvider? TableNameProvider { get; set; } + + /// + public IPrimaryKeyPropertiesProvider? PrimaryKeyCreation { get; set; } + + /// + public IEntityPropertiesProvider? PropertiesToInsert { get; set; } + + /// + /// Advanced settings. + /// + public AdvancedSqliteTempTableBulkInsertOptions Advanced { get; } + + /// + /// Initializes new instance of . + /// + public SqliteTempTableBulkInsertOptions(ITempTableBulkInsertOptions? optionsToInitializeFrom = null) + { + Advanced = new AdvancedSqliteTempTableBulkInsertOptions(); + + if (optionsToInitializeFrom is null) + { + DropTableOnDispose = true; + AutoIncrementBehavior = SqliteAutoIncrementBehavior.SetZeroToNull; + } + else + { + TruncateTableIfExists = optionsToInitializeFrom.TruncateTableIfExists; + DropTableOnDispose = optionsToInitializeFrom.DropTableOnDispose; + TableNameProvider = optionsToInitializeFrom.TableNameProvider; + PrimaryKeyCreation = optionsToInitializeFrom.PrimaryKeyCreation; + PropertiesToInsert = optionsToInitializeFrom.PropertiesToInsert; + + if (optionsToInitializeFrom is SqliteTempTableBulkInsertOptions sqliteOptions) + { + AutoIncrementBehavior = sqliteOptions.AutoIncrementBehavior; + Advanced.UsePropertiesToInsertForTempTableCreation = sqliteOptions.Advanced.UsePropertiesToInsertForTempTableCreation; + } + } + } + + /// + /// Gets options for creation of the temp table. + /// + public TempTableCreationOptions GetTempTableCreationOptions() + { + return new TempTableCreationOptions + { + TruncateTableIfExists = TruncateTableIfExists, + DropTableOnDispose = DropTableOnDispose, + TableNameProvider = TableNameProvider, + PrimaryKeyCreation = PrimaryKeyCreation, + PropertiesToInclude = Advanced.UsePropertiesToInsertForTempTableCreation ? PropertiesToInsert : null + }; + } + + /// + /// Get options for bulk insert. + /// + public SqliteBulkInsertOptions GetBulkInsertOptions() + { + return new SqliteBulkInsertOptions + { + AutoIncrementBehavior = AutoIncrementBehavior, + PropertiesToInsert = PropertiesToInsert + }; + } + + /// + /// Advanced options. + /// + public class AdvancedSqliteTempTableBulkInsertOptions + { + /// + /// By default all properties of the corresponding entity are used to create the temp table, + /// i.e. the are ignored. + /// This approach ensures that the returned doesn't throw an exception on read. + /// The drawback is that all required (i.e. non-null) properties must be set accordingly and included into . + /// + /// If a use case requires the use of a subset of properties of the corresponding entity then set this setting to true. + /// In this case the temp table is created by using only. + /// The omitted properties must not be selected or used by the returned . + /// + /// Default is false. + /// + public bool UsePropertiesToInsertForTempTableCreation { get; set; } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Infrastructure/SqliteDbContextOptionsExtension.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Infrastructure/SqliteDbContextOptionsExtension.cs index aca12f2c..bc0cfa63 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Infrastructure/SqliteDbContextOptionsExtension.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Infrastructure/SqliteDbContextOptionsExtension.cs @@ -1,194 +1,194 @@ -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Text; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Query.Internal; -using Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Thinktecture.EntityFrameworkCore.BulkOperations; -using Thinktecture.EntityFrameworkCore.Query; -using Thinktecture.EntityFrameworkCore.TempTables; - -namespace Thinktecture.EntityFrameworkCore.Infrastructure; - -/// -/// Extensions for DbContextOptions. -/// -public sealed class SqliteDbContextOptionsExtension : DbContextOptionsExtensionBase, IDbContextOptionsExtension -{ - private readonly RelationalDbContextOptionsExtension _relationalOptions; - - private SqliteDbContextOptionsExtensionInfo? _info; - - /// - public DbContextOptionsExtensionInfo Info => _info ??= new SqliteDbContextOptionsExtensionInfo(this); - - /// - /// Enables and disables support for "RowNumber". - /// - [Obsolete($"Use '{nameof(AddWindowFunctionsSupport)}' instead.")] - public bool AddRowNumberSupport - { - get => _relationalOptions.AddWindowFunctionsSupport; - set => _relationalOptions.AddWindowFunctionsSupport = value; - } - - /// - /// Enables and disables support for window functions like "RowNumber". - /// - public bool AddWindowFunctionsSupport - { - get => _relationalOptions.AddWindowFunctionsSupport; - set => _relationalOptions.AddWindowFunctionsSupport = value; - } - - private bool _addCustomQueryableMethodTranslatingExpressionVisitorFactory; - - /// - /// A custom factory is registered if true. - /// The factory is required to be able to translate custom methods like . - /// - public bool AddCustomQueryableMethodTranslatingExpressionVisitorFactory - { - get => _addCustomQueryableMethodTranslatingExpressionVisitorFactory || AddBulkOperationSupport || AddWindowFunctionsSupport; - set => _addCustomQueryableMethodTranslatingExpressionVisitorFactory = value; - } - - private bool _addCustomRelationalParameterBasedSqlProcessorFactory; - - /// - /// A custom factory is registered if true. - /// The factory is required for some features. - /// - public bool AddCustomRelationalParameterBasedSqlProcessorFactory - { - get => _addCustomRelationalParameterBasedSqlProcessorFactory || AddBulkOperationSupport || AddWindowFunctionsSupport; - set => _addCustomRelationalParameterBasedSqlProcessorFactory = value; - } - - private bool _addCustomQuerySqlGeneratorFactory; - - /// - /// A custom factory is registered if true. - /// The factory is required for some features like for generation of 'DELETE' statements. - /// - public bool AddCustomQuerySqlGeneratorFactory - { - get => _addCustomQuerySqlGeneratorFactory || AddBulkOperationSupport; - set => _addCustomQuerySqlGeneratorFactory = value; - } - - /// - /// Enables and disables support for bulk operations and temp tables. - /// - public bool AddBulkOperationSupport { get; set; } - - /// - /// Indication whether to configure temp tables for primitive types. - /// - public bool ConfigureTempTablesForPrimitiveTypes { get; set; } - - /// - /// Initializes new instance of . - /// - /// An instance of . - public SqliteDbContextOptionsExtension(RelationalDbContextOptionsExtension relationalOptions) - { - _relationalOptions = relationalOptions ?? throw new ArgumentNullException(nameof(relationalOptions)); - } - - /// - [SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] - public void ApplyServices(IServiceCollection services) - { - services.TryAddSingleton(); - services.AddSingleton(provider => provider.GetRequiredService()); - services.AddSingleton(provider => provider.GetRequiredService()); - - if (AddCustomQueryableMethodTranslatingExpressionVisitorFactory) - AddWithCheck(services); - - if (AddCustomQuerySqlGeneratorFactory) - AddWithCheck(services); - - if (AddCustomRelationalParameterBasedSqlProcessorFactory) - AddWithCheck(services); - - if (AddBulkOperationSupport) - { - services.Add(GetLifetime()); - - services.AddSingleton>(); - services.TryAddScoped(); - services.AddTempTableSuffixComponents(); - - AddEntityDataReader(services); - services.TryAddScoped(); - services.TryAddScoped(provider => provider.GetRequiredService()); - services.TryAddScoped(provider => provider.GetRequiredService()); - services.TryAddScoped(provider => provider.GetRequiredService()); - services.TryAddScoped(provider => provider.GetRequiredService()); - services.TryAddScoped(provider => provider.GetRequiredService()); - } - } - - /// - public void Validate(IDbContextOptions options) - { - } - - private class SqliteDbContextOptionsExtensionInfo : DbContextOptionsExtensionInfo - { - private readonly SqliteDbContextOptionsExtension _extension; - public override bool IsDatabaseProvider => false; - - private string? _logFragment; - - public override string LogFragment => _logFragment ??= CreateLogFragment(); - - private string CreateLogFragment() - { - return _extension.AddBulkOperationSupport ? "BulkOperationSupport " : String.Empty; - } - - /// - public SqliteDbContextOptionsExtensionInfo(SqliteDbContextOptionsExtension extension) - : base(extension) - { - _extension = extension ?? throw new ArgumentNullException(nameof(extension)); - } - - /// - public override int GetServiceProviderHashCode() - { - return HashCode.Combine(_extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory, - _extension.AddCustomQuerySqlGeneratorFactory, - _extension.AddCustomRelationalParameterBasedSqlProcessorFactory, - _extension.AddBulkOperationSupport, - _extension.ConfigureTempTablesForPrimitiveTypes); - } - - /// - public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) - { - return other is SqliteDbContextOptionsExtensionInfo otherSqliteInfo - && _extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory == otherSqliteInfo._extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory - && _extension.AddCustomQuerySqlGeneratorFactory == otherSqliteInfo._extension.AddCustomQuerySqlGeneratorFactory - && _extension.AddCustomRelationalParameterBasedSqlProcessorFactory == otherSqliteInfo._extension.AddCustomRelationalParameterBasedSqlProcessorFactory - && _extension.AddBulkOperationSupport == otherSqliteInfo._extension.AddBulkOperationSupport - && _extension.ConfigureTempTablesForPrimitiveTypes == otherSqliteInfo._extension.ConfigureTempTablesForPrimitiveTypes; - } - - /// - public override void PopulateDebugInfo(IDictionary debugInfo) - { - debugInfo["Thinktecture:CustomQueryableMethodTranslatingExpressionVisitorFactory"] = _extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:CustomQuerySqlGeneratorFactory"] = _extension.AddCustomQuerySqlGeneratorFactory.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:CustomRelationalParameterBasedSqlProcessorFactory"] = _extension.AddCustomRelationalParameterBasedSqlProcessorFactory.ToString(CultureInfo.InvariantCulture); - debugInfo["Thinktecture:BulkOperationSupport"] = _extension.AddBulkOperationSupport.ToString(CultureInfo.InvariantCulture); - } - } -} +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.Internal; +using Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Thinktecture.EntityFrameworkCore.BulkOperations; +using Thinktecture.EntityFrameworkCore.Query; +using Thinktecture.EntityFrameworkCore.TempTables; + +namespace Thinktecture.EntityFrameworkCore.Infrastructure; + +/// +/// Extensions for DbContextOptions. +/// +public sealed class SqliteDbContextOptionsExtension : DbContextOptionsExtensionBase, IDbContextOptionsExtension +{ + private readonly RelationalDbContextOptionsExtension _relationalOptions; + + private SqliteDbContextOptionsExtensionInfo? _info; + + /// + public DbContextOptionsExtensionInfo Info => _info ??= new SqliteDbContextOptionsExtensionInfo(this); + + /// + /// Enables and disables support for "RowNumber". + /// + [Obsolete($"Use '{nameof(AddWindowFunctionsSupport)}' instead.")] + public bool AddRowNumberSupport + { + get => _relationalOptions.AddWindowFunctionsSupport; + set => _relationalOptions.AddWindowFunctionsSupport = value; + } + + /// + /// Enables and disables support for window functions like "RowNumber". + /// + public bool AddWindowFunctionsSupport + { + get => _relationalOptions.AddWindowFunctionsSupport; + set => _relationalOptions.AddWindowFunctionsSupport = value; + } + + private bool _addCustomQueryableMethodTranslatingExpressionVisitorFactory; + + /// + /// A custom factory is registered if true. + /// The factory is required to be able to translate custom methods like . + /// + public bool AddCustomQueryableMethodTranslatingExpressionVisitorFactory + { + get => _addCustomQueryableMethodTranslatingExpressionVisitorFactory || AddBulkOperationSupport || AddWindowFunctionsSupport; + set => _addCustomQueryableMethodTranslatingExpressionVisitorFactory = value; + } + + private bool _addCustomRelationalParameterBasedSqlProcessorFactory; + + /// + /// A custom factory is registered if true. + /// The factory is required for some features. + /// + public bool AddCustomRelationalParameterBasedSqlProcessorFactory + { + get => _addCustomRelationalParameterBasedSqlProcessorFactory || AddBulkOperationSupport || AddWindowFunctionsSupport; + set => _addCustomRelationalParameterBasedSqlProcessorFactory = value; + } + + private bool _addCustomQuerySqlGeneratorFactory; + + /// + /// A custom factory is registered if true. + /// The factory is required for some features like for generation of 'DELETE' statements. + /// + public bool AddCustomQuerySqlGeneratorFactory + { + get => _addCustomQuerySqlGeneratorFactory || AddBulkOperationSupport; + set => _addCustomQuerySqlGeneratorFactory = value; + } + + /// + /// Enables and disables support for bulk operations and temp tables. + /// + public bool AddBulkOperationSupport { get; set; } + + /// + /// Indication whether to configure temp tables for primitive types. + /// + public bool ConfigureTempTablesForPrimitiveTypes { get; set; } + + /// + /// Initializes new instance of . + /// + /// An instance of . + public SqliteDbContextOptionsExtension(RelationalDbContextOptionsExtension relationalOptions) + { + _relationalOptions = relationalOptions ?? throw new ArgumentNullException(nameof(relationalOptions)); + } + + /// + [SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] + public void ApplyServices(IServiceCollection services) + { + services.TryAddSingleton(); + services.AddSingleton(provider => provider.GetRequiredService()); + services.AddSingleton(provider => provider.GetRequiredService()); + + if (AddCustomQueryableMethodTranslatingExpressionVisitorFactory) + AddWithCheck(services); + + if (AddCustomQuerySqlGeneratorFactory) + AddWithCheck(services); + + if (AddCustomRelationalParameterBasedSqlProcessorFactory) + AddWithCheck(services); + + if (AddBulkOperationSupport) + { + services.Add(GetLifetime()); + + services.AddSingleton>(); + services.TryAddScoped(); + services.AddTempTableSuffixComponents(); + + AddEntityDataReader(services); + services.TryAddScoped(); + services.TryAddScoped(provider => provider.GetRequiredService()); + services.TryAddScoped(provider => provider.GetRequiredService()); + services.TryAddScoped(provider => provider.GetRequiredService()); + services.TryAddScoped(provider => provider.GetRequiredService()); + services.TryAddScoped(provider => provider.GetRequiredService()); + } + } + + /// + public void Validate(IDbContextOptions options) + { + } + + private class SqliteDbContextOptionsExtensionInfo : DbContextOptionsExtensionInfo + { + private readonly SqliteDbContextOptionsExtension _extension; + public override bool IsDatabaseProvider => false; + + private string? _logFragment; + + public override string LogFragment => _logFragment ??= CreateLogFragment(); + + private string CreateLogFragment() + { + return _extension.AddBulkOperationSupport ? "BulkOperationSupport " : String.Empty; + } + + /// + public SqliteDbContextOptionsExtensionInfo(SqliteDbContextOptionsExtension extension) + : base(extension) + { + _extension = extension ?? throw new ArgumentNullException(nameof(extension)); + } + + /// + public override int GetServiceProviderHashCode() + { + return HashCode.Combine(_extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory, + _extension.AddCustomQuerySqlGeneratorFactory, + _extension.AddCustomRelationalParameterBasedSqlProcessorFactory, + _extension.AddBulkOperationSupport, + _extension.ConfigureTempTablesForPrimitiveTypes); + } + + /// + public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) + { + return other is SqliteDbContextOptionsExtensionInfo otherSqliteInfo + && _extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory == otherSqliteInfo._extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory + && _extension.AddCustomQuerySqlGeneratorFactory == otherSqliteInfo._extension.AddCustomQuerySqlGeneratorFactory + && _extension.AddCustomRelationalParameterBasedSqlProcessorFactory == otherSqliteInfo._extension.AddCustomRelationalParameterBasedSqlProcessorFactory + && _extension.AddBulkOperationSupport == otherSqliteInfo._extension.AddBulkOperationSupport + && _extension.ConfigureTempTablesForPrimitiveTypes == otherSqliteInfo._extension.ConfigureTempTablesForPrimitiveTypes; + } + + /// + public override void PopulateDebugInfo(IDictionary debugInfo) + { + debugInfo["Thinktecture:CustomQueryableMethodTranslatingExpressionVisitorFactory"] = _extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:CustomQuerySqlGeneratorFactory"] = _extension.AddCustomQuerySqlGeneratorFactory.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:CustomRelationalParameterBasedSqlProcessorFactory"] = _extension.AddCustomRelationalParameterBasedSqlProcessorFactory.ToString(CultureInfo.InvariantCulture); + debugInfo["Thinktecture:BulkOperationSupport"] = _extension.AddBulkOperationSupport.ToString(CultureInfo.InvariantCulture); + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureRelationalParameterBasedSqlProcessor.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureRelationalParameterBasedSqlProcessor.cs index f65cd82c..e24ef7f1 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureRelationalParameterBasedSqlProcessor.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureRelationalParameterBasedSqlProcessor.cs @@ -1,30 +1,30 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; - -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -/// Extends . -/// -[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] -public class ThinktectureSqliteParameterBasedSqlProcessor : SqliteParameterBasedSqlProcessor -{ - /// - public ThinktectureSqliteParameterBasedSqlProcessor( - RelationalParameterBasedSqlProcessorDependencies dependencies, - RelationalParameterBasedSqlProcessorParameters parameters) - : base(dependencies, parameters) - { - } - - /// - protected override Expression ProcessSqlNullability(Expression expression, IReadOnlyDictionary parametersValues, out bool canCache) - { - ArgumentNullException.ThrowIfNull(expression); - ArgumentNullException.ThrowIfNull(parametersValues); - - return new ThinktectureSqlNullabilityProcessor(Dependencies, Parameters).Process(expression, parametersValues, out canCache); - } -} +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; + +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +/// Extends . +/// +[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.")] +public class ThinktectureSqliteParameterBasedSqlProcessor : SqliteParameterBasedSqlProcessor +{ + /// + public ThinktectureSqliteParameterBasedSqlProcessor( + RelationalParameterBasedSqlProcessorDependencies dependencies, + RelationalParameterBasedSqlProcessorParameters parameters) + : base(dependencies, parameters) + { + } + + /// + protected override Expression ProcessSqlNullability(Expression expression, IReadOnlyDictionary parametersValues, out bool canCache) + { + ArgumentNullException.ThrowIfNull(expression); + ArgumentNullException.ThrowIfNull(parametersValues); + + return new ThinktectureSqlNullabilityProcessor(Dependencies, Parameters).Process(expression, parametersValues, out canCache); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureRelationalParameterBasedSqlProcessorFactory.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureRelationalParameterBasedSqlProcessorFactory.cs index 409a723b..3f56f7dd 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureRelationalParameterBasedSqlProcessorFactory.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureRelationalParameterBasedSqlProcessorFactory.cs @@ -1,25 +1,25 @@ -using Microsoft.EntityFrameworkCore.Query; - -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -public class ThinktectureSqliteParameterBasedSqlProcessorFactory : IRelationalParameterBasedSqlProcessorFactory -{ - private readonly RelationalParameterBasedSqlProcessorDependencies _dependencies; - - /// - /// Initializes new instance of . - /// - /// Dependencies. - public ThinktectureSqliteParameterBasedSqlProcessorFactory( - RelationalParameterBasedSqlProcessorDependencies dependencies) - { - _dependencies = dependencies; - } - - /// - public RelationalParameterBasedSqlProcessor Create(RelationalParameterBasedSqlProcessorParameters parameters) - { - return new ThinktectureSqliteParameterBasedSqlProcessor(_dependencies, parameters); - } -} +using Microsoft.EntityFrameworkCore.Query; + +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +public class ThinktectureSqliteParameterBasedSqlProcessorFactory : IRelationalParameterBasedSqlProcessorFactory +{ + private readonly RelationalParameterBasedSqlProcessorDependencies _dependencies; + + /// + /// Initializes new instance of . + /// + /// Dependencies. + public ThinktectureSqliteParameterBasedSqlProcessorFactory( + RelationalParameterBasedSqlProcessorDependencies dependencies) + { + _dependencies = dependencies; + } + + /// + public RelationalParameterBasedSqlProcessor Create(RelationalParameterBasedSqlProcessorParameters parameters) + { + return new ThinktectureSqliteParameterBasedSqlProcessor(_dependencies, parameters); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQuerySqlGenerator.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQuerySqlGenerator.cs index 870b5130..1285033f 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQuerySqlGenerator.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQuerySqlGenerator.cs @@ -1,39 +1,39 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; -using Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; -using Thinktecture.EntityFrameworkCore.Internal; - -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -[SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] -public class ThinktectureSqliteQuerySqlGenerator : SqliteQuerySqlGenerator -{ - /// - public ThinktectureSqliteQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies) - : base(dependencies) - { - } - - /// - protected override Expression VisitTable(TableExpression tableExpression) - { - ArgumentNullException.ThrowIfNull(tableExpression); - - var tempTable = tableExpression.FindAnnotation(ThinktectureBulkOperationsAnnotationNames.TEMP_TABLE); - - if (tempTable is not null) - { - var tempTableName = (string?)tempTable.Value ?? throw new Exception("Temp table name cannot be null."); - Sql.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(tempTableName)) - .Append(AliasSeparator) - .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(tableExpression.Alias)); - - return tableExpression; - } - - return base.VisitTable(tableExpression); - } -} +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; +using Thinktecture.EntityFrameworkCore.Internal; + +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +[SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] +public class ThinktectureSqliteQuerySqlGenerator : SqliteQuerySqlGenerator +{ + /// + public ThinktectureSqliteQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies) + : base(dependencies) + { + } + + /// + protected override Expression VisitTable(TableExpression tableExpression) + { + ArgumentNullException.ThrowIfNull(tableExpression); + + var tempTable = tableExpression.FindAnnotation(ThinktectureBulkOperationsAnnotationNames.TEMP_TABLE); + + if (tempTable is not null) + { + var tempTableName = (string?)tempTable.Value ?? throw new Exception("Temp table name cannot be null."); + Sql.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(tempTableName)) + .Append(AliasSeparator) + .Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(tableExpression.Alias)); + + return tableExpression; + } + + return base.VisitTable(tableExpression); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQuerySqlGeneratorFactory.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQuerySqlGeneratorFactory.cs index 5f3a7d9f..24b1af67 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQuerySqlGeneratorFactory.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQuerySqlGeneratorFactory.cs @@ -1,24 +1,24 @@ -using Microsoft.EntityFrameworkCore.Query; - -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -public class ThinktectureSqliteQuerySqlGeneratorFactory : IQuerySqlGeneratorFactory -{ - private readonly QuerySqlGeneratorDependencies _dependencies; - - /// - /// Initializes new instance of . - /// - /// Dependencies. - public ThinktectureSqliteQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies) - { - _dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies)); - } - - /// - public QuerySqlGenerator Create() - { - return new ThinktectureSqliteQuerySqlGenerator(_dependencies); - } -} +using Microsoft.EntityFrameworkCore.Query; + +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +public class ThinktectureSqliteQuerySqlGeneratorFactory : IQuerySqlGeneratorFactory +{ + private readonly QuerySqlGeneratorDependencies _dependencies; + + /// + /// Initializes new instance of . + /// + /// Dependencies. + public ThinktectureSqliteQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies) + { + _dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies)); + } + + /// + public QuerySqlGenerator Create() + { + return new ThinktectureSqliteQuerySqlGenerator(_dependencies); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor.cs index 5dc4881a..a225b020 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor.cs @@ -1,43 +1,43 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; - -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -/// Extends the capabilities of . -/// -[SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] -public class ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor : SqliteQueryableMethodTranslatingExpressionVisitor -{ - /// - public ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor( - QueryableMethodTranslatingExpressionVisitorDependencies dependencies, - RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies, - RelationalQueryCompilationContext queryCompilationContext) - : base(dependencies, relationalDependencies, queryCompilationContext) - { - } - - /// - protected ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor( - ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor parentVisitor) - : base(parentVisitor) - { - } - - /// - protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVisitor() - { - return new ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor(this); - } - - /// - protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) - { - return this.TranslateRelationalMethods(methodCallExpression) ?? - this.TranslateBulkMethods(methodCallExpression) ?? - base.VisitMethodCall(methodCallExpression); - } -} +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Sqlite.Query.Internal; + +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +/// Extends the capabilities of . +/// +[SuppressMessage("Usage", "EF1001", MessageId = "Internal EF Core API usage.")] +public class ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor : SqliteQueryableMethodTranslatingExpressionVisitor +{ + /// + public ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor( + QueryableMethodTranslatingExpressionVisitorDependencies dependencies, + RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies, + RelationalQueryCompilationContext queryCompilationContext) + : base(dependencies, relationalDependencies, queryCompilationContext) + { + } + + /// + protected ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor( + ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor parentVisitor) + : base(parentVisitor) + { + } + + /// + protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVisitor() + { + return new ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor(this); + } + + /// + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) + { + return this.TranslateRelationalMethods(methodCallExpression) ?? + this.TranslateBulkMethods(methodCallExpression) ?? + base.VisitMethodCall(methodCallExpression); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQueryableMethodTranslatingExpressionVisitorFactory.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQueryableMethodTranslatingExpressionVisitorFactory.cs index 2568e9be..89715de7 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQueryableMethodTranslatingExpressionVisitorFactory.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/Query/ThinktectureSqliteQueryableMethodTranslatingExpressionVisitorFactory.cs @@ -1,33 +1,33 @@ -using Microsoft.EntityFrameworkCore.Query; - -namespace Thinktecture.EntityFrameworkCore.Query; - -/// -/// Factory for creation of the . -/// -public sealed class ThinktectureSqliteQueryableMethodTranslatingExpressionVisitorFactory : IQueryableMethodTranslatingExpressionVisitorFactory -{ - private readonly QueryableMethodTranslatingExpressionVisitorDependencies _dependencies; - private readonly RelationalQueryableMethodTranslatingExpressionVisitorDependencies _relationalDependencies; - - /// - /// Initializes new instance of . - /// - /// Dependencies. - /// Relational dependencies. - public ThinktectureSqliteQueryableMethodTranslatingExpressionVisitorFactory( - QueryableMethodTranslatingExpressionVisitorDependencies dependencies, - RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies) - { - _dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies)); - _relationalDependencies = relationalDependencies ?? throw new ArgumentNullException(nameof(relationalDependencies)); - } - - /// - public QueryableMethodTranslatingExpressionVisitor Create(QueryCompilationContext queryCompilationContext) - { - return new ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor(_dependencies, - _relationalDependencies, - (RelationalQueryCompilationContext)queryCompilationContext); - } -} +using Microsoft.EntityFrameworkCore.Query; + +namespace Thinktecture.EntityFrameworkCore.Query; + +/// +/// Factory for creation of the . +/// +public sealed class ThinktectureSqliteQueryableMethodTranslatingExpressionVisitorFactory : IQueryableMethodTranslatingExpressionVisitorFactory +{ + private readonly QueryableMethodTranslatingExpressionVisitorDependencies _dependencies; + private readonly RelationalQueryableMethodTranslatingExpressionVisitorDependencies _relationalDependencies; + + /// + /// Initializes new instance of . + /// + /// Dependencies. + /// Relational dependencies. + public ThinktectureSqliteQueryableMethodTranslatingExpressionVisitorFactory( + QueryableMethodTranslatingExpressionVisitorDependencies dependencies, + RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies) + { + _dependencies = dependencies ?? throw new ArgumentNullException(nameof(dependencies)); + _relationalDependencies = relationalDependencies ?? throw new ArgumentNullException(nameof(relationalDependencies)); + } + + /// + public QueryableMethodTranslatingExpressionVisitor Create(QueryCompilationContext queryCompilationContext) + { + return new ThinktectureSqliteQueryableMethodTranslatingExpressionVisitor(_dependencies, + _relationalDependencies, + (RelationalQueryCompilationContext)queryCompilationContext); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/SqliteDbLoggerCategory.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/SqliteDbLoggerCategory.cs index 9fda5261..8286be2b 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/SqliteDbLoggerCategory.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/SqliteDbLoggerCategory.cs @@ -1,16 +1,16 @@ -using Microsoft.EntityFrameworkCore.Diagnostics; - -namespace Thinktecture.EntityFrameworkCore; - -/// -/// Logger category. -/// -public static class SqliteDbLoggerCategory -{ - /// - /// Logger category for bulk operations. - /// - public class BulkOperation : LoggerCategory - { - } -} +using Microsoft.EntityFrameworkCore.Diagnostics; + +namespace Thinktecture.EntityFrameworkCore; + +/// +/// Logger category. +/// +public static class SqliteDbLoggerCategory +{ + /// + /// Logger category for bulk operations. + /// + public class BulkOperation : LoggerCategory + { + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/TempTables/SqliteTempTableCreator.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/TempTables/SqliteTempTableCreator.cs index 6dc4e1db..5196493f 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/TempTables/SqliteTempTableCreator.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/TempTables/SqliteTempTableCreator.cs @@ -1,224 +1,224 @@ -using System.Text; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.ObjectPool; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Creates temp tables. -/// -public sealed class SqliteTempTableCreator : ITempTableCreator -{ - private readonly DbContext _ctx; - private readonly IDiagnosticsLogger _logger; - private readonly ISqlGenerationHelper _sqlGenerationHelper; - private readonly IRelationalTypeMappingSource _typeMappingSource; - private readonly ObjectPool _stringBuilderPool; - private readonly TempTableStatementCache _cache; - - /// - /// Initializes . - /// - /// Current database context. - /// Logger. - /// SQL generation helper. - /// Type mappings. - /// SQL statement cache. - /// String builder pool. - public SqliteTempTableCreator( - ICurrentDbContext ctx, - IDiagnosticsLogger logger, - ISqlGenerationHelper sqlGenerationHelper, - IRelationalTypeMappingSource typeMappingSource, - ObjectPool stringBuilderPool, - TempTableStatementCache cache) - { - ArgumentNullException.ThrowIfNull(ctx); - - _ctx = ctx.Context; - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _sqlGenerationHelper = sqlGenerationHelper ?? throw new ArgumentNullException(nameof(sqlGenerationHelper)); - _typeMappingSource = typeMappingSource ?? throw new ArgumentNullException(nameof(typeMappingSource)); - _stringBuilderPool = stringBuilderPool ?? throw new ArgumentNullException(nameof(stringBuilderPool)); - _cache = cache ?? throw new ArgumentNullException(nameof(cache)); - } - - /// - public async Task CreateTempTableAsync( - IEntityType entityType, - ITempTableCreationOptions options, - CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(entityType); - ArgumentNullException.ThrowIfNull(options); - - var nameLease = options.TableNameProvider.LeaseName(_ctx, entityType); - - try - { - var sql = GetTempTableCreationSql(entityType, nameLease.Name, options); - - await _ctx.Database.OpenConnectionAsync(cancellationToken).ConfigureAwait(false); - - try - { - await _ctx.Database.ExecuteSqlRawAsync(sql, cancellationToken).ConfigureAwait(false); - } - catch (Exception) - { - await _ctx.Database.CloseConnectionAsync().ConfigureAwait(false); - throw; - } - - return new SqliteTempTableReference(_logger, _sqlGenerationHelper, nameLease.Name, _ctx.Database, nameLease, options.DropTableOnDispose); - } - catch (Exception) - { - nameLease.Dispose(); - throw; - } - } - - private string GetTempTableCreationSql(IEntityType entityType, string tableName, ITempTableCreationOptions options) - { - ArgumentNullException.ThrowIfNull(tableName); - - var cachedStatement = _cache.GetOrAdd(new SqliteTempTableCreatorCacheKey(options, entityType), CreateCachedStatement); - - return cachedStatement.GetSqlStatement(_sqlGenerationHelper, tableName); - } - - private ICachedTempTableStatement CreateCachedStatement(SqliteTempTableCreatorCacheKey cacheKey) - { - var columnDefinitions = GetColumnsDefinitions(cacheKey); - - if (!cacheKey.TruncateTableIfExists) - { - return new CachedTempTableStatement(columnDefinitions, - static (sqlGenerationHelper, name, columnDefinitions) => - $""" - CREATE TEMPORARY TABLE {sqlGenerationHelper.DelimitIdentifier(name)} - ( - {columnDefinitions} - ); - """); - } - - return new CachedTempTableStatement(columnDefinitions, - static (sqlGenerationHelper, name, columnDefinitions) => - $""" - DROP TABLE IF EXISTS {sqlGenerationHelper.DelimitIdentifier(name, "temp")}; - - CREATE TEMPORARY TABLE {sqlGenerationHelper.DelimitIdentifier(name)} - ( - {columnDefinitions} - ); - """); - } - - private string GetColumnsDefinitions(SqliteTempTableCreatorCacheKey options) - { - var sb = _stringBuilderPool.Get(); - - try - { - var isFirst = true; - var createPk = true; - StoreObjectIdentifier? storeObject = null; - - foreach (var property in options.Properties) - { - if (!isFirst) - sb.AppendLine(","); - - storeObject ??= property.GetStoreObject(); - var columnName = property.GetColumnName(storeObject.Value) - ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.DeclaringType.Name}'."); - var columnType = property.GetColumnType(storeObject.Value); - - sb.Append("\t\t") - .Append(_sqlGenerationHelper.DelimitIdentifier(columnName)).Append(' ') - .Append(columnType) - .Append(property.IsNullable ? " NULL" : " NOT NULL"); - - if (property.IsAutoIncrement()) - { - if (options.PrimaryKeys.Count != 1 || !property.Equals(options.PrimaryKeys.First())) - { - throw new NotSupportedException($""" - SQLite does not allow the property '{property.Name}' of the entity '{property.DeclaringType.Name}' to be an AUTOINCREMENT column unless this column is the PRIMARY KEY. - Currently configured primary keys: [{String.Join(", ", options.PrimaryKeys.Select(p => p.Name))}] - """); - } - - sb.Append(" PRIMARY KEY AUTOINCREMENT"); - createPk = false; - } - - var defaultValueSql = property.GetDefaultValueSql(storeObject.Value); - - if (!String.IsNullOrWhiteSpace(defaultValueSql)) - { - sb.Append(" DEFAULT (").Append(defaultValueSql).Append(')'); - } - else if (property.TryGetDefaultValue(storeObject.Value, out var defaultValue) && defaultValue is not null) - { - var converter = property.GetValueConverter(); - - if (converter is not null) - defaultValue = converter.ConvertToProvider(defaultValue); - - if (defaultValue is not null) - { - var mappingForValue = _typeMappingSource.FindMapping(defaultValue.GetType(), columnType) - ?? _typeMappingSource.GetMappingForValue(defaultValue); - - sb.Append(" DEFAULT ").Append(mappingForValue.GenerateSqlLiteral(defaultValue)); - } - } - - isFirst = false; - } - - if (createPk) - CreatePkClause(options.PrimaryKeys, sb); - - return sb.ToString(); - } - finally - { - _stringBuilderPool.Return(sb); - } - } - - private void CreatePkClause(IReadOnlyCollection keyProperties, StringBuilder sb) - { - if (!keyProperties.Any()) - return; - - var columnNames = keyProperties.Select(p => - { - var storeObject = p.GetStoreObject(); - return p.GetColumnName(storeObject) - ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{p.DeclaringType.Name}'."); - }); - - sb.AppendLine(","); - sb.Append("\t\tPRIMARY KEY ("); - var isFirst = true; - - foreach (var columnName in columnNames) - { - if (!isFirst) - sb.Append(", "); - - sb.Append(_sqlGenerationHelper.DelimitIdentifier(columnName)); - isFirst = false; - } - - sb.Append(')'); - } -} +using System.Text; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.ObjectPool; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Creates temp tables. +/// +public sealed class SqliteTempTableCreator : ITempTableCreator +{ + private readonly DbContext _ctx; + private readonly IDiagnosticsLogger _logger; + private readonly ISqlGenerationHelper _sqlGenerationHelper; + private readonly IRelationalTypeMappingSource _typeMappingSource; + private readonly ObjectPool _stringBuilderPool; + private readonly TempTableStatementCache _cache; + + /// + /// Initializes . + /// + /// Current database context. + /// Logger. + /// SQL generation helper. + /// Type mappings. + /// SQL statement cache. + /// String builder pool. + public SqliteTempTableCreator( + ICurrentDbContext ctx, + IDiagnosticsLogger logger, + ISqlGenerationHelper sqlGenerationHelper, + IRelationalTypeMappingSource typeMappingSource, + ObjectPool stringBuilderPool, + TempTableStatementCache cache) + { + ArgumentNullException.ThrowIfNull(ctx); + + _ctx = ctx.Context; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _sqlGenerationHelper = sqlGenerationHelper ?? throw new ArgumentNullException(nameof(sqlGenerationHelper)); + _typeMappingSource = typeMappingSource ?? throw new ArgumentNullException(nameof(typeMappingSource)); + _stringBuilderPool = stringBuilderPool ?? throw new ArgumentNullException(nameof(stringBuilderPool)); + _cache = cache ?? throw new ArgumentNullException(nameof(cache)); + } + + /// + public async Task CreateTempTableAsync( + IEntityType entityType, + ITempTableCreationOptions options, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(entityType); + ArgumentNullException.ThrowIfNull(options); + + var nameLease = options.TableNameProvider.LeaseName(_ctx, entityType); + + try + { + var sql = GetTempTableCreationSql(entityType, nameLease.Name, options); + + await _ctx.Database.OpenConnectionAsync(cancellationToken).ConfigureAwait(false); + + try + { + await _ctx.Database.ExecuteSqlRawAsync(sql, cancellationToken).ConfigureAwait(false); + } + catch (Exception) + { + await _ctx.Database.CloseConnectionAsync().ConfigureAwait(false); + throw; + } + + return new SqliteTempTableReference(_logger, _sqlGenerationHelper, nameLease.Name, _ctx.Database, nameLease, options.DropTableOnDispose); + } + catch (Exception) + { + nameLease.Dispose(); + throw; + } + } + + private string GetTempTableCreationSql(IEntityType entityType, string tableName, ITempTableCreationOptions options) + { + ArgumentNullException.ThrowIfNull(tableName); + + var cachedStatement = _cache.GetOrAdd(new SqliteTempTableCreatorCacheKey(options, entityType), CreateCachedStatement); + + return cachedStatement.GetSqlStatement(_sqlGenerationHelper, tableName); + } + + private ICachedTempTableStatement CreateCachedStatement(SqliteTempTableCreatorCacheKey cacheKey) + { + var columnDefinitions = GetColumnsDefinitions(cacheKey); + + if (!cacheKey.TruncateTableIfExists) + { + return new CachedTempTableStatement(columnDefinitions, + static (sqlGenerationHelper, name, columnDefinitions) => + $""" + CREATE TEMPORARY TABLE {sqlGenerationHelper.DelimitIdentifier(name)} + ( + {columnDefinitions} + ); + """); + } + + return new CachedTempTableStatement(columnDefinitions, + static (sqlGenerationHelper, name, columnDefinitions) => + $""" + DROP TABLE IF EXISTS {sqlGenerationHelper.DelimitIdentifier(name, "temp")}; + + CREATE TEMPORARY TABLE {sqlGenerationHelper.DelimitIdentifier(name)} + ( + {columnDefinitions} + ); + """); + } + + private string GetColumnsDefinitions(SqliteTempTableCreatorCacheKey options) + { + var sb = _stringBuilderPool.Get(); + + try + { + var isFirst = true; + var createPk = true; + StoreObjectIdentifier? storeObject = null; + + foreach (var property in options.Properties) + { + if (!isFirst) + sb.AppendLine(","); + + storeObject ??= property.GetStoreObject(); + var columnName = property.GetColumnName(storeObject.Value) + ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{property.DeclaringType.Name}'."); + var columnType = property.GetColumnType(storeObject.Value); + + sb.Append("\t\t") + .Append(_sqlGenerationHelper.DelimitIdentifier(columnName)).Append(' ') + .Append(columnType) + .Append(property.IsNullable ? " NULL" : " NOT NULL"); + + if (property.IsAutoIncrement()) + { + if (options.PrimaryKeys.Count != 1 || !property.Equals(options.PrimaryKeys.First())) + { + throw new NotSupportedException($""" + SQLite does not allow the property '{property.Name}' of the entity '{property.DeclaringType.Name}' to be an AUTOINCREMENT column unless this column is the PRIMARY KEY. + Currently configured primary keys: [{String.Join(", ", options.PrimaryKeys.Select(p => p.Name))}] + """); + } + + sb.Append(" PRIMARY KEY AUTOINCREMENT"); + createPk = false; + } + + var defaultValueSql = property.GetDefaultValueSql(storeObject.Value); + + if (!String.IsNullOrWhiteSpace(defaultValueSql)) + { + sb.Append(" DEFAULT (").Append(defaultValueSql).Append(')'); + } + else if (property.TryGetDefaultValue(storeObject.Value, out var defaultValue) && defaultValue is not null) + { + var converter = property.GetValueConverter(); + + if (converter is not null) + defaultValue = converter.ConvertToProvider(defaultValue); + + if (defaultValue is not null) + { + var mappingForValue = _typeMappingSource.FindMapping(defaultValue.GetType(), columnType) + ?? _typeMappingSource.GetMappingForValue(defaultValue); + + sb.Append(" DEFAULT ").Append(mappingForValue.GenerateSqlLiteral(defaultValue)); + } + } + + isFirst = false; + } + + if (createPk) + CreatePkClause(options.PrimaryKeys, sb); + + return sb.ToString(); + } + finally + { + _stringBuilderPool.Return(sb); + } + } + + private void CreatePkClause(IReadOnlyCollection keyProperties, StringBuilder sb) + { + if (!keyProperties.Any()) + return; + + var columnNames = keyProperties.Select(p => + { + var storeObject = p.GetStoreObject(); + return p.GetColumnName(storeObject) + ?? throw new Exception($"Could not create StoreObjectIdentifier for table '{p.DeclaringType.Name}'."); + }); + + sb.AppendLine(","); + sb.Append("\t\tPRIMARY KEY ("); + var isFirst = true; + + foreach (var columnName in columnNames) + { + if (!isFirst) + sb.Append(", "); + + sb.Append(_sqlGenerationHelper.DelimitIdentifier(columnName)); + isFirst = false; + } + + sb.Append(')'); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/TempTables/SqliteTempTableCreatorCacheKey.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/TempTables/SqliteTempTableCreatorCacheKey.cs index dfe331a2..a5ba5df5 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/TempTables/SqliteTempTableCreatorCacheKey.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/TempTables/SqliteTempTableCreatorCacheKey.cs @@ -1,92 +1,92 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.EntityFrameworkCore.Data; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// Cache key for . -/// -public readonly struct SqliteTempTableCreatorCacheKey - : IEquatable -{ - /// - public bool TruncateTableIfExists { get; } - - /// - /// Properties to create temp table with. - /// - public IReadOnlyList Properties { get; } - - /// - /// Properties the primary key should be created with. - /// - public IReadOnlyCollection PrimaryKeys { get; } - - /// - /// Initializes new instance of . - /// - /// Options. - /// Entity type. - public SqliteTempTableCreatorCacheKey( - ITempTableCreationOptions options, - IEntityType entityType) - { - TruncateTableIfExists = options.TruncateTableIfExists; - Properties = options.PropertiesToInclude.DeterminePropertiesForTempTable(entityType); - PrimaryKeys = options.PrimaryKeyCreation.GetPrimaryKeyProperties(entityType, Properties); - } - - /// - public bool Equals(SqliteTempTableCreatorCacheKey other) - { - return TruncateTableIfExists == other.TruncateTableIfExists && - Equals(Properties, other.Properties) && - Equals(PrimaryKeys, other.PrimaryKeys); - } - - private static bool Equals(IReadOnlyCollection collection, IReadOnlyCollection other) - { - if (collection.Count != other.Count) - return false; - - using var otherEnumerator = other.GetEnumerator(); - - foreach (var item in collection) - { - otherEnumerator.MoveNext(); - - if (!item.Equals(otherEnumerator.Current)) - return false; - } - - return true; - } - - /// - public override bool Equals(object? obj) - { - return obj is SqliteTempTableCreatorCacheKey other && Equals(other); - } - - /// - public override int GetHashCode() - { - var hashCode = new HashCode(); - hashCode.Add(TruncateTableIfExists); - - ComputeHashCode(hashCode, Properties); - ComputeHashCode(hashCode, PrimaryKeys); - - return hashCode.ToHashCode(); - } - - private static void ComputeHashCode( - HashCode hashCode, - IEnumerable properties) - { - foreach (var property in properties) - { - hashCode.Add(property); - } - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.EntityFrameworkCore.Data; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// Cache key for . +/// +public readonly struct SqliteTempTableCreatorCacheKey + : IEquatable +{ + /// + public bool TruncateTableIfExists { get; } + + /// + /// Properties to create temp table with. + /// + public IReadOnlyList Properties { get; } + + /// + /// Properties the primary key should be created with. + /// + public IReadOnlyCollection PrimaryKeys { get; } + + /// + /// Initializes new instance of . + /// + /// Options. + /// Entity type. + public SqliteTempTableCreatorCacheKey( + ITempTableCreationOptions options, + IEntityType entityType) + { + TruncateTableIfExists = options.TruncateTableIfExists; + Properties = options.PropertiesToInclude.DeterminePropertiesForTempTable(entityType); + PrimaryKeys = options.PrimaryKeyCreation.GetPrimaryKeyProperties(entityType, Properties); + } + + /// + public bool Equals(SqliteTempTableCreatorCacheKey other) + { + return TruncateTableIfExists == other.TruncateTableIfExists && + Equals(Properties, other.Properties) && + Equals(PrimaryKeys, other.PrimaryKeys); + } + + private static bool Equals(IReadOnlyCollection collection, IReadOnlyCollection other) + { + if (collection.Count != other.Count) + return false; + + using var otherEnumerator = other.GetEnumerator(); + + foreach (var item in collection) + { + otherEnumerator.MoveNext(); + + if (!item.Equals(otherEnumerator.Current)) + return false; + } + + return true; + } + + /// + public override bool Equals(object? obj) + { + return obj is SqliteTempTableCreatorCacheKey other && Equals(other); + } + + /// + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(TruncateTableIfExists); + + ComputeHashCode(hashCode, Properties); + ComputeHashCode(hashCode, PrimaryKeys); + + return hashCode.ToHashCode(); + } + + private static void ComputeHashCode( + HashCode hashCode, + IEnumerable properties) + { + foreach (var property in properties) + { + hashCode.Add(property); + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/TempTables/SqliteTempTableReference.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/TempTables/SqliteTempTableReference.cs index 3abff51f..b968dd3e 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/TempTables/SqliteTempTableReference.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/EntityFrameworkCore/TempTables/SqliteTempTableReference.cs @@ -1,124 +1,124 @@ -using System.Data; -using System.Data.Common; -using System.Diagnostics.CodeAnalysis; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.Logging; - -namespace Thinktecture.EntityFrameworkCore.TempTables; - -/// -/// A reference to SQLite temp table. -/// -public sealed class SqliteTempTableReference : ITempTableReference -{ - private readonly IDiagnosticsLogger _logger; - private readonly ISqlGenerationHelper _sqlGenerationHelper; - private readonly DatabaseFacade _database; - private readonly ITempTableNameLease _nameLease; - private readonly bool _dropTableOnDispose; - - /// - public string Name { get; } - - /// - /// Initializes new instance of . - /// - /// Logger - /// SQL generation helper. - /// The name of the temp table. - /// Database facade. - /// Leased table name that will be disposed along with the temp table. - /// Indication whether to drop the temp table on dispose or not. - public SqliteTempTableReference(IDiagnosticsLogger logger, - ISqlGenerationHelper sqlGenerationHelper, - string tableName, - DatabaseFacade database, - ITempTableNameLease nameLease, - bool dropTableOnDispose) - { - Name = tableName ?? throw new ArgumentNullException(nameof(tableName)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _sqlGenerationHelper = sqlGenerationHelper ?? throw new ArgumentNullException(nameof(sqlGenerationHelper)); - _database = database ?? throw new ArgumentNullException(nameof(database)); - _nameLease = nameLease ?? throw new ArgumentNullException(nameof(nameLease)); - _dropTableOnDispose = dropTableOnDispose; - } - - /// - public void Dispose() - { - try - { - using var command = TryCreateCleanupCommand(); - - if (command is null) - return; - - command.ExecuteNonQuery(); - - _database.CloseConnection(); - } - catch (ObjectDisposedException ex) - { - _logger.Logger.LogWarning(ex, $"Trying to dispose of the temp table reference '{Name}' after the corresponding DbContext has been disposed."); - } - finally - { - _nameLease.Dispose(); - } - } - - /// - public async ValueTask DisposeAsync() - { - try - { - await using var command = TryCreateCleanupCommand(); - - if (command is null) - return; - - await command.ExecuteNonQueryAsync(); - - await _database.CloseConnectionAsync().ConfigureAwait(false); - } - catch (ObjectDisposedException ex) - { - _logger.Logger.LogWarning(ex, $"Trying to dispose of the temp table reference '{Name}' after the corresponding DbContext has been disposed."); - } - finally - { - _nameLease.Dispose(); - } - } - - private DbCommand? TryCreateCleanupCommand() - { - DbCommand? command = null; - - try - { - if (!_dropTableOnDispose) - return null; - - var connection = _database.GetDbConnection(); - - if (connection.State != ConnectionState.Open) - return null; - - var sql = $"DROP TABLE IF EXISTS {_sqlGenerationHelper.DelimitIdentifier(Name, "temp")}"; - - command = connection.CreateCommand(); - command.CommandText = sql; - - return command; - } - catch - { - command?.Dispose(); - throw; - } - } -} +using System.Data; +using System.Data.Common; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.Logging; + +namespace Thinktecture.EntityFrameworkCore.TempTables; + +/// +/// A reference to SQLite temp table. +/// +public sealed class SqliteTempTableReference : ITempTableReference +{ + private readonly IDiagnosticsLogger _logger; + private readonly ISqlGenerationHelper _sqlGenerationHelper; + private readonly DatabaseFacade _database; + private readonly ITempTableNameLease _nameLease; + private readonly bool _dropTableOnDispose; + + /// + public string Name { get; } + + /// + /// Initializes new instance of . + /// + /// Logger + /// SQL generation helper. + /// The name of the temp table. + /// Database facade. + /// Leased table name that will be disposed along with the temp table. + /// Indication whether to drop the temp table on dispose or not. + public SqliteTempTableReference(IDiagnosticsLogger logger, + ISqlGenerationHelper sqlGenerationHelper, + string tableName, + DatabaseFacade database, + ITempTableNameLease nameLease, + bool dropTableOnDispose) + { + Name = tableName ?? throw new ArgumentNullException(nameof(tableName)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _sqlGenerationHelper = sqlGenerationHelper ?? throw new ArgumentNullException(nameof(sqlGenerationHelper)); + _database = database ?? throw new ArgumentNullException(nameof(database)); + _nameLease = nameLease ?? throw new ArgumentNullException(nameof(nameLease)); + _dropTableOnDispose = dropTableOnDispose; + } + + /// + public void Dispose() + { + try + { + using var command = TryCreateCleanupCommand(); + + if (command is null) + return; + + command.ExecuteNonQuery(); + + _database.CloseConnection(); + } + catch (ObjectDisposedException ex) + { + _logger.Logger.LogWarning(ex, $"Trying to dispose of the temp table reference '{Name}' after the corresponding DbContext has been disposed."); + } + finally + { + _nameLease.Dispose(); + } + } + + /// + public async ValueTask DisposeAsync() + { + try + { + await using var command = TryCreateCleanupCommand(); + + if (command is null) + return; + + await command.ExecuteNonQueryAsync(); + + await _database.CloseConnectionAsync().ConfigureAwait(false); + } + catch (ObjectDisposedException ex) + { + _logger.Logger.LogWarning(ex, $"Trying to dispose of the temp table reference '{Name}' after the corresponding DbContext has been disposed."); + } + finally + { + _nameLease.Dispose(); + } + } + + private DbCommand? TryCreateCleanupCommand() + { + DbCommand? command = null; + + try + { + if (!_dropTableOnDispose) + return null; + + var connection = _database.GetDbConnection(); + + if (connection.State != ConnectionState.Open) + return null; + + var sql = $"DROP TABLE IF EXISTS {_sqlGenerationHelper.DelimitIdentifier(Name, "temp")}"; + + command = connection.CreateCommand(); + command.CommandText = sql; + + return command; + } + catch + { + command?.Dispose(); + throw; + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/Extensions/SqliteDbContextOptionsBuilderExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/Extensions/SqliteDbContextOptionsBuilderExtensions.cs index 982489a1..c7fe94df 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/Extensions/SqliteDbContextOptionsBuilderExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/Extensions/SqliteDbContextOptionsBuilderExtensions.cs @@ -1,98 +1,98 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Thinktecture.EntityFrameworkCore.Infrastructure; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extensions for . -/// -public static class SqliteDbContextOptionsBuilderExtensions -{ - /// - /// Adds support for bulk operations and temp tables. - /// - /// SQLite options builder. - /// Indication whether to enable or disable the feature. - /// Indication whether to configure temp tables for primitive types. - /// Provided . - public static SqliteDbContextOptionsBuilder AddBulkOperationSupport( - this SqliteDbContextOptionsBuilder sqliteOptionsBuilder, - bool addBulkOperationSupport = true, - bool configureTempTablesForPrimitiveTypes = true) - { - return AddOrUpdateExtension(sqliteOptionsBuilder, extension => - { - extension.AddBulkOperationSupport = addBulkOperationSupport; - extension.ConfigureTempTablesForPrimitiveTypes = addBulkOperationSupport && configureTempTablesForPrimitiveTypes; - return extension; - }); - } - - /// - /// Adds custom factory required for translation of custom methods like . - /// - /// Options builder. - /// Indication whether to add a custom factory. - /// Provided . - public static SqliteDbContextOptionsBuilder AddCustomQueryableMethodTranslatingExpressionVisitorFactory( - this SqliteDbContextOptionsBuilder builder, - bool addCustomQueryableMethodTranslatingExpressionVisitorFactory = true) - { - builder.AddOrUpdateExtension(extension => - { - extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory = addCustomQueryableMethodTranslatingExpressionVisitorFactory; - return extension; - }); - return builder; - } - - /// - /// Adds support for "RowNumber". - /// - /// Options builder. - /// Indication whether to enable or disable the feature. - /// Provided . - [Obsolete($"Use '{nameof(AddWindowFunctionsSupport)}' instead.")] - public static SqliteDbContextOptionsBuilder AddRowNumberSupport( - this SqliteDbContextOptionsBuilder builder, - bool addRowNumberSupport = true) - { - builder.AddOrUpdateExtension(extension => - { - extension.AddWindowFunctionsSupport = addRowNumberSupport; - return extension; - }); - return builder; - } - - /// - /// Adds support for window functions like "RowNumber". - /// - /// Options builder. - /// Indication whether to enable or disable the feature. - /// Provided . - public static SqliteDbContextOptionsBuilder AddWindowFunctionsSupport( - this SqliteDbContextOptionsBuilder builder, - bool addWindowFunctionsSupport = true) - { - builder.AddOrUpdateExtension(extension => - { - extension.AddWindowFunctionsSupport = addWindowFunctionsSupport; - return extension; - }); - return builder; - } - - private static SqliteDbContextOptionsBuilder AddOrUpdateExtension(this SqliteDbContextOptionsBuilder sqliteOptionsBuilder, - Func callback) - { - ArgumentNullException.ThrowIfNull(sqliteOptionsBuilder); - - var infrastructure = (IRelationalDbContextOptionsBuilderInfrastructure)sqliteOptionsBuilder; - var relationalOptions = infrastructure.OptionsBuilder.TryAddExtension(); - infrastructure.OptionsBuilder.AddOrUpdateExtension(callback, () => new SqliteDbContextOptionsExtension(relationalOptions)); - - return sqliteOptionsBuilder; - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; +using Thinktecture.EntityFrameworkCore.Infrastructure; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extensions for . +/// +public static class SqliteDbContextOptionsBuilderExtensions +{ + /// + /// Adds support for bulk operations and temp tables. + /// + /// SQLite options builder. + /// Indication whether to enable or disable the feature. + /// Indication whether to configure temp tables for primitive types. + /// Provided . + public static SqliteDbContextOptionsBuilder AddBulkOperationSupport( + this SqliteDbContextOptionsBuilder sqliteOptionsBuilder, + bool addBulkOperationSupport = true, + bool configureTempTablesForPrimitiveTypes = true) + { + return AddOrUpdateExtension(sqliteOptionsBuilder, extension => + { + extension.AddBulkOperationSupport = addBulkOperationSupport; + extension.ConfigureTempTablesForPrimitiveTypes = addBulkOperationSupport && configureTempTablesForPrimitiveTypes; + return extension; + }); + } + + /// + /// Adds custom factory required for translation of custom methods like . + /// + /// Options builder. + /// Indication whether to add a custom factory. + /// Provided . + public static SqliteDbContextOptionsBuilder AddCustomQueryableMethodTranslatingExpressionVisitorFactory( + this SqliteDbContextOptionsBuilder builder, + bool addCustomQueryableMethodTranslatingExpressionVisitorFactory = true) + { + builder.AddOrUpdateExtension(extension => + { + extension.AddCustomQueryableMethodTranslatingExpressionVisitorFactory = addCustomQueryableMethodTranslatingExpressionVisitorFactory; + return extension; + }); + return builder; + } + + /// + /// Adds support for "RowNumber". + /// + /// Options builder. + /// Indication whether to enable or disable the feature. + /// Provided . + [Obsolete($"Use '{nameof(AddWindowFunctionsSupport)}' instead.")] + public static SqliteDbContextOptionsBuilder AddRowNumberSupport( + this SqliteDbContextOptionsBuilder builder, + bool addRowNumberSupport = true) + { + builder.AddOrUpdateExtension(extension => + { + extension.AddWindowFunctionsSupport = addRowNumberSupport; + return extension; + }); + return builder; + } + + /// + /// Adds support for window functions like "RowNumber". + /// + /// Options builder. + /// Indication whether to enable or disable the feature. + /// Provided . + public static SqliteDbContextOptionsBuilder AddWindowFunctionsSupport( + this SqliteDbContextOptionsBuilder builder, + bool addWindowFunctionsSupport = true) + { + builder.AddOrUpdateExtension(extension => + { + extension.AddWindowFunctionsSupport = addWindowFunctionsSupport; + return extension; + }); + return builder; + } + + private static SqliteDbContextOptionsBuilder AddOrUpdateExtension(this SqliteDbContextOptionsBuilder sqliteOptionsBuilder, + Func callback) + { + ArgumentNullException.ThrowIfNull(sqliteOptionsBuilder); + + var infrastructure = (IRelationalDbContextOptionsBuilderInfrastructure)sqliteOptionsBuilder; + var relationalOptions = infrastructure.OptionsBuilder.TryAddExtension(); + infrastructure.OptionsBuilder.AddOrUpdateExtension(callback, () => new SqliteDbContextOptionsExtension(relationalOptions)); + + return sqliteOptionsBuilder; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/Extensions/SqlitePropertyExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Sqlite/Extensions/SqlitePropertyExtensions.cs index 65c7f4c6..340bf1a8 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/Extensions/SqlitePropertyExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/Extensions/SqlitePropertyExtensions.cs @@ -1,20 +1,20 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -/// -/// Extensions for . -/// -public static class SqlitePropertyExtensions -{ - internal static bool IsAutoIncrement(this IProperty property) - { - ArgumentNullException.ThrowIfNull(property); - - return property.ValueGenerated == ValueGenerated.OnAdd - && (property.ClrType == typeof(int) || property.ClrType == typeof(int?)) - && property.FindTypeMapping()?.Converter == null - && property.GetAfterSaveBehavior() != PropertySaveBehavior.Save; - } -} +using Microsoft.EntityFrameworkCore.Metadata; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +/// +/// Extensions for . +/// +public static class SqlitePropertyExtensions +{ + internal static bool IsAutoIncrement(this IProperty property) + { + ArgumentNullException.ThrowIfNull(property); + + return property.ValueGenerated == ValueGenerated.OnAdd + && (property.ClrType == typeof(int) || property.ClrType == typeof(int?)) + && property.FindTypeMapping()?.Converter == null + && property.GetAfterSaveBehavior() != PropertySaveBehavior.Save; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Sqlite/Thinktecture.EntityFrameworkCore.Sqlite.csproj b/src/Thinktecture.EntityFrameworkCore.Sqlite/Thinktecture.EntityFrameworkCore.Sqlite.csproj index 62ad95c7..8d8a8afb 100644 --- a/src/Thinktecture.EntityFrameworkCore.Sqlite/Thinktecture.EntityFrameworkCore.Sqlite.csproj +++ b/src/Thinktecture.EntityFrameworkCore.Sqlite/Thinktecture.EntityFrameworkCore.Sqlite.csproj @@ -1,11 +1,11 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/src/Thinktecture.EntityFrameworkCore.Testing/Collections/AsyncEnumerable.cs b/src/Thinktecture.EntityFrameworkCore.Testing/Collections/AsyncEnumerable.cs index ae5101e6..2883a890 100644 --- a/src/Thinktecture.EntityFrameworkCore.Testing/Collections/AsyncEnumerable.cs +++ b/src/Thinktecture.EntityFrameworkCore.Testing/Collections/AsyncEnumerable.cs @@ -1,108 +1,108 @@ -using System.Linq.Expressions; -using System.Reflection; -using Microsoft.EntityFrameworkCore.Query; - -namespace Thinktecture.Collections; - -internal sealed class AsyncEnumerable : EnumerableQuery, IAsyncEnumerable, IQueryable -{ - IQueryProvider IQueryable.Provider => new AsyncQueryProvider(this); - - public AsyncEnumerable(IEnumerable enumerable) - : base(enumerable) - { - ArgumentNullException.ThrowIfNull(enumerable); - } - - private AsyncEnumerable(Expression expression) - : base(expression) - { - ArgumentNullException.ThrowIfNull(expression); - } - - public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken) - { - return new AsyncEnumerator(this.AsEnumerable().GetEnumerator()); - } - - private sealed class AsyncEnumerator : IAsyncEnumerator - { - private readonly IEnumerator _enumerator; - - public T Current => _enumerator.Current; - - public AsyncEnumerator(IEnumerator enumerator) - { - _enumerator = enumerator ?? throw new ArgumentNullException(nameof(enumerator)); - } - - public ValueTask MoveNextAsync() - { - return new(_enumerator.MoveNext()); - } - - public ValueTask DisposeAsync() - { - _enumerator.Dispose(); - - return default; - } - } - - private sealed class AsyncQueryProvider : IAsyncQueryProvider - { - private static readonly MethodInfo _genericCreateQuery = typeof(AsyncQueryProvider).GetMethods(BindingFlags.Instance | BindingFlags.Public) - .First(m => m.Name == nameof(CreateQuery) && m.IsGenericMethod); - - private readonly IQueryProvider _queryProvider; - - internal AsyncQueryProvider(IQueryProvider queryProvider) - { - _queryProvider = queryProvider ?? throw new ArgumentNullException(nameof(queryProvider)); - } - - public IQueryable CreateQuery(Expression expression) - { - if (IsQueryableType(expression.Type)) - { - var entityType = expression.Type.GetGenericArguments()[0]; - - if (entityType != typeof(T)) - { - return (IQueryable?)_genericCreateQuery.MakeGenericMethod(entityType).Invoke(this, new object[] { expression }) - ?? throw new Exception($"Could not make a generic query using the method '{nameof(AsyncQueryProvider)}.{nameof(CreateQuery)}' and expression '{expression}'"); - } - } - - return new AsyncEnumerable(expression); - } - - private static bool IsQueryableType(Type type) - { - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IQueryable<>)) - return true; - - return type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IQueryable<>)); - } - - public IQueryable CreateQuery(Expression expression) - { - return new AsyncEnumerable(expression); - } - - public object? Execute(Expression expression) - { - return _queryProvider.Execute(expression); - } - - public TResult Execute(Expression expression) - { - return _queryProvider.Execute(expression); - } - - public TResult ExecuteAsync(Expression expression, CancellationToken cancellationToken) - { - return Execute(expression); - } - } -} +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Query; + +namespace Thinktecture.Collections; + +internal sealed class AsyncEnumerable : EnumerableQuery, IAsyncEnumerable, IQueryable +{ + IQueryProvider IQueryable.Provider => new AsyncQueryProvider(this); + + public AsyncEnumerable(IEnumerable enumerable) + : base(enumerable) + { + ArgumentNullException.ThrowIfNull(enumerable); + } + + private AsyncEnumerable(Expression expression) + : base(expression) + { + ArgumentNullException.ThrowIfNull(expression); + } + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken) + { + return new AsyncEnumerator(this.AsEnumerable().GetEnumerator()); + } + + private sealed class AsyncEnumerator : IAsyncEnumerator + { + private readonly IEnumerator _enumerator; + + public T Current => _enumerator.Current; + + public AsyncEnumerator(IEnumerator enumerator) + { + _enumerator = enumerator ?? throw new ArgumentNullException(nameof(enumerator)); + } + + public ValueTask MoveNextAsync() + { + return new(_enumerator.MoveNext()); + } + + public ValueTask DisposeAsync() + { + _enumerator.Dispose(); + + return default; + } + } + + private sealed class AsyncQueryProvider : IAsyncQueryProvider + { + private static readonly MethodInfo _genericCreateQuery = typeof(AsyncQueryProvider).GetMethods(BindingFlags.Instance | BindingFlags.Public) + .First(m => m.Name == nameof(CreateQuery) && m.IsGenericMethod); + + private readonly IQueryProvider _queryProvider; + + internal AsyncQueryProvider(IQueryProvider queryProvider) + { + _queryProvider = queryProvider ?? throw new ArgumentNullException(nameof(queryProvider)); + } + + public IQueryable CreateQuery(Expression expression) + { + if (IsQueryableType(expression.Type)) + { + var entityType = expression.Type.GetGenericArguments()[0]; + + if (entityType != typeof(T)) + { + return (IQueryable?)_genericCreateQuery.MakeGenericMethod(entityType).Invoke(this, new object[] { expression }) + ?? throw new Exception($"Could not make a generic query using the method '{nameof(AsyncQueryProvider)}.{nameof(CreateQuery)}' and expression '{expression}'"); + } + } + + return new AsyncEnumerable(expression); + } + + private static bool IsQueryableType(Type type) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IQueryable<>)) + return true; + + return type.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IQueryable<>)); + } + + public IQueryable CreateQuery(Expression expression) + { + return new AsyncEnumerable(expression); + } + + public object? Execute(Expression expression) + { + return _queryProvider.Execute(expression); + } + + public TResult Execute(Expression expression) + { + return _queryProvider.Execute(expression); + } + + public TResult ExecuteAsync(Expression expression, CancellationToken cancellationToken) + { + return Execute(expression); + } + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/EnsureCreatedMigrationExecutionStrategy.cs b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/EnsureCreatedMigrationExecutionStrategy.cs index cb6d7780..e3b6c98e 100644 --- a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/EnsureCreatedMigrationExecutionStrategy.cs +++ b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/EnsureCreatedMigrationExecutionStrategy.cs @@ -1,9 +1,9 @@ -namespace Thinktecture.EntityFrameworkCore; - -internal sealed class EnsureCreatedMigrationExecutionStrategy : IMigrationExecutionStrategy -{ - public void Migrate(DbContext ctx) - { - ctx.Database.EnsureCreated(); - } +namespace Thinktecture.EntityFrameworkCore; + +internal sealed class EnsureCreatedMigrationExecutionStrategy : IMigrationExecutionStrategy +{ + public void Migrate(DbContext ctx) + { + ctx.Database.EnsureCreated(); + } } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/IMigrationExecutionStrategy.cs b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/IMigrationExecutionStrategy.cs index bf334a4f..976720c2 100644 --- a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/IMigrationExecutionStrategy.cs +++ b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/IMigrationExecutionStrategy.cs @@ -1,30 +1,30 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; - -namespace Thinktecture.EntityFrameworkCore; - -/// -/// Migrates the database. -/// -public interface IMigrationExecutionStrategy -{ - /// - /// The database will not be migrated. - /// - public static readonly IMigrationExecutionStrategy NoMigration = new NoMigrationExecutionStrategy(); - - /// - /// The database will be migrated using . - /// - public static readonly IMigrationExecutionStrategy Migrations = new MigrationExecutionStrategy(); - - /// - /// The database will be migrated using . - /// - public static readonly IMigrationExecutionStrategy EnsureCreated = new EnsureCreatedMigrationExecutionStrategy(); - - /// - /// Migrates the database. - /// - /// Database context. - void Migrate(DbContext ctx); -} +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Thinktecture.EntityFrameworkCore; + +/// +/// Migrates the database. +/// +public interface IMigrationExecutionStrategy +{ + /// + /// The database will not be migrated. + /// + public static readonly IMigrationExecutionStrategy NoMigration = new NoMigrationExecutionStrategy(); + + /// + /// The database will be migrated using . + /// + public static readonly IMigrationExecutionStrategy Migrations = new MigrationExecutionStrategy(); + + /// + /// The database will be migrated using . + /// + public static readonly IMigrationExecutionStrategy EnsureCreated = new EnsureCreatedMigrationExecutionStrategy(); + + /// + /// Migrates the database. + /// + /// Database context. + void Migrate(DbContext ctx); +} diff --git a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Infrastructure/CachePerContextModelCacheKeyFactory.cs b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Infrastructure/CachePerContextModelCacheKeyFactory.cs index e3f42049..ccac81f3 100644 --- a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Infrastructure/CachePerContextModelCacheKeyFactory.cs +++ b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Infrastructure/CachePerContextModelCacheKeyFactory.cs @@ -1,24 +1,24 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; - -namespace Thinktecture.EntityFrameworkCore.Infrastructure; - -/// -/// Disables the model cache. -/// -// ReSharper disable once ClassNeverInstantiated.Global -public sealed class CachePerContextModelCacheKeyFactory : IModelCacheKeyFactory -{ - /// Gets the model cache key for a given context. - /// The context to get the model cache key for. - /// The created key. - public object Create(DbContext context) - { - return Create(context, false); - } - - /// - public object Create(DbContext context, bool designTime) - { - return (context, designTime); - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Thinktecture.EntityFrameworkCore.Infrastructure; + +/// +/// Disables the model cache. +/// +// ReSharper disable once ClassNeverInstantiated.Global +public sealed class CachePerContextModelCacheKeyFactory : IModelCacheKeyFactory +{ + /// Gets the model cache key for a given context. + /// The context to get the model cache key for. + /// The created key. + public object Create(DbContext context) + { + return Create(context, false); + } + + /// + public object Create(DbContext context, bool designTime) + { + return (context, designTime); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/MigrationExecutionStrategy.cs b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/MigrationExecutionStrategy.cs index 21ee0a32..df19167d 100644 --- a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/MigrationExecutionStrategy.cs +++ b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/MigrationExecutionStrategy.cs @@ -1,9 +1,9 @@ -namespace Thinktecture.EntityFrameworkCore; - -internal sealed class MigrationExecutionStrategy : IMigrationExecutionStrategy -{ - public void Migrate(DbContext ctx) - { - ctx.Database.Migrate(); - } +namespace Thinktecture.EntityFrameworkCore; + +internal sealed class MigrationExecutionStrategy : IMigrationExecutionStrategy +{ + public void Migrate(DbContext ctx) + { + ctx.Database.Migrate(); + } } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/NoMigrationExecutionStrategy.cs b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/NoMigrationExecutionStrategy.cs index b1097ffc..de77519a 100644 --- a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/NoMigrationExecutionStrategy.cs +++ b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/NoMigrationExecutionStrategy.cs @@ -1,8 +1,8 @@ -namespace Thinktecture.EntityFrameworkCore; - -internal sealed class NoMigrationExecutionStrategy : IMigrationExecutionStrategy -{ - public void Migrate(DbContext ctx) - { - } +namespace Thinktecture.EntityFrameworkCore; + +internal sealed class NoMigrationExecutionStrategy : IMigrationExecutionStrategy +{ + public void Migrate(DbContext ctx) + { + } } \ No newline at end of file diff --git a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Testing/ITestDbContextProvider.cs b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Testing/ITestDbContextProvider.cs index f20ea7a4..6e289085 100644 --- a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Testing/ITestDbContextProvider.cs +++ b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Testing/ITestDbContextProvider.cs @@ -1,33 +1,33 @@ -namespace Thinktecture.EntityFrameworkCore.Testing; - -/// -/// Provides instances of for testing purposes. -/// -/// Type of the database context. -public interface ITestDbContextProvider : IDbContextFactory, IAsyncDisposable, IDisposable - where T : DbContext -{ - /// - /// Database context for setting up the test data. - /// - T ArrangeDbContext { get; } - - /// - /// Database context for the actual test. - /// - T ActDbContext { get; } - - /// - /// Database context for making assertions. - /// - T AssertDbContext { get; } - - /// - /// Creates a new . - /// - /// - /// Indication whether to use the master connection or a new one. - /// - /// A new instance of . - T CreateDbContext(bool useMasterConnection); -} +namespace Thinktecture.EntityFrameworkCore.Testing; + +/// +/// Provides instances of for testing purposes. +/// +/// Type of the database context. +public interface ITestDbContextProvider : IDbContextFactory, IAsyncDisposable, IDisposable + where T : DbContext +{ + /// + /// Database context for setting up the test data. + /// + T ArrangeDbContext { get; } + + /// + /// Database context for the actual test. + /// + T ActDbContext { get; } + + /// + /// Database context for making assertions. + /// + T AssertDbContext { get; } + + /// + /// Creates a new . + /// + /// + /// Indication whether to use the master connection or a new one. + /// + /// A new instance of . + T CreateDbContext(bool useMasterConnection); +} diff --git a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Testing/TestDbContextProviderBuilder.cs b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Testing/TestDbContextProviderBuilder.cs index 7910f834..98c3c160 100644 --- a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Testing/TestDbContextProviderBuilder.cs +++ b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Testing/TestDbContextProviderBuilder.cs @@ -1,147 +1,147 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.Extensions.Logging; -using Serilog; -using Thinktecture.EntityFrameworkCore.Diagnostics; -using Thinktecture.EntityFrameworkCore.Infrastructure; -using Thinktecture.Logging; -using Xunit.Abstractions; - -namespace Thinktecture.EntityFrameworkCore.Testing; - -/// -/// Base class for builders of . -/// -public abstract class TestDbContextProviderBuilder -{ - private Serilog.ILogger? _serilogLogger; - private ILoggerFactory? _loggerFactory; - private bool _enableSensitiveDataLogging; - private bool _collectExecutedCommands; - private LogLevel _migrationLogLevel = LogLevel.Information; - private bool _disableModelCache; - private IMigrationExecutionStrategy? _migrationExecutionStrategy; - - /// - /// Specifies the migration strategy to use. - /// Default is . - /// - /// Migration strategy to use. - /// Current builder for chaining - public void UseMigrationExecutionStrategy(IMigrationExecutionStrategy migrationExecutionStrategy) - { - ArgumentNullException.ThrowIfNull(migrationExecutionStrategy); - - _migrationExecutionStrategy = migrationExecutionStrategy; - } - - /// - /// Indication whether collect executed commands or not. - /// - /// Current builder for chaining - public void CollectExecutedCommands(bool collectExecutedCommands) - { - _collectExecutedCommands = collectExecutedCommands; - } - - /// - /// Sets the logger factory to be used by EF. - /// - /// Logger factory to use. - /// Enables or disables sensitive data logging. - protected void UseLogging(ILoggerFactory? loggerFactory, bool enableSensitiveDataLogging) - { - _loggerFactory = loggerFactory; - _enableSensitiveDataLogging = enableSensitiveDataLogging; - } - - /// - /// Sets output helper to be used by Serilog which is passed to EF. - /// - /// XUnit output. - /// Enables or disables sensitive data logging. - /// The serilog output template. - protected void UseLogging( - ITestOutputHelper? testOutputHelper, - bool enableSensitiveDataLogging, - string? outputTemplate) - { - _serilogLogger = testOutputHelper is null - ? null - : new LoggerConfiguration() - .WriteTo.TestOutput(testOutputHelper, outputTemplate: outputTemplate ?? "[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}") - .CreateLogger(); - - UseLogging(null, enableSensitiveDataLogging); - } - - /// - /// Sets the log level during migrations. - /// - /// Minimum log level to use during migrations. - protected void UseMigrationLogLevel(LogLevel logLevel) - { - _migrationLogLevel = logLevel; - } - - /// - /// Disables EF model cache. - /// - /// Current builder for chaining. - protected void DisableModelCache(bool disableModelCache) - { - _disableModelCache = disableModelCache; - } - - /// - /// Disables "LoggingCacheTime" of EF which is required to be able to change the at will. - /// - /// Builder. - protected virtual void DisableLoggingCacheTime(DbContextOptionsBuilder builder) - { - builder.AddOrUpdateExtension(extension => extension.WithLoggingCacheTime(TimeSpan.Zero)); - } - - /// - /// Applies default settings to provided . - /// - /// Current building state. - /// Builder to apply settings to. - protected virtual void ApplyDefaultConfiguration( - TestDbContextProviderBuilderState state, - DbContextOptionsBuilder dbContextOptionsBuilder) - { - state.MigrationExecutionStrategy ??= _migrationExecutionStrategy; - - dbContextOptionsBuilder.UseLoggerFactory(state.LoggingOptions.LoggerFactory) - .EnableSensitiveDataLogging(state.LoggingOptions.EnableSensitiveDataLogging); - - DisableLoggingCacheTime(dbContextOptionsBuilder); - - if (_collectExecutedCommands) - { - state.CommandCapturingInterceptor ??= new CommandCapturingInterceptor(); - dbContextOptionsBuilder.AddInterceptors(state.CommandCapturingInterceptor); - } - - if (_disableModelCache) - dbContextOptionsBuilder.ReplaceService(); - } - - /// - /// Creates logging options. - /// - /// A new instance of . - protected TestingLoggingOptions CreateLoggingOptions() - { - return CreateLoggingOptions(_loggerFactory, _serilogLogger); - } - - /// - /// Creates logging options. - /// - /// A new instance of . - public TestingLoggingOptions CreateLoggingOptions(ILoggerFactory? loggerFactory, Serilog.ILogger? serilogLogger) - { - return TestingLoggingOptions.Create(loggerFactory, serilogLogger, _enableSensitiveDataLogging, _migrationLogLevel); - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.Extensions.Logging; +using Serilog; +using Thinktecture.EntityFrameworkCore.Diagnostics; +using Thinktecture.EntityFrameworkCore.Infrastructure; +using Thinktecture.Logging; +using Xunit.Abstractions; + +namespace Thinktecture.EntityFrameworkCore.Testing; + +/// +/// Base class for builders of . +/// +public abstract class TestDbContextProviderBuilder +{ + private Serilog.ILogger? _serilogLogger; + private ILoggerFactory? _loggerFactory; + private bool _enableSensitiveDataLogging; + private bool _collectExecutedCommands; + private LogLevel _migrationLogLevel = LogLevel.Information; + private bool _disableModelCache; + private IMigrationExecutionStrategy? _migrationExecutionStrategy; + + /// + /// Specifies the migration strategy to use. + /// Default is . + /// + /// Migration strategy to use. + /// Current builder for chaining + public void UseMigrationExecutionStrategy(IMigrationExecutionStrategy migrationExecutionStrategy) + { + ArgumentNullException.ThrowIfNull(migrationExecutionStrategy); + + _migrationExecutionStrategy = migrationExecutionStrategy; + } + + /// + /// Indication whether collect executed commands or not. + /// + /// Current builder for chaining + public void CollectExecutedCommands(bool collectExecutedCommands) + { + _collectExecutedCommands = collectExecutedCommands; + } + + /// + /// Sets the logger factory to be used by EF. + /// + /// Logger factory to use. + /// Enables or disables sensitive data logging. + protected void UseLogging(ILoggerFactory? loggerFactory, bool enableSensitiveDataLogging) + { + _loggerFactory = loggerFactory; + _enableSensitiveDataLogging = enableSensitiveDataLogging; + } + + /// + /// Sets output helper to be used by Serilog which is passed to EF. + /// + /// XUnit output. + /// Enables or disables sensitive data logging. + /// The serilog output template. + protected void UseLogging( + ITestOutputHelper? testOutputHelper, + bool enableSensitiveDataLogging, + string? outputTemplate) + { + _serilogLogger = testOutputHelper is null + ? null + : new LoggerConfiguration() + .WriteTo.TestOutput(testOutputHelper, outputTemplate: outputTemplate ?? "[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}") + .CreateLogger(); + + UseLogging(null, enableSensitiveDataLogging); + } + + /// + /// Sets the log level during migrations. + /// + /// Minimum log level to use during migrations. + protected void UseMigrationLogLevel(LogLevel logLevel) + { + _migrationLogLevel = logLevel; + } + + /// + /// Disables EF model cache. + /// + /// Current builder for chaining. + protected void DisableModelCache(bool disableModelCache) + { + _disableModelCache = disableModelCache; + } + + /// + /// Disables "LoggingCacheTime" of EF which is required to be able to change the at will. + /// + /// Builder. + protected virtual void DisableLoggingCacheTime(DbContextOptionsBuilder builder) + { + builder.AddOrUpdateExtension(extension => extension.WithLoggingCacheTime(TimeSpan.Zero)); + } + + /// + /// Applies default settings to provided . + /// + /// Current building state. + /// Builder to apply settings to. + protected virtual void ApplyDefaultConfiguration( + TestDbContextProviderBuilderState state, + DbContextOptionsBuilder dbContextOptionsBuilder) + { + state.MigrationExecutionStrategy ??= _migrationExecutionStrategy; + + dbContextOptionsBuilder.UseLoggerFactory(state.LoggingOptions.LoggerFactory) + .EnableSensitiveDataLogging(state.LoggingOptions.EnableSensitiveDataLogging); + + DisableLoggingCacheTime(dbContextOptionsBuilder); + + if (_collectExecutedCommands) + { + state.CommandCapturingInterceptor ??= new CommandCapturingInterceptor(); + dbContextOptionsBuilder.AddInterceptors(state.CommandCapturingInterceptor); + } + + if (_disableModelCache) + dbContextOptionsBuilder.ReplaceService(); + } + + /// + /// Creates logging options. + /// + /// A new instance of . + protected TestingLoggingOptions CreateLoggingOptions() + { + return CreateLoggingOptions(_loggerFactory, _serilogLogger); + } + + /// + /// Creates logging options. + /// + /// A new instance of . + public TestingLoggingOptions CreateLoggingOptions(ILoggerFactory? loggerFactory, Serilog.ILogger? serilogLogger) + { + return TestingLoggingOptions.Create(loggerFactory, serilogLogger, _enableSensitiveDataLogging, _migrationLogLevel); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Testing/TestDbContextProviderOptions.cs b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Testing/TestDbContextProviderOptions.cs index f0ac5f79..e4897883 100644 --- a/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Testing/TestDbContextProviderOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Testing/EntityFrameworkCore/Testing/TestDbContextProviderOptions.cs @@ -1,66 +1,66 @@ -using System.Data.Common; -using Thinktecture.Logging; - -namespace Thinktecture.EntityFrameworkCore.Testing; - -/// -/// Options for the . -/// -/// -public abstract class TestDbContextProviderOptions - where T : DbContext -{ - /// - /// Master database connection. - /// - public DbConnection MasterConnection { get; } - - /// - /// Determines whether and how to migrate the database. - /// - public IMigrationExecutionStrategy MigrationExecutionStrategy { get; } - - /// - /// Options that use the . - /// - public DbContextOptionsBuilder MasterDbContextOptionsBuilder { get; } - - /// - /// Options that create a new connection. - /// - public DbContextOptionsBuilder DbContextOptionsBuilder { get; } - - /// - /// Contains executed commands if this feature was activated. - /// - public TestingLoggingOptions TestingLoggingOptions { get; } - - /// - /// Callback to execute on every creation of a new . - /// - public IReadOnlyList> ContextInitializations { get; } - - /// - /// Contains executed commands if this feature was activated. - /// - public IReadOnlyCollection? ExecutedCommands { get; init; } - - /// - /// Initializes new instance of . - /// - protected TestDbContextProviderOptions( - DbConnection masterConnection, - IMigrationExecutionStrategy migrationExecutionStrategy, - DbContextOptionsBuilder masterDbContextOptionsBuilder, - DbContextOptionsBuilder dbContextOptionsBuilder, - TestingLoggingOptions testingLoggingOptions, - IReadOnlyList> contextInitializations) - { - MasterConnection = masterConnection; - MigrationExecutionStrategy = migrationExecutionStrategy; - MasterDbContextOptionsBuilder = masterDbContextOptionsBuilder; - DbContextOptionsBuilder = dbContextOptionsBuilder; - ContextInitializations = contextInitializations; - TestingLoggingOptions = testingLoggingOptions; - } -} +using System.Data.Common; +using Thinktecture.Logging; + +namespace Thinktecture.EntityFrameworkCore.Testing; + +/// +/// Options for the . +/// +/// +public abstract class TestDbContextProviderOptions + where T : DbContext +{ + /// + /// Master database connection. + /// + public DbConnection MasterConnection { get; } + + /// + /// Determines whether and how to migrate the database. + /// + public IMigrationExecutionStrategy MigrationExecutionStrategy { get; } + + /// + /// Options that use the . + /// + public DbContextOptionsBuilder MasterDbContextOptionsBuilder { get; } + + /// + /// Options that create a new connection. + /// + public DbContextOptionsBuilder DbContextOptionsBuilder { get; } + + /// + /// Contains executed commands if this feature was activated. + /// + public TestingLoggingOptions TestingLoggingOptions { get; } + + /// + /// Callback to execute on every creation of a new . + /// + public IReadOnlyList> ContextInitializations { get; } + + /// + /// Contains executed commands if this feature was activated. + /// + public IReadOnlyCollection? ExecutedCommands { get; init; } + + /// + /// Initializes new instance of . + /// + protected TestDbContextProviderOptions( + DbConnection masterConnection, + IMigrationExecutionStrategy migrationExecutionStrategy, + DbContextOptionsBuilder masterDbContextOptionsBuilder, + DbContextOptionsBuilder dbContextOptionsBuilder, + TestingLoggingOptions testingLoggingOptions, + IReadOnlyList> contextInitializations) + { + MasterConnection = masterConnection; + MigrationExecutionStrategy = migrationExecutionStrategy; + MasterDbContextOptionsBuilder = masterDbContextOptionsBuilder; + DbContextOptionsBuilder = dbContextOptionsBuilder; + ContextInitializations = contextInitializations; + TestingLoggingOptions = testingLoggingOptions; + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Testing/Extensions/TestingEnumerableExtensions.cs b/src/Thinktecture.EntityFrameworkCore.Testing/Extensions/TestingEnumerableExtensions.cs index 8b8d90d0..a723d572 100644 --- a/src/Thinktecture.EntityFrameworkCore.Testing/Extensions/TestingEnumerableExtensions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Testing/Extensions/TestingEnumerableExtensions.cs @@ -1,21 +1,21 @@ -using Thinktecture.Collections; - -namespace Thinktecture; - -/// -/// Extension methods for . -/// -public static class TestingEnumerableExtensions -{ - /// - /// Creates an that implements . - /// - /// A collection to make an from. - /// Item type. - /// An implementation of . - /// is null. - public static IQueryable AsAsyncQueryable(this IEnumerable collection) - { - return new AsyncEnumerable(collection); - } -} +using Thinktecture.Collections; + +namespace Thinktecture; + +/// +/// Extension methods for . +/// +public static class TestingEnumerableExtensions +{ + /// + /// Creates an that implements . + /// + /// A collection to make an from. + /// Item type. + /// An implementation of . + /// is null. + public static IQueryable AsAsyncQueryable(this IEnumerable collection) + { + return new AsyncEnumerable(collection); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Testing/Logging/TestingLoggingOptions.cs b/src/Thinktecture.EntityFrameworkCore.Testing/Logging/TestingLoggingOptions.cs index 8796007d..116d3fd9 100644 --- a/src/Thinktecture.EntityFrameworkCore.Testing/Logging/TestingLoggingOptions.cs +++ b/src/Thinktecture.EntityFrameworkCore.Testing/Logging/TestingLoggingOptions.cs @@ -1,90 +1,90 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Serilog; -using Thinktecture.EntityFrameworkCore.Testing; - -namespace Thinktecture.Logging; - -/// -/// Options provided to -/// -public class TestingLoggingOptions : IDisposable -{ - /// - /// Options without concrete logger. - /// - public static readonly TestingLoggingOptions Empty = Create(null, null, false, LogLevel.Information); - - /// - /// Logger factory. - /// - public ILoggerFactory LoggerFactory { get; } - - /// - /// Log level switch. - /// - public TestingLogLevelSwitch LogLevelSwitch { get; } - - /// - /// Indication whether EF should enable sensitive data logging or not. - /// - public bool EnableSensitiveDataLogging { get; } - - /// - /// Log level to use during migrations. - /// - public LogLevel MigrationLogLevel { get; } - - private TestingLoggingOptions( - ILoggerFactory loggerFactory, - TestingLogLevelSwitch logLevelSwitch, - bool enableSensitiveDataLogging, - LogLevel migrationLogLevel) - { - LoggerFactory = loggerFactory; - LogLevelSwitch = logLevelSwitch; - EnableSensitiveDataLogging = enableSensitiveDataLogging; - MigrationLogLevel = migrationLogLevel; - } - - /// - /// Create a new instance of . - /// - /// Logger factory. - /// Serilog config. - /// Indication whether EF should enable sensitive data logging or not. - /// Log level to use during migrations. - /// A new instance of . - public static TestingLoggingOptions Create( - ILoggerFactory? loggerFactory, - Serilog.ILogger? serilogLogger, - bool enableSensitiveDataLogging, - LogLevel migrationLogLevel) - { - var logLevelSwitch = new TestingLogLevelSwitch(); - - var newLoggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder => - { - if (loggerFactory is not null) - builder.AddProvider(new SubLoggerFactory(loggerFactory)); - - if (serilogLogger is not null) - builder.AddSerilog(serilogLogger); - - builder.Services.Configure(options => - { - options.MinLevel = LogLevel.Trace; - options.Rules.Clear(); - }); - builder.AddFilter(level => level >= logLevelSwitch.MinimumLogLevel); - }); - - return new TestingLoggingOptions(newLoggerFactory, logLevelSwitch, enableSensitiveDataLogging, migrationLogLevel); - } - - /// - public void Dispose() - { - LoggerFactory.Dispose(); - } -} +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Serilog; +using Thinktecture.EntityFrameworkCore.Testing; + +namespace Thinktecture.Logging; + +/// +/// Options provided to +/// +public class TestingLoggingOptions : IDisposable +{ + /// + /// Options without concrete logger. + /// + public static readonly TestingLoggingOptions Empty = Create(null, null, false, LogLevel.Information); + + /// + /// Logger factory. + /// + public ILoggerFactory LoggerFactory { get; } + + /// + /// Log level switch. + /// + public TestingLogLevelSwitch LogLevelSwitch { get; } + + /// + /// Indication whether EF should enable sensitive data logging or not. + /// + public bool EnableSensitiveDataLogging { get; } + + /// + /// Log level to use during migrations. + /// + public LogLevel MigrationLogLevel { get; } + + private TestingLoggingOptions( + ILoggerFactory loggerFactory, + TestingLogLevelSwitch logLevelSwitch, + bool enableSensitiveDataLogging, + LogLevel migrationLogLevel) + { + LoggerFactory = loggerFactory; + LogLevelSwitch = logLevelSwitch; + EnableSensitiveDataLogging = enableSensitiveDataLogging; + MigrationLogLevel = migrationLogLevel; + } + + /// + /// Create a new instance of . + /// + /// Logger factory. + /// Serilog config. + /// Indication whether EF should enable sensitive data logging or not. + /// Log level to use during migrations. + /// A new instance of . + public static TestingLoggingOptions Create( + ILoggerFactory? loggerFactory, + Serilog.ILogger? serilogLogger, + bool enableSensitiveDataLogging, + LogLevel migrationLogLevel) + { + var logLevelSwitch = new TestingLogLevelSwitch(); + + var newLoggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder => + { + if (loggerFactory is not null) + builder.AddProvider(new SubLoggerFactory(loggerFactory)); + + if (serilogLogger is not null) + builder.AddSerilog(serilogLogger); + + builder.Services.Configure(options => + { + options.MinLevel = LogLevel.Trace; + options.Rules.Clear(); + }); + builder.AddFilter(level => level >= logLevelSwitch.MinimumLogLevel); + }); + + return new TestingLoggingOptions(newLoggerFactory, logLevelSwitch, enableSensitiveDataLogging, migrationLogLevel); + } + + /// + public void Dispose() + { + LoggerFactory.Dispose(); + } +} diff --git a/src/Thinktecture.EntityFrameworkCore.Testing/Thinktecture.EntityFrameworkCore.Testing.csproj b/src/Thinktecture.EntityFrameworkCore.Testing/Thinktecture.EntityFrameworkCore.Testing.csproj index f618dda9..0a327455 100644 --- a/src/Thinktecture.EntityFrameworkCore.Testing/Thinktecture.EntityFrameworkCore.Testing.csproj +++ b/src/Thinktecture.EntityFrameworkCore.Testing/Thinktecture.EntityFrameworkCore.Testing.csproj @@ -1,16 +1,16 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 1ab93600..f4e4fdd8 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -1,33 +1,33 @@ - - - - $([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../')) - false - $(NoWarn);CA1062;EF1002;xUnit1041 - - - - - - - net8.0;net9.0 - - - - - - - - - - - - - - - - - - - - + + + + $([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../')) + false + $(NoWarn);CA1062;EF1002;xUnit1041 + + + + + + + net8.0;net9.0 + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/EntityFrameworkCore/BulkOperations/PropertiesProviderTests/Exclude.cs b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/EntityFrameworkCore/BulkOperations/PropertiesProviderTests/Exclude.cs index de0c6546..8245c761 100644 --- a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/EntityFrameworkCore/BulkOperations/PropertiesProviderTests/Exclude.cs +++ b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/EntityFrameworkCore/BulkOperations/PropertiesProviderTests/Exclude.cs @@ -1,94 +1,94 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations.PropertiesProviderTests; - -public class Exclude -{ - [Fact] - public void Should_throw_if_expression_is_null() - { - Action action = () => IEntityPropertiesProvider.Exclude(null!); - action.Should().Throw(); - } - - [Fact] - public void Should_throw_if_expression_return_constant() - { - Action action = () => IEntityPropertiesProvider.Exclude(entity => null!); - action.Should().Throw(); - } - - [Fact] - public void Should_return_all_properties_if_no_properties_provided() - { - var entityType = GetEntityType(); - var propertiesProvider = IEntityPropertiesProvider.Exclude(entity => new { }); - var properties = propertiesProvider.GetPropertiesForTempTable(entityType); - - properties.Should().HaveCount(entityType.GetFlattenedProperties().Count()); - } - - [Fact] - public void Should_extract_all_properties_besides_the_one_specified_by_property_accessor() - { - var entityType = GetEntityType(); - var idProperty = entityType.FindProperty(nameof(TestEntity.Id)) ?? throw new Exception("Property must no be null"); - - var propertiesProvider = IEntityPropertiesProvider.Exclude(entity => entity.Id); - - var properties = propertiesProvider.GetPropertiesForTempTable(entityType); - properties.Should().HaveCount(entityType.GetFlattenedProperties().Count() - 1); - properties.Should().NotContain(idProperty); - } - - [Fact] - public void Should_be_able_to_extract_complex_property_as_a_whole() - { - var entityType = GetEntityType(); - var complexProperty = entityType.FindComplexProperty(nameof(TestEntity.Boundary)) ?? throw new Exception("Property must no be null"); - var propertiesOfComplexType = complexProperty.ComplexType.GetFlattenedProperties(); - - var propertiesProvider = IEntityPropertiesProvider.Exclude(entity => entity.Boundary); - - var properties = propertiesProvider.GetPropertiesForTempTable(entityType); - properties.Should().HaveCount(entityType.GetFlattenedProperties().Count() - 2); - properties.Should().NotContain(propertiesOfComplexType); - } - - [Fact] - public void Should_extract_all_properties_besides_the_ones_specified_by_expression() - { - var entityType = GetEntityType(); - var idProperty = entityType.FindProperty(nameof(TestEntity.Id)) ?? throw new Exception("Property must no be null"); - var countProperty = entityType.FindProperty(nameof(TestEntity.Count)) ?? throw new Exception("Property must no be null"); - - var propertiesProvider = IEntityPropertiesProvider.Exclude(entity => new { entity.Id, entity.Count }); - - var properties = propertiesProvider.GetPropertiesForTempTable(entityType); - properties.Should().HaveCount(entityType.GetFlattenedProperties().Count() - 2); - properties.Should().NotContain(idProperty); - properties.Should().NotContain(countProperty); - } - - [Fact] - public void Should_handle_properties_from_base_classes() - { - var entityType = GetEntityType(); - var idProperty = entityType.FindProperty(nameof(TestEntityWithBaseClass.Id)) ?? throw new Exception("Property must no be null"); - var baseProperty = entityType.FindProperty(nameof(TestEntityWithBaseClass.Base_A)) ?? throw new Exception("Property must no be null"); - - var propertiesProvider = IEntityPropertiesProvider.Exclude(entity => new { entity.Id, entity.Base_A }); - - var properties = propertiesProvider.GetPropertiesForTempTable(entityType); - properties.Should().HaveCount(entityType.GetFlattenedProperties().Count() - 2); - properties.Should().NotContain(idProperty); - properties.Should().NotContain(baseProperty); - } - - private static IEntityType GetEntityType() - { - var options = new DbContextOptionsBuilder().UseSqlite("DataSource=:memory:").Options; - return new TestDbContext(options).Model.GetEntityType(typeof(T)); - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations.PropertiesProviderTests; + +public class Exclude +{ + [Fact] + public void Should_throw_if_expression_is_null() + { + Action action = () => IEntityPropertiesProvider.Exclude(null!); + action.Should().Throw(); + } + + [Fact] + public void Should_throw_if_expression_return_constant() + { + Action action = () => IEntityPropertiesProvider.Exclude(entity => null!); + action.Should().Throw(); + } + + [Fact] + public void Should_return_all_properties_if_no_properties_provided() + { + var entityType = GetEntityType(); + var propertiesProvider = IEntityPropertiesProvider.Exclude(entity => new { }); + var properties = propertiesProvider.GetPropertiesForTempTable(entityType); + + properties.Should().HaveCount(entityType.GetFlattenedProperties().Count()); + } + + [Fact] + public void Should_extract_all_properties_besides_the_one_specified_by_property_accessor() + { + var entityType = GetEntityType(); + var idProperty = entityType.FindProperty(nameof(TestEntity.Id)) ?? throw new Exception("Property must no be null"); + + var propertiesProvider = IEntityPropertiesProvider.Exclude(entity => entity.Id); + + var properties = propertiesProvider.GetPropertiesForTempTable(entityType); + properties.Should().HaveCount(entityType.GetFlattenedProperties().Count() - 1); + properties.Should().NotContain(idProperty); + } + + [Fact] + public void Should_be_able_to_extract_complex_property_as_a_whole() + { + var entityType = GetEntityType(); + var complexProperty = entityType.FindComplexProperty(nameof(TestEntity.Boundary)) ?? throw new Exception("Property must no be null"); + var propertiesOfComplexType = complexProperty.ComplexType.GetFlattenedProperties(); + + var propertiesProvider = IEntityPropertiesProvider.Exclude(entity => entity.Boundary); + + var properties = propertiesProvider.GetPropertiesForTempTable(entityType); + properties.Should().HaveCount(entityType.GetFlattenedProperties().Count() - 2); + properties.Should().NotContain(propertiesOfComplexType); + } + + [Fact] + public void Should_extract_all_properties_besides_the_ones_specified_by_expression() + { + var entityType = GetEntityType(); + var idProperty = entityType.FindProperty(nameof(TestEntity.Id)) ?? throw new Exception("Property must no be null"); + var countProperty = entityType.FindProperty(nameof(TestEntity.Count)) ?? throw new Exception("Property must no be null"); + + var propertiesProvider = IEntityPropertiesProvider.Exclude(entity => new { entity.Id, entity.Count }); + + var properties = propertiesProvider.GetPropertiesForTempTable(entityType); + properties.Should().HaveCount(entityType.GetFlattenedProperties().Count() - 2); + properties.Should().NotContain(idProperty); + properties.Should().NotContain(countProperty); + } + + [Fact] + public void Should_handle_properties_from_base_classes() + { + var entityType = GetEntityType(); + var idProperty = entityType.FindProperty(nameof(TestEntityWithBaseClass.Id)) ?? throw new Exception("Property must no be null"); + var baseProperty = entityType.FindProperty(nameof(TestEntityWithBaseClass.Base_A)) ?? throw new Exception("Property must no be null"); + + var propertiesProvider = IEntityPropertiesProvider.Exclude(entity => new { entity.Id, entity.Base_A }); + + var properties = propertiesProvider.GetPropertiesForTempTable(entityType); + properties.Should().HaveCount(entityType.GetFlattenedProperties().Count() - 2); + properties.Should().NotContain(idProperty); + properties.Should().NotContain(baseProperty); + } + + private static IEntityType GetEntityType() + { + var options = new DbContextOptionsBuilder().UseSqlite("DataSource=:memory:").Options; + return new TestDbContext(options).Model.GetEntityType(typeof(T)); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/EntityFrameworkCore/BulkOperations/PropertiesProviderTests/Include.cs b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/EntityFrameworkCore/BulkOperations/PropertiesProviderTests/Include.cs index a2309a3e..c9333039 100644 --- a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/EntityFrameworkCore/BulkOperations/PropertiesProviderTests/Include.cs +++ b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/EntityFrameworkCore/BulkOperations/PropertiesProviderTests/Include.cs @@ -1,90 +1,90 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations.PropertiesProviderTests; - -public class Include -{ - [Fact] - public void Should_throw_if_expression_is_null() - { - Action action = () => IEntityPropertiesProvider.Include(null!); - action.Should().Throw(); - } - - [Fact] - public void Should_throw_if_expression_return_constant() - { - Action action = () => IEntityPropertiesProvider.Include(entity => null!); - action.Should().Throw(); - } - - [Fact] - public void Should_return_empty_collection_if_no_properties_provided() - { - var entityType = GetEntityType(); - var propertiesProvider = IEntityPropertiesProvider.Include(entity => new { }); - var properties = propertiesProvider.GetPropertiesForTempTable(entityType); - - properties.Should().BeEmpty(); - } - - [Fact] - public void Should_extract_property_accessor() - { - var entityType = GetEntityType(); - var idProperty = entityType.FindProperty(nameof(TestEntity.Id)) ?? throw new Exception("Property must no be null"); - var propertiesProvider = IEntityPropertiesProvider.Include(entity => entity.Id); - - var properties = propertiesProvider.GetPropertiesForTempTable(entityType); - properties.Should().HaveCount(1); - properties.Should().Contain(idProperty); - } - - [Fact] - public void Should_extract_properties() - { - var entityType = GetEntityType(); - var idProperty = entityType.FindProperty(nameof(TestEntity.Id)) ?? throw new Exception("Property must no be null"); - var countProperty = entityType.FindProperty(nameof(TestEntity.Count)) ?? throw new Exception("Property must no be null"); - var propertiesProvider = IEntityPropertiesProvider.Include(entity => new { entity.Id, entity.Count }); - - var properties = propertiesProvider.GetPropertiesForTempTable(entityType); - properties.Should().HaveCount(2); - properties.Should().Contain(idProperty); - properties.Should().Contain(countProperty); - } - - [Fact] - public void Should_extract_complex_object() - { - var entityType = GetEntityType(); - var complexProperty = entityType.FindComplexProperty(nameof(TestEntity.Boundary)) ?? throw new Exception("Property must no be null"); - var propertiesProvider = IEntityPropertiesProvider.Include(entity => entity.Boundary); - - var properties = propertiesProvider.GetPropertiesForTempTable(entityType); - properties.Should().HaveCount(2); - properties.Should().Contain(complexProperty.ComplexType.GetFlattenedProperties()); - } - - [Fact] - public void Should_handle_properties_from_base_classes() - { - var entityType = GetEntityType(); - var idProperty = entityType.FindProperty(nameof(TestEntityWithBaseClass.Id)) ?? throw new Exception("Property must no be null"); - var baseProperty = entityType.FindProperty(nameof(TestEntityWithBaseClass.Base_A)) ?? throw new Exception("Property must no be null"); - - var propertiesProvider = IEntityPropertiesProvider.Include(entity => new { entity.Id, entity.Base_A }); - - var properties = propertiesProvider.GetPropertiesForTempTable(entityType); - properties.Should().HaveCount(2); - properties.Should().Contain(idProperty); - properties.Should().Contain(baseProperty); - } - - private static IEntityType GetEntityType() - { - var options = new DbContextOptionsBuilder().UseSqlite("DataSource=:memory:").Options; - return new TestDbContext(options).Model.GetEntityType(typeof(T)); - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations.PropertiesProviderTests; + +public class Include +{ + [Fact] + public void Should_throw_if_expression_is_null() + { + Action action = () => IEntityPropertiesProvider.Include(null!); + action.Should().Throw(); + } + + [Fact] + public void Should_throw_if_expression_return_constant() + { + Action action = () => IEntityPropertiesProvider.Include(entity => null!); + action.Should().Throw(); + } + + [Fact] + public void Should_return_empty_collection_if_no_properties_provided() + { + var entityType = GetEntityType(); + var propertiesProvider = IEntityPropertiesProvider.Include(entity => new { }); + var properties = propertiesProvider.GetPropertiesForTempTable(entityType); + + properties.Should().BeEmpty(); + } + + [Fact] + public void Should_extract_property_accessor() + { + var entityType = GetEntityType(); + var idProperty = entityType.FindProperty(nameof(TestEntity.Id)) ?? throw new Exception("Property must no be null"); + var propertiesProvider = IEntityPropertiesProvider.Include(entity => entity.Id); + + var properties = propertiesProvider.GetPropertiesForTempTable(entityType); + properties.Should().HaveCount(1); + properties.Should().Contain(idProperty); + } + + [Fact] + public void Should_extract_properties() + { + var entityType = GetEntityType(); + var idProperty = entityType.FindProperty(nameof(TestEntity.Id)) ?? throw new Exception("Property must no be null"); + var countProperty = entityType.FindProperty(nameof(TestEntity.Count)) ?? throw new Exception("Property must no be null"); + var propertiesProvider = IEntityPropertiesProvider.Include(entity => new { entity.Id, entity.Count }); + + var properties = propertiesProvider.GetPropertiesForTempTable(entityType); + properties.Should().HaveCount(2); + properties.Should().Contain(idProperty); + properties.Should().Contain(countProperty); + } + + [Fact] + public void Should_extract_complex_object() + { + var entityType = GetEntityType(); + var complexProperty = entityType.FindComplexProperty(nameof(TestEntity.Boundary)) ?? throw new Exception("Property must no be null"); + var propertiesProvider = IEntityPropertiesProvider.Include(entity => entity.Boundary); + + var properties = propertiesProvider.GetPropertiesForTempTable(entityType); + properties.Should().HaveCount(2); + properties.Should().Contain(complexProperty.ComplexType.GetFlattenedProperties()); + } + + [Fact] + public void Should_handle_properties_from_base_classes() + { + var entityType = GetEntityType(); + var idProperty = entityType.FindProperty(nameof(TestEntityWithBaseClass.Id)) ?? throw new Exception("Property must no be null"); + var baseProperty = entityType.FindProperty(nameof(TestEntityWithBaseClass.Base_A)) ?? throw new Exception("Property must no be null"); + + var propertiesProvider = IEntityPropertiesProvider.Include(entity => new { entity.Id, entity.Base_A }); + + var properties = propertiesProvider.GetPropertiesForTempTable(entityType); + properties.Should().HaveCount(2); + properties.Should().Contain(idProperty); + properties.Should().Contain(baseProperty); + } + + private static IEntityType GetEntityType() + { + var options = new DbContextOptionsBuilder().UseSqlite("DataSource=:memory:").Options; + return new TestDbContext(options).Model.GetEntityType(typeof(T)); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/ConvertibleClass.cs b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/ConvertibleClass.cs index 3748667d..5e79af6d 100644 --- a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/ConvertibleClass.cs +++ b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/ConvertibleClass.cs @@ -1,11 +1,11 @@ -namespace Thinktecture.TestDatabaseContext; - -public class ConvertibleClass -{ - public int Key { get; set; } - - public ConvertibleClass(int key) - { - Key = key; - } +namespace Thinktecture.TestDatabaseContext; + +public class ConvertibleClass +{ + public int Key { get; set; } + + public ConvertibleClass(int key) + { + Key = key; + } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestDbContext.cs b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestDbContext.cs index e97bb6b0..004dcc75 100644 --- a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestDbContext.cs +++ b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestDbContext.cs @@ -1,29 +1,29 @@ -namespace Thinktecture.TestDatabaseContext; - -public class TestDbContext : DbContext -{ - public DbSet TestEntities { get; set; } = null!; - - public TestDbContext(DbContextOptions options) - : base(options) - { - } - - /// - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity().Property(e => e.ConvertibleClass) - .HasConversion(c => c!.Key, k => new ConvertibleClass(k)); - - modelBuilder.Entity().ComplexProperty(e => e.Boundary, - builder => - { - builder.Property(b => b.Upper); - builder.Property(b => b.Lower); - }); - - TestEntityWithBaseClass.Configure(modelBuilder); - } -} +namespace Thinktecture.TestDatabaseContext; + +public class TestDbContext : DbContext +{ + public DbSet TestEntities { get; set; } = null!; + + public TestDbContext(DbContextOptions options) + : base(options) + { + } + + /// + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity().Property(e => e.ConvertibleClass) + .HasConversion(c => c!.Key, k => new ConvertibleClass(k)); + + modelBuilder.Entity().ComplexProperty(e => e.Boundary, + builder => + { + builder.Property(b => b.Upper); + builder.Property(b => b.Lower); + }); + + TestEntityWithBaseClass.Configure(modelBuilder); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestEntity.cs b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestEntity.cs index d60b2662..d194056d 100644 --- a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestEntity.cs +++ b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestEntity.cs @@ -1,30 +1,30 @@ -namespace Thinktecture.TestDatabaseContext; - -public class TestEntity -{ - public Guid Id { get; set; } - public string? Name { get; set; } - public int Count { get; set; } - public ConvertibleClass? ConvertibleClass { get; set; } - public BoundaryValueObject Boundary { get; set; } = new(1, 2); - - private int _propertyWithBackingField; - - public int PropertyWithBackingField - { - get => _propertyWithBackingField; - set => _propertyWithBackingField = value; - } - - private int _privateField; - - public int GetPrivateField() - { - return _privateField; - } - - public void SetPrivateField(int value) - { - _privateField = value; - } -} +namespace Thinktecture.TestDatabaseContext; + +public class TestEntity +{ + public Guid Id { get; set; } + public string? Name { get; set; } + public int Count { get; set; } + public ConvertibleClass? ConvertibleClass { get; set; } + public BoundaryValueObject Boundary { get; set; } = new(1, 2); + + private int _propertyWithBackingField; + + public int PropertyWithBackingField + { + get => _propertyWithBackingField; + set => _propertyWithBackingField = value; + } + + private int _privateField; + + public int GetPrivateField() + { + return _privateField; + } + + public void SetPrivateField(int value) + { + _privateField = value; + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestEntityBaseClass.cs b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestEntityBaseClass.cs index a87cde01..a314b0fa 100644 --- a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestEntityBaseClass.cs +++ b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestEntityBaseClass.cs @@ -1,8 +1,8 @@ -namespace Thinktecture.TestDatabaseContext; - -#pragma warning disable 8618 -public class TestEntityBaseClass -{ - public string Base_A { get; set; } - public string Base_B { get; set; } -} +namespace Thinktecture.TestDatabaseContext; + +#pragma warning disable 8618 +public class TestEntityBaseClass +{ + public string Base_A { get; set; } + public string Base_B { get; set; } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestEntityWithBaseClass.cs b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestEntityWithBaseClass.cs index f8d01b18..6630eaef 100644 --- a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestEntityWithBaseClass.cs +++ b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/TestDatabaseContext/TestEntityWithBaseClass.cs @@ -1,13 +1,13 @@ -namespace Thinktecture.TestDatabaseContext; -#pragma warning disable 8618 - -public class TestEntityWithBaseClass : TestEntityBaseClass -{ - public Guid Id { get; set; } - public Guid Description { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(); - } -} +namespace Thinktecture.TestDatabaseContext; +#pragma warning disable 8618 + +public class TestEntityWithBaseClass : TestEntityBaseClass +{ + public Guid Id { get; set; } + public Guid Description { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests.csproj b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests.csproj index 743b9e47..4d9740eb 100644 --- a/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests.csproj +++ b/tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests/Thinktecture.EntityFrameworkCore.BulkOperations.Tests.csproj @@ -1,15 +1,15 @@ - - - - $(NoWarn);CS1591;CA2000 - - - - - - - - - - - + + + + $(NoWarn);CS1591;CA2000 + + + + + + + + + + + diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderFactoryTests/Create.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderFactoryTests/Create.cs index 69431901..e549733a 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderFactoryTests/Create.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderFactoryTests/Create.cs @@ -1,61 +1,61 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.Data.EntityDataReaderFactoryTests; - -public class Create : IntegrationTestsBase -{ - private EntityDataReaderFactory? _sut; - - private readonly PropertyWithNavigations _column2Property; - - // ReSharper disable once InconsistentNaming - private EntityDataReaderFactory SUT => _sut ??= new EntityDataReaderFactory(new PropertyGetterCache(LoggerFactory)); - - public Create(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - _column2Property = new PropertyWithNavigations(ArrangeDbContext.GetEntityType().GetProperty(nameof(TestEntity.Column2)), Array.Empty()); - } - - [Fact] - public void Should_throw_if_context_is_null() - { - SUT.Invoking(sut => sut.Create(null!, Array.Empty(), Array.Empty(), false)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_if_entities_is_null() - { - SUT.Invoking(sut => sut.Create(ActDbContext, null!, Array.Empty(), false)) - .Should().Throw(); - } - - [Fact] - public void Should_throw_if_properties_is_null() - { - SUT.Invoking(sut => sut.Create(ActDbContext, Array.Empty(), null!, false)) - .Should().Throw(); - } - - [Fact] - public void Should_generate_factory_if_entities_are_empty() - { - var factory = SUT.Create(ActDbContext, Array.Empty(), new[] { _column2Property }, false); - - factory.Should().NotBeNull(); - factory.Read().Should().BeFalse(); - } - - [Fact] - public void Should_generate_factory_for_provided_properties() - { - var entity = new TestEntity { Column2 = "value" }; - var factory = SUT.Create(ActDbContext, new[] { entity }, new[] { _column2Property }, false); - - factory.FieldCount.Should().Be(1); - factory.Read().Should().BeTrue(); - factory.GetValue(0).Should().Be("value"); - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.Data.EntityDataReaderFactoryTests; + +public class Create : IntegrationTestsBase +{ + private EntityDataReaderFactory? _sut; + + private readonly PropertyWithNavigations _column2Property; + + // ReSharper disable once InconsistentNaming + private EntityDataReaderFactory SUT => _sut ??= new EntityDataReaderFactory(new PropertyGetterCache(LoggerFactory)); + + public Create(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + _column2Property = new PropertyWithNavigations(ArrangeDbContext.GetEntityType().GetProperty(nameof(TestEntity.Column2)), Array.Empty()); + } + + [Fact] + public void Should_throw_if_context_is_null() + { + SUT.Invoking(sut => sut.Create(null!, Array.Empty(), Array.Empty(), false)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_if_entities_is_null() + { + SUT.Invoking(sut => sut.Create(ActDbContext, null!, Array.Empty(), false)) + .Should().Throw(); + } + + [Fact] + public void Should_throw_if_properties_is_null() + { + SUT.Invoking(sut => sut.Create(ActDbContext, Array.Empty(), null!, false)) + .Should().Throw(); + } + + [Fact] + public void Should_generate_factory_if_entities_are_empty() + { + var factory = SUT.Create(ActDbContext, Array.Empty(), new[] { _column2Property }, false); + + factory.Should().NotBeNull(); + factory.Read().Should().BeFalse(); + } + + [Fact] + public void Should_generate_factory_for_provided_properties() + { + var entity = new TestEntity { Column2 = "value" }; + var factory = SUT.Create(ActDbContext, new[] { entity }, new[] { _column2Property }, false); + + factory.FieldCount.Should().Be(1); + factory.Read().Should().BeTrue(); + factory.GetValue(0).Should().Be("value"); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderTests/GetFinishedEntities.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderTests/GetFinishedEntities.cs index 1e3f56d5..5b7904df 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderTests/GetFinishedEntities.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderTests/GetFinishedEntities.cs @@ -1,64 +1,64 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.Data.EntityDataReaderTests; - -public class GetFinishedEntities : IntegrationTestsBase -{ - private readonly List _propertiesToRead = new(); - - public GetFinishedEntities(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - _propertiesToRead.Add(new PropertyWithNavigations(ArrangeDbContext.GetEntityType().GetProperty(nameof(TestEntity.Column1)), Array.Empty())); - } - - private EntityDataReader CreateReader(IEnumerable entities, bool ensureReadEntitiesCollection) - where T : class - { - return new(ActDbContext, new PropertyGetterCache(LoggerFactory), entities, _propertiesToRead, ensureReadEntitiesCollection); - } - - [Fact] - public void Should_throw_if_ensureEntitiesCollectionAfterFinish_is_false() - { - CreateReader(Array.Empty(), false) - .Invoking(sut => sut.GetReadEntities()) - .Should().Throw() - .WithMessage("'Read entities' were not requested previously."); - } - - [Fact] - public void Should_return_the_same_collection_if_entities_is_list() - { - var entities = new List { new() }; - - CreateReader(entities, true).GetReadEntities() - .Should().BeSameAs(entities); - } - - [Fact] - public void Should_return_empty_collection_if_reader_is_not_read_to_end() - { - var entities = new HashSet { new() }; - - CreateReader(entities, true).GetReadEntities() - .Should().HaveCount(0); - } - - [Fact] - public void Should_return_the_new_collection_if_entities_is_not_list() - { - var entities = new HashSet { new() }; - - var reader = CreateReader(entities, true); - - while (reader.Read()) - { - } - - reader.GetReadEntities() - .Should().HaveCount(1) - .And.BeEquivalentTo(entities); - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.Data.EntityDataReaderTests; + +public class GetFinishedEntities : IntegrationTestsBase +{ + private readonly List _propertiesToRead = new(); + + public GetFinishedEntities(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + _propertiesToRead.Add(new PropertyWithNavigations(ArrangeDbContext.GetEntityType().GetProperty(nameof(TestEntity.Column1)), Array.Empty())); + } + + private EntityDataReader CreateReader(IEnumerable entities, bool ensureReadEntitiesCollection) + where T : class + { + return new(ActDbContext, new PropertyGetterCache(LoggerFactory), entities, _propertiesToRead, ensureReadEntitiesCollection); + } + + [Fact] + public void Should_throw_if_ensureEntitiesCollectionAfterFinish_is_false() + { + CreateReader(Array.Empty(), false) + .Invoking(sut => sut.GetReadEntities()) + .Should().Throw() + .WithMessage("'Read entities' were not requested previously."); + } + + [Fact] + public void Should_return_the_same_collection_if_entities_is_list() + { + var entities = new List { new() }; + + CreateReader(entities, true).GetReadEntities() + .Should().BeSameAs(entities); + } + + [Fact] + public void Should_return_empty_collection_if_reader_is_not_read_to_end() + { + var entities = new HashSet { new() }; + + CreateReader(entities, true).GetReadEntities() + .Should().HaveCount(0); + } + + [Fact] + public void Should_return_the_new_collection_if_entities_is_not_list() + { + var entities = new HashSet { new() }; + + var reader = CreateReader(entities, true); + + while (reader.Read()) + { + } + + reader.GetReadEntities() + .Should().HaveCount(1) + .And.BeEquivalentTo(entities); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderTests/GetPropertyIndex.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderTests/GetPropertyIndex.cs index 71f72f3c..688b6126 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderTests/GetPropertyIndex.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderTests/GetPropertyIndex.cs @@ -1,48 +1,48 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.Data.EntityDataReaderTests; - -public class GetPropertyIndex : IntegrationTestsBase -{ - private readonly List _propertiesToRead = new(); - private readonly PropertyWithNavigations _column1; - private readonly PropertyWithNavigations _column2; - - private EntityDataReader? _sut; - - // ReSharper disable once InconsistentNaming - private EntityDataReader SUT => _sut ??= new EntityDataReader(ActDbContext, new PropertyGetterCache(LoggerFactory), Array.Empty(), _propertiesToRead, false); - - public GetPropertyIndex(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - _column1 = new PropertyWithNavigations(ArrangeDbContext.GetEntityType().GetProperty(nameof(TestEntity.Column1)), Array.Empty()); - _column2 = new PropertyWithNavigations(ArrangeDbContext.GetEntityType().GetProperty(nameof(TestEntity.Column2)), Array.Empty()); - } - - [Fact] - public void Should_throw_if_property_is_in_propertiesToRead() - { - _propertiesToRead.Add(_column1); - - SUT.Invoking(sut => sut.GetPropertyIndex(_column2)) - .Should().Throw(); - } - - [Fact] - public void Should_return_index_of_the_property() - { - _propertiesToRead.Add(_column1); - _propertiesToRead.Add(_column2); - - SUT.GetPropertyIndex(_column2).Should().Be(1); - } - - protected override void Dispose(bool disposing) - { - _sut?.Dispose(); - - base.Dispose(disposing); - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.Data.EntityDataReaderTests; + +public class GetPropertyIndex : IntegrationTestsBase +{ + private readonly List _propertiesToRead = new(); + private readonly PropertyWithNavigations _column1; + private readonly PropertyWithNavigations _column2; + + private EntityDataReader? _sut; + + // ReSharper disable once InconsistentNaming + private EntityDataReader SUT => _sut ??= new EntityDataReader(ActDbContext, new PropertyGetterCache(LoggerFactory), Array.Empty(), _propertiesToRead, false); + + public GetPropertyIndex(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + _column1 = new PropertyWithNavigations(ArrangeDbContext.GetEntityType().GetProperty(nameof(TestEntity.Column1)), Array.Empty()); + _column2 = new PropertyWithNavigations(ArrangeDbContext.GetEntityType().GetProperty(nameof(TestEntity.Column2)), Array.Empty()); + } + + [Fact] + public void Should_throw_if_property_is_in_propertiesToRead() + { + _propertiesToRead.Add(_column1); + + SUT.Invoking(sut => sut.GetPropertyIndex(_column2)) + .Should().Throw(); + } + + [Fact] + public void Should_return_index_of_the_property() + { + _propertiesToRead.Add(_column1); + _propertiesToRead.Add(_column2); + + SUT.GetPropertyIndex(_column2).Should().Be(1); + } + + protected override void Dispose(bool disposing) + { + _sut?.Dispose(); + + base.Dispose(disposing); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderTests/Properties.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderTests/Properties.cs index 9f304941..9bf8d129 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderTests/Properties.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Data/EntityDataReaderTests/Properties.cs @@ -1,41 +1,41 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.Data.EntityDataReaderTests; - -public class Properties : IntegrationTestsBase -{ - private readonly List _propertiesToRead = new(); - private readonly PropertyWithNavigations _column1; - private readonly PropertyWithNavigations _column2; - - private EntityDataReader? _sut; - - // ReSharper disable once InconsistentNaming - private EntityDataReader SUT => _sut ??= new EntityDataReader(ActDbContext, new PropertyGetterCache(LoggerFactory), Array.Empty(), _propertiesToRead, false); - - public Properties(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - _column1 = new PropertyWithNavigations(ArrangeDbContext.GetEntityType().GetProperty(nameof(TestEntity.Column1)), Array.Empty()); - _column2 = new PropertyWithNavigations(ArrangeDbContext.GetEntityType().GetProperty(nameof(TestEntity.Column2)), Array.Empty()); - } - - [Fact] - public void Should_return_propertiesToRead() - { - _propertiesToRead.Add(_column1); - _propertiesToRead.Add(_column2); - - SUT.Properties.Should().HaveCount(2); - SUT.Properties.Should().Contain(_column1); - SUT.Properties.Should().Contain(_column2); - } - - protected override void Dispose(bool disposing) - { - _sut?.Dispose(); - - base.Dispose(disposing); - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.Data.EntityDataReaderTests; + +public class Properties : IntegrationTestsBase +{ + private readonly List _propertiesToRead = new(); + private readonly PropertyWithNavigations _column1; + private readonly PropertyWithNavigations _column2; + + private EntityDataReader? _sut; + + // ReSharper disable once InconsistentNaming + private EntityDataReader SUT => _sut ??= new EntityDataReader(ActDbContext, new PropertyGetterCache(LoggerFactory), Array.Empty(), _propertiesToRead, false); + + public Properties(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + _column1 = new PropertyWithNavigations(ArrangeDbContext.GetEntityType().GetProperty(nameof(TestEntity.Column1)), Array.Empty()); + _column2 = new PropertyWithNavigations(ArrangeDbContext.GetEntityType().GetProperty(nameof(TestEntity.Column2)), Array.Empty()); + } + + [Fact] + public void Should_return_propertiesToRead() + { + _propertiesToRead.Add(_column1); + _propertiesToRead.Add(_column2); + + SUT.Properties.Should().HaveCount(2); + SUT.Properties.Should().Contain(_column1); + SUT.Properties.Should().Contain(_column2); + } + + protected override void Dispose(bool disposing) + { + _sut?.Dispose(); + + base.Dispose(disposing); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Infrastructure/DefaultSchemaModelCustomizerTests/Customize.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Infrastructure/DefaultSchemaModelCustomizerTests/Customize.cs index 5e02333b..9f647bd1 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Infrastructure/DefaultSchemaModelCustomizerTests/Customize.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Infrastructure/DefaultSchemaModelCustomizerTests/Customize.cs @@ -1,81 +1,81 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.Extensions.DependencyInjection; -using Thinktecture.EntityFrameworkCore.Testing; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.Infrastructure.DefaultSchemaModelCustomizerTests; - -public class Customize : IntegrationTestsBase -{ - private string? _schema; - - public Customize(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - - protected override void ConfigureTestDbContextProvider(SqliteTestDbContextProviderBuilder builder) - { - base.ConfigureTestDbContextProvider(builder); - - builder.ConfigureOptions(optionsBuilder => - { - optionsBuilder.AddOrUpdateExtension(extension => - { - extension.Register(typeof(ModelCustomizer), typeof(ModelCustomizer), ServiceLifetime.Singleton); - return extension; - }); - optionsBuilder.ReplaceService>(); - }); - - if (_schema is not null) - { - builder.UseContextFactory(options => new DbContextWithSchema(options, _schema)) - .DisableModelCache(); - } - } - - [Fact] - public void Should_set_schema_if_entity_schema_is_null() - { - _schema = "BA3C32B0-D7EC-422C-AE3B-3206E7D67735"; - - ActDbContext.Model.FindEntityType(typeof(TestEntity))!.GetSchema().Should().Be(_schema); - } - - [Fact] - public void Should_set_schema_if_view_schema_is_null() - { - _schema = "E2FBA720-E24C-46C9-B326-46C3C91707F5"; - - ActDbContext.Model.FindEntityType(typeof(TestQuery))!.GetViewSchema().Should().Be(_schema); - } - - [Fact] - public void Should_set_schema_if_dbFunction_schema_is_null() - { - _schema = "E2FBA720-E24C-46C9-B326-46C3C91707F5"; - - ActDbContext.Model.GetDbFunctions() - .Single(f => f.MethodInfo!.Name == nameof(DbContextWithSchema.TestDbFunction)) - .Schema.Should().Be(_schema); - } - - [Fact] - public void Should_not_change_schema_if_entity_schema_is_set_already() - { - _schema = "4BA05B95-7FEA-4F32-A1E0-ACA9CB486EB9"; - ConfigureModel = builder => builder.Entity().ToTable("Table", "Schema"); - - ActDbContext.Model.FindEntityType(typeof(TestEntity))!.GetSchema().Should().Be("Schema"); - } - - [Fact] - public void Should_not_reset_schema_if_entity_schema_is_set_already() - { - _schema = null; - ConfigureModel = builder => builder.Entity().ToTable("Table", "Schema"); - - ActDbContext.Model.FindEntityType(typeof(TestEntity))!.GetSchema().Should().Be("Schema"); - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.Extensions.DependencyInjection; +using Thinktecture.EntityFrameworkCore.Testing; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.Infrastructure.DefaultSchemaModelCustomizerTests; + +public class Customize : IntegrationTestsBase +{ + private string? _schema; + + public Customize(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + } + + protected override void ConfigureTestDbContextProvider(SqliteTestDbContextProviderBuilder builder) + { + base.ConfigureTestDbContextProvider(builder); + + builder.ConfigureOptions(optionsBuilder => + { + optionsBuilder.AddOrUpdateExtension(extension => + { + extension.Register(typeof(ModelCustomizer), typeof(ModelCustomizer), ServiceLifetime.Singleton); + return extension; + }); + optionsBuilder.ReplaceService>(); + }); + + if (_schema is not null) + { + builder.UseContextFactory(options => new DbContextWithSchema(options, _schema)) + .DisableModelCache(); + } + } + + [Fact] + public void Should_set_schema_if_entity_schema_is_null() + { + _schema = "BA3C32B0-D7EC-422C-AE3B-3206E7D67735"; + + ActDbContext.Model.FindEntityType(typeof(TestEntity))!.GetSchema().Should().Be(_schema); + } + + [Fact] + public void Should_set_schema_if_view_schema_is_null() + { + _schema = "E2FBA720-E24C-46C9-B326-46C3C91707F5"; + + ActDbContext.Model.FindEntityType(typeof(TestQuery))!.GetViewSchema().Should().Be(_schema); + } + + [Fact] + public void Should_set_schema_if_dbFunction_schema_is_null() + { + _schema = "E2FBA720-E24C-46C9-B326-46C3C91707F5"; + + ActDbContext.Model.GetDbFunctions() + .Single(f => f.MethodInfo!.Name == nameof(DbContextWithSchema.TestDbFunction)) + .Schema.Should().Be(_schema); + } + + [Fact] + public void Should_not_change_schema_if_entity_schema_is_set_already() + { + _schema = "4BA05B95-7FEA-4F32-A1E0-ACA9CB486EB9"; + ConfigureModel = builder => builder.Entity().ToTable("Table", "Schema"); + + ActDbContext.Model.FindEntityType(typeof(TestEntity))!.GetSchema().Should().Be("Schema"); + } + + [Fact] + public void Should_not_reset_schema_if_entity_schema_is_set_already() + { + _schema = null; + ConfigureModel = builder => builder.Entity().ToTable("Table", "Schema"); + + ActDbContext.Model.FindEntityType(typeof(TestEntity))!.GetSchema().Should().Be("Schema"); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Infrastructure/DefaultSchemaRespectingModelCacheKeyFactoryTests/Create.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Infrastructure/DefaultSchemaRespectingModelCacheKeyFactoryTests/Create.cs index 01507d3b..e648995c 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Infrastructure/DefaultSchemaRespectingModelCacheKeyFactoryTests/Create.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Infrastructure/DefaultSchemaRespectingModelCacheKeyFactoryTests/Create.cs @@ -1,62 +1,62 @@ -namespace Thinktecture.EntityFrameworkCore.Infrastructure.DefaultSchemaRespectingModelCacheKeyFactoryTests; - -public class Create : IntegrationTestsBase -{ - private DefaultSchemaRespectingModelCacheKeyFactory? _sut; - - // ReSharper disable once InconsistentNaming - private DefaultSchemaRespectingModelCacheKeyFactory SUT => _sut ??= new DefaultSchemaRespectingModelCacheKeyFactory(new TestModelCacheKeyFactory()); - - public Create(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - - [Fact] - public void Should_throw_if_ctx_is_null() - { - // ReSharper disable once AssignNullToNotNullAttribute - SUT.Invoking(sut => sut.Create(null!)) - .Should().Throw(); - } - - [Fact] - public void Should_create_class_with_correct_equals_implementation_for_schema_aware_ctx() - { - var cacheKey1 = SUT.Create(CreateContextWithSchema("Schema1")); - var anotherCacheKey1 = SUT.Create(CreateContextWithSchema("Schema1")); - var cacheKey2 = SUT.Create(CreateContextWithSchema("Schema2")); - - cacheKey1.Equals(anotherCacheKey1).Should().BeTrue(); - cacheKey1.Equals(cacheKey2).Should().BeFalse(); - } - - [Fact] - public void Should_create_class_with_correct_gethashcode_implementation_for_schema_aware_ctx() - { - var cacheKey1 = SUT.Create(CreateContextWithSchema("Schema1")); - var anotherCacheKey1 = SUT.Create(CreateContextWithSchema("Schema1")); - var cacheKey2 = SUT.Create(CreateContextWithSchema("Schema2")); - - cacheKey1.GetHashCode().Should().Be(anotherCacheKey1.GetHashCode()); - cacheKey1.GetHashCode().Should().NotBe(cacheKey2.GetHashCode()); - } - - [Fact] - public void Should_create_class_with_correct_equals_implementation_for_schema_unaware_ctx() - { - var cacheKey1 = SUT.Create(CreateContextWithoutSchema()); - var cacheKey2 = SUT.Create(CreateContextWithoutSchema()); - - cacheKey1.Equals(cacheKey2).Should().BeTrue(); - } - - [Fact] - public void Should_create_class_with_correct_gethashcode_implementation_for_schema_unaware_ctx() - { - var cacheKey1 = SUT.Create(CreateContextWithoutSchema()); - var cacheKey2 = SUT.Create(CreateContextWithoutSchema()); - - cacheKey1.GetHashCode().Should().Be(cacheKey2.GetHashCode()); - } -} +namespace Thinktecture.EntityFrameworkCore.Infrastructure.DefaultSchemaRespectingModelCacheKeyFactoryTests; + +public class Create : IntegrationTestsBase +{ + private DefaultSchemaRespectingModelCacheKeyFactory? _sut; + + // ReSharper disable once InconsistentNaming + private DefaultSchemaRespectingModelCacheKeyFactory SUT => _sut ??= new DefaultSchemaRespectingModelCacheKeyFactory(new TestModelCacheKeyFactory()); + + public Create(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + } + + [Fact] + public void Should_throw_if_ctx_is_null() + { + // ReSharper disable once AssignNullToNotNullAttribute + SUT.Invoking(sut => sut.Create(null!)) + .Should().Throw(); + } + + [Fact] + public void Should_create_class_with_correct_equals_implementation_for_schema_aware_ctx() + { + var cacheKey1 = SUT.Create(CreateContextWithSchema("Schema1")); + var anotherCacheKey1 = SUT.Create(CreateContextWithSchema("Schema1")); + var cacheKey2 = SUT.Create(CreateContextWithSchema("Schema2")); + + cacheKey1.Equals(anotherCacheKey1).Should().BeTrue(); + cacheKey1.Equals(cacheKey2).Should().BeFalse(); + } + + [Fact] + public void Should_create_class_with_correct_gethashcode_implementation_for_schema_aware_ctx() + { + var cacheKey1 = SUT.Create(CreateContextWithSchema("Schema1")); + var anotherCacheKey1 = SUT.Create(CreateContextWithSchema("Schema1")); + var cacheKey2 = SUT.Create(CreateContextWithSchema("Schema2")); + + cacheKey1.GetHashCode().Should().Be(anotherCacheKey1.GetHashCode()); + cacheKey1.GetHashCode().Should().NotBe(cacheKey2.GetHashCode()); + } + + [Fact] + public void Should_create_class_with_correct_equals_implementation_for_schema_unaware_ctx() + { + var cacheKey1 = SUT.Create(CreateContextWithoutSchema()); + var cacheKey2 = SUT.Create(CreateContextWithoutSchema()); + + cacheKey1.Equals(cacheKey2).Should().BeTrue(); + } + + [Fact] + public void Should_create_class_with_correct_gethashcode_implementation_for_schema_unaware_ctx() + { + var cacheKey1 = SUT.Create(CreateContextWithoutSchema()); + var cacheKey2 = SUT.Create(CreateContextWithoutSchema()); + + cacheKey1.GetHashCode().Should().Be(cacheKey2.GetHashCode()); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Infrastructure/DefaultSchemaRespectingModelCacheKeyFactoryTests/TestModelCacheKeyFactory.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Infrastructure/DefaultSchemaRespectingModelCacheKeyFactoryTests/TestModelCacheKeyFactory.cs index 3ab3f557..182b9149 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Infrastructure/DefaultSchemaRespectingModelCacheKeyFactoryTests/TestModelCacheKeyFactory.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Infrastructure/DefaultSchemaRespectingModelCacheKeyFactoryTests/TestModelCacheKeyFactory.cs @@ -1,24 +1,24 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; - -namespace Thinktecture.EntityFrameworkCore.Infrastructure.DefaultSchemaRespectingModelCacheKeyFactoryTests; - -public class TestModelCacheKeyFactory : IModelCacheKeyFactory -{ - public IModelCacheKeyFactory Mock { get; } - - public TestModelCacheKeyFactory() - { - Mock = Substitute.For(); - Mock.Create(Arg.Any(), Arg.Any()).Returns(x => new ModelCacheKey((DbContext)x[0], (bool)x[1])); - } - - public object Create(DbContext context) - { - return Create(context, false); - } - - public object Create(DbContext context, bool designTime) - { - return Mock.Create(context, designTime); - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; + +namespace Thinktecture.EntityFrameworkCore.Infrastructure.DefaultSchemaRespectingModelCacheKeyFactoryTests; + +public class TestModelCacheKeyFactory : IModelCacheKeyFactory +{ + public IModelCacheKeyFactory Mock { get; } + + public TestModelCacheKeyFactory() + { + Mock = Substitute.For(); + Mock.Create(Arg.Any(), Arg.Any()).Returns(x => new ModelCacheKey((DbContext)x[0], (bool)x[1])); + } + + public object Create(DbContext context) + { + return Create(context, false); + } + + public object Create(DbContext context, bool designTime) + { + return Mock.Create(context, designTime); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssemblyTests/CreateMigration.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssemblyTests/CreateMigration.cs index 4fb0b1a6..cba15798 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssemblyTests/CreateMigration.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssemblyTests/CreateMigration.cs @@ -1,126 +1,126 @@ -using System.Reflection; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Migrations.Operations; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.Migrations.DefaultSchemaRespectingMigrationAssemblyTests; - -public class CreateMigration : DefaultSchemaRespectingMigrationAssemblyTestsBase -{ - public CreateMigration(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - - [Fact] - public void Should_throw_when_schema_type_is_null() - { - CurrentCtxMock.Context.Returns(CreateContextWithSchema("Schema1")); - - // ReSharper disable once AssignNullToNotNullAttribute - SUT.Invoking(sut => sut.CreateMigration(null!, "DummyProvider")) - .Should().Throw(); - } - - [Fact] - public void Should_throw_when_active_provider_is_null() - { - CurrentCtxMock.Context.Returns(CreateContextWithSchema("Schema1")); - - // ReSharper disable once AssignNullToNotNullAttribute - SUT.Invoking(sut => sut.CreateMigration(typeof(MigrationWithSchema).GetTypeInfo(), null!)) - .Should().Throw(); - } - - [Fact] - public void Should_create_schema_aware_migration_having_schema_aware_ctx() - { - CurrentCtxMock.Context.Returns(CreateContextWithSchema("Schema1")); - - var migration = SUT.CreateMigration(typeof(MigrationWithSchema).GetTypeInfo(), "DummyProvider"); - - migration.Should().NotBeNull(); - migration.Should().BeOfType(); - } - - [Fact] - public void Should_set_schema_of_schema_aware_migration_having_schema_aware_ctx() - { - CurrentCtxMock.Context.Returns(CreateContextWithSchema("Schema1")); - SchemaSetterMock.When(s => s.SetSchema(Arg.Any>(), Arg.Any())) - .Do(x => new MigrationOperationSchemaSetter().SetSchema((IReadOnlyList)x[0], (string)x[1])); - - var migration = SUT.CreateMigration(typeof(MigrationWithSchema).GetTypeInfo(), "DummyProvider"); - - VerifySchema(migration, "Schema1"); - } - - [Fact] - public void Should_create_migration_having_schema_aware_ctx() - { - CurrentCtxMock.Context.Returns(CreateContextWithSchema("Schema1")); - var migration = new MigrationWithoutSchema { ActiveProvider = "DummyProvider" }; - - var createMigration = SUT.CreateMigration(typeof(MigrationWithoutSchema).GetTypeInfo(), "DummyProvider"); - - migration.Should().NotBeNull(); - migration.Should().BeOfType(); - - createMigration.Should().BeEquivalentTo(migration, options => options.Excluding(m => m.TargetModel.ModelId) - .Excluding(m => m.TargetModel.ModelDependencies)); - } - - [Fact] - public void Should_set_schema_of_migration_having_schema_aware_ctx() - { - CurrentCtxMock.Context.Returns(CreateContextWithSchema("Schema1")); - SchemaSetterMock.When(s => s.SetSchema(Arg.Any>(), Arg.Any())) - .Do(x => new MigrationOperationSchemaSetter().SetSchema((IReadOnlyList)x[0], (string)x[1])); - - var migration = SUT.CreateMigration(typeof(MigrationWithoutSchema).GetTypeInfo(), "DummyProvider"); - - VerifySchema(migration, "Schema1"); - } - - [Fact] - public void Should_throw_when_creating_schema_aware_migration_having_schema_unaware_ctx() - { - CurrentCtxMock.Context.Returns(CreateContextWithoutSchema()); - - SUT.Invoking(sut => sut.CreateMigration(typeof(MigrationWithSchema).GetTypeInfo(), "DummyProvider")) - .Should().Throw().WithMessage($"For instantiation of default schema respecting migration of type '{nameof(MigrationWithSchema)}' the database context of type '{nameof(DbContextWithoutSchema)}' has to implement the interface '{nameof(IDbDefaultSchema)}'. (Parameter 'migrationClass')"); - } - - [Fact] - public void Should_create_schema_unaware_migration_having_schema_unaware_ctx() - { - CurrentCtxMock.Context.Returns(CreateContextWithoutSchema()); - var migration = new MigrationWithoutSchema { ActiveProvider = "DummyProvider" }; - - var createMigration = SUT.CreateMigration(typeof(MigrationWithoutSchema).GetTypeInfo(), "DummyProvider"); - - createMigration.Should().BeEquivalentTo(migration, options => options.Excluding(m => m.TargetModel.ModelId) - .Excluding(m => m.TargetModel.ModelDependencies)); - } - - [Fact] - public void Should_not_set_schema_on_schema_unaware_migration_having_schema_unaware_ctx() - { - CurrentCtxMock.Context.Returns(CreateContextWithoutSchema()); - - var migration = SUT.CreateMigration(typeof(MigrationWithoutSchema).GetTypeInfo(), "DummyProvider"); - - SchemaSetterMock.DidNotReceive().SetSchema(Arg.Any>(), Arg.Any()); - migration.UpOperations[0].Should().BeOfType().Subject.Schema.Should().BeNull(); - migration.DownOperations[0].Should().BeOfType().Subject.Schema.Should().BeNull(); - } - - private void VerifySchema(Migration migration, string schema) - { - SchemaSetterMock.Received(1).SetSchema(migration.UpOperations, schema); - SchemaSetterMock.Received(1).SetSchema(migration.DownOperations, schema); - - migration.UpOperations[0].Should().BeOfType().Subject.Schema.Should().Be(schema); - migration.DownOperations[0].Should().BeOfType().Subject.Schema.Should().Be(schema); - } -} +using System.Reflection; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.Migrations.DefaultSchemaRespectingMigrationAssemblyTests; + +public class CreateMigration : DefaultSchemaRespectingMigrationAssemblyTestsBase +{ + public CreateMigration(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + } + + [Fact] + public void Should_throw_when_schema_type_is_null() + { + CurrentCtxMock.Context.Returns(CreateContextWithSchema("Schema1")); + + // ReSharper disable once AssignNullToNotNullAttribute + SUT.Invoking(sut => sut.CreateMigration(null!, "DummyProvider")) + .Should().Throw(); + } + + [Fact] + public void Should_throw_when_active_provider_is_null() + { + CurrentCtxMock.Context.Returns(CreateContextWithSchema("Schema1")); + + // ReSharper disable once AssignNullToNotNullAttribute + SUT.Invoking(sut => sut.CreateMigration(typeof(MigrationWithSchema).GetTypeInfo(), null!)) + .Should().Throw(); + } + + [Fact] + public void Should_create_schema_aware_migration_having_schema_aware_ctx() + { + CurrentCtxMock.Context.Returns(CreateContextWithSchema("Schema1")); + + var migration = SUT.CreateMigration(typeof(MigrationWithSchema).GetTypeInfo(), "DummyProvider"); + + migration.Should().NotBeNull(); + migration.Should().BeOfType(); + } + + [Fact] + public void Should_set_schema_of_schema_aware_migration_having_schema_aware_ctx() + { + CurrentCtxMock.Context.Returns(CreateContextWithSchema("Schema1")); + SchemaSetterMock.When(s => s.SetSchema(Arg.Any>(), Arg.Any())) + .Do(x => new MigrationOperationSchemaSetter().SetSchema((IReadOnlyList)x[0], (string)x[1])); + + var migration = SUT.CreateMigration(typeof(MigrationWithSchema).GetTypeInfo(), "DummyProvider"); + + VerifySchema(migration, "Schema1"); + } + + [Fact] + public void Should_create_migration_having_schema_aware_ctx() + { + CurrentCtxMock.Context.Returns(CreateContextWithSchema("Schema1")); + var migration = new MigrationWithoutSchema { ActiveProvider = "DummyProvider" }; + + var createMigration = SUT.CreateMigration(typeof(MigrationWithoutSchema).GetTypeInfo(), "DummyProvider"); + + migration.Should().NotBeNull(); + migration.Should().BeOfType(); + + createMigration.Should().BeEquivalentTo(migration, options => options.Excluding(m => m.TargetModel.ModelId) + .Excluding(m => m.TargetModel.ModelDependencies)); + } + + [Fact] + public void Should_set_schema_of_migration_having_schema_aware_ctx() + { + CurrentCtxMock.Context.Returns(CreateContextWithSchema("Schema1")); + SchemaSetterMock.When(s => s.SetSchema(Arg.Any>(), Arg.Any())) + .Do(x => new MigrationOperationSchemaSetter().SetSchema((IReadOnlyList)x[0], (string)x[1])); + + var migration = SUT.CreateMigration(typeof(MigrationWithoutSchema).GetTypeInfo(), "DummyProvider"); + + VerifySchema(migration, "Schema1"); + } + + [Fact] + public void Should_throw_when_creating_schema_aware_migration_having_schema_unaware_ctx() + { + CurrentCtxMock.Context.Returns(CreateContextWithoutSchema()); + + SUT.Invoking(sut => sut.CreateMigration(typeof(MigrationWithSchema).GetTypeInfo(), "DummyProvider")) + .Should().Throw().WithMessage($"For instantiation of default schema respecting migration of type '{nameof(MigrationWithSchema)}' the database context of type '{nameof(DbContextWithoutSchema)}' has to implement the interface '{nameof(IDbDefaultSchema)}'. (Parameter 'migrationClass')"); + } + + [Fact] + public void Should_create_schema_unaware_migration_having_schema_unaware_ctx() + { + CurrentCtxMock.Context.Returns(CreateContextWithoutSchema()); + var migration = new MigrationWithoutSchema { ActiveProvider = "DummyProvider" }; + + var createMigration = SUT.CreateMigration(typeof(MigrationWithoutSchema).GetTypeInfo(), "DummyProvider"); + + createMigration.Should().BeEquivalentTo(migration, options => options.Excluding(m => m.TargetModel.ModelId) + .Excluding(m => m.TargetModel.ModelDependencies)); + } + + [Fact] + public void Should_not_set_schema_on_schema_unaware_migration_having_schema_unaware_ctx() + { + CurrentCtxMock.Context.Returns(CreateContextWithoutSchema()); + + var migration = SUT.CreateMigration(typeof(MigrationWithoutSchema).GetTypeInfo(), "DummyProvider"); + + SchemaSetterMock.DidNotReceive().SetSchema(Arg.Any>(), Arg.Any()); + migration.UpOperations[0].Should().BeOfType().Subject.Schema.Should().BeNull(); + migration.DownOperations[0].Should().BeOfType().Subject.Schema.Should().BeNull(); + } + + private void VerifySchema(Migration migration, string schema) + { + SchemaSetterMock.Received(1).SetSchema(migration.UpOperations, schema); + SchemaSetterMock.Received(1).SetSchema(migration.DownOperations, schema); + + migration.UpOperations[0].Should().BeOfType().Subject.Schema.Should().Be(schema); + migration.DownOperations[0].Should().BeOfType().Subject.Schema.Should().Be(schema); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssemblyTests/DefaultSchemaRespectingMigrationAssemblyTestsBase.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssemblyTests/DefaultSchemaRespectingMigrationAssemblyTestsBase.cs index 94477298..8df04a3d 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssemblyTests/DefaultSchemaRespectingMigrationAssemblyTestsBase.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssemblyTests/DefaultSchemaRespectingMigrationAssemblyTestsBase.cs @@ -1,25 +1,25 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.Extensions.DependencyInjection; - -namespace Thinktecture.EntityFrameworkCore.Migrations.DefaultSchemaRespectingMigrationAssemblyTests; - -public abstract class DefaultSchemaRespectingMigrationAssemblyTestsBase : IntegrationTestsBase -{ - private TestMigrationsAssembly InnerMigrationsAssembly { get; } - protected ICurrentDbContext CurrentCtxMock { get; } - protected IMigrationOperationSchemaSetter SchemaSetterMock { get; } - protected IServiceCollection Services { get; } - - private DefaultSchemaRespectingMigrationAssembly? _sut; - - protected DefaultSchemaRespectingMigrationAssembly SUT => _sut ??= new DefaultSchemaRespectingMigrationAssembly(InnerMigrationsAssembly, SchemaSetterMock, CurrentCtxMock, Services.BuildServiceProvider()); - - protected DefaultSchemaRespectingMigrationAssemblyTestsBase(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - InnerMigrationsAssembly = new TestMigrationsAssembly(); - CurrentCtxMock = Substitute.For(); - SchemaSetterMock = Substitute.For(); - Services = new ServiceCollection(); - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.Extensions.DependencyInjection; + +namespace Thinktecture.EntityFrameworkCore.Migrations.DefaultSchemaRespectingMigrationAssemblyTests; + +public abstract class DefaultSchemaRespectingMigrationAssemblyTestsBase : IntegrationTestsBase +{ + private TestMigrationsAssembly InnerMigrationsAssembly { get; } + protected ICurrentDbContext CurrentCtxMock { get; } + protected IMigrationOperationSchemaSetter SchemaSetterMock { get; } + protected IServiceCollection Services { get; } + + private DefaultSchemaRespectingMigrationAssembly? _sut; + + protected DefaultSchemaRespectingMigrationAssembly SUT => _sut ??= new DefaultSchemaRespectingMigrationAssembly(InnerMigrationsAssembly, SchemaSetterMock, CurrentCtxMock, Services.BuildServiceProvider()); + + protected DefaultSchemaRespectingMigrationAssemblyTestsBase(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + InnerMigrationsAssembly = new TestMigrationsAssembly(); + CurrentCtxMock = Substitute.For(); + SchemaSetterMock = Substitute.For(); + Services = new ServiceCollection(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssemblyTests/TestMigrationsAssembly.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssemblyTests/TestMigrationsAssembly.cs index 2d678c1a..0635a87c 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssemblyTests/TestMigrationsAssembly.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/DefaultSchemaRespectingMigrationAssemblyTests/TestMigrationsAssembly.cs @@ -1,31 +1,31 @@ -using System.Reflection; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.EntityFrameworkCore.Migrations.DefaultSchemaRespectingMigrationAssemblyTests; - -public class TestMigrationsAssembly : IMigrationsAssembly -{ - public IMigrationsAssembly Mock { get; } = Substitute.For(); - - /// - public string? FindMigrationId(string nameOrId) - { - return Mock.FindMigrationId(nameOrId); - } - - /// - public Migration CreateMigration(TypeInfo migrationClass, string activeProvider) - { - return Mock.CreateMigration(migrationClass, activeProvider); - } - - /// - public IReadOnlyDictionary Migrations => Mock.Migrations; - - /// - public ModelSnapshot? ModelSnapshot => Mock.ModelSnapshot; - - /// - public Assembly Assembly => Mock.Assembly; -} +using System.Reflection; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.EntityFrameworkCore.Migrations.DefaultSchemaRespectingMigrationAssemblyTests; + +public class TestMigrationsAssembly : IMigrationsAssembly +{ + public IMigrationsAssembly Mock { get; } = Substitute.For(); + + /// + public string? FindMigrationId(string nameOrId) + { + return Mock.FindMigrationId(nameOrId); + } + + /// + public Migration CreateMigration(TypeInfo migrationClass, string activeProvider) + { + return Mock.CreateMigration(migrationClass, activeProvider); + } + + /// + public IReadOnlyDictionary Migrations => Mock.Migrations; + + /// + public ModelSnapshot? ModelSnapshot => Mock.ModelSnapshot; + + /// + public Assembly Assembly => Mock.Assembly; +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/MigrationOperationSchemaSetterTests/SetSchema.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/MigrationOperationSchemaSetterTests/SetSchema.cs index 069c6a64..1f72b509 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/MigrationOperationSchemaSetterTests/SetSchema.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Migrations/MigrationOperationSchemaSetterTests/SetSchema.cs @@ -1,245 +1,245 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Migrations.Operations; - -namespace Thinktecture.EntityFrameworkCore.Migrations.MigrationOperationSchemaSetterTests; - -public class SetSchema -{ - private IDbDefaultSchema? _schema; - private List? _operations; - - private readonly MigrationBuilder _builder = new("provider"); - - private List Operations - { - get - { - if (_operations == null) - { - _operations = _builder.Operations; - - if (_schema?.Schema is not null) - new MigrationOperationSchemaSetter().SetSchema(_operations, _schema.Schema); - } - - return _operations; - } - } - - [Fact] - public void Should_return_operations_unchanged_if_no_schema_set() - { - _builder.AddColumn("Col1", "Table1"); - - Operations.Should().HaveCount(1); - Operations[0].Should().BeOfType() - .And.Subject.As().Schema.Should().BeNull(); - } - - [Fact] - public void Should_not_set_schema_if_the_schema_is_set_already() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.AddColumn("Col1", "Table1", schema: "CustomSchema"); - - Operations[0].As().Schema.Should().Be("CustomSchema"); - } - - [Fact] - public void Should_set_schema_on_AddColumnOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.AddColumn("Col1", "Table1"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_AlterColumnOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.AlterColumn("Col1", "Table1"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_RenameColumnOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.RenameColumn("Col1", "Table1", "Col1_New"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_DropColumnOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.DropColumn("Table1", "Col1"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_CreateTable() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.CreateTable("Table1", - table => new - { - Col1 = table.Column() - }, - constraints: table => - { - table.PrimaryKey("PK", t => t.Col1); - table.UniqueConstraint("UX", t => t.Col1); - table.ForeignKey("FK", t => t.Col1, "OtherTable", "OtherColumn"); - }); - - var op = Operations[0].As(); - op.Schema.Should().Be("Schema1"); - op.Columns[0].Schema.Should().Be("Schema1"); - op.PrimaryKey!.Schema.Should().Be("Schema1"); - op.UniqueConstraints[0].Schema.Should().Be("Schema1"); - op.ForeignKeys[0].Schema.Should().Be("Schema1"); - op.ForeignKeys[0].PrincipalSchema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_AlterTableOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.AlterTable("Table1"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_RenameTableOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.RenameTable("Table1", newName: "Table1_New"); - - Operations[0].As().Schema.Should().Be("Schema1"); - Operations[0].As().NewSchema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_DropTableOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.DropTable("Table1"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_AddPrimaryKeyOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.AddPrimaryKey("PK", "Table1", "Col1"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_DropPrimaryKeyOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.DropPrimaryKey("PK", "Table1"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_AddForeignKeyOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.AddForeignKey("FK", "Table1", "Col1", "OtherTable", principalColumn: "OtherCol"); - - Operations[0].As().Schema.Should().Be("Schema1"); - Operations[0].As().PrincipalSchema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_DropForeignKeyOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.DropForeignKey("FK", "Table1"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_AddUniqueConstraintOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.AddUniqueConstraint("UX", "Table1", "Col1"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_DropUniqueConstraintOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.DropUniqueConstraint("UX", "Table1"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_CreateIndexOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.CreateIndex("IX", "Table1", "Col1"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_RenameIndexOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.RenameIndex("IX", "IX_New", "Table"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_DropIndex() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.DropIndex("IX", "Table1"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_InsertDataOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.InsertData("Table1", "Col1", new { }); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_UpdateDataOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.UpdateData("Table1", "Col1", "Key1", "Col1", "Value1"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } - - [Fact] - public void Should_set_schema_on_DeleteDataOperation() - { - _schema = new DbDefaultSchema("Schema1"); - _builder.DeleteData("Table1", "Col1", "Key1"); - - Operations[0].As().Schema.Should().Be("Schema1"); - } -} +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations.Operations; + +namespace Thinktecture.EntityFrameworkCore.Migrations.MigrationOperationSchemaSetterTests; + +public class SetSchema +{ + private IDbDefaultSchema? _schema; + private List? _operations; + + private readonly MigrationBuilder _builder = new("provider"); + + private List Operations + { + get + { + if (_operations == null) + { + _operations = _builder.Operations; + + if (_schema?.Schema is not null) + new MigrationOperationSchemaSetter().SetSchema(_operations, _schema.Schema); + } + + return _operations; + } + } + + [Fact] + public void Should_return_operations_unchanged_if_no_schema_set() + { + _builder.AddColumn("Col1", "Table1"); + + Operations.Should().HaveCount(1); + Operations[0].Should().BeOfType() + .And.Subject.As().Schema.Should().BeNull(); + } + + [Fact] + public void Should_not_set_schema_if_the_schema_is_set_already() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.AddColumn("Col1", "Table1", schema: "CustomSchema"); + + Operations[0].As().Schema.Should().Be("CustomSchema"); + } + + [Fact] + public void Should_set_schema_on_AddColumnOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.AddColumn("Col1", "Table1"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_AlterColumnOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.AlterColumn("Col1", "Table1"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_RenameColumnOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.RenameColumn("Col1", "Table1", "Col1_New"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_DropColumnOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.DropColumn("Table1", "Col1"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_CreateTable() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.CreateTable("Table1", + table => new + { + Col1 = table.Column() + }, + constraints: table => + { + table.PrimaryKey("PK", t => t.Col1); + table.UniqueConstraint("UX", t => t.Col1); + table.ForeignKey("FK", t => t.Col1, "OtherTable", "OtherColumn"); + }); + + var op = Operations[0].As(); + op.Schema.Should().Be("Schema1"); + op.Columns[0].Schema.Should().Be("Schema1"); + op.PrimaryKey!.Schema.Should().Be("Schema1"); + op.UniqueConstraints[0].Schema.Should().Be("Schema1"); + op.ForeignKeys[0].Schema.Should().Be("Schema1"); + op.ForeignKeys[0].PrincipalSchema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_AlterTableOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.AlterTable("Table1"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_RenameTableOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.RenameTable("Table1", newName: "Table1_New"); + + Operations[0].As().Schema.Should().Be("Schema1"); + Operations[0].As().NewSchema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_DropTableOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.DropTable("Table1"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_AddPrimaryKeyOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.AddPrimaryKey("PK", "Table1", "Col1"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_DropPrimaryKeyOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.DropPrimaryKey("PK", "Table1"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_AddForeignKeyOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.AddForeignKey("FK", "Table1", "Col1", "OtherTable", principalColumn: "OtherCol"); + + Operations[0].As().Schema.Should().Be("Schema1"); + Operations[0].As().PrincipalSchema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_DropForeignKeyOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.DropForeignKey("FK", "Table1"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_AddUniqueConstraintOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.AddUniqueConstraint("UX", "Table1", "Col1"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_DropUniqueConstraintOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.DropUniqueConstraint("UX", "Table1"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_CreateIndexOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.CreateIndex("IX", "Table1", "Col1"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_RenameIndexOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.RenameIndex("IX", "IX_New", "Table"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_DropIndex() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.DropIndex("IX", "Table1"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_InsertDataOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.InsertData("Table1", "Col1", new { }); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_UpdateDataOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.UpdateData("Table1", "Col1", "Key1", "Col1", "Value1"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } + + [Fact] + public void Should_set_schema_on_DeleteDataOperation() + { + _schema = new DbDefaultSchema("Schema1"); + _builder.DeleteData("Table1", "Col1", "Key1"); + + Operations[0].As().Schema.Should().Be("Schema1"); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedDbContextTransactionTests/Commit.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedDbContextTransactionTests/Commit.cs index 5ee56790..ae2f3c40 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedDbContextTransactionTests/Commit.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedDbContextTransactionTests/Commit.cs @@ -1,183 +1,183 @@ -using System.Transactions; -using Microsoft.EntityFrameworkCore.Storage; -using Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.Storage.NestedDbContextTransactionTests; - -public class Commit : NestedRelationalTransactionManagerTestBase -{ - public Commit(ITestOutputHelper testOutputHelper) - : base(testOutputHelper, IMigrationExecutionStrategy.EnsureCreated) - { - } - - [Fact] - public void Should_throw_when_trying_to_commit_twice() - { - var rootTx = SUT.BeginTransaction(); - ActDbContext.Add(new TestEntity()); - ActDbContext.SaveChanges(); - - rootTx.Commit(); - - rootTx.Invoking(tx => tx.Commit()) - .Should().Throw().WithMessage("This root transaction has completed; it is no longer usable."); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - AssertDbContext.TestEntities.Should().HaveCount(1); - } - - [Fact] - public void Should_commit_root_transaction_and_underlying_transaction() - { - var rootTx = SUT.BeginTransaction(); - ActDbContext.Add(new TestEntity()); - ActDbContext.SaveChanges(); - - rootTx.Commit(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - AssertDbContext.TestEntities.Should().HaveCount(1); - } - - [Fact] - public void Should_commit_child_transaction_but_not_underlying_transaction() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - - childTx.Commit(); - - SUT.CurrentTransaction.Should().Be(rootTx); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); - } - - [Fact] - public void Should_commit_child_and_root_transaction() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - ActDbContext.Add(new TestEntity()); - ActDbContext.SaveChanges(); - - childTx.Commit(); - rootTx.Commit(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - AssertDbContext.TestEntities.Should().HaveCount(1); - } - - [Fact] - public void Should_commit_children_and_root_transactions() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - var anotherChildTx = SUT.BeginTransaction(); - ActDbContext.Add(new TestEntity()); - ActDbContext.SaveChanges(); - - anotherChildTx.Commit(); - childTx.Commit(); - rootTx.Commit(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - AssertDbContext.TestEntities.Should().HaveCount(1); - } - - [Fact] - public void Should_throw_when_committing_root_transaction_if_child_transaction_is_rolled_back() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - - childTx.Rollback(); - - rootTx.Invoking(tx => tx.Commit()) - .Should().Throw().WithMessage("The transaction has aborted."); - - SUT.CurrentTransaction.Should().Be(rootTx); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); - } - - [Fact] - public void Should_throw_when_committing_root_transaction_if_child_transaction_is_rolled_back_due_to_disposed() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - - childTx.Dispose(); - - rootTx.Invoking(tx => tx.Commit()) - .Should().Throw().WithMessage("The transaction has aborted."); - - SUT.CurrentTransaction.Should().Be(rootTx); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); - } - - [Fact] - public void Should_throw_when_committing_child_transaction_if_another_child_transaction_is_rolled_back() - { - // ReSharper disable once UnusedVariable - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - var anotherChildTx = SUT.BeginTransaction(); - - anotherChildTx.Rollback(); - - childTx.Invoking(tx => tx.Commit()) - .Should().Throw().WithMessage("The transaction has aborted."); - - SUT.CurrentTransaction.Should().Be(childTx); - IsTransactionUsable(childTx.GetDbTransaction()).Should().BeTrue(); - } - - [Fact] - public void Should_throw_when_committing_child_transaction_if_another_child_transaction_is_rolled_back_due_to_disposed() - { - // ReSharper disable once UnusedVariable - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - var anotherChildTx = SUT.BeginTransaction(); - - anotherChildTx.Dispose(); - - childTx.Invoking(tx => tx.Commit()) - .Should().Throw().WithMessage("The transaction has aborted."); - - SUT.CurrentTransaction.Should().Be(childTx); - IsTransactionUsable(childTx.GetDbTransaction()).Should().BeTrue(); - } - - [Fact] - public void Should_throw_when_committing_root_transaction_if_child_transaction_is_not_completed() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - - rootTx.Invoking(tx => tx.Commit()) - .Should().Throw().WithMessage("Transactions nested incorrectly. At least one of the child transactions is not completed."); - - SUT.CurrentTransaction.Should().Be(childTx); - IsTransactionUsable(childTx.GetDbTransaction()).Should().BeTrue(); - } - - [Fact] - public void Should_throw_when_committing_child_transaction_if_another_child_transaction_is_not_completed() - { - // ReSharper disable once UnusedVariable - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - var anotherChildTx = SUT.BeginTransaction(); - - childTx.Invoking(tx => tx.Commit()) - .Should().Throw().WithMessage("Transactions nested incorrectly. At least one of the child transactions is not completed."); - - SUT.CurrentTransaction.Should().Be(anotherChildTx); - IsTransactionUsable(anotherChildTx.GetDbTransaction()).Should().BeTrue(); - } -} +using System.Transactions; +using Microsoft.EntityFrameworkCore.Storage; +using Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.Storage.NestedDbContextTransactionTests; + +public class Commit : NestedRelationalTransactionManagerTestBase +{ + public Commit(ITestOutputHelper testOutputHelper) + : base(testOutputHelper, IMigrationExecutionStrategy.EnsureCreated) + { + } + + [Fact] + public void Should_throw_when_trying_to_commit_twice() + { + var rootTx = SUT.BeginTransaction(); + ActDbContext.Add(new TestEntity()); + ActDbContext.SaveChanges(); + + rootTx.Commit(); + + rootTx.Invoking(tx => tx.Commit()) + .Should().Throw().WithMessage("This root transaction has completed; it is no longer usable."); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + AssertDbContext.TestEntities.Should().HaveCount(1); + } + + [Fact] + public void Should_commit_root_transaction_and_underlying_transaction() + { + var rootTx = SUT.BeginTransaction(); + ActDbContext.Add(new TestEntity()); + ActDbContext.SaveChanges(); + + rootTx.Commit(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + AssertDbContext.TestEntities.Should().HaveCount(1); + } + + [Fact] + public void Should_commit_child_transaction_but_not_underlying_transaction() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + + childTx.Commit(); + + SUT.CurrentTransaction.Should().Be(rootTx); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); + } + + [Fact] + public void Should_commit_child_and_root_transaction() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + ActDbContext.Add(new TestEntity()); + ActDbContext.SaveChanges(); + + childTx.Commit(); + rootTx.Commit(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + AssertDbContext.TestEntities.Should().HaveCount(1); + } + + [Fact] + public void Should_commit_children_and_root_transactions() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + var anotherChildTx = SUT.BeginTransaction(); + ActDbContext.Add(new TestEntity()); + ActDbContext.SaveChanges(); + + anotherChildTx.Commit(); + childTx.Commit(); + rootTx.Commit(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + AssertDbContext.TestEntities.Should().HaveCount(1); + } + + [Fact] + public void Should_throw_when_committing_root_transaction_if_child_transaction_is_rolled_back() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + + childTx.Rollback(); + + rootTx.Invoking(tx => tx.Commit()) + .Should().Throw().WithMessage("The transaction has aborted."); + + SUT.CurrentTransaction.Should().Be(rootTx); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); + } + + [Fact] + public void Should_throw_when_committing_root_transaction_if_child_transaction_is_rolled_back_due_to_disposed() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + + childTx.Dispose(); + + rootTx.Invoking(tx => tx.Commit()) + .Should().Throw().WithMessage("The transaction has aborted."); + + SUT.CurrentTransaction.Should().Be(rootTx); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); + } + + [Fact] + public void Should_throw_when_committing_child_transaction_if_another_child_transaction_is_rolled_back() + { + // ReSharper disable once UnusedVariable + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + var anotherChildTx = SUT.BeginTransaction(); + + anotherChildTx.Rollback(); + + childTx.Invoking(tx => tx.Commit()) + .Should().Throw().WithMessage("The transaction has aborted."); + + SUT.CurrentTransaction.Should().Be(childTx); + IsTransactionUsable(childTx.GetDbTransaction()).Should().BeTrue(); + } + + [Fact] + public void Should_throw_when_committing_child_transaction_if_another_child_transaction_is_rolled_back_due_to_disposed() + { + // ReSharper disable once UnusedVariable + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + var anotherChildTx = SUT.BeginTransaction(); + + anotherChildTx.Dispose(); + + childTx.Invoking(tx => tx.Commit()) + .Should().Throw().WithMessage("The transaction has aborted."); + + SUT.CurrentTransaction.Should().Be(childTx); + IsTransactionUsable(childTx.GetDbTransaction()).Should().BeTrue(); + } + + [Fact] + public void Should_throw_when_committing_root_transaction_if_child_transaction_is_not_completed() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + + rootTx.Invoking(tx => tx.Commit()) + .Should().Throw().WithMessage("Transactions nested incorrectly. At least one of the child transactions is not completed."); + + SUT.CurrentTransaction.Should().Be(childTx); + IsTransactionUsable(childTx.GetDbTransaction()).Should().BeTrue(); + } + + [Fact] + public void Should_throw_when_committing_child_transaction_if_another_child_transaction_is_not_completed() + { + // ReSharper disable once UnusedVariable + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + var anotherChildTx = SUT.BeginTransaction(); + + childTx.Invoking(tx => tx.Commit()) + .Should().Throw().WithMessage("Transactions nested incorrectly. At least one of the child transactions is not completed."); + + SUT.CurrentTransaction.Should().Be(anotherChildTx); + IsTransactionUsable(anotherChildTx.GetDbTransaction()).Should().BeTrue(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedDbContextTransactionTests/Dispose.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedDbContextTransactionTests/Dispose.cs index 388931d9..4f329b66 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedDbContextTransactionTests/Dispose.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedDbContextTransactionTests/Dispose.cs @@ -1,63 +1,63 @@ -using Microsoft.EntityFrameworkCore.Storage; -using Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.Storage.NestedDbContextTransactionTests; - -public class Dispose : NestedRelationalTransactionManagerTestBase -{ - public Dispose(ITestOutputHelper testOutputHelper) - : base(testOutputHelper, IMigrationExecutionStrategy.EnsureCreated) - { - } - - [Fact] - public void Should_rollback_uncompleted_root_transaction() - { - var rootTx = SUT.BeginTransaction(); - ActDbContext.Add(new TestEntity()); - ActDbContext.SaveChanges(); - - rootTx.Dispose(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - AssertDbContext.TestEntities.Should().HaveCount(0); - } - - [Fact] - public void Should_do_nothing_when_disposing_multiple_times() - { - var rootTx = SUT.BeginTransaction(); - ActDbContext.Add(new TestEntity()); - ActDbContext.SaveChanges(); - - rootTx.Dispose(); - rootTx.Dispose(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - AssertDbContext.TestEntities.Should().HaveCount(0); - } - - [Fact] - public void Should_rollback_uncompleted_child_transaction() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - ActDbContext.Add(new TestEntity()); - ActDbContext.SaveChanges(); - - childTx.Dispose(); - - SUT.CurrentTransaction.Should().Be(rootTx); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); - AssertDbContext.TestEntities.Should().HaveCount(1); - - rootTx.Dispose(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - AssertDbContext.TestEntities.Should().HaveCount(0); - } -} +using Microsoft.EntityFrameworkCore.Storage; +using Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.Storage.NestedDbContextTransactionTests; + +public class Dispose : NestedRelationalTransactionManagerTestBase +{ + public Dispose(ITestOutputHelper testOutputHelper) + : base(testOutputHelper, IMigrationExecutionStrategy.EnsureCreated) + { + } + + [Fact] + public void Should_rollback_uncompleted_root_transaction() + { + var rootTx = SUT.BeginTransaction(); + ActDbContext.Add(new TestEntity()); + ActDbContext.SaveChanges(); + + rootTx.Dispose(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + AssertDbContext.TestEntities.Should().HaveCount(0); + } + + [Fact] + public void Should_do_nothing_when_disposing_multiple_times() + { + var rootTx = SUT.BeginTransaction(); + ActDbContext.Add(new TestEntity()); + ActDbContext.SaveChanges(); + + rootTx.Dispose(); + rootTx.Dispose(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + AssertDbContext.TestEntities.Should().HaveCount(0); + } + + [Fact] + public void Should_rollback_uncompleted_child_transaction() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + ActDbContext.Add(new TestEntity()); + ActDbContext.SaveChanges(); + + childTx.Dispose(); + + SUT.CurrentTransaction.Should().Be(rootTx); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); + AssertDbContext.TestEntities.Should().HaveCount(1); + + rootTx.Dispose(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + AssertDbContext.TestEntities.Should().HaveCount(0); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedDbContextTransactionTests/Rollback.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedDbContextTransactionTests/Rollback.cs index e83ade38..0979e1fe 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedDbContextTransactionTests/Rollback.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedDbContextTransactionTests/Rollback.cs @@ -1,180 +1,180 @@ -using Microsoft.EntityFrameworkCore.Storage; -using Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.Storage.NestedDbContextTransactionTests; - -public class Rollback : NestedRelationalTransactionManagerTestBase -{ - public Rollback(ITestOutputHelper testOutputHelper) - : base(testOutputHelper, IMigrationExecutionStrategy.EnsureCreated) - { - } - - [Fact] - public void Should_throw_when_trying_to_rollback_twice() - { - var rootTx = SUT.BeginTransaction(); - ActDbContext.Add(new TestEntity()); - ActDbContext.SaveChanges(); - - rootTx.Rollback(); - - rootTx.Invoking(tx => tx.Rollback()) - .Should().Throw().WithMessage("This root transaction has completed; it is no longer usable."); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - AssertDbContext.TestEntities.Should().HaveCount(0); - } - - [Fact] - public void Should_rollback_root_transaction_and_underlying_transaction() - { - var rootTx = SUT.BeginTransaction(); - ActDbContext.Add(new TestEntity()); - ActDbContext.SaveChanges(); - - rootTx.Rollback(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - AssertDbContext.TestEntities.Should().BeEmpty(); - } - - [Fact] - public void Should_rollback_child_transaction_but_not_underlying_transaction() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - - childTx.Rollback(); - - SUT.CurrentTransaction.Should().Be(rootTx); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); - } - - [Fact] - public void Should_rollback_child_and_root_transaction() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - ActDbContext.Add(new TestEntity()); - ActDbContext.SaveChanges(); - - childTx.Rollback(); - rootTx.Rollback(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - AssertDbContext.TestEntities.Should().HaveCount(0); - } - - [Fact] - public void Should_rollback_children_and_root_transactions() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - var anotherChildTx = SUT.BeginTransaction(); - ActDbContext.Add(new TestEntity()); - ActDbContext.SaveChanges(); - - anotherChildTx.Rollback(); - childTx.Rollback(); - rootTx.Rollback(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - AssertDbContext.TestEntities.Should().HaveCount(0); - } - - [Fact] - public void Should_rollback_root_transaction_if_child_transaction_is_committed() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - ActDbContext.Add(new TestEntity()); - ActDbContext.SaveChanges(); - - childTx.Commit(); - rootTx.Rollback(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - AssertDbContext.TestEntities.Should().HaveCount(0); - } - - [Fact] - public void Should_rollback_root_transaction_if_child_transaction_is_rolled_backed_due_to_dispose() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - ActDbContext.Add(new TestEntity()); - ActDbContext.SaveChanges(); - - childTx.Dispose(); - rootTx.Rollback(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - AssertDbContext.TestEntities.Should().HaveCount(0); - } - - [Fact] - public void Should_rollback_child_transaction_if_another_child_transaction_is_committed() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - var anotherChildTx = SUT.BeginTransaction(); - - anotherChildTx.Commit(); - childTx.Rollback(); - - SUT.CurrentTransaction.Should().Be(rootTx); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); - } - - [Fact] - public void Should_rollback_child_transaction_if_another_child_transaction_is_rolled_back_due_to_disposed() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - var anotherChildTx = SUT.BeginTransaction(); - - anotherChildTx.Dispose(); - childTx.Rollback(); - - SUT.CurrentTransaction.Should().Be(rootTx); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); - } - - [Fact] - public void Should_throw_when_rollback_root_transaction_if_child_transaction_is_not_completed() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - - rootTx.Invoking(tx => tx.Rollback()) - .Should().Throw().WithMessage("Transactions nested incorrectly. At least one of the child transactions is not completed."); - - SUT.CurrentTransaction.Should().Be(childTx); - IsTransactionUsable(childTx.GetDbTransaction()).Should().BeTrue(); - } - - [Fact] - public void Should_throw_when_rollback_child_transaction_if_another_child_transaction_is_not_completed() - { - // ReSharper disable once UnusedVariable - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - var anotherChildTx = SUT.BeginTransaction(); - ActDbContext.Add(new TestEntity()); - ActDbContext.SaveChanges(); - - childTx.Invoking(tx => tx.Rollback()) - .Should().Throw().WithMessage("Transactions nested incorrectly. At least one of the child transactions is not completed."); - - SUT.CurrentTransaction.Should().Be(anotherChildTx); - IsTransactionUsable(anotherChildTx.GetDbTransaction()).Should().BeTrue(); - } -} +using Microsoft.EntityFrameworkCore.Storage; +using Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.Storage.NestedDbContextTransactionTests; + +public class Rollback : NestedRelationalTransactionManagerTestBase +{ + public Rollback(ITestOutputHelper testOutputHelper) + : base(testOutputHelper, IMigrationExecutionStrategy.EnsureCreated) + { + } + + [Fact] + public void Should_throw_when_trying_to_rollback_twice() + { + var rootTx = SUT.BeginTransaction(); + ActDbContext.Add(new TestEntity()); + ActDbContext.SaveChanges(); + + rootTx.Rollback(); + + rootTx.Invoking(tx => tx.Rollback()) + .Should().Throw().WithMessage("This root transaction has completed; it is no longer usable."); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + AssertDbContext.TestEntities.Should().HaveCount(0); + } + + [Fact] + public void Should_rollback_root_transaction_and_underlying_transaction() + { + var rootTx = SUT.BeginTransaction(); + ActDbContext.Add(new TestEntity()); + ActDbContext.SaveChanges(); + + rootTx.Rollback(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + AssertDbContext.TestEntities.Should().BeEmpty(); + } + + [Fact] + public void Should_rollback_child_transaction_but_not_underlying_transaction() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + + childTx.Rollback(); + + SUT.CurrentTransaction.Should().Be(rootTx); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); + } + + [Fact] + public void Should_rollback_child_and_root_transaction() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + ActDbContext.Add(new TestEntity()); + ActDbContext.SaveChanges(); + + childTx.Rollback(); + rootTx.Rollback(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + AssertDbContext.TestEntities.Should().HaveCount(0); + } + + [Fact] + public void Should_rollback_children_and_root_transactions() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + var anotherChildTx = SUT.BeginTransaction(); + ActDbContext.Add(new TestEntity()); + ActDbContext.SaveChanges(); + + anotherChildTx.Rollback(); + childTx.Rollback(); + rootTx.Rollback(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + AssertDbContext.TestEntities.Should().HaveCount(0); + } + + [Fact] + public void Should_rollback_root_transaction_if_child_transaction_is_committed() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + ActDbContext.Add(new TestEntity()); + ActDbContext.SaveChanges(); + + childTx.Commit(); + rootTx.Rollback(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + AssertDbContext.TestEntities.Should().HaveCount(0); + } + + [Fact] + public void Should_rollback_root_transaction_if_child_transaction_is_rolled_backed_due_to_dispose() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + ActDbContext.Add(new TestEntity()); + ActDbContext.SaveChanges(); + + childTx.Dispose(); + rootTx.Rollback(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + AssertDbContext.TestEntities.Should().HaveCount(0); + } + + [Fact] + public void Should_rollback_child_transaction_if_another_child_transaction_is_committed() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + var anotherChildTx = SUT.BeginTransaction(); + + anotherChildTx.Commit(); + childTx.Rollback(); + + SUT.CurrentTransaction.Should().Be(rootTx); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); + } + + [Fact] + public void Should_rollback_child_transaction_if_another_child_transaction_is_rolled_back_due_to_disposed() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + var anotherChildTx = SUT.BeginTransaction(); + + anotherChildTx.Dispose(); + childTx.Rollback(); + + SUT.CurrentTransaction.Should().Be(rootTx); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); + } + + [Fact] + public void Should_throw_when_rollback_root_transaction_if_child_transaction_is_not_completed() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + + rootTx.Invoking(tx => tx.Rollback()) + .Should().Throw().WithMessage("Transactions nested incorrectly. At least one of the child transactions is not completed."); + + SUT.CurrentTransaction.Should().Be(childTx); + IsTransactionUsable(childTx.GetDbTransaction()).Should().BeTrue(); + } + + [Fact] + public void Should_throw_when_rollback_child_transaction_if_another_child_transaction_is_not_completed() + { + // ReSharper disable once UnusedVariable + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + var anotherChildTx = SUT.BeginTransaction(); + ActDbContext.Add(new TestEntity()); + ActDbContext.SaveChanges(); + + childTx.Invoking(tx => tx.Rollback()) + .Should().Throw().WithMessage("Transactions nested incorrectly. At least one of the child transactions is not completed."); + + SUT.CurrentTransaction.Should().Be(anotherChildTx); + IsTransactionUsable(anotherChildTx.GetDbTransaction()).Should().BeTrue(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/BeginTransaction.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/BeginTransaction.cs index 777dca95..1e32a6c1 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/BeginTransaction.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/BeginTransaction.cs @@ -1,134 +1,134 @@ -using System.Data; -using Microsoft.EntityFrameworkCore.Storage; - -namespace Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; - -public class BeginTransaction : NestedRelationalTransactionManagerTestBase -{ - public BeginTransaction(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - - [Fact] - public void Should_create_new_root_transaction() - { - SUT.CurrentTransaction.Should().BeNull(); - - var tx = SUT.BeginTransaction(); - - tx.Should().NotBeNull(); - tx.Should().Be(SUT.CurrentTransaction); - tx.GetDbTransaction().Should().NotBeNull(); - } - - [Fact] - public void Should_create_new_child_transaction_if_root_transaction_exist() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - - childTx.Should().NotBeNull(); - childTx.Should().Be(SUT.CurrentTransaction); - childTx.GetDbTransaction().Should().NotBeNull(); - childTx.GetDbTransaction().Should().Be(rootTx.GetDbTransaction()); - } - - [Fact] - public void Should_create_new_child_transaction_if_another_child_transaction_exist() - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - var secondChildTx = SUT.BeginTransaction(); - - secondChildTx.Should().NotBeNull(); - secondChildTx.Should().Be(SUT.CurrentTransaction); - secondChildTx.GetDbTransaction().Should().NotBeNull(); - secondChildTx.GetDbTransaction().Should().Be(childTx.GetDbTransaction()); - secondChildTx.GetDbTransaction().Should().Be(rootTx.GetDbTransaction()); - } - - public static readonly IEnumerable TransactionCompletions = new List - { - new object[] { new Action(tx => tx.Commit()) }, - new object[] { new Action(tx => tx.Rollback()) }, - new object[] { new Action(tx => tx.Dispose()) } - }; - - [Theory] - [MemberData(nameof(TransactionCompletions))] - public void Should_create_new_child_transaction_from_root_after_another_child_is_completed(Action action) - { - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - - action(childTx); - - var secondChildTx = SUT.BeginTransaction(); - - secondChildTx.Should().NotBeNull(); - secondChildTx.Should().Be(SUT.CurrentTransaction); - secondChildTx.GetDbTransaction().Should().NotBeNull(); - secondChildTx.GetDbTransaction().Should().Be(rootTx.GetDbTransaction()); - - SUT.RollbackTransaction(); // rollback of secondChildTx - SUT.RollbackTransaction(); // rollback of rootTx - - SUT.CurrentTransaction.Should().BeNull(); - } - - [Fact] - public void Should_allow_create_child_transactions_with_lower_level() - { - // ReSharper disable UnusedVariable - var rootTx = SUT.BeginTransaction(IsolationLevel.Serializable); - var childTx = SUT.BeginTransaction(IsolationLevel.RepeatableRead); - // ReSharper restore UnusedVariable - } - - [Fact] - public void Should_allow_create_child_transactions_with_higher_level_if_underlying_transaction_uses_higher_level_internally() - { - var rootTx = SUT.BeginTransaction(IsolationLevel.RepeatableRead); - rootTx.GetDbTransaction().IsolationLevel.Should().Be(IsolationLevel.Serializable); // sqlite uses "Serializable" internally - - // ReSharper disable once UnusedVariable - var childTx = SUT.BeginTransaction(IsolationLevel.Serializable); - } - - [Fact] - public void Should_allow_create_child_transactions_with_no_isolation_level() - { - // ReSharper disable UnusedVariable - var rootTx = SUT.BeginTransaction(IsolationLevel.Serializable); - var childTx = SUT.BeginTransaction(); - // ReSharper restore UnusedVariable - } - - [Fact] - public void Should_not_allow_create_child_transactions_having_Snapshot() - { - SUT.BeginTransaction(IsolationLevel.Serializable); - - SUT.Invoking(sut => sut.BeginTransaction(IsolationLevel.Snapshot)) - .Should().Throw().WithMessage("The isolation level 'Serializable' of the parent transaction is not compatible to the provided isolation level 'Snapshot'."); - } - - [Fact] - public void Should_not_allow_create_child_transactions_having_Chaos() - { - SUT.BeginTransaction(IsolationLevel.Serializable); - - SUT.Invoking(sut => sut.BeginTransaction(IsolationLevel.Chaos)) - .Should().Throw().WithMessage("The isolation level 'Serializable' of the parent transaction is not compatible to the provided isolation level 'Chaos'."); - } - - [Fact] - public void Should_not_allow_create_child_transactions_having_Unspecified() - { - SUT.BeginTransaction(IsolationLevel.Serializable); - - SUT.Invoking(sut => sut.BeginTransaction(IsolationLevel.Unspecified)) - .Should().Throw().WithMessage("The isolation level 'Unspecified' is not allowed. (Parameter 'isolationLevel')"); - } -} +using System.Data; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; + +public class BeginTransaction : NestedRelationalTransactionManagerTestBase +{ + public BeginTransaction(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + } + + [Fact] + public void Should_create_new_root_transaction() + { + SUT.CurrentTransaction.Should().BeNull(); + + var tx = SUT.BeginTransaction(); + + tx.Should().NotBeNull(); + tx.Should().Be(SUT.CurrentTransaction); + tx.GetDbTransaction().Should().NotBeNull(); + } + + [Fact] + public void Should_create_new_child_transaction_if_root_transaction_exist() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + + childTx.Should().NotBeNull(); + childTx.Should().Be(SUT.CurrentTransaction); + childTx.GetDbTransaction().Should().NotBeNull(); + childTx.GetDbTransaction().Should().Be(rootTx.GetDbTransaction()); + } + + [Fact] + public void Should_create_new_child_transaction_if_another_child_transaction_exist() + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + var secondChildTx = SUT.BeginTransaction(); + + secondChildTx.Should().NotBeNull(); + secondChildTx.Should().Be(SUT.CurrentTransaction); + secondChildTx.GetDbTransaction().Should().NotBeNull(); + secondChildTx.GetDbTransaction().Should().Be(childTx.GetDbTransaction()); + secondChildTx.GetDbTransaction().Should().Be(rootTx.GetDbTransaction()); + } + + public static readonly IEnumerable TransactionCompletions = new List + { + new object[] { new Action(tx => tx.Commit()) }, + new object[] { new Action(tx => tx.Rollback()) }, + new object[] { new Action(tx => tx.Dispose()) } + }; + + [Theory] + [MemberData(nameof(TransactionCompletions))] + public void Should_create_new_child_transaction_from_root_after_another_child_is_completed(Action action) + { + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + + action(childTx); + + var secondChildTx = SUT.BeginTransaction(); + + secondChildTx.Should().NotBeNull(); + secondChildTx.Should().Be(SUT.CurrentTransaction); + secondChildTx.GetDbTransaction().Should().NotBeNull(); + secondChildTx.GetDbTransaction().Should().Be(rootTx.GetDbTransaction()); + + SUT.RollbackTransaction(); // rollback of secondChildTx + SUT.RollbackTransaction(); // rollback of rootTx + + SUT.CurrentTransaction.Should().BeNull(); + } + + [Fact] + public void Should_allow_create_child_transactions_with_lower_level() + { + // ReSharper disable UnusedVariable + var rootTx = SUT.BeginTransaction(IsolationLevel.Serializable); + var childTx = SUT.BeginTransaction(IsolationLevel.RepeatableRead); + // ReSharper restore UnusedVariable + } + + [Fact] + public void Should_allow_create_child_transactions_with_higher_level_if_underlying_transaction_uses_higher_level_internally() + { + var rootTx = SUT.BeginTransaction(IsolationLevel.RepeatableRead); + rootTx.GetDbTransaction().IsolationLevel.Should().Be(IsolationLevel.Serializable); // sqlite uses "Serializable" internally + + // ReSharper disable once UnusedVariable + var childTx = SUT.BeginTransaction(IsolationLevel.Serializable); + } + + [Fact] + public void Should_allow_create_child_transactions_with_no_isolation_level() + { + // ReSharper disable UnusedVariable + var rootTx = SUT.BeginTransaction(IsolationLevel.Serializable); + var childTx = SUT.BeginTransaction(); + // ReSharper restore UnusedVariable + } + + [Fact] + public void Should_not_allow_create_child_transactions_having_Snapshot() + { + SUT.BeginTransaction(IsolationLevel.Serializable); + + SUT.Invoking(sut => sut.BeginTransaction(IsolationLevel.Snapshot)) + .Should().Throw().WithMessage("The isolation level 'Serializable' of the parent transaction is not compatible to the provided isolation level 'Snapshot'."); + } + + [Fact] + public void Should_not_allow_create_child_transactions_having_Chaos() + { + SUT.BeginTransaction(IsolationLevel.Serializable); + + SUT.Invoking(sut => sut.BeginTransaction(IsolationLevel.Chaos)) + .Should().Throw().WithMessage("The isolation level 'Serializable' of the parent transaction is not compatible to the provided isolation level 'Chaos'."); + } + + [Fact] + public void Should_not_allow_create_child_transactions_having_Unspecified() + { + SUT.BeginTransaction(IsolationLevel.Serializable); + + SUT.Invoking(sut => sut.BeginTransaction(IsolationLevel.Unspecified)) + .Should().Throw().WithMessage("The isolation level 'Unspecified' is not allowed. (Parameter 'isolationLevel')"); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/BeginTransactionAsync.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/BeginTransactionAsync.cs index f723963f..c16f9f71 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/BeginTransactionAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/BeginTransactionAsync.cs @@ -1,79 +1,79 @@ -using Microsoft.EntityFrameworkCore.Storage; - -namespace Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; - -// ReSharper disable once InconsistentNaming -public class BeginTransactionAsync : NestedRelationalTransactionManagerTestBase -{ - public BeginTransactionAsync(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - - [Fact] - public async Task Should_create_new_root_transaction() - { - SUT.CurrentTransaction.Should().BeNull(); - - var tx = await SUT.BeginTransactionAsync(); - - tx.Should().NotBeNull(); - tx.Should().Be(SUT.CurrentTransaction); - tx.GetDbTransaction().Should().NotBeNull(); - } - - [Fact] - public async Task Should_create_new_child_transaction_if_root_transaction_exist() - { - var rootTx = await SUT.BeginTransactionAsync(); - var childTx = await SUT.BeginTransactionAsync(); - - childTx.Should().NotBeNull(); - childTx.Should().Be(SUT.CurrentTransaction); - childTx.GetDbTransaction().Should().NotBeNull(); - childTx.GetDbTransaction().Should().Be(rootTx.GetDbTransaction()); - } - - [Fact] - public async Task Should_create_new_child_transaction_if_another_child_transaction_exist() - { - var rootTx = await SUT.BeginTransactionAsync(); - var childTx = await SUT.BeginTransactionAsync(); - var secondChildTx = await SUT.BeginTransactionAsync(); - - secondChildTx.Should().NotBeNull(); - secondChildTx.Should().Be(SUT.CurrentTransaction); - secondChildTx.GetDbTransaction().Should().NotBeNull(); - secondChildTx.GetDbTransaction().Should().Be(childTx.GetDbTransaction()); - secondChildTx.GetDbTransaction().Should().Be(rootTx.GetDbTransaction()); - } - - public static readonly IEnumerable TransactionCompletions = new List - { - new object[] { new Action(tx => tx.Commit()) }, - new object[] { new Action(tx => tx.Rollback()) }, - new object[] { new Action(tx => tx.Dispose()) } - }; - - [Theory] - [MemberData(nameof(TransactionCompletions))] - public async Task Should_create_new_child_transaction_from_root_after_another_child_is_completed(Action action) - { - var rootTx = await SUT.BeginTransactionAsync(); - var childTx = await SUT.BeginTransactionAsync(); - - action(childTx); - - var secondChildTx = await SUT.BeginTransactionAsync(); - - secondChildTx.Should().NotBeNull(); - secondChildTx.Should().Be(SUT.CurrentTransaction); - secondChildTx.GetDbTransaction().Should().NotBeNull(); - secondChildTx.GetDbTransaction().Should().Be(rootTx.GetDbTransaction()); - - SUT.RollbackTransaction(); // rollback of secondChildTx - SUT.RollbackTransaction(); // rollback of rootTx - - SUT.CurrentTransaction.Should().BeNull(); - } -} +using Microsoft.EntityFrameworkCore.Storage; + +namespace Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; + +// ReSharper disable once InconsistentNaming +public class BeginTransactionAsync : NestedRelationalTransactionManagerTestBase +{ + public BeginTransactionAsync(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + } + + [Fact] + public async Task Should_create_new_root_transaction() + { + SUT.CurrentTransaction.Should().BeNull(); + + var tx = await SUT.BeginTransactionAsync(); + + tx.Should().NotBeNull(); + tx.Should().Be(SUT.CurrentTransaction); + tx.GetDbTransaction().Should().NotBeNull(); + } + + [Fact] + public async Task Should_create_new_child_transaction_if_root_transaction_exist() + { + var rootTx = await SUT.BeginTransactionAsync(); + var childTx = await SUT.BeginTransactionAsync(); + + childTx.Should().NotBeNull(); + childTx.Should().Be(SUT.CurrentTransaction); + childTx.GetDbTransaction().Should().NotBeNull(); + childTx.GetDbTransaction().Should().Be(rootTx.GetDbTransaction()); + } + + [Fact] + public async Task Should_create_new_child_transaction_if_another_child_transaction_exist() + { + var rootTx = await SUT.BeginTransactionAsync(); + var childTx = await SUT.BeginTransactionAsync(); + var secondChildTx = await SUT.BeginTransactionAsync(); + + secondChildTx.Should().NotBeNull(); + secondChildTx.Should().Be(SUT.CurrentTransaction); + secondChildTx.GetDbTransaction().Should().NotBeNull(); + secondChildTx.GetDbTransaction().Should().Be(childTx.GetDbTransaction()); + secondChildTx.GetDbTransaction().Should().Be(rootTx.GetDbTransaction()); + } + + public static readonly IEnumerable TransactionCompletions = new List + { + new object[] { new Action(tx => tx.Commit()) }, + new object[] { new Action(tx => tx.Rollback()) }, + new object[] { new Action(tx => tx.Dispose()) } + }; + + [Theory] + [MemberData(nameof(TransactionCompletions))] + public async Task Should_create_new_child_transaction_from_root_after_another_child_is_completed(Action action) + { + var rootTx = await SUT.BeginTransactionAsync(); + var childTx = await SUT.BeginTransactionAsync(); + + action(childTx); + + var secondChildTx = await SUT.BeginTransactionAsync(); + + secondChildTx.Should().NotBeNull(); + secondChildTx.Should().Be(SUT.CurrentTransaction); + secondChildTx.GetDbTransaction().Should().NotBeNull(); + secondChildTx.GetDbTransaction().Should().Be(rootTx.GetDbTransaction()); + + SUT.RollbackTransaction(); // rollback of secondChildTx + SUT.RollbackTransaction(); // rollback of rootTx + + SUT.CurrentTransaction.Should().BeNull(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/CommitTransaction.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/CommitTransaction.cs index 7773ec4f..8d18e613 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/CommitTransaction.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/CommitTransaction.cs @@ -1,56 +1,56 @@ -using Microsoft.EntityFrameworkCore.Storage; - -namespace Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; - -public class CommitTransaction : NestedRelationalTransactionManagerTestBase -{ - public CommitTransaction(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - - [Fact] - public void Should_throw_InvalidOperationException_no_transaction_active() - { - SUT.Invoking(sut => sut.CommitTransaction()) - .Should().Throw().WithMessage("The connection does not have any active transactions."); - } - - [Fact] - public void Should_commit_root_transaction_and_the_underlying_db_transaction() - { - var underlyingTx = SUT.BeginTransaction().GetDbTransaction(); - - SUT.CommitTransaction(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(underlyingTx).Should().BeFalse(); - } - - [Fact] - public void Should_commit_child_transaction_only() - { - var rootTx = SUT.BeginTransaction(); - SUT.BeginTransaction(); - - SUT.CommitTransaction(); - - SUT.CurrentTransaction.Should().Be(rootTx); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); - } - - [Fact] - public void Should_create_newest_child_transaction_only() - { - // ReSharper disable UnusedVariable - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - var secondChildTx = SUT.BeginTransaction(); - // ReSharper restore UnusedVariable - - SUT.CommitTransaction(); - - SUT.CurrentTransaction.Should().Be(childTx); - IsTransactionUsable(childTx.GetDbTransaction()).Should().BeTrue(); - } -} +using Microsoft.EntityFrameworkCore.Storage; + +namespace Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; + +public class CommitTransaction : NestedRelationalTransactionManagerTestBase +{ + public CommitTransaction(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + } + + [Fact] + public void Should_throw_InvalidOperationException_no_transaction_active() + { + SUT.Invoking(sut => sut.CommitTransaction()) + .Should().Throw().WithMessage("The connection does not have any active transactions."); + } + + [Fact] + public void Should_commit_root_transaction_and_the_underlying_db_transaction() + { + var underlyingTx = SUT.BeginTransaction().GetDbTransaction(); + + SUT.CommitTransaction(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(underlyingTx).Should().BeFalse(); + } + + [Fact] + public void Should_commit_child_transaction_only() + { + var rootTx = SUT.BeginTransaction(); + SUT.BeginTransaction(); + + SUT.CommitTransaction(); + + SUT.CurrentTransaction.Should().Be(rootTx); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); + } + + [Fact] + public void Should_create_newest_child_transaction_only() + { + // ReSharper disable UnusedVariable + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + var secondChildTx = SUT.BeginTransaction(); + // ReSharper restore UnusedVariable + + SUT.CommitTransaction(); + + SUT.CurrentTransaction.Should().Be(childTx); + IsTransactionUsable(childTx.GetDbTransaction()).Should().BeTrue(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/NestedRelationalTransactionManagerTestBase.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/NestedRelationalTransactionManagerTestBase.cs index f0a82863..63fe1c59 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/NestedRelationalTransactionManagerTestBase.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/NestedRelationalTransactionManagerTestBase.cs @@ -1,37 +1,37 @@ -using System.Data.Common; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage; - -namespace Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; - -public abstract class NestedRelationalTransactionManagerTestBase : IntegrationTestsBase -{ - protected NestedRelationalTransactionManager SUT => (NestedRelationalTransactionManager)ActDbContext.GetService(); - - protected NestedRelationalTransactionManagerTestBase( - ITestOutputHelper testOutputHelper, - IMigrationExecutionStrategy? migrationExecutionStrategy = null) - : base(testOutputHelper, migrationExecutionStrategy) - { - ConfigureOptionsBuilder = builder => builder.AddNestedTransactionSupport(); - } - - protected bool IsTransactionUsable(DbTransaction tx) - { - try - { - var connection = ActDbContext.Database.GetDbConnection(); - var command = connection.CreateCommand(); - command.Transaction = tx; - command.CommandText = "PRAGMA user_version;"; - command.ExecuteNonQuery(); - } - catch (InvalidOperationException ex) - { - if (ex.Message == "The transaction object is not associated with the same connection object as this command.") - return false; - } - - return true; - } -} +using System.Data.Common; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; + +public abstract class NestedRelationalTransactionManagerTestBase : IntegrationTestsBase +{ + protected NestedRelationalTransactionManager SUT => (NestedRelationalTransactionManager)ActDbContext.GetService(); + + protected NestedRelationalTransactionManagerTestBase( + ITestOutputHelper testOutputHelper, + IMigrationExecutionStrategy? migrationExecutionStrategy = null) + : base(testOutputHelper, migrationExecutionStrategy) + { + ConfigureOptionsBuilder = builder => builder.AddNestedTransactionSupport(); + } + + protected bool IsTransactionUsable(DbTransaction tx) + { + try + { + var connection = ActDbContext.Database.GetDbConnection(); + var command = connection.CreateCommand(); + command.Transaction = tx; + command.CommandText = "PRAGMA user_version;"; + command.ExecuteNonQuery(); + } + catch (InvalidOperationException ex) + { + if (ex.Message == "The transaction object is not associated with the same connection object as this command.") + return false; + } + + return true; + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/ResetState.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/ResetState.cs index 5deb9f0d..b698415d 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/ResetState.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/ResetState.cs @@ -1,45 +1,45 @@ -using Microsoft.EntityFrameworkCore.Storage; - -namespace Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; - -public class ResetState : NestedRelationalTransactionManagerTestBase -{ - public ResetState(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - - [Fact] - public void Should_do_nothing_if_not_transaction_is_active() - { - SUT.ResetState(); - - SUT.CurrentTransaction.Should().BeNull(); - } - - [Fact] - public void Should_dispose_open_root_transaction() - { - var rootTx = SUT.BeginTransaction(); - - SUT.ResetState(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - } - - [Fact] - public void Should_dispose_current_child_and_root_transactions() - { - // ReSharper disable UnusedVariable - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - var secondChildTx = SUT.BeginTransaction(); - // ReSharper restore UnusedVariable - - SUT.ResetState(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); - } +using Microsoft.EntityFrameworkCore.Storage; + +namespace Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; + +public class ResetState : NestedRelationalTransactionManagerTestBase +{ + public ResetState(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + } + + [Fact] + public void Should_do_nothing_if_not_transaction_is_active() + { + SUT.ResetState(); + + SUT.CurrentTransaction.Should().BeNull(); + } + + [Fact] + public void Should_dispose_open_root_transaction() + { + var rootTx = SUT.BeginTransaction(); + + SUT.ResetState(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + } + + [Fact] + public void Should_dispose_current_child_and_root_transactions() + { + // ReSharper disable UnusedVariable + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + var secondChildTx = SUT.BeginTransaction(); + // ReSharper restore UnusedVariable + + SUT.ResetState(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeFalse(); + } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/RollbackTransaction.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/RollbackTransaction.cs index a25aa4cb..2d1e1885 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/RollbackTransaction.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/EntityFrameworkCore/Storage/NestedRelationalTransactionManagerTests/RollbackTransaction.cs @@ -1,56 +1,56 @@ -using Microsoft.EntityFrameworkCore.Storage; - -namespace Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; - -public class RollbackTransaction : NestedRelationalTransactionManagerTestBase -{ - public RollbackTransaction(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - - [Fact] - public void Should_throw_InvalidOperationException_no_transaction_active() - { - SUT.Invoking(sut => sut.RollbackTransaction()) - .Should().Throw().WithMessage("The connection does not have any active transactions."); - } - - [Fact] - public void Should_commit_root_transaction_and_the_underlying_db_transaction() - { - var underlyingTx = SUT.BeginTransaction().GetDbTransaction(); - - SUT.RollbackTransaction(); - - SUT.CurrentTransaction.Should().BeNull(); - IsTransactionUsable(underlyingTx).Should().BeFalse(); - } - - [Fact] - public void Should_commit_child_transaction_only() - { - var rootTx = SUT.BeginTransaction(); - SUT.BeginTransaction(); - - SUT.RollbackTransaction(); - - SUT.CurrentTransaction.Should().Be(rootTx); - IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); - } - - [Fact] - public void Should_create_newest_child_transaction_only() - { - // ReSharper disable UnusedVariable - var rootTx = SUT.BeginTransaction(); - var childTx = SUT.BeginTransaction(); - var secondChildTx = SUT.BeginTransaction(); - // ReSharper restore UnusedVariable - - SUT.RollbackTransaction(); - - SUT.CurrentTransaction.Should().Be(childTx); - IsTransactionUsable(childTx.GetDbTransaction()).Should().BeTrue(); - } -} +using Microsoft.EntityFrameworkCore.Storage; + +namespace Thinktecture.EntityFrameworkCore.Storage.NestedRelationalTransactionManagerTests; + +public class RollbackTransaction : NestedRelationalTransactionManagerTestBase +{ + public RollbackTransaction(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + } + + [Fact] + public void Should_throw_InvalidOperationException_no_transaction_active() + { + SUT.Invoking(sut => sut.RollbackTransaction()) + .Should().Throw().WithMessage("The connection does not have any active transactions."); + } + + [Fact] + public void Should_commit_root_transaction_and_the_underlying_db_transaction() + { + var underlyingTx = SUT.BeginTransaction().GetDbTransaction(); + + SUT.RollbackTransaction(); + + SUT.CurrentTransaction.Should().BeNull(); + IsTransactionUsable(underlyingTx).Should().BeFalse(); + } + + [Fact] + public void Should_commit_child_transaction_only() + { + var rootTx = SUT.BeginTransaction(); + SUT.BeginTransaction(); + + SUT.RollbackTransaction(); + + SUT.CurrentTransaction.Should().Be(rootTx); + IsTransactionUsable(rootTx.GetDbTransaction()).Should().BeTrue(); + } + + [Fact] + public void Should_create_newest_child_transaction_only() + { + // ReSharper disable UnusedVariable + var rootTx = SUT.BeginTransaction(); + var childTx = SUT.BeginTransaction(); + var secondChildTx = SUT.BeginTransaction(); + // ReSharper restore UnusedVariable + + SUT.RollbackTransaction(); + + SUT.CurrentTransaction.Should().Be(childTx); + IsTransactionUsable(childTx.GetDbTransaction()).Should().BeTrue(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Extensions/DbContextExtensions.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Extensions/DbContextExtensions.cs index 73f733d9..0243e4c1 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Extensions/DbContextExtensions.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Extensions/DbContextExtensions.cs @@ -1,12 +1,12 @@ -using Microsoft.EntityFrameworkCore.Metadata; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -public static class DbContextExtensions -{ - public static IEntityType GetEntityType(this DbContext ctx) - { - return ctx.Model.GetEntityType(typeof(T)); - } +using Microsoft.EntityFrameworkCore.Metadata; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +public static class DbContextExtensions +{ + public static IEntityType GetEntityType(this DbContext ctx) + { + return ctx.Model.GetEntityType(typeof(T)); + } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/IntegrationTestsBase.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/IntegrationTestsBase.cs index 89b08d76..b74fa6c7 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/IntegrationTestsBase.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/IntegrationTestsBase.cs @@ -1,48 +1,48 @@ -using Microsoft.Extensions.Logging; -using Thinktecture.EntityFrameworkCore; -using Thinktecture.EntityFrameworkCore.Testing; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture; - -public class IntegrationTestsBase : SqliteDbContextIntegrationTests -{ - private readonly IMigrationExecutionStrategy? _migrationExecutionStrategy; - - protected Action>? ConfigureOptionsBuilder { get; set; } - protected Action? ConfigureModel { get; set; } - protected ILoggerFactory LoggerFactory { get; } - - protected IntegrationTestsBase( - ITestOutputHelper testOutputHelper, - IMigrationExecutionStrategy? migrationExecutionStrategy = null) - : base(testOutputHelper) - { - _migrationExecutionStrategy = migrationExecutionStrategy; - LoggerFactory = testOutputHelper.ToLoggerFactory(); - } - - protected override void ConfigureTestDbContextProvider(SqliteTestDbContextProviderBuilder builder) - { - builder.UseMigrationExecutionStrategy(_migrationExecutionStrategy ?? IMigrationExecutionStrategy.NoMigration) - .UseMigrationLogLevel(LogLevel.Warning) - .ConfigureOptions(optionsBuilder => ConfigureOptionsBuilder?.Invoke(optionsBuilder)) - .InitializeContext(ctx => ctx.ConfigureModel = ConfigureModel) - .UseContextFactory(options => new DbContextWithSchema(options, null)); - - if (ConfigureModel is not null) - builder.DisableModelCache(); - } - - protected DbContextWithSchema CreateContextWithSchema(string schema) - { - var options = new DbContextOptionsBuilder().Options; - return new DbContextWithSchema(options, schema) { ConfigureModel = ConfigureModel }; - } - - protected DbContextWithoutSchema CreateContextWithoutSchema() - { - var options = new DbContextOptionsBuilder().Options; - return new DbContextWithoutSchema(options) { ConfigureModel = ConfigureModel }; - } -} +using Microsoft.Extensions.Logging; +using Thinktecture.EntityFrameworkCore; +using Thinktecture.EntityFrameworkCore.Testing; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture; + +public class IntegrationTestsBase : SqliteDbContextIntegrationTests +{ + private readonly IMigrationExecutionStrategy? _migrationExecutionStrategy; + + protected Action>? ConfigureOptionsBuilder { get; set; } + protected Action? ConfigureModel { get; set; } + protected ILoggerFactory LoggerFactory { get; } + + protected IntegrationTestsBase( + ITestOutputHelper testOutputHelper, + IMigrationExecutionStrategy? migrationExecutionStrategy = null) + : base(testOutputHelper) + { + _migrationExecutionStrategy = migrationExecutionStrategy; + LoggerFactory = testOutputHelper.ToLoggerFactory(); + } + + protected override void ConfigureTestDbContextProvider(SqliteTestDbContextProviderBuilder builder) + { + builder.UseMigrationExecutionStrategy(_migrationExecutionStrategy ?? IMigrationExecutionStrategy.NoMigration) + .UseMigrationLogLevel(LogLevel.Warning) + .ConfigureOptions(optionsBuilder => ConfigureOptionsBuilder?.Invoke(optionsBuilder)) + .InitializeContext(ctx => ctx.ConfigureModel = ConfigureModel) + .UseContextFactory(options => new DbContextWithSchema(options, null)); + + if (ConfigureModel is not null) + builder.DisableModelCache(); + } + + protected DbContextWithSchema CreateContextWithSchema(string schema) + { + var options = new DbContextOptionsBuilder().Options; + return new DbContextWithSchema(options, schema) { ConfigureModel = ConfigureModel }; + } + + protected DbContextWithoutSchema CreateContextWithoutSchema() + { + var options = new DbContextOptionsBuilder().Options; + return new DbContextWithoutSchema(options) { ConfigureModel = ConfigureModel }; + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Linq/Expressions/ExpressionBodyExtractingVisitorTests/Visit.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Linq/Expressions/ExpressionBodyExtractingVisitorTests/Visit.cs index c65f1902..45ec3157 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Linq/Expressions/ExpressionBodyExtractingVisitorTests/Visit.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Linq/Expressions/ExpressionBodyExtractingVisitorTests/Visit.cs @@ -1,106 +1,106 @@ -using System.Linq.Expressions; -using System.Reflection; - -namespace Thinktecture.Linq.Expressions.ExpressionBodyExtractingVisitorTests; - -public class Visit -{ - private static readonly PropertyInfo _myOtherProperty = typeof(MyObject).GetProperty(nameof(MyObject.MyOtherProperty), BindingFlags.Instance | BindingFlags.Public) - ?? throw new Exception($"The property {nameof(MyObject.MyOtherProperty)} not found."); - - [Fact] - public void Should_return_the_same_expression_if_no_ExtractBody_found() - { - Expression> expression = o => o.MyProperty == null; - - var visitedExpression = ExpressionBodyExtractingVisitor.Rewrite(expression); - - visitedExpression.Should().Be(expression); - } - - [Fact] - public void Should_replace_ExtractBody_with_lambda_body_provided_as_field() - { - Expression> extractFromExpression = o => o.MyOtherProperty; - Expression> expression = o => o.MyProperty == extractFromExpression.ExtractBody(o); - - var visitedExpression = ExpressionBodyExtractingVisitor.Rewrite(expression); - - ValidateVisitedExpressions(expression, visitedExpression); - } - - [Fact] - public void Should_replace_ExtractBody_with_lambda_body_provided_as_property() - { - Expression> extractFromExpression = o => o.MyOtherProperty; - var extractExpressionHolder = new { Expr = extractFromExpression }; - Expression> expression = o => o.MyProperty == extractExpressionHolder.Expr.ExtractBody(o); - - var visitedExpression = ExpressionBodyExtractingVisitor.Rewrite(expression); - - ValidateVisitedExpressions(expression, visitedExpression); - } - - [Fact] - public void Should_replace_ExtractBody_with_lambda_body_provided_inline() - { - Expression> expression = o => o.MyProperty == ((Expression>)(i => i.MyOtherProperty)).ExtractBody(o); - - var visitedExpression = ExpressionBodyExtractingVisitor.Rewrite(expression); - - ValidateVisitedExpressions(expression, visitedExpression); - } - - [Fact] - public void Should_replace_ExtractBody_with_lambda_body_provided_as_static_method_returnvalue() - { - Expression> expression = o => o.MyProperty == MyObject.GetExpressionFromStaticMethod().ExtractBody(o); - - var visitedExpression = ExpressionBodyExtractingVisitor.Rewrite(expression); - - ValidateVisitedExpressions(expression, visitedExpression); - } - - [Fact] - public void Should_replace_ExtractBody_with_lambda_body_provided_as_instance_method_returnvalue() - { - var myObj = new MyObject(); - Expression> expression = o => o.MyProperty == myObj.GetExpressionFromInstanceMethod().ExtractBody(o); - - var visitedExpression = ExpressionBodyExtractingVisitor.Rewrite(expression); - - ValidateVisitedExpressions(expression, visitedExpression); - } - - private static void ValidateVisitedExpressions(Expression> expression, Expression> visitedExpression) - { - visitedExpression.Should() - .NotBeNull().And - .NotBe(expression); - - var rightMemberAccess = visitedExpression.Body.As() // o.MyProperty == o.MyOtherProperty - .Right.As(); // o.MyOtherProperty - - rightMemberAccess.Should().NotBeNull(); - - rightMemberAccess.Member.Should().Be(_myOtherProperty); // MyOtherProperty - rightMemberAccess.Expression.Should().Be(expression.Parameters[0]); // o - } - - private class MyObject - { - // ReSharper disable once UnusedAutoPropertyAccessor.Local - public string? MyProperty { get; set; } - public string? MyOtherProperty { get; set; } - - public static Expression> GetExpressionFromStaticMethod() - { - return o => o.MyOtherProperty; - } - - public Expression> GetExpressionFromInstanceMethod() - { - return o => o.MyOtherProperty; - } - } -} +using System.Linq.Expressions; +using System.Reflection; + +namespace Thinktecture.Linq.Expressions.ExpressionBodyExtractingVisitorTests; + +public class Visit +{ + private static readonly PropertyInfo _myOtherProperty = typeof(MyObject).GetProperty(nameof(MyObject.MyOtherProperty), BindingFlags.Instance | BindingFlags.Public) + ?? throw new Exception($"The property {nameof(MyObject.MyOtherProperty)} not found."); + + [Fact] + public void Should_return_the_same_expression_if_no_ExtractBody_found() + { + Expression> expression = o => o.MyProperty == null; + + var visitedExpression = ExpressionBodyExtractingVisitor.Rewrite(expression); + + visitedExpression.Should().Be(expression); + } + + [Fact] + public void Should_replace_ExtractBody_with_lambda_body_provided_as_field() + { + Expression> extractFromExpression = o => o.MyOtherProperty; + Expression> expression = o => o.MyProperty == extractFromExpression.ExtractBody(o); + + var visitedExpression = ExpressionBodyExtractingVisitor.Rewrite(expression); + + ValidateVisitedExpressions(expression, visitedExpression); + } + + [Fact] + public void Should_replace_ExtractBody_with_lambda_body_provided_as_property() + { + Expression> extractFromExpression = o => o.MyOtherProperty; + var extractExpressionHolder = new { Expr = extractFromExpression }; + Expression> expression = o => o.MyProperty == extractExpressionHolder.Expr.ExtractBody(o); + + var visitedExpression = ExpressionBodyExtractingVisitor.Rewrite(expression); + + ValidateVisitedExpressions(expression, visitedExpression); + } + + [Fact] + public void Should_replace_ExtractBody_with_lambda_body_provided_inline() + { + Expression> expression = o => o.MyProperty == ((Expression>)(i => i.MyOtherProperty)).ExtractBody(o); + + var visitedExpression = ExpressionBodyExtractingVisitor.Rewrite(expression); + + ValidateVisitedExpressions(expression, visitedExpression); + } + + [Fact] + public void Should_replace_ExtractBody_with_lambda_body_provided_as_static_method_returnvalue() + { + Expression> expression = o => o.MyProperty == MyObject.GetExpressionFromStaticMethod().ExtractBody(o); + + var visitedExpression = ExpressionBodyExtractingVisitor.Rewrite(expression); + + ValidateVisitedExpressions(expression, visitedExpression); + } + + [Fact] + public void Should_replace_ExtractBody_with_lambda_body_provided_as_instance_method_returnvalue() + { + var myObj = new MyObject(); + Expression> expression = o => o.MyProperty == myObj.GetExpressionFromInstanceMethod().ExtractBody(o); + + var visitedExpression = ExpressionBodyExtractingVisitor.Rewrite(expression); + + ValidateVisitedExpressions(expression, visitedExpression); + } + + private static void ValidateVisitedExpressions(Expression> expression, Expression> visitedExpression) + { + visitedExpression.Should() + .NotBeNull().And + .NotBe(expression); + + var rightMemberAccess = visitedExpression.Body.As() // o.MyProperty == o.MyOtherProperty + .Right.As(); // o.MyOtherProperty + + rightMemberAccess.Should().NotBeNull(); + + rightMemberAccess.Member.Should().Be(_myOtherProperty); // MyOtherProperty + rightMemberAccess.Expression.Should().Be(expression.Parameters[0]); // o + } + + private class MyObject + { + // ReSharper disable once UnusedAutoPropertyAccessor.Local + public string? MyProperty { get; set; } + public string? MyOtherProperty { get; set; } + + public static Expression> GetExpressionFromStaticMethod() + { + return o => o.MyOtherProperty; + } + + public Expression> GetExpressionFromInstanceMethod() + { + return o => o.MyOtherProperty; + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Linq/Expressions/ExpressionReplacingVisitorTests/Visit.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Linq/Expressions/ExpressionReplacingVisitorTests/Visit.cs index c941bc52..7bdb0c70 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Linq/Expressions/ExpressionReplacingVisitorTests/Visit.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Linq/Expressions/ExpressionReplacingVisitorTests/Visit.cs @@ -1,30 +1,30 @@ -using System.Linq.Expressions; - -namespace Thinktecture.Linq.Expressions.ExpressionReplacingVisitorTests; - -public class Visit -{ - [Fact] - public void Should_not_change_expression_if_expression_not_exists() - { - Expression> expression = s => s == 1; - var visitor = new ExpressionReplacingVisitor(Expression.Constant(1), Expression.Constant(2)); - - var visitedExpression = visitor.Visit(expression); - - visitedExpression.Should().Be(expression); - } - - [Fact] - public void Should_replace_found_expression() - { - Expression> expression = s => s == 1; - var oldExpression = expression.As().Body.As().Right; - var newExpression = Expression.Constant(2); - var visitor = new ExpressionReplacingVisitor(oldExpression, newExpression); - - var visitedExpression = visitor.Visit(expression); - - visitedExpression.As().Body.As().Right.Should().Be(newExpression); - } -} +using System.Linq.Expressions; + +namespace Thinktecture.Linq.Expressions.ExpressionReplacingVisitorTests; + +public class Visit +{ + [Fact] + public void Should_not_change_expression_if_expression_not_exists() + { + Expression> expression = s => s == 1; + var visitor = new ExpressionReplacingVisitor(Expression.Constant(1), Expression.Constant(2)); + + var visitedExpression = visitor.Visit(expression); + + visitedExpression.Should().Be(expression); + } + + [Fact] + public void Should_replace_found_expression() + { + Expression> expression = s => s == 1; + var oldExpression = expression.As().Body.As().Right; + var newExpression = Expression.Constant(2); + var visitor = new ExpressionReplacingVisitor(oldExpression, newExpression); + + var visitedExpression = visitor.Visit(expression); + + visitedExpression.As().Body.As().Right.Should().Be(newExpression); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Linq/Expressions/RelinqBaseTypeMemberAccessVisitorTests/Visit.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Linq/Expressions/RelinqBaseTypeMemberAccessVisitorTests/Visit.cs index bd0ce127..a9493c2a 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Linq/Expressions/RelinqBaseTypeMemberAccessVisitorTests/Visit.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Linq/Expressions/RelinqBaseTypeMemberAccessVisitorTests/Visit.cs @@ -1,111 +1,111 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reflection; - -namespace Thinktecture.Linq.Expressions.RelinqBaseTypeMemberAccessVisitorTests; - -[SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] -public class Visit -{ - [Fact] - public void Should_not_change_expression_without_conversion() - { - Expression> expression = o => o.MyProperty; - - var visitedExpression = RelinqInterfaceMemberAccessVisitor.Rewrite(expression); - - visitedExpression.Should().Be(expression); - } - - [Fact] - public void Should_change_property_expression_of_implicit_implemented_interface_to_one_of_concrete_type() - { - var expression = GetInterfaceImplementingExpression(); - - var visitedExpression = RelinqInterfaceMemberAccessVisitor.Rewrite(expression); - - var memberExpression = visitedExpression.As().Body.As(); - - memberExpression.Should().NotBeNull(); - memberExpression.Member.Should().Be(typeof(MyImplicitImplementingObject).GetProperty(nameof(MyImplicitImplementingObject.MyProperty), BindingFlags.Instance | BindingFlags.Public)); - memberExpression.Expression.Should().Be(expression.Parameters[0]); - } - - [Fact] - public void Should_change_property_expression_of_implicit_implemented_interface_with_setter_to_one_of_concrete_type() - { - var expression = GetInterfaceImplementingExpression(); - - var visitedExpression = RelinqInterfaceMemberAccessVisitor.Rewrite(expression); - - var memberExpression = visitedExpression.As().Body.As(); - - memberExpression.Should().NotBeNull(); - memberExpression.Member.Should().Be(typeof(MyImplicitImplementingObjectWithSetter).GetProperty(nameof(MyImplicitImplementingObjectWithSetter.MyProperty), BindingFlags.Instance | BindingFlags.Public)); - memberExpression.Expression.Should().Be(expression.Parameters[0]); - } - - [Fact] - public void Should_change_property_expression_of_explicit_implemented_interface_to_one_of_concrete_type() - { - var expression = GetInterfaceImplementingExpression(); - - var visitedExpression = RelinqInterfaceMemberAccessVisitor.Rewrite(expression); - - var memberExpression = visitedExpression.As().Body.As(); - - memberExpression.Should().NotBeNull(); - memberExpression.Member.Should().NotBeNull().And.Subject - .Should().Be(typeof(MyExplicitImplementingObject).GetProperties(BindingFlags.Instance | BindingFlags.NonPublic).FirstOrDefault()); - memberExpression.Expression.Should().Be(expression.Parameters[0]); - } - - [Fact] - public void Should_pick_correct_property_if_interface_is_implemented_explicit_and_implicit() - { - var expression = GetInterfaceImplementingExpression(); - - var visitedExpression = RelinqInterfaceMemberAccessVisitor.Rewrite(expression); - - var memberExpression = visitedExpression.As().Body.As(); - - memberExpression.Should().NotBeNull(); - memberExpression.Member.Should().NotBeNull().And.Subject - .Should().Be(typeof(MyDualImplementingObject).GetProperties(BindingFlags.Instance | BindingFlags.NonPublic).FirstOrDefault()) - .Should().NotBe(typeof(MyDualImplementingObject).GetProperty(nameof(MyDualImplementingObject.MyProperty), BindingFlags.Instance | BindingFlags.NonPublic)); - memberExpression.Expression.Should().Be(expression.Parameters[0]); - } - - private static Expression> GetInterfaceImplementingExpression() - where T : IMyObject - { - return o => o.MyProperty; - } - - private interface IMyObject - { - string? MyProperty { get; } - } - - private class MyImplicitImplementingObject : IMyObject - { - public string? MyProperty { get; } - } - - private class MyImplicitImplementingObjectWithSetter : IMyObject - { - public string? MyProperty { get; set; } - } - - private class MyExplicitImplementingObject : IMyObject - { - string? IMyObject.MyProperty { get; } - } - - private class MyDualImplementingObject : IMyObject - { - string? IMyObject.MyProperty { get; } - - public string? MyProperty { get; } - } -} +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using System.Reflection; + +namespace Thinktecture.Linq.Expressions.RelinqBaseTypeMemberAccessVisitorTests; + +[SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] +public class Visit +{ + [Fact] + public void Should_not_change_expression_without_conversion() + { + Expression> expression = o => o.MyProperty; + + var visitedExpression = RelinqInterfaceMemberAccessVisitor.Rewrite(expression); + + visitedExpression.Should().Be(expression); + } + + [Fact] + public void Should_change_property_expression_of_implicit_implemented_interface_to_one_of_concrete_type() + { + var expression = GetInterfaceImplementingExpression(); + + var visitedExpression = RelinqInterfaceMemberAccessVisitor.Rewrite(expression); + + var memberExpression = visitedExpression.As().Body.As(); + + memberExpression.Should().NotBeNull(); + memberExpression.Member.Should().Be(typeof(MyImplicitImplementingObject).GetProperty(nameof(MyImplicitImplementingObject.MyProperty), BindingFlags.Instance | BindingFlags.Public)); + memberExpression.Expression.Should().Be(expression.Parameters[0]); + } + + [Fact] + public void Should_change_property_expression_of_implicit_implemented_interface_with_setter_to_one_of_concrete_type() + { + var expression = GetInterfaceImplementingExpression(); + + var visitedExpression = RelinqInterfaceMemberAccessVisitor.Rewrite(expression); + + var memberExpression = visitedExpression.As().Body.As(); + + memberExpression.Should().NotBeNull(); + memberExpression.Member.Should().Be(typeof(MyImplicitImplementingObjectWithSetter).GetProperty(nameof(MyImplicitImplementingObjectWithSetter.MyProperty), BindingFlags.Instance | BindingFlags.Public)); + memberExpression.Expression.Should().Be(expression.Parameters[0]); + } + + [Fact] + public void Should_change_property_expression_of_explicit_implemented_interface_to_one_of_concrete_type() + { + var expression = GetInterfaceImplementingExpression(); + + var visitedExpression = RelinqInterfaceMemberAccessVisitor.Rewrite(expression); + + var memberExpression = visitedExpression.As().Body.As(); + + memberExpression.Should().NotBeNull(); + memberExpression.Member.Should().NotBeNull().And.Subject + .Should().Be(typeof(MyExplicitImplementingObject).GetProperties(BindingFlags.Instance | BindingFlags.NonPublic).FirstOrDefault()); + memberExpression.Expression.Should().Be(expression.Parameters[0]); + } + + [Fact] + public void Should_pick_correct_property_if_interface_is_implemented_explicit_and_implicit() + { + var expression = GetInterfaceImplementingExpression(); + + var visitedExpression = RelinqInterfaceMemberAccessVisitor.Rewrite(expression); + + var memberExpression = visitedExpression.As().Body.As(); + + memberExpression.Should().NotBeNull(); + memberExpression.Member.Should().NotBeNull().And.Subject + .Should().Be(typeof(MyDualImplementingObject).GetProperties(BindingFlags.Instance | BindingFlags.NonPublic).FirstOrDefault()) + .Should().NotBe(typeof(MyDualImplementingObject).GetProperty(nameof(MyDualImplementingObject.MyProperty), BindingFlags.Instance | BindingFlags.NonPublic)); + memberExpression.Expression.Should().Be(expression.Parameters[0]); + } + + private static Expression> GetInterfaceImplementingExpression() + where T : IMyObject + { + return o => o.MyProperty; + } + + private interface IMyObject + { + string? MyProperty { get; } + } + + private class MyImplicitImplementingObject : IMyObject + { + public string? MyProperty { get; } + } + + private class MyImplicitImplementingObjectWithSetter : IMyObject + { + public string? MyProperty { get; set; } + } + + private class MyExplicitImplementingObject : IMyObject + { + string? IMyObject.MyProperty { get; } + } + + private class MyDualImplementingObject : IMyObject + { + string? IMyObject.MyProperty { get; } + + public string? MyProperty { get; } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/DbContextWithSchema.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/DbContextWithSchema.cs index 7276d0b6..b3dae70f 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/DbContextWithSchema.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/DbContextWithSchema.cs @@ -1,41 +1,41 @@ -using Thinktecture.EntityFrameworkCore; - -namespace Thinktecture.TestDatabaseContext; - -public class DbContextWithSchema : DbContext, IDbDefaultSchema -{ - /// - public string? Schema { get; } - -#nullable disable - public DbSet TestEntities { get; set; } - public DbSet TestQuery { get; set; } - public DbSet EntitiesWithArrayValueComparer { get; set; } -#nullable enable - public Action? ConfigureModel { get; set; } - - public DbContextWithSchema(DbContextOptions options, string? schema) - : base(options) - { - Schema = schema; - } - - public static string TestDbFunction() - { - throw new NotSupportedException(); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity().HasNoKey().ToView("TestQuery"); - - modelBuilder.Entity(builder => builder.Property(e => e.Bytes) - .UseReferenceEqualityComparer()); - - modelBuilder.HasDbFunction(() => TestDbFunction()); - - ConfigureModel?.Invoke(modelBuilder); - } -} +using Thinktecture.EntityFrameworkCore; + +namespace Thinktecture.TestDatabaseContext; + +public class DbContextWithSchema : DbContext, IDbDefaultSchema +{ + /// + public string? Schema { get; } + +#nullable disable + public DbSet TestEntities { get; set; } + public DbSet TestQuery { get; set; } + public DbSet EntitiesWithArrayValueComparer { get; set; } +#nullable enable + public Action? ConfigureModel { get; set; } + + public DbContextWithSchema(DbContextOptions options, string? schema) + : base(options) + { + Schema = schema; + } + + public static string TestDbFunction() + { + throw new NotSupportedException(); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity().HasNoKey().ToView("TestQuery"); + + modelBuilder.Entity(builder => builder.Property(e => e.Bytes) + .UseReferenceEqualityComparer()); + + modelBuilder.HasDbFunction(() => TestDbFunction()); + + ConfigureModel?.Invoke(modelBuilder); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/DbContextWithoutSchema.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/DbContextWithoutSchema.cs index 286960c7..5f53951f 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/DbContextWithoutSchema.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/DbContextWithoutSchema.cs @@ -1,18 +1,18 @@ -namespace Thinktecture.TestDatabaseContext; - -public class DbContextWithoutSchema : DbContext -{ - public Action? ConfigureModel { get; set; } - - public DbContextWithoutSchema(DbContextOptions options) - : base(options) - { - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - ConfigureModel?.Invoke(modelBuilder); - } -} +namespace Thinktecture.TestDatabaseContext; + +public class DbContextWithoutSchema : DbContext +{ + public Action? ConfigureModel { get; set; } + + public DbContextWithoutSchema(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + ConfigureModel?.Invoke(modelBuilder); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/MigrationWithSchema.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/MigrationWithSchema.cs index 88a805e5..9167ca80 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/MigrationWithSchema.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/MigrationWithSchema.cs @@ -1,25 +1,25 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Thinktecture.EntityFrameworkCore; - -namespace Thinktecture.TestDatabaseContext; - -public class MigrationWithSchema : Migration, IDbDefaultSchema -{ - /// - public string? Schema { get; } - - public MigrationWithSchema(IDbDefaultSchema? schema) - { - Schema = schema?.Schema; - } - - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn("Table1", "Col1"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn("Table1", "Col1"); - } -} +using Microsoft.EntityFrameworkCore.Migrations; +using Thinktecture.EntityFrameworkCore; + +namespace Thinktecture.TestDatabaseContext; + +public class MigrationWithSchema : Migration, IDbDefaultSchema +{ + /// + public string? Schema { get; } + + public MigrationWithSchema(IDbDefaultSchema? schema) + { + Schema = schema?.Schema; + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn("Table1", "Col1"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn("Table1", "Col1"); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/MigrationWithoutSchema.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/MigrationWithoutSchema.cs index 933083e9..66fbbaa4 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/MigrationWithoutSchema.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/MigrationWithoutSchema.cs @@ -1,16 +1,16 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.TestDatabaseContext; - -public class MigrationWithoutSchema : Migration -{ - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn("Table1", "Col1"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn("Table1", "Col1"); - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.TestDatabaseContext; + +public class MigrationWithoutSchema : Migration +{ + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn("Table1", "Col1"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn("Table1", "Col1"); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/TestEntity.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/TestEntity.cs index bbb24cb1..47408b44 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/TestEntity.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/TestEntity.cs @@ -1,8 +1,8 @@ -namespace Thinktecture.TestDatabaseContext; - -public class TestEntity -{ - public Guid Id { get; set; } - public int Column1 { get; set; } - public string? Column2 { get; set; } -} +namespace Thinktecture.TestDatabaseContext; + +public class TestEntity +{ + public Guid Id { get; set; } + public int Column1 { get; set; } + public string? Column2 { get; set; } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/TestQuery.cs b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/TestQuery.cs index 731e32d8..575e4413 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/TestQuery.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/TestDatabaseContext/TestQuery.cs @@ -1,5 +1,5 @@ -namespace Thinktecture.TestDatabaseContext; - -public class TestQuery -{ +namespace Thinktecture.TestDatabaseContext; + +public class TestQuery +{ } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Thinktecture.EntityFrameworkCore.Relational.Tests.csproj b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Thinktecture.EntityFrameworkCore.Relational.Tests.csproj index 93de4897..ddf74f17 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Thinktecture.EntityFrameworkCore.Relational.Tests.csproj +++ b/tests/Thinktecture.EntityFrameworkCore.Relational.Tests/Thinktecture.EntityFrameworkCore.Relational.Tests.csproj @@ -1,13 +1,13 @@ - - - - $(NoWarn);CS1591;CA1063;CA1812;CA1816;CA1822;CA2000;EF1001 - - - - - - - - - + + + + $(NoWarn);CS1591;CA1063;CA1812;CA1816;CA1822;CA2000;EF1001 + + + + + + + + + diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/BulkInsertAsync.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/BulkInsertAsync.cs index 6a121eaf..489f1b53 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/BulkInsertAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/BulkInsertAsync.cs @@ -1,744 +1,744 @@ -using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Thinktecture.EntityFrameworkCore.TempTables; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqlServerBulkOperationExecutorTests; - -// ReSharper disable once InconsistentNaming -public class BulkInsertAsync : IntegrationTestsBase -{ - private SqlServerBulkOperationExecutor? _sut; - - private SqlServerBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); - - public BulkInsertAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_throw_when_trying_to_insert_into_pure_temp_table_entity() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await SUT.Invoking(sut => sut.BulkInsertAsync(new List> { new(0) }, new SqlServerBulkInsertOptions())) - .Should().ThrowAsync() - .WithMessage("The provided type 'TempTable' is not part of the provided Entity Framework model. (Parameter 'type')"); - } - - [Fact] - public async Task Should_insert_column_with_converter() - { - var entities = new List { new() { ConvertibleClass = new ConvertibleClass(42), RequiredName = "RequiredName" } }; - - await SUT.BulkInsertAsync(entities, new SqlServerBulkInsertOptions()); - - var entity = AssertDbContext.TestEntities.Single(); - - entity.ConvertibleClass.Should().NotBeNull(); - entity.ConvertibleClass!.Key.Should().Be(42); - } - - [Fact] - public async Task Should_insert_entities() - { - var testEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - - var testEntities = new[] { testEntity }; - - await SUT.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1) - .And.Subject.First() - .Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }); - } - - [Fact] - public async Task Should_insert_private_property() - { - var testEntity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - testEntity.SetPrivateField(3); - - var testEntities = new[] { testEntity }; - - await SUT.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions()); - - var loadedEntity = await AssertDbContext.TestEntities.FirstOrDefaultAsync(); - loadedEntity!.GetPrivateField().Should().Be(3); - } - - [Fact] - public async Task Should_insert_shadow_properties() - { - var testEntity = new TestEntityWithShadowProperties { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866") }; - ActDbContext.Entry(testEntity).Property("ShadowStringProperty").CurrentValue = "value"; - ActDbContext.Entry(testEntity).Property("ShadowIntProperty").CurrentValue = 42; - - var testEntities = new[] { testEntity }; - - await SUT.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions()); - - var loadedEntity = await AssertDbContext.TestEntitiesWithShadowProperties.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - AssertDbContext.Entry(loadedEntity!).Property("ShadowStringProperty").CurrentValue.Should().Be("value"); - AssertDbContext.Entry(loadedEntity!).Property("ShadowIntProperty").CurrentValue.Should().Be(42); - } - - [Fact] - public async Task Should_throw_because_sqlbulkcopy_dont_support_null_for_NOT_NULL_despite_sql_default_value() - { - var testEntity = new TestEntityWithSqlDefaultValues { String = null! }; - var testEntities = new[] { testEntity }; - - await SUT.Awaiting(sut => sut.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions())) - .Should().ThrowAsync() - .WithMessage("Column 'String' does not allow DBNull.Value."); - } - - [Fact] - public async Task Should_write_not_nullable_structs_as_is_despite_sql_default_value() - { - var testEntity = new TestEntityWithSqlDefaultValues - { - Id = Guid.Empty, - Int = 0, - String = null!, - NullableInt = null, - NullableString = null - }; - var testEntities = new[] { testEntity }; - - var options = new SqlServerBulkInsertOptions - { - // we skip TestEntityWithSqlDefaultValues.String - PropertiesToInsert = IEntityPropertiesProvider.Include(e => new - { - e.Id, - e.Int, - e.NullableInt, - e.NullableString - }) - }; - - await SUT.BulkInsertAsync(testEntities, options); - - var loadedEntity = await AssertDbContext.TestEntitiesWithDefaultValues.FirstOrDefaultAsync(); - loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues - { - Id = Guid.Empty, // persisted as-is - Int = 0, // persisted as-is - NullableInt = 2, // DEFAULT value constraint - String = "3", // DEFAULT value constraint - NullableString = "4" // DEFAULT value constraint - }); - } - - [Fact] - public async Task Should_throw_because_sqlbulkcopy_dont_support_null_for_NOT_NULL_despite_dotnet_default_value() - { - var testEntity = new TestEntityWithDotnetDefaultValues { String = null! }; - var testEntities = new[] { testEntity }; - - await SUT.Awaiting(sut => sut.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions())) - .Should().ThrowAsync() - .WithMessage("Column 'String' does not allow DBNull.Value."); - } - - [Fact] - public async Task Should_write_not_nullable_structs_as_is_despite_dotnet_default_value() - { - var testEntity = new TestEntityWithDotnetDefaultValues - { - Id = Guid.Empty, - Int = 0, - String = null!, - NullableInt = null, - NullableString = null - }; - var testEntities = new[] { testEntity }; - - var options = new SqlServerBulkInsertOptions - { - // we skip TestEntityWithDefaultValues.String - PropertiesToInsert = IEntityPropertiesProvider.Include(e => new - { - e.Id, - e.Int, - e.NullableInt, - e.NullableString - }) - }; - - await SUT.BulkInsertAsync(testEntities, options); - - var loadedEntity = await AssertDbContext.TestEntitiesWithDotnetDefaultValues.FirstOrDefaultAsync(); - loadedEntity.Should().BeEquivalentTo(new TestEntityWithDotnetDefaultValues - { - Id = Guid.Empty, // persisted as-is - Int = 0, // persisted as-is - NullableInt = 2, // DEFAULT value constraint - String = "3", // DEFAULT value constraint - NullableString = "4" // DEFAULT value constraint - }); - } - - [Fact] - public async Task Should_insert_auto_increment_column_with_KeepIdentity() - { - var testEntity = new TestEntityWithAutoIncrement { Id = 42 }; - var testEntities = new[] { testEntity }; - - var options = new SqlServerBulkInsertOptions { SqlBulkCopyOptions = SqlBulkCopyOptions.KeepIdentity }; - await SUT.BulkInsertAsync(testEntities, options); - - var loadedEntity = await AssertDbContext.TestEntitiesWithAutoIncrement.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - loadedEntity!.Id.Should().Be(42); - } - - [Fact] - public async Task Should_ignore_auto_increment_column_without_KeepIdentity() - { - var testEntity = new TestEntityWithAutoIncrement { Id = 42, Name = "value" }; - var testEntities = new[] { testEntity }; - - await SUT.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions()); - - var loadedEntity = await AssertDbContext.TestEntitiesWithAutoIncrement.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - loadedEntity!.Id.Should().NotBe(0); - loadedEntity.Name.Should().Be("value"); - } - - [Fact] - public async Task Should_ignore_RowVersion() - { - var testEntity = new TestEntityWithRowVersion { Id = new Guid("EBC95620-4D80-4318-9B92-AD7528B2965C"), RowVersion = Int32.MaxValue }; - var testEntities = new[] { testEntity }; - - await SUT.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions()); - - var loadedEntity = await AssertDbContext.TestEntitiesWithRowVersion.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - loadedEntity!.Id.Should().Be(new Guid("EBC95620-4D80-4318-9B92-AD7528B2965C")); - loadedEntity.RowVersion.Should().NotBe(Int32.MaxValue); - } - - [Fact] - public async Task Should_insert_specified_properties_only() - { - var testEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42, - PropertyWithBackingField = 7 - }; - testEntity.SetPrivateField(3); - - await SUT.BulkInsertAsync(new[] { testEntity }, - new SqlServerBulkInsertOptions - { - PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) - }); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - RequiredName = "RequiredName", - Count = 42, - PropertyWithBackingField = 7 - }); - loadedEntity.GetPrivateField().Should().Be(3); - } - - [Fact] - public async Task Should_throw_entity_contains_inlined_owned_type() - { - var testEntity = new TestEntity_Owns_Inline - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - InlineEntity = null! - }; - - await ActDbContext.Awaiting(ctx => ctx.BulkInsertIntoTempTableAsync(new[] { testEntity })) - .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); - } - - [Fact] - public async Task Should_insert_inlined_owned_type_if_it_has_default_values_only() - { - var testEntity = new TestEntity_Owns_Inline - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - InlineEntity = new OwnedEntity() - }; - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_Inline.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity_Owns_Inline - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - InlineEntity = new OwnedEntity - { - IntColumn = 0, - StringColumn = null - } - }); - } - - [Fact] - public async Task Should_insert_inlined_owned_types() - { - var testEntity = new TestEntity_Owns_Inline - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - InlineEntity = new OwnedEntity - { - IntColumn = 42, - StringColumn = "value" - } - }; - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_Inline.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity_Owns_Inline - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - InlineEntity = new OwnedEntity - { - IntColumn = 42, - StringColumn = "value" - } - }); - } - - [Fact] - public async Task Should_throw_if_separated_owned_type_uses_shadow_property_id_and_is_detached() - { - var testEntity = new TestEntity_Owns_SeparateOne - { - Id = new Guid("7C00ABFE-875B-4396-BE51-3E898647A264"), - SeparateEntity = new OwnedEntity - { - IntColumn = 42, - StringColumn = "value" - } - }; - - await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions())) - .Should().ThrowAsync() - .WithMessage("The entity type 'OwnedEntity' uses a shared type and the supplied entity is currently not being tracked. To start tracking this entity, call '.Reference().TargetEntry' or '.Collection().FindEntry()' on the owner entry."); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_SeparateOne() - { - var testEntity = new TestEntity_Owns_SeparateOne - { - Id = new Guid("7C00ABFE-875B-4396-BE51-3E898647A264"), - SeparateEntity = new OwnedEntity - { - IntColumn = 42, - StringColumn = "value" - } - }; - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(testEntity); - } - - [Fact] - public async Task Should_throw_if_separated_owned_types_uses_shadow_property_id_and_is_detached() - { - var testEntity = new TestEntity_Owns_SeparateMany - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntities = new List - { - new() - { - IntColumn = 42, - StringColumn = "value 1" - } - } - }; - - await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions())) - .Should().ThrowAsync() - .WithMessage("The entity type 'OwnedEntity' uses a shared type and the supplied entity is currently not being tracked. To start tracking this entity, call '.Reference().TargetEntry' or '.Collection().FindEntry()' on the owner entry."); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_SeparateMany() - { - var testEntity = new TestEntity_Owns_SeparateMany - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntities = new List - { - new() - { - IntColumn = 42, - StringColumn = "value 1" - }, - new() - { - IntColumn = 43, - StringColumn = "value 2" - } - } - }; - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateMany.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(testEntity); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_Inline_Inline() - { - var testEntity = new TestEntity_Owns_Inline_Inline - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - InlineEntity = new OwnedEntity_Owns_Inline - { - IntColumn = 42, - StringColumn = "value 1", - InlineEntity = new OwnedEntity - { - IntColumn = 43, - StringColumn = "value 2" - } - } - }; - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_Inline_Inline.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_Inline_SeparateMany() - { - var testEntity = new TestEntity_Owns_Inline_SeparateMany - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - InlineEntity = new OwnedEntity_Owns_SeparateMany - { - IntColumn = 42, - StringColumn = "value 1", - SeparateEntities = new List - { - new() - { - IntColumn = 43, - StringColumn = "value 2" - }, - new() - { - IntColumn = 44, - StringColumn = "value 3" - } - } - } - }; - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_Inline_SeparateMany.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_Inline_SeparateOne() - { - var testEntity = new TestEntity_Owns_Inline_SeparateOne - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - InlineEntity = new OwnedEntity_Owns_SeparateOne - { - IntColumn = 42, - StringColumn = "value 1", - SeparateEntity = new() - { - IntColumn = 43, - StringColumn = "value 2" - } - } - }; - - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] - { - testEntity - }, new SqlServerBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_Inline_SeparateOne.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_SeparateMany_Inline() - { - var testEntity = new TestEntity_Owns_SeparateMany_Inline - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntities = new List - { - new() - { - IntColumn = 42, - StringColumn = "value 1", - InlineEntity = new OwnedEntity - { - IntColumn = 43, - StringColumn = "value 2" - } - }, - new() - { - IntColumn = 44, - StringColumn = "value 3", - InlineEntity = new OwnedEntity - { - IntColumn = 45, - StringColumn = "value 4" - } - } - } - }; - - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateMany_Inline.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } - - [Fact] - public async Task Should_throw_on_insert_of_TestEntity_Owns_SeparateMany_SeparateMany() - { - var testEntity = new TestEntity_Owns_SeparateMany_SeparateMany - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntities = new List - { - new() - { - IntColumn = 42, - StringColumn = "value 1", - SeparateEntities = new List - { - new() - { - IntColumn = 43, - StringColumn = "value 2" - }, - new() - { - IntColumn = 44, - StringColumn = "value 3" - } - } - }, - new() - { - IntColumn = 45, - StringColumn = "value 4", - SeparateEntities = new List - { - new() - { - IntColumn = 46, - StringColumn = "value 5" - }, - new() - { - IntColumn = 47, - StringColumn = "value 6" - } - } - } - } - }; - - ActDbContext.Add(testEntity); - - await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions())) - .Should().ThrowAsync().WithMessage("Non-inlined (i.e. with its own table) nested owned type 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany.SeparateEntities#OwnedEntity_Owns_SeparateMany.SeparateEntities' inside another owned type collection 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany.SeparateEntities' is not supported."); - } - - [Fact] - public async Task Should_throw_on_insert_of_TestEntity_Owns_SeparateMany_SeparateOne() - { - var testEntity = new TestEntity_Owns_SeparateMany_SeparateOne - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntities = new List - { - new() - { - IntColumn = 42, - StringColumn = "value 1", - SeparateEntity = new() - { - IntColumn = 43, - StringColumn = "value 2" - } - }, - new() - { - IntColumn = 45, - StringColumn = "value 4", - SeparateEntity = new() - { - IntColumn = 46, - StringColumn = "value 5" - } - } - } - }; - - ActDbContext.Add(testEntity); - - await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions())) - .Should().ThrowAsync().WithMessage("Non-inlined (i.e. with its own table) nested owned type 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne.SeparateEntities#OwnedEntity_Owns_SeparateOne.SeparateEntity' inside another owned type collection 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne.SeparateEntities' is not supported."); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_SeparateOne_Inline() - { - var testEntity = new TestEntity_Owns_SeparateOne_Inline - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntity = new() - { - IntColumn = 42, - StringColumn = "value 1", - InlineEntity = new OwnedEntity - { - IntColumn = 43, - StringColumn = "value 2" - } - } - }; - - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne_Inline.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_SeparateOne_SeparateMany() - { - var testEntity = new TestEntity_Owns_SeparateOne_SeparateMany - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntity = new() - { - IntColumn = 42, - StringColumn = "value 1", - SeparateEntities = new List - { - new() - { - IntColumn = 43, - StringColumn = "value 2" - }, - new() - { - IntColumn = 44, - StringColumn = "value 3" - } - } - } - }; - - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne_SeparateMany.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_SeparateOne_SeparateOne() - { - var testEntity = new TestEntity_Owns_SeparateOne_SeparateOne - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntity = new() - { - IntColumn = 42, - StringColumn = "value 1", - SeparateEntity = new() - { - IntColumn = 43, - StringColumn = "value 2" - } - } - }; - - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne_SeparateOne.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } - - [Fact] - public async Task Should_insert_TestEntity_with_ComplexType() - { - var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - new BoundaryValueObject(2, 5)); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } -} +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Thinktecture.EntityFrameworkCore.TempTables; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqlServerBulkOperationExecutorTests; + +// ReSharper disable once InconsistentNaming +public class BulkInsertAsync : IntegrationTestsBase +{ + private SqlServerBulkOperationExecutor? _sut; + + private SqlServerBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); + + public BulkInsertAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_throw_when_trying_to_insert_into_pure_temp_table_entity() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await SUT.Invoking(sut => sut.BulkInsertAsync(new List> { new(0) }, new SqlServerBulkInsertOptions())) + .Should().ThrowAsync() + .WithMessage("The provided type 'TempTable' is not part of the provided Entity Framework model. (Parameter 'type')"); + } + + [Fact] + public async Task Should_insert_column_with_converter() + { + var entities = new List { new() { ConvertibleClass = new ConvertibleClass(42), RequiredName = "RequiredName" } }; + + await SUT.BulkInsertAsync(entities, new SqlServerBulkInsertOptions()); + + var entity = AssertDbContext.TestEntities.Single(); + + entity.ConvertibleClass.Should().NotBeNull(); + entity.ConvertibleClass!.Key.Should().Be(42); + } + + [Fact] + public async Task Should_insert_entities() + { + var testEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + + var testEntities = new[] { testEntity }; + + await SUT.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1) + .And.Subject.First() + .Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }); + } + + [Fact] + public async Task Should_insert_private_property() + { + var testEntity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + testEntity.SetPrivateField(3); + + var testEntities = new[] { testEntity }; + + await SUT.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions()); + + var loadedEntity = await AssertDbContext.TestEntities.FirstOrDefaultAsync(); + loadedEntity!.GetPrivateField().Should().Be(3); + } + + [Fact] + public async Task Should_insert_shadow_properties() + { + var testEntity = new TestEntityWithShadowProperties { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866") }; + ActDbContext.Entry(testEntity).Property("ShadowStringProperty").CurrentValue = "value"; + ActDbContext.Entry(testEntity).Property("ShadowIntProperty").CurrentValue = 42; + + var testEntities = new[] { testEntity }; + + await SUT.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions()); + + var loadedEntity = await AssertDbContext.TestEntitiesWithShadowProperties.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + AssertDbContext.Entry(loadedEntity!).Property("ShadowStringProperty").CurrentValue.Should().Be("value"); + AssertDbContext.Entry(loadedEntity!).Property("ShadowIntProperty").CurrentValue.Should().Be(42); + } + + [Fact] + public async Task Should_throw_because_sqlbulkcopy_dont_support_null_for_NOT_NULL_despite_sql_default_value() + { + var testEntity = new TestEntityWithSqlDefaultValues { String = null! }; + var testEntities = new[] { testEntity }; + + await SUT.Awaiting(sut => sut.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions())) + .Should().ThrowAsync() + .WithMessage("Column 'String' does not allow DBNull.Value."); + } + + [Fact] + public async Task Should_write_not_nullable_structs_as_is_despite_sql_default_value() + { + var testEntity = new TestEntityWithSqlDefaultValues + { + Id = Guid.Empty, + Int = 0, + String = null!, + NullableInt = null, + NullableString = null + }; + var testEntities = new[] { testEntity }; + + var options = new SqlServerBulkInsertOptions + { + // we skip TestEntityWithSqlDefaultValues.String + PropertiesToInsert = IEntityPropertiesProvider.Include(e => new + { + e.Id, + e.Int, + e.NullableInt, + e.NullableString + }) + }; + + await SUT.BulkInsertAsync(testEntities, options); + + var loadedEntity = await AssertDbContext.TestEntitiesWithDefaultValues.FirstOrDefaultAsync(); + loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues + { + Id = Guid.Empty, // persisted as-is + Int = 0, // persisted as-is + NullableInt = 2, // DEFAULT value constraint + String = "3", // DEFAULT value constraint + NullableString = "4" // DEFAULT value constraint + }); + } + + [Fact] + public async Task Should_throw_because_sqlbulkcopy_dont_support_null_for_NOT_NULL_despite_dotnet_default_value() + { + var testEntity = new TestEntityWithDotnetDefaultValues { String = null! }; + var testEntities = new[] { testEntity }; + + await SUT.Awaiting(sut => sut.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions())) + .Should().ThrowAsync() + .WithMessage("Column 'String' does not allow DBNull.Value."); + } + + [Fact] + public async Task Should_write_not_nullable_structs_as_is_despite_dotnet_default_value() + { + var testEntity = new TestEntityWithDotnetDefaultValues + { + Id = Guid.Empty, + Int = 0, + String = null!, + NullableInt = null, + NullableString = null + }; + var testEntities = new[] { testEntity }; + + var options = new SqlServerBulkInsertOptions + { + // we skip TestEntityWithDefaultValues.String + PropertiesToInsert = IEntityPropertiesProvider.Include(e => new + { + e.Id, + e.Int, + e.NullableInt, + e.NullableString + }) + }; + + await SUT.BulkInsertAsync(testEntities, options); + + var loadedEntity = await AssertDbContext.TestEntitiesWithDotnetDefaultValues.FirstOrDefaultAsync(); + loadedEntity.Should().BeEquivalentTo(new TestEntityWithDotnetDefaultValues + { + Id = Guid.Empty, // persisted as-is + Int = 0, // persisted as-is + NullableInt = 2, // DEFAULT value constraint + String = "3", // DEFAULT value constraint + NullableString = "4" // DEFAULT value constraint + }); + } + + [Fact] + public async Task Should_insert_auto_increment_column_with_KeepIdentity() + { + var testEntity = new TestEntityWithAutoIncrement { Id = 42 }; + var testEntities = new[] { testEntity }; + + var options = new SqlServerBulkInsertOptions { SqlBulkCopyOptions = SqlBulkCopyOptions.KeepIdentity }; + await SUT.BulkInsertAsync(testEntities, options); + + var loadedEntity = await AssertDbContext.TestEntitiesWithAutoIncrement.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + loadedEntity!.Id.Should().Be(42); + } + + [Fact] + public async Task Should_ignore_auto_increment_column_without_KeepIdentity() + { + var testEntity = new TestEntityWithAutoIncrement { Id = 42, Name = "value" }; + var testEntities = new[] { testEntity }; + + await SUT.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions()); + + var loadedEntity = await AssertDbContext.TestEntitiesWithAutoIncrement.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + loadedEntity!.Id.Should().NotBe(0); + loadedEntity.Name.Should().Be("value"); + } + + [Fact] + public async Task Should_ignore_RowVersion() + { + var testEntity = new TestEntityWithRowVersion { Id = new Guid("EBC95620-4D80-4318-9B92-AD7528B2965C"), RowVersion = Int32.MaxValue }; + var testEntities = new[] { testEntity }; + + await SUT.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions()); + + var loadedEntity = await AssertDbContext.TestEntitiesWithRowVersion.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + loadedEntity!.Id.Should().Be(new Guid("EBC95620-4D80-4318-9B92-AD7528B2965C")); + loadedEntity.RowVersion.Should().NotBe(Int32.MaxValue); + } + + [Fact] + public async Task Should_insert_specified_properties_only() + { + var testEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42, + PropertyWithBackingField = 7 + }; + testEntity.SetPrivateField(3); + + await SUT.BulkInsertAsync(new[] { testEntity }, + new SqlServerBulkInsertOptions + { + PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) + }); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + RequiredName = "RequiredName", + Count = 42, + PropertyWithBackingField = 7 + }); + loadedEntity.GetPrivateField().Should().Be(3); + } + + [Fact] + public async Task Should_throw_entity_contains_inlined_owned_type() + { + var testEntity = new TestEntity_Owns_Inline + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + InlineEntity = null! + }; + + await ActDbContext.Awaiting(ctx => ctx.BulkInsertIntoTempTableAsync(new[] { testEntity })) + .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); + } + + [Fact] + public async Task Should_insert_inlined_owned_type_if_it_has_default_values_only() + { + var testEntity = new TestEntity_Owns_Inline + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + InlineEntity = new OwnedEntity() + }; + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_Inline.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity_Owns_Inline + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + InlineEntity = new OwnedEntity + { + IntColumn = 0, + StringColumn = null + } + }); + } + + [Fact] + public async Task Should_insert_inlined_owned_types() + { + var testEntity = new TestEntity_Owns_Inline + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + InlineEntity = new OwnedEntity + { + IntColumn = 42, + StringColumn = "value" + } + }; + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_Inline.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity_Owns_Inline + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + InlineEntity = new OwnedEntity + { + IntColumn = 42, + StringColumn = "value" + } + }); + } + + [Fact] + public async Task Should_throw_if_separated_owned_type_uses_shadow_property_id_and_is_detached() + { + var testEntity = new TestEntity_Owns_SeparateOne + { + Id = new Guid("7C00ABFE-875B-4396-BE51-3E898647A264"), + SeparateEntity = new OwnedEntity + { + IntColumn = 42, + StringColumn = "value" + } + }; + + await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions())) + .Should().ThrowAsync() + .WithMessage("The entity type 'OwnedEntity' uses a shared type and the supplied entity is currently not being tracked. To start tracking this entity, call '.Reference().TargetEntry' or '.Collection().FindEntry()' on the owner entry."); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_SeparateOne() + { + var testEntity = new TestEntity_Owns_SeparateOne + { + Id = new Guid("7C00ABFE-875B-4396-BE51-3E898647A264"), + SeparateEntity = new OwnedEntity + { + IntColumn = 42, + StringColumn = "value" + } + }; + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(testEntity); + } + + [Fact] + public async Task Should_throw_if_separated_owned_types_uses_shadow_property_id_and_is_detached() + { + var testEntity = new TestEntity_Owns_SeparateMany + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntities = new List + { + new() + { + IntColumn = 42, + StringColumn = "value 1" + } + } + }; + + await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions())) + .Should().ThrowAsync() + .WithMessage("The entity type 'OwnedEntity' uses a shared type and the supplied entity is currently not being tracked. To start tracking this entity, call '.Reference().TargetEntry' or '.Collection().FindEntry()' on the owner entry."); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_SeparateMany() + { + var testEntity = new TestEntity_Owns_SeparateMany + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntities = new List + { + new() + { + IntColumn = 42, + StringColumn = "value 1" + }, + new() + { + IntColumn = 43, + StringColumn = "value 2" + } + } + }; + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateMany.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(testEntity); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_Inline_Inline() + { + var testEntity = new TestEntity_Owns_Inline_Inline + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + InlineEntity = new OwnedEntity_Owns_Inline + { + IntColumn = 42, + StringColumn = "value 1", + InlineEntity = new OwnedEntity + { + IntColumn = 43, + StringColumn = "value 2" + } + } + }; + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_Inline_Inline.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_Inline_SeparateMany() + { + var testEntity = new TestEntity_Owns_Inline_SeparateMany + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + InlineEntity = new OwnedEntity_Owns_SeparateMany + { + IntColumn = 42, + StringColumn = "value 1", + SeparateEntities = new List + { + new() + { + IntColumn = 43, + StringColumn = "value 2" + }, + new() + { + IntColumn = 44, + StringColumn = "value 3" + } + } + } + }; + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_Inline_SeparateMany.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_Inline_SeparateOne() + { + var testEntity = new TestEntity_Owns_Inline_SeparateOne + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + InlineEntity = new OwnedEntity_Owns_SeparateOne + { + IntColumn = 42, + StringColumn = "value 1", + SeparateEntity = new() + { + IntColumn = 43, + StringColumn = "value 2" + } + } + }; + + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] + { + testEntity + }, new SqlServerBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_Inline_SeparateOne.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_SeparateMany_Inline() + { + var testEntity = new TestEntity_Owns_SeparateMany_Inline + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntities = new List + { + new() + { + IntColumn = 42, + StringColumn = "value 1", + InlineEntity = new OwnedEntity + { + IntColumn = 43, + StringColumn = "value 2" + } + }, + new() + { + IntColumn = 44, + StringColumn = "value 3", + InlineEntity = new OwnedEntity + { + IntColumn = 45, + StringColumn = "value 4" + } + } + } + }; + + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateMany_Inline.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } + + [Fact] + public async Task Should_throw_on_insert_of_TestEntity_Owns_SeparateMany_SeparateMany() + { + var testEntity = new TestEntity_Owns_SeparateMany_SeparateMany + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntities = new List + { + new() + { + IntColumn = 42, + StringColumn = "value 1", + SeparateEntities = new List + { + new() + { + IntColumn = 43, + StringColumn = "value 2" + }, + new() + { + IntColumn = 44, + StringColumn = "value 3" + } + } + }, + new() + { + IntColumn = 45, + StringColumn = "value 4", + SeparateEntities = new List + { + new() + { + IntColumn = 46, + StringColumn = "value 5" + }, + new() + { + IntColumn = 47, + StringColumn = "value 6" + } + } + } + } + }; + + ActDbContext.Add(testEntity); + + await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions())) + .Should().ThrowAsync().WithMessage("Non-inlined (i.e. with its own table) nested owned type 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany.SeparateEntities#OwnedEntity_Owns_SeparateMany.SeparateEntities' inside another owned type collection 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany.SeparateEntities' is not supported."); + } + + [Fact] + public async Task Should_throw_on_insert_of_TestEntity_Owns_SeparateMany_SeparateOne() + { + var testEntity = new TestEntity_Owns_SeparateMany_SeparateOne + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntities = new List + { + new() + { + IntColumn = 42, + StringColumn = "value 1", + SeparateEntity = new() + { + IntColumn = 43, + StringColumn = "value 2" + } + }, + new() + { + IntColumn = 45, + StringColumn = "value 4", + SeparateEntity = new() + { + IntColumn = 46, + StringColumn = "value 5" + } + } + } + }; + + ActDbContext.Add(testEntity); + + await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions())) + .Should().ThrowAsync().WithMessage("Non-inlined (i.e. with its own table) nested owned type 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne.SeparateEntities#OwnedEntity_Owns_SeparateOne.SeparateEntity' inside another owned type collection 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne.SeparateEntities' is not supported."); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_SeparateOne_Inline() + { + var testEntity = new TestEntity_Owns_SeparateOne_Inline + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntity = new() + { + IntColumn = 42, + StringColumn = "value 1", + InlineEntity = new OwnedEntity + { + IntColumn = 43, + StringColumn = "value 2" + } + } + }; + + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne_Inline.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_SeparateOne_SeparateMany() + { + var testEntity = new TestEntity_Owns_SeparateOne_SeparateMany + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntity = new() + { + IntColumn = 42, + StringColumn = "value 1", + SeparateEntities = new List + { + new() + { + IntColumn = 43, + StringColumn = "value 2" + }, + new() + { + IntColumn = 44, + StringColumn = "value 3" + } + } + } + }; + + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne_SeparateMany.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_SeparateOne_SeparateOne() + { + var testEntity = new TestEntity_Owns_SeparateOne_SeparateOne + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntity = new() + { + IntColumn = 42, + StringColumn = "value 1", + SeparateEntity = new() + { + IntColumn = 43, + StringColumn = "value 2" + } + } + }; + + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne_SeparateOne.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } + + [Fact] + public async Task Should_insert_TestEntity_with_ComplexType() + { + var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + new BoundaryValueObject(2, 5)); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/BulkInsertOrUpdateAsync.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/BulkInsertOrUpdateAsync.cs index f3208583..5033dfb6 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/BulkInsertOrUpdateAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/BulkInsertOrUpdateAsync.cs @@ -1,441 +1,441 @@ -using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqlServerBulkOperationExecutorTests; - -// ReSharper disable once InconsistentNaming -public class BulkInsertOrUpdateAsync : IntegrationTestsBase -{ - private SqlServerBulkOperationExecutor? _sut; - - private SqlServerBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); - - public BulkInsertOrUpdateAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_throw_when_entity_has_no_key() - { - await SUT.Invoking(sut => sut.BulkInsertOrUpdateAsync(new List { new() }, new SqlServerBulkInsertOrUpdateOptions())) - .Should().ThrowAsync() - .WithMessage("The entity 'Thinktecture.TestDatabaseContext.KeylessTestEntity' has no primary key. Please provide key properties to perform JOIN/match on."); - } - - [Fact] - public async Task Should_not_throw_if_entities_is_empty() - { - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new List(), new SqlServerBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(0); - } - - [Fact] - public async Task Should_insert_column_with_converter() - { - var existingEntity = new TestEntity { Id = new Guid("79DA4171-C90B-4A5D-B0B5-D0A1E1BDF966"), RequiredName = "RequiredName" }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - existingEntity.ConvertibleClass = new ConvertibleClass(43); - var newEntity = new TestEntity - { - Id = new Guid("3DAEA618-B732-4BCA-A5A1-D1E075022DEC"), - RequiredName = "RequiredName", - ConvertibleClass = new ConvertibleClass(42) - }; - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, new SqlServerBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(2); - - var entities = AssertDbContext.TestEntities.ToList(); - entities.Should().BeEquivalentTo(new[] - { - new TestEntity { Id = new Guid("79DA4171-C90B-4A5D-B0B5-D0A1E1BDF966"), RequiredName = "RequiredName", ConvertibleClass = new ConvertibleClass(43) }, - new TestEntity { Id = new Guid("3DAEA618-B732-4BCA-A5A1-D1E075022DEC"), RequiredName = "RequiredName", ConvertibleClass = new ConvertibleClass(42) } - }); - } - - [Fact] - public async Task Should_insert_entities() - { - var testEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - - await SUT.BulkInsertOrUpdateAsync(new[] { testEntity }, new SqlServerBulkInsertOrUpdateOptions()); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1) - .And.Subject.First() - .Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }); - } - - [Fact] - public async Task Should_insert_private_property() - { - var existingEntity = new TestEntity { Id = new Guid("7C200656-E633-4F93-9F73-C5C7628196DC"), RequiredName = "RequiredName" }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - existingEntity.SetPrivateField(1); - - var newEntity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - newEntity.SetPrivateField(3); - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { newEntity, existingEntity }, new SqlServerBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(2); - - var expectedExistingEntity = new TestEntity { Id = new Guid("7C200656-E633-4F93-9F73-C5C7628196DC"), RequiredName = "RequiredName" }; - expectedExistingEntity.SetPrivateField(1); - - var expectedNewEntity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - expectedNewEntity.SetPrivateField(3); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { expectedNewEntity, expectedExistingEntity }); - } - - [Fact] - public async Task Should_insert_shadow_properties() - { - var existingEntity = new TestEntityWithShadowProperties { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866") }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - ActDbContext.Entry(existingEntity).Property("ShadowStringProperty").CurrentValue = "value1"; - ActDbContext.Entry(existingEntity).Property("ShadowIntProperty").CurrentValue = 42; - - var newEntity = new TestEntityWithShadowProperties { Id = new Guid("884BB11B-088D-4BA5-B75D-C7F36B88378B") }; - ActDbContext.Entry(newEntity).Property("ShadowStringProperty").CurrentValue = "value2"; - ActDbContext.Entry(newEntity).Property("ShadowIntProperty").CurrentValue = 43; - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { newEntity, existingEntity }, new SqlServerBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(2); - - var loadedEntities = await AssertDbContext.TestEntitiesWithShadowProperties.ToListAsync(); - loadedEntities.Should().HaveCount(2); - - var existingEntry = AssertDbContext.Entry(loadedEntities.Single(e => e.Id == new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"))); - existingEntry.Property("ShadowStringProperty").CurrentValue.Should().Be("value1"); - existingEntry.Property("ShadowIntProperty").CurrentValue.Should().Be(42); - - var newEntry = AssertDbContext.Entry(loadedEntities.Single(e => e.Id == new Guid("884BB11B-088D-4BA5-B75D-C7F36B88378B"))); - newEntry.Property("ShadowStringProperty").CurrentValue.Should().Be("value2"); - newEntry.Property("ShadowIntProperty").CurrentValue.Should().Be(43); - } - - [Fact] - public async Task Should_throw_because_sqlbulkcopy_dont_support_null_for_NOT_NULL_despite_sql_default_value() - { - var testEntity = new TestEntityWithSqlDefaultValues { String = null! }; - var testEntities = new[] { testEntity }; - - await SUT.Awaiting(sut => sut.BulkInsertOrUpdateAsync(testEntities, new SqlServerBulkInsertOrUpdateOptions())) - .Should().ThrowAsync() - .WithMessage("Column 'String' does not allow DBNull.Value."); - } - - [Fact] - public async Task Should_throw_because_sqlbulkcopy_dont_support_null_for_NOT_NULL_despite_dotnet_default_value() - { - var testEntity = new TestEntityWithDotnetDefaultValues { String = null! }; - var testEntities = new[] { testEntity }; - - await SUT.Awaiting(sut => sut.BulkInsertOrUpdateAsync(testEntities, new SqlServerBulkInsertOrUpdateOptions())) - .Should().ThrowAsync() - .WithMessage("Column 'String' does not allow DBNull.Value."); - } - - [Fact] - public async Task Should_throw_when_trying_to_insert_entities_with_auto_increment_column_without_excluding_auto_increment_column() - { - var existingEntity = new TestEntityWithAutoIncrement { Name = "original value" }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - existingEntity.Name = "Name"; - - var newEntity = new TestEntityWithAutoIncrement { Name = "New Entity" }; - var testEntities = new[] { existingEntity, newEntity }; - - await SUT.Awaiting(sut => sut.BulkInsertOrUpdateAsync(testEntities, new SqlServerBulkInsertOrUpdateOptions())) - .Should().ThrowAsync().WithMessage("Cannot insert explicit value for identity column in table 'TestEntitiesWithAutoIncrement' when IDENTITY_INSERT is set to OFF."); - } - - [Fact] - public async Task Should_insert_and_update_entities_with_auto_increment_column_having_property_selector() - { - var existingEntity = new TestEntityWithAutoIncrement { Name = "original value" }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - existingEntity.Name = "Name"; - - var newEntity = new TestEntityWithAutoIncrement { Name = "New Entity" }; - var testEntities = new[] { existingEntity, newEntity }; - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(testEntities, new SqlServerBulkInsertOrUpdateOptions - { - PropertiesToInsert = IEntityPropertiesProvider.Include(entity => entity.Name) - }); - - affectedRows.Should().Be(2); - - var loadedEntities = await AssertDbContext.TestEntitiesWithAutoIncrement.ToListAsync(); - loadedEntities.Should().HaveCount(2); - loadedEntities.Should().AllSatisfy(e => e.Id.Should().NotBe(0)); - loadedEntities.Should().BeEquivalentTo(new[] - { - existingEntity, - new TestEntityWithAutoIncrement - { - Id = loadedEntities.Single(e => e.Id != existingEntity.Id).Id, - Name = "New Entity" - } - }); - } - - [Fact] - public async Task Should_insert_specified_properties_only() - { - var testEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42, - PropertyWithBackingField = 7 - }; - testEntity.SetPrivateField(3); - var testEntities = new[] { testEntity }; - - await SUT.BulkInsertOrUpdateAsync(testEntities, - new SqlServerBulkInsertOrUpdateOptions - { - PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) - }); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - RequiredName = "RequiredName", - Count = 42, - PropertyWithBackingField = 7 - }); - loadedEntity.GetPrivateField().Should().Be(3); - } - - [Fact] - public async Task Should_not_throw_if_key_property_is_not_present_in_PropertiesToUpdate() - { - var propertiesProvider = IEntityPropertiesProvider.Include(entity => new { entity.Name }); - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new List(), new SqlServerBulkInsertOrUpdateOptions - { - PropertiesToUpdate = propertiesProvider - }); - - affectedRows.Should().Be(0); - } - - [Fact] - public async Task Should_update_entities() - { - var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - entity_1.Name = "Name"; - entity_1.Count = 1; - entity_2.Name = "OtherName"; - entity_2.Count = 2; - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity_1, entity_2 }, new SqlServerBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(2); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); - } - - [Fact] - public async Task Should_update_entities_based_on_non_pk_property() - { - var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "value", RequiredName = "RequiredName" }; - var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), Name = null, RequiredName = "RequiredName" }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - entity_1.Name = null; - entity_1.Count = 1; - entity_2.Name = "value"; - entity_2.Count = 2; - - var properties = IEntityPropertiesProvider.Include(e => new { e.Count }); - var keyProperties = IEntityPropertiesProvider.Include(e => e.Name); - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity_1, entity_2 }, new SqlServerBulkInsertOrUpdateOptions { PropertiesToUpdate = properties, KeyProperties = keyProperties }); - - affectedRows.Should().Be(2); - - entity_1.Name = "value"; - entity_2.Name = null; - - entity_1.Count = 2; - entity_2.Count = 1; - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); - } - - [Fact] - public async Task Should_update_provided_entity_only() - { - var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - entity_1.Name = "Name"; - entity_1.Count = 1; - entity_2.Name = "OtherName"; - entity_2.Count = 2; - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity_1 }, new SqlServerBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(1); - - entity_2.Name = default; - entity_2.Count = default; - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); - } - - [Fact] - public async Task Should_write_not_nullable_structs_as_is_despite_sql_default_value() - { - var entity = new TestEntityWithSqlDefaultValues - { - Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), - Int = 1, - String = "value", - NullableInt = 2, - NullableString = "otherValue" - }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Int = 0; - entity.String = "other value"; - entity.NullableInt = null; - entity.NullableString = null; - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity }, new SqlServerBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntitiesWithDefaultValues.FirstOrDefaultAsync(); - loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues - { - Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), - Int = 0, // persisted as-is - NullableInt = 2, // DEFAULT value constraint - String = "other value", - NullableString = "4" // DEFAULT value constraint - }); - } - - [Fact] - public async Task Should_ignore_RowVersion() - { - var existingEntity = new TestEntityWithRowVersion { Id = new Guid("EBC95620-4D80-4318-9B92-AD7528B2965C") }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - var newEntity = new TestEntityWithRowVersion { Id = new Guid("7C1234E1-69EE-435D-99C0-119338280017"), RowVersion = Int32.MaxValue }; - existingEntity.RowVersion = Int32.MaxValue; - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, new SqlServerBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(2); - - var loadedEntities = await AssertDbContext.TestEntitiesWithRowVersion.ToListAsync(); - loadedEntities.Should().HaveCount(2); - loadedEntities.ForEach(e => e.RowVersion.Should().NotBe(Int32.MaxValue)); - } - - [Fact] - public async Task Should_update_specified_properties_only() - { - var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "original value", RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Name = "Name"; // this would not be updated - entity.Count = 42; - entity.PropertyWithBackingField = 7; - entity.SetPrivateField(3); - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity }, - new SqlServerBulkInsertOrUpdateOptions - { - PropertiesToUpdate = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) - }); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Count = 42, - PropertyWithBackingField = 7, - Name = "original value", - RequiredName = "RequiredName" - }); - loadedEntity.GetPrivateField().Should().Be(3); - } - - [Fact] - public async Task Should_insert_and_update_TestEntity_with_ComplexType() - { - // Arrange - var testEntity_1 = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - new BoundaryValueObject(2, 5)); - - ArrangeDbContext.Add(testEntity_1); - await ArrangeDbContext.SaveChangesAsync(); - - // Act - testEntity_1.Boundary = new BoundaryValueObject(10, 20); - var testEntity_2 = new TestEntityWithComplexType(new Guid("67A9500B-CF51-4A39-8C89-F2EBF7EDE84D"), - new BoundaryValueObject(3, 4)); - - await SUT.BulkInsertOrUpdateAsync(new[] { testEntity_1, testEntity_2 }, new SqlServerBulkInsertOrUpdateOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity_1, testEntity_2 }); - } -} +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqlServerBulkOperationExecutorTests; + +// ReSharper disable once InconsistentNaming +public class BulkInsertOrUpdateAsync : IntegrationTestsBase +{ + private SqlServerBulkOperationExecutor? _sut; + + private SqlServerBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); + + public BulkInsertOrUpdateAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_throw_when_entity_has_no_key() + { + await SUT.Invoking(sut => sut.BulkInsertOrUpdateAsync(new List { new() }, new SqlServerBulkInsertOrUpdateOptions())) + .Should().ThrowAsync() + .WithMessage("The entity 'Thinktecture.TestDatabaseContext.KeylessTestEntity' has no primary key. Please provide key properties to perform JOIN/match on."); + } + + [Fact] + public async Task Should_not_throw_if_entities_is_empty() + { + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new List(), new SqlServerBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(0); + } + + [Fact] + public async Task Should_insert_column_with_converter() + { + var existingEntity = new TestEntity { Id = new Guid("79DA4171-C90B-4A5D-B0B5-D0A1E1BDF966"), RequiredName = "RequiredName" }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + existingEntity.ConvertibleClass = new ConvertibleClass(43); + var newEntity = new TestEntity + { + Id = new Guid("3DAEA618-B732-4BCA-A5A1-D1E075022DEC"), + RequiredName = "RequiredName", + ConvertibleClass = new ConvertibleClass(42) + }; + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, new SqlServerBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(2); + + var entities = AssertDbContext.TestEntities.ToList(); + entities.Should().BeEquivalentTo(new[] + { + new TestEntity { Id = new Guid("79DA4171-C90B-4A5D-B0B5-D0A1E1BDF966"), RequiredName = "RequiredName", ConvertibleClass = new ConvertibleClass(43) }, + new TestEntity { Id = new Guid("3DAEA618-B732-4BCA-A5A1-D1E075022DEC"), RequiredName = "RequiredName", ConvertibleClass = new ConvertibleClass(42) } + }); + } + + [Fact] + public async Task Should_insert_entities() + { + var testEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + + await SUT.BulkInsertOrUpdateAsync(new[] { testEntity }, new SqlServerBulkInsertOrUpdateOptions()); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1) + .And.Subject.First() + .Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }); + } + + [Fact] + public async Task Should_insert_private_property() + { + var existingEntity = new TestEntity { Id = new Guid("7C200656-E633-4F93-9F73-C5C7628196DC"), RequiredName = "RequiredName" }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + existingEntity.SetPrivateField(1); + + var newEntity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + newEntity.SetPrivateField(3); + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { newEntity, existingEntity }, new SqlServerBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(2); + + var expectedExistingEntity = new TestEntity { Id = new Guid("7C200656-E633-4F93-9F73-C5C7628196DC"), RequiredName = "RequiredName" }; + expectedExistingEntity.SetPrivateField(1); + + var expectedNewEntity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + expectedNewEntity.SetPrivateField(3); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { expectedNewEntity, expectedExistingEntity }); + } + + [Fact] + public async Task Should_insert_shadow_properties() + { + var existingEntity = new TestEntityWithShadowProperties { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866") }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + ActDbContext.Entry(existingEntity).Property("ShadowStringProperty").CurrentValue = "value1"; + ActDbContext.Entry(existingEntity).Property("ShadowIntProperty").CurrentValue = 42; + + var newEntity = new TestEntityWithShadowProperties { Id = new Guid("884BB11B-088D-4BA5-B75D-C7F36B88378B") }; + ActDbContext.Entry(newEntity).Property("ShadowStringProperty").CurrentValue = "value2"; + ActDbContext.Entry(newEntity).Property("ShadowIntProperty").CurrentValue = 43; + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { newEntity, existingEntity }, new SqlServerBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(2); + + var loadedEntities = await AssertDbContext.TestEntitiesWithShadowProperties.ToListAsync(); + loadedEntities.Should().HaveCount(2); + + var existingEntry = AssertDbContext.Entry(loadedEntities.Single(e => e.Id == new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"))); + existingEntry.Property("ShadowStringProperty").CurrentValue.Should().Be("value1"); + existingEntry.Property("ShadowIntProperty").CurrentValue.Should().Be(42); + + var newEntry = AssertDbContext.Entry(loadedEntities.Single(e => e.Id == new Guid("884BB11B-088D-4BA5-B75D-C7F36B88378B"))); + newEntry.Property("ShadowStringProperty").CurrentValue.Should().Be("value2"); + newEntry.Property("ShadowIntProperty").CurrentValue.Should().Be(43); + } + + [Fact] + public async Task Should_throw_because_sqlbulkcopy_dont_support_null_for_NOT_NULL_despite_sql_default_value() + { + var testEntity = new TestEntityWithSqlDefaultValues { String = null! }; + var testEntities = new[] { testEntity }; + + await SUT.Awaiting(sut => sut.BulkInsertOrUpdateAsync(testEntities, new SqlServerBulkInsertOrUpdateOptions())) + .Should().ThrowAsync() + .WithMessage("Column 'String' does not allow DBNull.Value."); + } + + [Fact] + public async Task Should_throw_because_sqlbulkcopy_dont_support_null_for_NOT_NULL_despite_dotnet_default_value() + { + var testEntity = new TestEntityWithDotnetDefaultValues { String = null! }; + var testEntities = new[] { testEntity }; + + await SUT.Awaiting(sut => sut.BulkInsertOrUpdateAsync(testEntities, new SqlServerBulkInsertOrUpdateOptions())) + .Should().ThrowAsync() + .WithMessage("Column 'String' does not allow DBNull.Value."); + } + + [Fact] + public async Task Should_throw_when_trying_to_insert_entities_with_auto_increment_column_without_excluding_auto_increment_column() + { + var existingEntity = new TestEntityWithAutoIncrement { Name = "original value" }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + existingEntity.Name = "Name"; + + var newEntity = new TestEntityWithAutoIncrement { Name = "New Entity" }; + var testEntities = new[] { existingEntity, newEntity }; + + await SUT.Awaiting(sut => sut.BulkInsertOrUpdateAsync(testEntities, new SqlServerBulkInsertOrUpdateOptions())) + .Should().ThrowAsync().WithMessage("Cannot insert explicit value for identity column in table 'TestEntitiesWithAutoIncrement' when IDENTITY_INSERT is set to OFF."); + } + + [Fact] + public async Task Should_insert_and_update_entities_with_auto_increment_column_having_property_selector() + { + var existingEntity = new TestEntityWithAutoIncrement { Name = "original value" }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + existingEntity.Name = "Name"; + + var newEntity = new TestEntityWithAutoIncrement { Name = "New Entity" }; + var testEntities = new[] { existingEntity, newEntity }; + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(testEntities, new SqlServerBulkInsertOrUpdateOptions + { + PropertiesToInsert = IEntityPropertiesProvider.Include(entity => entity.Name) + }); + + affectedRows.Should().Be(2); + + var loadedEntities = await AssertDbContext.TestEntitiesWithAutoIncrement.ToListAsync(); + loadedEntities.Should().HaveCount(2); + loadedEntities.Should().AllSatisfy(e => e.Id.Should().NotBe(0)); + loadedEntities.Should().BeEquivalentTo(new[] + { + existingEntity, + new TestEntityWithAutoIncrement + { + Id = loadedEntities.Single(e => e.Id != existingEntity.Id).Id, + Name = "New Entity" + } + }); + } + + [Fact] + public async Task Should_insert_specified_properties_only() + { + var testEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42, + PropertyWithBackingField = 7 + }; + testEntity.SetPrivateField(3); + var testEntities = new[] { testEntity }; + + await SUT.BulkInsertOrUpdateAsync(testEntities, + new SqlServerBulkInsertOrUpdateOptions + { + PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) + }); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + RequiredName = "RequiredName", + Count = 42, + PropertyWithBackingField = 7 + }); + loadedEntity.GetPrivateField().Should().Be(3); + } + + [Fact] + public async Task Should_not_throw_if_key_property_is_not_present_in_PropertiesToUpdate() + { + var propertiesProvider = IEntityPropertiesProvider.Include(entity => new { entity.Name }); + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new List(), new SqlServerBulkInsertOrUpdateOptions + { + PropertiesToUpdate = propertiesProvider + }); + + affectedRows.Should().Be(0); + } + + [Fact] + public async Task Should_update_entities() + { + var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + entity_1.Name = "Name"; + entity_1.Count = 1; + entity_2.Name = "OtherName"; + entity_2.Count = 2; + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity_1, entity_2 }, new SqlServerBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(2); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); + } + + [Fact] + public async Task Should_update_entities_based_on_non_pk_property() + { + var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "value", RequiredName = "RequiredName" }; + var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), Name = null, RequiredName = "RequiredName" }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + entity_1.Name = null; + entity_1.Count = 1; + entity_2.Name = "value"; + entity_2.Count = 2; + + var properties = IEntityPropertiesProvider.Include(e => new { e.Count }); + var keyProperties = IEntityPropertiesProvider.Include(e => e.Name); + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity_1, entity_2 }, new SqlServerBulkInsertOrUpdateOptions { PropertiesToUpdate = properties, KeyProperties = keyProperties }); + + affectedRows.Should().Be(2); + + entity_1.Name = "value"; + entity_2.Name = null; + + entity_1.Count = 2; + entity_2.Count = 1; + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); + } + + [Fact] + public async Task Should_update_provided_entity_only() + { + var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + entity_1.Name = "Name"; + entity_1.Count = 1; + entity_2.Name = "OtherName"; + entity_2.Count = 2; + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity_1 }, new SqlServerBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(1); + + entity_2.Name = default; + entity_2.Count = default; + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); + } + + [Fact] + public async Task Should_write_not_nullable_structs_as_is_despite_sql_default_value() + { + var entity = new TestEntityWithSqlDefaultValues + { + Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), + Int = 1, + String = "value", + NullableInt = 2, + NullableString = "otherValue" + }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Int = 0; + entity.String = "other value"; + entity.NullableInt = null; + entity.NullableString = null; + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity }, new SqlServerBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntitiesWithDefaultValues.FirstOrDefaultAsync(); + loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues + { + Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), + Int = 0, // persisted as-is + NullableInt = 2, // DEFAULT value constraint + String = "other value", + NullableString = "4" // DEFAULT value constraint + }); + } + + [Fact] + public async Task Should_ignore_RowVersion() + { + var existingEntity = new TestEntityWithRowVersion { Id = new Guid("EBC95620-4D80-4318-9B92-AD7528B2965C") }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + var newEntity = new TestEntityWithRowVersion { Id = new Guid("7C1234E1-69EE-435D-99C0-119338280017"), RowVersion = Int32.MaxValue }; + existingEntity.RowVersion = Int32.MaxValue; + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, new SqlServerBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(2); + + var loadedEntities = await AssertDbContext.TestEntitiesWithRowVersion.ToListAsync(); + loadedEntities.Should().HaveCount(2); + loadedEntities.ForEach(e => e.RowVersion.Should().NotBe(Int32.MaxValue)); + } + + [Fact] + public async Task Should_update_specified_properties_only() + { + var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "original value", RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Name = "Name"; // this would not be updated + entity.Count = 42; + entity.PropertyWithBackingField = 7; + entity.SetPrivateField(3); + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity }, + new SqlServerBulkInsertOrUpdateOptions + { + PropertiesToUpdate = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) + }); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Count = 42, + PropertyWithBackingField = 7, + Name = "original value", + RequiredName = "RequiredName" + }); + loadedEntity.GetPrivateField().Should().Be(3); + } + + [Fact] + public async Task Should_insert_and_update_TestEntity_with_ComplexType() + { + // Arrange + var testEntity_1 = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + new BoundaryValueObject(2, 5)); + + ArrangeDbContext.Add(testEntity_1); + await ArrangeDbContext.SaveChangesAsync(); + + // Act + testEntity_1.Boundary = new BoundaryValueObject(10, 20); + var testEntity_2 = new TestEntityWithComplexType(new Guid("67A9500B-CF51-4A39-8C89-F2EBF7EDE84D"), + new BoundaryValueObject(3, 4)); + + await SUT.BulkInsertOrUpdateAsync(new[] { testEntity_1, testEntity_2 }, new SqlServerBulkInsertOrUpdateOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity_1, testEntity_2 }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/BulkUpdateAsync.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/BulkUpdateAsync.cs index 4950fc5b..80a447e4 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/BulkUpdateAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/BulkUpdateAsync.cs @@ -1,367 +1,367 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Thinktecture.TestDatabaseContext; - -// ReSharper disable InconsistentNaming - -namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqlServerBulkOperationExecutorTests; - -// ReSharper disable once InconsistentNaming -public class BulkUpdateAsync : IntegrationTestsBase -{ - private SqlServerBulkOperationExecutor? _sut; - - private SqlServerBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); - - public BulkUpdateAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_not_throw_if_entities_is_empty() - { - var affectedRows = await SUT.BulkUpdateAsync(new List(), new SqlServerBulkUpdateOptions()); - - affectedRows.Should().Be(0); - } - - [Fact] - public async Task Should_not_throw_if_key_property_is_not_present_in_PropertiesToUpdate() - { - var propertiesProvider = IEntityPropertiesProvider.Include(entity => new { entity.Name }); - - var affectedRows = await SUT.BulkUpdateAsync(new List(), new SqlServerBulkUpdateOptions - { - PropertiesToUpdate = propertiesProvider - }); - - affectedRows.Should().Be(0); - } - - [Fact] - public async Task Should_update_column_with_converter() - { - var entity = new TestEntity { RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.ConvertibleClass = new ConvertibleClass(43); - - var affectedRows = await SUT.BulkUpdateAsync(new List { entity }, new SqlServerBulkUpdateOptions()); - - affectedRows.Should().Be(1); - - var loadedEntity = AssertDbContext.TestEntities.Single(); - loadedEntity.ConvertibleClass.Should().NotBeNull(); - loadedEntity.ConvertibleClass!.Key.Should().Be(43); - } - - [Fact] - public async Task Should_return_0_if_no_rows_match() - { - var entity = new TestEntity { Id = new Guid("5B9587A3-2312-43DF-9681-38EC22AD8606"), RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - var affectedRows = await SUT.BulkUpdateAsync(new List { new() { Id = new Guid("506E664A-9ADC-4221-9577-71DCFD73DE64"), RequiredName = "RequiredName" } }, new SqlServerBulkUpdateOptions()); - - affectedRows.Should().Be(0); - } - - [Fact] - public async Task Should_update_entities() - { - var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - entity_1.Name = "Name"; - entity_1.Count = 1; - entity_2.Name = "OtherName"; - entity_2.Count = 2; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity_1, entity_2 }, new SqlServerBulkUpdateOptions()); - - affectedRows.Should().Be(2); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); - } - - [Fact] - public async Task Should_update_entities_based_on_non_pk_property() - { - var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "value", RequiredName = "RequiredName" }; - var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), Name = null, RequiredName = "RequiredName" }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - entity_1.Name = null; - entity_1.Count = 1; - entity_2.Name = "value"; - entity_2.Count = 2; - - var properties = IEntityPropertiesProvider.Include(e => new { e.Count }); - var keyProperties = IEntityPropertiesProvider.Include(e => e.Name); - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity_1, entity_2 }, new SqlServerBulkUpdateOptions { PropertiesToUpdate = properties, KeyProperties = keyProperties }); - - affectedRows.Should().Be(2); - - entity_1.Name = "value"; - entity_2.Name = null; - - entity_1.Count = 2; - entity_2.Count = 1; - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); - } - - [Fact] - public async Task Should_update_provided_entity_only() - { - var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - entity_1.Name = "Name"; - entity_1.Count = 1; - entity_2.Name = "OtherName"; - entity_2.Count = 2; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity_1 }, new SqlServerBulkUpdateOptions()); - - affectedRows.Should().Be(1); - - entity_2.Name = default; - entity_2.Count = default; - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); - } - - [Fact] - public async Task Should_update_private_property() - { - var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.SetPrivateField(1); - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqlServerBulkUpdateOptions()); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntities.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - loadedEntity!.GetPrivateField().Should().Be(1); - } - - [Fact] - public async Task Should_update_shadow_properties() - { - var entity = new TestEntityWithShadowProperties { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866") }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - ActDbContext.Entry(entity).Property("ShadowStringProperty").CurrentValue = "value"; - ActDbContext.Entry(entity).Property("ShadowIntProperty").CurrentValue = 42; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqlServerBulkUpdateOptions()); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntitiesWithShadowProperties.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - AssertDbContext.Entry(loadedEntity!).Property("ShadowStringProperty").CurrentValue.Should().Be("value"); - AssertDbContext.Entry(loadedEntity!).Property("ShadowIntProperty").CurrentValue.Should().Be(42); - } - - [Fact] - public async Task Should_write_not_nullable_structs_as_is_despite_sql_default_value() - { - var entity = new TestEntityWithSqlDefaultValues - { - Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), - Int = 1, - String = "value", - NullableInt = 2, - NullableString = "otherValue" - }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Int = 0; - entity.String = "other value"; - entity.NullableInt = null; - entity.NullableString = null; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqlServerBulkUpdateOptions()); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntitiesWithDefaultValues.FirstOrDefaultAsync(); - loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues - { - Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), - Int = 0, // persisted as-is - NullableInt = 2, // DEFAULT value constraint - String = "other value", - NullableString = "4" // DEFAULT value constraint - }); - } - - [Fact] - public async Task Should_ignore_RowVersion() - { - var entity = new TestEntityWithRowVersion { Id = new Guid("EBC95620-4D80-4318-9B92-AD7528B2965C") }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.RowVersion = Int32.MaxValue; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqlServerBulkUpdateOptions()); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntitiesWithRowVersion.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - loadedEntity!.Id.Should().Be(new Guid("EBC95620-4D80-4318-9B92-AD7528B2965C")); - loadedEntity.RowVersion.Should().NotBe(Int32.MaxValue); - } - - [Fact] - public async Task Should_update_specified_properties_only() - { - var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "original value", RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Name = "Name"; // this would not be updated - entity.Count = 42; - entity.PropertyWithBackingField = 7; - entity.SetPrivateField(3); - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, - new SqlServerBulkUpdateOptions - { - PropertiesToUpdate = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) - }); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Count = 42, - PropertyWithBackingField = 7, - Name = "original value", - RequiredName = "RequiredName" - }); - loadedEntity.GetPrivateField().Should().Be(3); - } - - [Fact] - public async Task Should_update_entity_with_auto_increment() - { - var entity = new TestEntityWithAutoIncrement { Name = "original value" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Name = "Name"; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, - new SqlServerBulkUpdateOptions()); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntitiesWithAutoIncrement.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Id.Should().NotBe(0); - loadedEntity.Should().BeEquivalentTo(new TestEntityWithAutoIncrement - { - Id = loadedEntity.Id, - Name = "Name" - }); - } - - [Fact] - public async Task Should_update_entity_with_auto_increment_having_custom_property_selector() - { - var entity = new TestEntityWithAutoIncrement { Name = "original value" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Name = "Name"; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, - new SqlServerBulkUpdateOptions - { - KeyProperties = IEntityPropertiesProvider.Include(e => e.Id), - PropertiesToUpdate = IEntityPropertiesProvider.Include(e => new - { - e.Id, - e.Name - }) - }); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntitiesWithAutoIncrement.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Id.Should().NotBe(0); - loadedEntity.Should().BeEquivalentTo(new TestEntityWithAutoIncrement - { - Id = entity.Id, - Name = "Name" - }); - } - - [Fact] - public async Task Should_update_property_of_base_class() - { - var entity = new TestEntityWithBaseClass { Id = new Guid("3D3AECE7-3B5A-48C6-9C8C-CAB7FFE2120D"), Name = "Initial" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Name = "changed"; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqlServerBulkUpdateOptions - { - PropertiesToUpdate = IEntityPropertiesProvider.Include(e => e.Name) - }); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntitiesWithBaseClass.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - loadedEntity!.Name.Should().Be("changed"); - } - - [Fact] - public async Task Should_insert_and_update_TestEntity_with_ComplexType() - { - // Arrange - var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - new BoundaryValueObject(2, 5)); - - ArrangeDbContext.Add(testEntity); - await ArrangeDbContext.SaveChangesAsync(); - - // Act - testEntity.Boundary = new BoundaryValueObject(10, 20); - - await SUT.BulkUpdateAsync(new[] { testEntity }, new SqlServerBulkUpdateOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; +using Thinktecture.TestDatabaseContext; + +// ReSharper disable InconsistentNaming + +namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqlServerBulkOperationExecutorTests; + +// ReSharper disable once InconsistentNaming +public class BulkUpdateAsync : IntegrationTestsBase +{ + private SqlServerBulkOperationExecutor? _sut; + + private SqlServerBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); + + public BulkUpdateAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_not_throw_if_entities_is_empty() + { + var affectedRows = await SUT.BulkUpdateAsync(new List(), new SqlServerBulkUpdateOptions()); + + affectedRows.Should().Be(0); + } + + [Fact] + public async Task Should_not_throw_if_key_property_is_not_present_in_PropertiesToUpdate() + { + var propertiesProvider = IEntityPropertiesProvider.Include(entity => new { entity.Name }); + + var affectedRows = await SUT.BulkUpdateAsync(new List(), new SqlServerBulkUpdateOptions + { + PropertiesToUpdate = propertiesProvider + }); + + affectedRows.Should().Be(0); + } + + [Fact] + public async Task Should_update_column_with_converter() + { + var entity = new TestEntity { RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.ConvertibleClass = new ConvertibleClass(43); + + var affectedRows = await SUT.BulkUpdateAsync(new List { entity }, new SqlServerBulkUpdateOptions()); + + affectedRows.Should().Be(1); + + var loadedEntity = AssertDbContext.TestEntities.Single(); + loadedEntity.ConvertibleClass.Should().NotBeNull(); + loadedEntity.ConvertibleClass!.Key.Should().Be(43); + } + + [Fact] + public async Task Should_return_0_if_no_rows_match() + { + var entity = new TestEntity { Id = new Guid("5B9587A3-2312-43DF-9681-38EC22AD8606"), RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + var affectedRows = await SUT.BulkUpdateAsync(new List { new() { Id = new Guid("506E664A-9ADC-4221-9577-71DCFD73DE64"), RequiredName = "RequiredName" } }, new SqlServerBulkUpdateOptions()); + + affectedRows.Should().Be(0); + } + + [Fact] + public async Task Should_update_entities() + { + var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + entity_1.Name = "Name"; + entity_1.Count = 1; + entity_2.Name = "OtherName"; + entity_2.Count = 2; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity_1, entity_2 }, new SqlServerBulkUpdateOptions()); + + affectedRows.Should().Be(2); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); + } + + [Fact] + public async Task Should_update_entities_based_on_non_pk_property() + { + var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "value", RequiredName = "RequiredName" }; + var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), Name = null, RequiredName = "RequiredName" }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + entity_1.Name = null; + entity_1.Count = 1; + entity_2.Name = "value"; + entity_2.Count = 2; + + var properties = IEntityPropertiesProvider.Include(e => new { e.Count }); + var keyProperties = IEntityPropertiesProvider.Include(e => e.Name); + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity_1, entity_2 }, new SqlServerBulkUpdateOptions { PropertiesToUpdate = properties, KeyProperties = keyProperties }); + + affectedRows.Should().Be(2); + + entity_1.Name = "value"; + entity_2.Name = null; + + entity_1.Count = 2; + entity_2.Count = 1; + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); + } + + [Fact] + public async Task Should_update_provided_entity_only() + { + var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + entity_1.Name = "Name"; + entity_1.Count = 1; + entity_2.Name = "OtherName"; + entity_2.Count = 2; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity_1 }, new SqlServerBulkUpdateOptions()); + + affectedRows.Should().Be(1); + + entity_2.Name = default; + entity_2.Count = default; + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); + } + + [Fact] + public async Task Should_update_private_property() + { + var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.SetPrivateField(1); + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqlServerBulkUpdateOptions()); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntities.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + loadedEntity!.GetPrivateField().Should().Be(1); + } + + [Fact] + public async Task Should_update_shadow_properties() + { + var entity = new TestEntityWithShadowProperties { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866") }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + ActDbContext.Entry(entity).Property("ShadowStringProperty").CurrentValue = "value"; + ActDbContext.Entry(entity).Property("ShadowIntProperty").CurrentValue = 42; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqlServerBulkUpdateOptions()); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntitiesWithShadowProperties.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + AssertDbContext.Entry(loadedEntity!).Property("ShadowStringProperty").CurrentValue.Should().Be("value"); + AssertDbContext.Entry(loadedEntity!).Property("ShadowIntProperty").CurrentValue.Should().Be(42); + } + + [Fact] + public async Task Should_write_not_nullable_structs_as_is_despite_sql_default_value() + { + var entity = new TestEntityWithSqlDefaultValues + { + Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), + Int = 1, + String = "value", + NullableInt = 2, + NullableString = "otherValue" + }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Int = 0; + entity.String = "other value"; + entity.NullableInt = null; + entity.NullableString = null; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqlServerBulkUpdateOptions()); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntitiesWithDefaultValues.FirstOrDefaultAsync(); + loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues + { + Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), + Int = 0, // persisted as-is + NullableInt = 2, // DEFAULT value constraint + String = "other value", + NullableString = "4" // DEFAULT value constraint + }); + } + + [Fact] + public async Task Should_ignore_RowVersion() + { + var entity = new TestEntityWithRowVersion { Id = new Guid("EBC95620-4D80-4318-9B92-AD7528B2965C") }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.RowVersion = Int32.MaxValue; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqlServerBulkUpdateOptions()); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntitiesWithRowVersion.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + loadedEntity!.Id.Should().Be(new Guid("EBC95620-4D80-4318-9B92-AD7528B2965C")); + loadedEntity.RowVersion.Should().NotBe(Int32.MaxValue); + } + + [Fact] + public async Task Should_update_specified_properties_only() + { + var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "original value", RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Name = "Name"; // this would not be updated + entity.Count = 42; + entity.PropertyWithBackingField = 7; + entity.SetPrivateField(3); + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, + new SqlServerBulkUpdateOptions + { + PropertiesToUpdate = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) + }); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Count = 42, + PropertyWithBackingField = 7, + Name = "original value", + RequiredName = "RequiredName" + }); + loadedEntity.GetPrivateField().Should().Be(3); + } + + [Fact] + public async Task Should_update_entity_with_auto_increment() + { + var entity = new TestEntityWithAutoIncrement { Name = "original value" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Name = "Name"; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, + new SqlServerBulkUpdateOptions()); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntitiesWithAutoIncrement.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Id.Should().NotBe(0); + loadedEntity.Should().BeEquivalentTo(new TestEntityWithAutoIncrement + { + Id = loadedEntity.Id, + Name = "Name" + }); + } + + [Fact] + public async Task Should_update_entity_with_auto_increment_having_custom_property_selector() + { + var entity = new TestEntityWithAutoIncrement { Name = "original value" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Name = "Name"; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, + new SqlServerBulkUpdateOptions + { + KeyProperties = IEntityPropertiesProvider.Include(e => e.Id), + PropertiesToUpdate = IEntityPropertiesProvider.Include(e => new + { + e.Id, + e.Name + }) + }); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntitiesWithAutoIncrement.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Id.Should().NotBe(0); + loadedEntity.Should().BeEquivalentTo(new TestEntityWithAutoIncrement + { + Id = entity.Id, + Name = "Name" + }); + } + + [Fact] + public async Task Should_update_property_of_base_class() + { + var entity = new TestEntityWithBaseClass { Id = new Guid("3D3AECE7-3B5A-48C6-9C8C-CAB7FFE2120D"), Name = "Initial" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Name = "changed"; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqlServerBulkUpdateOptions + { + PropertiesToUpdate = IEntityPropertiesProvider.Include(e => e.Name) + }); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntitiesWithBaseClass.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + loadedEntity!.Name.Should().Be("changed"); + } + + [Fact] + public async Task Should_insert_and_update_TestEntity_with_ComplexType() + { + // Arrange + var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + new BoundaryValueObject(2, 5)); + + ArrangeDbContext.Add(testEntity); + await ArrangeDbContext.SaveChangesAsync(); + + // Act + testEntity.Boundary = new BoundaryValueObject(10, 20); + + await SUT.BulkUpdateAsync(new[] { testEntity }, new SqlServerBulkUpdateOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/TruncateTableAsync.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/TruncateTableAsync.cs index 44e04cd1..7c9c09ef 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/TruncateTableAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/BulkOperations/SqlServerBulkOperationExecutorTests/TruncateTableAsync.cs @@ -1,41 +1,41 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqlServerBulkOperationExecutorTests; - -// ReSharper disable once InconsistentNaming -public class TruncateTableAsync : IntegrationTestsBase -{ - private SqlServerBulkOperationExecutor? _sut; - private SqlServerBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); - - public TruncateTableAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_not_throw_if_table_is_empty() - { - await SUT.Awaiting(sut => sut.TruncateTableAsync()) - .Should().NotThrowAsync(); - } - - [Fact] - public async Task Should_delete_entities() - { - ArrangeDbContext.Add(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }); - await ArrangeDbContext.SaveChangesAsync(); - - await SUT.TruncateTableAsync(); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEmpty(); - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqlServerBulkOperationExecutorTests; + +// ReSharper disable once InconsistentNaming +public class TruncateTableAsync : IntegrationTestsBase +{ + private SqlServerBulkOperationExecutor? _sut; + private SqlServerBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); + + public TruncateTableAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_not_throw_if_table_is_empty() + { + await SUT.Awaiting(sut => sut.TruncateTableAsync()) + .Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_delete_entities() + { + ArrangeDbContext.Add(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }); + await ArrangeDbContext.SaveChangesAsync(); + + await SUT.TruncateTableAsync(); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEmpty(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/Storage/NestedTransactionTests.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/Storage/NestedTransactionTests.cs index 32a3944c..6c44aa38 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/Storage/NestedTransactionTests.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/Storage/NestedTransactionTests.cs @@ -1,257 +1,257 @@ -using System.Data.Common; -using System.Transactions; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage; -using Thinktecture.TestDatabaseContext; -using IsolationLevel = System.Data.IsolationLevel; - -namespace Thinktecture.EntityFrameworkCore.Storage; - -public class NestedTransactionTests : IntegrationTestsBase -{ - protected NestedRelationalTransactionManager SUT => (NestedRelationalTransactionManager)ActDbContext.GetService(); - - public NestedTransactionTests(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - TestCtxProviderBuilder.UseSharedTablesIsolationLevel(IsolationLevel.Serializable); - } - - [Fact] - public void Should_create_new_root_transaction() - { - SUT.CurrentTransaction.Should().BeNull(); - - using (var tx = SUT.BeginTransaction()) - { - tx.Should().NotBeNull(); - tx.Should().Be(SUT.CurrentTransaction); - tx.GetDbTransaction().Should().NotBeNull(); - IsTransactionUsable(tx.GetDbTransaction()).Should().BeTrue(); - } - } - - [Fact] - public void Should_create_new_root_transaction_and_commit_correctly() - { - using (var tx = SUT.BeginTransaction()) - { - ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); - ActDbContext.SaveChanges(); - - tx.Commit(); - } - - AssertDbContext.TestEntities.Should().HaveCount(1); - } - - [Fact] - public void Should_create_new_root_transaction_and_rollback_correctly() - { - using (var tx = SUT.BeginTransaction()) - { - ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); - ActDbContext.SaveChanges(); - - tx.Rollback(); - } - - AssertDbContext.TestEntities.Should().HaveCount(0); - } - - [Fact] - public void Should_create_new_root_transaction_and_dispose_correctly() - { - // ReSharper disable once UnusedVariable - using (var tx = SUT.BeginTransaction()) - { - ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); - ActDbContext.SaveChanges(); - } - - AssertDbContext.TestEntities.Should().HaveCount(0); - } - - [Fact] - public void Should_create_new_child_transaction() - { - using (var tx = SUT.BeginTransaction()) - using (var childTx = SUT.BeginTransaction()) - { - childTx.Should().NotBeNull(); - childTx.Should().Be(SUT.CurrentTransaction); - childTx.GetDbTransaction().Should().NotBeNull(); - childTx.GetDbTransaction().Should().Be(tx.GetDbTransaction()); - IsTransactionUsable(childTx.GetDbTransaction()).Should().BeTrue(); - } - } - - [Fact] - public void Should_create_new_child_transaction_and_commit_correctly() - { - using (var tx = SUT.BeginTransaction()) - using (var child = SUT.BeginTransaction()) - { - ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); - ActDbContext.SaveChanges(); - - child.Commit(); - tx.Commit(); - } - - AssertDbContext.TestEntities.Should().HaveCount(1); - } - - [Fact] - public void Should_create_new_child_transaction_and_rollback_correctly() - { - using (var tx = SUT.BeginTransaction()) - using (var childTx = SUT.BeginTransaction()) - { - ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); - ActDbContext.SaveChanges(); - - childTx.Rollback(); - tx.Rollback(); - } - - AssertDbContext.TestEntities.Should().HaveCount(0); - } - - [Fact] - public void Should_create_new_child_transaction_and_dispose_correctly() - { - using (var tx = SUT.BeginTransaction()) - using (var childTx = SUT.BeginTransaction()) - { - ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); - ActDbContext.SaveChanges(); - } - - AssertDbContext.TestEntities.Should().HaveCount(0); - } - - [Fact] - public void Should_complete_underlying_translations_after_root_is_completed() - { - using (var tx = SUT.BeginTransaction()) - using (var child = SUT.BeginTransaction()) - { - ActDbContext.TestEntities.Add(new TestEntity { Id = new Guid("7631FF3B-E5B1-485A-ABD5-386C6B37E991"), RequiredName = "RequiredName" }); - ActDbContext.SaveChanges(); - - child.Commit(); - ActDbContext.TestEntities.Add(new TestEntity { Id = new Guid("7A4397B0-C9B6-46FF-B8C8-F39A9D6F5105"), RequiredName = "RequiredName" }); - ActDbContext.SaveChanges(); - - tx.Rollback(); - - // outside of the transaction - ActDbContext.TestEntities.Add(new TestEntity { Id = new Guid("CB682609-1503-4FBE-8A29-66E2DF8F00E5"), RequiredName = "RequiredName" }); - ActDbContext.SaveChanges(); - } - - AssertDbContext.TestEntities.Should().HaveCount(1); - AssertDbContext.TestEntities.First().Id.Should().Be(new Guid("CB682609-1503-4FBE-8A29-66E2DF8F00E5")); - } - - [Fact] - public void Should_rollback_if_a_sibling_transaction_rollbacks() - { - using (var tx = SUT.BeginTransaction()) - { - using (var child = SUT.BeginTransaction()) - { - child.Rollback(); - } - - using (var child = SUT.BeginTransaction()) - { - child.Commit(); - } - - ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); - ActDbContext.SaveChanges(); - - tx.Invoking(rootTx => rootTx.Commit()) - .Should().Throw().WithMessage("The transaction has aborted."); - } - - AssertDbContext.TestEntities.Should().HaveCount(0); - } - - [Fact] - public void Should_dispose_child_transactions_if_root_transactions_is_disposed() - { - using (var tx = SUT.BeginTransaction()) - { - var child = SUT.BeginTransaction(); - - ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); - ActDbContext.SaveChanges(); - } - - SUT.CurrentTransaction.Should().BeNull(); - AssertDbContext.TestEntities.Should().HaveCount(0); - } - - [Fact] - public void Should_dispose_inner_child_transactions_if_outer_transactions_is_disposed() - { - using (var tx = SUT.BeginTransaction()) - { - var child = SUT.BeginTransaction(); - var otherChild = SUT.BeginTransaction(); - - ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); - ActDbContext.SaveChanges(); - - child.Dispose(); - } - - SUT.CurrentTransaction.Should().BeNull(); - AssertDbContext.TestEntities.Should().HaveCount(0); - } - - [Fact] - public void Should_not_allow_create_child_transactions_with_higher_level() - { - using (var rootTx = SUT.BeginTransaction(IsolationLevel.RepeatableRead)) - { - rootTx.GetDbTransaction().IsolationLevel.Should().Be(IsolationLevel.RepeatableRead); - - SUT.Invoking(sut => sut.BeginTransaction(IsolationLevel.Serializable)) - .Should().Throw().WithMessage("The isolation level 'RepeatableRead' of the parent transaction is not compatible to the provided isolation level 'Serializable'."); - } - } - - [Fact] - public void Should_honor_default_isolation_level_if_no_level_is_provided() - { - using (var rootTx = SUT.BeginTransaction()) - { - rootTx.GetDbTransaction().IsolationLevel.Should().Be(IsolationLevel.ReadCommitted); - - var childTx = SUT.BeginTransaction(IsolationLevel.ReadCommitted); - } - } - - private bool IsTransactionUsable(DbTransaction tx) - { - try - { - var connection = ActDbContext.Database.GetDbConnection(); - var command = connection.CreateCommand(); - command.Transaction = tx; - command.CommandText = "SELECT @@version;"; - command.ExecuteNonQuery(); - } - catch (InvalidOperationException ex) - { - if (ex.Message == "The transaction object is not associated with the connection object.") - return false; - } - - return true; - } -} +using System.Data.Common; +using System.Transactions; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Thinktecture.TestDatabaseContext; +using IsolationLevel = System.Data.IsolationLevel; + +namespace Thinktecture.EntityFrameworkCore.Storage; + +public class NestedTransactionTests : IntegrationTestsBase +{ + protected NestedRelationalTransactionManager SUT => (NestedRelationalTransactionManager)ActDbContext.GetService(); + + public NestedTransactionTests(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + TestCtxProviderBuilder.UseSharedTablesIsolationLevel(IsolationLevel.Serializable); + } + + [Fact] + public void Should_create_new_root_transaction() + { + SUT.CurrentTransaction.Should().BeNull(); + + using (var tx = SUT.BeginTransaction()) + { + tx.Should().NotBeNull(); + tx.Should().Be(SUT.CurrentTransaction); + tx.GetDbTransaction().Should().NotBeNull(); + IsTransactionUsable(tx.GetDbTransaction()).Should().BeTrue(); + } + } + + [Fact] + public void Should_create_new_root_transaction_and_commit_correctly() + { + using (var tx = SUT.BeginTransaction()) + { + ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); + ActDbContext.SaveChanges(); + + tx.Commit(); + } + + AssertDbContext.TestEntities.Should().HaveCount(1); + } + + [Fact] + public void Should_create_new_root_transaction_and_rollback_correctly() + { + using (var tx = SUT.BeginTransaction()) + { + ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); + ActDbContext.SaveChanges(); + + tx.Rollback(); + } + + AssertDbContext.TestEntities.Should().HaveCount(0); + } + + [Fact] + public void Should_create_new_root_transaction_and_dispose_correctly() + { + // ReSharper disable once UnusedVariable + using (var tx = SUT.BeginTransaction()) + { + ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); + ActDbContext.SaveChanges(); + } + + AssertDbContext.TestEntities.Should().HaveCount(0); + } + + [Fact] + public void Should_create_new_child_transaction() + { + using (var tx = SUT.BeginTransaction()) + using (var childTx = SUT.BeginTransaction()) + { + childTx.Should().NotBeNull(); + childTx.Should().Be(SUT.CurrentTransaction); + childTx.GetDbTransaction().Should().NotBeNull(); + childTx.GetDbTransaction().Should().Be(tx.GetDbTransaction()); + IsTransactionUsable(childTx.GetDbTransaction()).Should().BeTrue(); + } + } + + [Fact] + public void Should_create_new_child_transaction_and_commit_correctly() + { + using (var tx = SUT.BeginTransaction()) + using (var child = SUT.BeginTransaction()) + { + ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); + ActDbContext.SaveChanges(); + + child.Commit(); + tx.Commit(); + } + + AssertDbContext.TestEntities.Should().HaveCount(1); + } + + [Fact] + public void Should_create_new_child_transaction_and_rollback_correctly() + { + using (var tx = SUT.BeginTransaction()) + using (var childTx = SUT.BeginTransaction()) + { + ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); + ActDbContext.SaveChanges(); + + childTx.Rollback(); + tx.Rollback(); + } + + AssertDbContext.TestEntities.Should().HaveCount(0); + } + + [Fact] + public void Should_create_new_child_transaction_and_dispose_correctly() + { + using (var tx = SUT.BeginTransaction()) + using (var childTx = SUT.BeginTransaction()) + { + ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); + ActDbContext.SaveChanges(); + } + + AssertDbContext.TestEntities.Should().HaveCount(0); + } + + [Fact] + public void Should_complete_underlying_translations_after_root_is_completed() + { + using (var tx = SUT.BeginTransaction()) + using (var child = SUT.BeginTransaction()) + { + ActDbContext.TestEntities.Add(new TestEntity { Id = new Guid("7631FF3B-E5B1-485A-ABD5-386C6B37E991"), RequiredName = "RequiredName" }); + ActDbContext.SaveChanges(); + + child.Commit(); + ActDbContext.TestEntities.Add(new TestEntity { Id = new Guid("7A4397B0-C9B6-46FF-B8C8-F39A9D6F5105"), RequiredName = "RequiredName" }); + ActDbContext.SaveChanges(); + + tx.Rollback(); + + // outside of the transaction + ActDbContext.TestEntities.Add(new TestEntity { Id = new Guid("CB682609-1503-4FBE-8A29-66E2DF8F00E5"), RequiredName = "RequiredName" }); + ActDbContext.SaveChanges(); + } + + AssertDbContext.TestEntities.Should().HaveCount(1); + AssertDbContext.TestEntities.First().Id.Should().Be(new Guid("CB682609-1503-4FBE-8A29-66E2DF8F00E5")); + } + + [Fact] + public void Should_rollback_if_a_sibling_transaction_rollbacks() + { + using (var tx = SUT.BeginTransaction()) + { + using (var child = SUT.BeginTransaction()) + { + child.Rollback(); + } + + using (var child = SUT.BeginTransaction()) + { + child.Commit(); + } + + ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); + ActDbContext.SaveChanges(); + + tx.Invoking(rootTx => rootTx.Commit()) + .Should().Throw().WithMessage("The transaction has aborted."); + } + + AssertDbContext.TestEntities.Should().HaveCount(0); + } + + [Fact] + public void Should_dispose_child_transactions_if_root_transactions_is_disposed() + { + using (var tx = SUT.BeginTransaction()) + { + var child = SUT.BeginTransaction(); + + ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); + ActDbContext.SaveChanges(); + } + + SUT.CurrentTransaction.Should().BeNull(); + AssertDbContext.TestEntities.Should().HaveCount(0); + } + + [Fact] + public void Should_dispose_inner_child_transactions_if_outer_transactions_is_disposed() + { + using (var tx = SUT.BeginTransaction()) + { + var child = SUT.BeginTransaction(); + var otherChild = SUT.BeginTransaction(); + + ActDbContext.TestEntities.Add(new TestEntity { RequiredName = "RequiredName" }); + ActDbContext.SaveChanges(); + + child.Dispose(); + } + + SUT.CurrentTransaction.Should().BeNull(); + AssertDbContext.TestEntities.Should().HaveCount(0); + } + + [Fact] + public void Should_not_allow_create_child_transactions_with_higher_level() + { + using (var rootTx = SUT.BeginTransaction(IsolationLevel.RepeatableRead)) + { + rootTx.GetDbTransaction().IsolationLevel.Should().Be(IsolationLevel.RepeatableRead); + + SUT.Invoking(sut => sut.BeginTransaction(IsolationLevel.Serializable)) + .Should().Throw().WithMessage("The isolation level 'RepeatableRead' of the parent transaction is not compatible to the provided isolation level 'Serializable'."); + } + } + + [Fact] + public void Should_honor_default_isolation_level_if_no_level_is_provided() + { + using (var rootTx = SUT.BeginTransaction()) + { + rootTx.GetDbTransaction().IsolationLevel.Should().Be(IsolationLevel.ReadCommitted); + + var childTx = SUT.BeginTransaction(IsolationLevel.ReadCommitted); + } + } + + private bool IsTransactionUsable(DbTransaction tx) + { + try + { + var connection = ActDbContext.Database.GetDbConnection(); + var command = connection.CreateCommand(); + command.Transaction = tx; + command.CommandText = "SELECT @@version;"; + command.ExecuteNonQuery(); + } + catch (InvalidOperationException ex) + { + if (ex.Message == "The transaction object is not associated with the connection object.") + return false; + } + + return true; + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/TempTables/SqlServerTempTableCreatorTests/CreatePrimaryKeyAsync.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/TempTables/SqlServerTempTableCreatorTests/CreatePrimaryKeyAsync.cs index d57f154b..3ae54698 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/TempTables/SqlServerTempTableCreatorTests/CreatePrimaryKeyAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/TempTables/SqlServerTempTableCreatorTests/CreatePrimaryKeyAsync.cs @@ -1,104 +1,104 @@ -using System.Data; -using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.TempTables.SqlServerTempTableCreatorTests; - -// ReSharper disable once InconsistentNaming -public class CreatePrimaryKeyAsync : IntegrationTestsBase -{ - private SqlServerTempTableCreator? _sut; - private SqlServerTempTableCreator SUT => _sut ??= (SqlServerTempTableCreator)ActDbContext.GetService(); - - public CreatePrimaryKeyAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - TestCtxProviderBuilder.UseSharedTablesIsolationLevel(IsolationLevel.Serializable); - } - - [Fact] - public async Task Should_create_primary_key_for_keylessType() - { - await using var tempTableReference = await ArrangeDbContext.CreateTempTableAsync(new TempTableCreationOptions - { - TableNameProvider = DefaultTempTableNameProvider.Instance, - PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None - }); - - var entityType = ActDbContext.GetEntityType(); - var allProperties = entityType.GetProperties().ToList(); - await SUT.CreatePrimaryKeyAsync(ActDbContext, IPrimaryKeyPropertiesProvider.AdaptiveForced.GetPrimaryKeyProperties(entityType, allProperties), tempTableReference.Name); - - var constraints = await AssertDbContext.GetTempTableConstraints().ToListAsync(); - constraints.Should().HaveCount(1) - .And.Subject.First().CONSTRAINT_TYPE.Should().Be("PRIMARY KEY"); - - var keyColumns = await AssertDbContext.GetTempTableKeyColumns().ToListAsync(); - keyColumns.Should().HaveCount(1) - .And.Subject.First().COLUMN_NAME.Should().Be(nameof(KeylessTestEntity.IntColumn)); - } - - [Fact] - public async Task Should_create_primary_key_for_entityType() - { - await using var tempTableReference = await ArrangeDbContext.CreateTempTableAsync(new TempTableCreationOptions - { - TableNameProvider = DefaultTempTableNameProvider.Instance, - PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None - }); - - var entityType = ActDbContext.GetEntityType(); - var allProperties = entityType.GetProperties().ToList(); - await SUT.CreatePrimaryKeyAsync(ActDbContext, IPrimaryKeyPropertiesProvider.AdaptiveForced.GetPrimaryKeyProperties(entityType, allProperties), tempTableReference.Name); - - var constraints = await AssertDbContext.GetTempTableConstraints().ToListAsync(); - constraints.Should().HaveCount(1) - .And.Subject.First().CONSTRAINT_TYPE.Should().Be("PRIMARY KEY"); - - var keyColumns = await AssertDbContext.GetTempTableKeyColumns().ToListAsync(); - keyColumns.Should().HaveCount(1) - .And.Subject.First().COLUMN_NAME.Should().Be(nameof(TestEntity.Id)); - } - - [Fact] - public async Task Should_not_create_primary_key_if_key_exists_and_checkForExistence_is_true() - { - await using var tempTableReference = await ArrangeDbContext.CreateTempTableAsync(new TempTableCreationOptions - { - TableNameProvider = DefaultTempTableNameProvider.Instance, - PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None - }); - var entityType = ArrangeDbContext.GetEntityType(); - var allProperties = entityType.GetProperties().ToList(); - var keyProperties = IPrimaryKeyPropertiesProvider.AdaptiveForced.GetPrimaryKeyProperties(entityType, allProperties); - await SUT.CreatePrimaryKeyAsync(ArrangeDbContext, keyProperties, tempTableReference.Name, true); - - var constraints = await AssertDbContext.GetTempTableConstraints().ToListAsync(); - constraints.Should().HaveCount(1) - .And.Subject.First().CONSTRAINT_TYPE.Should().Be("PRIMARY KEY"); - - await SUT.Awaiting(sut => sut.CreatePrimaryKeyAsync(ActDbContext, keyProperties, tempTableReference.Name, true)) - .Should().NotThrowAsync(); - } - - [Fact] - public async Task Should_throw_if_key_exists_and_checkForExistence_is_false() - { - await using var tempTableReference = await ArrangeDbContext.CreateTempTableAsync(new TempTableCreationOptions - { - TableNameProvider = DefaultTempTableNameProvider.Instance, - PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None - }); - var entityType = ArrangeDbContext.GetEntityType(); - var allProperties = entityType.GetProperties().ToList(); - var keyProperties = IPrimaryKeyPropertiesProvider.AdaptiveForced.GetPrimaryKeyProperties(entityType, allProperties); - await SUT.CreatePrimaryKeyAsync(ArrangeDbContext, keyProperties, tempTableReference.Name); - - // ReSharper disable once RedundantArgumentDefaultValue - // ReSharper disable once AccessToDisposedClosure - await SUT.Awaiting(sut => sut.CreatePrimaryKeyAsync(ActDbContext, keyProperties, tempTableReference.Name, false)) - .Should() - .ThrowAsync(); - } -} +using System.Data; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.TempTables.SqlServerTempTableCreatorTests; + +// ReSharper disable once InconsistentNaming +public class CreatePrimaryKeyAsync : IntegrationTestsBase +{ + private SqlServerTempTableCreator? _sut; + private SqlServerTempTableCreator SUT => _sut ??= (SqlServerTempTableCreator)ActDbContext.GetService(); + + public CreatePrimaryKeyAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + TestCtxProviderBuilder.UseSharedTablesIsolationLevel(IsolationLevel.Serializable); + } + + [Fact] + public async Task Should_create_primary_key_for_keylessType() + { + await using var tempTableReference = await ArrangeDbContext.CreateTempTableAsync(new TempTableCreationOptions + { + TableNameProvider = DefaultTempTableNameProvider.Instance, + PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None + }); + + var entityType = ActDbContext.GetEntityType(); + var allProperties = entityType.GetProperties().ToList(); + await SUT.CreatePrimaryKeyAsync(ActDbContext, IPrimaryKeyPropertiesProvider.AdaptiveForced.GetPrimaryKeyProperties(entityType, allProperties), tempTableReference.Name); + + var constraints = await AssertDbContext.GetTempTableConstraints().ToListAsync(); + constraints.Should().HaveCount(1) + .And.Subject.First().CONSTRAINT_TYPE.Should().Be("PRIMARY KEY"); + + var keyColumns = await AssertDbContext.GetTempTableKeyColumns().ToListAsync(); + keyColumns.Should().HaveCount(1) + .And.Subject.First().COLUMN_NAME.Should().Be(nameof(KeylessTestEntity.IntColumn)); + } + + [Fact] + public async Task Should_create_primary_key_for_entityType() + { + await using var tempTableReference = await ArrangeDbContext.CreateTempTableAsync(new TempTableCreationOptions + { + TableNameProvider = DefaultTempTableNameProvider.Instance, + PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None + }); + + var entityType = ActDbContext.GetEntityType(); + var allProperties = entityType.GetProperties().ToList(); + await SUT.CreatePrimaryKeyAsync(ActDbContext, IPrimaryKeyPropertiesProvider.AdaptiveForced.GetPrimaryKeyProperties(entityType, allProperties), tempTableReference.Name); + + var constraints = await AssertDbContext.GetTempTableConstraints().ToListAsync(); + constraints.Should().HaveCount(1) + .And.Subject.First().CONSTRAINT_TYPE.Should().Be("PRIMARY KEY"); + + var keyColumns = await AssertDbContext.GetTempTableKeyColumns().ToListAsync(); + keyColumns.Should().HaveCount(1) + .And.Subject.First().COLUMN_NAME.Should().Be(nameof(TestEntity.Id)); + } + + [Fact] + public async Task Should_not_create_primary_key_if_key_exists_and_checkForExistence_is_true() + { + await using var tempTableReference = await ArrangeDbContext.CreateTempTableAsync(new TempTableCreationOptions + { + TableNameProvider = DefaultTempTableNameProvider.Instance, + PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None + }); + var entityType = ArrangeDbContext.GetEntityType(); + var allProperties = entityType.GetProperties().ToList(); + var keyProperties = IPrimaryKeyPropertiesProvider.AdaptiveForced.GetPrimaryKeyProperties(entityType, allProperties); + await SUT.CreatePrimaryKeyAsync(ArrangeDbContext, keyProperties, tempTableReference.Name, true); + + var constraints = await AssertDbContext.GetTempTableConstraints().ToListAsync(); + constraints.Should().HaveCount(1) + .And.Subject.First().CONSTRAINT_TYPE.Should().Be("PRIMARY KEY"); + + await SUT.Awaiting(sut => sut.CreatePrimaryKeyAsync(ActDbContext, keyProperties, tempTableReference.Name, true)) + .Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_throw_if_key_exists_and_checkForExistence_is_false() + { + await using var tempTableReference = await ArrangeDbContext.CreateTempTableAsync(new TempTableCreationOptions + { + TableNameProvider = DefaultTempTableNameProvider.Instance, + PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None + }); + var entityType = ArrangeDbContext.GetEntityType(); + var allProperties = entityType.GetProperties().ToList(); + var keyProperties = IPrimaryKeyPropertiesProvider.AdaptiveForced.GetPrimaryKeyProperties(entityType, allProperties); + await SUT.CreatePrimaryKeyAsync(ArrangeDbContext, keyProperties, tempTableReference.Name); + + // ReSharper disable once RedundantArgumentDefaultValue + // ReSharper disable once AccessToDisposedClosure + await SUT.Awaiting(sut => sut.CreatePrimaryKeyAsync(ActDbContext, keyProperties, tempTableReference.Name, false)) + .Should() + .ThrowAsync(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/TempTables/SqlServerTempTableCreatorTests/CreateTempTableAsync.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/TempTables/SqlServerTempTableCreatorTests/CreateTempTableAsync.cs index eb170f53..176f8fae 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/TempTables/SqlServerTempTableCreatorTests/CreateTempTableAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/TempTables/SqlServerTempTableCreatorTests/CreateTempTableAsync.cs @@ -1,756 +1,756 @@ -using System.Data; -using System.Data.Common; -using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage; -using Thinktecture.EntityFrameworkCore.BulkOperations; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.TempTables.SqlServerTempTableCreatorTests; - -// ReSharper disable once InconsistentNaming -public class CreateTempTableAsync : IntegrationTestsBase -{ - private readonly SqlServerTempTableCreationOptions _optionsWithNonUniqueName; - private readonly string _connectionString; - - private SqlServerTempTableCreator? _sut; - private SqlServerTempTableCreator SUT => _sut ??= (SqlServerTempTableCreator)ActDbContext.GetService(); - - public CreateTempTableAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - _connectionString = sqlServerFixture.ConnectionString; - _optionsWithNonUniqueName = new SqlServerTempTableCreationOptions { TableNameProvider = DefaultTempTableNameProvider.Instance, PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }; - } - - [Fact] - public async Task Should_create_temp_table_for_keyless_entity() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns().ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); - } - - [Fact] - public async Task Should_delete_temp_table_on_dispose_if_DropTableOnDispose_is_true() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - _optionsWithNonUniqueName.DropTableOnDispose = true; - - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName)) - { - } - - AssertDbContext.GetTempTableColumns().ToList() - .Should().HaveCount(0); - } - - [Fact] - public async Task Should_delete_temp_table_on_disposeAsync_if_DropTableOnDispose_is_true() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - _optionsWithNonUniqueName.DropTableOnDispose = true; - - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName)) - { - } - - AssertDbContext.GetTempTableColumns().ToList() - .Should().HaveCount(0); - } - - [Fact] - public async Task Should_not_delete_temp_table_on_dispose_if_DropTableOnDispose_is_false() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - _optionsWithNonUniqueName.DropTableOnDispose = false; - - // ReSharper disable once UseAwaitUsing - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName)) - { - } - - var columns = AssertDbContext.GetTempTableColumns().ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); - } - - [Fact] - public async Task Should_not_delete_temp_table_on_disposeAsync_if_DropTableOnDispose_is_false() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - _optionsWithNonUniqueName.DropTableOnDispose = false; - - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName)) - { - } - - var columns = AssertDbContext.GetTempTableColumns().ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); - } - - [Fact] - public async Task Should_create_temp_table_with_reusable_name() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance, PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }; - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); - - var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); - } - - [Fact] - public async Task Should_reuse_name_after_it_is_freed() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance, PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }; - - // ReSharper disable once RedundantArgumentDefaultValue - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) - { - } - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); - - var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); - } - - [Fact] - public async Task Should_reuse_name_after_it_is_freed_although_previously_not_dropped() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - var options = new TempTableCreationOptions - { - TableNameProvider = ReusingTempTableNameProvider.Instance, - PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None, - DropTableOnDispose = false - }; - - // ReSharper disable once RedundantArgumentDefaultValue - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) - { - } - - options.TruncateTableIfExists = true; - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); - - var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); - - AssertDbContext.GetTempTableColumns("#CustomTempTable_2").ToList() - .Should().HaveCount(0); - } - - [Fact] - public async Task Should_not_reuse_name_before_it_is_freed() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance, PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }; - - // ReSharper disable once RedundantArgumentDefaultValue - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) - { - await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); - } - - var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_2").ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); - } - - [Fact] - public async Task Should_reuse_name_in_sorted_order() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance, PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }; - - // #CustomTempTable_1 - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) - { - // #CustomTempTable_2 - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) - { - } - } - - // #CustomTempTable_1 - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); - - var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); - } - - [Fact] - public async Task Should_create_temp_table_with_provided_column_only() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - _optionsWithNonUniqueName.PropertiesToInclude = IEntityPropertiesProvider.Include(t => t.Column1); - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns().ToList(); - columns.Should().HaveCount(1); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); - } - - [Fact] - public async Task Should_create_pk_if_options_flag_is_set() - { - _optionsWithNonUniqueName.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced; - - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(s => s.Column2).IsRequired().HasMaxLength(100)); - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); - - var constraints = await AssertDbContext.GetTempTableConstraints>().ToListAsync(); - constraints.Should().HaveCount(1) - .And.Subject.First().CONSTRAINT_TYPE.Should().Be("PRIMARY KEY"); - - var keyColumns = await AssertDbContext.GetTempTableKeyColumns>().ToListAsync(); - keyColumns.Should().HaveCount(2); - keyColumns[0].COLUMN_NAME.Should().Be(nameof(TempTable.Column1)); - keyColumns[1].COLUMN_NAME.Should().Be(nameof(TempTable.Column2)); - } - - [Fact] - public async Task Should_throw_if_some_pk_columns_are_missing() - { - _optionsWithNonUniqueName.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.EntityTypeConfiguration; - _optionsWithNonUniqueName.PropertiesToInclude = IEntityPropertiesProvider.Include(t => t.Column1); - - ConfigureModel = builder => builder.ConfigureTempTableEntity(false, typeBuilder => - { - typeBuilder.Property(s => s.Column2).HasMaxLength(100); - typeBuilder.HasKey(s => new { s.Column1, s.Column2 }); - }); - - // ReSharper disable once RedundantArgumentDefaultValue - await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName)) - .Should().ThrowAsync().WithMessage(""" - Cannot create PRIMARY KEY because not all key columns are part of the temp table. - You may use other key properties providers like 'IPrimaryKeyPropertiesProvider.AdaptiveEntityTypeConfiguration' instead of 'IPrimaryKeyPropertiesProvider.EntityTypeConfiguration' to get different behaviors. - Missing columns: Column2. - """); - } - - [Fact] - public async Task Should_not_throw_if_some_pk_columns_are_missing_and_provider_is_Adaptive() - { - _optionsWithNonUniqueName.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced; - _optionsWithNonUniqueName.PropertiesToInclude = IEntityPropertiesProvider.Include(t => t.Column1); - - ConfigureModel = builder => builder.ConfigureTempTableEntity(false, typeBuilder => - { - typeBuilder.Property(s => s.Column2).HasMaxLength(100); - typeBuilder.HasKey(s => new { s.Column1, s.Column2 }); - }); - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName); - - var keyColumns = await AssertDbContext.GetTempTableKeyColumns().ToListAsync(); - keyColumns.Should().HaveCount(1); - keyColumns[0].COLUMN_NAME.Should().Be(nameof(CustomTempTable.Column1)); - } - - [Fact] - public async Task Should_open_connection() - { - await using var con = CreateConnection(); - - var options = CreateOptions(con); - - await using var ctx = new TestDbContext(options, CreateDefaultSchema()); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await ctx.GetService() - .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueName); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Open); - } - - [Fact] - public async Task Should_return_reference_to_be_able_to_close_connection_using_dispose() - { - await using var con = CreateConnection(); - - var options = CreateOptions(con); - - await using var ctx = new TestDbContext(options, CreateDefaultSchema()); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - - // ReSharper disable once RedundantArgumentDefaultValue - var tempTableReference = await ctx.GetService() - .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueName); - tempTableReference.Dispose(); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - } - - [Fact] - public async Task Should_return_reference_to_be_able_to_close_connection_using_disposeAsync() - { - await using var con = CreateConnection(); - - var options = CreateOptions(con); - - await using var ctx = new TestDbContext(options, CreateDefaultSchema()); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - - // ReSharper disable once RedundantArgumentDefaultValue - var tempTableReference = await ctx.GetService() - .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueName); - await tempTableReference.DisposeAsync(); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - } - - [Fact] - public async Task Should_return_reference_to_be_able_to_close_connection_even_if_ctx_is_disposed() - { - await using var con = CreateConnection(); - - var options = CreateOptions(con); - - ITempTableReference tempTableReference; - - await using (var ctx = new TestDbContext(options, CreateDefaultSchema())) - { - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - - // ReSharper disable once RedundantArgumentDefaultValue - tempTableReference = await ctx.GetService() - .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueName); - } - - con.State.Should().Be(ConnectionState.Open); - await tempTableReference.DisposeAsync(); - con.State.Should().Be(ConnectionState.Closed); - } - - [Fact] - public async Task Should_return_table_ref_that_does_nothing_after_connection_is_disposed() - { - await using var con = CreateConnection(); - - var options = CreateOptions(con); - - await using var ctx = new TestDbContext(options, CreateDefaultSchema()); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - - // ReSharper disable once RedundantArgumentDefaultValue - var tempTableReference = await ctx.GetService() - .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueName); - con.Dispose(); - - con.State.Should().Be(ConnectionState.Closed); - await tempTableReference.DisposeAsync(); - con.State.Should().Be(ConnectionState.Closed); - } - - [Fact] - public async Task Should_return_table_ref_that_does_nothing_after_connection_is_closed() - { - await using var con = CreateConnection(); - - var options = CreateOptions(con); - - await using var ctx = new TestDbContext(options, CreateDefaultSchema()); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - - // ReSharper disable once RedundantArgumentDefaultValue - var tempTableReference = await ctx.GetService() - .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueName); - await con.CloseAsync(); - - await tempTableReference.DisposeAsync(); - con.State.Should().Be(ConnectionState.Closed); - } - - [Fact] - public async Task Should_return_reference_to_remove_temp_table() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - // ReSharper disable once RedundantArgumentDefaultValue - var tempTableReference = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName); - await tempTableReference.DisposeAsync(); - - var columns = AssertDbContext.GetTempTableColumns().ToList(); - columns.Should().BeEmpty(); - } - - [Fact] - public async Task Should_create_temp_table_for_entityType() - { - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns().OrderBy(c => c.COLUMN_NAME).ToList(); - columns.Should().HaveCount(9); - - ValidateColumn(columns[0], "_privateField", "int", false); - ValidateColumn(columns[1], nameof(TestEntity.ConvertibleClass), "int", true); - ValidateColumn(columns[2], nameof(TestEntity.Count), "int", false); - ValidateColumn(columns[3], nameof(TestEntity.Id), "uniqueidentifier", false); - ValidateColumn(columns[4], nameof(TestEntity.Name), "nvarchar", true); - ValidateColumn(columns[5], nameof(TestEntity.NullableCount), "int", true); - ValidateColumn(columns[6], nameof(TestEntity.ParentId), "uniqueidentifier", true); - ValidateColumn(columns[7], nameof(TestEntity.PropertyWithBackingField), "int", false); - ValidateColumn(columns[8], nameof(TestEntity.RequiredName), "nvarchar", false); - } - - [Fact] - public async Task Should_throw_if_temp_table_is_not_introduced() - { - await SUT.Awaiting(c => c.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName)) - .Should().ThrowAsync(); - } - - [Fact] - public async Task Should_create_temp_table_with_one_column() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); - - AssertDbContext.GetTempTableColumns>().ToList().Should().HaveCount(1); - } - - [Fact] - public async Task Should_create_temp_table_without_primary_key() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); - - var constraints = await AssertDbContext.GetTempTableConstraints>().ToListAsync(); - constraints.Should().HaveCount(0); - } - - [Fact] - public async Task Should_create_temp_table_with_int() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "int", false); - } - - [Fact] - public async Task Should_create_temp_table_with_nullable_int() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var temptTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "int", true); - } - - [Fact] - public async Task Should_make_nullable_int_to_non_nullable_if_set_via_modelbuilder() - { - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).IsRequired()); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "int", false); - } - - [Fact] - public async Task Should_create_temp_table_with_double() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "float", false); - } - - [Fact] - public async Task Should_create_temp_table_with_decimal() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "decimal", false); - } - - [Fact] - public async Task Should_create_temp_table_with_decimal_with_explicit_precision() - { - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).HasPrecision(20, 5)); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "decimal", false, 20, 5); - } - - [Fact] - public async Task Should_create_temp_table_with_bool() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "bit", false); - } - - [Fact] - public async Task Should_create_temp_table_with_string() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "nvarchar", true); - } - - [Fact] - public async Task Should_create_temp_table_with_converter_and_default_value() - { - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1) - .HasConversion(c => c.Key, k => new ConvertibleClass(k)) - .HasDefaultValue(new ConvertibleClass(1))); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "int", true, defaultValue: "((1))"); - } - - [Fact] - public async Task Should_create_temp_table_with_string_with_max_length() - { - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).HasMaxLength(50)); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "nvarchar", true, charMaxLength: 50); - } - - [Fact] - public async Task Should_create_temp_table_with_2_columns() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(TempTable.Column1), "int", false); - ValidateColumn(columns[1], nameof(TempTable.Column2), "nvarchar", true); - } - - [Fact] - public async Task Should_create_temp_table_with_default_database_collation() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - _optionsWithNonUniqueName.UseDefaultDatabaseCollation = true; - - await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns().ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); - } - - [Fact] - public async Task Should_throw_if_temp_table_entity_contains_inlined_owned_type() - { - await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName)) - .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); - } - - [Fact] - public async Task Should_throw_if_temp_table_entity_contains_separated_owned_type() - { - var ownerEntityType = ActDbContext.GetTempTableEntityType(); - await SUT.Awaiting(sut => sut.CreateTempTableAsync(ownerEntityType, _optionsWithNonUniqueName)) - .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); - - var ownedTypeEntityType = ownerEntityType.GetNavigations().Single().TargetEntityType; - await using var ownedTempTable = await SUT.CreateTempTableAsync(ownedTypeEntityType, _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns(ownedTypeEntityType).ToList(); - columns.Should().HaveCount(3); - ValidateColumn(columns[0], $"{nameof(TestEntity_Owns_SeparateOne)}{nameof(TestEntity_Owns_SeparateOne.Id)}", "uniqueidentifier", false); - ValidateColumn(columns[1], nameof(OwnedEntity.IntColumn), "int", false); - ValidateColumn(columns[2], nameof(OwnedEntity.StringColumn), "nvarchar", true); - } - - [Fact] - public async Task Should_throw_when_selecting_separated_owned_type() - { - _optionsWithNonUniqueName.PropertiesToInclude = IEntityPropertiesProvider.Include(e => new - { - e.Id, - e.SeparateEntity - }); - await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName)) - .Should().ThrowAsync() - .WithMessage("The entity 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne' must not contain owned entities."); - } - - [Fact] - public async Task Should_throw_if_temp_table_entity_contains_many_owned_types() - { - var ownerEntityType = ActDbContext.GetTempTableEntityType(); - await SUT.Awaiting(sut => sut.CreateTempTableAsync(ownerEntityType, _optionsWithNonUniqueName)) - .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); - - var ownedTypeEntityType = ownerEntityType.GetNavigations().Single().TargetEntityType; - await using var ownedTempTable = await SUT.CreateTempTableAsync(ownedTypeEntityType, _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns(ownedTypeEntityType).ToList(); - columns.Should().HaveCount(4); - ValidateColumn(columns[0], $"{nameof(TestEntity_Owns_SeparateMany)}{nameof(TestEntity_Owns_SeparateMany.Id)}", "uniqueidentifier", false); - ValidateColumn(columns[1], "Id", "int", false); - ValidateColumn(columns[2], nameof(OwnedEntity.IntColumn), "int", false); - ValidateColumn(columns[3], nameof(OwnedEntity.StringColumn), "nvarchar", true); - } - - [Fact] - public async Task Should_honor_collation() - { - var entityType = ActDbContext.GetTempTableEntityType(); - await using var tempTable = await SUT.CreateTempTableAsync(entityType, _optionsWithNonUniqueName); - - var columns = AssertDbContext.GetTempTableColumns().ToList(); - columns.Should().HaveCount(3); - - var connection = AssertDbContext.Database.GetDbConnection(); - await using var command = connection.CreateCommand(); - command.Transaction = AssertDbContext.Database.CurrentTransaction?.GetDbTransaction(); - command.CommandText = "SELECT CONVERT (varchar(256), SERVERPROPERTY('collation'))"; - var databaseCollation = (string?)await command.ExecuteScalarAsync() ?? throw new Exception("Couldn't fetch database collection."); - - ValidateColumn(columns[0], nameof(TestEntityWithCollation.Id), "uniqueidentifier", false); - ValidateColumn(columns[1], nameof(TestEntityWithCollation.ColumnWithCollation), "nvarchar", false, collation: "Japanese_CI_AS"); - ValidateColumn(columns[2], nameof(TestEntityWithCollation.ColumnWithoutCollation), "nvarchar", false, collation: databaseCollation); - } - - [Fact] - public async Task Should_create_temp_table_for_entity_with_complex_type() - { - var testEntity = ActDbContext.GetTempTableEntityType(); - - await using var tempTable = await SUT.CreateTempTableAsync(testEntity, _optionsWithNonUniqueName); - - var columns = await AssertDbContext.GetTempTableColumns(testEntity).ToListAsync(); - columns.Should().HaveCount(3); - ValidateColumn(columns[0], nameof(TestEntityWithComplexType.Id), "uniqueidentifier", false); - ValidateColumn(columns[1], $"{nameof(TestEntityWithComplexType.Boundary)}_{nameof(BoundaryValueObject.Lower)}", "int", false); - ValidateColumn(columns[2], $"{nameof(TestEntityWithComplexType.Boundary)}_{nameof(BoundaryValueObject.Upper)}", "int", false); - } - - private DbConnection CreateConnection() - { - return new SqlConnection(_connectionString); - } - - private DbContextOptions CreateOptions(DbConnection connection) - { - return TestCtxProviderBuilder.CreateOptionsBuilder(connection, Schema).Options; - } - - private static void ValidateColumn( - InformationSchemaColumn column, - string name, - string type, - bool isNullable, - byte? numericPrecision = null, - int? numericScale = null, - int? charMaxLength = null, - string? defaultValue = null, - string? collation = null) - { - ArgumentNullException.ThrowIfNull(column); - - column.COLUMN_NAME.Should().Be(name); - column.DATA_TYPE.Should().Be(type); - column.IS_NULLABLE.Should().Be(isNullable ? "YES" : "NO"); - column.COLUMN_DEFAULT.Should().Be(defaultValue); - - if (collation is not null) - column.COLLATION_NAME.Should().Be(collation); - - if (numericPrecision.HasValue) - column.NUMERIC_PRECISION.Should().Be(numericPrecision.Value); - - if (numericScale.HasValue) - column.NUMERIC_SCALE.Should().Be(numericScale.Value); - - if (charMaxLength.HasValue) - column.CHARACTER_MAXIMUM_LENGTH.Should().Be(charMaxLength.Value); - } - - private DbDefaultSchema? CreateDefaultSchema() - { - return Schema is null ? null : new DbDefaultSchema(Schema); - } -} +using System.Data; +using System.Data.Common; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Thinktecture.EntityFrameworkCore.BulkOperations; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.TempTables.SqlServerTempTableCreatorTests; + +// ReSharper disable once InconsistentNaming +public class CreateTempTableAsync : IntegrationTestsBase +{ + private readonly SqlServerTempTableCreationOptions _optionsWithNonUniqueName; + private readonly string _connectionString; + + private SqlServerTempTableCreator? _sut; + private SqlServerTempTableCreator SUT => _sut ??= (SqlServerTempTableCreator)ActDbContext.GetService(); + + public CreateTempTableAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + _connectionString = sqlServerFixture.ConnectionString; + _optionsWithNonUniqueName = new SqlServerTempTableCreationOptions { TableNameProvider = DefaultTempTableNameProvider.Instance, PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }; + } + + [Fact] + public async Task Should_create_temp_table_for_keyless_entity() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns().ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); + } + + [Fact] + public async Task Should_delete_temp_table_on_dispose_if_DropTableOnDispose_is_true() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + _optionsWithNonUniqueName.DropTableOnDispose = true; + + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName)) + { + } + + AssertDbContext.GetTempTableColumns().ToList() + .Should().HaveCount(0); + } + + [Fact] + public async Task Should_delete_temp_table_on_disposeAsync_if_DropTableOnDispose_is_true() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + _optionsWithNonUniqueName.DropTableOnDispose = true; + + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName)) + { + } + + AssertDbContext.GetTempTableColumns().ToList() + .Should().HaveCount(0); + } + + [Fact] + public async Task Should_not_delete_temp_table_on_dispose_if_DropTableOnDispose_is_false() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + _optionsWithNonUniqueName.DropTableOnDispose = false; + + // ReSharper disable once UseAwaitUsing + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName)) + { + } + + var columns = AssertDbContext.GetTempTableColumns().ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); + } + + [Fact] + public async Task Should_not_delete_temp_table_on_disposeAsync_if_DropTableOnDispose_is_false() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + _optionsWithNonUniqueName.DropTableOnDispose = false; + + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName)) + { + } + + var columns = AssertDbContext.GetTempTableColumns().ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); + } + + [Fact] + public async Task Should_create_temp_table_with_reusable_name() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance, PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }; + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); + + var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); + } + + [Fact] + public async Task Should_reuse_name_after_it_is_freed() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance, PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }; + + // ReSharper disable once RedundantArgumentDefaultValue + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) + { + } + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); + + var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); + } + + [Fact] + public async Task Should_reuse_name_after_it_is_freed_although_previously_not_dropped() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + var options = new TempTableCreationOptions + { + TableNameProvider = ReusingTempTableNameProvider.Instance, + PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None, + DropTableOnDispose = false + }; + + // ReSharper disable once RedundantArgumentDefaultValue + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) + { + } + + options.TruncateTableIfExists = true; + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); + + var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); + + AssertDbContext.GetTempTableColumns("#CustomTempTable_2").ToList() + .Should().HaveCount(0); + } + + [Fact] + public async Task Should_not_reuse_name_before_it_is_freed() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance, PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }; + + // ReSharper disable once RedundantArgumentDefaultValue + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) + { + await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); + } + + var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_2").ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); + } + + [Fact] + public async Task Should_reuse_name_in_sorted_order() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance, PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }; + + // #CustomTempTable_1 + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) + { + // #CustomTempTable_2 + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) + { + } + } + + // #CustomTempTable_1 + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); + + var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); + } + + [Fact] + public async Task Should_create_temp_table_with_provided_column_only() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + _optionsWithNonUniqueName.PropertiesToInclude = IEntityPropertiesProvider.Include(t => t.Column1); + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns().ToList(); + columns.Should().HaveCount(1); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); + } + + [Fact] + public async Task Should_create_pk_if_options_flag_is_set() + { + _optionsWithNonUniqueName.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced; + + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(s => s.Column2).IsRequired().HasMaxLength(100)); + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); + + var constraints = await AssertDbContext.GetTempTableConstraints>().ToListAsync(); + constraints.Should().HaveCount(1) + .And.Subject.First().CONSTRAINT_TYPE.Should().Be("PRIMARY KEY"); + + var keyColumns = await AssertDbContext.GetTempTableKeyColumns>().ToListAsync(); + keyColumns.Should().HaveCount(2); + keyColumns[0].COLUMN_NAME.Should().Be(nameof(TempTable.Column1)); + keyColumns[1].COLUMN_NAME.Should().Be(nameof(TempTable.Column2)); + } + + [Fact] + public async Task Should_throw_if_some_pk_columns_are_missing() + { + _optionsWithNonUniqueName.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.EntityTypeConfiguration; + _optionsWithNonUniqueName.PropertiesToInclude = IEntityPropertiesProvider.Include(t => t.Column1); + + ConfigureModel = builder => builder.ConfigureTempTableEntity(false, typeBuilder => + { + typeBuilder.Property(s => s.Column2).HasMaxLength(100); + typeBuilder.HasKey(s => new { s.Column1, s.Column2 }); + }); + + // ReSharper disable once RedundantArgumentDefaultValue + await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName)) + .Should().ThrowAsync().WithMessage(""" + Cannot create PRIMARY KEY because not all key columns are part of the temp table. + You may use other key properties providers like 'IPrimaryKeyPropertiesProvider.AdaptiveEntityTypeConfiguration' instead of 'IPrimaryKeyPropertiesProvider.EntityTypeConfiguration' to get different behaviors. + Missing columns: Column2. + """); + } + + [Fact] + public async Task Should_not_throw_if_some_pk_columns_are_missing_and_provider_is_Adaptive() + { + _optionsWithNonUniqueName.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced; + _optionsWithNonUniqueName.PropertiesToInclude = IEntityPropertiesProvider.Include(t => t.Column1); + + ConfigureModel = builder => builder.ConfigureTempTableEntity(false, typeBuilder => + { + typeBuilder.Property(s => s.Column2).HasMaxLength(100); + typeBuilder.HasKey(s => new { s.Column1, s.Column2 }); + }); + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName); + + var keyColumns = await AssertDbContext.GetTempTableKeyColumns().ToListAsync(); + keyColumns.Should().HaveCount(1); + keyColumns[0].COLUMN_NAME.Should().Be(nameof(CustomTempTable.Column1)); + } + + [Fact] + public async Task Should_open_connection() + { + await using var con = CreateConnection(); + + var options = CreateOptions(con); + + await using var ctx = new TestDbContext(options, CreateDefaultSchema()); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await ctx.GetService() + .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueName); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Open); + } + + [Fact] + public async Task Should_return_reference_to_be_able_to_close_connection_using_dispose() + { + await using var con = CreateConnection(); + + var options = CreateOptions(con); + + await using var ctx = new TestDbContext(options, CreateDefaultSchema()); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + + // ReSharper disable once RedundantArgumentDefaultValue + var tempTableReference = await ctx.GetService() + .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueName); + tempTableReference.Dispose(); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + } + + [Fact] + public async Task Should_return_reference_to_be_able_to_close_connection_using_disposeAsync() + { + await using var con = CreateConnection(); + + var options = CreateOptions(con); + + await using var ctx = new TestDbContext(options, CreateDefaultSchema()); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + + // ReSharper disable once RedundantArgumentDefaultValue + var tempTableReference = await ctx.GetService() + .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueName); + await tempTableReference.DisposeAsync(); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + } + + [Fact] + public async Task Should_return_reference_to_be_able_to_close_connection_even_if_ctx_is_disposed() + { + await using var con = CreateConnection(); + + var options = CreateOptions(con); + + ITempTableReference tempTableReference; + + await using (var ctx = new TestDbContext(options, CreateDefaultSchema())) + { + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + + // ReSharper disable once RedundantArgumentDefaultValue + tempTableReference = await ctx.GetService() + .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueName); + } + + con.State.Should().Be(ConnectionState.Open); + await tempTableReference.DisposeAsync(); + con.State.Should().Be(ConnectionState.Closed); + } + + [Fact] + public async Task Should_return_table_ref_that_does_nothing_after_connection_is_disposed() + { + await using var con = CreateConnection(); + + var options = CreateOptions(con); + + await using var ctx = new TestDbContext(options, CreateDefaultSchema()); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + + // ReSharper disable once RedundantArgumentDefaultValue + var tempTableReference = await ctx.GetService() + .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueName); + con.Dispose(); + + con.State.Should().Be(ConnectionState.Closed); + await tempTableReference.DisposeAsync(); + con.State.Should().Be(ConnectionState.Closed); + } + + [Fact] + public async Task Should_return_table_ref_that_does_nothing_after_connection_is_closed() + { + await using var con = CreateConnection(); + + var options = CreateOptions(con); + + await using var ctx = new TestDbContext(options, CreateDefaultSchema()); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + + // ReSharper disable once RedundantArgumentDefaultValue + var tempTableReference = await ctx.GetService() + .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueName); + await con.CloseAsync(); + + await tempTableReference.DisposeAsync(); + con.State.Should().Be(ConnectionState.Closed); + } + + [Fact] + public async Task Should_return_reference_to_remove_temp_table() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + // ReSharper disable once RedundantArgumentDefaultValue + var tempTableReference = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName); + await tempTableReference.DisposeAsync(); + + var columns = AssertDbContext.GetTempTableColumns().ToList(); + columns.Should().BeEmpty(); + } + + [Fact] + public async Task Should_create_temp_table_for_entityType() + { + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns().OrderBy(c => c.COLUMN_NAME).ToList(); + columns.Should().HaveCount(9); + + ValidateColumn(columns[0], "_privateField", "int", false); + ValidateColumn(columns[1], nameof(TestEntity.ConvertibleClass), "int", true); + ValidateColumn(columns[2], nameof(TestEntity.Count), "int", false); + ValidateColumn(columns[3], nameof(TestEntity.Id), "uniqueidentifier", false); + ValidateColumn(columns[4], nameof(TestEntity.Name), "nvarchar", true); + ValidateColumn(columns[5], nameof(TestEntity.NullableCount), "int", true); + ValidateColumn(columns[6], nameof(TestEntity.ParentId), "uniqueidentifier", true); + ValidateColumn(columns[7], nameof(TestEntity.PropertyWithBackingField), "int", false); + ValidateColumn(columns[8], nameof(TestEntity.RequiredName), "nvarchar", false); + } + + [Fact] + public async Task Should_throw_if_temp_table_is_not_introduced() + { + await SUT.Awaiting(c => c.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName)) + .Should().ThrowAsync(); + } + + [Fact] + public async Task Should_create_temp_table_with_one_column() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); + + AssertDbContext.GetTempTableColumns>().ToList().Should().HaveCount(1); + } + + [Fact] + public async Task Should_create_temp_table_without_primary_key() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); + + var constraints = await AssertDbContext.GetTempTableConstraints>().ToListAsync(); + constraints.Should().HaveCount(0); + } + + [Fact] + public async Task Should_create_temp_table_with_int() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "int", false); + } + + [Fact] + public async Task Should_create_temp_table_with_nullable_int() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var temptTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "int", true); + } + + [Fact] + public async Task Should_make_nullable_int_to_non_nullable_if_set_via_modelbuilder() + { + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).IsRequired()); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "int", false); + } + + [Fact] + public async Task Should_create_temp_table_with_double() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "float", false); + } + + [Fact] + public async Task Should_create_temp_table_with_decimal() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "decimal", false); + } + + [Fact] + public async Task Should_create_temp_table_with_decimal_with_explicit_precision() + { + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).HasPrecision(20, 5)); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "decimal", false, 20, 5); + } + + [Fact] + public async Task Should_create_temp_table_with_bool() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "bit", false); + } + + [Fact] + public async Task Should_create_temp_table_with_string() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "nvarchar", true); + } + + [Fact] + public async Task Should_create_temp_table_with_converter_and_default_value() + { + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1) + .HasConversion(c => c.Key, k => new ConvertibleClass(k)) + .HasDefaultValue(new ConvertibleClass(1))); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "int", true, defaultValue: "((1))"); + } + + [Fact] + public async Task Should_create_temp_table_with_string_with_max_length() + { + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).HasMaxLength(50)); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "nvarchar", true, charMaxLength: 50); + } + + [Fact] + public async Task Should_create_temp_table_with_2_columns() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(TempTable.Column1), "int", false); + ValidateColumn(columns[1], nameof(TempTable.Column2), "nvarchar", true); + } + + [Fact] + public async Task Should_create_temp_table_with_default_database_collation() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + _optionsWithNonUniqueName.UseDefaultDatabaseCollation = true; + + await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns().ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "int", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "nvarchar", true); + } + + [Fact] + public async Task Should_throw_if_temp_table_entity_contains_inlined_owned_type() + { + await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName)) + .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); + } + + [Fact] + public async Task Should_throw_if_temp_table_entity_contains_separated_owned_type() + { + var ownerEntityType = ActDbContext.GetTempTableEntityType(); + await SUT.Awaiting(sut => sut.CreateTempTableAsync(ownerEntityType, _optionsWithNonUniqueName)) + .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); + + var ownedTypeEntityType = ownerEntityType.GetNavigations().Single().TargetEntityType; + await using var ownedTempTable = await SUT.CreateTempTableAsync(ownedTypeEntityType, _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns(ownedTypeEntityType).ToList(); + columns.Should().HaveCount(3); + ValidateColumn(columns[0], $"{nameof(TestEntity_Owns_SeparateOne)}{nameof(TestEntity_Owns_SeparateOne.Id)}", "uniqueidentifier", false); + ValidateColumn(columns[1], nameof(OwnedEntity.IntColumn), "int", false); + ValidateColumn(columns[2], nameof(OwnedEntity.StringColumn), "nvarchar", true); + } + + [Fact] + public async Task Should_throw_when_selecting_separated_owned_type() + { + _optionsWithNonUniqueName.PropertiesToInclude = IEntityPropertiesProvider.Include(e => new + { + e.Id, + e.SeparateEntity + }); + await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueName)) + .Should().ThrowAsync() + .WithMessage("The entity 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne' must not contain owned entities."); + } + + [Fact] + public async Task Should_throw_if_temp_table_entity_contains_many_owned_types() + { + var ownerEntityType = ActDbContext.GetTempTableEntityType(); + await SUT.Awaiting(sut => sut.CreateTempTableAsync(ownerEntityType, _optionsWithNonUniqueName)) + .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); + + var ownedTypeEntityType = ownerEntityType.GetNavigations().Single().TargetEntityType; + await using var ownedTempTable = await SUT.CreateTempTableAsync(ownedTypeEntityType, _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns(ownedTypeEntityType).ToList(); + columns.Should().HaveCount(4); + ValidateColumn(columns[0], $"{nameof(TestEntity_Owns_SeparateMany)}{nameof(TestEntity_Owns_SeparateMany.Id)}", "uniqueidentifier", false); + ValidateColumn(columns[1], "Id", "int", false); + ValidateColumn(columns[2], nameof(OwnedEntity.IntColumn), "int", false); + ValidateColumn(columns[3], nameof(OwnedEntity.StringColumn), "nvarchar", true); + } + + [Fact] + public async Task Should_honor_collation() + { + var entityType = ActDbContext.GetTempTableEntityType(); + await using var tempTable = await SUT.CreateTempTableAsync(entityType, _optionsWithNonUniqueName); + + var columns = AssertDbContext.GetTempTableColumns().ToList(); + columns.Should().HaveCount(3); + + var connection = AssertDbContext.Database.GetDbConnection(); + await using var command = connection.CreateCommand(); + command.Transaction = AssertDbContext.Database.CurrentTransaction?.GetDbTransaction(); + command.CommandText = "SELECT CONVERT (varchar(256), SERVERPROPERTY('collation'))"; + var databaseCollation = (string?)await command.ExecuteScalarAsync() ?? throw new Exception("Couldn't fetch database collection."); + + ValidateColumn(columns[0], nameof(TestEntityWithCollation.Id), "uniqueidentifier", false); + ValidateColumn(columns[1], nameof(TestEntityWithCollation.ColumnWithCollation), "nvarchar", false, collation: "Japanese_CI_AS"); + ValidateColumn(columns[2], nameof(TestEntityWithCollation.ColumnWithoutCollation), "nvarchar", false, collation: databaseCollation); + } + + [Fact] + public async Task Should_create_temp_table_for_entity_with_complex_type() + { + var testEntity = ActDbContext.GetTempTableEntityType(); + + await using var tempTable = await SUT.CreateTempTableAsync(testEntity, _optionsWithNonUniqueName); + + var columns = await AssertDbContext.GetTempTableColumns(testEntity).ToListAsync(); + columns.Should().HaveCount(3); + ValidateColumn(columns[0], nameof(TestEntityWithComplexType.Id), "uniqueidentifier", false); + ValidateColumn(columns[1], $"{nameof(TestEntityWithComplexType.Boundary)}_{nameof(BoundaryValueObject.Lower)}", "int", false); + ValidateColumn(columns[2], $"{nameof(TestEntityWithComplexType.Boundary)}_{nameof(BoundaryValueObject.Upper)}", "int", false); + } + + private DbConnection CreateConnection() + { + return new SqlConnection(_connectionString); + } + + private DbContextOptions CreateOptions(DbConnection connection) + { + return TestCtxProviderBuilder.CreateOptionsBuilder(connection, Schema).Options; + } + + private static void ValidateColumn( + InformationSchemaColumn column, + string name, + string type, + bool isNullable, + byte? numericPrecision = null, + int? numericScale = null, + int? charMaxLength = null, + string? defaultValue = null, + string? collation = null) + { + ArgumentNullException.ThrowIfNull(column); + + column.COLUMN_NAME.Should().Be(name); + column.DATA_TYPE.Should().Be(type); + column.IS_NULLABLE.Should().Be(isNullable ? "YES" : "NO"); + column.COLUMN_DEFAULT.Should().Be(defaultValue); + + if (collation is not null) + column.COLLATION_NAME.Should().Be(collation); + + if (numericPrecision.HasValue) + column.NUMERIC_PRECISION.Should().Be(numericPrecision.Value); + + if (numericScale.HasValue) + column.NUMERIC_SCALE.Should().Be(numericScale.Value); + + if (charMaxLength.HasValue) + column.CHARACTER_MAXIMUM_LENGTH.Should().Be(charMaxLength.Value); + } + + private DbDefaultSchema? CreateDefaultSchema() + { + return Schema is null ? null : new DbDefaultSchema(Schema); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/TenantDatabase/TenantDatabaseTests.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/TenantDatabase/TenantDatabaseTests.cs index 42bcdae2..b2eb20c5 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/TenantDatabase/TenantDatabaseTests.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/EntityFrameworkCore/TenantDatabase/TenantDatabaseTests.cs @@ -1,83 +1,83 @@ -namespace Thinktecture.EntityFrameworkCore.TenantDatabase; - -public class TenantDatabaseTests : IntegrationTestsBase -{ - private string? _tenant; - - public TenantDatabaseTests(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - IsTenantDatabaseSupportEnabled = true; - TenantDatabaseProviderMock.GetDatabaseName(Arg.Any(), Arg.Any()).Returns((string)null!); - TenantDatabaseProviderMock.Tenant.Returns(_ => _tenant); - } - - [Fact] - public async Task Should_behave_the_same_if_no_tenant_and_database_provided() - { - TenantDatabaseProviderMock.GetDatabaseName(Schema, "TestEntities").Returns((string)null!); - await ActDbContext.TestEntities.ToListAsync(); - - ExecutedCommands.Last().Should().Contain($"FROM [{Schema}].[TestEntities]"); - } - - [Fact] - public async Task Should_behave_the_same_if_database_name_is_specified_explicitly() - { - _tenant = "1"; - var database = ArrangeDbContext.Database.GetDbConnection().Database; - TenantDatabaseProviderMock.GetDatabaseName(Schema, "TestEntities") - .Returns(database); - - await ActDbContext.TestEntities.ToListAsync(); - - ExecutedCommands.Last().Should().Contain($"FROM [{database}].[{Schema}].[TestEntities]"); - } - - [Fact] - public async Task Should_use_database_name_in_includes() - { - _tenant = "1"; - var database = ArrangeDbContext.Database.GetDbConnection().Database; - TenantDatabaseProviderMock.GetDatabaseName(Schema, "TestEntities") - .Returns(database); - - await ActDbContext.TestEntities - .Include(t => t.Parent) - .Include(t => t.Children) - .ToListAsync(); - - ExecutedCommands.Last().Should().Contain($"FROM [{database}].[{Schema}].[TestEntities]") - .And.Contain($"LEFT JOIN [{database}].[{Schema}].[TestEntities] AS [t0]") - .And.Contain($"LEFT JOIN [{database}].[{Schema}].[TestEntities] AS [t1]"); - } - - [Fact] - public async Task Should_use_database_name_in_joins() - { - _tenant = "1"; - var database = ArrangeDbContext.Database.GetDbConnection().Database; - TenantDatabaseProviderMock.GetDatabaseName(Schema, "TestEntities") - .Returns(database); - - await ActDbContext.TestEntities - .Join(ActDbContext.TestEntities, t => t.ParentId, t => t.Id, (t1, t2) => new { t1, t2 }) - .ToListAsync(); - - ExecutedCommands.Last().Should().Contain($"FROM [{database}].[{Schema}].[TestEntities]") - .And.Contain($"INNER JOIN [{database}].[{Schema}].[TestEntities]"); - } - - [Fact] - public async Task Should_use_database_name_in_views() - { - _tenant = "1"; - var database = ArrangeDbContext.Database.GetDbConnection().Database; - TenantDatabaseProviderMock.GetDatabaseName(Schema, "TestView") - .Returns(database); - - await ActDbContext.TestView.ToListAsync(); - - ExecutedCommands.Last().Should().Contain($"FROM [{database}].[{Schema}].[TestView]"); - } -} +namespace Thinktecture.EntityFrameworkCore.TenantDatabase; + +public class TenantDatabaseTests : IntegrationTestsBase +{ + private string? _tenant; + + public TenantDatabaseTests(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + IsTenantDatabaseSupportEnabled = true; + TenantDatabaseProviderMock.GetDatabaseName(Arg.Any(), Arg.Any()).Returns((string)null!); + TenantDatabaseProviderMock.Tenant.Returns(_ => _tenant); + } + + [Fact] + public async Task Should_behave_the_same_if_no_tenant_and_database_provided() + { + TenantDatabaseProviderMock.GetDatabaseName(Schema, "TestEntities").Returns((string)null!); + await ActDbContext.TestEntities.ToListAsync(); + + ExecutedCommands.Last().Should().Contain($"FROM [{Schema}].[TestEntities]"); + } + + [Fact] + public async Task Should_behave_the_same_if_database_name_is_specified_explicitly() + { + _tenant = "1"; + var database = ArrangeDbContext.Database.GetDbConnection().Database; + TenantDatabaseProviderMock.GetDatabaseName(Schema, "TestEntities") + .Returns(database); + + await ActDbContext.TestEntities.ToListAsync(); + + ExecutedCommands.Last().Should().Contain($"FROM [{database}].[{Schema}].[TestEntities]"); + } + + [Fact] + public async Task Should_use_database_name_in_includes() + { + _tenant = "1"; + var database = ArrangeDbContext.Database.GetDbConnection().Database; + TenantDatabaseProviderMock.GetDatabaseName(Schema, "TestEntities") + .Returns(database); + + await ActDbContext.TestEntities + .Include(t => t.Parent) + .Include(t => t.Children) + .ToListAsync(); + + ExecutedCommands.Last().Should().Contain($"FROM [{database}].[{Schema}].[TestEntities]") + .And.Contain($"LEFT JOIN [{database}].[{Schema}].[TestEntities] AS [t0]") + .And.Contain($"LEFT JOIN [{database}].[{Schema}].[TestEntities] AS [t1]"); + } + + [Fact] + public async Task Should_use_database_name_in_joins() + { + _tenant = "1"; + var database = ArrangeDbContext.Database.GetDbConnection().Database; + TenantDatabaseProviderMock.GetDatabaseName(Schema, "TestEntities") + .Returns(database); + + await ActDbContext.TestEntities + .Join(ActDbContext.TestEntities, t => t.ParentId, t => t.Id, (t1, t2) => new { t1, t2 }) + .ToListAsync(); + + ExecutedCommands.Last().Should().Contain($"FROM [{database}].[{Schema}].[TestEntities]") + .And.Contain($"INNER JOIN [{database}].[{Schema}].[TestEntities]"); + } + + [Fact] + public async Task Should_use_database_name_in_views() + { + _tenant = "1"; + var database = ArrangeDbContext.Database.GetDbConnection().Database; + TenantDatabaseProviderMock.GetDatabaseName(Schema, "TestView") + .Returns(database); + + await ActDbContext.TestView.ToListAsync(); + + ExecutedCommands.Last().Should().Contain($"FROM [{database}].[{Schema}].[TestView]"); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensions.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensions.cs index 6a61965e..e2f53afd 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensions.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensions.cs @@ -1,20 +1,20 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.Internal; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -public static class DbContextExtensions -{ - public static IEntityType GetEntityType(this DbContext ctx) - { - return ctx.Model.GetEntityType(typeof(T)); - } - - public static IEntityType GetTempTableEntityType(this DbContext ctx) - { - var name = EntityNameProvider.GetTempTableName(typeof(T)); - - return ctx.Model.GetEntityType(name, typeof(T)); - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.Internal; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +public static class DbContextExtensions +{ + public static IEntityType GetEntityType(this DbContext ctx) + { + return ctx.Model.GetEntityType(typeof(T)); + } + + public static IEntityType GetTempTableEntityType(this DbContext ctx) + { + var name = EntityNameProvider.GetTempTableName(typeof(T)); + + return ctx.Model.GetEntityType(name, typeof(T)); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertAsync.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertAsync.cs index 3bdd6b77..235b2370 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertAsync.cs @@ -1,80 +1,80 @@ -using Thinktecture.EntityFrameworkCore.BulkOperations; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Extensions.DbContextExtensionsTests; - -// ReSharper disable once InconsistentNaming -public class BulkInsertAsync : IntegrationTestsBase -{ - /// - public BulkInsertAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_insert_entities() - { - var testEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - var testEntities = new[] { testEntity }; - - await ActDbContext.BulkInsertAsync(testEntities); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }); - } - - [Fact] - public async Task Should_insert_specified_properties_only() - { - var testEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - var testEntities = new[] { testEntity }; - - await ActDbContext.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions - { - PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) - }); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - RequiredName = "RequiredName", - Count = 42 - }); - } - - [Fact] - public async Task Should_insert_TestEntity_with_ComplexType() - { - var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - new BoundaryValueObject(2, 5)); - - await ActDbContext.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } -} +using Thinktecture.EntityFrameworkCore.BulkOperations; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Extensions.DbContextExtensionsTests; + +// ReSharper disable once InconsistentNaming +public class BulkInsertAsync : IntegrationTestsBase +{ + /// + public BulkInsertAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_insert_entities() + { + var testEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + var testEntities = new[] { testEntity }; + + await ActDbContext.BulkInsertAsync(testEntities); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }); + } + + [Fact] + public async Task Should_insert_specified_properties_only() + { + var testEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + var testEntities = new[] { testEntity }; + + await ActDbContext.BulkInsertAsync(testEntities, new SqlServerBulkInsertOptions + { + PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) + }); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + RequiredName = "RequiredName", + Count = 42 + }); + } + + [Fact] + public async Task Should_insert_TestEntity_with_ComplexType() + { + var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + new BoundaryValueObject(2, 5)); + + await ActDbContext.BulkInsertAsync(new[] { testEntity }, new SqlServerBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertIntoTempTableAsync.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertIntoTempTableAsync.cs index ded11991..b605d926 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertIntoTempTableAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertIntoTempTableAsync.cs @@ -1,251 +1,251 @@ -using Microsoft.Data.SqlClient; -using Thinktecture.EntityFrameworkCore.BulkOperations; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Extensions.DbContextExtensionsTests; - -// ReSharper disable InconsistentNaming -public class BulkInsertIntoTempTableAsync : IntegrationTestsBase -{ - public BulkInsertIntoTempTableAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_insert_keyless_type() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(typeBuilder => typeBuilder.Property(t => t.Column2).HasMaxLength(100).IsRequired()); - - var entities = new List { new(1, "value") }; - await using var query = await ActDbContext.BulkInsertIntoTempTableAsync(entities); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] { new CustomTempTable(1, "value") }); - } - - [Fact] - public async Task Should_insert_entityType_without_touching_real_table() - { - var entity = new TestEntity - { - Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42, - ConvertibleClass = new ConvertibleClass(43) - }; - ArrangeDbContext.TestEntities.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - var entities = new List { entity }; - await using var query = await ActDbContext.BulkInsertIntoTempTableAsync(entities); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] - { - new TestEntity - { - Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42, - ConvertibleClass = new ConvertibleClass(43) - } - }); - } - - [Fact] - public async Task Should_insert_entityType_without_required_fields_if_excluded_and_with_UsePropertiesToInsertForTempTableCreation() - { - var entity = new TestEntity - { - Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730") - }; - var entities = new List { entity }; - await using var query = await ActDbContext.BulkInsertIntoTempTableAsync(entities, new SqlServerTempTableBulkInsertOptions - { - PropertiesToInsert = IEntityPropertiesProvider.Include(e => e.Id), - Advanced = { UsePropertiesToInsertForTempTableCreation = true } - }); - - var tempTable = await query.Query.Select(t => new { t.Id }).ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] - { - new { Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730") } - }); - } - - [Fact] - public async Task Should_return_disposable_query() - { - await using var tempTableQuery = await ActDbContext.BulkInsertIntoTempTableAsync(Array.Empty()); - var query = tempTableQuery.Query; - tempTableQuery.Dispose(); - - await query.Awaiting(q => q.ToListAsync()) - .Should().ThrowAsync().WithMessage("Invalid object name '#TestEntities_1'."); - } - - [Fact] - public async Task Should_return_detached_entities_for_entities_with_a_primary_key() - { - var testEntity = new TestEntity - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - RequiredName = "Name1" - }; - - await using var tempTable = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity }); - - var entities = await tempTable.Query.ToListAsync(); - - ActDbContext.Entry(entities[0]).State.Should().Be(EntityState.Detached); - } - - [Fact] - public async Task Should_not_mess_up_temp_tables_with_alternating_requests_without_disposing_previous_one() - { - var testEntity1 = new TestEntity - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - RequiredName = "Name1" - }; - var testEntity2 = new TestEntity - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - RequiredName = "Name2" - }; - - await using var tempTable1_1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 }); - await using var tempTable2_1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 }); - await using var tempTable1_2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 }); - await using var tempTable2_2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 }); - - tempTable1_1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); - tempTable1_2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); - tempTable2_1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); - tempTable2_2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); - } - - [Fact] - public async Task Should_not_mess_up_temp_tables_with_alternating_requests_with_disposing_previous_one() - { - var testEntity1 = new TestEntity - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - RequiredName = "Name1" - }; - var testEntity2 = new TestEntity - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - RequiredName = "Name2" - }; - - await using (var tempTable1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 })) - await using (var tempTable2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 })) - { - tempTable1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); - tempTable2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); - } - - await using (var tempTable1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 })) - await using (var tempTable2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 })) - { - tempTable1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); - tempTable2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); - } - } - - [Fact] - public async Task Should_properly_join_real_table_with_temp_table() - { - var realEntity = new TestEntity - { - Id = new Guid("C0A98E8F-2715-4764-A02E-033FF5278B9B"), - RequiredName = "Name1" - }; - ArrangeDbContext.Add(realEntity); - await ArrangeDbContext.SaveChangesAsync(); - - var tempEntity = new TestEntity - { - Id = new Guid("C0A98E8F-2715-4764-A02E-033FF5278B9B"), - RequiredName = "Name" - }; - - await using var tempTable = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { tempEntity }); - - var entities = await tempTable.Query - .Join(ActDbContext.TestEntities, e => e.Id, e => e.Id, (temp, real) => new { temp, real }) - .ToListAsync(); - - entities.Should().BeEquivalentTo(new[] { new { temp = tempEntity, real = realEntity } }); - - entities = await tempTable.Query - .Join(ActDbContext.TestEntities, e => e.Id, e => e.Id, (temp, real) => new { temp, real }) - .ToListAsync(); - - entities.Should().BeEquivalentTo(new[] { new { temp = tempEntity, real = realEntity } }); - } - - [Fact] - public async Task Should_throw_if_entity_contains_inlined_owned_type() - { - var testEntity = new TestEntity_Owns_Inline - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - InlineEntity = null! - }; - var testEntities = new[] { testEntity }; - - await ActDbContext.Awaiting(ctx => ctx.BulkInsertIntoTempTableAsync(testEntities)) - .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); - } - - [Fact] - public async Task Should_throw_for_entities_with_separated_owned_type() - { - var testEntity = new TestEntity_Owns_SeparateOne - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - SeparateEntity = new OwnedEntity - { - IntColumn = 42, - StringColumn = "value" - } - }; - - await ActDbContext.Awaiting(sut => sut.BulkInsertIntoTempTableAsync(new[] { testEntity })) - .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); - } - - [Fact] - public async Task Should_throw_if_entities_contain_separated_owned_type() - { - var testEntity = new TestEntity_Owns_SeparateOne - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - SeparateEntity = new OwnedEntity - { - IntColumn = 42, - StringColumn = "value" - } - }; - - await ActDbContext.Awaiting(sut => sut.BulkInsertIntoTempTableAsync(new[] { testEntity }, e => e.Id)) - .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); - } - - [Fact] - public async Task Should_insert_TestEntity_with_ComplexType() - { - var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - new BoundaryValueObject(2, 5)); - - await using var tempTable = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity }); - - var loadedEntities = await tempTable.Query.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } -} +using Microsoft.Data.SqlClient; +using Thinktecture.EntityFrameworkCore.BulkOperations; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Extensions.DbContextExtensionsTests; + +// ReSharper disable InconsistentNaming +public class BulkInsertIntoTempTableAsync : IntegrationTestsBase +{ + public BulkInsertIntoTempTableAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_insert_keyless_type() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(typeBuilder => typeBuilder.Property(t => t.Column2).HasMaxLength(100).IsRequired()); + + var entities = new List { new(1, "value") }; + await using var query = await ActDbContext.BulkInsertIntoTempTableAsync(entities); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] { new CustomTempTable(1, "value") }); + } + + [Fact] + public async Task Should_insert_entityType_without_touching_real_table() + { + var entity = new TestEntity + { + Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42, + ConvertibleClass = new ConvertibleClass(43) + }; + ArrangeDbContext.TestEntities.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + var entities = new List { entity }; + await using var query = await ActDbContext.BulkInsertIntoTempTableAsync(entities); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] + { + new TestEntity + { + Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42, + ConvertibleClass = new ConvertibleClass(43) + } + }); + } + + [Fact] + public async Task Should_insert_entityType_without_required_fields_if_excluded_and_with_UsePropertiesToInsertForTempTableCreation() + { + var entity = new TestEntity + { + Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730") + }; + var entities = new List { entity }; + await using var query = await ActDbContext.BulkInsertIntoTempTableAsync(entities, new SqlServerTempTableBulkInsertOptions + { + PropertiesToInsert = IEntityPropertiesProvider.Include(e => e.Id), + Advanced = { UsePropertiesToInsertForTempTableCreation = true } + }); + + var tempTable = await query.Query.Select(t => new { t.Id }).ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] + { + new { Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730") } + }); + } + + [Fact] + public async Task Should_return_disposable_query() + { + await using var tempTableQuery = await ActDbContext.BulkInsertIntoTempTableAsync(Array.Empty()); + var query = tempTableQuery.Query; + tempTableQuery.Dispose(); + + await query.Awaiting(q => q.ToListAsync()) + .Should().ThrowAsync().WithMessage("Invalid object name '#TestEntities_1'."); + } + + [Fact] + public async Task Should_return_detached_entities_for_entities_with_a_primary_key() + { + var testEntity = new TestEntity + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + RequiredName = "Name1" + }; + + await using var tempTable = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity }); + + var entities = await tempTable.Query.ToListAsync(); + + ActDbContext.Entry(entities[0]).State.Should().Be(EntityState.Detached); + } + + [Fact] + public async Task Should_not_mess_up_temp_tables_with_alternating_requests_without_disposing_previous_one() + { + var testEntity1 = new TestEntity + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + RequiredName = "Name1" + }; + var testEntity2 = new TestEntity + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + RequiredName = "Name2" + }; + + await using var tempTable1_1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 }); + await using var tempTable2_1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 }); + await using var tempTable1_2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 }); + await using var tempTable2_2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 }); + + tempTable1_1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); + tempTable1_2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); + tempTable2_1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); + tempTable2_2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); + } + + [Fact] + public async Task Should_not_mess_up_temp_tables_with_alternating_requests_with_disposing_previous_one() + { + var testEntity1 = new TestEntity + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + RequiredName = "Name1" + }; + var testEntity2 = new TestEntity + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + RequiredName = "Name2" + }; + + await using (var tempTable1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 })) + await using (var tempTable2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 })) + { + tempTable1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); + tempTable2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); + } + + await using (var tempTable1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 })) + await using (var tempTable2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 })) + { + tempTable1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); + tempTable2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); + } + } + + [Fact] + public async Task Should_properly_join_real_table_with_temp_table() + { + var realEntity = new TestEntity + { + Id = new Guid("C0A98E8F-2715-4764-A02E-033FF5278B9B"), + RequiredName = "Name1" + }; + ArrangeDbContext.Add(realEntity); + await ArrangeDbContext.SaveChangesAsync(); + + var tempEntity = new TestEntity + { + Id = new Guid("C0A98E8F-2715-4764-A02E-033FF5278B9B"), + RequiredName = "Name" + }; + + await using var tempTable = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { tempEntity }); + + var entities = await tempTable.Query + .Join(ActDbContext.TestEntities, e => e.Id, e => e.Id, (temp, real) => new { temp, real }) + .ToListAsync(); + + entities.Should().BeEquivalentTo(new[] { new { temp = tempEntity, real = realEntity } }); + + entities = await tempTable.Query + .Join(ActDbContext.TestEntities, e => e.Id, e => e.Id, (temp, real) => new { temp, real }) + .ToListAsync(); + + entities.Should().BeEquivalentTo(new[] { new { temp = tempEntity, real = realEntity } }); + } + + [Fact] + public async Task Should_throw_if_entity_contains_inlined_owned_type() + { + var testEntity = new TestEntity_Owns_Inline + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + InlineEntity = null! + }; + var testEntities = new[] { testEntity }; + + await ActDbContext.Awaiting(ctx => ctx.BulkInsertIntoTempTableAsync(testEntities)) + .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); + } + + [Fact] + public async Task Should_throw_for_entities_with_separated_owned_type() + { + var testEntity = new TestEntity_Owns_SeparateOne + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + SeparateEntity = new OwnedEntity + { + IntColumn = 42, + StringColumn = "value" + } + }; + + await ActDbContext.Awaiting(sut => sut.BulkInsertIntoTempTableAsync(new[] { testEntity })) + .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); + } + + [Fact] + public async Task Should_throw_if_entities_contain_separated_owned_type() + { + var testEntity = new TestEntity_Owns_SeparateOne + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + SeparateEntity = new OwnedEntity + { + IntColumn = 42, + StringColumn = "value" + } + }; + + await ActDbContext.Awaiting(sut => sut.BulkInsertIntoTempTableAsync(new[] { testEntity }, e => e.Id)) + .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); + } + + [Fact] + public async Task Should_insert_TestEntity_with_ComplexType() + { + var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + new BoundaryValueObject(2, 5)); + + await using var tempTable = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity }); + + var loadedEntities = await tempTable.Query.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertOrUpdateAsync.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertOrUpdateAsync.cs index 86c91c7a..7737dbfa 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertOrUpdateAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertOrUpdateAsync.cs @@ -1,312 +1,312 @@ -using Thinktecture.EntityFrameworkCore.BulkOperations; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Extensions.DbContextExtensionsTests; - -// ReSharper disable once InconsistentNaming -public class BulkInsertOrUpdateAsync : IntegrationTestsBase -{ - /// - public BulkInsertOrUpdateAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_insert_non_existing_entities() - { - var testEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - - var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { testEntity }); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] - { - new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - } - }); - } - - [Fact] - public async Task Should_update_existing_entities() - { - var testEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - ArrangeDbContext.Add(testEntity); - await ArrangeDbContext.SaveChangesAsync(); - - testEntity.Name = "changed"; - testEntity.Count = 43; - - var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { testEntity }); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] - { - new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "changed", - RequiredName = "RequiredName", - Count = 43 - } - }); - } - - [Fact] - public async Task Should_insert_and_update_specified_properties_only() - { - var existingEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - var newEntity = new TestEntity - { - Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), - Name = "new", - RequiredName = "RequiredName", - Count = 1 - }; - - existingEntity.Name = "changed"; - existingEntity.Count = 43; - - var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, - new SqlServerBulkInsertOrUpdateOptions - { - PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()), - PropertiesToUpdate = IEntityPropertiesProvider.Include(entity => entity.Name) - } - ); - - affectedRows.Should().Be(2); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] - { - new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "changed", - RequiredName = "RequiredName", - Count = 42 - }, - new TestEntity - { - Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), - Name = null, // is not a required property - RequiredName = "RequiredName", - Count = 1 - } - }); - } - - [Fact] - public async Task Should_match_on_provided_properties() - { - var entity_1 = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", // matching criteria - RequiredName = "RequiredName", - Count = 42 - }; - var entity_2 = new TestEntity - { - Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), - Name = "other", - RequiredName = "RequiredName", - Count = 1 - }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - var testEntity = new TestEntity - { - Id = entity_2.Id, - Name = entity_1.Name, // matching criteria - RequiredName = "RequiredName", - Count = 100 - }; - - var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { testEntity }, - propertiesToUpdate: entity => entity.Count, - propertiesToMatchOn: entity => entity.Name); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] - { - new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 100 // the only updated value - }, - new TestEntity - { - Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), - Name = "other", - RequiredName = "RequiredName", - Count = 1 - } - }); - } - - [Fact] - public async Task Should_skip_update_if_no_properties_to_update() - { - var existingEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - var newEntity = new TestEntity - { - Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), - Name = "new", - RequiredName = "RequiredName", - Count = 1 - }; - - existingEntity.Name = "changed"; - existingEntity.Count = 43; - - var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, - new SqlServerBulkInsertOrUpdateOptions - { - PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()), - PropertiesToUpdate = IEntityPropertiesProvider.Empty - } - ); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] - { - new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }, - new TestEntity - { - Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), - Name = null, // is not a required property - RequiredName = "RequiredName", - Count = 1 - } - }); - } - - [Fact] - public async Task Should_skip_update_if_provided_key_properties_only() - { - var existingEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - var newEntity = new TestEntity - { - Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), - Name = "new", - RequiredName = "RequiredName", - Count = 1 - }; - - existingEntity.Name = "changed"; - existingEntity.Count = 43; - - var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, - new SqlServerBulkInsertOrUpdateOptions - { - PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()), - PropertiesToUpdate = IEntityPropertiesProvider.Include(entity => entity.Id) - } - ); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] - { - new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }, - new TestEntity - { - Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), - Name = null, // is not a required property - RequiredName = "RequiredName", - Count = 1 - } - }); - } - - [Fact] - public async Task Should_insert_and_update_TestEntity_with_ComplexType() - { - // Arrange - var testEntity_1 = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - new BoundaryValueObject(2, 5)); - - ArrangeDbContext.Add(testEntity_1); - await ArrangeDbContext.SaveChangesAsync(); - - // Act - testEntity_1.Boundary = new BoundaryValueObject(10, 20); - var testEntity_2 = new TestEntityWithComplexType(new Guid("67A9500B-CF51-4A39-8C89-F2EBF7EDE84D"), - new BoundaryValueObject(3, 4)); - - await ActDbContext.BulkInsertOrUpdateAsync(new[] { testEntity_1, testEntity_2 }); - - var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity_1, testEntity_2 }); - } -} +using Thinktecture.EntityFrameworkCore.BulkOperations; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Extensions.DbContextExtensionsTests; + +// ReSharper disable once InconsistentNaming +public class BulkInsertOrUpdateAsync : IntegrationTestsBase +{ + /// + public BulkInsertOrUpdateAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_insert_non_existing_entities() + { + var testEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + + var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { testEntity }); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] + { + new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + } + }); + } + + [Fact] + public async Task Should_update_existing_entities() + { + var testEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + ArrangeDbContext.Add(testEntity); + await ArrangeDbContext.SaveChangesAsync(); + + testEntity.Name = "changed"; + testEntity.Count = 43; + + var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { testEntity }); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] + { + new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "changed", + RequiredName = "RequiredName", + Count = 43 + } + }); + } + + [Fact] + public async Task Should_insert_and_update_specified_properties_only() + { + var existingEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + var newEntity = new TestEntity + { + Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), + Name = "new", + RequiredName = "RequiredName", + Count = 1 + }; + + existingEntity.Name = "changed"; + existingEntity.Count = 43; + + var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, + new SqlServerBulkInsertOrUpdateOptions + { + PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()), + PropertiesToUpdate = IEntityPropertiesProvider.Include(entity => entity.Name) + } + ); + + affectedRows.Should().Be(2); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] + { + new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "changed", + RequiredName = "RequiredName", + Count = 42 + }, + new TestEntity + { + Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), + Name = null, // is not a required property + RequiredName = "RequiredName", + Count = 1 + } + }); + } + + [Fact] + public async Task Should_match_on_provided_properties() + { + var entity_1 = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", // matching criteria + RequiredName = "RequiredName", + Count = 42 + }; + var entity_2 = new TestEntity + { + Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), + Name = "other", + RequiredName = "RequiredName", + Count = 1 + }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + var testEntity = new TestEntity + { + Id = entity_2.Id, + Name = entity_1.Name, // matching criteria + RequiredName = "RequiredName", + Count = 100 + }; + + var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { testEntity }, + propertiesToUpdate: entity => entity.Count, + propertiesToMatchOn: entity => entity.Name); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] + { + new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 100 // the only updated value + }, + new TestEntity + { + Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), + Name = "other", + RequiredName = "RequiredName", + Count = 1 + } + }); + } + + [Fact] + public async Task Should_skip_update_if_no_properties_to_update() + { + var existingEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + var newEntity = new TestEntity + { + Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), + Name = "new", + RequiredName = "RequiredName", + Count = 1 + }; + + existingEntity.Name = "changed"; + existingEntity.Count = 43; + + var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, + new SqlServerBulkInsertOrUpdateOptions + { + PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()), + PropertiesToUpdate = IEntityPropertiesProvider.Empty + } + ); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] + { + new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }, + new TestEntity + { + Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), + Name = null, // is not a required property + RequiredName = "RequiredName", + Count = 1 + } + }); + } + + [Fact] + public async Task Should_skip_update_if_provided_key_properties_only() + { + var existingEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + var newEntity = new TestEntity + { + Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), + Name = "new", + RequiredName = "RequiredName", + Count = 1 + }; + + existingEntity.Name = "changed"; + existingEntity.Count = 43; + + var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, + new SqlServerBulkInsertOrUpdateOptions + { + PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()), + PropertiesToUpdate = IEntityPropertiesProvider.Include(entity => entity.Id) + } + ); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] + { + new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }, + new TestEntity + { + Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), + Name = null, // is not a required property + RequiredName = "RequiredName", + Count = 1 + } + }); + } + + [Fact] + public async Task Should_insert_and_update_TestEntity_with_ComplexType() + { + // Arrange + var testEntity_1 = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + new BoundaryValueObject(2, 5)); + + ArrangeDbContext.Add(testEntity_1); + await ArrangeDbContext.SaveChangesAsync(); + + // Act + testEntity_1.Boundary = new BoundaryValueObject(10, 20); + var testEntity_2 = new TestEntityWithComplexType(new Guid("67A9500B-CF51-4A39-8C89-F2EBF7EDE84D"), + new BoundaryValueObject(3, 4)); + + await ActDbContext.BulkInsertOrUpdateAsync(new[] { testEntity_1, testEntity_2 }); + + var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity_1, testEntity_2 }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertValuesIntoTempTableAsync_1_Column.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertValuesIntoTempTableAsync_1_Column.cs index 441b8870..e9206728 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertValuesIntoTempTableAsync_1_Column.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertValuesIntoTempTableAsync_1_Column.cs @@ -1,208 +1,208 @@ -using Microsoft.Data.SqlClient; -using Thinktecture.EntityFrameworkCore.BulkOperations; -using Thinktecture.EntityFrameworkCore.TempTables; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Extensions.DbContextExtensionsTests; - -// ReSharper disable once InconsistentNaming -public class BulkInsertValuesIntoTempTableAsync_1_Column : IntegrationTestsBase -{ - public BulkInsertValuesIntoTempTableAsync_1_Column(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_insert_int() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var values = new List { 1, 2 }; - await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] { 1, 2 }); - } - - [Fact] - public async Task Should_throw_if_inserting_duplicates() - { - ConfigureModel = builder => builder.ConfigureTempTable(false); - - var values = new List { 1, 1 }; - await ActDbContext.Awaiting(ctx => ctx.BulkInsertValuesIntoTempTableAsync(values)) - .Should().ThrowAsync().Where(ex => ex.Message.StartsWith("The CREATE UNIQUE INDEX statement terminated because a duplicate key was found", StringComparison.Ordinal)); - } - - [Fact] - public async Task Should_insert_int_with_streaming_disabled() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var options = new SqlServerTempTableBulkInsertOptions { EnableStreaming = false }; - var values = new List { 1, 2 }; - await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, options); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] { 1, 2 }); - } - - [Fact] - public async Task Should_insert_nullable_int_of_a_keyless_entity() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var values = new List { 1, null }; - await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqlServerTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] { 1, (int?)null }); - } - - [Fact] - public async Task Should_insert_nullable_int_of_entity_with_a_key() - { - ConfigureModel = builder => builder.ConfigureTempTable(false, typeBuilder => - { - typeBuilder.HasKey(t => t.Column1); - typeBuilder.Property(t => t.Column1).ValueGeneratedNever(); - }); - - var values = new List { 1, 2 }; - await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqlServerTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] { 1, 2 }); - } - - [Fact] - public async Task Should_insert_string() - { - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).IsRequired(false)); - - var values = new List { "value1", null }; - await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqlServerTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] { "value1", null }); - } - - [Fact] - public async Task Should_create_pk_by_default_on_string_column() - { - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).HasMaxLength(100).IsRequired()); - - await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { "value" }, new SqlServerTempTableBulkInsertOptions - { - TableNameProvider = DefaultTempTableNameProvider.Instance, - PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced - }); - - var keys = AssertDbContext.GetTempTableKeyColumns>().ToList(); - keys.Should().HaveCount(1); - keys[0].COLUMN_NAME.Should().Be(nameof(TempTable.Column1)); - } - - [Fact] - public async Task Should_throw_when_trying_to_create_pk_on_nullable_column() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await ActDbContext.Awaiting(ctx => ctx.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqlServerTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced })) - .Should().ThrowAsync(); - } - - [Fact] - public async Task Should_create_pk_by_default() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqlServerTempTableBulkInsertOptions - { - TableNameProvider = DefaultTempTableNameProvider.Instance, - PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced - }); - - var keys = ArrangeDbContext.GetTempTableKeyColumns>().ToList(); - keys.Should().HaveCount(1); - keys[0].COLUMN_NAME.Should().Be(nameof(TempTable.Column1)); - } - - [Fact] - public async Task Should_not_create_pk_if_entity_is_keyless_and_provider_is_AccordingToEntityTypeConfiguration() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqlServerTempTableBulkInsertOptions - { - TableNameProvider = DefaultTempTableNameProvider.Instance, - PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.EntityTypeConfiguration - }); - - var keys = ArrangeDbContext.GetTempTableKeyColumns>().ToList(); - keys.Should().HaveCount(0); - } - - [Fact] - public async Task Should_not_create_pk_if_specified_in_options() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqlServerTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); - - var keys = ArrangeDbContext.GetTempTableKeyColumns>().ToList(); - keys.Should().HaveCount(0); - } - - [Fact] - public async Task Should_work_with_projection_using_SplitQuery_behavior() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var parentId = new Guid("6EB48130-454B-46BC-9FEE-CDF411F69807"); - var childId = new Guid("17A2014C-F7B2-40E8-82F4-42E754DCAA2D"); - ArrangeDbContext.TestEntities.Add(new TestEntity - { - Id = parentId, - RequiredName = "RequiredName", - Children = { new() { Id = childId, RequiredName = "RequiredName" } } - }); - await ArrangeDbContext.SaveChangesAsync(); - - await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new[] { parentId }); - - var query = await ActDbContext.TestEntities - .AsSplitQuery() - .Where(c => tempTable.Query.Any(id => c.Id == id)) - .Select(c => new - { - c.Id, - ChildrenIds = c.Children.Select(o => o.Id).ToList() - }) - .ToListAsync(); - - query.Should().BeEquivalentTo(new[] - { - new - { - Id = parentId, - ChildrenIds = new List { childId } - } - }); - } - - [Fact] - public async Task Should_join_with_itself() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new[] { Guid.Empty }); - - var joinQuery = tempTable.Query.LeftJoin(tempTable.Query, e => e, e => e); - - joinQuery.ToQueryString().Should().Be("SELECT [#].[Column1] AS [Left], [#0].[Column1] AS [Right]" + Environment.NewLine + - "FROM [#TempTable_1] AS [#]" + Environment.NewLine + - "LEFT JOIN [#TempTable_1] AS [#0] ON [#].[Column1] = [#0].[Column1]"); - } -} +using Microsoft.Data.SqlClient; +using Thinktecture.EntityFrameworkCore.BulkOperations; +using Thinktecture.EntityFrameworkCore.TempTables; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Extensions.DbContextExtensionsTests; + +// ReSharper disable once InconsistentNaming +public class BulkInsertValuesIntoTempTableAsync_1_Column : IntegrationTestsBase +{ + public BulkInsertValuesIntoTempTableAsync_1_Column(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_insert_int() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var values = new List { 1, 2 }; + await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] { 1, 2 }); + } + + [Fact] + public async Task Should_throw_if_inserting_duplicates() + { + ConfigureModel = builder => builder.ConfigureTempTable(false); + + var values = new List { 1, 1 }; + await ActDbContext.Awaiting(ctx => ctx.BulkInsertValuesIntoTempTableAsync(values)) + .Should().ThrowAsync().Where(ex => ex.Message.StartsWith("The CREATE UNIQUE INDEX statement terminated because a duplicate key was found", StringComparison.Ordinal)); + } + + [Fact] + public async Task Should_insert_int_with_streaming_disabled() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var options = new SqlServerTempTableBulkInsertOptions { EnableStreaming = false }; + var values = new List { 1, 2 }; + await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, options); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] { 1, 2 }); + } + + [Fact] + public async Task Should_insert_nullable_int_of_a_keyless_entity() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var values = new List { 1, null }; + await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqlServerTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] { 1, (int?)null }); + } + + [Fact] + public async Task Should_insert_nullable_int_of_entity_with_a_key() + { + ConfigureModel = builder => builder.ConfigureTempTable(false, typeBuilder => + { + typeBuilder.HasKey(t => t.Column1); + typeBuilder.Property(t => t.Column1).ValueGeneratedNever(); + }); + + var values = new List { 1, 2 }; + await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqlServerTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] { 1, 2 }); + } + + [Fact] + public async Task Should_insert_string() + { + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).IsRequired(false)); + + var values = new List { "value1", null }; + await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqlServerTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] { "value1", null }); + } + + [Fact] + public async Task Should_create_pk_by_default_on_string_column() + { + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).HasMaxLength(100).IsRequired()); + + await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { "value" }, new SqlServerTempTableBulkInsertOptions + { + TableNameProvider = DefaultTempTableNameProvider.Instance, + PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced + }); + + var keys = AssertDbContext.GetTempTableKeyColumns>().ToList(); + keys.Should().HaveCount(1); + keys[0].COLUMN_NAME.Should().Be(nameof(TempTable.Column1)); + } + + [Fact] + public async Task Should_throw_when_trying_to_create_pk_on_nullable_column() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await ActDbContext.Awaiting(ctx => ctx.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqlServerTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced })) + .Should().ThrowAsync(); + } + + [Fact] + public async Task Should_create_pk_by_default() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqlServerTempTableBulkInsertOptions + { + TableNameProvider = DefaultTempTableNameProvider.Instance, + PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced + }); + + var keys = ArrangeDbContext.GetTempTableKeyColumns>().ToList(); + keys.Should().HaveCount(1); + keys[0].COLUMN_NAME.Should().Be(nameof(TempTable.Column1)); + } + + [Fact] + public async Task Should_not_create_pk_if_entity_is_keyless_and_provider_is_AccordingToEntityTypeConfiguration() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqlServerTempTableBulkInsertOptions + { + TableNameProvider = DefaultTempTableNameProvider.Instance, + PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.EntityTypeConfiguration + }); + + var keys = ArrangeDbContext.GetTempTableKeyColumns>().ToList(); + keys.Should().HaveCount(0); + } + + [Fact] + public async Task Should_not_create_pk_if_specified_in_options() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqlServerTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); + + var keys = ArrangeDbContext.GetTempTableKeyColumns>().ToList(); + keys.Should().HaveCount(0); + } + + [Fact] + public async Task Should_work_with_projection_using_SplitQuery_behavior() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var parentId = new Guid("6EB48130-454B-46BC-9FEE-CDF411F69807"); + var childId = new Guid("17A2014C-F7B2-40E8-82F4-42E754DCAA2D"); + ArrangeDbContext.TestEntities.Add(new TestEntity + { + Id = parentId, + RequiredName = "RequiredName", + Children = { new() { Id = childId, RequiredName = "RequiredName" } } + }); + await ArrangeDbContext.SaveChangesAsync(); + + await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new[] { parentId }); + + var query = await ActDbContext.TestEntities + .AsSplitQuery() + .Where(c => tempTable.Query.Any(id => c.Id == id)) + .Select(c => new + { + c.Id, + ChildrenIds = c.Children.Select(o => o.Id).ToList() + }) + .ToListAsync(); + + query.Should().BeEquivalentTo(new[] + { + new + { + Id = parentId, + ChildrenIds = new List { childId } + } + }); + } + + [Fact] + public async Task Should_join_with_itself() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new[] { Guid.Empty }); + + var joinQuery = tempTable.Query.LeftJoin(tempTable.Query, e => e, e => e); + + joinQuery.ToQueryString().Should().Be("SELECT [#].[Column1] AS [Left], [#0].[Column1] AS [Right]" + Environment.NewLine + + "FROM [#TempTable_1] AS [#]" + Environment.NewLine + + "LEFT JOIN [#TempTable_1] AS [#0] ON [#].[Column1] = [#0].[Column1]"); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertValuesIntoTempTableAsync_2_Column.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertValuesIntoTempTableAsync_2_Column.cs index 18ee23a3..6190528b 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertValuesIntoTempTableAsync_2_Column.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkInsertValuesIntoTempTableAsync_2_Column.cs @@ -1,50 +1,50 @@ -using Thinktecture.EntityFrameworkCore.BulkOperations; -using Thinktecture.EntityFrameworkCore.TempTables; - -namespace Thinktecture.Extensions.DbContextExtensionsTests; - -// ReSharper disable once InconsistentNaming -public class BulkInsertValuesIntoTempTableAsync_2_Column : IntegrationTestsBase -{ - public BulkInsertValuesIntoTempTableAsync_2_Column(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_insert_int_nullable_int() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var values = new List<(int, int?)> { (1, null) }; - await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqlServerTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] { new TempTable(1, null) }); - } - - [Fact] - public async Task Should_insert_string_string() - { - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column2).IsRequired(false)); - - var values = new List<(string, string?)> { ("value1", null) }; - await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqlServerTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] { new TempTable("value1", null) }); - } - - [Fact] - public async Task Should_create_pk_by_default() - { - ConfigureModel = builder => builder.ConfigureTempTable(false); - - await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List<(int, int)> { (1, 2) }, new SqlServerTempTableBulkInsertOptions { TableNameProvider = DefaultTempTableNameProvider.Instance }); - - var keys = AssertDbContext.GetTempTableKeyColumns().ToList(); - keys.Should().HaveCount(2); - keys[0].COLUMN_NAME.Should().Be(nameof(TempTable.Column1)); - keys[1].COLUMN_NAME.Should().Be(nameof(TempTable.Column2)); - } -} +using Thinktecture.EntityFrameworkCore.BulkOperations; +using Thinktecture.EntityFrameworkCore.TempTables; + +namespace Thinktecture.Extensions.DbContextExtensionsTests; + +// ReSharper disable once InconsistentNaming +public class BulkInsertValuesIntoTempTableAsync_2_Column : IntegrationTestsBase +{ + public BulkInsertValuesIntoTempTableAsync_2_Column(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_insert_int_nullable_int() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var values = new List<(int, int?)> { (1, null) }; + await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqlServerTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] { new TempTable(1, null) }); + } + + [Fact] + public async Task Should_insert_string_string() + { + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column2).IsRequired(false)); + + var values = new List<(string, string?)> { ("value1", null) }; + await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqlServerTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] { new TempTable("value1", null) }); + } + + [Fact] + public async Task Should_create_pk_by_default() + { + ConfigureModel = builder => builder.ConfigureTempTable(false); + + await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List<(int, int)> { (1, 2) }, new SqlServerTempTableBulkInsertOptions { TableNameProvider = DefaultTempTableNameProvider.Instance }); + + var keys = AssertDbContext.GetTempTableKeyColumns().ToList(); + keys.Should().HaveCount(2); + keys[0].COLUMN_NAME.Should().Be(nameof(TempTable.Column1)); + keys[1].COLUMN_NAME.Should().Be(nameof(TempTable.Column2)); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkUpdateAsync.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkUpdateAsync.cs index 487a153e..646c9095 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkUpdateAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/BulkUpdateAsync.cs @@ -1,297 +1,297 @@ -using Thinktecture.EntityFrameworkCore.BulkOperations; -using Thinktecture.TestDatabaseContext; - -// ReSharper disable InconsistentNaming - -namespace Thinktecture.Extensions.DbContextExtensionsTests; - -// ReSharper disable once InconsistentNaming -public class BulkUpdateAsync : IntegrationTestsBase -{ - public BulkUpdateAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_not_throw_if_entities_is_empty() - { - var affectedRows = await ActDbContext.BulkUpdateAsync(new List()); - - affectedRows.Should().Be(0); - } - - [Fact] - public async Task Should_not_throw_if_key_property_is_not_present_in_PropertiesToUpdate() - { - var affectedRows = await ActDbContext.BulkUpdateAsync(new List(), entity => new { entity.Name }); - - affectedRows.Should().Be(0); - } - - [Fact] - public async Task Should_update_column_with_converter() - { - var entity = new TestEntity { RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.ConvertibleClass = new ConvertibleClass(43); - - var affectedRows = await ActDbContext.BulkUpdateAsync(new List { entity }); - - affectedRows.Should().Be(1); - - var loadedEntity = AssertDbContext.TestEntities.Single(); - loadedEntity.ConvertibleClass.Should().NotBeNull(); - loadedEntity.ConvertibleClass!.Key.Should().Be(43); - } - - [Fact] - public async Task Should_update_entities() - { - var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - entity_1.Name = "Name"; - entity_1.Count = 1; - entity_2.Name = "OtherName"; - entity_2.Count = 2; - - var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity_1, entity_2 }); - - affectedRows.Should().Be(2); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); - } - - [Fact] - public async Task Should_update_entities_based_on_non_pk_property() - { - var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "value", RequiredName = "RequiredName" }; - var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), Name = null, RequiredName = "RequiredName" }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - entity_1.Name = null; - entity_1.Count = 1; - entity_2.Name = "value"; - entity_2.Count = 2; - - var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity_1, entity_2 }, - e => e.Count, - e => e.Name); - - affectedRows.Should().Be(2); - - entity_1.Name = "value"; - entity_2.Name = null; - - entity_1.Count = 2; - entity_2.Count = 1; - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); - } - - [Fact] - public async Task Should_update_provided_entity_only() - { - var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - entity_1.Name = "Name"; - entity_1.Count = 1; - entity_2.Name = "OtherName"; - entity_2.Count = 2; - - var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity_1 }); - - affectedRows.Should().Be(1); - - entity_2.Name = default; - entity_2.Count = default; - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); - } - - [Fact] - public async Task Should_update_private_property() - { - var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.SetPrivateField(1); - - var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity }); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntities.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - loadedEntity!.GetPrivateField().Should().Be(1); - } - - [Fact] - public async Task Should_update_shadow_properties() - { - var entity = new TestEntityWithShadowProperties { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866") }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - ActDbContext.Entry(entity).Property("ShadowStringProperty").CurrentValue = "value"; - ActDbContext.Entry(entity).Property("ShadowIntProperty").CurrentValue = 42; - - var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity }); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntitiesWithShadowProperties.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - AssertDbContext.Entry(loadedEntity!).Property("ShadowStringProperty").CurrentValue.Should().Be("value"); - AssertDbContext.Entry(loadedEntity!).Property("ShadowIntProperty").CurrentValue.Should().Be(42); - } - - [Fact] - public async Task Should_write_not_nullable_structs_as_is_despite_sql_default_value() - { - var entity = new TestEntityWithSqlDefaultValues - { - Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), - Int = 1, - String = "value", - NullableInt = 2, - NullableString = "otherValue" - }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Int = 0; - entity.String = "other value"; - entity.NullableInt = null; - entity.NullableString = null; - - var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity }); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntitiesWithDefaultValues.FirstOrDefaultAsync(); - loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues - { - Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), - Int = 0, // persisted as-is - NullableInt = 2, // DEFAULT value constraint - String = "other value", - NullableString = "4" // DEFAULT value constraint - }); - } - - [Fact] - public async Task Should_ignore_RowVersion() - { - var entity = new TestEntityWithRowVersion { Id = new Guid("EBC95620-4D80-4318-9B92-AD7528B2965C") }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.RowVersion = Int32.MaxValue; - - var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity }); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntitiesWithRowVersion.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - loadedEntity!.Id.Should().Be(new Guid("EBC95620-4D80-4318-9B92-AD7528B2965C")); - loadedEntity.RowVersion.Should().NotBe(Int32.MaxValue); - } - - [Fact] - public async Task Should_update_specified_properties_only() - { - var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "original value", RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Name = "Name"; // this would not be updated - entity.Count = 42; - entity.PropertyWithBackingField = 7; - entity.SetPrivateField(3); - - var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity }, - new SqlServerBulkUpdateOptions - { - PropertiesToUpdate = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) - }); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Count = 42, - PropertyWithBackingField = 7, - Name = "original value", - RequiredName = "RequiredName" - }); - loadedEntity.GetPrivateField().Should().Be(3); - } - - [Fact] - public async Task Should_update_entity_without_required_fields_if_excluded() - { - var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Name = "Name"; // this would not be updated - entity.RequiredName = null!; // this would not be updated - entity.Count = 42; - - var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity }, - new SqlServerBulkUpdateOptions - { - PropertiesToUpdate = IEntityPropertiesProvider.Include(e => e.Count) - }); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Count = 42, - RequiredName = "RequiredName" - }); - } - - [Fact] - public async Task Should_insert_and_update_TestEntity_with_ComplexType() - { - // Arrange - var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - new BoundaryValueObject(2, 5)); - - ArrangeDbContext.Add(testEntity); - await ArrangeDbContext.SaveChangesAsync(); - - // Act - testEntity.Boundary = new BoundaryValueObject(10, 20); - - await ActDbContext.BulkUpdateAsync(new[] { testEntity }, new SqlServerBulkUpdateOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } -} +using Thinktecture.EntityFrameworkCore.BulkOperations; +using Thinktecture.TestDatabaseContext; + +// ReSharper disable InconsistentNaming + +namespace Thinktecture.Extensions.DbContextExtensionsTests; + +// ReSharper disable once InconsistentNaming +public class BulkUpdateAsync : IntegrationTestsBase +{ + public BulkUpdateAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_not_throw_if_entities_is_empty() + { + var affectedRows = await ActDbContext.BulkUpdateAsync(new List()); + + affectedRows.Should().Be(0); + } + + [Fact] + public async Task Should_not_throw_if_key_property_is_not_present_in_PropertiesToUpdate() + { + var affectedRows = await ActDbContext.BulkUpdateAsync(new List(), entity => new { entity.Name }); + + affectedRows.Should().Be(0); + } + + [Fact] + public async Task Should_update_column_with_converter() + { + var entity = new TestEntity { RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.ConvertibleClass = new ConvertibleClass(43); + + var affectedRows = await ActDbContext.BulkUpdateAsync(new List { entity }); + + affectedRows.Should().Be(1); + + var loadedEntity = AssertDbContext.TestEntities.Single(); + loadedEntity.ConvertibleClass.Should().NotBeNull(); + loadedEntity.ConvertibleClass!.Key.Should().Be(43); + } + + [Fact] + public async Task Should_update_entities() + { + var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + entity_1.Name = "Name"; + entity_1.Count = 1; + entity_2.Name = "OtherName"; + entity_2.Count = 2; + + var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity_1, entity_2 }); + + affectedRows.Should().Be(2); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); + } + + [Fact] + public async Task Should_update_entities_based_on_non_pk_property() + { + var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "value", RequiredName = "RequiredName" }; + var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), Name = null, RequiredName = "RequiredName" }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + entity_1.Name = null; + entity_1.Count = 1; + entity_2.Name = "value"; + entity_2.Count = 2; + + var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity_1, entity_2 }, + e => e.Count, + e => e.Name); + + affectedRows.Should().Be(2); + + entity_1.Name = "value"; + entity_2.Name = null; + + entity_1.Count = 2; + entity_2.Count = 1; + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); + } + + [Fact] + public async Task Should_update_provided_entity_only() + { + var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + entity_1.Name = "Name"; + entity_1.Count = 1; + entity_2.Name = "OtherName"; + entity_2.Count = 2; + + var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity_1 }); + + affectedRows.Should().Be(1); + + entity_2.Name = default; + entity_2.Count = default; + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); + } + + [Fact] + public async Task Should_update_private_property() + { + var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.SetPrivateField(1); + + var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity }); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntities.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + loadedEntity!.GetPrivateField().Should().Be(1); + } + + [Fact] + public async Task Should_update_shadow_properties() + { + var entity = new TestEntityWithShadowProperties { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866") }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + ActDbContext.Entry(entity).Property("ShadowStringProperty").CurrentValue = "value"; + ActDbContext.Entry(entity).Property("ShadowIntProperty").CurrentValue = 42; + + var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity }); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntitiesWithShadowProperties.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + AssertDbContext.Entry(loadedEntity!).Property("ShadowStringProperty").CurrentValue.Should().Be("value"); + AssertDbContext.Entry(loadedEntity!).Property("ShadowIntProperty").CurrentValue.Should().Be(42); + } + + [Fact] + public async Task Should_write_not_nullable_structs_as_is_despite_sql_default_value() + { + var entity = new TestEntityWithSqlDefaultValues + { + Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), + Int = 1, + String = "value", + NullableInt = 2, + NullableString = "otherValue" + }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Int = 0; + entity.String = "other value"; + entity.NullableInt = null; + entity.NullableString = null; + + var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity }); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntitiesWithDefaultValues.FirstOrDefaultAsync(); + loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues + { + Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), + Int = 0, // persisted as-is + NullableInt = 2, // DEFAULT value constraint + String = "other value", + NullableString = "4" // DEFAULT value constraint + }); + } + + [Fact] + public async Task Should_ignore_RowVersion() + { + var entity = new TestEntityWithRowVersion { Id = new Guid("EBC95620-4D80-4318-9B92-AD7528B2965C") }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.RowVersion = Int32.MaxValue; + + var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity }); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntitiesWithRowVersion.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + loadedEntity!.Id.Should().Be(new Guid("EBC95620-4D80-4318-9B92-AD7528B2965C")); + loadedEntity.RowVersion.Should().NotBe(Int32.MaxValue); + } + + [Fact] + public async Task Should_update_specified_properties_only() + { + var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "original value", RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Name = "Name"; // this would not be updated + entity.Count = 42; + entity.PropertyWithBackingField = 7; + entity.SetPrivateField(3); + + var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity }, + new SqlServerBulkUpdateOptions + { + PropertiesToUpdate = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) + }); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Count = 42, + PropertyWithBackingField = 7, + Name = "original value", + RequiredName = "RequiredName" + }); + loadedEntity.GetPrivateField().Should().Be(3); + } + + [Fact] + public async Task Should_update_entity_without_required_fields_if_excluded() + { + var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Name = "Name"; // this would not be updated + entity.RequiredName = null!; // this would not be updated + entity.Count = 42; + + var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity }, + new SqlServerBulkUpdateOptions + { + PropertiesToUpdate = IEntityPropertiesProvider.Include(e => e.Count) + }); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Count = 42, + RequiredName = "RequiredName" + }); + } + + [Fact] + public async Task Should_insert_and_update_TestEntity_with_ComplexType() + { + // Arrange + var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + new BoundaryValueObject(2, 5)); + + ArrangeDbContext.Add(testEntity); + await ArrangeDbContext.SaveChangesAsync(); + + // Act + testEntity.Boundary = new BoundaryValueObject(10, 20); + + await ActDbContext.BulkUpdateAsync(new[] { testEntity }, new SqlServerBulkUpdateOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/CreateComplexCollectionParameter.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/CreateComplexCollectionParameter.cs index df03b5e1..9de75c0f 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/CreateComplexCollectionParameter.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/CreateComplexCollectionParameter.cs @@ -1,52 +1,52 @@ -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Extensions.DbContextExtensionsTests; - -public class CreateComplexCollectionParameter : IntegrationTestsBase -{ - public CreateComplexCollectionParameter(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Should_do_roundtrip(bool applyDistinct) - { - ActDbContext.CreateComplexCollectionParameter(new[] { new MyParameter(new Guid("D3E99F44-40A1-4E4E-820F-9D7C7B02AFA5"), new ConvertibleClass(42)) }, applyDistinct) - .ToList() - .Should().BeEquivalentTo(new[] { new MyParameter(new Guid("D3E99F44-40A1-4E4E-820F-9D7C7B02AFA5"), new ConvertibleClass(42)) }); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task Should_work_with_joins(bool applyDistinct) - { - var testEntity = new TestEntity - { - Id = new Guid("7F8B0E79-2C91-4682-9F61-6FC86B4E5244"), - Name = "Name", - RequiredName = "RequiredName", - ConvertibleClass = new ConvertibleClass(42) - }; - await ArrangeDbContext.AddAsync(testEntity); - await ArrangeDbContext.SaveChangesAsync(); - - var collectionParameter = ActDbContext.CreateComplexCollectionParameter(new[] { new MyParameter(testEntity.Id, new ConvertibleClass(42)) }, applyDistinct); - var loadedEntities = await ActDbContext.TestEntities - .Join(collectionParameter, t => new { t.Id, ConvertibleClass = t.ConvertibleClass! }, p => new { Id = p.Column1, ConvertibleClass = p.Column2 }, (t, p) => t) - .ToListAsync(); - - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("7F8B0E79-2C91-4682-9F61-6FC86B4E5244"), - Name = "Name", - RequiredName = "RequiredName", - ConvertibleClass = new ConvertibleClass(42) - }); - } -} +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Extensions.DbContextExtensionsTests; + +public class CreateComplexCollectionParameter : IntegrationTestsBase +{ + public CreateComplexCollectionParameter(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Should_do_roundtrip(bool applyDistinct) + { + ActDbContext.CreateComplexCollectionParameter(new[] { new MyParameter(new Guid("D3E99F44-40A1-4E4E-820F-9D7C7B02AFA5"), new ConvertibleClass(42)) }, applyDistinct) + .ToList() + .Should().BeEquivalentTo(new[] { new MyParameter(new Guid("D3E99F44-40A1-4E4E-820F-9D7C7B02AFA5"), new ConvertibleClass(42)) }); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Should_work_with_joins(bool applyDistinct) + { + var testEntity = new TestEntity + { + Id = new Guid("7F8B0E79-2C91-4682-9F61-6FC86B4E5244"), + Name = "Name", + RequiredName = "RequiredName", + ConvertibleClass = new ConvertibleClass(42) + }; + await ArrangeDbContext.AddAsync(testEntity); + await ArrangeDbContext.SaveChangesAsync(); + + var collectionParameter = ActDbContext.CreateComplexCollectionParameter(new[] { new MyParameter(testEntity.Id, new ConvertibleClass(42)) }, applyDistinct); + var loadedEntities = await ActDbContext.TestEntities + .Join(collectionParameter, t => new { t.Id, ConvertibleClass = t.ConvertibleClass! }, p => new { Id = p.Column1, ConvertibleClass = p.Column2 }, (t, p) => t) + .ToListAsync(); + + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("7F8B0E79-2C91-4682-9F61-6FC86B4E5244"), + Name = "Name", + RequiredName = "RequiredName", + ConvertibleClass = new ConvertibleClass(42) + }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/CreateScalarCollectionParameter.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/CreateScalarCollectionParameter.cs index 9e998abd..bdc87057 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/CreateScalarCollectionParameter.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/CreateScalarCollectionParameter.cs @@ -1,115 +1,115 @@ -using System.Reflection; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Extensions.DbContextExtensionsTests; - -public class CreateScalarCollectionParameter : IntegrationTestsBase -{ - public CreateScalarCollectionParameter(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - public static readonly IEnumerable Values = new[] - { - new object[] { 42 }, - new object[] { 43L }, - new object[] { new DateTime(2021, 1, 15, 12, 30, 40) }, - new object[] { new Guid("CE5E3D80-13D6-43C3-BB41-6499E2AD6B63") }, - new object[] { true }, - new object[] { (byte)4 }, - new object[] { 4.2d }, - new object[] { new DateTimeOffset(new DateTime(2021, 1, 15, 12, 30, 40), TimeSpan.FromMinutes(60)) }, - new object[] { (short)5 }, - new object[] { 4.3f }, - new object[] { 4.4m }, - new object[] { TimeSpan.FromMinutes(60) }, - new object[] { "test" }, - new object[] { new ConvertibleClass(99) } - }; - - private static readonly MethodInfo _genericDataTypeTest = typeof(CreateScalarCollectionParameter).GetMethod(nameof(MakeGenericCreateCollectionParameterTest), BindingFlags.Instance | BindingFlags.NonPublic) - ?? throw new Exception($"Method '{nameof(MakeGenericCreateCollectionParameterTest)}' not found."); - - [Theory] - [MemberData(nameof(Values))] - public void Should_work_with_default_data_types_with_distinct(object value) - { - _genericDataTypeTest.MakeGenericMethod(value.GetType()).Invoke(this, new[] { value, true }); - } - - [Theory] - [MemberData(nameof(Values))] - public void Should_work_with_default_data_types_without_distinct(object value) - { - _genericDataTypeTest.MakeGenericMethod(value.GetType()).Invoke(this, new[] { value, false }); - } - - private void MakeGenericCreateCollectionParameterTest(T value, bool applyDistinct) - { - ActDbContext.CreateScalarCollectionParameter(new[] { value }, applyDistinct) - .ToList() - .Should().BeEquivalentTo(new[] { value }); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task Should_work_with_contains(bool applyDistinct) - { - var testEntity = new TestEntity - { - Id = new Guid("7F8B0E79-2C91-4682-9F61-6FC86B4E5244"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - await ArrangeDbContext.AddAsync(testEntity); - await ArrangeDbContext.SaveChangesAsync(); - - var collectionParameter = ActDbContext.CreateScalarCollectionParameter(new[] { testEntity.Id }, applyDistinct); - var loadedEntities = await ActDbContext.TestEntities - .Where(e => collectionParameter.Contains(e.Id)) - .ToListAsync(); - - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("7F8B0E79-2C91-4682-9F61-6FC86B4E5244"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }); - } - - [Fact] - public async Task Should_work_GroupBy_and_aggregate() - { - var testEntity = new TestEntity - { - Id = new Guid("7F8B0E79-2C91-4682-9F61-6FC86B4E5244"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - await ArrangeDbContext.AddAsync(testEntity); - await ArrangeDbContext.SaveChangesAsync(); - - var collectionParameter = ActDbContext.CreateScalarCollectionParameter(new[] { testEntity.Id }); - var loadedEntities = await ActDbContext.TestEntities - .Where(e => collectionParameter.Contains(e.Id)) - .GroupBy(e => e.Id) - .Select(g => new { g.Key, Aggregate = g.Count() }) - .ToListAsync(); - - loadedEntities.Should().BeEquivalentTo(new[] - { - new - { - Key = new Guid("7F8B0E79-2C91-4682-9F61-6FC86B4E5244"), - Aggregate = 1 - } - }); - } -} +using System.Reflection; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Extensions.DbContextExtensionsTests; + +public class CreateScalarCollectionParameter : IntegrationTestsBase +{ + public CreateScalarCollectionParameter(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + public static readonly IEnumerable Values = new[] + { + new object[] { 42 }, + new object[] { 43L }, + new object[] { new DateTime(2021, 1, 15, 12, 30, 40) }, + new object[] { new Guid("CE5E3D80-13D6-43C3-BB41-6499E2AD6B63") }, + new object[] { true }, + new object[] { (byte)4 }, + new object[] { 4.2d }, + new object[] { new DateTimeOffset(new DateTime(2021, 1, 15, 12, 30, 40), TimeSpan.FromMinutes(60)) }, + new object[] { (short)5 }, + new object[] { 4.3f }, + new object[] { 4.4m }, + new object[] { TimeSpan.FromMinutes(60) }, + new object[] { "test" }, + new object[] { new ConvertibleClass(99) } + }; + + private static readonly MethodInfo _genericDataTypeTest = typeof(CreateScalarCollectionParameter).GetMethod(nameof(MakeGenericCreateCollectionParameterTest), BindingFlags.Instance | BindingFlags.NonPublic) + ?? throw new Exception($"Method '{nameof(MakeGenericCreateCollectionParameterTest)}' not found."); + + [Theory] + [MemberData(nameof(Values))] + public void Should_work_with_default_data_types_with_distinct(object value) + { + _genericDataTypeTest.MakeGenericMethod(value.GetType()).Invoke(this, new[] { value, true }); + } + + [Theory] + [MemberData(nameof(Values))] + public void Should_work_with_default_data_types_without_distinct(object value) + { + _genericDataTypeTest.MakeGenericMethod(value.GetType()).Invoke(this, new[] { value, false }); + } + + private void MakeGenericCreateCollectionParameterTest(T value, bool applyDistinct) + { + ActDbContext.CreateScalarCollectionParameter(new[] { value }, applyDistinct) + .ToList() + .Should().BeEquivalentTo(new[] { value }); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Should_work_with_contains(bool applyDistinct) + { + var testEntity = new TestEntity + { + Id = new Guid("7F8B0E79-2C91-4682-9F61-6FC86B4E5244"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + await ArrangeDbContext.AddAsync(testEntity); + await ArrangeDbContext.SaveChangesAsync(); + + var collectionParameter = ActDbContext.CreateScalarCollectionParameter(new[] { testEntity.Id }, applyDistinct); + var loadedEntities = await ActDbContext.TestEntities + .Where(e => collectionParameter.Contains(e.Id)) + .ToListAsync(); + + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("7F8B0E79-2C91-4682-9F61-6FC86B4E5244"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }); + } + + [Fact] + public async Task Should_work_GroupBy_and_aggregate() + { + var testEntity = new TestEntity + { + Id = new Guid("7F8B0E79-2C91-4682-9F61-6FC86B4E5244"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + await ArrangeDbContext.AddAsync(testEntity); + await ArrangeDbContext.SaveChangesAsync(); + + var collectionParameter = ActDbContext.CreateScalarCollectionParameter(new[] { testEntity.Id }); + var loadedEntities = await ActDbContext.TestEntities + .Where(e => collectionParameter.Contains(e.Id)) + .GroupBy(e => e.Id) + .Select(g => new { g.Key, Aggregate = g.Count() }) + .ToListAsync(); + + loadedEntities.Should().BeEquivalentTo(new[] + { + new + { + Key = new Guid("7F8B0E79-2C91-4682-9F61-6FC86B4E5244"), + Aggregate = 1 + } + }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/GetLastUsedRowVersionAsync.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/GetLastUsedRowVersionAsync.cs index 6e58dfeb..6008fbdd 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/GetLastUsedRowVersionAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/GetLastUsedRowVersionAsync.cs @@ -1,17 +1,17 @@ -namespace Thinktecture.Extensions.DbContextExtensionsTests; - -// ReSharper disable once InconsistentNaming -public class GetLastUsedRowVersionAsync : IntegrationTestsBase -{ - public GetLastUsedRowVersionAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_fetch_last_used_rowversion() - { - var rowVersion = await ActDbContext.GetLastUsedRowVersionAsync(CancellationToken.None); - rowVersion.Should().NotBe(0); - } -} +namespace Thinktecture.Extensions.DbContextExtensionsTests; + +// ReSharper disable once InconsistentNaming +public class GetLastUsedRowVersionAsync : IntegrationTestsBase +{ + public GetLastUsedRowVersionAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_fetch_last_used_rowversion() + { + var rowVersion = await ActDbContext.GetLastUsedRowVersionAsync(CancellationToken.None); + rowVersion.Should().NotBe(0); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/GetMinActiveRowVersionAsync.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/GetMinActiveRowVersionAsync.cs index 0aae3661..4f98bd52 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/GetMinActiveRowVersionAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/GetMinActiveRowVersionAsync.cs @@ -1,17 +1,17 @@ -namespace Thinktecture.Extensions.DbContextExtensionsTests; - -// ReSharper disable once InconsistentNaming -public class GetMinActiveRowVersionAsync : IntegrationTestsBase -{ - public GetMinActiveRowVersionAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_fetch_min_action_rowversion() - { - var rowVersion = await ActDbContext.GetMinActiveRowVersionAsync(CancellationToken.None); - rowVersion.Should().NotBe(0); - } -} +namespace Thinktecture.Extensions.DbContextExtensionsTests; + +// ReSharper disable once InconsistentNaming +public class GetMinActiveRowVersionAsync : IntegrationTestsBase +{ + public GetMinActiveRowVersionAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_fetch_min_action_rowversion() + { + var rowVersion = await ActDbContext.GetMinActiveRowVersionAsync(CancellationToken.None); + rowVersion.Should().NotBe(0); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/TruncateTableAsync.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/TruncateTableAsync.cs index 4a01bbec..6698fcb7 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/TruncateTableAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/DbContextExtensionsTests/TruncateTableAsync.cs @@ -1,37 +1,37 @@ -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Extensions.DbContextExtensionsTests; - -// ReSharper disable once InconsistentNaming -public class TruncateTableAsync : IntegrationTestsBase -{ - public TruncateTableAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_not_throw_if_table_is_empty() - { - await ActDbContext.Awaiting(ctx => ctx.TruncateTableAsync()) - .Should().NotThrowAsync(); - } - - [Fact] - public async Task Should_delete_entities() - { - ArrangeDbContext.Add(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }); - await ArrangeDbContext.SaveChangesAsync(); - - await ActDbContext.TruncateTableAsync(); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEmpty(); - } -} +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Extensions.DbContextExtensionsTests; + +// ReSharper disable once InconsistentNaming +public class TruncateTableAsync : IntegrationTestsBase +{ + public TruncateTableAsync(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_not_throw_if_table_is_empty() + { + await ActDbContext.Awaiting(ctx => ctx.TruncateTableAsync()) + .Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_delete_entities() + { + ArrangeDbContext.Add(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }); + await ArrangeDbContext.SaveChangesAsync(); + + await ActDbContext.TruncateTableAsync(); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEmpty(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/ModelBuilderExtensionsTests/ConfigureTempTableEntity.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/ModelBuilderExtensionsTests/ConfigureTempTableEntity.cs index 97620a4b..7bbc89fa 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/ModelBuilderExtensionsTests/ConfigureTempTableEntity.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/ModelBuilderExtensionsTests/ConfigureTempTableEntity.cs @@ -1,45 +1,45 @@ -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Extensions.ModelBuilderExtensionsTests; - -// ReSharper disable once InconsistentNaming -public class ConfigureTempTableEntity : IntegrationTestsBase -{ - public ConfigureTempTableEntity(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public void Should_introduce_temp_table_with_custom_type() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - var entityType = ActDbContext.GetTempTableEntityType(); - entityType.Should().NotBeNull(); - entityType.Name.Should().Be("Thinktecture:TempTable:Thinktecture.TestDatabaseContext.CustomTempTable"); - - var properties = entityType.GetProperties().ToList(); - properties.Should().HaveCount(2); - - var intProperty = properties.First(); - intProperty.Name.Should().Be(nameof(CustomTempTable.Column1)); - intProperty.IsNullable.Should().BeFalse(); - intProperty.ClrType.Should().Be(typeof(int)); - - var stringProperty = properties.Skip(1).First(); - stringProperty.Name.Should().Be(nameof(CustomTempTable.Column2)); - stringProperty.IsNullable.Should().BeTrue(); - stringProperty.ClrType.Should().Be(typeof(string)); - } - - [Fact] - public void Should_generate_table_name_for_string() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - var entityType = ActDbContext.GetTempTableEntityType(); - entityType.Should().NotBeNull(); - entityType.GetTableName().Should().Be("#CustomTempTable"); - } -} +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Extensions.ModelBuilderExtensionsTests; + +// ReSharper disable once InconsistentNaming +public class ConfigureTempTableEntity : IntegrationTestsBase +{ + public ConfigureTempTableEntity(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public void Should_introduce_temp_table_with_custom_type() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + var entityType = ActDbContext.GetTempTableEntityType(); + entityType.Should().NotBeNull(); + entityType.Name.Should().Be("Thinktecture:TempTable:Thinktecture.TestDatabaseContext.CustomTempTable"); + + var properties = entityType.GetProperties().ToList(); + properties.Should().HaveCount(2); + + var intProperty = properties.First(); + intProperty.Name.Should().Be(nameof(CustomTempTable.Column1)); + intProperty.IsNullable.Should().BeFalse(); + intProperty.ClrType.Should().Be(typeof(int)); + + var stringProperty = properties.Skip(1).First(); + stringProperty.Name.Should().Be(nameof(CustomTempTable.Column2)); + stringProperty.IsNullable.Should().BeTrue(); + stringProperty.ClrType.Should().Be(typeof(string)); + } + + [Fact] + public void Should_generate_table_name_for_string() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + var entityType = ActDbContext.GetTempTableEntityType(); + entityType.Should().NotBeNull(); + entityType.GetTableName().Should().Be("#CustomTempTable"); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/ModelBuilderExtensionsTests/ConfigureTempTable_1_Column.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/ModelBuilderExtensionsTests/ConfigureTempTable_1_Column.cs index d1fbbeff..c9e7de21 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/ModelBuilderExtensionsTests/ConfigureTempTable_1_Column.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/ModelBuilderExtensionsTests/ConfigureTempTable_1_Column.cs @@ -1,116 +1,116 @@ -using Thinktecture.EntityFrameworkCore.TempTables; - -namespace Thinktecture.Extensions.ModelBuilderExtensionsTests; - -// ReSharper disable once InconsistentNaming -public class ConfigureTempTable_1_Column : IntegrationTestsBase -{ - public ConfigureTempTable_1_Column(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public void Should_introduce_keyless_temp_table() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var entityType = ActDbContext.GetTempTableEntityType>(); - entityType.Should().NotBeNull(); - entityType.GetKeys().Should().BeEmpty(); - } - - [Fact] - public void Should_introduce_temp_table_with_int() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var entityType = ActDbContext.GetTempTableEntityType>(); - entityType.Should().NotBeNull(); - entityType.Name.Should().Be("Thinktecture:TempTable:Thinktecture.EntityFrameworkCore.TempTables.TempTable"); - - var properties = entityType.GetProperties().ToList(); - properties.Should().HaveCount(1); - - var property = properties.First(); - property.Name.Should().Be("Column1"); - property.IsNullable.Should().BeFalse(); - } - - [Fact] - public void Should_introduce_temp_table_with_nullable_int() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var entityType = ActDbContext.GetTempTableEntityType>(); - entityType.Should().NotBeNull(); - entityType!.Name.Should().Be("Thinktecture:TempTable:Thinktecture.EntityFrameworkCore.TempTables.TempTable"); - - var properties = entityType.GetProperties(); - properties.Should().HaveCount(1); - - var property = properties.First(); - property.Name.Should().Be("Column1"); - property.IsNullable.Should().BeTrue(); - } - - [Fact] - public void Should_flag_nullable_int_as_not_nullable_if_set_via_modelbuilder() - { - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).IsRequired()); - - var entityType = ActDbContext.GetTempTableEntityType>(); - entityType.Should().NotBeNull(); - entityType.Name.Should().Be("Thinktecture:TempTable:Thinktecture.EntityFrameworkCore.TempTables.TempTable"); - - var properties = entityType.GetProperties(); - properties.First().IsNullable.Should().BeFalse(); - } - - [Fact] - public void Should_introduce_temp_table_with_string() - { - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).IsRequired(false)); - - var entityType = ActDbContext.GetTempTableEntityType>(); - entityType.Should().NotBeNull(); - entityType.Name.Should().Be("Thinktecture:TempTable:Thinktecture.EntityFrameworkCore.TempTables.TempTable"); - - var properties = entityType.GetProperties(); - properties.Should().HaveCount(1); - - var property = properties.First(); - property.Name.Should().Be("Column1"); - property.IsNullable.Should().BeTrue(); - } - - [Fact] - public void Should_generate_table_name_for_int() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var entityType = ActDbContext.GetTempTableEntityType>(); - entityType.Should().NotBeNull(); - entityType.GetTableName().Should().Be("#TempTable"); - } - - [Fact] - public void Should_generate_table_name_for_nullable_int() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var entityType = ActDbContext.GetTempTableEntityType>(); - entityType.Should().NotBeNull(); - entityType.GetTableName().Should().Be("#TempTable"); - } - - [Fact] - public void Should_generate_table_name_for_string() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var entityType = ActDbContext.GetTempTableEntityType>(); - entityType.Should().NotBeNull(); - entityType.GetTableName().Should().Be("#TempTable"); - } -} +using Thinktecture.EntityFrameworkCore.TempTables; + +namespace Thinktecture.Extensions.ModelBuilderExtensionsTests; + +// ReSharper disable once InconsistentNaming +public class ConfigureTempTable_1_Column : IntegrationTestsBase +{ + public ConfigureTempTable_1_Column(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public void Should_introduce_keyless_temp_table() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var entityType = ActDbContext.GetTempTableEntityType>(); + entityType.Should().NotBeNull(); + entityType.GetKeys().Should().BeEmpty(); + } + + [Fact] + public void Should_introduce_temp_table_with_int() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var entityType = ActDbContext.GetTempTableEntityType>(); + entityType.Should().NotBeNull(); + entityType.Name.Should().Be("Thinktecture:TempTable:Thinktecture.EntityFrameworkCore.TempTables.TempTable"); + + var properties = entityType.GetProperties().ToList(); + properties.Should().HaveCount(1); + + var property = properties.First(); + property.Name.Should().Be("Column1"); + property.IsNullable.Should().BeFalse(); + } + + [Fact] + public void Should_introduce_temp_table_with_nullable_int() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var entityType = ActDbContext.GetTempTableEntityType>(); + entityType.Should().NotBeNull(); + entityType!.Name.Should().Be("Thinktecture:TempTable:Thinktecture.EntityFrameworkCore.TempTables.TempTable"); + + var properties = entityType.GetProperties(); + properties.Should().HaveCount(1); + + var property = properties.First(); + property.Name.Should().Be("Column1"); + property.IsNullable.Should().BeTrue(); + } + + [Fact] + public void Should_flag_nullable_int_as_not_nullable_if_set_via_modelbuilder() + { + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).IsRequired()); + + var entityType = ActDbContext.GetTempTableEntityType>(); + entityType.Should().NotBeNull(); + entityType.Name.Should().Be("Thinktecture:TempTable:Thinktecture.EntityFrameworkCore.TempTables.TempTable"); + + var properties = entityType.GetProperties(); + properties.First().IsNullable.Should().BeFalse(); + } + + [Fact] + public void Should_introduce_temp_table_with_string() + { + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).IsRequired(false)); + + var entityType = ActDbContext.GetTempTableEntityType>(); + entityType.Should().NotBeNull(); + entityType.Name.Should().Be("Thinktecture:TempTable:Thinktecture.EntityFrameworkCore.TempTables.TempTable"); + + var properties = entityType.GetProperties(); + properties.Should().HaveCount(1); + + var property = properties.First(); + property.Name.Should().Be("Column1"); + property.IsNullable.Should().BeTrue(); + } + + [Fact] + public void Should_generate_table_name_for_int() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var entityType = ActDbContext.GetTempTableEntityType>(); + entityType.Should().NotBeNull(); + entityType.GetTableName().Should().Be("#TempTable"); + } + + [Fact] + public void Should_generate_table_name_for_nullable_int() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var entityType = ActDbContext.GetTempTableEntityType>(); + entityType.Should().NotBeNull(); + entityType.GetTableName().Should().Be("#TempTable"); + } + + [Fact] + public void Should_generate_table_name_for_string() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var entityType = ActDbContext.GetTempTableEntityType>(); + entityType.Should().NotBeNull(); + entityType.GetTableName().Should().Be("#TempTable"); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/ModelBuilderExtensionsTests/ConfigureTempTable_2_Columns.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/ModelBuilderExtensionsTests/ConfigureTempTable_2_Columns.cs index bfcf5b72..b519329a 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/ModelBuilderExtensionsTests/ConfigureTempTable_2_Columns.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/ModelBuilderExtensionsTests/ConfigureTempTable_2_Columns.cs @@ -1,91 +1,91 @@ -using Thinktecture.EntityFrameworkCore.TempTables; - -namespace Thinktecture.Extensions.ModelBuilderExtensionsTests; - -// ReSharper disable once InconsistentNaming -public class ConfigureTempTable_2_Columns : IntegrationTestsBase -{ - public ConfigureTempTable_2_Columns(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public void Should_introduce_keyless_temp_table() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var entityType = ActDbContext.GetTempTableEntityType>(); - entityType.Should().NotBeNull(); - entityType.GetKeys().Should().BeEmpty(); - } - - [Fact] - public void Should_introduce_temp_table_with_int_nullable_int() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var entityType = ActDbContext.GetTempTableEntityType>(); - entityType.Should().NotBeNull(); - entityType.Name.Should().Be("Thinktecture:TempTable:Thinktecture.EntityFrameworkCore.TempTables.TempTable"); - - var properties = entityType.GetProperties().ToList(); - properties.Should().HaveCount(2); - - properties.Select(p => p.IsNullable).Should().BeEquivalentTo(new[] { false, true }); - properties.First().Name.Should().Be("Column1"); - properties.Skip(1).First().Name.Should().Be("Column2"); - } - - [Fact] - public void Should_introduce_temp_table_with_string_string() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var entityType = ActDbContext.GetTempTableEntityType>(); - entityType.Should().NotBeNull(); - entityType.Name.Should().Be("Thinktecture:TempTable:Thinktecture.EntityFrameworkCore.TempTables.TempTable"); - - var properties = entityType.GetProperties().ToList(); - properties.Should().HaveCount(2); - - properties.Select(p => p.IsNullable).Should().BeEquivalentTo(new[] { true, true }); - properties.First().Name.Should().Be("Column1"); - properties.Skip(1).First().Name.Should().Be("Column2"); - } - - [Fact] - public void Should_flag_col1_as_nullable_if_set_via_modelbuilder() - { - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => - { - typeBuilder.Property(t => t.Column1).IsRequired(false); - typeBuilder.Property(t => t.Column2).IsRequired(); - }); - - var entityType = ActDbContext.GetTempTableEntityType>(); - entityType.Should().NotBeNull(); - var properties = entityType.GetProperties(); - properties.OrderBy(p => p.Name).Select(p => p.IsNullable).Should().BeEquivalentTo(new[] { true, false }); - } - - [Fact] - public void Should_generate_table_name_for_int_nullable_int() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var entityType = ActDbContext.GetTempTableEntityType>(); - entityType.Should().NotBeNull(); - entityType.GetTableName().Should().Be("#TempTable"); - } - - [Fact] - public void Should_generate_table_name_for_string_string() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var entityType = ActDbContext.GetTempTableEntityType>(); - entityType.Should().NotBeNull(); - entityType.GetTableName().Should().Be("#TempTable"); - } -} +using Thinktecture.EntityFrameworkCore.TempTables; + +namespace Thinktecture.Extensions.ModelBuilderExtensionsTests; + +// ReSharper disable once InconsistentNaming +public class ConfigureTempTable_2_Columns : IntegrationTestsBase +{ + public ConfigureTempTable_2_Columns(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public void Should_introduce_keyless_temp_table() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var entityType = ActDbContext.GetTempTableEntityType>(); + entityType.Should().NotBeNull(); + entityType.GetKeys().Should().BeEmpty(); + } + + [Fact] + public void Should_introduce_temp_table_with_int_nullable_int() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var entityType = ActDbContext.GetTempTableEntityType>(); + entityType.Should().NotBeNull(); + entityType.Name.Should().Be("Thinktecture:TempTable:Thinktecture.EntityFrameworkCore.TempTables.TempTable"); + + var properties = entityType.GetProperties().ToList(); + properties.Should().HaveCount(2); + + properties.Select(p => p.IsNullable).Should().BeEquivalentTo(new[] { false, true }); + properties.First().Name.Should().Be("Column1"); + properties.Skip(1).First().Name.Should().Be("Column2"); + } + + [Fact] + public void Should_introduce_temp_table_with_string_string() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var entityType = ActDbContext.GetTempTableEntityType>(); + entityType.Should().NotBeNull(); + entityType.Name.Should().Be("Thinktecture:TempTable:Thinktecture.EntityFrameworkCore.TempTables.TempTable"); + + var properties = entityType.GetProperties().ToList(); + properties.Should().HaveCount(2); + + properties.Select(p => p.IsNullable).Should().BeEquivalentTo(new[] { true, true }); + properties.First().Name.Should().Be("Column1"); + properties.Skip(1).First().Name.Should().Be("Column2"); + } + + [Fact] + public void Should_flag_col1_as_nullable_if_set_via_modelbuilder() + { + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => + { + typeBuilder.Property(t => t.Column1).IsRequired(false); + typeBuilder.Property(t => t.Column2).IsRequired(); + }); + + var entityType = ActDbContext.GetTempTableEntityType>(); + entityType.Should().NotBeNull(); + var properties = entityType.GetProperties(); + properties.OrderBy(p => p.Name).Select(p => p.IsNullable).Should().BeEquivalentTo(new[] { true, false }); + } + + [Fact] + public void Should_generate_table_name_for_int_nullable_int() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var entityType = ActDbContext.GetTempTableEntityType>(); + entityType.Should().NotBeNull(); + entityType.GetTableName().Should().Be("#TempTable"); + } + + [Fact] + public void Should_generate_table_name_for_string_string() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var entityType = ActDbContext.GetTempTableEntityType>(); + entityType.Should().NotBeNull(); + entityType.GetTableName().Should().Be("#TempTable"); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/QueryableExtensionsTests/WithTableHints.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/QueryableExtensionsTests/WithTableHints.cs index 7275d84f..778d40a7 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/QueryableExtensionsTests/WithTableHints.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/QueryableExtensionsTests/WithTableHints.cs @@ -1,223 +1,223 @@ -using Thinktecture.EntityFrameworkCore; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Extensions.QueryableExtensionsTests; - -public class WithTableHints : IntegrationTestsBase -{ - private string? _escapedSchema; - private string EscapedSchema => _escapedSchema ??= $"[{ActDbContext.Schema}]"; - - public WithTableHints(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_add_table_hints_to_table() - { - var query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock); - - query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField]" + Environment.NewLine + - $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (NOLOCK)"); - - var result = await query.ToListAsync(); - result.Should().BeEmpty(); - } - - [Fact] - public async Task Should_escape_index_name() - { - var query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.Index("IX_TestEntities_Id")); - - query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField]" + Environment.NewLine + - $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (INDEX([IX_TestEntities_Id]))"); - - var result = await query.ToListAsync(); - result.Should().BeEmpty(); - } - - [Fact] - public void Should_not_mess_up_table_hints() - { - var query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock); - - query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField]" + Environment.NewLine + - $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (NOLOCK)"); - - query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.UpdLock); - - query.ToQueryString().Should().Be(@"SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField]" + Environment.NewLine + - $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (UPDLOCK)"); - } - - [Fact] - public async Task Should_add_table_hints_to_table_without_touching_included_navigations() - { - var query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock) - .Include(e => e.Children); - - query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField], [t0].[Id], [t0].[ConvertibleClass], [t0].[Count], [t0].[Name], [t0].[NullableCount], [t0].[ParentId], [t0].[PropertyWithBackingField], [t0].[RequiredName], [t0].[_privateField]" + Environment.NewLine + - $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (NOLOCK)" + Environment.NewLine + - $"LEFT JOIN {EscapedSchema}.[TestEntities] AS [t0] ON [t].[Id] = [t0].[ParentId]" + Environment.NewLine + - "ORDER BY [t].[Id]"); - - var result = await query.ToListAsync(); - result.Should().BeEmpty(); - } - - [Fact] - public async Task Should_work_with_joins_on_itself() - { - var query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock) - .Join(ActDbContext.TestEntities, e => e.Id, e => e.Id, (e1, e2) => new { e1, e2 }); - - query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField], [t0].[Id], [t0].[ConvertibleClass], [t0].[Count], [t0].[Name], [t0].[NullableCount], [t0].[ParentId], [t0].[PropertyWithBackingField], [t0].[RequiredName], [t0].[_privateField]" + Environment.NewLine + - $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (NOLOCK)" + Environment.NewLine + - $"INNER JOIN {EscapedSchema}.[TestEntities] AS [t0] ON [t].[Id] = [t0].[Id]"); - - (await query.ToListAsync()).Should().BeEmpty(); - - query = ActDbContext.TestEntities - .Join(ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock), e => e.Id, e => e.Id, (e1, e2) => new { e1, e2 }); - - query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField], [t0].[Id], [t0].[ConvertibleClass], [t0].[Count], [t0].[Name], [t0].[NullableCount], [t0].[ParentId], [t0].[PropertyWithBackingField], [t0].[RequiredName], [t0].[_privateField]" + Environment.NewLine + - $"FROM {EscapedSchema}.[TestEntities] AS [t]" + Environment.NewLine + - $"INNER JOIN {EscapedSchema}.[TestEntities] AS [t0] WITH (NOLOCK) ON [t].[Id] = [t0].[Id]"); - - (await query.ToListAsync()).Should().BeEmpty(); - - query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock) - .Join(ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock), e => e.Id, e => e.Id, (e1, e2) => new { e1, e2 }); - - query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField], [t0].[Id], [t0].[ConvertibleClass], [t0].[Count], [t0].[Name], [t0].[NullableCount], [t0].[ParentId], [t0].[PropertyWithBackingField], [t0].[RequiredName], [t0].[_privateField]" + Environment.NewLine + - $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (NOLOCK)" + Environment.NewLine + - $"INNER JOIN {EscapedSchema}.[TestEntities] AS [t0] WITH (NOLOCK) ON [t].[Id] = [t0].[Id]"); - - (await query.ToListAsync()).Should().BeEmpty(); - - query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock) - .Join(ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.UpdLock), e => e.Id, e => e.Id, (e1, e2) => new { e1, e2 }); - - query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField], [t0].[Id], [t0].[ConvertibleClass], [t0].[Count], [t0].[Name], [t0].[NullableCount], [t0].[ParentId], [t0].[PropertyWithBackingField], [t0].[RequiredName], [t0].[_privateField]" + Environment.NewLine + - $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (NOLOCK)" + Environment.NewLine + - $"INNER JOIN {EscapedSchema}.[TestEntities] AS [t0] WITH (UPDLOCK) ON [t].[Id] = [t0].[Id]"); - - (await query.ToListAsync()).Should().BeEmpty(); - - var tempQuery = ActDbContext.TestEntities.Select(e => new TestEntity { Id = e.Id }); - query = tempQuery.WithTableHints(SqlServerTableHint.NoLock) - .Join(tempQuery.WithTableHints(SqlServerTableHint.UpdLock), e => e.Id, e => e.Id, (e1, e2) => new { e1, e2 }); - - query.ToQueryString().Should().Be("SELECT [t].[Id], [t0].[Id]" + Environment.NewLine + - $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (NOLOCK)" + Environment.NewLine + - $"INNER JOIN {EscapedSchema}.[TestEntities] AS [t0] WITH (UPDLOCK) ON [t].[Id] = [t0].[Id]"); - - (await query.ToListAsync()).Should().BeEmpty(); - } - - [Fact] - public async Task Should_add_table_hints_to_table_and_owned_entities() - { - var query = ActDbContext.TestEntities_Own_SeparateMany_SeparateMany.WithTableHints(SqlServerTableHint.NoLock); - - query.ToQueryString().Should().Be("SELECT [t].[Id], [s1].[TestEntity_Owns_SeparateMany_SeparateManyId], [s1].[Id], [s1].[IntColumn], [s1].[StringColumn], [s1].[OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId], [s1].[OwnedEntity_Owns_SeparateManyId], [s1].[Id0], [s1].[IntColumn0], [s1].[StringColumn0]" + Environment.NewLine + - $"FROM {EscapedSchema}.[TestEntities_Own_SeparateMany_SeparateMany] AS [t] WITH (NOLOCK)" + Environment.NewLine + - "LEFT JOIN (" + Environment.NewLine + - " SELECT [s].[TestEntity_Owns_SeparateMany_SeparateManyId], [s].[Id], [s].[IntColumn], [s].[StringColumn], [s0].[OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId], [s0].[OwnedEntity_Owns_SeparateManyId], [s0].[Id] AS [Id0], [s0].[IntColumn] AS [IntColumn0], [s0].[StringColumn] AS [StringColumn0]" + Environment.NewLine + - $" FROM {EscapedSchema}.[SeparateEntitiesMany_SeparateEntitiesMany] AS [s] WITH (NOLOCK)" + Environment.NewLine + - $" LEFT JOIN {EscapedSchema}.[SeparateEntitiesMany_SeparateEntitiesMany_Inner] AS [s0] WITH (NOLOCK) ON [s].[TestEntity_Owns_SeparateMany_SeparateManyId] = [s0].[OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId] AND [s].[Id] = [s0].[OwnedEntity_Owns_SeparateManyId]" + Environment.NewLine + - ") AS [s1] ON [t].[Id] = [s1].[TestEntity_Owns_SeparateMany_SeparateManyId]" + Environment.NewLine + - "ORDER BY [t].[Id], [s1].[TestEntity_Owns_SeparateMany_SeparateManyId], [s1].[Id], [s1].[OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId], [s1].[OwnedEntity_Owns_SeparateManyId]"); - - var result = await query.ToListAsync(); - result.Should().BeEmpty(); - } - - [Fact] - public async Task Should_add_table_hints_to_temp_table() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new[] { Guid.Empty }); - var query = tempTable.Query.WithTableHints(SqlServerTableHint.UpdLock, SqlServerTableHint.RowLock); - - query.ToQueryString().Should().Be("SELECT [#].[Column1]" + Environment.NewLine + - "FROM [#TempTable_1] AS [#] WITH (UPDLOCK, ROWLOCK)"); - - var result = await query.ToListAsync(); - result.Should().HaveCount(1); - result[0].Should().Be(Guid.Empty); - - var joinQuery = tempTable.Query.WithTableHints(SqlServerTableHint.UpdLock, SqlServerTableHint.RowLock) - .LeftJoin(tempTable.Query.WithTableHints(SqlServerTableHint.UpdLock), - e => e, e => e); - - joinQuery.ToQueryString().Should().Be("SELECT [#].[Column1] AS [Left], [#0].[Column1] AS [Right]" + Environment.NewLine + - "FROM [#TempTable_1] AS [#] WITH (UPDLOCK, ROWLOCK)" + Environment.NewLine + - "LEFT JOIN [#TempTable_1] AS [#0] WITH (UPDLOCK) ON [#].[Column1] = [#0].[Column1]"); - - joinQuery = tempTable.Query.WithTableHints(SqlServerTableHint.UpdLock, SqlServerTableHint.RowLock) - .LeftJoin(tempTable.Query.WithTableHints(SqlServerTableHint.UpdLock, SqlServerTableHint.RowLock), - e => e, e => e); - - joinQuery.ToQueryString().Should().Be("SELECT [#].[Column1] AS [Left], [#0].[Column1] AS [Right]" + Environment.NewLine + - "FROM [#TempTable_1] AS [#] WITH (UPDLOCK, ROWLOCK)" + Environment.NewLine + - "LEFT JOIN [#TempTable_1] AS [#0] WITH (UPDLOCK, ROWLOCK) ON [#].[Column1] = [#0].[Column1]"); - } - - [Fact] - public async Task Should_work_with_projection_using_SplitQuery_behavior() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var parentId = new Guid("6EB48130-454B-46BC-9FEE-CDF411F69807"); - var childId = new Guid("17A2014C-F7B2-40E8-82F4-42E754DCAA2D"); - ArrangeDbContext.TestEntities.Add(new TestEntity - { - Id = parentId, - RequiredName = "RequiredName", - Children = { new() { Id = childId, RequiredName = "RequiredName" } } - }); - await ArrangeDbContext.SaveChangesAsync(); - - await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new[] { parentId }); - - var result = await ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.UpdLock, SqlServerTableHint.RowLock) - .AsSplitQuery() - .Where(c => tempTable.Query - .WithTableHints(SqlServerTableHint.UpdLock) - .Any(id => c.Id == id)) - .Select(c => new - { - c.Id, - ChildrenIds = c.Children.Select(o => o.Id).ToList() - }) - .ToListAsync(); - - result.Should().BeEquivalentTo(new[] - { - new - { - Id = parentId, - ChildrenIds = new List { childId } - } - }); - - ExecutedCommands.Skip(ExecutedCommands.Count - 2).First().Should().Be("SELECT [t].[Id]" + Environment.NewLine + - $"FROM [{TestCtxProvider.Schema}].[TestEntities] AS [t] WITH (UPDLOCK, ROWLOCK)" + Environment.NewLine + - "WHERE [t].[Id] IN (" + Environment.NewLine + - " SELECT [#].[Column1]" + Environment.NewLine + - " FROM [#TempTable_1] AS [#] WITH (UPDLOCK)" + Environment.NewLine + - ")" + Environment.NewLine + - "ORDER BY [t].[Id]"); - ExecutedCommands.Last().Should().Be("SELECT [t2].[Id], [t].[Id]" + Environment.NewLine + - $"FROM [{TestCtxProvider.Schema}].[TestEntities] AS [t] WITH (UPDLOCK, ROWLOCK)" + Environment.NewLine + - $"INNER JOIN [{TestCtxProvider.Schema}].[TestEntities] AS [t2] ON [t].[Id] = [t2].[ParentId]" + Environment.NewLine + - "WHERE [t].[Id] IN (" + Environment.NewLine + - " SELECT [#].[Column1]" + Environment.NewLine + - " FROM [#TempTable_1] AS [#] WITH (UPDLOCK)" + Environment.NewLine + - ")" + Environment.NewLine + - "ORDER BY [t].[Id]"); - } -} +using Thinktecture.EntityFrameworkCore; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Extensions.QueryableExtensionsTests; + +public class WithTableHints : IntegrationTestsBase +{ + private string? _escapedSchema; + private string EscapedSchema => _escapedSchema ??= $"[{ActDbContext.Schema}]"; + + public WithTableHints(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_add_table_hints_to_table() + { + var query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock); + + query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField]" + Environment.NewLine + + $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (NOLOCK)"); + + var result = await query.ToListAsync(); + result.Should().BeEmpty(); + } + + [Fact] + public async Task Should_escape_index_name() + { + var query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.Index("IX_TestEntities_Id")); + + query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField]" + Environment.NewLine + + $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (INDEX([IX_TestEntities_Id]))"); + + var result = await query.ToListAsync(); + result.Should().BeEmpty(); + } + + [Fact] + public void Should_not_mess_up_table_hints() + { + var query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock); + + query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField]" + Environment.NewLine + + $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (NOLOCK)"); + + query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.UpdLock); + + query.ToQueryString().Should().Be(@"SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField]" + Environment.NewLine + + $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (UPDLOCK)"); + } + + [Fact] + public async Task Should_add_table_hints_to_table_without_touching_included_navigations() + { + var query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock) + .Include(e => e.Children); + + query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField], [t0].[Id], [t0].[ConvertibleClass], [t0].[Count], [t0].[Name], [t0].[NullableCount], [t0].[ParentId], [t0].[PropertyWithBackingField], [t0].[RequiredName], [t0].[_privateField]" + Environment.NewLine + + $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (NOLOCK)" + Environment.NewLine + + $"LEFT JOIN {EscapedSchema}.[TestEntities] AS [t0] ON [t].[Id] = [t0].[ParentId]" + Environment.NewLine + + "ORDER BY [t].[Id]"); + + var result = await query.ToListAsync(); + result.Should().BeEmpty(); + } + + [Fact] + public async Task Should_work_with_joins_on_itself() + { + var query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock) + .Join(ActDbContext.TestEntities, e => e.Id, e => e.Id, (e1, e2) => new { e1, e2 }); + + query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField], [t0].[Id], [t0].[ConvertibleClass], [t0].[Count], [t0].[Name], [t0].[NullableCount], [t0].[ParentId], [t0].[PropertyWithBackingField], [t0].[RequiredName], [t0].[_privateField]" + Environment.NewLine + + $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (NOLOCK)" + Environment.NewLine + + $"INNER JOIN {EscapedSchema}.[TestEntities] AS [t0] ON [t].[Id] = [t0].[Id]"); + + (await query.ToListAsync()).Should().BeEmpty(); + + query = ActDbContext.TestEntities + .Join(ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock), e => e.Id, e => e.Id, (e1, e2) => new { e1, e2 }); + + query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField], [t0].[Id], [t0].[ConvertibleClass], [t0].[Count], [t0].[Name], [t0].[NullableCount], [t0].[ParentId], [t0].[PropertyWithBackingField], [t0].[RequiredName], [t0].[_privateField]" + Environment.NewLine + + $"FROM {EscapedSchema}.[TestEntities] AS [t]" + Environment.NewLine + + $"INNER JOIN {EscapedSchema}.[TestEntities] AS [t0] WITH (NOLOCK) ON [t].[Id] = [t0].[Id]"); + + (await query.ToListAsync()).Should().BeEmpty(); + + query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock) + .Join(ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock), e => e.Id, e => e.Id, (e1, e2) => new { e1, e2 }); + + query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField], [t0].[Id], [t0].[ConvertibleClass], [t0].[Count], [t0].[Name], [t0].[NullableCount], [t0].[ParentId], [t0].[PropertyWithBackingField], [t0].[RequiredName], [t0].[_privateField]" + Environment.NewLine + + $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (NOLOCK)" + Environment.NewLine + + $"INNER JOIN {EscapedSchema}.[TestEntities] AS [t0] WITH (NOLOCK) ON [t].[Id] = [t0].[Id]"); + + (await query.ToListAsync()).Should().BeEmpty(); + + query = ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.NoLock) + .Join(ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.UpdLock), e => e.Id, e => e.Id, (e1, e2) => new { e1, e2 }); + + query.ToQueryString().Should().Be("SELECT [t].[Id], [t].[ConvertibleClass], [t].[Count], [t].[Name], [t].[NullableCount], [t].[ParentId], [t].[PropertyWithBackingField], [t].[RequiredName], [t].[_privateField], [t0].[Id], [t0].[ConvertibleClass], [t0].[Count], [t0].[Name], [t0].[NullableCount], [t0].[ParentId], [t0].[PropertyWithBackingField], [t0].[RequiredName], [t0].[_privateField]" + Environment.NewLine + + $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (NOLOCK)" + Environment.NewLine + + $"INNER JOIN {EscapedSchema}.[TestEntities] AS [t0] WITH (UPDLOCK) ON [t].[Id] = [t0].[Id]"); + + (await query.ToListAsync()).Should().BeEmpty(); + + var tempQuery = ActDbContext.TestEntities.Select(e => new TestEntity { Id = e.Id }); + query = tempQuery.WithTableHints(SqlServerTableHint.NoLock) + .Join(tempQuery.WithTableHints(SqlServerTableHint.UpdLock), e => e.Id, e => e.Id, (e1, e2) => new { e1, e2 }); + + query.ToQueryString().Should().Be("SELECT [t].[Id], [t0].[Id]" + Environment.NewLine + + $"FROM {EscapedSchema}.[TestEntities] AS [t] WITH (NOLOCK)" + Environment.NewLine + + $"INNER JOIN {EscapedSchema}.[TestEntities] AS [t0] WITH (UPDLOCK) ON [t].[Id] = [t0].[Id]"); + + (await query.ToListAsync()).Should().BeEmpty(); + } + + [Fact] + public async Task Should_add_table_hints_to_table_and_owned_entities() + { + var query = ActDbContext.TestEntities_Own_SeparateMany_SeparateMany.WithTableHints(SqlServerTableHint.NoLock); + + query.ToQueryString().Should().Be("SELECT [t].[Id], [s1].[TestEntity_Owns_SeparateMany_SeparateManyId], [s1].[Id], [s1].[IntColumn], [s1].[StringColumn], [s1].[OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId], [s1].[OwnedEntity_Owns_SeparateManyId], [s1].[Id0], [s1].[IntColumn0], [s1].[StringColumn0]" + Environment.NewLine + + $"FROM {EscapedSchema}.[TestEntities_Own_SeparateMany_SeparateMany] AS [t] WITH (NOLOCK)" + Environment.NewLine + + "LEFT JOIN (" + Environment.NewLine + + " SELECT [s].[TestEntity_Owns_SeparateMany_SeparateManyId], [s].[Id], [s].[IntColumn], [s].[StringColumn], [s0].[OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId], [s0].[OwnedEntity_Owns_SeparateManyId], [s0].[Id] AS [Id0], [s0].[IntColumn] AS [IntColumn0], [s0].[StringColumn] AS [StringColumn0]" + Environment.NewLine + + $" FROM {EscapedSchema}.[SeparateEntitiesMany_SeparateEntitiesMany] AS [s] WITH (NOLOCK)" + Environment.NewLine + + $" LEFT JOIN {EscapedSchema}.[SeparateEntitiesMany_SeparateEntitiesMany_Inner] AS [s0] WITH (NOLOCK) ON [s].[TestEntity_Owns_SeparateMany_SeparateManyId] = [s0].[OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId] AND [s].[Id] = [s0].[OwnedEntity_Owns_SeparateManyId]" + Environment.NewLine + + ") AS [s1] ON [t].[Id] = [s1].[TestEntity_Owns_SeparateMany_SeparateManyId]" + Environment.NewLine + + "ORDER BY [t].[Id], [s1].[TestEntity_Owns_SeparateMany_SeparateManyId], [s1].[Id], [s1].[OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId], [s1].[OwnedEntity_Owns_SeparateManyId]"); + + var result = await query.ToListAsync(); + result.Should().BeEmpty(); + } + + [Fact] + public async Task Should_add_table_hints_to_temp_table() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new[] { Guid.Empty }); + var query = tempTable.Query.WithTableHints(SqlServerTableHint.UpdLock, SqlServerTableHint.RowLock); + + query.ToQueryString().Should().Be("SELECT [#].[Column1]" + Environment.NewLine + + "FROM [#TempTable_1] AS [#] WITH (UPDLOCK, ROWLOCK)"); + + var result = await query.ToListAsync(); + result.Should().HaveCount(1); + result[0].Should().Be(Guid.Empty); + + var joinQuery = tempTable.Query.WithTableHints(SqlServerTableHint.UpdLock, SqlServerTableHint.RowLock) + .LeftJoin(tempTable.Query.WithTableHints(SqlServerTableHint.UpdLock), + e => e, e => e); + + joinQuery.ToQueryString().Should().Be("SELECT [#].[Column1] AS [Left], [#0].[Column1] AS [Right]" + Environment.NewLine + + "FROM [#TempTable_1] AS [#] WITH (UPDLOCK, ROWLOCK)" + Environment.NewLine + + "LEFT JOIN [#TempTable_1] AS [#0] WITH (UPDLOCK) ON [#].[Column1] = [#0].[Column1]"); + + joinQuery = tempTable.Query.WithTableHints(SqlServerTableHint.UpdLock, SqlServerTableHint.RowLock) + .LeftJoin(tempTable.Query.WithTableHints(SqlServerTableHint.UpdLock, SqlServerTableHint.RowLock), + e => e, e => e); + + joinQuery.ToQueryString().Should().Be("SELECT [#].[Column1] AS [Left], [#0].[Column1] AS [Right]" + Environment.NewLine + + "FROM [#TempTable_1] AS [#] WITH (UPDLOCK, ROWLOCK)" + Environment.NewLine + + "LEFT JOIN [#TempTable_1] AS [#0] WITH (UPDLOCK, ROWLOCK) ON [#].[Column1] = [#0].[Column1]"); + } + + [Fact] + public async Task Should_work_with_projection_using_SplitQuery_behavior() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var parentId = new Guid("6EB48130-454B-46BC-9FEE-CDF411F69807"); + var childId = new Guid("17A2014C-F7B2-40E8-82F4-42E754DCAA2D"); + ArrangeDbContext.TestEntities.Add(new TestEntity + { + Id = parentId, + RequiredName = "RequiredName", + Children = { new() { Id = childId, RequiredName = "RequiredName" } } + }); + await ArrangeDbContext.SaveChangesAsync(); + + await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new[] { parentId }); + + var result = await ActDbContext.TestEntities.WithTableHints(SqlServerTableHint.UpdLock, SqlServerTableHint.RowLock) + .AsSplitQuery() + .Where(c => tempTable.Query + .WithTableHints(SqlServerTableHint.UpdLock) + .Any(id => c.Id == id)) + .Select(c => new + { + c.Id, + ChildrenIds = c.Children.Select(o => o.Id).ToList() + }) + .ToListAsync(); + + result.Should().BeEquivalentTo(new[] + { + new + { + Id = parentId, + ChildrenIds = new List { childId } + } + }); + + ExecutedCommands.Skip(ExecutedCommands.Count - 2).First().Should().Be("SELECT [t].[Id]" + Environment.NewLine + + $"FROM [{TestCtxProvider.Schema}].[TestEntities] AS [t] WITH (UPDLOCK, ROWLOCK)" + Environment.NewLine + + "WHERE [t].[Id] IN (" + Environment.NewLine + + " SELECT [#].[Column1]" + Environment.NewLine + + " FROM [#TempTable_1] AS [#] WITH (UPDLOCK)" + Environment.NewLine + + ")" + Environment.NewLine + + "ORDER BY [t].[Id]"); + ExecutedCommands.Last().Should().Be("SELECT [t2].[Id], [t].[Id]" + Environment.NewLine + + $"FROM [{TestCtxProvider.Schema}].[TestEntities] AS [t] WITH (UPDLOCK, ROWLOCK)" + Environment.NewLine + + $"INNER JOIN [{TestCtxProvider.Schema}].[TestEntities] AS [t2] ON [t].[Id] = [t2].[ParentId]" + Environment.NewLine + + "WHERE [t].[Id] IN (" + Environment.NewLine + + " SELECT [#].[Column1]" + Environment.NewLine + + " FROM [#TempTable_1] AS [#] WITH (UPDLOCK)" + Environment.NewLine + + ")" + Environment.NewLine + + "ORDER BY [t].[Id]"); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerDbFunctionsExtensionsTests/RowNumber.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerDbFunctionsExtensionsTests/RowNumber.cs index 88ed7519..d68e7885 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerDbFunctionsExtensionsTests/RowNumber.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerDbFunctionsExtensionsTests/RowNumber.cs @@ -1,363 +1,363 @@ -using System.Linq.Expressions; -using Microsoft.Data.SqlClient; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Extensions.SqlServerDbFunctionsExtensionsTests; - -// ReSharper disable once InconsistentNaming -public class RowNumber : IntegrationTestsBase -{ - public RowNumber(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public void Generates_RowNumber_with_orderby_and_one_column() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Name, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) - }) - .ToList(); - - result.First(t => t.Name == "1").RowNumber.Should().Be(1); - result.First(t => t.Name == "2").RowNumber.Should().Be(2); - } - - [Fact] - public void Generates_RowNumber_with_orderby_and_one_column_generic_approach() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var propertyName = nameof(TestEntity.Name); - - var query = ActDbContext.TestEntities; - var result = AppendSelect(query, propertyName, new { Name = String.Empty, RowNumber = 0L }).ToList(); - - result.First(t => t.Name == "1").RowNumber.Should().Be(1); - result.First(t => t.Name == "2").RowNumber.Should().Be(2); - } - - private static IQueryable AppendSelect(IQueryable query, string propertyName, TResult anonymousTypeSample) - { - var testEntityType = typeof(T); - var propertyInfo = testEntityType.GetProperty(propertyName) ?? throw new Exception($"Property '{propertyName}' not found."); - - var returnType = new { Name = String.Empty, RowNumber = 0L }.GetType(); - var returnTypeCtor = returnType.GetConstructor(new[] { typeof(string), typeof(long) }) - ?? throw new Exception("Constructor not found."); - - var efFunctions = Expression.Constant(EF.Functions); // EF.Functions - var extensionsType = typeof(RelationalDbFunctionsExtensions); - var orderByMethod = extensionsType.GetMethod(nameof(RelationalDbFunctionsExtensions.OrderBy)) // EF.Functions.OrderBy - ?.MakeGenericMethod(propertyInfo.PropertyType) // EF.Functions.OrderBy - ?? throw new Exception("Method 'OrderBy' not found."); - var rowNumberMethod = extensionsType.GetMethods() - .Single(m => m.Name == nameof(RelationalDbFunctionsExtensions.RowNumber) && !m.IsGenericMethod); // EF.Functions.RowNumber - - var param = Expression.Parameter(testEntityType); // e - var nameAccessor = Expression.MakeMemberAccess(param, propertyInfo); // e.Name - var orderByCall = Expression.Call(null, orderByMethod, efFunctions, nameAccessor); // EF.Functions.OrderBy(e.Name) - var rowNumberCall = Expression.Call(null, rowNumberMethod, efFunctions, orderByCall); // EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) - var returnTypeCtorCall = Expression.New(returnTypeCtor, nameAccessor, rowNumberCall); // new { ... } - - var projection = Expression.Lambda>(returnTypeCtorCall, param); - - return query.Select(projection); - } - - [Fact] - public void Generates_RowNumber_with_orderby_and_one_struct_column() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB"), RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("28CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB"), RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Id, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Id)) - }) - .ToList(); - - result.First(t => t.Id == new Guid("18CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB")).RowNumber.Should().Be(1); - result.First(t => t.Id == new Guid("28CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB")).RowNumber.Should().Be(2); - } - - [Fact] - public void Generates_RowNumber_with_orderby_desc_and_one_column() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Name, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderByDescending(e.Name)) - }) - .ToList(); - - result.First(t => t.Name == "1").RowNumber.Should().Be(2); - result.First(t => t.Name == "2").RowNumber.Should().Be(1); - } - - [Fact] - public void Generates_RowNumber_with_orderby_and_two_columns() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Count, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name).ThenBy(e.Count)) - }) - .ToList(); - - result.First(t => t.Count == 1).RowNumber.Should().Be(1); - result.First(t => t.Count == 2).RowNumber.Should().Be(2); - } - - [Fact] - public void Generates_RowNumber_with_orderby_desc_and_two_columns() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Count, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name).ThenByDescending(e.Count)) - }) - .ToList(); - - result.First(t => t.Count == 1).RowNumber.Should().Be(2); - result.First(t => t.Count == 2).RowNumber.Should().Be(1); - } - - [Fact] - public void Generates_RowNumber_with_partitionby_and_orderby_and_one_column() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Name, - RowNumber = EF.Functions.RowNumber(e.Name, EF.Functions.OrderBy(e.Name)) - }) - .ToList(); - - result.First(t => t.Name == "1").RowNumber.Should().Be(1); - result.First(t => t.Name == "2").RowNumber.Should().Be(1); - } - - [Fact] - public void Generates_RowNumber_with_partitionby_and_orderby_and_two_columns() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Count, - RowNumber = EF.Functions.RowNumber(e.Name, e.Count, - EF.Functions.OrderBy(e.Name).ThenBy(e.Count)) - }) - .ToList(); - - result.First(t => t.Count == 1).RowNumber.Should().Be(1); - result.First(t => t.Count == 2).RowNumber.Should().Be(1); - } - - [Fact] - public void Throws_if_RowNumber_contains_NewExpression() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var query = ActDbContext.TestEntities - .Select(e => new - { - e.Count, - RowNumber = EF.Functions.RowNumber(new { e.Name, e.Count }, - EF.Functions.OrderBy(e.Name).ThenBy(e.Count)) - }); - query.Invoking(q => q.ToList()).Should().Throw() - .WithMessage("The window function contains some expressions not supported by the Entity Framework. One of the reason is the creation of new objects like: 'new { e.MyProperty, e.MyOtherProperty }'."); - } - - [Fact] - public void Should_throw_if_accessing_RowNumber_not_within_subquery() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var query = ActDbContext.TestEntities - .Select(e => new - { - e.Name, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) - }) - .Where(i => i.RowNumber == 1); - - query.Invoking(q => q.ToList()) - .Should() - .Throw(); - } - - [Fact] - public void Should_be_able_to_fetch_whole_entity() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var query = ActDbContext.TestEntities - .Select(e => new - { - e, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) - }); - - var entities = query.ToList(); - entities.Should().HaveCount(1); - entities[0].Should().BeEquivalentTo(new - { - e = new TestEntity - { - Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), - Name = "1", - RequiredName = "RequiredName" - }, - RowNumber = 1 - }); - } - - [Fact] - public void Should_filter_for_RowNumber_if_accessing_within_subquery() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Name, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) - }) - .AsSubQuery() - .Where(i => i.RowNumber == 1) - .ToList(); - - result.Should().HaveCount(1); - result[0].Name.Should().Be("1"); - } - - [Fact] - public void Should_support_columns_with_converters() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.ConvertibleClass, - RowNumber = EF.Functions.RowNumber(e.Name, - EF.Functions.OrderBy(e.Name).ThenBy(e.ConvertibleClass)) - }) - .ToList(); - - result.Should().HaveCount(2); - result.Select(e => e.ConvertibleClass?.Key).Should().BeEquivalentTo(new[] { 1, 2 }); - } - - [Fact] - public void Should_support_conversion_to_underlying_column_type() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - ConvertibleClass = (int)e.ConvertibleClass!, -#pragma warning disable CS8604 - RowNumber = EF.Functions.RowNumber(e.Name, (int)e.ConvertibleClass, (int)e.Count, - EF.Functions.OrderBy(e.Name).ThenBy((int)e.ConvertibleClass).ThenBy((int)e.Count)) -#pragma warning restore CS8604 - }) - .ToList(); - - result.Should().HaveCount(2); - result.Select(e => e.ConvertibleClass).Should().AllBeOfType().And.BeEquivalentTo(new[] { 1, 2 }); - } - - [Fact] - public void Should_support_conversion_if_column_type_is_convertable_on_database() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - ConvertibleClass = (long)e.ConvertibleClass!, -#pragma warning disable CS8604 - RowNumber = EF.Functions.RowNumber(e.Name, (long)e.ConvertibleClass, (long)e.Count, - EF.Functions.OrderBy(e.Name).ThenBy((long)e.ConvertibleClass).ThenBy((long)e.Count)) -#pragma warning restore CS8604 - }) - .ToList(); - - result.Should().HaveCount(2); - result.Select(e => e.ConvertibleClass).Should().AllBeOfType().And.BeEquivalentTo(new long[] { 1, 2 }); - } - - [Fact] - public void Should_support_conversion_for_non_trivial_expressions() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - ConvertibleClass = (long)e.ConvertibleClass!, -#pragma warning disable CS8604 - RowNumber = EF.Functions.RowNumber(e.Name, (long)e.ConvertibleClass + 1, (long)e.Count + 1, - EF.Functions.OrderBy(e.Name).ThenBy((long)e.ConvertibleClass + 1).ThenBy((long)e.Count + 1)) -#pragma warning restore CS8604 - }) - .ToList(); - - result.Should().HaveCount(2); - result.Select(e => e.ConvertibleClass).Should().AllBeOfType().And.BeEquivalentTo(new long[] { 1, 2 }); - } -} +using System.Linq.Expressions; +using Microsoft.Data.SqlClient; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Extensions.SqlServerDbFunctionsExtensionsTests; + +// ReSharper disable once InconsistentNaming +public class RowNumber : IntegrationTestsBase +{ + public RowNumber(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public void Generates_RowNumber_with_orderby_and_one_column() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Name, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) + }) + .ToList(); + + result.First(t => t.Name == "1").RowNumber.Should().Be(1); + result.First(t => t.Name == "2").RowNumber.Should().Be(2); + } + + [Fact] + public void Generates_RowNumber_with_orderby_and_one_column_generic_approach() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var propertyName = nameof(TestEntity.Name); + + var query = ActDbContext.TestEntities; + var result = AppendSelect(query, propertyName, new { Name = String.Empty, RowNumber = 0L }).ToList(); + + result.First(t => t.Name == "1").RowNumber.Should().Be(1); + result.First(t => t.Name == "2").RowNumber.Should().Be(2); + } + + private static IQueryable AppendSelect(IQueryable query, string propertyName, TResult anonymousTypeSample) + { + var testEntityType = typeof(T); + var propertyInfo = testEntityType.GetProperty(propertyName) ?? throw new Exception($"Property '{propertyName}' not found."); + + var returnType = new { Name = String.Empty, RowNumber = 0L }.GetType(); + var returnTypeCtor = returnType.GetConstructor(new[] { typeof(string), typeof(long) }) + ?? throw new Exception("Constructor not found."); + + var efFunctions = Expression.Constant(EF.Functions); // EF.Functions + var extensionsType = typeof(RelationalDbFunctionsExtensions); + var orderByMethod = extensionsType.GetMethod(nameof(RelationalDbFunctionsExtensions.OrderBy)) // EF.Functions.OrderBy + ?.MakeGenericMethod(propertyInfo.PropertyType) // EF.Functions.OrderBy + ?? throw new Exception("Method 'OrderBy' not found."); + var rowNumberMethod = extensionsType.GetMethods() + .Single(m => m.Name == nameof(RelationalDbFunctionsExtensions.RowNumber) && !m.IsGenericMethod); // EF.Functions.RowNumber + + var param = Expression.Parameter(testEntityType); // e + var nameAccessor = Expression.MakeMemberAccess(param, propertyInfo); // e.Name + var orderByCall = Expression.Call(null, orderByMethod, efFunctions, nameAccessor); // EF.Functions.OrderBy(e.Name) + var rowNumberCall = Expression.Call(null, rowNumberMethod, efFunctions, orderByCall); // EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) + var returnTypeCtorCall = Expression.New(returnTypeCtor, nameAccessor, rowNumberCall); // new { ... } + + var projection = Expression.Lambda>(returnTypeCtorCall, param); + + return query.Select(projection); + } + + [Fact] + public void Generates_RowNumber_with_orderby_and_one_struct_column() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB"), RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("28CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB"), RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Id, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Id)) + }) + .ToList(); + + result.First(t => t.Id == new Guid("18CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB")).RowNumber.Should().Be(1); + result.First(t => t.Id == new Guid("28CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB")).RowNumber.Should().Be(2); + } + + [Fact] + public void Generates_RowNumber_with_orderby_desc_and_one_column() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Name, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderByDescending(e.Name)) + }) + .ToList(); + + result.First(t => t.Name == "1").RowNumber.Should().Be(2); + result.First(t => t.Name == "2").RowNumber.Should().Be(1); + } + + [Fact] + public void Generates_RowNumber_with_orderby_and_two_columns() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Count, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name).ThenBy(e.Count)) + }) + .ToList(); + + result.First(t => t.Count == 1).RowNumber.Should().Be(1); + result.First(t => t.Count == 2).RowNumber.Should().Be(2); + } + + [Fact] + public void Generates_RowNumber_with_orderby_desc_and_two_columns() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Count, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name).ThenByDescending(e.Count)) + }) + .ToList(); + + result.First(t => t.Count == 1).RowNumber.Should().Be(2); + result.First(t => t.Count == 2).RowNumber.Should().Be(1); + } + + [Fact] + public void Generates_RowNumber_with_partitionby_and_orderby_and_one_column() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Name, + RowNumber = EF.Functions.RowNumber(e.Name, EF.Functions.OrderBy(e.Name)) + }) + .ToList(); + + result.First(t => t.Name == "1").RowNumber.Should().Be(1); + result.First(t => t.Name == "2").RowNumber.Should().Be(1); + } + + [Fact] + public void Generates_RowNumber_with_partitionby_and_orderby_and_two_columns() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Count, + RowNumber = EF.Functions.RowNumber(e.Name, e.Count, + EF.Functions.OrderBy(e.Name).ThenBy(e.Count)) + }) + .ToList(); + + result.First(t => t.Count == 1).RowNumber.Should().Be(1); + result.First(t => t.Count == 2).RowNumber.Should().Be(1); + } + + [Fact] + public void Throws_if_RowNumber_contains_NewExpression() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var query = ActDbContext.TestEntities + .Select(e => new + { + e.Count, + RowNumber = EF.Functions.RowNumber(new { e.Name, e.Count }, + EF.Functions.OrderBy(e.Name).ThenBy(e.Count)) + }); + query.Invoking(q => q.ToList()).Should().Throw() + .WithMessage("The window function contains some expressions not supported by the Entity Framework. One of the reason is the creation of new objects like: 'new { e.MyProperty, e.MyOtherProperty }'."); + } + + [Fact] + public void Should_throw_if_accessing_RowNumber_not_within_subquery() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var query = ActDbContext.TestEntities + .Select(e => new + { + e.Name, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) + }) + .Where(i => i.RowNumber == 1); + + query.Invoking(q => q.ToList()) + .Should() + .Throw(); + } + + [Fact] + public void Should_be_able_to_fetch_whole_entity() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var query = ActDbContext.TestEntities + .Select(e => new + { + e, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) + }); + + var entities = query.ToList(); + entities.Should().HaveCount(1); + entities[0].Should().BeEquivalentTo(new + { + e = new TestEntity + { + Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), + Name = "1", + RequiredName = "RequiredName" + }, + RowNumber = 1 + }); + } + + [Fact] + public void Should_filter_for_RowNumber_if_accessing_within_subquery() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Name, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) + }) + .AsSubQuery() + .Where(i => i.RowNumber == 1) + .ToList(); + + result.Should().HaveCount(1); + result[0].Name.Should().Be("1"); + } + + [Fact] + public void Should_support_columns_with_converters() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.ConvertibleClass, + RowNumber = EF.Functions.RowNumber(e.Name, + EF.Functions.OrderBy(e.Name).ThenBy(e.ConvertibleClass)) + }) + .ToList(); + + result.Should().HaveCount(2); + result.Select(e => e.ConvertibleClass?.Key).Should().BeEquivalentTo(new[] { 1, 2 }); + } + + [Fact] + public void Should_support_conversion_to_underlying_column_type() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + ConvertibleClass = (int)e.ConvertibleClass!, +#pragma warning disable CS8604 + RowNumber = EF.Functions.RowNumber(e.Name, (int)e.ConvertibleClass, (int)e.Count, + EF.Functions.OrderBy(e.Name).ThenBy((int)e.ConvertibleClass).ThenBy((int)e.Count)) +#pragma warning restore CS8604 + }) + .ToList(); + + result.Should().HaveCount(2); + result.Select(e => e.ConvertibleClass).Should().AllBeOfType().And.BeEquivalentTo(new[] { 1, 2 }); + } + + [Fact] + public void Should_support_conversion_if_column_type_is_convertable_on_database() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + ConvertibleClass = (long)e.ConvertibleClass!, +#pragma warning disable CS8604 + RowNumber = EF.Functions.RowNumber(e.Name, (long)e.ConvertibleClass, (long)e.Count, + EF.Functions.OrderBy(e.Name).ThenBy((long)e.ConvertibleClass).ThenBy((long)e.Count)) +#pragma warning restore CS8604 + }) + .ToList(); + + result.Should().HaveCount(2); + result.Select(e => e.ConvertibleClass).Should().AllBeOfType().And.BeEquivalentTo(new long[] { 1, 2 }); + } + + [Fact] + public void Should_support_conversion_for_non_trivial_expressions() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + ConvertibleClass = (long)e.ConvertibleClass!, +#pragma warning disable CS8604 + RowNumber = EF.Functions.RowNumber(e.Name, (long)e.ConvertibleClass + 1, (long)e.Count + 1, + EF.Functions.OrderBy(e.Name).ThenBy((long)e.ConvertibleClass + 1).ThenBy((long)e.Count + 1)) +#pragma warning restore CS8604 + }) + .ToList(); + + result.Should().HaveCount(2); + result.Select(e => e.ConvertibleClass).Should().AllBeOfType().And.BeEquivalentTo(new long[] { 1, 2 }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/DelegatingMigration.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/DelegatingMigration.cs index abe19b19..f6bd4835 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/DelegatingMigration.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/DelegatingMigration.cs @@ -1,13 +1,13 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; - -public class DelegatingMigration : Migration -{ - public Action? ConfigureUp { get; set; } - - protected override void Up(MigrationBuilder migrationBuilder) - { - ConfigureUp?.Invoke(migrationBuilder); - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; + +public class DelegatingMigration : Migration +{ + public Action? ConfigureUp { get; set; } + + protected override void Up(MigrationBuilder migrationBuilder) + { + ConfigureUp?.Invoke(migrationBuilder); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/ExistTestBase.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/ExistTestBase.cs index d1014d71..47787ff3 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/ExistTestBase.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/ExistTestBase.cs @@ -1,40 +1,40 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; - -namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; - -[Collection("SqlServerTests")] -public abstract class ExistTestBase -{ - protected MigrationExtensionsTestDbContext Context { get; } - protected DelegatingMigration Migration { get; } - protected Action ExecuteMigration { get; } - - protected ExistTestBase(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - { - var loggerFactory = TestContext.Instance.GetLoggerFactory(testOutputHelper); - var options = new DbContextOptionsBuilder() - .UseSqlServer(sqlServerFixture.ConnectionString, - sqlServerBuilder => sqlServerBuilder.UseThinktectureSqlServerMigrationsSqlGenerator()) - .UseLoggerFactory(loggerFactory) - .EnableDetailedErrors() - .EnableSensitiveDataLogging() - .Options; - - Context = new MigrationExtensionsTestDbContext(options); - Migration = new DelegatingMigration(); - - Context.Database.OpenConnection(); - - var migrator = Context.GetService(); - var commandExecutor = Context.GetService(); - var connection = Context.GetService(); - - ExecuteMigration = () => - { - var commands = migrator.Generate(Migration.UpOperations); - commandExecutor.ExecuteNonQuery(commands, connection); - }; - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; + +[Collection("SqlServerTests")] +public abstract class ExistTestBase +{ + protected MigrationExtensionsTestDbContext Context { get; } + protected DelegatingMigration Migration { get; } + protected Action ExecuteMigration { get; } + + protected ExistTestBase(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + { + var loggerFactory = TestContext.Instance.GetLoggerFactory(testOutputHelper); + var options = new DbContextOptionsBuilder() + .UseSqlServer(sqlServerFixture.ConnectionString, + sqlServerBuilder => sqlServerBuilder.UseThinktectureSqlServerMigrationsSqlGenerator()) + .UseLoggerFactory(loggerFactory) + .EnableDetailedErrors() + .EnableSensitiveDataLogging() + .Options; + + Context = new MigrationExtensionsTestDbContext(options); + Migration = new DelegatingMigration(); + + Context.Database.OpenConnection(); + + var migrator = Context.GetService(); + var commandExecutor = Context.GetService(); + var connection = Context.GetService(); + + ExecuteMigration = () => + { + var commands = migrator.Generate(Migration.UpOperations); + commandExecutor.ExecuteNonQuery(commands, connection); + }; + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/IfExists.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/IfExists.cs index 404740e5..e2625090 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/IfExists.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/IfExists.cs @@ -1,166 +1,166 @@ -using System.Reflection; - -namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; - -public class IfExists : ExistTestBase -{ - public IfExists(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public void Should_throw_InvalidOperationException_if_used_with_CreateTable() - { - Migration.ConfigureUp = builder => builder.CreateTable("Foo", table => new { Bar = table.Column() }).IfExists(); - - ExecuteMigration.Invoking(a => a()) - .Should().Throw(); - } - - [Fact] - public void Should_not_throw_if_used_with_DropTable_and_table_not_exists() - { - Migration.ConfigureUp = builder => builder.DropTable("Foo").IfExists(); - - ExecuteMigration.Invoking(a => a()) - .Should().NotThrow(); - } - - [Fact] - public void Should_drop_table_if_used_with_DropTable_and_table_exists() - { - var tableName = MethodBase.GetCurrentMethod()!.Name; - - try - { - Migration.ConfigureUp = builder => builder.DropTable(tableName).IfExists(); - Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); - - ExecuteMigration(); - - Context.GetTableColumns(tableName).ToList().Should().BeEmpty(); - } - finally - { - Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); - } - } - - [Fact] - public void Should_throw_InvalidOperationException_if_used_with_AddColumn() - { - Migration.ConfigureUp = builder => builder.AddColumn("Foo", "Bar").IfExists(); - - ExecuteMigration.Invoking(a => a()) - .Should().Throw(); - } - - [Fact] - public void Should_not_throw_if_used_with_DropColumn_and_column_not_exists() - { - Migration.ConfigureUp = builder => builder.DropColumn("Foo", "Bar").IfExists(); - - ExecuteMigration.Invoking(a => a()) - .Should().NotThrow(); - } - - [Fact] - public void Should_drop_column_if_used_with_DropColumn_and_column_exists() - { - var tableName = MethodBase.GetCurrentMethod()!.Name; - - try - { - Migration.ConfigureUp = builder => builder.DropColumn("Col1", tableName).IfExists(); - Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); - - ExecuteMigration(); - - Context.GetTableColumns(tableName).ToList().Should().HaveCount(1); - } - finally - { - Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); - } - } - - [Fact] - public void Should_throw_InvalidOperationException_if_used_with_CreateIndex() - { - Migration.ConfigureUp = builder => builder.CreateIndex("Foo", "Bar", "Col1").IfExists(); - - ExecuteMigration.Invoking(a => a()) - .Should().Throw(); - } - - [Fact] - public void Should_not_throw_if_used_with_DropIndex_and_index_not_exists() - { - Migration.ConfigureUp = builder => builder.DropIndex("Foo", "Bar").IfExists(); - - ExecuteMigration.Invoking(a => a()) - .Should().NotThrow(); - } - - [Fact] - public void Should_drop_index_if_used_with_DropIndex_and_index_exists() - { - var tableName = MethodBase.GetCurrentMethod()!.Name; - - try - { - Migration.ConfigureUp = builder => builder.DropIndex("IX1", tableName).IfExists(); - Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); - Context.Database.ExecuteSqlRaw($"CREATE INDEX IX1 ON [{tableName}] (Col1, Col2)"); - - ExecuteMigration(); - - Context.GetIndexes(tableName).ToList().Should().BeEmpty(); - } - finally - { - Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); - } - } - - [Fact] - public void Should_throw_InvalidOperationException_if_used_with_AddUniqueConstraint() - { - Migration.ConfigureUp = builder => builder.AddUniqueConstraint("Foo", "Bar", "Col1").IfExists(); - - ExecuteMigration.Invoking(a => a()) - .Should().Throw(); - } - - [Fact] - public void Should_not_throw_if_used_with_DropUniqueConstraint_and_constraint_not_exists() - { - Migration.ConfigureUp = builder => builder.DropUniqueConstraint("Foo", "Bar").IfExists(); - - ExecuteMigration.Invoking(a => a()) - .Should().NotThrow(); - } - - [Fact] - public void Should_drop_constraint_if_used_with_DropUniqueConstraint_and_constraint_exists() - { - var tableName = MethodBase.GetCurrentMethod()!.Name; - var constraintName = $"UC_{tableName}"; - - try - { - Migration.ConfigureUp = builder => builder.DropUniqueConstraint(constraintName, tableName).IfExists(); - Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); - Context.Database.ExecuteSqlRaw($"ALTER TABLE [{tableName}] ADD CONSTRAINT [{constraintName}] UNIQUE (Col1);"); - - ExecuteMigration(); - - Context.GetUniqueConstraints(constraintName).ToList().Should().BeEmpty(); - } - finally - { - Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); - } - } -} +using System.Reflection; + +namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; + +public class IfExists : ExistTestBase +{ + public IfExists(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public void Should_throw_InvalidOperationException_if_used_with_CreateTable() + { + Migration.ConfigureUp = builder => builder.CreateTable("Foo", table => new { Bar = table.Column() }).IfExists(); + + ExecuteMigration.Invoking(a => a()) + .Should().Throw(); + } + + [Fact] + public void Should_not_throw_if_used_with_DropTable_and_table_not_exists() + { + Migration.ConfigureUp = builder => builder.DropTable("Foo").IfExists(); + + ExecuteMigration.Invoking(a => a()) + .Should().NotThrow(); + } + + [Fact] + public void Should_drop_table_if_used_with_DropTable_and_table_exists() + { + var tableName = MethodBase.GetCurrentMethod()!.Name; + + try + { + Migration.ConfigureUp = builder => builder.DropTable(tableName).IfExists(); + Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); + + ExecuteMigration(); + + Context.GetTableColumns(tableName).ToList().Should().BeEmpty(); + } + finally + { + Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); + } + } + + [Fact] + public void Should_throw_InvalidOperationException_if_used_with_AddColumn() + { + Migration.ConfigureUp = builder => builder.AddColumn("Foo", "Bar").IfExists(); + + ExecuteMigration.Invoking(a => a()) + .Should().Throw(); + } + + [Fact] + public void Should_not_throw_if_used_with_DropColumn_and_column_not_exists() + { + Migration.ConfigureUp = builder => builder.DropColumn("Foo", "Bar").IfExists(); + + ExecuteMigration.Invoking(a => a()) + .Should().NotThrow(); + } + + [Fact] + public void Should_drop_column_if_used_with_DropColumn_and_column_exists() + { + var tableName = MethodBase.GetCurrentMethod()!.Name; + + try + { + Migration.ConfigureUp = builder => builder.DropColumn("Col1", tableName).IfExists(); + Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); + + ExecuteMigration(); + + Context.GetTableColumns(tableName).ToList().Should().HaveCount(1); + } + finally + { + Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); + } + } + + [Fact] + public void Should_throw_InvalidOperationException_if_used_with_CreateIndex() + { + Migration.ConfigureUp = builder => builder.CreateIndex("Foo", "Bar", "Col1").IfExists(); + + ExecuteMigration.Invoking(a => a()) + .Should().Throw(); + } + + [Fact] + public void Should_not_throw_if_used_with_DropIndex_and_index_not_exists() + { + Migration.ConfigureUp = builder => builder.DropIndex("Foo", "Bar").IfExists(); + + ExecuteMigration.Invoking(a => a()) + .Should().NotThrow(); + } + + [Fact] + public void Should_drop_index_if_used_with_DropIndex_and_index_exists() + { + var tableName = MethodBase.GetCurrentMethod()!.Name; + + try + { + Migration.ConfigureUp = builder => builder.DropIndex("IX1", tableName).IfExists(); + Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); + Context.Database.ExecuteSqlRaw($"CREATE INDEX IX1 ON [{tableName}] (Col1, Col2)"); + + ExecuteMigration(); + + Context.GetIndexes(tableName).ToList().Should().BeEmpty(); + } + finally + { + Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); + } + } + + [Fact] + public void Should_throw_InvalidOperationException_if_used_with_AddUniqueConstraint() + { + Migration.ConfigureUp = builder => builder.AddUniqueConstraint("Foo", "Bar", "Col1").IfExists(); + + ExecuteMigration.Invoking(a => a()) + .Should().Throw(); + } + + [Fact] + public void Should_not_throw_if_used_with_DropUniqueConstraint_and_constraint_not_exists() + { + Migration.ConfigureUp = builder => builder.DropUniqueConstraint("Foo", "Bar").IfExists(); + + ExecuteMigration.Invoking(a => a()) + .Should().NotThrow(); + } + + [Fact] + public void Should_drop_constraint_if_used_with_DropUniqueConstraint_and_constraint_exists() + { + var tableName = MethodBase.GetCurrentMethod()!.Name; + var constraintName = $"UC_{tableName}"; + + try + { + Migration.ConfigureUp = builder => builder.DropUniqueConstraint(constraintName, tableName).IfExists(); + Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); + Context.Database.ExecuteSqlRaw($"ALTER TABLE [{tableName}] ADD CONSTRAINT [{constraintName}] UNIQUE (Col1);"); + + ExecuteMigration(); + + Context.GetUniqueConstraints(constraintName).ToList().Should().BeEmpty(); + } + finally + { + Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/IfNotExists.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/IfNotExists.cs index fe457d0c..743ab75f 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/IfNotExists.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/IfNotExists.cs @@ -1,213 +1,213 @@ -using System.Reflection; - -namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; - -public class IfNotExists : ExistTestBase -{ - public IfNotExists(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public void Should_create_table_if_used_with_CreateTable_and_table_not_exists() - { - var tableName = MethodBase.GetCurrentMethod()!.Name; - - try - { - Migration.ConfigureUp = builder => builder.CreateTable(tableName, table => new { Bar = table.Column() }).IfNotExists(); - - ExecuteMigration(); - - Context.GetTableColumns(tableName).ToList().Should().HaveCount(1); - } - finally - { - Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); - } - } - - [Fact] - public void Should_not_throw_if_used_with_CreateTable_and_table_exists() - { - var tableName = MethodBase.GetCurrentMethod()!.Name; - - try - { - Migration.ConfigureUp = builder => builder.CreateTable(tableName, table => new { Bar = table.Column() }).IfNotExists(); - Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); - - ExecuteMigration(); - - Context.GetTableColumns(tableName).ToList().Should().HaveCount(2); - } - finally - { - Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); - } - } - - [Fact] - public void Should_throw_InvalidOperationException_if_used_with_DropTable() - { - Migration.ConfigureUp = builder => builder.DropTable("Foo").IfNotExists(); - - ExecuteMigration.Invoking(a => a()) - .Should().Throw(); - } - - [Fact] - public void Should_add_column_if_used_with_AddColumn_and_column_not_exists() - { - var tableName = MethodBase.GetCurrentMethod()!.Name; - - try - { - Migration.ConfigureUp = builder => builder.AddColumn("Col2", tableName).IfNotExists(); - Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT)"); - - ExecuteMigration(); - - Context.GetTableColumns(tableName).ToList().Should().HaveCount(2); - } - finally - { - Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); - } - } - - [Fact] - public void Should_not_throw_if_used_with_AddColumn_and_column_exists() - { - var tableName = MethodBase.GetCurrentMethod()!.Name; - - try - { - Migration.ConfigureUp = builder => builder.AddColumn("Col2", tableName).IfNotExists(); - Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); - - ExecuteMigration(); - - Context.GetTableColumns(tableName).ToList().Should().HaveCount(2); - } - finally - { - Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); - } - } - - [Fact] - public void Should_throw_InvalidOperationException_if_used_with_DropColumn() - { - Migration.ConfigureUp = builder => builder.DropColumn("Foo", "Bar").IfNotExists(); - - ExecuteMigration.Invoking(a => a()) - .Should().Throw(); - } - - [Fact] - public void Should_not_throw_if_used_with_CreateIndex_and_index_exists() - { - var tableName = MethodBase.GetCurrentMethod()!.Name; - - try - { - Migration.ConfigureUp = builder => builder.CreateIndex("IX1", tableName, "Col1").IfNotExists(); - Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); - Context.Database.ExecuteSqlRaw($"CREATE INDEX IX1 ON [{tableName}] (Col1, Col2)"); - - ExecuteMigration(); - - var indexes = Context.GetIndexes(tableName).ToList(); - indexes.Should().HaveCount(1); - Context.GetIndexColumns(tableName, indexes[0].Index_Id).ToList().Should().HaveCount(2); - } - finally - { - Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); - } - } - - [Fact] - public void Should_create_index_if_used_with_CreateIndex_and_index_not_exists() - { - var tableName = MethodBase.GetCurrentMethod()!.Name; - - try - { - Migration.ConfigureUp = builder => builder.CreateIndex("IX1", tableName, "Col1").IfNotExists(); - Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); - - ExecuteMigration(); - - var indexes = Context.GetIndexes(tableName).ToList(); - indexes.Should().HaveCount(1); - Context.GetIndexColumns(tableName, indexes[0].Index_Id).ToList().Should().HaveCount(1); - } - finally - { - Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); - } - } - - [Fact] - public void Should_throw_InvalidOperationException_if_used_with_DropIndex() - { - Migration.ConfigureUp = builder => builder.DropIndex("Foo", "Bar").IfNotExists(); - - ExecuteMigration.Invoking(a => a()) - .Should().Throw(); - } - - [Fact] - public void Should_not_throw_if_used_with_AddUniqueConstraint_and_constraint_exists() - { - var tableName = MethodBase.GetCurrentMethod()!.Name; - var constraintName = $"UC_{tableName}"; - - try - { - Migration.ConfigureUp = builder => builder.AddUniqueConstraint(constraintName, tableName, "Col1").IfNotExists(); - Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); - Context.Database.ExecuteSqlRaw($"ALTER TABLE [{tableName}] ADD CONSTRAINT [{constraintName}] UNIQUE (Col1);"); - - ExecuteMigration.Invoking(a => a()) - .Should().NotThrow(); - } - finally - { - Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); - } - } - - [Fact] - public void Should_create_constraint_if_used_with_AddUniqueConstraint_and_constraint_not_exists() - { - var tableName = MethodBase.GetCurrentMethod()!.Name; - var constraintName = $"UC_{tableName}"; - - try - { - Migration.ConfigureUp = builder => builder.AddUniqueConstraint(constraintName, tableName, "Col1").IfNotExists(); - Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); - - ExecuteMigration(); - - Context.GetUniqueConstraints(constraintName).ToList().Should().HaveCount(1); - } - finally - { - Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); - } - } - - [Fact] - public void Should_throw_InvalidOperationException_if_used_with_DropUniqueConstraint() - { - Migration.ConfigureUp = builder => builder.DropUniqueConstraint("Foo", "Bar").IfNotExists(); - - ExecuteMigration.Invoking(a => a()) - .Should().Throw(); - } -} +using System.Reflection; + +namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; + +public class IfNotExists : ExistTestBase +{ + public IfNotExists(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public void Should_create_table_if_used_with_CreateTable_and_table_not_exists() + { + var tableName = MethodBase.GetCurrentMethod()!.Name; + + try + { + Migration.ConfigureUp = builder => builder.CreateTable(tableName, table => new { Bar = table.Column() }).IfNotExists(); + + ExecuteMigration(); + + Context.GetTableColumns(tableName).ToList().Should().HaveCount(1); + } + finally + { + Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); + } + } + + [Fact] + public void Should_not_throw_if_used_with_CreateTable_and_table_exists() + { + var tableName = MethodBase.GetCurrentMethod()!.Name; + + try + { + Migration.ConfigureUp = builder => builder.CreateTable(tableName, table => new { Bar = table.Column() }).IfNotExists(); + Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); + + ExecuteMigration(); + + Context.GetTableColumns(tableName).ToList().Should().HaveCount(2); + } + finally + { + Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); + } + } + + [Fact] + public void Should_throw_InvalidOperationException_if_used_with_DropTable() + { + Migration.ConfigureUp = builder => builder.DropTable("Foo").IfNotExists(); + + ExecuteMigration.Invoking(a => a()) + .Should().Throw(); + } + + [Fact] + public void Should_add_column_if_used_with_AddColumn_and_column_not_exists() + { + var tableName = MethodBase.GetCurrentMethod()!.Name; + + try + { + Migration.ConfigureUp = builder => builder.AddColumn("Col2", tableName).IfNotExists(); + Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT)"); + + ExecuteMigration(); + + Context.GetTableColumns(tableName).ToList().Should().HaveCount(2); + } + finally + { + Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); + } + } + + [Fact] + public void Should_not_throw_if_used_with_AddColumn_and_column_exists() + { + var tableName = MethodBase.GetCurrentMethod()!.Name; + + try + { + Migration.ConfigureUp = builder => builder.AddColumn("Col2", tableName).IfNotExists(); + Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); + + ExecuteMigration(); + + Context.GetTableColumns(tableName).ToList().Should().HaveCount(2); + } + finally + { + Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); + } + } + + [Fact] + public void Should_throw_InvalidOperationException_if_used_with_DropColumn() + { + Migration.ConfigureUp = builder => builder.DropColumn("Foo", "Bar").IfNotExists(); + + ExecuteMigration.Invoking(a => a()) + .Should().Throw(); + } + + [Fact] + public void Should_not_throw_if_used_with_CreateIndex_and_index_exists() + { + var tableName = MethodBase.GetCurrentMethod()!.Name; + + try + { + Migration.ConfigureUp = builder => builder.CreateIndex("IX1", tableName, "Col1").IfNotExists(); + Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); + Context.Database.ExecuteSqlRaw($"CREATE INDEX IX1 ON [{tableName}] (Col1, Col2)"); + + ExecuteMigration(); + + var indexes = Context.GetIndexes(tableName).ToList(); + indexes.Should().HaveCount(1); + Context.GetIndexColumns(tableName, indexes[0].Index_Id).ToList().Should().HaveCount(2); + } + finally + { + Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); + } + } + + [Fact] + public void Should_create_index_if_used_with_CreateIndex_and_index_not_exists() + { + var tableName = MethodBase.GetCurrentMethod()!.Name; + + try + { + Migration.ConfigureUp = builder => builder.CreateIndex("IX1", tableName, "Col1").IfNotExists(); + Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); + + ExecuteMigration(); + + var indexes = Context.GetIndexes(tableName).ToList(); + indexes.Should().HaveCount(1); + Context.GetIndexColumns(tableName, indexes[0].Index_Id).ToList().Should().HaveCount(1); + } + finally + { + Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); + } + } + + [Fact] + public void Should_throw_InvalidOperationException_if_used_with_DropIndex() + { + Migration.ConfigureUp = builder => builder.DropIndex("Foo", "Bar").IfNotExists(); + + ExecuteMigration.Invoking(a => a()) + .Should().Throw(); + } + + [Fact] + public void Should_not_throw_if_used_with_AddUniqueConstraint_and_constraint_exists() + { + var tableName = MethodBase.GetCurrentMethod()!.Name; + var constraintName = $"UC_{tableName}"; + + try + { + Migration.ConfigureUp = builder => builder.AddUniqueConstraint(constraintName, tableName, "Col1").IfNotExists(); + Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); + Context.Database.ExecuteSqlRaw($"ALTER TABLE [{tableName}] ADD CONSTRAINT [{constraintName}] UNIQUE (Col1);"); + + ExecuteMigration.Invoking(a => a()) + .Should().NotThrow(); + } + finally + { + Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); + } + } + + [Fact] + public void Should_create_constraint_if_used_with_AddUniqueConstraint_and_constraint_not_exists() + { + var tableName = MethodBase.GetCurrentMethod()!.Name; + var constraintName = $"UC_{tableName}"; + + try + { + Migration.ConfigureUp = builder => builder.AddUniqueConstraint(constraintName, tableName, "Col1").IfNotExists(); + Context.Database.ExecuteSqlRaw($"CREATE TABLE [{tableName}] (Col1 INT, Col2 INT)"); + + ExecuteMigration(); + + Context.GetUniqueConstraints(constraintName).ToList().Should().HaveCount(1); + } + finally + { + Context.Database.ExecuteSqlRaw($"DROP TABLE IF EXISTS [{tableName}]"); + } + } + + [Fact] + public void Should_throw_InvalidOperationException_if_used_with_DropUniqueConstraint() + { + Migration.ConfigureUp = builder => builder.DropUniqueConstraint("Foo", "Bar").IfNotExists(); + + ExecuteMigration.Invoking(a => a()) + .Should().Throw(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/MigrationExtensionsTestDbContext.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/MigrationExtensionsTestDbContext.cs index 73e6cd14..27fa8630 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/MigrationExtensionsTestDbContext.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/MigrationExtensionsTestDbContext.cs @@ -1,90 +1,90 @@ -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; - -public class MigrationExtensionsTestDbContext : DbContext -{ - public MigrationExtensionsTestDbContext(DbContextOptions options) - : base(options) - { - } - - /// - protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) - { - configurationBuilder.Properties(builder => builder - .HavePrecision(18, 5)); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity().HasNoKey(); - modelBuilder.Entity().HasNoKey(); - modelBuilder.Entity().HasNoKey(); - modelBuilder.Entity().HasNoKey(); - } - - public IQueryable GetTableColumns(string tableName) - { - ArgumentNullException.ThrowIfNull(tableName); - - return Set().FromSqlInterpolated($""" - SELECT - * - FROM - INFORMATION_SCHEMA.COLUMNS WITH (NOLOCK) - WHERE - OBJECT_ID(TABLE_NAME) = OBJECT_ID({tableName}) - """); - } - - public IQueryable GetIndexes(string tableName) - { - ArgumentNullException.ThrowIfNull(tableName); - - return Set().FromSqlInterpolated($""" - SELECT - * - FROM - SYS.INDEXES WITH (NOLOCK) - WHERE - OBJECT_ID = OBJECT_ID({tableName}) - AND Index_Id <> 0 - """); - } - - public IQueryable GetIndexColumns(string tableName, int indexId) - { - ArgumentNullException.ThrowIfNull(tableName); - - return Set().FromSqlInterpolated($""" - SELECT - c.* - FROM - SYS.INDEXES AS i - INNER JOIN SYS.INDEX_COLUMNS AS c - ON i.object_id = c.object_id - AND i.index_id = c.index_id - WHERE - i.object_id = OBJECT_ID({tableName}) - AND i.index_id = {indexId}; - """); - } - - public IQueryable GetUniqueConstraints(string constraintName) - { - ArgumentNullException.ThrowIfNull(constraintName); - - return Set().FromSqlInterpolated($""" - SELECT - * - FROM - INFORMATION_SCHEMA.TABLE_CONSTRAINTS - WHERE - CONSTRAINT_TYPE = 'UNIQUE' - AND CONSTRAINT_NAME = {constraintName}; - """); - } -} +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; + +public class MigrationExtensionsTestDbContext : DbContext +{ + public MigrationExtensionsTestDbContext(DbContextOptions options) + : base(options) + { + } + + /// + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + configurationBuilder.Properties(builder => builder + .HavePrecision(18, 5)); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity().HasNoKey(); + modelBuilder.Entity().HasNoKey(); + modelBuilder.Entity().HasNoKey(); + modelBuilder.Entity().HasNoKey(); + } + + public IQueryable GetTableColumns(string tableName) + { + ArgumentNullException.ThrowIfNull(tableName); + + return Set().FromSqlInterpolated($""" + SELECT + * + FROM + INFORMATION_SCHEMA.COLUMNS WITH (NOLOCK) + WHERE + OBJECT_ID(TABLE_NAME) = OBJECT_ID({tableName}) + """); + } + + public IQueryable GetIndexes(string tableName) + { + ArgumentNullException.ThrowIfNull(tableName); + + return Set().FromSqlInterpolated($""" + SELECT + * + FROM + SYS.INDEXES WITH (NOLOCK) + WHERE + OBJECT_ID = OBJECT_ID({tableName}) + AND Index_Id <> 0 + """); + } + + public IQueryable GetIndexColumns(string tableName, int indexId) + { + ArgumentNullException.ThrowIfNull(tableName); + + return Set().FromSqlInterpolated($""" + SELECT + c.* + FROM + SYS.INDEXES AS i + INNER JOIN SYS.INDEX_COLUMNS AS c + ON i.object_id = c.object_id + AND i.index_id = c.index_id + WHERE + i.object_id = OBJECT_ID({tableName}) + AND i.index_id = {indexId}; + """); + } + + public IQueryable GetUniqueConstraints(string constraintName) + { + ArgumentNullException.ThrowIfNull(constraintName); + + return Set().FromSqlInterpolated($""" + SELECT + * + FROM + INFORMATION_SCHEMA.TABLE_CONSTRAINTS + WHERE + CONSTRAINT_TYPE = 'UNIQUE' + AND CONSTRAINT_NAME = {constraintName}; + """); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/SysIndex.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/SysIndex.cs index 808dbdb9..5ca3b5cc 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/SysIndex.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/SysIndex.cs @@ -1,7 +1,7 @@ -namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; - -public class SysIndex -{ - public int Object_Id { get; set; } - public int Index_Id { get; set; } +namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; + +public class SysIndex +{ + public int Object_Id { get; set; } + public int Index_Id { get; set; } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/SysIndexColumn.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/SysIndexColumn.cs index eb3c8441..4b41ff0b 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/SysIndexColumn.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/SysIndexColumn.cs @@ -1,7 +1,7 @@ -namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; - -public class SysIndexColumn -{ - public int Index_Id { get; set; } - public int Index_Column_Id { get; set; } +namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; + +public class SysIndexColumn +{ + public int Index_Id { get; set; } + public int Index_Column_Id { get; set; } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/UniqueConstraint.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/UniqueConstraint.cs index 82868f4b..b207a275 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/UniqueConstraint.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Extensions/SqlServerOperationBuilderExtensionsTests/UniqueConstraint.cs @@ -1,12 +1,12 @@ -namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; - -public class UniqueConstraint -{ - public string Constraint_Name { get; set; } - -#nullable disable - private UniqueConstraint() - { - } -#nullable enable +namespace Thinktecture.Extensions.SqlServerOperationBuilderExtensionsTests; + +public class UniqueConstraint +{ + public string Constraint_Name { get; set; } + +#nullable disable + private UniqueConstraint() + { + } +#nullable enable } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/IntegrationTestsBase.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/IntegrationTestsBase.cs index ef95748f..f0b09a21 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/IntegrationTestsBase.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/IntegrationTestsBase.cs @@ -1,87 +1,87 @@ -using System.Text.Json; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.Extensions.Logging; -using Thinktecture.EntityFrameworkCore.Infrastructure; -using Thinktecture.EntityFrameworkCore.Query; -using Thinktecture.EntityFrameworkCore.Testing; -using Thinktecture.Json; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture; - -[Collection("SqlServerTests")] -public class IntegrationTestsBase : SqlServerDbContextIntegrationTests -{ - private static readonly JsonSerializerOptions _jsonSerializerOptions = new() { Converters = { new ConvertibleClassConverter() } }; - - protected Action? ConfigureModel { get; set; } - protected Action? Configure { get; set; } - protected IReadOnlyCollection ExecutedCommands => TestCtxProvider.ExecutedCommands ?? throw new InvalidOperationException("Capturing executed commands wasn't enabled."); - protected string? Schema => TestCtxProvider.Schema; - - protected bool IsTenantDatabaseSupportEnabled { get; set; } - protected ITenantDatabaseProvider TenantDatabaseProviderMock { get; } - - protected IntegrationTestsBase(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : this(sqlServerFixture.ConnectionString, testOutputHelper, ITestIsolationOptions.DeleteData(NonExistingTableFilter)) - { - Configure = b => b.ConfigureWarnings(warningsBuilder => warningsBuilder.Ignore(RelationalEventId.PendingModelChangesWarning)); - } - - private static bool NonExistingTableFilter(IEntityType entityType) - { - return entityType.ClrType != typeof(TestEntityWithCollation) - && entityType.ClrType != typeof(CustomTempTable) - && entityType.ClrType != typeof(OwnedEntity) - && entityType.ClrType != typeof(OwnedEntity_Owns_Inline) - && entityType.ClrType != typeof(OwnedEntity_Owns_SeparateOne) - && entityType.ClrType != typeof(OwnedEntity_Owns_SeparateMany) - && entityType.ClrType != typeof(MyParameter) - && entityType.ClrType != typeof(TestTemporalTableEntity); - } - - protected IntegrationTestsBase(string connectionString, ITestOutputHelper testOutputHelper, ITestIsolationOptions isolationOptions) - : base(connectionString, isolationOptions, testOutputHelper) - { - TenantDatabaseProviderMock = Substitute.For(); - } - - protected override void ConfigureTestDbContextProvider(SqlServerTestDbContextProviderBuilder builder) - { - var gitBranchName = TestContext.Instance.Configuration["SourceBranchName"]; - var schema = String.IsNullOrWhiteSpace(gitBranchName) ? "tests" : $"{TestContext.Instance.Configuration["SourceBranchName"]}_tests"; - - schema += "_" + Environment.Version.Major; // for multi-targeting - - builder.UseMigrationExecutionStrategy(new OneTimeMigrationStrategy()) - .UseMigrationLogLevel(LogLevel.Warning) - .CollectExecutedCommands() - .ConfigureOptions((optionsBuilder, _) => - { - optionsBuilder.AddNestedTransactionSupport() - .ConfigureWarnings(warningsBuilder => warningsBuilder.Ignore(CoreEventId.ManyServiceProvidersCreatedWarning)); - - optionsBuilder.AddOrUpdateExtension(extension => - { - extension.Register(typeof(ITenantDatabaseProvider), TenantDatabaseProviderMock); - return extension; - }); - }) - .ConfigureSqlServerOptions((optionsBuilder, _) => - { - optionsBuilder.AddBulkOperationSupport() - .AddWindowFunctionsSupport() - .AddCollectionParameterSupport(_jsonSerializerOptions); - - if (IsTenantDatabaseSupportEnabled) - optionsBuilder.AddTenantDatabaseSupport(); - }) - .UseSharedTableSchema(schema) - .InitializeContext(ctx => - { - ctx.ConfigureModel = ConfigureModel; - ctx.Configure = Configure; - }); - } -} +using System.Text.Json; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.Extensions.Logging; +using Thinktecture.EntityFrameworkCore.Infrastructure; +using Thinktecture.EntityFrameworkCore.Query; +using Thinktecture.EntityFrameworkCore.Testing; +using Thinktecture.Json; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture; + +[Collection("SqlServerTests")] +public class IntegrationTestsBase : SqlServerDbContextIntegrationTests +{ + private static readonly JsonSerializerOptions _jsonSerializerOptions = new() { Converters = { new ConvertibleClassConverter() } }; + + protected Action? ConfigureModel { get; set; } + protected Action? Configure { get; set; } + protected IReadOnlyCollection ExecutedCommands => TestCtxProvider.ExecutedCommands ?? throw new InvalidOperationException("Capturing executed commands wasn't enabled."); + protected string? Schema => TestCtxProvider.Schema; + + protected bool IsTenantDatabaseSupportEnabled { get; set; } + protected ITenantDatabaseProvider TenantDatabaseProviderMock { get; } + + protected IntegrationTestsBase(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : this(sqlServerFixture.ConnectionString, testOutputHelper, ITestIsolationOptions.DeleteData(NonExistingTableFilter)) + { + Configure = b => b.ConfigureWarnings(warningsBuilder => warningsBuilder.Ignore(RelationalEventId.PendingModelChangesWarning)); + } + + private static bool NonExistingTableFilter(IEntityType entityType) + { + return entityType.ClrType != typeof(TestEntityWithCollation) + && entityType.ClrType != typeof(CustomTempTable) + && entityType.ClrType != typeof(OwnedEntity) + && entityType.ClrType != typeof(OwnedEntity_Owns_Inline) + && entityType.ClrType != typeof(OwnedEntity_Owns_SeparateOne) + && entityType.ClrType != typeof(OwnedEntity_Owns_SeparateMany) + && entityType.ClrType != typeof(MyParameter) + && entityType.ClrType != typeof(TestTemporalTableEntity); + } + + protected IntegrationTestsBase(string connectionString, ITestOutputHelper testOutputHelper, ITestIsolationOptions isolationOptions) + : base(connectionString, isolationOptions, testOutputHelper) + { + TenantDatabaseProviderMock = Substitute.For(); + } + + protected override void ConfigureTestDbContextProvider(SqlServerTestDbContextProviderBuilder builder) + { + var gitBranchName = TestContext.Instance.Configuration["SourceBranchName"]; + var schema = String.IsNullOrWhiteSpace(gitBranchName) ? "tests" : $"{TestContext.Instance.Configuration["SourceBranchName"]}_tests"; + + schema += "_" + Environment.Version.Major; // for multi-targeting + + builder.UseMigrationExecutionStrategy(new OneTimeMigrationStrategy()) + .UseMigrationLogLevel(LogLevel.Warning) + .CollectExecutedCommands() + .ConfigureOptions((optionsBuilder, _) => + { + optionsBuilder.AddNestedTransactionSupport() + .ConfigureWarnings(warningsBuilder => warningsBuilder.Ignore(CoreEventId.ManyServiceProvidersCreatedWarning)); + + optionsBuilder.AddOrUpdateExtension(extension => + { + extension.Register(typeof(ITenantDatabaseProvider), TenantDatabaseProviderMock); + return extension; + }); + }) + .ConfigureSqlServerOptions((optionsBuilder, _) => + { + optionsBuilder.AddBulkOperationSupport() + .AddWindowFunctionsSupport() + .AddCollectionParameterSupport(_jsonSerializerOptions); + + if (IsTenantDatabaseSupportEnabled) + optionsBuilder.AddTenantDatabaseSupport(); + }) + .UseSharedTableSchema(schema) + .InitializeContext(ctx => + { + ctx.ConfigureModel = ConfigureModel; + ctx.Configure = Configure; + }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/InteroperabilityTests/TemporalTableTests.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/InteroperabilityTests/TemporalTableTests.cs index c1c70956..44ecac6c 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/InteroperabilityTests/TemporalTableTests.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/InteroperabilityTests/TemporalTableTests.cs @@ -1,18 +1,18 @@ -namespace Thinktecture.InteroperabilityTests; - -public class TemporalTableTests : IntegrationTestsBase -{ - public TemporalTableTests(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) - : base(testOutputHelper, sqlServerFixture) - { - } - - [Fact] - public async Task Should_not_conflict_with_other_components() - { - var query = ActDbContext.TestTemporalTableEntity - .TemporalBetween(new DateTime(2021, 12, 1), new DateTime(2021, 12, 15)); - - await query.Awaiting(q => q.ToListAsync()).Should().NotThrowAsync(); - } -} +namespace Thinktecture.InteroperabilityTests; + +public class TemporalTableTests : IntegrationTestsBase +{ + public TemporalTableTests(ITestOutputHelper testOutputHelper, SqlServerFixture sqlServerFixture) + : base(testOutputHelper, sqlServerFixture) + { + } + + [Fact] + public async Task Should_not_conflict_with_other_components() + { + var query = ActDbContext.TestTemporalTableEntity + .TemporalBetween(new DateTime(2021, 12, 1), new DateTime(2021, 12, 15)); + + await query.Awaiting(q => q.ToListAsync()).Should().NotThrowAsync(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190530131130_Initial_Migration.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190530131130_Initial_Migration.Designer.cs index 15d86b36..3672e86e 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190530131130_Initial_Migration.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190530131130_Initial_Migration.Designer.cs @@ -1,40 +1,40 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20190530131130_Initial_Migration")] - partial class Initial_Migration - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.4-servicing-10062") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Count"); - - b.Property("Name"); - - b.HasKey("Id"); - - b.ToTable("TestEntities"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20190530131130_Initial_Migration")] + partial class Initial_Migration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("TestEntities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190530131130_Initial_Migration.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190530131130_Initial_Migration.cs index 68e14d19..d3c88f2c 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190530131130_Initial_Migration.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190530131130_Initial_Migration.cs @@ -1,26 +1,26 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - // ReSharper disable once UnusedMember.Global - // ReSharper disable once InconsistentNaming - public partial class Initial_Migration : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable("TestEntities", - table => new - { - Id = table.Column(), - Name = table.Column(nullable: true), - Count = table.Column() - }, - constraints: table => table.PrimaryKey("PK_TestEntities", x => x.Id)); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable("TestEntities"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + // ReSharper disable once UnusedMember.Global + // ReSharper disable once InconsistentNaming + public partial class Initial_Migration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable("TestEntities", + table => new + { + Id = table.Column(), + Name = table.Column(nullable: true), + Count = table.Column() + }, + constraints: table => table.PrimaryKey("PK_TestEntities", x => x.Id)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("TestEntities"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190716122852_Add_shadow_and_private_properties.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190716122852_Add_shadow_and_private_properties.Designer.cs index 01622266..e61c0737 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190716122852_Add_shadow_and_private_properties.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190716122852_Add_shadow_and_private_properties.Designer.cs @@ -1,47 +1,47 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20190716122852_Add_shadow_and_private_properties")] - partial class Add_shadow_and_private_properties - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.4-servicing-10062") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Count"); - - b.Property("Name"); - - b.Property("PropertyWithBackingField"); - - b.Property("ShadowProperty") - .HasMaxLength(50); - - b.Property("_privateField"); - - b.HasKey("Id"); - - b.ToTable("TestEntities"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20190716122852_Add_shadow_and_private_properties")] + partial class Add_shadow_and_private_properties + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("Name"); + + b.Property("PropertyWithBackingField"); + + b.Property("ShadowProperty") + .HasMaxLength(50); + + b.Property("_privateField"); + + b.HasKey("Id"); + + b.ToTable("TestEntities"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190716122852_Add_shadow_and_private_properties.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190716122852_Add_shadow_and_private_properties.cs index 02afd1d9..2f042680 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190716122852_Add_shadow_and_private_properties.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190716122852_Add_shadow_and_private_properties.cs @@ -1,23 +1,23 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - // ReSharper disable once UnusedMember.Global - // ReSharper disable once InconsistentNaming - public partial class Add_shadow_and_private_properties : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn("PropertyWithBackingField", "TestEntities"); - migrationBuilder.AddColumn("ShadowProperty", "TestEntities", maxLength: 50, nullable: true); - migrationBuilder.AddColumn("_privateField", "TestEntities"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn("PropertyWithBackingField", "TestEntities"); - migrationBuilder.DropColumn("ShadowProperty", "TestEntities"); - migrationBuilder.DropColumn("_privateField", "TestEntities"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + // ReSharper disable once UnusedMember.Global + // ReSharper disable once InconsistentNaming + public partial class Add_shadow_and_private_properties : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn("PropertyWithBackingField", "TestEntities"); + migrationBuilder.AddColumn("ShadowProperty", "TestEntities", maxLength: 50, nullable: true); + migrationBuilder.AddColumn("_privateField", "TestEntities"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn("PropertyWithBackingField", "TestEntities"); + migrationBuilder.DropColumn("ShadowProperty", "TestEntities"); + migrationBuilder.DropColumn("_privateField", "TestEntities"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808163448_Add_Entity_with_AutoIncrement.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808163448_Add_Entity_with_AutoIncrement.Designer.cs index de65116b..48a1bb89 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808163448_Add_Entity_with_AutoIncrement.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808163448_Add_Entity_with_AutoIncrement.Designer.cs @@ -1,60 +1,60 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20190808163448_Add_Entity_with_AutoIncrement")] - partial class Add_Entity_with_AutoIncrement - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Count"); - - b.Property("Name"); - - b.Property("PropertyWithBackingField"); - - b.Property("ShadowProperty") - .HasMaxLength(50); - - b.Property("_privateField"); - - b.HasKey("Id"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20190808163448_Add_Entity_with_AutoIncrement")] + partial class Add_Entity_with_AutoIncrement + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("Name"); + + b.Property("PropertyWithBackingField"); + + b.Property("ShadowProperty") + .HasMaxLength(50); + + b.Property("_privateField"); + + b.HasKey("Id"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808163448_Add_Entity_with_AutoIncrement.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808163448_Add_Entity_with_AutoIncrement.cs index c54ae4bd..f08996a2 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808163448_Add_Entity_with_AutoIncrement.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808163448_Add_Entity_with_AutoIncrement.cs @@ -1,26 +1,26 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - // ReSharper disable once InconsistentNaming - public partial class Add_Entity_with_AutoIncrement : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable("TestEntitiesWithAutoIncrement", - table => new - { - Id = table.Column() - .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), - Name = table.Column(nullable: true) - }, - constraints: table => table.PrimaryKey("PK_TestEntitiesWithAutoIncrement", x => x.Id)); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable("TestEntitiesWithAutoIncrement"); - } - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + // ReSharper disable once InconsistentNaming + public partial class Add_Entity_with_AutoIncrement : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable("TestEntitiesWithAutoIncrement", + table => new + { + Id = table.Column() + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: true) + }, + constraints: table => table.PrimaryKey("PK_TestEntitiesWithAutoIncrement", x => x.Id)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("TestEntitiesWithAutoIncrement"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808164450_Add_Entity_with_RowVersion.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808164450_Add_Entity_with_RowVersion.Designer.cs index c468a10d..6f809389 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808164450_Add_Entity_with_RowVersion.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808164450_Add_Entity_with_RowVersion.Designer.cs @@ -1,77 +1,77 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20190808164450_Add_Entity_with_RowVersion")] - partial class Add_Entity_with_RowVersion - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Count"); - - b.Property("Name"); - - b.Property("PropertyWithBackingField"); - - b.Property("ShadowProperty") - .HasMaxLength(50); - - b.Property("_privateField"); - - b.HasKey("Id"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Name"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate(); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithRowVersion"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20190808164450_Add_Entity_with_RowVersion")] + partial class Add_Entity_with_RowVersion + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("Name"); + + b.Property("PropertyWithBackingField"); + + b.Property("ShadowProperty") + .HasMaxLength(50); + + b.Property("_privateField"); + + b.HasKey("Id"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate(); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithRowVersion"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808164450_Add_Entity_with_RowVersion.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808164450_Add_Entity_with_RowVersion.cs index 1f70c7f6..0467a001 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808164450_Add_Entity_with_RowVersion.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808164450_Add_Entity_with_RowVersion.cs @@ -1,25 +1,25 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - // ReSharper disable once InconsistentNaming - public partial class Add_Entity_with_RowVersion : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable("TestEntitiesWithRowVersion", - table => new - { - Id = table.Column(), - Name = table.Column(nullable: true), - RowVersion = table.Column(rowVersion: true) - }, - constraints: table => table.PrimaryKey("PK_TestEntitiesWithRowVersion", x => x.Id)); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable("TestEntitiesWithRowVersion"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + // ReSharper disable once InconsistentNaming + public partial class Add_Entity_with_RowVersion : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable("TestEntitiesWithRowVersion", + table => new + { + Id = table.Column(), + Name = table.Column(nullable: true), + RowVersion = table.Column(rowVersion: true) + }, + constraints: table => table.PrimaryKey("PK_TestEntitiesWithRowVersion", x => x.Id)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("TestEntitiesWithRowVersion"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808172621_Add_Entity_with_ShadowProperties.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808172621_Add_Entity_with_ShadowProperties.Designer.cs index 8838b26f..aa2a8a95 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808172621_Add_Entity_with_ShadowProperties.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808172621_Add_Entity_with_ShadowProperties.Designer.cs @@ -1,91 +1,91 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20190808172621_Add_Entity_with_ShadowProperties")] - partial class Add_Entity_with_ShadowProperties - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Count"); - - b.Property("Name"); - - b.Property("PropertyWithBackingField"); - - b.Property("_privateField"); - - b.HasKey("Id"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Name"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate(); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithRowVersion"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Name"); - - b.Property("ShadowIntProperty"); - - b.Property("ShadowStringProperty") - .HasMaxLength(50); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20190808172621_Add_Entity_with_ShadowProperties")] + partial class Add_Entity_with_ShadowProperties + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("Name"); + + b.Property("PropertyWithBackingField"); + + b.Property("_privateField"); + + b.HasKey("Id"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate(); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithRowVersion"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.Property("ShadowIntProperty"); + + b.Property("ShadowStringProperty") + .HasMaxLength(50); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808172621_Add_Entity_with_ShadowProperties.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808172621_Add_Entity_with_ShadowProperties.cs index 44a761a0..df0598c8 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808172621_Add_Entity_with_ShadowProperties.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190808172621_Add_Entity_with_ShadowProperties.cs @@ -1,30 +1,30 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - // ReSharper disable once InconsistentNaming - public partial class Add_Entity_with_ShadowProperties : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn("ShadowProperty", "TestEntities"); - - migrationBuilder.CreateTable("TestEntitiesWithShadowProperties", - table => new - { - Id = table.Column(), - Name = table.Column(nullable: true), - ShadowIntProperty = table.Column(nullable: true), - ShadowStringProperty = table.Column(maxLength: 50, nullable: true) - }, - constraints: table => table.PrimaryKey("PK_TestEntitiesWithShadowProperties", x => x.Id)); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable("TestEntitiesWithShadowProperties"); - - migrationBuilder.AddColumn("ShadowProperty", "TestEntities", maxLength: 50, nullable: true); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + // ReSharper disable once InconsistentNaming + public partial class Add_Entity_with_ShadowProperties : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn("ShadowProperty", "TestEntities"); + + migrationBuilder.CreateTable("TestEntitiesWithShadowProperties", + table => new + { + Id = table.Column(), + Name = table.Column(nullable: true), + ShadowIntProperty = table.Column(nullable: true), + ShadowStringProperty = table.Column(maxLength: 50, nullable: true) + }, + constraints: table => table.PrimaryKey("PK_TestEntitiesWithShadowProperties", x => x.Id)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("TestEntitiesWithShadowProperties"); + + migrationBuilder.AddColumn("ShadowProperty", "TestEntities", maxLength: 50, nullable: true); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190830151402_Property_with_Converter.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190830151402_Property_with_Converter.Designer.cs index b7dce7de..d7a83ef8 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190830151402_Property_with_Converter.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190830151402_Property_with_Converter.Designer.cs @@ -1,93 +1,93 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20190830151402_Property_with_Converter")] - partial class Property_with_Converter - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("ConvertibleClass"); - - b.Property("Count"); - - b.Property("Name"); - - b.Property("PropertyWithBackingField"); - - b.Property("_privateField"); - - b.HasKey("Id"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Name"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate(); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithRowVersion"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Name"); - - b.Property("ShadowIntProperty"); - - b.Property("ShadowStringProperty") - .HasMaxLength(50); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20190830151402_Property_with_Converter")] + partial class Property_with_Converter + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConvertibleClass"); + + b.Property("Count"); + + b.Property("Name"); + + b.Property("PropertyWithBackingField"); + + b.Property("_privateField"); + + b.HasKey("Id"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate(); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithRowVersion"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.Property("ShadowIntProperty"); + + b.Property("ShadowStringProperty") + .HasMaxLength(50); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190830151402_Property_with_Converter.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190830151402_Property_with_Converter.cs index 0180bbf5..de0234f9 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190830151402_Property_with_Converter.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20190830151402_Property_with_Converter.cs @@ -1,19 +1,19 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - // ReSharper disable once InconsistentNaming - // ReSharper disable once UnusedMember.Global - public partial class Property_with_Converter : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn("ConvertibleClass", "TestEntities", nullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn("ConvertibleClass", "TestEntities"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + // ReSharper disable once InconsistentNaming + // ReSharper disable once UnusedMember.Global + public partial class Property_with_Converter : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn("ConvertibleClass", "TestEntities", nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn("ConvertibleClass", "TestEntities"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429182732_Add_table_with_default_values.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429182732_Add_table_with_default_values.Designer.cs index 90588560..6b62e8a0 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429182732_Add_table_with_default_values.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429182732_Add_table_with_default_values.Designer.cs @@ -1,143 +1,143 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20200429182732_Add_table_with_default_values")] - partial class Add_table_with_default_values - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.3") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConvertibleClass") - .HasColumnType("int"); - - b.Property("Count") - .HasColumnType("int"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("PropertyWithBackingField") - .HasColumnType("int"); - - b.Property("_privateField") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:IdentityIncrement", 1) - .HasAnnotation("SqlServer:IdentitySeed", 1) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("newid()"); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("1"); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("2"); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'4'"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'3'"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("rowversion"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithRowVersion"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("ShadowIntProperty") - .HasColumnType("int"); - - b.Property("ShadowStringProperty") - .HasColumnType("nvarchar(50)") - .HasMaxLength(50); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20200429182732_Add_table_with_default_values")] + partial class Add_table_with_default_values + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConvertibleClass") + .HasColumnType("int"); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("PropertyWithBackingField") + .HasColumnType("int"); + + b.Property("_privateField") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:IdentityIncrement", 1) + .HasAnnotation("SqlServer:IdentitySeed", 1) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("newid()"); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("1"); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("2"); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'4'"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'3'"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithRowVersion"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ShadowIntProperty") + .HasColumnType("int"); + + b.Property("ShadowStringProperty") + .HasColumnType("nvarchar(50)") + .HasMaxLength(50); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429182732_Add_table_with_default_values.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429182732_Add_table_with_default_values.cs index 8e96cec1..15854cc0 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429182732_Add_table_with_default_values.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429182732_Add_table_with_default_values.cs @@ -1,26 +1,26 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - public partial class Add_table_with_default_values : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable("TestEntitiesWithDefaultValues", - table => new - { - Id = table.Column(nullable: false, defaultValueSql: "newid()"), - Int = table.Column(nullable: false, defaultValueSql: "1"), - NullableInt = table.Column(nullable: true, defaultValueSql: "2"), - String = table.Column(nullable: false, defaultValueSql: "'3'"), - NullableString = table.Column(nullable: true, defaultValueSql: "'4'") - }, - constraints: table => table.PrimaryKey("PK_TestEntitiesWithDefaultValues", x => x.Id)); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable("TestEntitiesWithDefaultValues"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + public partial class Add_table_with_default_values : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable("TestEntitiesWithDefaultValues", + table => new + { + Id = table.Column(nullable: false, defaultValueSql: "newid()"), + Int = table.Column(nullable: false, defaultValueSql: "1"), + NullableInt = table.Column(nullable: true, defaultValueSql: "2"), + String = table.Column(nullable: false, defaultValueSql: "'3'"), + NullableString = table.Column(nullable: true, defaultValueSql: "'4'") + }, + constraints: table => table.PrimaryKey("PK_TestEntitiesWithDefaultValues", x => x.Id)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("TestEntitiesWithDefaultValues"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429215341_Add_table_with_dotnet_default_values.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429215341_Add_table_with_dotnet_default_values.Designer.cs index db62ab56..3dd44eec 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429215341_Add_table_with_dotnet_default_values.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429215341_Add_table_with_dotnet_default_values.Designer.cs @@ -1,176 +1,176 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20200429215341_Add_table_with_dotnet_default_values")] - partial class Add_table_with_dotnet_default_values - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.3") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConvertibleClass") - .HasColumnType("int"); - - b.Property("Count") - .HasColumnType("int"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("PropertyWithBackingField") - .HasColumnType("int"); - - b.Property("_privateField") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:IdentityIncrement", 1) - .HasAnnotation("SqlServer:IdentitySeed", 1) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(2); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValue("4"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValue("3"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDotnetDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("rowversion"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithRowVersion"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("ShadowIntProperty") - .HasColumnType("int"); - - b.Property("ShadowStringProperty") - .HasColumnType("nvarchar(50)") - .HasMaxLength(50); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("newid()"); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("1"); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("2"); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'4'"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'3'"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDefaultValues"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20200429215341_Add_table_with_dotnet_default_values")] + partial class Add_table_with_dotnet_default_values + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConvertibleClass") + .HasColumnType("int"); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("PropertyWithBackingField") + .HasColumnType("int"); + + b.Property("_privateField") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:IdentityIncrement", 1) + .HasAnnotation("SqlServer:IdentitySeed", 1) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(2); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("4"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("3"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDotnetDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithRowVersion"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ShadowIntProperty") + .HasColumnType("int"); + + b.Property("ShadowStringProperty") + .HasColumnType("nvarchar(50)") + .HasMaxLength(50); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("newid()"); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("1"); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("2"); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'4'"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'3'"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDefaultValues"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429215341_Add_table_with_dotnet_default_values.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429215341_Add_table_with_dotnet_default_values.cs index cb1b5ea8..c9f1763f 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429215341_Add_table_with_dotnet_default_values.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20200429215341_Add_table_with_dotnet_default_values.cs @@ -1,26 +1,26 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - public partial class Add_table_with_dotnet_default_values : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable("TestEntitiesWithDotnetDefaultValues", - table => new - { - Id = table.Column(nullable: false, defaultValue: new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")), - Int = table.Column(nullable: false, defaultValue: 1), - NullableInt = table.Column(nullable: true, defaultValue: 2), - String = table.Column(nullable: false, defaultValue: "3"), - NullableString = table.Column(nullable: true, defaultValue: "4") - }, - constraints: table => table.PrimaryKey("PK_TestEntitiesWithDotnetDefaultValues", x => x.Id)); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable("TestEntitiesWithDotnetDefaultValues"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + public partial class Add_table_with_dotnet_default_values : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable("TestEntitiesWithDotnetDefaultValues", + table => new + { + Id = table.Column(nullable: false, defaultValue: new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")), + Int = table.Column(nullable: false, defaultValue: 1), + NullableInt = table.Column(nullable: true, defaultValue: 2), + String = table.Column(nullable: false, defaultValue: "3"), + NullableString = table.Column(nullable: true, defaultValue: "4") + }, + constraints: table => table.PrimaryKey("PK_TestEntitiesWithDotnetDefaultValues", x => x.Id)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("TestEntitiesWithDotnetDefaultValues"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111083007_Added_Relationship.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111083007_Added_Relationship.Designer.cs index fc9b5908..37fb39b5 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111083007_Added_Relationship.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111083007_Added_Relationship.Designer.cs @@ -1,188 +1,188 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20201111083007_Added_Relationship")] - partial class Added_Relationship - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.3") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConvertibleClass") - .HasColumnType("int"); - - b.Property("Count") - .HasColumnType("int"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("ParentId") - .HasColumnType("uniqueidentifier"); - - b.Property("PropertyWithBackingField") - .HasColumnType("int"); - - b.Property("_privateField") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("ParentId"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:IdentityIncrement", 1) - .HasAnnotation("SqlServer:IdentitySeed", 1) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(2); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValue("4"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValue("3"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDotnetDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("rowversion"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithRowVersion"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("ShadowIntProperty") - .HasColumnType("int"); - - b.Property("ShadowStringProperty") - .HasColumnType("nvarchar(50)") - .HasMaxLength(50); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("newid()"); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("1"); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("2"); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'4'"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'3'"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20201111083007_Added_Relationship")] + partial class Added_Relationship + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConvertibleClass") + .HasColumnType("int"); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("PropertyWithBackingField") + .HasColumnType("int"); + + b.Property("_privateField") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:IdentityIncrement", 1) + .HasAnnotation("SqlServer:IdentitySeed", 1) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(2); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("4"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("3"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDotnetDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithRowVersion"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ShadowIntProperty") + .HasColumnType("int"); + + b.Property("ShadowStringProperty") + .HasColumnType("nvarchar(50)") + .HasMaxLength(50); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("newid()"); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("1"); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("2"); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'4'"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'3'"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111083007_Added_Relationship.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111083007_Added_Relationship.cs index 2453e299..58d9057d 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111083007_Added_Relationship.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111083007_Added_Relationship.cs @@ -1,21 +1,21 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - public partial class Added_Relationship : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn("ParentId", "TestEntities", nullable: true); - migrationBuilder.CreateIndex("IX_TestEntities_ParentId", "TestEntities", "ParentId"); - migrationBuilder.AddForeignKey("FK_TestEntities_TestEntities_ParentId", "TestEntities", "ParentId", "TestEntities", principalColumn: "Id", onDelete: ReferentialAction.Restrict); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey("FK_TestEntities_TestEntities_ParentId", "TestEntities"); - migrationBuilder.DropIndex("IX_TestEntities_ParentId", "TestEntities"); - migrationBuilder.DropColumn("ParentId", "TestEntities"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + public partial class Added_Relationship : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn("ParentId", "TestEntities", nullable: true); + migrationBuilder.CreateIndex("IX_TestEntities_ParentId", "TestEntities", "ParentId"); + migrationBuilder.AddForeignKey("FK_TestEntities_TestEntities_ParentId", "TestEntities", "ParentId", "TestEntities", principalColumn: "Id", onDelete: ReferentialAction.Restrict); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey("FK_TestEntities_TestEntities_ParentId", "TestEntities"); + migrationBuilder.DropIndex("IX_TestEntities_ParentId", "TestEntities"); + migrationBuilder.DropColumn("ParentId", "TestEntities"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111122735_Added_View.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111122735_Added_View.Designer.cs index 02836f5e..989c680e 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111122735_Added_View.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111122735_Added_View.Designer.cs @@ -1,188 +1,188 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20201111122735_Added_View")] - partial class Added_View - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.3") - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConvertibleClass") - .HasColumnType("int"); - - b.Property("Count") - .HasColumnType("int"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("ParentId") - .HasColumnType("uniqueidentifier"); - - b.Property("PropertyWithBackingField") - .HasColumnType("int"); - - b.Property("_privateField") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("ParentId"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:IdentityIncrement", 1) - .HasAnnotation("SqlServer:IdentitySeed", 1) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(2); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValue("4"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValue("3"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDotnetDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("rowversion"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithRowVersion"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("ShadowIntProperty") - .HasColumnType("int"); - - b.Property("ShadowStringProperty") - .HasColumnType("nvarchar(50)") - .HasMaxLength(50); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("newid()"); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("1"); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("2"); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'4'"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'3'"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20201111122735_Added_View")] + partial class Added_View + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConvertibleClass") + .HasColumnType("int"); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("PropertyWithBackingField") + .HasColumnType("int"); + + b.Property("_privateField") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:IdentityIncrement", 1) + .HasAnnotation("SqlServer:IdentitySeed", 1) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(2); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("4"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("3"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDotnetDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithRowVersion"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ShadowIntProperty") + .HasColumnType("int"); + + b.Property("ShadowStringProperty") + .HasColumnType("nvarchar(50)") + .HasMaxLength(50); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("newid()"); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("1"); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("2"); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'4'"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'3'"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111122735_Added_View.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111122735_Added_View.cs index 6b9257f3..32e103c1 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111122735_Added_View.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20201111122735_Added_View.cs @@ -1,38 +1,38 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Thinktecture.EntityFrameworkCore; - -namespace Thinktecture.Migrations -{ - public partial class Added_View : Migration, IDbDefaultSchema - { - public string? Schema { get; } - - public Added_View(IDbDefaultSchema? schema) - { - Schema = schema?.Schema; - } - - protected override void Up(MigrationBuilder migrationBuilder) - { - var schema = GetEscapedSchema(); - - migrationBuilder.Sql($@" -CREATE VIEW {schema}[TestView] -AS -SELECT Id, Name -FROM {schema}[TestEntities]"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - var schema = GetEscapedSchema(); - - migrationBuilder.Sql($"DROP VIEW {schema}[TestView]"); - } - - private string? GetEscapedSchema() - { - return String.IsNullOrWhiteSpace(Schema) ? null : $"[{Schema}]."; - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; +using Thinktecture.EntityFrameworkCore; + +namespace Thinktecture.Migrations +{ + public partial class Added_View : Migration, IDbDefaultSchema + { + public string? Schema { get; } + + public Added_View(IDbDefaultSchema? schema) + { + Schema = schema?.Schema; + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var schema = GetEscapedSchema(); + + migrationBuilder.Sql($@" +CREATE VIEW {schema}[TestView] +AS +SELECT Id, Name +FROM {schema}[TestEntities]"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + var schema = GetEscapedSchema(); + + migrationBuilder.Sql($"DROP VIEW {schema}[TestView]"); + } + + private string? GetEscapedSchema() + { + return String.IsNullOrWhiteSpace(Schema) ? null : $"[{Schema}]."; + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20210306145325_Add_tables_with_owned_entities.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20210306145325_Add_tables_with_owned_entities.Designer.cs index 9a1385f9..074366fe 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20210306145325_Add_tables_with_owned_entities.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20210306145325_Add_tables_with_owned_entities.Designer.cs @@ -1,1038 +1,1038 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20210306145325_Add_tables_with_owned_entities")] - partial class Add_tables_with_owned_entities - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("ProductVersion", "5.0.3") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaColumn", b => - { - b.Property("CHARACTER_MAXIMUM_LENGTH") - .HasColumnType("int"); - - b.Property("CHARACTER_OCTET_LENGTH") - .HasColumnType("int"); - - b.Property("CHARACTER_SET_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CHARACTER_SET_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CHARACTER_SET_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("COLLATION_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("COLLATION_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("COLLATION_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("COLUMN_DEFAULT") - .HasColumnType("nvarchar(max)"); - - b.Property("COLUMN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("DATA_TYPE") - .HasColumnType("nvarchar(max)"); - - b.Property("DATETIME_PRECISION") - .HasColumnType("smallint"); - - b.Property("DOMAIN_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("DOMAIN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("DOMAIN_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("IS_NULLABLE") - .HasColumnType("nvarchar(max)"); - - b.Property("NUMERIC_PRECISION") - .HasColumnType("tinyint"); - - b.Property("NUMERIC_PRECISION_RADIX") - .HasColumnType("smallint"); - - b.Property("NUMERIC_SCALE") - .HasColumnType("int"); - - b.Property("ORDINAL_POSITION") - .HasColumnType("int"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToView("<>"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaConstraintColumn", b => - { - b.Property("COLUMN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToView("<>"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaKeyColumn", b => - { - b.Property("COLUMN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("ORDINAL_POSITION") - .HasColumnType("int"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToView("<>"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaTableConstraint", b => - { - b.Property("CONSTRAINT_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_TYPE") - .HasColumnType("nvarchar(max)"); - - b.Property("INITIALLY_DEFERRED") - .HasColumnType("nvarchar(max)"); - - b.Property("IS_DEFERRABLE") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToView("<>"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConvertibleClass") - .HasColumnType("int"); - - b.Property("Count") - .HasColumnType("int"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("ParentId") - .HasColumnType("uniqueidentifier"); - - b.Property("PropertyWithBackingField") - .HasColumnType("int"); - - b.Property("_privateField") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("ParentId"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:IdentityIncrement", 1) - .HasAnnotation("SqlServer:IdentitySeed", 1) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(2); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValue("4"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValue("3"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDotnetDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("rowversion"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithRowVersion"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("ShadowIntProperty") - .HasColumnType("int"); - - b.Property("ShadowStringProperty") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("newid()"); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("1"); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("2"); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'4'"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'3'"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestViewEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToView("TestView"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_InlineId"); - - b1.ToTable("TestEntities_Own_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_InlineId"); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_Inline_InlineId"); - - b1.ToTable("TestEntities_Own_Inline_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); - - b2.ToTable("TestEntities_Own_Inline_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_Inline_SeparateManyId"); - - b1.ToTable("TestEntities_Own_Inline_SeparateMany"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", "Id"); - - b2.ToTable("InlineEntities_SeparateMany"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_Inline_SeparateOneId"); - - b1.ToTable("TestEntities_Own_Inline_SeparateOne"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); - - b2.ToTable("InlineEntities_SeparateOne"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateManyId", "Id"); - - b1.ToTable("SeparateEntitiesMany"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateManyId"); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateMany_InlineId", "Id"); - - b1.ToTable("SeparateEntitiesMany_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId") - .HasColumnType("uniqueidentifier"); - - b2.Property("OwnedEntity_Owns_InlineId") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); - - b2.ToTable("SeparateEntitiesMany_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateMany_SeparateManyId", "Id"); - - b1.ToTable("SeparateEntitiesMany_SeparateEntitiesMany"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b2.Property("OwnedEntity_Owns_SeparateManyId") - .HasColumnType("int"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId", "Id"); - - b2.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateMany_SeparateOneId", "Id"); - - b1.ToTable("SeparateEntitiesMany_SeparateEntitiesOne"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b2.Property("OwnedEntity_Owns_SeparateOneId") - .HasColumnType("int"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); - - b2.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOneId"); - - b1.ToTable("SeparateEntitiesOne"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOneId"); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOne_InlineId"); - - b1.ToTable("SeparateEntitiesOne_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); - - b2.ToTable("SeparateEntitiesOne_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOne_SeparateManyId"); - - b1.ToTable("SeparateEntitiesOne_SeparateMany"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", "Id"); - - b2.ToTable("SeparateEntitiesOne_SeparateMany_Inner"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOne_SeparateOneId"); - - b1.ToTable("SeparateEntitiesOne_SeparateOne"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); - - b2.ToTable("SeparateEntitiesOne_SeparateOne_Inner"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Navigation("Children"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20210306145325_Add_tables_with_owned_entities")] + partial class Add_tables_with_owned_entities + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.3") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaColumn", b => + { + b.Property("CHARACTER_MAXIMUM_LENGTH") + .HasColumnType("int"); + + b.Property("CHARACTER_OCTET_LENGTH") + .HasColumnType("int"); + + b.Property("CHARACTER_SET_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CHARACTER_SET_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CHARACTER_SET_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("COLLATION_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("COLLATION_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("COLLATION_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("COLUMN_DEFAULT") + .HasColumnType("nvarchar(max)"); + + b.Property("COLUMN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("DATA_TYPE") + .HasColumnType("nvarchar(max)"); + + b.Property("DATETIME_PRECISION") + .HasColumnType("smallint"); + + b.Property("DOMAIN_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("DOMAIN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("DOMAIN_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("IS_NULLABLE") + .HasColumnType("nvarchar(max)"); + + b.Property("NUMERIC_PRECISION") + .HasColumnType("tinyint"); + + b.Property("NUMERIC_PRECISION_RADIX") + .HasColumnType("smallint"); + + b.Property("NUMERIC_SCALE") + .HasColumnType("int"); + + b.Property("ORDINAL_POSITION") + .HasColumnType("int"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToView("<>"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaConstraintColumn", b => + { + b.Property("COLUMN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToView("<>"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaKeyColumn", b => + { + b.Property("COLUMN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("ORDINAL_POSITION") + .HasColumnType("int"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToView("<>"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaTableConstraint", b => + { + b.Property("CONSTRAINT_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_TYPE") + .HasColumnType("nvarchar(max)"); + + b.Property("INITIALLY_DEFERRED") + .HasColumnType("nvarchar(max)"); + + b.Property("IS_DEFERRABLE") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToView("<>"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConvertibleClass") + .HasColumnType("int"); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("PropertyWithBackingField") + .HasColumnType("int"); + + b.Property("_privateField") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:IdentityIncrement", 1) + .HasAnnotation("SqlServer:IdentitySeed", 1) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(2); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("4"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("3"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDotnetDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithRowVersion"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ShadowIntProperty") + .HasColumnType("int"); + + b.Property("ShadowStringProperty") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("newid()"); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("1"); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("2"); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'4'"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'3'"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestViewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToView("TestView"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_InlineId"); + + b1.ToTable("TestEntities_Own_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_InlineId"); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_Inline_InlineId"); + + b1.ToTable("TestEntities_Own_Inline_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); + + b2.ToTable("TestEntities_Own_Inline_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_Inline_SeparateManyId"); + + b1.ToTable("TestEntities_Own_Inline_SeparateMany"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", "Id"); + + b2.ToTable("InlineEntities_SeparateMany"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_Inline_SeparateOneId"); + + b1.ToTable("TestEntities_Own_Inline_SeparateOne"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); + + b2.ToTable("InlineEntities_SeparateOne"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateManyId", "Id"); + + b1.ToTable("SeparateEntitiesMany"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateManyId"); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateMany_InlineId", "Id"); + + b1.ToTable("SeparateEntitiesMany_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OwnedEntity_Owns_InlineId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); + + b2.ToTable("SeparateEntitiesMany_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateMany_SeparateManyId", "Id"); + + b1.ToTable("SeparateEntitiesMany_SeparateEntitiesMany"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OwnedEntity_Owns_SeparateManyId") + .HasColumnType("int"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId", "Id"); + + b2.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateMany_SeparateOneId", "Id"); + + b1.ToTable("SeparateEntitiesMany_SeparateEntitiesOne"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OwnedEntity_Owns_SeparateOneId") + .HasColumnType("int"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); + + b2.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOneId"); + + b1.ToTable("SeparateEntitiesOne"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOneId"); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOne_InlineId"); + + b1.ToTable("SeparateEntitiesOne_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); + + b2.ToTable("SeparateEntitiesOne_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOne_SeparateManyId"); + + b1.ToTable("SeparateEntitiesOne_SeparateMany"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", "Id"); + + b2.ToTable("SeparateEntitiesOne_SeparateMany_Inner"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOne_SeparateOneId"); + + b1.ToTable("SeparateEntitiesOne_SeparateOne"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); + + b2.ToTable("SeparateEntitiesOne_SeparateOne_Inner"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Navigation("Children"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20210306145325_Add_tables_with_owned_entities.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20210306145325_Add_tables_with_owned_entities.cs index 33fdc857..5a613f02 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20210306145325_Add_tables_with_owned_entities.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20210306145325_Add_tables_with_owned_entities.cs @@ -1,406 +1,406 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - public partial class Add_tables_with_owned_entities : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable("TestEntities_Own_Inline", - table => new - { - Id = table.Column("uniqueidentifier", nullable: false), - InlineEntity_StringColumn = table.Column("nvarchar(max)", nullable: true), - InlineEntity_IntColumn = table.Column("int", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_Inline_Inline", - table => new - { - Id = table.Column("uniqueidentifier", nullable: false), - InlineEntity_StringColumn = table.Column("nvarchar(max)", nullable: true), - InlineEntity_IntColumn = table.Column("int", nullable: false), - InlineEntity_InlineEntity_StringColumn = table.Column("nvarchar(max)", nullable: true), - InlineEntity_InlineEntity_IntColumn = table.Column("int", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline_Inline", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_Inline_SeparateMany", - table => new - { - Id = table.Column("uniqueidentifier", nullable: false), - InlineEntity_StringColumn = table.Column("nvarchar(max)", nullable: true), - InlineEntity_IntColumn = table.Column("int", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline_SeparateMany", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_Inline_SeparateOne", - table => new - { - Id = table.Column("uniqueidentifier", nullable: false), - InlineEntity_StringColumn = table.Column("nvarchar(max)", nullable: true), - InlineEntity_IntColumn = table.Column("int", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline_SeparateOne", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateMany", - table => new - { - Id = table.Column("uniqueidentifier", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateMany_Inline", - table => new - { - Id = table.Column("uniqueidentifier", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany_Inline", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateMany_SeparateMany", - table => new - { - Id = table.Column("uniqueidentifier", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany_SeparateMany", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateMany_SeparateOne", - table => new - { - Id = table.Column("uniqueidentifier", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany_SeparateOne", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateOne", - table => new - { - Id = table.Column("uniqueidentifier", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateOne_Inline", - table => new - { - Id = table.Column("uniqueidentifier", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne_Inline", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateOne_SeparateMany", - table => new - { - Id = table.Column("uniqueidentifier", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne_SeparateMany", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateOne_SeparateOne", - table => new - { - Id = table.Column("uniqueidentifier", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne_SeparateOne", x => x.Id)); - - migrationBuilder.CreateTable("InlineEntities_SeparateMany", - table => new - { - OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId = table.Column("uniqueidentifier", nullable: false), - Id = table.Column("int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - StringColumn = table.Column("nvarchar(max)", nullable: true), - IntColumn = table.Column("int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_InlineEntities_SeparateMany", x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId, x.Id }); - table.ForeignKey( - "FK_InlineEntities_SeparateMany_TestEntities_Own_Inline_SeparateMany_OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_Separat~", - x => x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId, - "TestEntities_Own_Inline_SeparateMany", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("InlineEntities_SeparateOne", - table => new - { - OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId = table.Column("uniqueidentifier", nullable: false), - StringColumn = table.Column("nvarchar(max)", nullable: true), - IntColumn = table.Column("int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_InlineEntities_SeparateOne", x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId); - table.ForeignKey( - "FK_InlineEntities_SeparateOne_TestEntities_Own_Inline_SeparateOne_OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOn~", - x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId, - "TestEntities_Own_Inline_SeparateOne", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesMany", - table => new - { - TestEntity_Owns_SeparateManyId = table.Column("uniqueidentifier", nullable: false), - Id = table.Column("int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - StringColumn = table.Column("nvarchar(max)", nullable: true), - IntColumn = table.Column("int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesMany", x => new { x.TestEntity_Owns_SeparateManyId, x.Id }); - table.ForeignKey( - "FK_SeparateEntitiesMany_TestEntities_Own_SeparateMany_TestEntity_Owns_SeparateManyId", - x => x.TestEntity_Owns_SeparateManyId, - "TestEntities_Own_SeparateMany", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesMany_Inline", - table => new - { - TestEntity_Owns_SeparateMany_InlineId = table.Column("uniqueidentifier", nullable: false), - Id = table.Column("int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - StringColumn = table.Column("nvarchar(max)", nullable: true), - IntColumn = table.Column("int", nullable: false), - InlineEntity_StringColumn = table.Column("nvarchar(max)", nullable: true), - InlineEntity_IntColumn = table.Column("int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesMany_Inline", x => new { x.TestEntity_Owns_SeparateMany_InlineId, x.Id }); - table.ForeignKey( - "FK_SeparateEntitiesMany_Inline_TestEntities_Own_SeparateMany_Inline_TestEntity_Owns_SeparateMany_InlineId", - x => x.TestEntity_Owns_SeparateMany_InlineId, - "TestEntities_Own_SeparateMany_Inline", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesMany", - table => new - { - TestEntity_Owns_SeparateMany_SeparateManyId = table.Column("uniqueidentifier", nullable: false), - Id = table.Column("int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - StringColumn = table.Column("nvarchar(max)", nullable: true), - IntColumn = table.Column("int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesMany", x => new { x.TestEntity_Owns_SeparateMany_SeparateManyId, x.Id }); - table.ForeignKey( - "FK_SeparateEntitiesMany_SeparateEntitiesMany_TestEntities_Own_SeparateMany_SeparateMany_TestEntity_Owns_SeparateMany_SeparateMa~", - x => x.TestEntity_Owns_SeparateMany_SeparateManyId, - "TestEntities_Own_SeparateMany_SeparateMany", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesOne", - table => new - { - TestEntity_Owns_SeparateMany_SeparateOneId = table.Column("uniqueidentifier", nullable: false), - Id = table.Column("int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - StringColumn = table.Column("nvarchar(max)", nullable: true), - IntColumn = table.Column("int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesOne", x => new { x.TestEntity_Owns_SeparateMany_SeparateOneId, x.Id }); - table.ForeignKey( - "FK_SeparateEntitiesMany_SeparateEntitiesOne_TestEntities_Own_SeparateMany_SeparateOne_TestEntity_Owns_SeparateMany_SeparateOneId", - x => x.TestEntity_Owns_SeparateMany_SeparateOneId, - "TestEntities_Own_SeparateMany_SeparateOne", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesOne", - table => new - { - TestEntity_Owns_SeparateOneId = table.Column("uniqueidentifier", nullable: false), - StringColumn = table.Column("nvarchar(max)", nullable: true), - IntColumn = table.Column("int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesOne", x => x.TestEntity_Owns_SeparateOneId); - table.ForeignKey( - "FK_SeparateEntitiesOne_TestEntities_Own_SeparateOne_TestEntity_Owns_SeparateOneId", - x => x.TestEntity_Owns_SeparateOneId, - "TestEntities_Own_SeparateOne", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesOne_Inline", - table => new - { - TestEntity_Owns_SeparateOne_InlineId = table.Column("uniqueidentifier", nullable: false), - StringColumn = table.Column("nvarchar(max)", nullable: true), - IntColumn = table.Column("int", nullable: false), - InlineEntity_StringColumn = table.Column("nvarchar(max)", nullable: true), - InlineEntity_IntColumn = table.Column("int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesOne_Inline", x => x.TestEntity_Owns_SeparateOne_InlineId); - table.ForeignKey( - "FK_SeparateEntitiesOne_Inline_TestEntities_Own_SeparateOne_Inline_TestEntity_Owns_SeparateOne_InlineId", - x => x.TestEntity_Owns_SeparateOne_InlineId, - "TestEntities_Own_SeparateOne_Inline", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateMany", - table => new - { - TestEntity_Owns_SeparateOne_SeparateManyId = table.Column("uniqueidentifier", nullable: false), - StringColumn = table.Column("nvarchar(max)", nullable: true), - IntColumn = table.Column("int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesOne_SeparateMany", x => x.TestEntity_Owns_SeparateOne_SeparateManyId); - table.ForeignKey( - "FK_SeparateEntitiesOne_SeparateMany_TestEntities_Own_SeparateOne_SeparateMany_TestEntity_Owns_SeparateOne_SeparateManyId", - x => x.TestEntity_Owns_SeparateOne_SeparateManyId, - "TestEntities_Own_SeparateOne_SeparateMany", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateOne", - table => new - { - TestEntity_Owns_SeparateOne_SeparateOneId = table.Column("uniqueidentifier", nullable: false), - StringColumn = table.Column("nvarchar(max)", nullable: true), - IntColumn = table.Column("int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesOne_SeparateOne", x => x.TestEntity_Owns_SeparateOne_SeparateOneId); - table.ForeignKey( - "FK_SeparateEntitiesOne_SeparateOne_TestEntities_Own_SeparateOne_SeparateOne_TestEntity_Owns_SeparateOne_SeparateOneId", - x => x.TestEntity_Owns_SeparateOne_SeparateOneId, - "TestEntities_Own_SeparateOne_SeparateOne", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner", - table => new - { - OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId = table.Column("uniqueidentifier", nullable: false), - OwnedEntity_Owns_SeparateManyId = table.Column("int", nullable: false), - Id = table.Column("int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - StringColumn = table.Column("nvarchar(max)", nullable: true), - IntColumn = table.Column("int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesMany_Inner", x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId, x.OwnedEntity_Owns_SeparateManyId, x.Id }); - table.ForeignKey( - "FK_SeparateEntitiesMany_SeparateEntitiesMany_Inner_SeparateEntitiesMany_SeparateEntitiesMany_OwnedEntity_Owns_SeparateManyTestE~", - x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId, x.OwnedEntity_Owns_SeparateManyId }, - "SeparateEntitiesMany_SeparateEntitiesMany", - new[] { "TestEntity_Owns_SeparateMany_SeparateManyId", "Id" }, - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner", - table => new - { - OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId = table.Column("uniqueidentifier", nullable: false), - OwnedEntity_Owns_SeparateOneId = table.Column("int", nullable: false), - StringColumn = table.Column("nvarchar(max)", nullable: true), - IntColumn = table.Column("int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesOne_Inner", x => new { x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId, x.OwnedEntity_Owns_SeparateOneId }); - table.ForeignKey( - "FK_SeparateEntitiesMany_SeparateEntitiesOne_Inner_SeparateEntitiesMany_SeparateEntitiesOne_OwnedEntity_Owns_SeparateOneTestEnti~", - x => new { x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId, x.OwnedEntity_Owns_SeparateOneId }, - "SeparateEntitiesMany_SeparateEntitiesOne", - new[] { "TestEntity_Owns_SeparateMany_SeparateOneId", "Id" }, - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateMany_Inner", - table => new - { - OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId = table.Column("uniqueidentifier", nullable: false), - Id = table.Column("int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - StringColumn = table.Column("nvarchar(max)", nullable: true), - IntColumn = table.Column("int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesOne_SeparateMany_Inner", x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId, x.Id }); - table.ForeignKey( - "FK_SeparateEntitiesOne_SeparateMany_Inner_SeparateEntitiesOne_SeparateMany_OwnedEntity_Owns_SeparateManyTestEntity_Owns_Separat~", - x => x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId, - "SeparateEntitiesOne_SeparateMany", - "TestEntity_Owns_SeparateOne_SeparateManyId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateOne_Inner", - table => new - { - OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId = table.Column("uniqueidentifier", nullable: false), - StringColumn = table.Column("nvarchar(max)", nullable: true), - IntColumn = table.Column("int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesOne_SeparateOne_Inner", x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId); - table.ForeignKey( - "FK_SeparateEntitiesOne_SeparateOne_Inner_SeparateEntitiesOne_SeparateOne_OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOn~", - x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId, - "SeparateEntitiesOne_SeparateOne", - "TestEntity_Owns_SeparateOne_SeparateOneId", - onDelete: ReferentialAction.Cascade); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable("InlineEntities_SeparateMany"); - migrationBuilder.DropTable("InlineEntities_SeparateOne"); - migrationBuilder.DropTable("SeparateEntitiesMany"); - migrationBuilder.DropTable("SeparateEntitiesMany_Inline"); - migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner"); - migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner"); - migrationBuilder.DropTable("SeparateEntitiesOne"); - migrationBuilder.DropTable("SeparateEntitiesOne_Inline"); - migrationBuilder.DropTable("SeparateEntitiesOne_SeparateMany_Inner"); - migrationBuilder.DropTable("SeparateEntitiesOne_SeparateOne_Inner"); - migrationBuilder.DropTable("TestEntities_Own_Inline"); - migrationBuilder.DropTable("TestEntities_Own_Inline_Inline"); - migrationBuilder.DropTable("TestEntities_Own_Inline_SeparateMany"); - migrationBuilder.DropTable("TestEntities_Own_Inline_SeparateOne"); - migrationBuilder.DropTable("TestEntities_Own_SeparateMany"); - migrationBuilder.DropTable("TestEntities_Own_SeparateMany_Inline"); - migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesMany"); - migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesOne"); - migrationBuilder.DropTable("TestEntities_Own_SeparateOne"); - migrationBuilder.DropTable("TestEntities_Own_SeparateOne_Inline"); - migrationBuilder.DropTable("SeparateEntitiesOne_SeparateMany"); - migrationBuilder.DropTable("SeparateEntitiesOne_SeparateOne"); - migrationBuilder.DropTable("TestEntities_Own_SeparateMany_SeparateMany"); - migrationBuilder.DropTable("TestEntities_Own_SeparateMany_SeparateOne"); - migrationBuilder.DropTable("TestEntities_Own_SeparateOne_SeparateMany"); - migrationBuilder.DropTable("TestEntities_Own_SeparateOne_SeparateOne"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + public partial class Add_tables_with_owned_entities : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable("TestEntities_Own_Inline", + table => new + { + Id = table.Column("uniqueidentifier", nullable: false), + InlineEntity_StringColumn = table.Column("nvarchar(max)", nullable: true), + InlineEntity_IntColumn = table.Column("int", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_Inline_Inline", + table => new + { + Id = table.Column("uniqueidentifier", nullable: false), + InlineEntity_StringColumn = table.Column("nvarchar(max)", nullable: true), + InlineEntity_IntColumn = table.Column("int", nullable: false), + InlineEntity_InlineEntity_StringColumn = table.Column("nvarchar(max)", nullable: true), + InlineEntity_InlineEntity_IntColumn = table.Column("int", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline_Inline", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_Inline_SeparateMany", + table => new + { + Id = table.Column("uniqueidentifier", nullable: false), + InlineEntity_StringColumn = table.Column("nvarchar(max)", nullable: true), + InlineEntity_IntColumn = table.Column("int", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline_SeparateMany", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_Inline_SeparateOne", + table => new + { + Id = table.Column("uniqueidentifier", nullable: false), + InlineEntity_StringColumn = table.Column("nvarchar(max)", nullable: true), + InlineEntity_IntColumn = table.Column("int", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline_SeparateOne", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateMany", + table => new + { + Id = table.Column("uniqueidentifier", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateMany_Inline", + table => new + { + Id = table.Column("uniqueidentifier", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany_Inline", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateMany_SeparateMany", + table => new + { + Id = table.Column("uniqueidentifier", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany_SeparateMany", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateMany_SeparateOne", + table => new + { + Id = table.Column("uniqueidentifier", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany_SeparateOne", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateOne", + table => new + { + Id = table.Column("uniqueidentifier", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateOne_Inline", + table => new + { + Id = table.Column("uniqueidentifier", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne_Inline", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateOne_SeparateMany", + table => new + { + Id = table.Column("uniqueidentifier", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne_SeparateMany", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateOne_SeparateOne", + table => new + { + Id = table.Column("uniqueidentifier", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne_SeparateOne", x => x.Id)); + + migrationBuilder.CreateTable("InlineEntities_SeparateMany", + table => new + { + OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId = table.Column("uniqueidentifier", nullable: false), + Id = table.Column("int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + StringColumn = table.Column("nvarchar(max)", nullable: true), + IntColumn = table.Column("int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_InlineEntities_SeparateMany", x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId, x.Id }); + table.ForeignKey( + "FK_InlineEntities_SeparateMany_TestEntities_Own_Inline_SeparateMany_OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_Separat~", + x => x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId, + "TestEntities_Own_Inline_SeparateMany", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("InlineEntities_SeparateOne", + table => new + { + OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId = table.Column("uniqueidentifier", nullable: false), + StringColumn = table.Column("nvarchar(max)", nullable: true), + IntColumn = table.Column("int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_InlineEntities_SeparateOne", x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId); + table.ForeignKey( + "FK_InlineEntities_SeparateOne_TestEntities_Own_Inline_SeparateOne_OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOn~", + x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId, + "TestEntities_Own_Inline_SeparateOne", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesMany", + table => new + { + TestEntity_Owns_SeparateManyId = table.Column("uniqueidentifier", nullable: false), + Id = table.Column("int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + StringColumn = table.Column("nvarchar(max)", nullable: true), + IntColumn = table.Column("int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesMany", x => new { x.TestEntity_Owns_SeparateManyId, x.Id }); + table.ForeignKey( + "FK_SeparateEntitiesMany_TestEntities_Own_SeparateMany_TestEntity_Owns_SeparateManyId", + x => x.TestEntity_Owns_SeparateManyId, + "TestEntities_Own_SeparateMany", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesMany_Inline", + table => new + { + TestEntity_Owns_SeparateMany_InlineId = table.Column("uniqueidentifier", nullable: false), + Id = table.Column("int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + StringColumn = table.Column("nvarchar(max)", nullable: true), + IntColumn = table.Column("int", nullable: false), + InlineEntity_StringColumn = table.Column("nvarchar(max)", nullable: true), + InlineEntity_IntColumn = table.Column("int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesMany_Inline", x => new { x.TestEntity_Owns_SeparateMany_InlineId, x.Id }); + table.ForeignKey( + "FK_SeparateEntitiesMany_Inline_TestEntities_Own_SeparateMany_Inline_TestEntity_Owns_SeparateMany_InlineId", + x => x.TestEntity_Owns_SeparateMany_InlineId, + "TestEntities_Own_SeparateMany_Inline", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesMany", + table => new + { + TestEntity_Owns_SeparateMany_SeparateManyId = table.Column("uniqueidentifier", nullable: false), + Id = table.Column("int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + StringColumn = table.Column("nvarchar(max)", nullable: true), + IntColumn = table.Column("int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesMany", x => new { x.TestEntity_Owns_SeparateMany_SeparateManyId, x.Id }); + table.ForeignKey( + "FK_SeparateEntitiesMany_SeparateEntitiesMany_TestEntities_Own_SeparateMany_SeparateMany_TestEntity_Owns_SeparateMany_SeparateMa~", + x => x.TestEntity_Owns_SeparateMany_SeparateManyId, + "TestEntities_Own_SeparateMany_SeparateMany", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesOne", + table => new + { + TestEntity_Owns_SeparateMany_SeparateOneId = table.Column("uniqueidentifier", nullable: false), + Id = table.Column("int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + StringColumn = table.Column("nvarchar(max)", nullable: true), + IntColumn = table.Column("int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesOne", x => new { x.TestEntity_Owns_SeparateMany_SeparateOneId, x.Id }); + table.ForeignKey( + "FK_SeparateEntitiesMany_SeparateEntitiesOne_TestEntities_Own_SeparateMany_SeparateOne_TestEntity_Owns_SeparateMany_SeparateOneId", + x => x.TestEntity_Owns_SeparateMany_SeparateOneId, + "TestEntities_Own_SeparateMany_SeparateOne", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesOne", + table => new + { + TestEntity_Owns_SeparateOneId = table.Column("uniqueidentifier", nullable: false), + StringColumn = table.Column("nvarchar(max)", nullable: true), + IntColumn = table.Column("int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesOne", x => x.TestEntity_Owns_SeparateOneId); + table.ForeignKey( + "FK_SeparateEntitiesOne_TestEntities_Own_SeparateOne_TestEntity_Owns_SeparateOneId", + x => x.TestEntity_Owns_SeparateOneId, + "TestEntities_Own_SeparateOne", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesOne_Inline", + table => new + { + TestEntity_Owns_SeparateOne_InlineId = table.Column("uniqueidentifier", nullable: false), + StringColumn = table.Column("nvarchar(max)", nullable: true), + IntColumn = table.Column("int", nullable: false), + InlineEntity_StringColumn = table.Column("nvarchar(max)", nullable: true), + InlineEntity_IntColumn = table.Column("int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesOne_Inline", x => x.TestEntity_Owns_SeparateOne_InlineId); + table.ForeignKey( + "FK_SeparateEntitiesOne_Inline_TestEntities_Own_SeparateOne_Inline_TestEntity_Owns_SeparateOne_InlineId", + x => x.TestEntity_Owns_SeparateOne_InlineId, + "TestEntities_Own_SeparateOne_Inline", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateMany", + table => new + { + TestEntity_Owns_SeparateOne_SeparateManyId = table.Column("uniqueidentifier", nullable: false), + StringColumn = table.Column("nvarchar(max)", nullable: true), + IntColumn = table.Column("int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesOne_SeparateMany", x => x.TestEntity_Owns_SeparateOne_SeparateManyId); + table.ForeignKey( + "FK_SeparateEntitiesOne_SeparateMany_TestEntities_Own_SeparateOne_SeparateMany_TestEntity_Owns_SeparateOne_SeparateManyId", + x => x.TestEntity_Owns_SeparateOne_SeparateManyId, + "TestEntities_Own_SeparateOne_SeparateMany", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateOne", + table => new + { + TestEntity_Owns_SeparateOne_SeparateOneId = table.Column("uniqueidentifier", nullable: false), + StringColumn = table.Column("nvarchar(max)", nullable: true), + IntColumn = table.Column("int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesOne_SeparateOne", x => x.TestEntity_Owns_SeparateOne_SeparateOneId); + table.ForeignKey( + "FK_SeparateEntitiesOne_SeparateOne_TestEntities_Own_SeparateOne_SeparateOne_TestEntity_Owns_SeparateOne_SeparateOneId", + x => x.TestEntity_Owns_SeparateOne_SeparateOneId, + "TestEntities_Own_SeparateOne_SeparateOne", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner", + table => new + { + OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId = table.Column("uniqueidentifier", nullable: false), + OwnedEntity_Owns_SeparateManyId = table.Column("int", nullable: false), + Id = table.Column("int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + StringColumn = table.Column("nvarchar(max)", nullable: true), + IntColumn = table.Column("int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesMany_Inner", x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId, x.OwnedEntity_Owns_SeparateManyId, x.Id }); + table.ForeignKey( + "FK_SeparateEntitiesMany_SeparateEntitiesMany_Inner_SeparateEntitiesMany_SeparateEntitiesMany_OwnedEntity_Owns_SeparateManyTestE~", + x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId, x.OwnedEntity_Owns_SeparateManyId }, + "SeparateEntitiesMany_SeparateEntitiesMany", + new[] { "TestEntity_Owns_SeparateMany_SeparateManyId", "Id" }, + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner", + table => new + { + OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId = table.Column("uniqueidentifier", nullable: false), + OwnedEntity_Owns_SeparateOneId = table.Column("int", nullable: false), + StringColumn = table.Column("nvarchar(max)", nullable: true), + IntColumn = table.Column("int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesOne_Inner", x => new { x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId, x.OwnedEntity_Owns_SeparateOneId }); + table.ForeignKey( + "FK_SeparateEntitiesMany_SeparateEntitiesOne_Inner_SeparateEntitiesMany_SeparateEntitiesOne_OwnedEntity_Owns_SeparateOneTestEnti~", + x => new { x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId, x.OwnedEntity_Owns_SeparateOneId }, + "SeparateEntitiesMany_SeparateEntitiesOne", + new[] { "TestEntity_Owns_SeparateMany_SeparateOneId", "Id" }, + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateMany_Inner", + table => new + { + OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId = table.Column("uniqueidentifier", nullable: false), + Id = table.Column("int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + StringColumn = table.Column("nvarchar(max)", nullable: true), + IntColumn = table.Column("int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesOne_SeparateMany_Inner", x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId, x.Id }); + table.ForeignKey( + "FK_SeparateEntitiesOne_SeparateMany_Inner_SeparateEntitiesOne_SeparateMany_OwnedEntity_Owns_SeparateManyTestEntity_Owns_Separat~", + x => x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId, + "SeparateEntitiesOne_SeparateMany", + "TestEntity_Owns_SeparateOne_SeparateManyId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateOne_Inner", + table => new + { + OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId = table.Column("uniqueidentifier", nullable: false), + StringColumn = table.Column("nvarchar(max)", nullable: true), + IntColumn = table.Column("int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesOne_SeparateOne_Inner", x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId); + table.ForeignKey( + "FK_SeparateEntitiesOne_SeparateOne_Inner_SeparateEntitiesOne_SeparateOne_OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOn~", + x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId, + "SeparateEntitiesOne_SeparateOne", + "TestEntity_Owns_SeparateOne_SeparateOneId", + onDelete: ReferentialAction.Cascade); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("InlineEntities_SeparateMany"); + migrationBuilder.DropTable("InlineEntities_SeparateOne"); + migrationBuilder.DropTable("SeparateEntitiesMany"); + migrationBuilder.DropTable("SeparateEntitiesMany_Inline"); + migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner"); + migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner"); + migrationBuilder.DropTable("SeparateEntitiesOne"); + migrationBuilder.DropTable("SeparateEntitiesOne_Inline"); + migrationBuilder.DropTable("SeparateEntitiesOne_SeparateMany_Inner"); + migrationBuilder.DropTable("SeparateEntitiesOne_SeparateOne_Inner"); + migrationBuilder.DropTable("TestEntities_Own_Inline"); + migrationBuilder.DropTable("TestEntities_Own_Inline_Inline"); + migrationBuilder.DropTable("TestEntities_Own_Inline_SeparateMany"); + migrationBuilder.DropTable("TestEntities_Own_Inline_SeparateOne"); + migrationBuilder.DropTable("TestEntities_Own_SeparateMany"); + migrationBuilder.DropTable("TestEntities_Own_SeparateMany_Inline"); + migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesMany"); + migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesOne"); + migrationBuilder.DropTable("TestEntities_Own_SeparateOne"); + migrationBuilder.DropTable("TestEntities_Own_SeparateOne_Inline"); + migrationBuilder.DropTable("SeparateEntitiesOne_SeparateMany"); + migrationBuilder.DropTable("SeparateEntitiesOne_SeparateOne"); + migrationBuilder.DropTable("TestEntities_Own_SeparateMany_SeparateMany"); + migrationBuilder.DropTable("TestEntities_Own_SeparateMany_SeparateOne"); + migrationBuilder.DropTable("TestEntities_Own_SeparateOne_SeparateMany"); + migrationBuilder.DropTable("TestEntities_Own_SeparateOne_SeparateOne"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20221216112411_Add_TestEntityWithBaseClass.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20221216112411_Add_TestEntityWithBaseClass.Designer.cs index 009e1033..840f4d51 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20221216112411_Add_TestEntityWithBaseClass.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20221216112411_Add_TestEntityWithBaseClass.Designer.cs @@ -1,1144 +1,1144 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -#nullable disable - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20221216112411_Add_TestEntityWithBaseClass")] - partial class Add_TestEntityWithBaseClass - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "6.0.8") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); - - modelBuilder.Entity("Thinktecture:ComplexCollectionParameter:Thinktecture.TestDatabaseContext.MyParameter", b => - { - b.Property("Column1") - .HasColumnType("uniqueidentifier") - .HasColumnName("Id"); - - b.Property("Column2") - .HasColumnType("int"); - - b.ToTable("MyParameter", null, t => t.ExcludeFromMigrations()); - }); - - modelBuilder.Entity("Thinktecture:ScalarCollectionParameter:Thinktecture.TestDatabaseContext.ConvertibleClass", b => - { - b.Property("Value") - .HasColumnType("int"); - - b.ToTable("ScalarCollectionParameter", null, t => t.ExcludeFromMigrations()); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaColumn", b => - { - b.Property("CHARACTER_MAXIMUM_LENGTH") - .HasColumnType("int"); - - b.Property("CHARACTER_OCTET_LENGTH") - .HasColumnType("int"); - - b.Property("CHARACTER_SET_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CHARACTER_SET_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CHARACTER_SET_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("COLLATION_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("COLLATION_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("COLLATION_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("COLUMN_DEFAULT") - .HasColumnType("nvarchar(max)"); - - b.Property("COLUMN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("DATA_TYPE") - .HasColumnType("nvarchar(max)"); - - b.Property("DATETIME_PRECISION") - .HasColumnType("smallint"); - - b.Property("DOMAIN_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("DOMAIN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("DOMAIN_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("IS_NULLABLE") - .HasColumnType("nvarchar(max)"); - - b.Property("NUMERIC_PRECISION") - .HasColumnType("tinyint"); - - b.Property("NUMERIC_PRECISION_RADIX") - .HasColumnType("smallint"); - - b.Property("NUMERIC_SCALE") - .HasColumnType("int"); - - b.Property("ORDINAL_POSITION") - .HasColumnType("int"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToView("<>"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaConstraintColumn", b => - { - b.Property("COLUMN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToView("<>"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaKeyColumn", b => - { - b.Property("COLUMN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("ORDINAL_POSITION") - .HasColumnType("int"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToView("<>"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaTableConstraint", b => - { - b.Property("CONSTRAINT_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_TYPE") - .HasColumnType("nvarchar(max)"); - - b.Property("INITIALLY_DEFERRED") - .HasColumnType("nvarchar(max)"); - - b.Property("IS_DEFERRABLE") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToView("<>"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.KeylessTestEntity", b => - { - b.Property("IntColumn") - .HasColumnType("int"); - - b.ToTable("KeylessEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConvertibleClass") - .HasColumnType("int"); - - b.Property("Count") - .HasColumnType("int"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("ParentId") - .HasColumnType("uniqueidentifier"); - - b.Property("PropertyWithBackingField") - .HasColumnType("int"); - - b.Property("RequiredName") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("_privateField") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("ParentId"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithBaseClass", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithBaseClass"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithCollation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ColumnWithCollation") - .IsRequired() - .HasColumnType("nvarchar(max)") - .UseCollation("Japanese_CI_AS"); - - b.Property("ColumnWithoutCollation") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TestEntityWithCollation"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(2); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValue("4"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValue("3"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDotnetDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("rowversion"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithRowVersion"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("ShadowIntProperty") - .HasColumnType("int"); - - b.Property("ShadowStringProperty") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("newid()"); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("1"); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("2"); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'4'"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'3'"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestTemporalTableEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("PeriodEnd") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("datetime2") - .HasColumnName("PeriodEnd"); - - b.Property("PeriodStart") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("datetime2") - .HasColumnName("PeriodStart"); - - b.HasKey("Id"); - - b.ToTable("TestTemporalTableEntity", (string)null); - - b.ToTable(tb => tb.IsTemporal(ttb => - { - ttb - .HasPeriodStart("PeriodStart") - .HasColumnName("PeriodStart"); - ttb - .HasPeriodEnd("PeriodEnd") - .HasColumnName("PeriodEnd"); - } - )); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestViewEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToView("TestView"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_InlineId"); - - b1.ToTable("TestEntities_Own_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_InlineId"); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_Inline_InlineId"); - - b1.ToTable("TestEntities_Own_Inline_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); - - b2.ToTable("TestEntities_Own_Inline_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_Inline_SeparateManyId"); - - b1.ToTable("TestEntities_Own_Inline_SeparateMany"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id"), 1L, 1); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", "Id"); - - b2.ToTable("InlineEntities_SeparateMany", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_Inline_SeparateOneId"); - - b1.ToTable("TestEntities_Own_Inline_SeparateOne"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); - - b2.ToTable("InlineEntities_SeparateOne", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateManyId", "Id"); - - b1.ToTable("SeparateEntitiesMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateManyId"); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateMany_InlineId", "Id"); - - b1.ToTable("SeparateEntitiesMany_Inline", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId") - .HasColumnType("uniqueidentifier"); - - b2.Property("OwnedEntity_Owns_InlineId") - .HasColumnType("int"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); - - b2.ToTable("SeparateEntitiesMany_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateMany_SeparateManyId", "Id"); - - b1.ToTable("SeparateEntitiesMany_SeparateEntitiesMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b2.Property("OwnedEntity_Owns_SeparateManyId") - .HasColumnType("int"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id"), 1L, 1); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId", "Id"); - - b2.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateMany_SeparateOneId", "Id"); - - b1.ToTable("SeparateEntitiesMany_SeparateEntitiesOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b2.Property("OwnedEntity_Owns_SeparateOneId") - .HasColumnType("int"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); - - b2.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOneId"); - - b1.ToTable("SeparateEntitiesOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOneId"); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOne_InlineId"); - - b1.ToTable("SeparateEntitiesOne_Inline", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); - - b2.ToTable("SeparateEntitiesOne_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOne_SeparateManyId"); - - b1.ToTable("SeparateEntitiesOne_SeparateMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id"), 1L, 1); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", "Id"); - - b2.ToTable("SeparateEntitiesOne_SeparateMany_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOne_SeparateOneId"); - - b1.ToTable("SeparateEntitiesOne_SeparateOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); - - b2.ToTable("SeparateEntitiesOne_SeparateOne_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Navigation("Children"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +#nullable disable + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20221216112411_Add_TestEntityWithBaseClass")] + partial class Add_TestEntityWithBaseClass + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("Thinktecture:ComplexCollectionParameter:Thinktecture.TestDatabaseContext.MyParameter", b => + { + b.Property("Column1") + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("Column2") + .HasColumnType("int"); + + b.ToTable("MyParameter", null, t => t.ExcludeFromMigrations()); + }); + + modelBuilder.Entity("Thinktecture:ScalarCollectionParameter:Thinktecture.TestDatabaseContext.ConvertibleClass", b => + { + b.Property("Value") + .HasColumnType("int"); + + b.ToTable("ScalarCollectionParameter", null, t => t.ExcludeFromMigrations()); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaColumn", b => + { + b.Property("CHARACTER_MAXIMUM_LENGTH") + .HasColumnType("int"); + + b.Property("CHARACTER_OCTET_LENGTH") + .HasColumnType("int"); + + b.Property("CHARACTER_SET_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CHARACTER_SET_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CHARACTER_SET_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("COLLATION_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("COLLATION_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("COLLATION_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("COLUMN_DEFAULT") + .HasColumnType("nvarchar(max)"); + + b.Property("COLUMN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("DATA_TYPE") + .HasColumnType("nvarchar(max)"); + + b.Property("DATETIME_PRECISION") + .HasColumnType("smallint"); + + b.Property("DOMAIN_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("DOMAIN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("DOMAIN_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("IS_NULLABLE") + .HasColumnType("nvarchar(max)"); + + b.Property("NUMERIC_PRECISION") + .HasColumnType("tinyint"); + + b.Property("NUMERIC_PRECISION_RADIX") + .HasColumnType("smallint"); + + b.Property("NUMERIC_SCALE") + .HasColumnType("int"); + + b.Property("ORDINAL_POSITION") + .HasColumnType("int"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToView("<>"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaConstraintColumn", b => + { + b.Property("COLUMN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToView("<>"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaKeyColumn", b => + { + b.Property("COLUMN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("ORDINAL_POSITION") + .HasColumnType("int"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToView("<>"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaTableConstraint", b => + { + b.Property("CONSTRAINT_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_TYPE") + .HasColumnType("nvarchar(max)"); + + b.Property("INITIALLY_DEFERRED") + .HasColumnType("nvarchar(max)"); + + b.Property("IS_DEFERRABLE") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToView("<>"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.KeylessTestEntity", b => + { + b.Property("IntColumn") + .HasColumnType("int"); + + b.ToTable("KeylessEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConvertibleClass") + .HasColumnType("int"); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("PropertyWithBackingField") + .HasColumnType("int"); + + b.Property("RequiredName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("_privateField") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithBaseClass", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithBaseClass"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithCollation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ColumnWithCollation") + .IsRequired() + .HasColumnType("nvarchar(max)") + .UseCollation("Japanese_CI_AS"); + + b.Property("ColumnWithoutCollation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TestEntityWithCollation"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(2); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("4"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("3"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDotnetDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithRowVersion"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ShadowIntProperty") + .HasColumnType("int"); + + b.Property("ShadowStringProperty") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("newid()"); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("1"); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("2"); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'4'"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'3'"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestTemporalTableEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("PeriodEnd") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasColumnName("PeriodEnd"); + + b.Property("PeriodStart") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasColumnName("PeriodStart"); + + b.HasKey("Id"); + + b.ToTable("TestTemporalTableEntity", (string)null); + + b.ToTable(tb => tb.IsTemporal(ttb => + { + ttb + .HasPeriodStart("PeriodStart") + .HasColumnName("PeriodStart"); + ttb + .HasPeriodEnd("PeriodEnd") + .HasColumnName("PeriodEnd"); + } + )); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestViewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToView("TestView"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_InlineId"); + + b1.ToTable("TestEntities_Own_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_InlineId"); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_Inline_InlineId"); + + b1.ToTable("TestEntities_Own_Inline_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); + + b2.ToTable("TestEntities_Own_Inline_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_Inline_SeparateManyId"); + + b1.ToTable("TestEntities_Own_Inline_SeparateMany"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id"), 1L, 1); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", "Id"); + + b2.ToTable("InlineEntities_SeparateMany", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_Inline_SeparateOneId"); + + b1.ToTable("TestEntities_Own_Inline_SeparateOne"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); + + b2.ToTable("InlineEntities_SeparateOne", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateManyId", "Id"); + + b1.ToTable("SeparateEntitiesMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateManyId"); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateMany_InlineId", "Id"); + + b1.ToTable("SeparateEntitiesMany_Inline", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OwnedEntity_Owns_InlineId") + .HasColumnType("int"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); + + b2.ToTable("SeparateEntitiesMany_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateMany_SeparateManyId", "Id"); + + b1.ToTable("SeparateEntitiesMany_SeparateEntitiesMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OwnedEntity_Owns_SeparateManyId") + .HasColumnType("int"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id"), 1L, 1); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId", "Id"); + + b2.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateMany_SeparateOneId", "Id"); + + b1.ToTable("SeparateEntitiesMany_SeparateEntitiesOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OwnedEntity_Owns_SeparateOneId") + .HasColumnType("int"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); + + b2.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOneId"); + + b1.ToTable("SeparateEntitiesOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOneId"); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOne_InlineId"); + + b1.ToTable("SeparateEntitiesOne_Inline", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); + + b2.ToTable("SeparateEntitiesOne_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOne_SeparateManyId"); + + b1.ToTable("SeparateEntitiesOne_SeparateMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id"), 1L, 1); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", "Id"); + + b2.ToTable("SeparateEntitiesOne_SeparateMany_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOne_SeparateOneId"); + + b1.ToTable("SeparateEntitiesOne_SeparateOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); + + b2.ToTable("SeparateEntitiesOne_SeparateOne_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Navigation("Children"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20221216112411_Add_TestEntityWithBaseClass.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20221216112411_Add_TestEntityWithBaseClass.cs index 2a146a36..16a8d12b 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20221216112411_Add_TestEntityWithBaseClass.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20221216112411_Add_TestEntityWithBaseClass.cs @@ -1,47 +1,47 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Thinktecture.Migrations -{ - public partial class Add_TestEntityWithBaseClass : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "TestEntitiesWithBaseClass", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_TestEntitiesWithBaseClass", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "TestEntityWithCollation", - columns: table => new - { - Id = table.Column(type: "uniqueidentifier", nullable: false), - ColumnWithoutCollation = table.Column(type: "nvarchar(max)", nullable: false), - ColumnWithCollation = table.Column(type: "nvarchar(max)", nullable: false, collation: "Japanese_CI_AS") - }, - constraints: table => - { - table.PrimaryKey("PK_TestEntityWithCollation", x => x.Id); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "TestEntitiesWithBaseClass"); - - migrationBuilder.DropTable( - name: "TestEntityWithCollation"); - } - } -} +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Thinktecture.Migrations +{ + public partial class Add_TestEntityWithBaseClass : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "TestEntitiesWithBaseClass", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + Name = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TestEntitiesWithBaseClass", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "TestEntityWithCollation", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + ColumnWithoutCollation = table.Column(type: "nvarchar(max)", nullable: false), + ColumnWithCollation = table.Column(type: "nvarchar(max)", nullable: false, collation: "Japanese_CI_AS") + }, + constraints: table => + { + table.PrimaryKey("PK_TestEntityWithCollation", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TestEntitiesWithBaseClass"); + + migrationBuilder.DropTable( + name: "TestEntityWithCollation"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20230207183021_Add_Index_for_TableHintTesting.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20230207183021_Add_Index_for_TableHintTesting.Designer.cs index b1d445fd..d182b64f 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20230207183021_Add_Index_for_TableHintTesting.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20230207183021_Add_Index_for_TableHintTesting.Designer.cs @@ -1,1147 +1,1147 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -#nullable disable - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20230207183021_Add_Index_for_TableHintTesting")] - partial class Add_Index_for_TableHintTesting - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "6.0.12") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); - - modelBuilder.Entity("Thinktecture:ComplexCollectionParameter:Thinktecture.TestDatabaseContext.MyParameter", b => - { - b.Property("Column1") - .HasColumnType("uniqueidentifier") - .HasColumnName("Id"); - - b.Property("Column2") - .HasColumnType("int"); - - b.ToTable("MyParameter", null, t => t.ExcludeFromMigrations()); - }); - - modelBuilder.Entity("Thinktecture:ScalarCollectionParameter:Thinktecture.TestDatabaseContext.ConvertibleClass", b => - { - b.Property("Value") - .HasColumnType("int"); - - b.ToTable("ScalarCollectionParameter", null, t => t.ExcludeFromMigrations()); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaColumn", b => - { - b.Property("CHARACTER_MAXIMUM_LENGTH") - .HasColumnType("int"); - - b.Property("CHARACTER_OCTET_LENGTH") - .HasColumnType("int"); - - b.Property("CHARACTER_SET_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CHARACTER_SET_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CHARACTER_SET_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("COLLATION_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("COLLATION_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("COLLATION_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("COLUMN_DEFAULT") - .HasColumnType("nvarchar(max)"); - - b.Property("COLUMN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("DATA_TYPE") - .HasColumnType("nvarchar(max)"); - - b.Property("DATETIME_PRECISION") - .HasColumnType("smallint"); - - b.Property("DOMAIN_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("DOMAIN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("DOMAIN_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("IS_NULLABLE") - .HasColumnType("nvarchar(max)"); - - b.Property("NUMERIC_PRECISION") - .HasColumnType("tinyint"); - - b.Property("NUMERIC_PRECISION_RADIX") - .HasColumnType("smallint"); - - b.Property("NUMERIC_SCALE") - .HasColumnType("int"); - - b.Property("ORDINAL_POSITION") - .HasColumnType("int"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToView("<>"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaConstraintColumn", b => - { - b.Property("COLUMN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToView("<>"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaKeyColumn", b => - { - b.Property("COLUMN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("ORDINAL_POSITION") - .HasColumnType("int"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToView("<>"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaTableConstraint", b => - { - b.Property("CONSTRAINT_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_TYPE") - .HasColumnType("nvarchar(max)"); - - b.Property("INITIALLY_DEFERRED") - .HasColumnType("nvarchar(max)"); - - b.Property("IS_DEFERRABLE") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToView("<>"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.KeylessTestEntity", b => - { - b.Property("IntColumn") - .HasColumnType("int"); - - b.ToTable("KeylessEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConvertibleClass") - .HasColumnType("int"); - - b.Property("Count") - .HasColumnType("int"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("ParentId") - .HasColumnType("uniqueidentifier"); - - b.Property("PropertyWithBackingField") - .HasColumnType("int"); - - b.Property("RequiredName") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("_privateField") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasDatabaseName("IX_TestEntities_Id"); - - b.HasIndex("ParentId"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithBaseClass", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithBaseClass"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithCollation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ColumnWithCollation") - .IsRequired() - .HasColumnType("nvarchar(max)") - .UseCollation("Japanese_CI_AS"); - - b.Property("ColumnWithoutCollation") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TestEntityWithCollation"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(2); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValue("4"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValue("3"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDotnetDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("rowversion"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithRowVersion"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("ShadowIntProperty") - .HasColumnType("int"); - - b.Property("ShadowStringProperty") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("newid()"); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("1"); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("2"); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'4'"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'3'"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestTemporalTableEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("PeriodEnd") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("datetime2") - .HasColumnName("PeriodEnd"); - - b.Property("PeriodStart") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("datetime2") - .HasColumnName("PeriodStart"); - - b.HasKey("Id"); - - b.ToTable("TestTemporalTableEntity", (string)null); - - b.ToTable(tb => tb.IsTemporal(ttb => - { - ttb - .HasPeriodStart("PeriodStart") - .HasColumnName("PeriodStart"); - ttb - .HasPeriodEnd("PeriodEnd") - .HasColumnName("PeriodEnd"); - } - )); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestViewEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToView("TestView"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_InlineId"); - - b1.ToTable("TestEntities_Own_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_InlineId"); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_Inline_InlineId"); - - b1.ToTable("TestEntities_Own_Inline_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); - - b2.ToTable("TestEntities_Own_Inline_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_Inline_SeparateManyId"); - - b1.ToTable("TestEntities_Own_Inline_SeparateMany"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id"), 1L, 1); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", "Id"); - - b2.ToTable("InlineEntities_SeparateMany", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_Inline_SeparateOneId"); - - b1.ToTable("TestEntities_Own_Inline_SeparateOne"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); - - b2.ToTable("InlineEntities_SeparateOne", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateManyId", "Id"); - - b1.ToTable("SeparateEntitiesMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateManyId"); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateMany_InlineId", "Id"); - - b1.ToTable("SeparateEntitiesMany_Inline", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId") - .HasColumnType("uniqueidentifier"); - - b2.Property("OwnedEntity_Owns_InlineId") - .HasColumnType("int"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); - - b2.ToTable("SeparateEntitiesMany_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateMany_SeparateManyId", "Id"); - - b1.ToTable("SeparateEntitiesMany_SeparateEntitiesMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b2.Property("OwnedEntity_Owns_SeparateManyId") - .HasColumnType("int"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id"), 1L, 1); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId", "Id"); - - b2.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateMany_SeparateOneId", "Id"); - - b1.ToTable("SeparateEntitiesMany_SeparateEntitiesOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b2.Property("OwnedEntity_Owns_SeparateOneId") - .HasColumnType("int"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); - - b2.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOneId"); - - b1.ToTable("SeparateEntitiesOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOneId"); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOne_InlineId"); - - b1.ToTable("SeparateEntitiesOne_Inline", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); - - b2.ToTable("SeparateEntitiesOne_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOne_SeparateManyId"); - - b1.ToTable("SeparateEntitiesOne_SeparateMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id"), 1L, 1); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", "Id"); - - b2.ToTable("SeparateEntitiesOne_SeparateMany_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOne_SeparateOneId"); - - b1.ToTable("SeparateEntitiesOne_SeparateOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); - - b2.ToTable("SeparateEntitiesOne_SeparateOne_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Navigation("Children"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +#nullable disable + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20230207183021_Add_Index_for_TableHintTesting")] + partial class Add_Index_for_TableHintTesting + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + + modelBuilder.Entity("Thinktecture:ComplexCollectionParameter:Thinktecture.TestDatabaseContext.MyParameter", b => + { + b.Property("Column1") + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("Column2") + .HasColumnType("int"); + + b.ToTable("MyParameter", null, t => t.ExcludeFromMigrations()); + }); + + modelBuilder.Entity("Thinktecture:ScalarCollectionParameter:Thinktecture.TestDatabaseContext.ConvertibleClass", b => + { + b.Property("Value") + .HasColumnType("int"); + + b.ToTable("ScalarCollectionParameter", null, t => t.ExcludeFromMigrations()); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaColumn", b => + { + b.Property("CHARACTER_MAXIMUM_LENGTH") + .HasColumnType("int"); + + b.Property("CHARACTER_OCTET_LENGTH") + .HasColumnType("int"); + + b.Property("CHARACTER_SET_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CHARACTER_SET_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CHARACTER_SET_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("COLLATION_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("COLLATION_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("COLLATION_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("COLUMN_DEFAULT") + .HasColumnType("nvarchar(max)"); + + b.Property("COLUMN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("DATA_TYPE") + .HasColumnType("nvarchar(max)"); + + b.Property("DATETIME_PRECISION") + .HasColumnType("smallint"); + + b.Property("DOMAIN_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("DOMAIN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("DOMAIN_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("IS_NULLABLE") + .HasColumnType("nvarchar(max)"); + + b.Property("NUMERIC_PRECISION") + .HasColumnType("tinyint"); + + b.Property("NUMERIC_PRECISION_RADIX") + .HasColumnType("smallint"); + + b.Property("NUMERIC_SCALE") + .HasColumnType("int"); + + b.Property("ORDINAL_POSITION") + .HasColumnType("int"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToView("<>"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaConstraintColumn", b => + { + b.Property("COLUMN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToView("<>"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaKeyColumn", b => + { + b.Property("COLUMN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("ORDINAL_POSITION") + .HasColumnType("int"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToView("<>"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaTableConstraint", b => + { + b.Property("CONSTRAINT_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_TYPE") + .HasColumnType("nvarchar(max)"); + + b.Property("INITIALLY_DEFERRED") + .HasColumnType("nvarchar(max)"); + + b.Property("IS_DEFERRABLE") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToView("<>"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.KeylessTestEntity", b => + { + b.Property("IntColumn") + .HasColumnType("int"); + + b.ToTable("KeylessEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConvertibleClass") + .HasColumnType("int"); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("PropertyWithBackingField") + .HasColumnType("int"); + + b.Property("RequiredName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("_privateField") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasDatabaseName("IX_TestEntities_Id"); + + b.HasIndex("ParentId"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithBaseClass", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithBaseClass"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithCollation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ColumnWithCollation") + .IsRequired() + .HasColumnType("nvarchar(max)") + .UseCollation("Japanese_CI_AS"); + + b.Property("ColumnWithoutCollation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TestEntityWithCollation"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(2); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("4"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("3"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDotnetDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithRowVersion"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ShadowIntProperty") + .HasColumnType("int"); + + b.Property("ShadowStringProperty") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("newid()"); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("1"); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("2"); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'4'"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'3'"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestTemporalTableEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("PeriodEnd") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasColumnName("PeriodEnd"); + + b.Property("PeriodStart") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasColumnName("PeriodStart"); + + b.HasKey("Id"); + + b.ToTable("TestTemporalTableEntity", (string)null); + + b.ToTable(tb => tb.IsTemporal(ttb => + { + ttb + .HasPeriodStart("PeriodStart") + .HasColumnName("PeriodStart"); + ttb + .HasPeriodEnd("PeriodEnd") + .HasColumnName("PeriodEnd"); + } + )); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestViewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToView("TestView"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_InlineId"); + + b1.ToTable("TestEntities_Own_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_InlineId"); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_Inline_InlineId"); + + b1.ToTable("TestEntities_Own_Inline_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); + + b2.ToTable("TestEntities_Own_Inline_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_Inline_SeparateManyId"); + + b1.ToTable("TestEntities_Own_Inline_SeparateMany"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id"), 1L, 1); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", "Id"); + + b2.ToTable("InlineEntities_SeparateMany", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_Inline_SeparateOneId"); + + b1.ToTable("TestEntities_Own_Inline_SeparateOne"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); + + b2.ToTable("InlineEntities_SeparateOne", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateManyId", "Id"); + + b1.ToTable("SeparateEntitiesMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateManyId"); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateMany_InlineId", "Id"); + + b1.ToTable("SeparateEntitiesMany_Inline", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OwnedEntity_Owns_InlineId") + .HasColumnType("int"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); + + b2.ToTable("SeparateEntitiesMany_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateMany_SeparateManyId", "Id"); + + b1.ToTable("SeparateEntitiesMany_SeparateEntitiesMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OwnedEntity_Owns_SeparateManyId") + .HasColumnType("int"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id"), 1L, 1); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId", "Id"); + + b2.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id"), 1L, 1); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateMany_SeparateOneId", "Id"); + + b1.ToTable("SeparateEntitiesMany_SeparateEntitiesOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OwnedEntity_Owns_SeparateOneId") + .HasColumnType("int"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); + + b2.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOneId"); + + b1.ToTable("SeparateEntitiesOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOneId"); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOne_InlineId"); + + b1.ToTable("SeparateEntitiesOne_Inline", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); + + b2.ToTable("SeparateEntitiesOne_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOne_SeparateManyId"); + + b1.ToTable("SeparateEntitiesOne_SeparateMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id"), 1L, 1); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", "Id"); + + b2.ToTable("SeparateEntitiesOne_SeparateMany_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOne_SeparateOneId"); + + b1.ToTable("SeparateEntitiesOne_SeparateOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); + + b2.ToTable("SeparateEntitiesOne_SeparateOne_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Navigation("Children"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20230207183021_Add_Index_for_TableHintTesting.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20230207183021_Add_Index_for_TableHintTesting.cs index 4d9e0470..4cbb05dd 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20230207183021_Add_Index_for_TableHintTesting.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/20230207183021_Add_Index_for_TableHintTesting.cs @@ -1,24 +1,24 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Thinktecture.Migrations -{ - public partial class Add_Index_for_TableHintTesting : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateIndex( - name: "IX_TestEntities_Id", - table: "TestEntities", - column: "Id"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_TestEntities_Id", - table: "TestEntities"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Thinktecture.Migrations +{ + public partial class Add_Index_for_TableHintTesting : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_TestEntities_Id", + table: "TestEntities", + column: "Id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_TestEntities_Id", + table: "TestEntities"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/TestDbContextModelSnapshot.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/TestDbContextModelSnapshot.cs index b474f306..6c1cd043 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/TestDbContextModelSnapshot.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Migrations/TestDbContextModelSnapshot.cs @@ -1,1185 +1,1185 @@ -// -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -#nullable disable - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - partial class TestDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.0-rc.2.23480.1") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaColumn", b => - { - b.Property("CHARACTER_MAXIMUM_LENGTH") - .HasColumnType("int"); - - b.Property("CHARACTER_OCTET_LENGTH") - .HasColumnType("int"); - - b.Property("CHARACTER_SET_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CHARACTER_SET_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CHARACTER_SET_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("COLLATION_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("COLLATION_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("COLLATION_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("COLUMN_DEFAULT") - .HasColumnType("nvarchar(max)"); - - b.Property("COLUMN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("DATA_TYPE") - .HasColumnType("nvarchar(max)"); - - b.Property("DATETIME_PRECISION") - .HasColumnType("smallint"); - - b.Property("DOMAIN_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("DOMAIN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("DOMAIN_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("IS_NULLABLE") - .HasColumnType("nvarchar(max)"); - - b.Property("NUMERIC_PRECISION") - .HasColumnType("tinyint"); - - b.Property("NUMERIC_PRECISION_RADIX") - .HasColumnType("smallint"); - - b.Property("NUMERIC_SCALE") - .HasColumnType("int"); - - b.Property("ORDINAL_POSITION") - .HasColumnType("int"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToTable((string)null); - - b.ToView("<>", (string)null); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaConstraintColumn", b => - { - b.Property("COLUMN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToTable((string)null); - - b.ToView("<>", (string)null); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaKeyColumn", b => - { - b.Property("COLUMN_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("ORDINAL_POSITION") - .HasColumnType("int"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToTable((string)null); - - b.ToView("<>", (string)null); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaTableConstraint", b => - { - b.Property("CONSTRAINT_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.Property("CONSTRAINT_TYPE") - .HasColumnType("nvarchar(max)"); - - b.Property("INITIALLY_DEFERRED") - .HasColumnType("nvarchar(max)"); - - b.Property("IS_DEFERRABLE") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_CATALOG") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_NAME") - .HasColumnType("nvarchar(max)"); - - b.Property("TABLE_SCHEMA") - .HasColumnType("nvarchar(max)"); - - b.ToTable((string)null); - - b.ToView("<>", (string)null); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.KeylessTestEntity", b => - { - b.Property("IntColumn") - .HasColumnType("int"); - - b.ToTable("KeylessEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ConvertibleClass") - .HasColumnType("int"); - - b.Property("Count") - .HasColumnType("int"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("NullableCount") - .HasColumnType("int"); - - b.Property("ParentId") - .HasColumnType("uniqueidentifier"); - - b.Property("PropertyWithBackingField") - .HasColumnType("int"); - - b.Property("RequiredName") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("_privateField") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasDatabaseName("IX_TestEntities_Id"); - - b.HasIndex("ParentId"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithBaseClass", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithBaseClass"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithCollation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("ColumnWithCollation") - .IsRequired() - .HasColumnType("nvarchar(max)") - .UseCollation("Japanese_CI_AS"); - - b.Property("ColumnWithoutCollation") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("TestEntityWithCollation"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithComplexType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.ComplexProperty>("Boundary", "Thinktecture.TestDatabaseContext.TestEntityWithComplexType.Boundary#BoundaryValueObject", b1 => - { - b1.IsRequired(); - - b1.Property("Lower") - .HasColumnType("int"); - - b1.Property("Upper") - .HasColumnType("int"); - }); - - b.HasKey("Id"); - - b.ToTable("TestEntities_with_ComplexType"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(1); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValue(2); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValue("4"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValue("3"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDotnetDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .IsRequired() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("rowversion"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithRowVersion"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.Property("ShadowIntProperty") - .HasColumnType("int"); - - b.Property("ShadowStringProperty") - .HasMaxLength(50) - .HasColumnType("nvarchar(50)"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier") - .HasDefaultValueSql("newid()"); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("1"); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("int") - .HasDefaultValueSql("2"); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'4'"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("nvarchar(max)") - .HasDefaultValueSql("'3'"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestTemporalTableEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("PeriodEnd") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("datetime2") - .HasColumnName("PeriodEnd"); - - b.Property("PeriodStart") - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("datetime2") - .HasColumnName("PeriodStart"); - - b.HasKey("Id"); - - b.ToTable("TestTemporalTableEntity", (string)null); - - b.ToTable(tb => tb.IsTemporal(ttb => - { - ttb.UseHistoryTable("TestTemporalTableEntityHistory"); - ttb - .HasPeriodStart("PeriodStart") - .HasColumnName("PeriodStart"); - ttb - .HasPeriodEnd("PeriodEnd") - .HasColumnName("PeriodEnd"); - })); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestViewEntity", b => - { - b.Property("Id") - .HasColumnType("uniqueidentifier"); - - b.Property("Name") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable((string)null); - - b.ToView("TestView", (string)null); - }); - - modelBuilder.Entity("Thinktecture:ComplexCollectionParameter:Thinktecture.TestDatabaseContext.MyParameter", b => - { - b.Property("Column1") - .HasColumnType("uniqueidentifier") - .HasColumnName("Id"); - - b.Property("Column2") - .HasColumnType("int"); - - b.ToTable("MyParameter", null, t => - { - t.ExcludeFromMigrations(); - }); - }); - - modelBuilder.Entity("Thinktecture:ScalarCollectionParameter:Thinktecture.TestDatabaseContext.ConvertibleClass", b => - { - b.Property("Value") - .HasColumnType("int"); - - b.ToTable("ScalarCollectionParameter", null, t => - { - t.ExcludeFromMigrations(); - }); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_InlineId"); - - b1.ToTable("TestEntities_Own_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_InlineId"); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_Inline_InlineId"); - - b1.ToTable("TestEntities_Own_Inline_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); - - b2.ToTable("TestEntities_Own_Inline_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_Inline_SeparateManyId"); - - b1.ToTable("TestEntities_Own_Inline_SeparateMany"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id")); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", "Id"); - - b2.ToTable("InlineEntities_SeparateMany", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_Inline_SeparateOneId"); - - b1.ToTable("TestEntities_Own_Inline_SeparateOne"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); - - b2.ToTable("InlineEntities_SeparateOne", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateManyId", "Id"); - - b1.ToTable("SeparateEntitiesMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateManyId"); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateMany_InlineId", "Id"); - - b1.ToTable("SeparateEntitiesMany_Inline", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId") - .HasColumnType("uniqueidentifier"); - - b2.Property("OwnedEntity_Owns_InlineId") - .HasColumnType("int"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); - - b2.ToTable("SeparateEntitiesMany_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateMany_SeparateManyId", "Id"); - - b1.ToTable("SeparateEntitiesMany_SeparateEntitiesMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b2.Property("OwnedEntity_Owns_SeparateManyId") - .HasColumnType("int"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id")); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId", "Id"); - - b2.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateMany_SeparateOneId", "Id"); - - b1.ToTable("SeparateEntitiesMany_SeparateEntitiesOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b2.Property("OwnedEntity_Owns_SeparateOneId") - .HasColumnType("int"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); - - b2.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOneId"); - - b1.ToTable("SeparateEntitiesOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOneId"); - }); - - b.Navigation("SeparateEntity"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_InlineId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOne_InlineId"); - - b1.ToTable("SeparateEntitiesOne_Inline", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); - - b2.ToTable("SeparateEntitiesOne_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOne_SeparateManyId"); - - b1.ToTable("SeparateEntitiesOne_SeparateMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId") - .HasColumnType("uniqueidentifier"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id")); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", "Id"); - - b2.ToTable("SeparateEntitiesOne_SeparateMany_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b1.Property("IntColumn") - .HasColumnType("int"); - - b1.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b1.HasKey("TestEntity_Owns_SeparateOne_SeparateOneId"); - - b1.ToTable("SeparateEntitiesOne_SeparateOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId") - .HasColumnType("uniqueidentifier"); - - b2.Property("IntColumn") - .HasColumnType("int"); - - b2.Property("StringColumn") - .HasColumnType("nvarchar(max)"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); - - b2.ToTable("SeparateEntitiesOne_SeparateOne_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Navigation("Children"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +#nullable disable + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + partial class TestDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0-rc.2.23480.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaColumn", b => + { + b.Property("CHARACTER_MAXIMUM_LENGTH") + .HasColumnType("int"); + + b.Property("CHARACTER_OCTET_LENGTH") + .HasColumnType("int"); + + b.Property("CHARACTER_SET_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CHARACTER_SET_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CHARACTER_SET_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("COLLATION_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("COLLATION_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("COLLATION_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("COLUMN_DEFAULT") + .HasColumnType("nvarchar(max)"); + + b.Property("COLUMN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("DATA_TYPE") + .HasColumnType("nvarchar(max)"); + + b.Property("DATETIME_PRECISION") + .HasColumnType("smallint"); + + b.Property("DOMAIN_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("DOMAIN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("DOMAIN_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("IS_NULLABLE") + .HasColumnType("nvarchar(max)"); + + b.Property("NUMERIC_PRECISION") + .HasColumnType("tinyint"); + + b.Property("NUMERIC_PRECISION_RADIX") + .HasColumnType("smallint"); + + b.Property("NUMERIC_SCALE") + .HasColumnType("int"); + + b.Property("ORDINAL_POSITION") + .HasColumnType("int"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToTable((string)null); + + b.ToView("<>", (string)null); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaConstraintColumn", b => + { + b.Property("COLUMN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToTable((string)null); + + b.ToView("<>", (string)null); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaKeyColumn", b => + { + b.Property("COLUMN_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("ORDINAL_POSITION") + .HasColumnType("int"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToTable((string)null); + + b.ToView("<>", (string)null); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.InformationSchemaTableConstraint", b => + { + b.Property("CONSTRAINT_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.Property("CONSTRAINT_TYPE") + .HasColumnType("nvarchar(max)"); + + b.Property("INITIALLY_DEFERRED") + .HasColumnType("nvarchar(max)"); + + b.Property("IS_DEFERRABLE") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_CATALOG") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_NAME") + .HasColumnType("nvarchar(max)"); + + b.Property("TABLE_SCHEMA") + .HasColumnType("nvarchar(max)"); + + b.ToTable((string)null); + + b.ToView("<>", (string)null); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.KeylessTestEntity", b => + { + b.Property("IntColumn") + .HasColumnType("int"); + + b.ToTable("KeylessEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConvertibleClass") + .HasColumnType("int"); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("NullableCount") + .HasColumnType("int"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier"); + + b.Property("PropertyWithBackingField") + .HasColumnType("int"); + + b.Property("RequiredName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("_privateField") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasDatabaseName("IX_TestEntities_Id"); + + b.HasIndex("ParentId"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithBaseClass", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithBaseClass"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithCollation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ColumnWithCollation") + .IsRequired() + .HasColumnType("nvarchar(max)") + .UseCollation("Japanese_CI_AS"); + + b.Property("ColumnWithoutCollation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TestEntityWithCollation"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithComplexType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.ComplexProperty>("Boundary", "Thinktecture.TestDatabaseContext.TestEntityWithComplexType.Boundary#BoundaryValueObject", b1 => + { + b1.IsRequired(); + + b1.Property("Lower") + .HasColumnType("int"); + + b1.Property("Upper") + .HasColumnType("int"); + }); + + b.HasKey("Id"); + + b.ToTable("TestEntities_with_ComplexType"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(2); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("4"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValue("3"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDotnetDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithRowVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithRowVersion"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("ShadowIntProperty") + .HasColumnType("int"); + + b.Property("ShadowStringProperty") + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasDefaultValueSql("newid()"); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("1"); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValueSql("2"); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'4'"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("nvarchar(max)") + .HasDefaultValueSql("'3'"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestTemporalTableEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("PeriodEnd") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasColumnName("PeriodEnd"); + + b.Property("PeriodStart") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime2") + .HasColumnName("PeriodStart"); + + b.HasKey("Id"); + + b.ToTable("TestTemporalTableEntity", (string)null); + + b.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.UseHistoryTable("TestTemporalTableEntityHistory"); + ttb + .HasPeriodStart("PeriodStart") + .HasColumnName("PeriodStart"); + ttb + .HasPeriodEnd("PeriodEnd") + .HasColumnName("PeriodEnd"); + })); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestViewEntity", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable((string)null); + + b.ToView("TestView", (string)null); + }); + + modelBuilder.Entity("Thinktecture:ComplexCollectionParameter:Thinktecture.TestDatabaseContext.MyParameter", b => + { + b.Property("Column1") + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("Column2") + .HasColumnType("int"); + + b.ToTable("MyParameter", null, t => + { + t.ExcludeFromMigrations(); + }); + }); + + modelBuilder.Entity("Thinktecture:ScalarCollectionParameter:Thinktecture.TestDatabaseContext.ConvertibleClass", b => + { + b.Property("Value") + .HasColumnType("int"); + + b.ToTable("ScalarCollectionParameter", null, t => + { + t.ExcludeFromMigrations(); + }); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_InlineId"); + + b1.ToTable("TestEntities_Own_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_InlineId"); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_Inline_InlineId"); + + b1.ToTable("TestEntities_Own_Inline_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); + + b2.ToTable("TestEntities_Own_Inline_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_Inline_SeparateManyId"); + + b1.ToTable("TestEntities_Own_Inline_SeparateMany"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id")); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", "Id"); + + b2.ToTable("InlineEntities_SeparateMany", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_Inline_SeparateOneId"); + + b1.ToTable("TestEntities_Own_Inline_SeparateOne"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); + + b2.ToTable("InlineEntities_SeparateOne", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateManyId", "Id"); + + b1.ToTable("SeparateEntitiesMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateManyId"); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateMany_InlineId", "Id"); + + b1.ToTable("SeparateEntitiesMany_Inline", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OwnedEntity_Owns_InlineId") + .HasColumnType("int"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); + + b2.ToTable("SeparateEntitiesMany_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateMany_SeparateManyId", "Id"); + + b1.ToTable("SeparateEntitiesMany_SeparateEntitiesMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OwnedEntity_Owns_SeparateManyId") + .HasColumnType("int"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id")); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId", "Id"); + + b2.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property("Id")); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateMany_SeparateOneId", "Id"); + + b1.ToTable("SeparateEntitiesMany_SeparateEntitiesOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b2.Property("OwnedEntity_Owns_SeparateOneId") + .HasColumnType("int"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); + + b2.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOneId"); + + b1.ToTable("SeparateEntitiesOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOneId"); + }); + + b.Navigation("SeparateEntity"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_InlineId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOne_InlineId"); + + b1.ToTable("SeparateEntitiesOne_Inline", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); + + b2.ToTable("SeparateEntitiesOne_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOne_SeparateManyId"); + + b1.ToTable("SeparateEntitiesOne_SeparateMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId") + .HasColumnType("uniqueidentifier"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b2.Property("Id")); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", "Id"); + + b2.ToTable("SeparateEntitiesOne_SeparateMany_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b1.Property("IntColumn") + .HasColumnType("int"); + + b1.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b1.HasKey("TestEntity_Owns_SeparateOne_SeparateOneId"); + + b1.ToTable("SeparateEntitiesOne_SeparateOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId") + .HasColumnType("uniqueidentifier"); + + b2.Property("IntColumn") + .HasColumnType("int"); + + b2.Property("StringColumn") + .HasColumnType("nvarchar(max)"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); + + b2.ToTable("SeparateEntitiesOne_SeparateOne_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Navigation("Children"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/OneTimeMigrationStrategy.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/OneTimeMigrationStrategy.cs index a1e189d5..30386460 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/OneTimeMigrationStrategy.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/OneTimeMigrationStrategy.cs @@ -1,17 +1,17 @@ -using Thinktecture.EntityFrameworkCore; - -namespace Thinktecture; - -public class OneTimeMigrationStrategy : IMigrationExecutionStrategy -{ - private bool _isMigrated; - - public void Migrate(DbContext ctx) - { - if (_isMigrated) - return; - - ctx.Database.Migrate(); - _isMigrated = true; - } -} +using Thinktecture.EntityFrameworkCore; + +namespace Thinktecture; + +public class OneTimeMigrationStrategy : IMigrationExecutionStrategy +{ + private bool _isMigrated; + + public void Migrate(DbContext ctx) + { + if (_isMigrated) + return; + + ctx.Database.Migrate(); + _isMigrated = true; + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/SqlServerContainerFixture.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/SqlServerContainerFixture.cs index 95f019db..570504c6 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/SqlServerContainerFixture.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/SqlServerContainerFixture.cs @@ -1,51 +1,51 @@ -using Testcontainers.MsSql; - -namespace Thinktecture; - -public class SqlServerContainerFixture : IDisposable, IAsyncDisposable, IAsyncLifetime -{ - private readonly MsSqlContainer _sqlServerContainer; - private bool _isDisposed; - - public string ConnectionString => _sqlServerContainer.GetConnectionString(); - - public SqlServerContainerFixture() - { - _sqlServerContainer = BuildContainer(); - } - - private static MsSqlContainer BuildContainer() - { - return new MsSqlBuilder() - .WithImage("mcr.microsoft.com/mssql/server:2022-CU13-ubuntu-22.04") - .WithPassword($"P@sswo0d01_{Guid.NewGuid()}") - .WithCleanUp(true) - .Build(); - } - - public async Task InitializeAsync() - { - await _sqlServerContainer.StartAsync(); - } - - public void Dispose() - { - DisposeAsync().AsTask().GetAwaiter().GetResult(); - } - - async Task IAsyncLifetime.DisposeAsync() - { - await DisposeAsync(); - } - - public async ValueTask DisposeAsync() - { - if (_isDisposed) - return; - - _isDisposed = true; - - await _sqlServerContainer.StopAsync(); - await _sqlServerContainer.DisposeAsync(); - } -} +using Testcontainers.MsSql; + +namespace Thinktecture; + +public class SqlServerContainerFixture : IDisposable, IAsyncDisposable, IAsyncLifetime +{ + private readonly MsSqlContainer _sqlServerContainer; + private bool _isDisposed; + + public string ConnectionString => _sqlServerContainer.GetConnectionString(); + + public SqlServerContainerFixture() + { + _sqlServerContainer = BuildContainer(); + } + + private static MsSqlContainer BuildContainer() + { + return new MsSqlBuilder() + .WithImage("mcr.microsoft.com/mssql/server:2022-CU13-ubuntu-22.04") + .WithPassword($"P@sswo0d01_{Guid.NewGuid()}") + .WithCleanUp(true) + .Build(); + } + + public async Task InitializeAsync() + { + await _sqlServerContainer.StartAsync(); + } + + public void Dispose() + { + DisposeAsync().AsTask().GetAwaiter().GetResult(); + } + + async Task IAsyncLifetime.DisposeAsync() + { + await DisposeAsync(); + } + + public async ValueTask DisposeAsync() + { + if (_isDisposed) + return; + + _isDisposed = true; + + await _sqlServerContainer.StopAsync(); + await _sqlServerContainer.DisposeAsync(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/SqlServerTestsCollectionFixture.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/SqlServerTestsCollectionFixture.cs index 1c02e2ae..04e68d29 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/SqlServerTestsCollectionFixture.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/SqlServerTestsCollectionFixture.cs @@ -1,6 +1,6 @@ -namespace Thinktecture; - -[CollectionDefinition("SqlServerTests")] -public class SqlServerTestsCollectionFixture : ICollectionFixture -{ -} +namespace Thinktecture; + +[CollectionDefinition("SqlServerTests")] +public class SqlServerTestsCollectionFixture : ICollectionFixture +{ +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestContext.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestContext.cs index ab59ad3e..bc8bea5f 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestContext.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestContext.cs @@ -1,48 +1,48 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Serilog; - -namespace Thinktecture; - -public class TestContext -{ - private static readonly Lazy _lazy = new(CreateTestConfiguration); - - public static TestContext Instance => _lazy.Value; - - public IConfiguration Configuration { get; } - - public string ConnectionString => Configuration.GetConnectionString("default") - ?? throw new Exception("No connection string with name 'default' found."); - - private static TestContext CreateTestConfiguration() - { - var config = GetConfiguration(); - return new TestContext(config); - } - - public TestContext(IConfiguration config) - { - Configuration = config ?? throw new ArgumentNullException(nameof(config)); - } - - private static IConfiguration GetConfiguration() - { - return new ConfigurationBuilder() - .AddJsonFile("appsettings.json") - .AddEnvironmentVariables() - .AddUserSecrets() - .Build(); - } - - public ILoggerFactory GetLoggerFactory(ITestOutputHelper testOutputHelper) - { - ArgumentNullException.ThrowIfNull(testOutputHelper); - - var loggerConfig = new LoggerConfiguration() - .WriteTo.TestOutput(testOutputHelper, outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}"); - - return new LoggerFactory() - .AddSerilog(loggerConfig.CreateLogger()); - } -} +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Serilog; + +namespace Thinktecture; + +public class TestContext +{ + private static readonly Lazy _lazy = new(CreateTestConfiguration); + + public static TestContext Instance => _lazy.Value; + + public IConfiguration Configuration { get; } + + public string ConnectionString => Configuration.GetConnectionString("default") + ?? throw new Exception("No connection string with name 'default' found."); + + private static TestContext CreateTestConfiguration() + { + var config = GetConfiguration(); + return new TestContext(config); + } + + public TestContext(IConfiguration config) + { + Configuration = config ?? throw new ArgumentNullException(nameof(config)); + } + + private static IConfiguration GetConfiguration() + { + return new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .AddEnvironmentVariables() + .AddUserSecrets() + .Build(); + } + + public ILoggerFactory GetLoggerFactory(ITestOutputHelper testOutputHelper) + { + ArgumentNullException.ThrowIfNull(testOutputHelper); + + var loggerConfig = new LoggerConfiguration() + .WriteTo.TestOutput(testOutputHelper, outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}"); + + return new LoggerFactory() + .AddSerilog(loggerConfig.CreateLogger()); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaColumn.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaColumn.cs index de74b50b..95ff890c 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaColumn.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaColumn.cs @@ -1,30 +1,30 @@ -namespace Thinktecture.TestDatabaseContext; - -// ReSharper disable InconsistentNaming -// ReSharper disable UnusedMember.Global -public class InformationSchemaColumn -{ - public string? TABLE_CATALOG { get; set; } - public string? TABLE_SCHEMA { get; set; } - public string? TABLE_NAME { get; set; } - public string? COLUMN_NAME { get; set; } - public string? IS_NULLABLE { get; set; } - public string? DATA_TYPE { get; set; } - public int ORDINAL_POSITION { get; set; } - public string? COLUMN_DEFAULT { get; set; } - public int? CHARACTER_MAXIMUM_LENGTH { get; set; } - public int? CHARACTER_OCTET_LENGTH { get; set; } - public byte? NUMERIC_PRECISION { get; set; } - public short? NUMERIC_PRECISION_RADIX { get; set; } - public int? NUMERIC_SCALE { get; set; } - public short? DATETIME_PRECISION { get; set; } - public string? CHARACTER_SET_CATALOG { get; set; } - public string? CHARACTER_SET_SCHEMA { get; set; } - public string? CHARACTER_SET_NAME { get; set; } - public string? COLLATION_CATALOG { get; set; } - public string? COLLATION_SCHEMA { get; set; } - public string? COLLATION_NAME { get; set; } - public string? DOMAIN_CATALOG { get; set; } - public string? DOMAIN_SCHEMA { get; set; } - public string? DOMAIN_NAME { get; set; } +namespace Thinktecture.TestDatabaseContext; + +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Global +public class InformationSchemaColumn +{ + public string? TABLE_CATALOG { get; set; } + public string? TABLE_SCHEMA { get; set; } + public string? TABLE_NAME { get; set; } + public string? COLUMN_NAME { get; set; } + public string? IS_NULLABLE { get; set; } + public string? DATA_TYPE { get; set; } + public int ORDINAL_POSITION { get; set; } + public string? COLUMN_DEFAULT { get; set; } + public int? CHARACTER_MAXIMUM_LENGTH { get; set; } + public int? CHARACTER_OCTET_LENGTH { get; set; } + public byte? NUMERIC_PRECISION { get; set; } + public short? NUMERIC_PRECISION_RADIX { get; set; } + public int? NUMERIC_SCALE { get; set; } + public short? DATETIME_PRECISION { get; set; } + public string? CHARACTER_SET_CATALOG { get; set; } + public string? CHARACTER_SET_SCHEMA { get; set; } + public string? CHARACTER_SET_NAME { get; set; } + public string? COLLATION_CATALOG { get; set; } + public string? COLLATION_SCHEMA { get; set; } + public string? COLLATION_NAME { get; set; } + public string? DOMAIN_CATALOG { get; set; } + public string? DOMAIN_SCHEMA { get; set; } + public string? DOMAIN_NAME { get; set; } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaConstraintColumn.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaConstraintColumn.cs index 3b24fc19..2f9ebae5 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaConstraintColumn.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaConstraintColumn.cs @@ -1,14 +1,14 @@ -namespace Thinktecture.TestDatabaseContext; - -// ReSharper disable InconsistentNaming -// ReSharper disable UnusedMember.Global -public class InformationSchemaConstraintColumn -{ - public string? TABLE_CATALOG { get; set; } - public string? TABLE_SCHEMA { get; set; } - public string? TABLE_NAME { get; set; } - public string? COLUMN_NAME { get; set; } - public string? CONSTRAINT_CATALOG { get; set; } - public string? CONSTRAINT_SCHEMA { get; set; } - public string? CONSTRAINT_NAME { get; set; } +namespace Thinktecture.TestDatabaseContext; + +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Global +public class InformationSchemaConstraintColumn +{ + public string? TABLE_CATALOG { get; set; } + public string? TABLE_SCHEMA { get; set; } + public string? TABLE_NAME { get; set; } + public string? COLUMN_NAME { get; set; } + public string? CONSTRAINT_CATALOG { get; set; } + public string? CONSTRAINT_SCHEMA { get; set; } + public string? CONSTRAINT_NAME { get; set; } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaKeyColumn.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaKeyColumn.cs index b3c150a2..622cd935 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaKeyColumn.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaKeyColumn.cs @@ -1,15 +1,15 @@ -namespace Thinktecture.TestDatabaseContext; - -// ReSharper disable InconsistentNaming -// ReSharper disable UnusedMember.Global -public class InformationSchemaKeyColumn -{ - public string? CONSTRAINT_CATALOG { get; set; } - public string? CONSTRAINT_SCHEMA { get; set; } - public string? CONSTRAINT_NAME { get; set; } - public string? TABLE_CATALOG { get; set; } - public string? TABLE_SCHEMA { get; set; } - public string? TABLE_NAME { get; set; } - public string? COLUMN_NAME { get; set; } - public int ORDINAL_POSITION { get; set; } +namespace Thinktecture.TestDatabaseContext; + +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Global +public class InformationSchemaKeyColumn +{ + public string? CONSTRAINT_CATALOG { get; set; } + public string? CONSTRAINT_SCHEMA { get; set; } + public string? CONSTRAINT_NAME { get; set; } + public string? TABLE_CATALOG { get; set; } + public string? TABLE_SCHEMA { get; set; } + public string? TABLE_NAME { get; set; } + public string? COLUMN_NAME { get; set; } + public int ORDINAL_POSITION { get; set; } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaTableConstraint.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaTableConstraint.cs index 9665dc2f..4c44f975 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaTableConstraint.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/InformationSchemaTableConstraint.cs @@ -1,16 +1,16 @@ -namespace Thinktecture.TestDatabaseContext; - -// ReSharper disable InconsistentNaming -// ReSharper disable UnusedMember.Global -public class InformationSchemaTableConstraint -{ - public string? CONSTRAINT_CATALOG { get; set; } - public string? CONSTRAINT_SCHEMA { get; set; } - public string? CONSTRAINT_NAME { get; set; } - public string? TABLE_CATALOG { get; set; } - public string? TABLE_SCHEMA { get; set; } - public string? TABLE_NAME { get; set; } - public string? CONSTRAINT_TYPE { get; set; } - public string? IS_DEFERRABLE { get; set; } - public string? INITIALLY_DEFERRED { get; set; } -} +namespace Thinktecture.TestDatabaseContext; + +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Global +public class InformationSchemaTableConstraint +{ + public string? CONSTRAINT_CATALOG { get; set; } + public string? CONSTRAINT_SCHEMA { get; set; } + public string? CONSTRAINT_NAME { get; set; } + public string? TABLE_CATALOG { get; set; } + public string? TABLE_SCHEMA { get; set; } + public string? TABLE_NAME { get; set; } + public string? CONSTRAINT_TYPE { get; set; } + public string? IS_DEFERRABLE { get; set; } + public string? INITIALLY_DEFERRED { get; set; } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestDbContext.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestDbContext.cs index 1c7168a3..1ca88c75 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestDbContext.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestDbContext.cs @@ -1,217 +1,217 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.EntityFrameworkCore; -using Thinktecture.EntityFrameworkCore.TempTables; - -// ReSharper disable InconsistentNaming -namespace Thinktecture.TestDatabaseContext; - -public class TestDbContext : DbContext, IDbDefaultSchema -{ - /// - public string? Schema { get; } - -#nullable disable - // ReSharper disable UnusedAutoPropertyAccessor.Global - public DbSet TestEntities { get; set; } - public DbSet TestEntitiesWithBaseClass { get; set; } - public DbSet KeylessEntities { get; set; } - public DbSet TestTemporalTableEntity { get; set; } - public DbSet TestEntitiesWithAutoIncrement { get; set; } - public DbSet TestEntitiesWithRowVersion { get; set; } - public DbSet TestEntitiesWithShadowProperties { get; set; } - public DbSet TestEntitiesWithDefaultValues { get; set; } - public DbSet TestEntitiesWithDotnetDefaultValues { get; set; } - public DbSet TestEntities_Own_Inline { get; set; } - public DbSet TestEntities_Own_Inline_Inline { get; set; } - public DbSet TestEntities_Own_Inline_SeparateOne { get; set; } - public DbSet TestEntities_Own_Inline_SeparateMany { get; set; } - public DbSet TestEntities_Own_SeparateOne { get; set; } - public DbSet TestEntities_Own_SeparateOne_Inline { get; set; } - public DbSet TestEntities_Own_SeparateOne_SeparateOne { get; set; } - public DbSet TestEntities_Own_SeparateOne_SeparateMany { get; set; } - public DbSet TestEntities_Own_SeparateMany { get; set; } - public DbSet TestEntities_Own_SeparateMany_Inline { get; set; } - public DbSet TestEntities_Own_SeparateMany_SeparateOne { get; set; } - public DbSet TestEntities_Own_SeparateMany_SeparateMany { get; set; } - public DbSet TestEntities_with_ComplexType { get; set; } - public IQueryable TestView => Set(); - // ReSharper restore UnusedAutoPropertyAccessor.Global -#nullable enable - - public Action? ConfigureModel { get; set; } - public Action? Configure { get; set; } - - public TestDbContext(DbContextOptions options, IDbDefaultSchema? schema) - : base(options) - { - Schema = schema?.Schema; - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - base.OnConfiguring(optionsBuilder); - - Configure?.Invoke(optionsBuilder); - } - - /// - protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) - { - configurationBuilder.Properties(builder => builder - .HavePrecision(18, 5)); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.ConfigureScalarCollectionParameter(builder => builder.Property(e => e.Value) - .HasConversion(c => c.Key, k => new ConvertibleClass(k))); - - modelBuilder.ConfigureComplexCollectionParameter(myParamBuilder => - { - myParamBuilder.Property(e => e.Column1) - .HasColumnName("Id"); - myParamBuilder.Property(e => e.Column2) - .HasConversion(c => c.Key, k => new ConvertibleClass(k)); - }); - - TestEntity.Configure(modelBuilder); - TestEntityWithBaseClass.Configure(modelBuilder); - KeylessTestEntity.Configure(modelBuilder); - - modelBuilder.Entity(builder => builder.ToTable("TestTemporalTableEntity", tableBuilder => tableBuilder.IsTemporal())); - - modelBuilder.Entity(builder => builder.ToView("TestView")); - - modelBuilder.Entity().Property(e => e.Id).UseIdentityColumn(); - - modelBuilder.Entity() - .Property(e => e.RowVersion) - .IsRowVersion() - .HasConversion(new NumberToBytesConverter()); - - TestEntityWithShadowProperties.Configure(modelBuilder); - TestEntityWithSqlDefaultValues.Configure(modelBuilder); - modelBuilder.Entity(builder => builder.Property(e => e.Id).HasDefaultValueSql("newid()")); - - TestEntityWithDotnetDefaultValues.Configure(modelBuilder); - TestEntity_Owns_Inline.Configure(modelBuilder); - TestEntity_Owns_SeparateOne.Configure(modelBuilder); - TestEntity_Owns_SeparateMany.Configure(modelBuilder); - TestEntity_Owns_SeparateOne_SeparateOne.Configure(modelBuilder); - TestEntity_Owns_SeparateOne_Inline.Configure(modelBuilder); - TestEntity_Owns_SeparateOne_SeparateMany.Configure(modelBuilder); - TestEntity_Owns_Inline_SeparateOne.Configure(modelBuilder); - TestEntity_Owns_Inline_Inline.Configure(modelBuilder); - TestEntity_Owns_Inline_SeparateMany.Configure(modelBuilder); - TestEntity_Owns_SeparateMany_SeparateOne.Configure(modelBuilder); - TestEntity_Owns_SeparateMany_Inline.Configure(modelBuilder); - TestEntity_Owns_SeparateMany_SeparateMany.Configure(modelBuilder); - TestEntityWithCollation.Configure(modelBuilder); - TestEntityWithComplexType.Configure(modelBuilder); - - ConfigureModel?.Invoke(modelBuilder); - - modelBuilder.Entity().HasNoKey().ToView("<>"); - modelBuilder.Entity().HasNoKey().ToView("<>"); - modelBuilder.Entity().HasNoKey().ToView("<>"); - modelBuilder.Entity().HasNoKey().ToView("<>"); - } - - public IQueryable GetTempTableColumns() - where T : class - { - var entityType = this.GetTempTableEntityType(); - return GetTempTableColumns(entityType); - } - - public IQueryable GetTempTableColumns(IEntityType entityType) - { - var tableName = entityType.GetTableName() ?? throw new Exception($"The entity '{entityType.Name}' has no table name."); - - return GetTempTableColumns(tableName); - } - - public IQueryable GetTempTableColumns(string tableName) - { - ArgumentNullException.ThrowIfNull(tableName); - - if (!tableName.StartsWith("#", StringComparison.Ordinal)) - tableName = $"#{tableName}"; - - return Set().FromSqlInterpolated($""" - SELECT - * - FROM - tempdb.INFORMATION_SCHEMA.COLUMNS WITH (NOLOCK) - WHERE - OBJECT_ID(TABLE_CATALOG + '..' + TABLE_NAME) = OBJECT_ID({"tempdb.." + tableName}) - """); - } - - public IQueryable GetTempTableConstraints() - { - var tableName = this.GetTempTableEntityType().GetTableName() - ?? throw new Exception("No table name"); - - if (!tableName.StartsWith("#", StringComparison.Ordinal)) - tableName = $"#{tableName}"; - - return Set().FromSqlInterpolated($""" - SELECT - * - FROM - tempdb.INFORMATION_SCHEMA.TABLE_CONSTRAINTS WITH (NOLOCK) - WHERE - OBJECT_ID(TABLE_CATALOG + '..' + TABLE_NAME) = OBJECT_ID({"tempdb.." + tableName}) - """); - } - - public IQueryable GetTempTableConstraintsColumns() - { - var tableName = this.GetTempTableEntityType().GetTableName() ?? throw new Exception("No table name."); - - if (!tableName.StartsWith("#", StringComparison.Ordinal)) - tableName = $"#{tableName}"; - - return Set().FromSqlInterpolated($""" - SELECT - * - FROM - tempdb.INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WITH (NOLOCK) - WHERE - OBJECT_ID(TABLE_CATALOG + '..' + TABLE_NAME) = OBJECT_ID({"tempdb.." + tableName}) - """); - } - - public IQueryable GetTempTableKeyColumns() - { - var tableName = this.GetTempTableEntityType().GetTableName() ?? throw new Exception("No table name."); - - return GetTempTableKeyColumns(tableName); - } - - public IQueryable GetTempTableKeyColumns() - { - var tableName = this.GetTempTableEntityType>().GetTableName() ?? throw new Exception("No table name."); - - return GetTempTableKeyColumns(tableName); - } - - private IQueryable GetTempTableKeyColumns(string tableName) - { - if (!tableName.StartsWith("#", StringComparison.Ordinal)) - tableName = $"#{tableName}"; - - return Set().FromSqlInterpolated($""" - SELECT - * - FROM - tempdb.INFORMATION_SCHEMA.KEY_COLUMN_USAGE WITH (NOLOCK) - WHERE - OBJECT_ID(TABLE_CATALOG + '..' + TABLE_NAME) = OBJECT_ID({"tempdb.." + tableName}) - """); - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.EntityFrameworkCore; +using Thinktecture.EntityFrameworkCore.TempTables; + +// ReSharper disable InconsistentNaming +namespace Thinktecture.TestDatabaseContext; + +public class TestDbContext : DbContext, IDbDefaultSchema +{ + /// + public string? Schema { get; } + +#nullable disable + // ReSharper disable UnusedAutoPropertyAccessor.Global + public DbSet TestEntities { get; set; } + public DbSet TestEntitiesWithBaseClass { get; set; } + public DbSet KeylessEntities { get; set; } + public DbSet TestTemporalTableEntity { get; set; } + public DbSet TestEntitiesWithAutoIncrement { get; set; } + public DbSet TestEntitiesWithRowVersion { get; set; } + public DbSet TestEntitiesWithShadowProperties { get; set; } + public DbSet TestEntitiesWithDefaultValues { get; set; } + public DbSet TestEntitiesWithDotnetDefaultValues { get; set; } + public DbSet TestEntities_Own_Inline { get; set; } + public DbSet TestEntities_Own_Inline_Inline { get; set; } + public DbSet TestEntities_Own_Inline_SeparateOne { get; set; } + public DbSet TestEntities_Own_Inline_SeparateMany { get; set; } + public DbSet TestEntities_Own_SeparateOne { get; set; } + public DbSet TestEntities_Own_SeparateOne_Inline { get; set; } + public DbSet TestEntities_Own_SeparateOne_SeparateOne { get; set; } + public DbSet TestEntities_Own_SeparateOne_SeparateMany { get; set; } + public DbSet TestEntities_Own_SeparateMany { get; set; } + public DbSet TestEntities_Own_SeparateMany_Inline { get; set; } + public DbSet TestEntities_Own_SeparateMany_SeparateOne { get; set; } + public DbSet TestEntities_Own_SeparateMany_SeparateMany { get; set; } + public DbSet TestEntities_with_ComplexType { get; set; } + public IQueryable TestView => Set(); + // ReSharper restore UnusedAutoPropertyAccessor.Global +#nullable enable + + public Action? ConfigureModel { get; set; } + public Action? Configure { get; set; } + + public TestDbContext(DbContextOptions options, IDbDefaultSchema? schema) + : base(options) + { + Schema = schema?.Schema; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); + + Configure?.Invoke(optionsBuilder); + } + + /// + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + configurationBuilder.Properties(builder => builder + .HavePrecision(18, 5)); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ConfigureScalarCollectionParameter(builder => builder.Property(e => e.Value) + .HasConversion(c => c.Key, k => new ConvertibleClass(k))); + + modelBuilder.ConfigureComplexCollectionParameter(myParamBuilder => + { + myParamBuilder.Property(e => e.Column1) + .HasColumnName("Id"); + myParamBuilder.Property(e => e.Column2) + .HasConversion(c => c.Key, k => new ConvertibleClass(k)); + }); + + TestEntity.Configure(modelBuilder); + TestEntityWithBaseClass.Configure(modelBuilder); + KeylessTestEntity.Configure(modelBuilder); + + modelBuilder.Entity(builder => builder.ToTable("TestTemporalTableEntity", tableBuilder => tableBuilder.IsTemporal())); + + modelBuilder.Entity(builder => builder.ToView("TestView")); + + modelBuilder.Entity().Property(e => e.Id).UseIdentityColumn(); + + modelBuilder.Entity() + .Property(e => e.RowVersion) + .IsRowVersion() + .HasConversion(new NumberToBytesConverter()); + + TestEntityWithShadowProperties.Configure(modelBuilder); + TestEntityWithSqlDefaultValues.Configure(modelBuilder); + modelBuilder.Entity(builder => builder.Property(e => e.Id).HasDefaultValueSql("newid()")); + + TestEntityWithDotnetDefaultValues.Configure(modelBuilder); + TestEntity_Owns_Inline.Configure(modelBuilder); + TestEntity_Owns_SeparateOne.Configure(modelBuilder); + TestEntity_Owns_SeparateMany.Configure(modelBuilder); + TestEntity_Owns_SeparateOne_SeparateOne.Configure(modelBuilder); + TestEntity_Owns_SeparateOne_Inline.Configure(modelBuilder); + TestEntity_Owns_SeparateOne_SeparateMany.Configure(modelBuilder); + TestEntity_Owns_Inline_SeparateOne.Configure(modelBuilder); + TestEntity_Owns_Inline_Inline.Configure(modelBuilder); + TestEntity_Owns_Inline_SeparateMany.Configure(modelBuilder); + TestEntity_Owns_SeparateMany_SeparateOne.Configure(modelBuilder); + TestEntity_Owns_SeparateMany_Inline.Configure(modelBuilder); + TestEntity_Owns_SeparateMany_SeparateMany.Configure(modelBuilder); + TestEntityWithCollation.Configure(modelBuilder); + TestEntityWithComplexType.Configure(modelBuilder); + + ConfigureModel?.Invoke(modelBuilder); + + modelBuilder.Entity().HasNoKey().ToView("<>"); + modelBuilder.Entity().HasNoKey().ToView("<>"); + modelBuilder.Entity().HasNoKey().ToView("<>"); + modelBuilder.Entity().HasNoKey().ToView("<>"); + } + + public IQueryable GetTempTableColumns() + where T : class + { + var entityType = this.GetTempTableEntityType(); + return GetTempTableColumns(entityType); + } + + public IQueryable GetTempTableColumns(IEntityType entityType) + { + var tableName = entityType.GetTableName() ?? throw new Exception($"The entity '{entityType.Name}' has no table name."); + + return GetTempTableColumns(tableName); + } + + public IQueryable GetTempTableColumns(string tableName) + { + ArgumentNullException.ThrowIfNull(tableName); + + if (!tableName.StartsWith("#", StringComparison.Ordinal)) + tableName = $"#{tableName}"; + + return Set().FromSqlInterpolated($""" + SELECT + * + FROM + tempdb.INFORMATION_SCHEMA.COLUMNS WITH (NOLOCK) + WHERE + OBJECT_ID(TABLE_CATALOG + '..' + TABLE_NAME) = OBJECT_ID({"tempdb.." + tableName}) + """); + } + + public IQueryable GetTempTableConstraints() + { + var tableName = this.GetTempTableEntityType().GetTableName() + ?? throw new Exception("No table name"); + + if (!tableName.StartsWith("#", StringComparison.Ordinal)) + tableName = $"#{tableName}"; + + return Set().FromSqlInterpolated($""" + SELECT + * + FROM + tempdb.INFORMATION_SCHEMA.TABLE_CONSTRAINTS WITH (NOLOCK) + WHERE + OBJECT_ID(TABLE_CATALOG + '..' + TABLE_NAME) = OBJECT_ID({"tempdb.." + tableName}) + """); + } + + public IQueryable GetTempTableConstraintsColumns() + { + var tableName = this.GetTempTableEntityType().GetTableName() ?? throw new Exception("No table name."); + + if (!tableName.StartsWith("#", StringComparison.Ordinal)) + tableName = $"#{tableName}"; + + return Set().FromSqlInterpolated($""" + SELECT + * + FROM + tempdb.INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE WITH (NOLOCK) + WHERE + OBJECT_ID(TABLE_CATALOG + '..' + TABLE_NAME) = OBJECT_ID({"tempdb.." + tableName}) + """); + } + + public IQueryable GetTempTableKeyColumns() + { + var tableName = this.GetTempTableEntityType().GetTableName() ?? throw new Exception("No table name."); + + return GetTempTableKeyColumns(tableName); + } + + public IQueryable GetTempTableKeyColumns() + { + var tableName = this.GetTempTableEntityType>().GetTableName() ?? throw new Exception("No table name."); + + return GetTempTableKeyColumns(tableName); + } + + private IQueryable GetTempTableKeyColumns(string tableName) + { + if (!tableName.StartsWith("#", StringComparison.Ordinal)) + tableName = $"#{tableName}"; + + return Set().FromSqlInterpolated($""" + SELECT + * + FROM + tempdb.INFORMATION_SCHEMA.KEY_COLUMN_USAGE WITH (NOLOCK) + WHERE + OBJECT_ID(TABLE_CATALOG + '..' + TABLE_NAME) = OBJECT_ID({"tempdb.." + tableName}) + """); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestDbContextDesignTimeDbContextFactory.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestDbContextDesignTimeDbContextFactory.cs index 048dbe77..3703edc2 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestDbContextDesignTimeDbContextFactory.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestDbContextDesignTimeDbContextFactory.cs @@ -1,17 +1,17 @@ -using Microsoft.EntityFrameworkCore.Design; - -namespace Thinktecture.TestDatabaseContext; - -// ReSharper disable once UnusedMember.Global -public class TestDbContextDesignTimeDbContextFactory : IDesignTimeDbContextFactory -{ - public TestDbContext CreateDbContext(string[] args) - { - var options = new DbContextOptionsBuilder() - .UseSqlServer(TestContext.Instance.ConnectionString) - .AddSchemaRespectingComponents() - .Options; - - return new TestDbContext(options, null); - } +using Microsoft.EntityFrameworkCore.Design; + +namespace Thinktecture.TestDatabaseContext; + +// ReSharper disable once UnusedMember.Global +public class TestDbContextDesignTimeDbContextFactory : IDesignTimeDbContextFactory +{ + public TestDbContext CreateDbContext(string[] args) + { + var options = new DbContextOptionsBuilder() + .UseSqlServer(TestContext.Instance.ConnectionString) + .AddSchemaRespectingComponents() + .Options; + + return new TestDbContext(options, null); + } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestEntityWithRowVersion.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestEntityWithRowVersion.cs index 8a68ef05..8d3c35f2 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestEntityWithRowVersion.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestEntityWithRowVersion.cs @@ -1,8 +1,8 @@ -namespace Thinktecture.TestDatabaseContext; - -public class TestEntityWithRowVersion -{ - public Guid Id { get; set; } - public string? Name { get; set; } - public long RowVersion { get; set; } -} +namespace Thinktecture.TestDatabaseContext; + +public class TestEntityWithRowVersion +{ + public Guid Id { get; set; } + public string? Name { get; set; } + public long RowVersion { get; set; } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestViewEntity.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestViewEntity.cs index 9c4cc684..0c481f01 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestViewEntity.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestDatabaseContext/TestViewEntity.cs @@ -1,7 +1,7 @@ -namespace Thinktecture.TestDatabaseContext; - -public class TestViewEntity -{ - public Guid Id { get; set; } - public string? Name { get; set; } -} +namespace Thinktecture.TestDatabaseContext; + +public class TestViewEntity +{ + public Guid Id { get; set; } + public string? Name { get; set; } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestTenantDatabaseProviderFactory.cs b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestTenantDatabaseProviderFactory.cs index 0288955b..ce4a522b 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestTenantDatabaseProviderFactory.cs +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/TestTenantDatabaseProviderFactory.cs @@ -1,18 +1,18 @@ -using Thinktecture.EntityFrameworkCore.Query; - -namespace Thinktecture; - -public class TestTenantDatabaseProviderFactory : ITenantDatabaseProviderFactory -{ - private readonly ITenantDatabaseProvider _tenantDatabaseProviderMock; - - public TestTenantDatabaseProviderFactory(ITenantDatabaseProvider tenantDatabaseProviderMock) - { - _tenantDatabaseProviderMock = tenantDatabaseProviderMock ?? throw new ArgumentNullException(nameof(tenantDatabaseProviderMock)); - } - - public ITenantDatabaseProvider Create() - { - return _tenantDatabaseProviderMock; - } -} +using Thinktecture.EntityFrameworkCore.Query; + +namespace Thinktecture; + +public class TestTenantDatabaseProviderFactory : ITenantDatabaseProviderFactory +{ + private readonly ITenantDatabaseProvider _tenantDatabaseProviderMock; + + public TestTenantDatabaseProviderFactory(ITenantDatabaseProvider tenantDatabaseProviderMock) + { + _tenantDatabaseProviderMock = tenantDatabaseProviderMock ?? throw new ArgumentNullException(nameof(tenantDatabaseProviderMock)); + } + + public ITenantDatabaseProvider Create() + { + return _tenantDatabaseProviderMock; + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests.csproj b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests.csproj index e431eb68..6d0852c1 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests.csproj +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests.csproj @@ -1,28 +1,28 @@ - - - - $(NoWarn);CS1591;CA2000 - 293a3e23-fa48-4b1c-ac36-28721052e8fd - - - - - - - - - - - - - - - - - - - PreserveNewest - - - - + + + + $(NoWarn);CS1591;CA2000 + 293a3e23-fa48-4b1c-ac36-28721052e8fd + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/appsettings.json b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/appsettings.json index c3e2d55e..1114face 100644 --- a/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/appsettings.json +++ b/tests/Thinktecture.EntityFrameworkCore.SqlServer.Tests/appsettings.json @@ -1,6 +1,6 @@ -{ - "UseSqlServerContainer": true, - "ConnectionStrings": { - "default": "server=localhost;database=test;integrated security=true;TrustServerCertificate=true;Packet Size=32768;" - } -} +{ + "UseSqlServerContainer": true, + "ConnectionStrings": { + "default": "server=localhost;database=test;integrated security=true;TrustServerCertificate=true;Packet Size=32768;" + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/DbContextProviderFactoryFixture.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/DbContextProviderFactoryFixture.cs index ad98e39b..b9526cc2 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/DbContextProviderFactoryFixture.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/DbContextProviderFactoryFixture.cs @@ -1,43 +1,43 @@ -using Microsoft.Extensions.Logging; -using Thinktecture.EntityFrameworkCore; -using Thinktecture.EntityFrameworkCore.Testing; -using Thinktecture.TestDatabaseContext; -using Xunit.Extensions.AssemblyFixture; - -[assembly: TestFramework(AssemblyFixtureFramework.TypeName, AssemblyFixtureFramework.AssemblyName)] - -namespace Thinktecture; - -public class DbContextProviderFactoryFixture : IAsyncLifetime -{ - private readonly SqliteTestDbContextProviderFactory _factory; - - public DbContextProviderFactoryFixture() - { - _factory = ConfigureBuilder().BuildFactory(); - } - - private static SqliteTestDbContextProviderBuilder ConfigureBuilder() - { - return new SqliteTestDbContextProviderBuilder() - .UseMigrationExecutionStrategy(IMigrationExecutionStrategy.Migrations) - .UseMigrationLogLevel(LogLevel.Warning) - .ConfigureSqliteOptions(optionsBuilder => optionsBuilder.AddBulkOperationSupport() - .AddWindowFunctionsSupport()); - } - - public SqliteTestDbContextProvider CreateProvider(ILoggerFactory loggerFactory) - { - return _factory.Create(loggerFactory); - } - - public async Task InitializeAsync() - { - await _factory.InitializeAsync(); - } - - public async Task DisposeAsync() - { - await _factory.DisposeAsync(); - } -} +using Microsoft.Extensions.Logging; +using Thinktecture.EntityFrameworkCore; +using Thinktecture.EntityFrameworkCore.Testing; +using Thinktecture.TestDatabaseContext; +using Xunit.Extensions.AssemblyFixture; + +[assembly: TestFramework(AssemblyFixtureFramework.TypeName, AssemblyFixtureFramework.AssemblyName)] + +namespace Thinktecture; + +public class DbContextProviderFactoryFixture : IAsyncLifetime +{ + private readonly SqliteTestDbContextProviderFactory _factory; + + public DbContextProviderFactoryFixture() + { + _factory = ConfigureBuilder().BuildFactory(); + } + + private static SqliteTestDbContextProviderBuilder ConfigureBuilder() + { + return new SqliteTestDbContextProviderBuilder() + .UseMigrationExecutionStrategy(IMigrationExecutionStrategy.Migrations) + .UseMigrationLogLevel(LogLevel.Warning) + .ConfigureSqliteOptions(optionsBuilder => optionsBuilder.AddBulkOperationSupport() + .AddWindowFunctionsSupport()); + } + + public SqliteTestDbContextProvider CreateProvider(ILoggerFactory loggerFactory) + { + return _factory.Create(loggerFactory); + } + + public async Task InitializeAsync() + { + await _factory.InitializeAsync(); + } + + public async Task DisposeAsync() + { + await _factory.DisposeAsync(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/BulkInsertAsync.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/BulkInsertAsync.cs index 07680a7c..6400c046 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/BulkInsertAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/BulkInsertAsync.cs @@ -1,707 +1,707 @@ -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqliteBulkOperationExecutorTests; - -// ReSharper disable once InconsistentNaming -public class BulkInsertAsync : SchemaChangingIntegrationTestsBase -{ - private SqliteBulkOperationExecutor? _sut; - private SqliteBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); - - public BulkInsertAsync(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - - [Fact] - public async Task Should_throw_when_inserting_entities_without_creating_table_first() - { - ConfigureModel = builder => builder.Entity().HasNoKey(); - Configure = builder => builder.ConfigureWarnings(warningsBuilder => warningsBuilder.Ignore(RelationalEventId.PendingModelChangesWarning)); - - await SUT.Awaiting(sut => sut.BulkInsertAsync(new List(), new SqliteBulkInsertOptions())) - .Should().ThrowAsync().WithMessage("Error during bulk operation on table '\"CustomTempTable\"'. See inner exception for more details."); - } - - [Fact] - public async Task Should_insert_entities() - { - var testEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - - var testEntities = new[] { testEntity }; - - await SUT.BulkInsertAsync(testEntities, new SqliteBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1) - .And.Subject.First() - .Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }); - } - - [Fact] - public async Task Should_insert_private_property() - { - var testEntity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - testEntity.SetPrivateField(3); - - var testEntities = new[] { testEntity }; - - await SUT.BulkInsertAsync(testEntities, new SqliteBulkInsertOptions()); - - var loadedEntity = await AssertDbContext.TestEntities.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - loadedEntity!.GetPrivateField().Should().Be(3); - } - - [Fact] - public async Task Should_insert_shadow_properties() - { - var testEntity = new TestEntityWithShadowProperties { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866") }; - ActDbContext.Entry(testEntity).Property("ShadowStringProperty").CurrentValue = "value"; - ActDbContext.Entry(testEntity).Property("ShadowIntProperty").CurrentValue = 42; - - var testEntities = new[] { testEntity }; - - await SUT.BulkInsertAsync(testEntities, new SqliteBulkInsertOptions()); - - var loadedEntity = await AssertDbContext.TestEntitiesWithShadowProperties.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - AssertDbContext.Entry(loadedEntity!).Property("ShadowStringProperty").CurrentValue.Should().Be("value"); - AssertDbContext.Entry(loadedEntity!).Property("ShadowIntProperty").CurrentValue.Should().Be(42); - } - - [Fact] - public async Task Should_throw_because_sqlite_dont_support_null_for_NOT_NULL_despite_sql_default_value() - { - var testEntity = new TestEntityWithSqlDefaultValues { String = null! }; - var testEntities = new[] { testEntity }; - - await SUT.Awaiting(sut => sut.BulkInsertAsync(testEntities, new SqliteBulkInsertOptions())) - .Should().ThrowAsync() - .WithMessage("SQLite Error 19: 'NOT NULL constraint failed: TestEntitiesWithDefaultValues.String'."); - } - - [Fact] - public async Task Should_write_all_provided_column_values_as_is_despite_sql_default_value() - { - var testEntity = new TestEntityWithSqlDefaultValues - { - Id = Guid.Empty, - Int = 0, - String = null!, - NullableInt = null, - NullableString = null - }; - var testEntities = new[] { testEntity }; - - var options = new SqliteBulkInsertOptions - { - // we skip TestEntityWithSqlDefaultValues.String - PropertiesToInsert = IEntityPropertiesProvider.Include(e => new - { - e.Id, - e.Int, - e.NullableInt, - e.NullableString - }) - }; - - await SUT.BulkInsertAsync(testEntities, options); - - var loadedEntity = await AssertDbContext.TestEntitiesWithDefaultValues.FirstOrDefaultAsync(); - loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues - { - Id = Guid.Empty, // persisted as-is - Int = 0, // persisted as-is - NullableInt = null, // persisted as-is - String = "3", // DEFAULT value constraint - NullableString = null // persisted as-is - }); - } - - [Fact] - public async Task Should_throw_because_sqlite_dont_support_null_for_NOT_NULL_despite_dotnet_default_value() - { - var testEntity = new TestEntityWithDotnetDefaultValues { String = null! }; - var testEntities = new[] { testEntity }; - - await SUT.Awaiting(sut => sut.BulkInsertAsync(testEntities, new SqliteBulkInsertOptions())) - .Should().ThrowAsync() - .WithMessage("SQLite Error 19: 'NOT NULL constraint failed: TestEntitiesWithDotnetDefaultValues.String'."); - } - - [Fact] - public async Task Should_write_all_provided_column_values_as_is_despite_dotnet_default_value() - { - var testEntity = new TestEntityWithDotnetDefaultValues - { - Id = Guid.Empty, - Int = 0, - String = null!, - NullableInt = null, - NullableString = null - }; - var testEntities = new[] { testEntity }; - - var options = new SqliteBulkInsertOptions - { - // we skip TestEntityWithDefaultValues.String - PropertiesToInsert = IEntityPropertiesProvider.Include(e => new - { - e.Id, - e.Int, - e.NullableInt, - e.NullableString - }) - }; - - await SUT.BulkInsertAsync(testEntities, options); - - var loadedEntity = await AssertDbContext.TestEntitiesWithDotnetDefaultValues.FirstOrDefaultAsync(); - loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues - { - Id = Guid.Empty, // persisted as-is - Int = 0, // persisted as-is - NullableInt = null, // persisted as-is - String = "3", // DEFAULT value constraint - NullableString = null // persisted as-is - }); - } - - [Theory] - [InlineData(SqliteAutoIncrementBehavior.SetZeroToNull, 42, 42)] - [InlineData(SqliteAutoIncrementBehavior.KeepValueAsIs, 42, 42)] - [InlineData(SqliteAutoIncrementBehavior.SetZeroToNull, 0, 1)] // 1 because the DB is empty - [InlineData(SqliteAutoIncrementBehavior.KeepValueAsIs, 0, 0)] - public async Task Should_insert_0_to_auto_increment_column(SqliteAutoIncrementBehavior behavior, int id, int expectedId) - { - var testEntity = new TestEntityWithAutoIncrement { Id = id }; - var testEntities = new[] { testEntity }; - - var options = new SqliteBulkInsertOptions { AutoIncrementBehavior = behavior }; - await SUT.BulkInsertAsync(testEntities, options); - - var loadedEntity = await AssertDbContext.TestEntitiesWithAutoIncrement.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - loadedEntity!.Id.Should().Be(expectedId); - loadedEntity.Name.Should().BeNull(); - } - - [Fact] - public async Task Should_insert_specified_properties_only() - { - var testEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42, - PropertyWithBackingField = 7 - }; - testEntity.SetPrivateField(3); - var testEntities = new[] { testEntity }; - - await SUT.BulkInsertAsync(testEntities, - new SqliteBulkInsertOptions - { - PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) - }); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - RequiredName = "RequiredName", - Count = 42, - PropertyWithBackingField = 7 - }); - loadedEntity.GetPrivateField().Should().Be(3); - } - - [Fact] - public async Task Should_throw_if_required_inlined_owned_type_is_null() - { - var testEntity = new TestEntity_Owns_Inline - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - InlineEntity = null! - }; - var testEntities = new[] { testEntity }; - - await ActDbContext.Awaiting(ctx => ctx.BulkInsertIntoTempTableAsync(testEntities)) - .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_Inline_if_it_has_default_values_only() - { - var testEntity = new TestEntity_Owns_Inline - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - InlineEntity = new OwnedEntity() - }; - var testEntities = new[] { testEntity }; - - await SUT.BulkInsertAsync(testEntities, new SqliteBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_Inline.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity_Owns_Inline - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - InlineEntity = new OwnedEntity - { - IntColumn = 0, - StringColumn = null - } - }); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_Inline() - { - var testEntity = new TestEntity_Owns_Inline - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - InlineEntity = new OwnedEntity - { - IntColumn = 42, - StringColumn = "value" - } - }; - var testEntities = new[] { testEntity }; - - await SUT.BulkInsertAsync(testEntities, new SqliteBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_Inline.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity_Owns_Inline - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - InlineEntity = new OwnedEntity - { - IntColumn = 42, - StringColumn = "value" - } - }); - } - - [Fact] - public async Task Should_throw_if_separated_owned_type_uses_shadow_property_id_and_is_detached() - { - var testEntity = new TestEntity_Owns_SeparateOne - { - Id = new Guid("7C00ABFE-875B-4396-BE51-3E898647A264"), - SeparateEntity = new OwnedEntity - { - IntColumn = 42, - StringColumn = "value" - } - }; - - await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions())) - .Should().ThrowAsync() - .WithMessage("The entity type 'OwnedEntity' uses a shared type and the supplied entity is currently not being tracked. To start tracking this entity, call '.Reference().TargetEntry' or '.Collection().FindEntry()' on the owner entry."); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_SeparateOne() - { - var testEntity = new TestEntity_Owns_SeparateOne - { - Id = new Guid("7C00ABFE-875B-4396-BE51-3E898647A264"), - SeparateEntity = new OwnedEntity - { - IntColumn = 42, - StringColumn = "value" - } - }; - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(testEntity); - } - - [Fact] - public async Task Should_throw_if_separated_owned_types_uses_shadow_property_id_and_is_detached() - { - var testEntity = new TestEntity_Owns_SeparateMany - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntities = new List - { - new() - { - IntColumn = 42, - StringColumn = "value 1" - } - } - }; - - await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions())) - .Should().ThrowAsync() - .WithMessage("The entity type 'OwnedEntity' uses a shared type and the supplied entity is currently not being tracked. To start tracking this entity, call '.Reference().TargetEntry' or '.Collection().FindEntry()' on the owner entry."); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_SeparateMany() - { - var testEntity = new TestEntity_Owns_SeparateMany - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntities = new List - { - new() - { - IntColumn = 42, - StringColumn = "value 1" - } - } - }; - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateMany.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(testEntity); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_Inline_Inline() - { - var testEntity = new TestEntity_Owns_Inline_Inline - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - InlineEntity = new OwnedEntity_Owns_Inline - { - IntColumn = 42, - StringColumn = "value 1", - InlineEntity = new OwnedEntity - { - IntColumn = 43, - StringColumn = "value 2" - } - } - }; - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_Inline_Inline.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_Inline_SeparateMany() - { - var testEntity = new TestEntity_Owns_Inline_SeparateMany - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - InlineEntity = new OwnedEntity_Owns_SeparateMany - { - IntColumn = 42, - StringColumn = "value 1", - SeparateEntities = new List - { - new() - { - IntColumn = 43, - StringColumn = "value 2" - }, - new() - { - IntColumn = 44, - StringColumn = "value 3" - } - } - } - }; - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_Inline_SeparateMany.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_Inline_SeparateOne() - { - var testEntity = new TestEntity_Owns_Inline_SeparateOne - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - InlineEntity = new OwnedEntity_Owns_SeparateOne - { - IntColumn = 42, - StringColumn = "value 1", - SeparateEntity = new() - { - IntColumn = 43, - StringColumn = "value 2" - } - } - }; - - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] - { - testEntity - }, new SqliteBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_Inline_SeparateOne.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_SeparateMany_Inline() - { - var testEntity = new TestEntity_Owns_SeparateMany_Inline - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntities = new List - { - new() - { - IntColumn = 42, - StringColumn = "value 1", - InlineEntity = new OwnedEntity - { - IntColumn = 43, - StringColumn = "value 2" - } - }, - new() - { - IntColumn = 44, - StringColumn = "value 3", - InlineEntity = new OwnedEntity - { - IntColumn = 45, - StringColumn = "value 4" - } - } - } - }; - - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateMany_Inline.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } - - [Fact] - public async Task Should_throw_on_insert_of_TestEntity_Owns_SeparateMany_SeparateMany() - { - var testEntity = new TestEntity_Owns_SeparateMany_SeparateMany - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntities = new List - { - new() - { - IntColumn = 42, - StringColumn = "value 1", - SeparateEntities = new List - { - new() - { - IntColumn = 43, - StringColumn = "value 2" - }, - new() - { - IntColumn = 44, - StringColumn = "value 3" - } - } - }, - new() - { - IntColumn = 45, - StringColumn = "value 4", - SeparateEntities = new List - { - new() - { - IntColumn = 46, - StringColumn = "value 5" - }, - new() - { - IntColumn = 47, - StringColumn = "value 6" - } - } - } - } - }; - - ActDbContext.Add(testEntity); - - await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions())) - .Should().ThrowAsync().WithMessage("Non-inlined (i.e. with its own table) nested owned type 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany.SeparateEntities#OwnedEntity_Owns_SeparateMany.SeparateEntities' inside another owned type collection 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany.SeparateEntities' is not supported."); - } - - [Fact] - public async Task Should_throw_on_insert_of_TestEntity_Owns_SeparateMany_SeparateOne() - { - var testEntity = new TestEntity_Owns_SeparateMany_SeparateOne - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntities = new List - { - new() - { - IntColumn = 42, - StringColumn = "value 1", - SeparateEntity = new() - { - IntColumn = 43, - StringColumn = "value 2" - } - }, - new() - { - IntColumn = 45, - StringColumn = "value 4", - SeparateEntity = new() - { - IntColumn = 46, - StringColumn = "value 5" - } - } - } - }; - - ActDbContext.Add(testEntity); - - await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions())) - .Should().ThrowAsync().WithMessage("Non-inlined (i.e. with its own table) nested owned type 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne.SeparateEntities#OwnedEntity_Owns_SeparateOne.SeparateEntity' inside another owned type collection 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne.SeparateEntities' is not supported."); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_SeparateOne_Inline() - { - var testEntity = new TestEntity_Owns_SeparateOne_Inline - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntity = new() - { - IntColumn = 42, - StringColumn = "value 1", - InlineEntity = new OwnedEntity - { - IntColumn = 43, - StringColumn = "value 2" - } - } - }; - - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne_Inline.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_SeparateOne_SeparateMany() - { - var testEntity = new TestEntity_Owns_SeparateOne_SeparateMany - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntity = new() - { - IntColumn = 42, - StringColumn = "value 1", - SeparateEntities = new List - { - new() - { - IntColumn = 43, - StringColumn = "value 2" - }, - new() - { - IntColumn = 44, - StringColumn = "value 3" - } - } - } - }; - - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne_SeparateMany.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } - - [Fact] - public async Task Should_insert_TestEntity_Owns_SeparateOne_SeparateOne() - { - var testEntity = new TestEntity_Owns_SeparateOne_SeparateOne - { - Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - SeparateEntity = new() - { - IntColumn = 42, - StringColumn = "value 1", - SeparateEntity = new() - { - IntColumn = 43, - StringColumn = "value 2" - } - } - }; - - ActDbContext.Add(testEntity); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne_SeparateOne.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } - - [Fact] - public async Task Should_insert_TestEntity_with_ComplexType() - { - var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - new BoundaryValueObject(2, 5)); - - await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } -} +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqliteBulkOperationExecutorTests; + +// ReSharper disable once InconsistentNaming +public class BulkInsertAsync : SchemaChangingIntegrationTestsBase +{ + private SqliteBulkOperationExecutor? _sut; + private SqliteBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); + + public BulkInsertAsync(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + } + + [Fact] + public async Task Should_throw_when_inserting_entities_without_creating_table_first() + { + ConfigureModel = builder => builder.Entity().HasNoKey(); + Configure = builder => builder.ConfigureWarnings(warningsBuilder => warningsBuilder.Ignore(RelationalEventId.PendingModelChangesWarning)); + + await SUT.Awaiting(sut => sut.BulkInsertAsync(new List(), new SqliteBulkInsertOptions())) + .Should().ThrowAsync().WithMessage("Error during bulk operation on table '\"CustomTempTable\"'. See inner exception for more details."); + } + + [Fact] + public async Task Should_insert_entities() + { + var testEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + + var testEntities = new[] { testEntity }; + + await SUT.BulkInsertAsync(testEntities, new SqliteBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1) + .And.Subject.First() + .Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }); + } + + [Fact] + public async Task Should_insert_private_property() + { + var testEntity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + testEntity.SetPrivateField(3); + + var testEntities = new[] { testEntity }; + + await SUT.BulkInsertAsync(testEntities, new SqliteBulkInsertOptions()); + + var loadedEntity = await AssertDbContext.TestEntities.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + loadedEntity!.GetPrivateField().Should().Be(3); + } + + [Fact] + public async Task Should_insert_shadow_properties() + { + var testEntity = new TestEntityWithShadowProperties { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866") }; + ActDbContext.Entry(testEntity).Property("ShadowStringProperty").CurrentValue = "value"; + ActDbContext.Entry(testEntity).Property("ShadowIntProperty").CurrentValue = 42; + + var testEntities = new[] { testEntity }; + + await SUT.BulkInsertAsync(testEntities, new SqliteBulkInsertOptions()); + + var loadedEntity = await AssertDbContext.TestEntitiesWithShadowProperties.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + AssertDbContext.Entry(loadedEntity!).Property("ShadowStringProperty").CurrentValue.Should().Be("value"); + AssertDbContext.Entry(loadedEntity!).Property("ShadowIntProperty").CurrentValue.Should().Be(42); + } + + [Fact] + public async Task Should_throw_because_sqlite_dont_support_null_for_NOT_NULL_despite_sql_default_value() + { + var testEntity = new TestEntityWithSqlDefaultValues { String = null! }; + var testEntities = new[] { testEntity }; + + await SUT.Awaiting(sut => sut.BulkInsertAsync(testEntities, new SqliteBulkInsertOptions())) + .Should().ThrowAsync() + .WithMessage("SQLite Error 19: 'NOT NULL constraint failed: TestEntitiesWithDefaultValues.String'."); + } + + [Fact] + public async Task Should_write_all_provided_column_values_as_is_despite_sql_default_value() + { + var testEntity = new TestEntityWithSqlDefaultValues + { + Id = Guid.Empty, + Int = 0, + String = null!, + NullableInt = null, + NullableString = null + }; + var testEntities = new[] { testEntity }; + + var options = new SqliteBulkInsertOptions + { + // we skip TestEntityWithSqlDefaultValues.String + PropertiesToInsert = IEntityPropertiesProvider.Include(e => new + { + e.Id, + e.Int, + e.NullableInt, + e.NullableString + }) + }; + + await SUT.BulkInsertAsync(testEntities, options); + + var loadedEntity = await AssertDbContext.TestEntitiesWithDefaultValues.FirstOrDefaultAsync(); + loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues + { + Id = Guid.Empty, // persisted as-is + Int = 0, // persisted as-is + NullableInt = null, // persisted as-is + String = "3", // DEFAULT value constraint + NullableString = null // persisted as-is + }); + } + + [Fact] + public async Task Should_throw_because_sqlite_dont_support_null_for_NOT_NULL_despite_dotnet_default_value() + { + var testEntity = new TestEntityWithDotnetDefaultValues { String = null! }; + var testEntities = new[] { testEntity }; + + await SUT.Awaiting(sut => sut.BulkInsertAsync(testEntities, new SqliteBulkInsertOptions())) + .Should().ThrowAsync() + .WithMessage("SQLite Error 19: 'NOT NULL constraint failed: TestEntitiesWithDotnetDefaultValues.String'."); + } + + [Fact] + public async Task Should_write_all_provided_column_values_as_is_despite_dotnet_default_value() + { + var testEntity = new TestEntityWithDotnetDefaultValues + { + Id = Guid.Empty, + Int = 0, + String = null!, + NullableInt = null, + NullableString = null + }; + var testEntities = new[] { testEntity }; + + var options = new SqliteBulkInsertOptions + { + // we skip TestEntityWithDefaultValues.String + PropertiesToInsert = IEntityPropertiesProvider.Include(e => new + { + e.Id, + e.Int, + e.NullableInt, + e.NullableString + }) + }; + + await SUT.BulkInsertAsync(testEntities, options); + + var loadedEntity = await AssertDbContext.TestEntitiesWithDotnetDefaultValues.FirstOrDefaultAsync(); + loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues + { + Id = Guid.Empty, // persisted as-is + Int = 0, // persisted as-is + NullableInt = null, // persisted as-is + String = "3", // DEFAULT value constraint + NullableString = null // persisted as-is + }); + } + + [Theory] + [InlineData(SqliteAutoIncrementBehavior.SetZeroToNull, 42, 42)] + [InlineData(SqliteAutoIncrementBehavior.KeepValueAsIs, 42, 42)] + [InlineData(SqliteAutoIncrementBehavior.SetZeroToNull, 0, 1)] // 1 because the DB is empty + [InlineData(SqliteAutoIncrementBehavior.KeepValueAsIs, 0, 0)] + public async Task Should_insert_0_to_auto_increment_column(SqliteAutoIncrementBehavior behavior, int id, int expectedId) + { + var testEntity = new TestEntityWithAutoIncrement { Id = id }; + var testEntities = new[] { testEntity }; + + var options = new SqliteBulkInsertOptions { AutoIncrementBehavior = behavior }; + await SUT.BulkInsertAsync(testEntities, options); + + var loadedEntity = await AssertDbContext.TestEntitiesWithAutoIncrement.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + loadedEntity!.Id.Should().Be(expectedId); + loadedEntity.Name.Should().BeNull(); + } + + [Fact] + public async Task Should_insert_specified_properties_only() + { + var testEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42, + PropertyWithBackingField = 7 + }; + testEntity.SetPrivateField(3); + var testEntities = new[] { testEntity }; + + await SUT.BulkInsertAsync(testEntities, + new SqliteBulkInsertOptions + { + PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) + }); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + RequiredName = "RequiredName", + Count = 42, + PropertyWithBackingField = 7 + }); + loadedEntity.GetPrivateField().Should().Be(3); + } + + [Fact] + public async Task Should_throw_if_required_inlined_owned_type_is_null() + { + var testEntity = new TestEntity_Owns_Inline + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + InlineEntity = null! + }; + var testEntities = new[] { testEntity }; + + await ActDbContext.Awaiting(ctx => ctx.BulkInsertIntoTempTableAsync(testEntities)) + .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_Inline_if_it_has_default_values_only() + { + var testEntity = new TestEntity_Owns_Inline + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + InlineEntity = new OwnedEntity() + }; + var testEntities = new[] { testEntity }; + + await SUT.BulkInsertAsync(testEntities, new SqliteBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_Inline.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity_Owns_Inline + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + InlineEntity = new OwnedEntity + { + IntColumn = 0, + StringColumn = null + } + }); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_Inline() + { + var testEntity = new TestEntity_Owns_Inline + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + InlineEntity = new OwnedEntity + { + IntColumn = 42, + StringColumn = "value" + } + }; + var testEntities = new[] { testEntity }; + + await SUT.BulkInsertAsync(testEntities, new SqliteBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_Inline.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity_Owns_Inline + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + InlineEntity = new OwnedEntity + { + IntColumn = 42, + StringColumn = "value" + } + }); + } + + [Fact] + public async Task Should_throw_if_separated_owned_type_uses_shadow_property_id_and_is_detached() + { + var testEntity = new TestEntity_Owns_SeparateOne + { + Id = new Guid("7C00ABFE-875B-4396-BE51-3E898647A264"), + SeparateEntity = new OwnedEntity + { + IntColumn = 42, + StringColumn = "value" + } + }; + + await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions())) + .Should().ThrowAsync() + .WithMessage("The entity type 'OwnedEntity' uses a shared type and the supplied entity is currently not being tracked. To start tracking this entity, call '.Reference().TargetEntry' or '.Collection().FindEntry()' on the owner entry."); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_SeparateOne() + { + var testEntity = new TestEntity_Owns_SeparateOne + { + Id = new Guid("7C00ABFE-875B-4396-BE51-3E898647A264"), + SeparateEntity = new OwnedEntity + { + IntColumn = 42, + StringColumn = "value" + } + }; + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(testEntity); + } + + [Fact] + public async Task Should_throw_if_separated_owned_types_uses_shadow_property_id_and_is_detached() + { + var testEntity = new TestEntity_Owns_SeparateMany + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntities = new List + { + new() + { + IntColumn = 42, + StringColumn = "value 1" + } + } + }; + + await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions())) + .Should().ThrowAsync() + .WithMessage("The entity type 'OwnedEntity' uses a shared type and the supplied entity is currently not being tracked. To start tracking this entity, call '.Reference().TargetEntry' or '.Collection().FindEntry()' on the owner entry."); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_SeparateMany() + { + var testEntity = new TestEntity_Owns_SeparateMany + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntities = new List + { + new() + { + IntColumn = 42, + StringColumn = "value 1" + } + } + }; + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateMany.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(testEntity); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_Inline_Inline() + { + var testEntity = new TestEntity_Owns_Inline_Inline + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + InlineEntity = new OwnedEntity_Owns_Inline + { + IntColumn = 42, + StringColumn = "value 1", + InlineEntity = new OwnedEntity + { + IntColumn = 43, + StringColumn = "value 2" + } + } + }; + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_Inline_Inline.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_Inline_SeparateMany() + { + var testEntity = new TestEntity_Owns_Inline_SeparateMany + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + InlineEntity = new OwnedEntity_Owns_SeparateMany + { + IntColumn = 42, + StringColumn = "value 1", + SeparateEntities = new List + { + new() + { + IntColumn = 43, + StringColumn = "value 2" + }, + new() + { + IntColumn = 44, + StringColumn = "value 3" + } + } + } + }; + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_Inline_SeparateMany.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_Inline_SeparateOne() + { + var testEntity = new TestEntity_Owns_Inline_SeparateOne + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + InlineEntity = new OwnedEntity_Owns_SeparateOne + { + IntColumn = 42, + StringColumn = "value 1", + SeparateEntity = new() + { + IntColumn = 43, + StringColumn = "value 2" + } + } + }; + + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] + { + testEntity + }, new SqliteBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_Inline_SeparateOne.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_SeparateMany_Inline() + { + var testEntity = new TestEntity_Owns_SeparateMany_Inline + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntities = new List + { + new() + { + IntColumn = 42, + StringColumn = "value 1", + InlineEntity = new OwnedEntity + { + IntColumn = 43, + StringColumn = "value 2" + } + }, + new() + { + IntColumn = 44, + StringColumn = "value 3", + InlineEntity = new OwnedEntity + { + IntColumn = 45, + StringColumn = "value 4" + } + } + } + }; + + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateMany_Inline.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } + + [Fact] + public async Task Should_throw_on_insert_of_TestEntity_Owns_SeparateMany_SeparateMany() + { + var testEntity = new TestEntity_Owns_SeparateMany_SeparateMany + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntities = new List + { + new() + { + IntColumn = 42, + StringColumn = "value 1", + SeparateEntities = new List + { + new() + { + IntColumn = 43, + StringColumn = "value 2" + }, + new() + { + IntColumn = 44, + StringColumn = "value 3" + } + } + }, + new() + { + IntColumn = 45, + StringColumn = "value 4", + SeparateEntities = new List + { + new() + { + IntColumn = 46, + StringColumn = "value 5" + }, + new() + { + IntColumn = 47, + StringColumn = "value 6" + } + } + } + } + }; + + ActDbContext.Add(testEntity); + + await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions())) + .Should().ThrowAsync().WithMessage("Non-inlined (i.e. with its own table) nested owned type 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany.SeparateEntities#OwnedEntity_Owns_SeparateMany.SeparateEntities' inside another owned type collection 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany.SeparateEntities' is not supported."); + } + + [Fact] + public async Task Should_throw_on_insert_of_TestEntity_Owns_SeparateMany_SeparateOne() + { + var testEntity = new TestEntity_Owns_SeparateMany_SeparateOne + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntities = new List + { + new() + { + IntColumn = 42, + StringColumn = "value 1", + SeparateEntity = new() + { + IntColumn = 43, + StringColumn = "value 2" + } + }, + new() + { + IntColumn = 45, + StringColumn = "value 4", + SeparateEntity = new() + { + IntColumn = 46, + StringColumn = "value 5" + } + } + } + }; + + ActDbContext.Add(testEntity); + + await SUT.Awaiting(sut => sut.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions())) + .Should().ThrowAsync().WithMessage("Non-inlined (i.e. with its own table) nested owned type 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne.SeparateEntities#OwnedEntity_Owns_SeparateOne.SeparateEntity' inside another owned type collection 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne.SeparateEntities' is not supported."); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_SeparateOne_Inline() + { + var testEntity = new TestEntity_Owns_SeparateOne_Inline + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntity = new() + { + IntColumn = 42, + StringColumn = "value 1", + InlineEntity = new OwnedEntity + { + IntColumn = 43, + StringColumn = "value 2" + } + } + }; + + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne_Inline.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_SeparateOne_SeparateMany() + { + var testEntity = new TestEntity_Owns_SeparateOne_SeparateMany + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntity = new() + { + IntColumn = 42, + StringColumn = "value 1", + SeparateEntities = new List + { + new() + { + IntColumn = 43, + StringColumn = "value 2" + }, + new() + { + IntColumn = 44, + StringColumn = "value 3" + } + } + } + }; + + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne_SeparateMany.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } + + [Fact] + public async Task Should_insert_TestEntity_Owns_SeparateOne_SeparateOne() + { + var testEntity = new TestEntity_Owns_SeparateOne_SeparateOne + { + Id = new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + SeparateEntity = new() + { + IntColumn = 42, + StringColumn = "value 1", + SeparateEntity = new() + { + IntColumn = 43, + StringColumn = "value 2" + } + } + }; + + ActDbContext.Add(testEntity); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_Own_SeparateOne_SeparateOne.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } + + [Fact] + public async Task Should_insert_TestEntity_with_ComplexType() + { + var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + new BoundaryValueObject(2, 5)); + + await SUT.BulkInsertAsync(new[] { testEntity }, new SqliteBulkInsertOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/BulkInsertOrUpdateAsync.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/BulkInsertOrUpdateAsync.cs index a4f9004c..ffccc8e2 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/BulkInsertOrUpdateAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/BulkInsertOrUpdateAsync.cs @@ -1,473 +1,473 @@ -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqliteBulkOperationExecutorTests; - -// ReSharper disable once InconsistentNaming -public class BulkInsertOrUpdateAsync : IntegrationTestsBase -{ - private SqliteBulkOperationExecutor? _sut; - - private SqliteBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); - - public BulkInsertOrUpdateAsync(ITestOutputHelper testOutputHelper, DbContextProviderFactoryFixture providerFactoryFixture) - : base(testOutputHelper, providerFactoryFixture) - { - } - - [Fact] - public async Task Should_throw_when_entity_has_no_key() - { - await SUT.Invoking(sut => sut.BulkInsertOrUpdateAsync(new List { new() }, new SqliteBulkInsertOrUpdateOptions())) - .Should().ThrowAsync() - .WithMessage("The entity 'Thinktecture.TestDatabaseContext.KeylessTestEntity' has no primary key. Please provide key properties to perform JOIN/match on."); - } - - [Fact] - public async Task Should_not_throw_if_entities_is_empty() - { - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new List(), new SqliteBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(0); - } - - [Fact] - public async Task Should_insert_column_with_converter() - { - var existingEntity = new TestEntity { Id = new Guid("79DA4171-C90B-4A5D-B0B5-D0A1E1BDF966"), RequiredName = "RequiredName" }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - existingEntity.ConvertibleClass = new ConvertibleClass(43); - var newEntity = new TestEntity - { - Id = new Guid("3DAEA618-B732-4BCA-A5A1-D1E075022DEC"), - RequiredName = "RequiredName", - ConvertibleClass = new ConvertibleClass(42) - }; - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, new SqliteBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(2); - - var entities = AssertDbContext.TestEntities.ToList(); - entities.Should().BeEquivalentTo(new[] - { - new TestEntity { Id = new Guid("79DA4171-C90B-4A5D-B0B5-D0A1E1BDF966"), RequiredName = "RequiredName", ConvertibleClass = new ConvertibleClass(43) }, - new TestEntity { Id = new Guid("3DAEA618-B732-4BCA-A5A1-D1E075022DEC"), RequiredName = "RequiredName", ConvertibleClass = new ConvertibleClass(42) } - }); - } - - [Fact] - public async Task Should_insert_entities() - { - var testEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - - await SUT.BulkInsertOrUpdateAsync(new[] { testEntity }, new SqliteBulkInsertOrUpdateOptions()); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1) - .And.Subject.First() - .Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }); - } - - [Fact] - public async Task Should_insert_private_property() - { - var existingEntity = new TestEntity { Id = new Guid("7C200656-E633-4F93-9F73-C5C7628196DC"), RequiredName = "RequiredName" }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - existingEntity.SetPrivateField(1); - - var newEntity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - newEntity.SetPrivateField(3); - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { newEntity, existingEntity }, new SqliteBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(2); - - var expectedExistingEntity = new TestEntity { Id = new Guid("7C200656-E633-4F93-9F73-C5C7628196DC"), RequiredName = "RequiredName" }; - expectedExistingEntity.SetPrivateField(1); - - var expectedNewEntity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - expectedNewEntity.SetPrivateField(3); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { expectedNewEntity, expectedExistingEntity }); - } - - [Fact] - public async Task Should_insert_shadow_properties() - { - var existingEntity = new TestEntityWithShadowProperties { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866") }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - ActDbContext.Entry(existingEntity).Property("ShadowStringProperty").CurrentValue = "value1"; - ActDbContext.Entry(existingEntity).Property("ShadowIntProperty").CurrentValue = 42; - - var newEntity = new TestEntityWithShadowProperties { Id = new Guid("884BB11B-088D-4BA5-B75D-C7F36B88378B") }; - ActDbContext.Entry(newEntity).Property("ShadowStringProperty").CurrentValue = "value2"; - ActDbContext.Entry(newEntity).Property("ShadowIntProperty").CurrentValue = 43; - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { newEntity, existingEntity }, new SqliteBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(2); - - var loadedEntities = await AssertDbContext.TestEntitiesWithShadowProperties.ToListAsync(); - loadedEntities.Should().HaveCount(2); - - var existingEntry = AssertDbContext.Entry(loadedEntities.Single(e => e.Id == new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"))); - existingEntry.Property("ShadowStringProperty").CurrentValue.Should().Be("value1"); - existingEntry.Property("ShadowIntProperty").CurrentValue.Should().Be(42); - - var newEntry = AssertDbContext.Entry(loadedEntities.Single(e => e.Id == new Guid("884BB11B-088D-4BA5-B75D-C7F36B88378B"))); - newEntry.Property("ShadowStringProperty").CurrentValue.Should().Be("value2"); - newEntry.Property("ShadowIntProperty").CurrentValue.Should().Be(43); - } - - [Fact] - public async Task Should_insert_and_update_entities_with_auto_increment_column_having_property_selector() - { - var existingEntity = new TestEntityWithAutoIncrement { Name = "original value" }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - existingEntity.Name = "Name"; - - var newEntity = new TestEntityWithAutoIncrement { Name = "New Entity" }; - var testEntities = new[] { existingEntity, newEntity }; - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(testEntities, new SqliteBulkInsertOrUpdateOptions - { - PropertiesToInsert = IEntityPropertiesProvider.Include(entity => entity.Name) - }); - - affectedRows.Should().Be(2); - - var loadedEntities = await AssertDbContext.TestEntitiesWithAutoIncrement.ToListAsync(); - loadedEntities.Should().HaveCount(2); - loadedEntities.Should().AllSatisfy(e => e.Id.Should().NotBe(0)); - loadedEntities.Should().BeEquivalentTo(new[] - { - existingEntity, - new TestEntityWithAutoIncrement - { - Id = loadedEntities.Single(e => e.Id != existingEntity.Id).Id, - Name = "New Entity" - } - }); - } - - [Fact] - public async Task Should_insert_specified_properties_only() - { - var testEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42, - PropertyWithBackingField = 7 - }; - testEntity.SetPrivateField(3); - var testEntities = new[] { testEntity }; - - await SUT.BulkInsertOrUpdateAsync(testEntities, - new SqliteBulkInsertOrUpdateOptions - { - PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) - }); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - RequiredName = "RequiredName", - Count = 42, - PropertyWithBackingField = 7 - }); - loadedEntity.GetPrivateField().Should().Be(3); - } - - [Fact] - public async Task Should_not_throw_if_key_property_is_not_present_in_PropertiesToUpdate() - { - var propertiesProvider = IEntityPropertiesProvider.Include(entity => new { entity.Name }); - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new List(), new SqliteBulkInsertOrUpdateOptions - { - PropertiesToUpdate = propertiesProvider - }); - - affectedRows.Should().Be(0); - } - - [Fact] - public async Task Should_update_entities() - { - var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - entity_1.Name = "Name"; - entity_1.Count = 1; - entity_2.Name = "OtherName"; - entity_2.Count = 2; - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity_1, entity_2 }, new SqliteBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(2); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); - } - - [Fact] - public async Task Should_update_entities_based_on_non_pk_property() - { - var keyProperties = IEntityPropertiesProvider.Include(e => e.Name); - - await SUT.Awaiting(sut => sut.BulkInsertOrUpdateAsync(new TestEntity[0], new SqliteBulkInsertOrUpdateOptions { KeyProperties = keyProperties })) - .Should().ThrowAsync().WithMessage("Error during bulk operation on table '\"TestEntities\"'. See inner exception for more details.") - .WithInnerException().WithMessage("SQLite Error 1: 'ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint'."); - } - - [Fact] - public async Task Should_update_provided_entity_only() - { - var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - entity_1.Name = "Name"; - entity_1.Count = 1; - entity_2.Name = "OtherName"; - entity_2.Count = 2; - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity_1 }, new SqliteBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(1); - - entity_2.Name = default; - entity_2.Count = default; - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); - } - - [Fact] - public async Task Should_write_not_nullable_structs_as_is_despite_sql_default_value() - { - var entity = new TestEntityWithSqlDefaultValues - { - Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), - Int = 1, - String = "value", - NullableInt = 2, - NullableString = "otherValue" - }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Int = 0; - entity.String = "other value"; - entity.NullableInt = null; - entity.NullableString = null; - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity }, new SqliteBulkInsertOrUpdateOptions()); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntitiesWithDefaultValues.FirstOrDefaultAsync(); - loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues - { - Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), - Int = 0, // persisted as-is - NullableInt = null, // persisted as-is - String = "other value", - NullableString = null // persisted as-is - }); - } - - [Fact] - public async Task Should_update_specified_properties_only() - { - var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "original value", RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Name = "Name"; // this would not be updated - entity.Count = 42; - entity.PropertyWithBackingField = 7; - entity.SetPrivateField(3); - - var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity }, - new SqliteBulkInsertOrUpdateOptions - { - PropertiesToUpdate = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) - }); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Count = 42, - PropertyWithBackingField = 7, - Name = "original value", - RequiredName = "RequiredName" - }); - loadedEntity.GetPrivateField().Should().Be(3); - } - - [Fact] - public async Task Should_skip_update_if_no_properties_to_update() - { - var existingEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - var newEntity = new TestEntity - { - Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), - Name = "new", - RequiredName = "RequiredName", - Count = 1 - }; - - existingEntity.Name = "changed"; - existingEntity.Count = 43; - - var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, - new SqliteBulkInsertOrUpdateOptions - { - PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()), - PropertiesToUpdate = IEntityPropertiesProvider.Empty - } - ); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] - { - new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }, - new TestEntity - { - Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), - Name = null, // is not a required property - RequiredName = "RequiredName", - Count = 1 - } - }); - } - - [Fact] - public async Task Should_skip_update_if_provided_key_properties_only() - { - var existingEntity = new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }; - ArrangeDbContext.Add(existingEntity); - await ArrangeDbContext.SaveChangesAsync(); - - var newEntity = new TestEntity - { - Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), - Name = "new", - RequiredName = "RequiredName", - Count = 1 - }; - - existingEntity.Name = "changed"; - existingEntity.Count = 43; - - var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, - new SqliteBulkInsertOrUpdateOptions - { - PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()), - PropertiesToUpdate = IEntityPropertiesProvider.Include(entity => entity.Id) - } - ); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] - { - new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42 - }, - new TestEntity - { - Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), - Name = null, // is not a required property - RequiredName = "RequiredName", - Count = 1 - } - }); - } - - [Fact] - public async Task Should_insert_and_update_TestEntity_with_ComplexType() - { - // Arrange - var testEntity_1 = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - new BoundaryValueObject(2, 5)); - - ArrangeDbContext.Add(testEntity_1); - await ArrangeDbContext.SaveChangesAsync(); - - // Act - testEntity_1.Boundary = new BoundaryValueObject(10, 20); - var testEntity_2 = new TestEntityWithComplexType(new Guid("67A9500B-CF51-4A39-8C89-F2EBF7EDE84D"), - new BoundaryValueObject(3, 4)); - - await SUT.BulkInsertOrUpdateAsync(new[] { testEntity_1, testEntity_2 }, new SqliteBulkInsertOrUpdateOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity_1, testEntity_2 }); - } -} +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqliteBulkOperationExecutorTests; + +// ReSharper disable once InconsistentNaming +public class BulkInsertOrUpdateAsync : IntegrationTestsBase +{ + private SqliteBulkOperationExecutor? _sut; + + private SqliteBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); + + public BulkInsertOrUpdateAsync(ITestOutputHelper testOutputHelper, DbContextProviderFactoryFixture providerFactoryFixture) + : base(testOutputHelper, providerFactoryFixture) + { + } + + [Fact] + public async Task Should_throw_when_entity_has_no_key() + { + await SUT.Invoking(sut => sut.BulkInsertOrUpdateAsync(new List { new() }, new SqliteBulkInsertOrUpdateOptions())) + .Should().ThrowAsync() + .WithMessage("The entity 'Thinktecture.TestDatabaseContext.KeylessTestEntity' has no primary key. Please provide key properties to perform JOIN/match on."); + } + + [Fact] + public async Task Should_not_throw_if_entities_is_empty() + { + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new List(), new SqliteBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(0); + } + + [Fact] + public async Task Should_insert_column_with_converter() + { + var existingEntity = new TestEntity { Id = new Guid("79DA4171-C90B-4A5D-B0B5-D0A1E1BDF966"), RequiredName = "RequiredName" }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + existingEntity.ConvertibleClass = new ConvertibleClass(43); + var newEntity = new TestEntity + { + Id = new Guid("3DAEA618-B732-4BCA-A5A1-D1E075022DEC"), + RequiredName = "RequiredName", + ConvertibleClass = new ConvertibleClass(42) + }; + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, new SqliteBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(2); + + var entities = AssertDbContext.TestEntities.ToList(); + entities.Should().BeEquivalentTo(new[] + { + new TestEntity { Id = new Guid("79DA4171-C90B-4A5D-B0B5-D0A1E1BDF966"), RequiredName = "RequiredName", ConvertibleClass = new ConvertibleClass(43) }, + new TestEntity { Id = new Guid("3DAEA618-B732-4BCA-A5A1-D1E075022DEC"), RequiredName = "RequiredName", ConvertibleClass = new ConvertibleClass(42) } + }); + } + + [Fact] + public async Task Should_insert_entities() + { + var testEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + + await SUT.BulkInsertOrUpdateAsync(new[] { testEntity }, new SqliteBulkInsertOrUpdateOptions()); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1) + .And.Subject.First() + .Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }); + } + + [Fact] + public async Task Should_insert_private_property() + { + var existingEntity = new TestEntity { Id = new Guid("7C200656-E633-4F93-9F73-C5C7628196DC"), RequiredName = "RequiredName" }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + existingEntity.SetPrivateField(1); + + var newEntity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + newEntity.SetPrivateField(3); + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { newEntity, existingEntity }, new SqliteBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(2); + + var expectedExistingEntity = new TestEntity { Id = new Guid("7C200656-E633-4F93-9F73-C5C7628196DC"), RequiredName = "RequiredName" }; + expectedExistingEntity.SetPrivateField(1); + + var expectedNewEntity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + expectedNewEntity.SetPrivateField(3); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { expectedNewEntity, expectedExistingEntity }); + } + + [Fact] + public async Task Should_insert_shadow_properties() + { + var existingEntity = new TestEntityWithShadowProperties { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866") }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + ActDbContext.Entry(existingEntity).Property("ShadowStringProperty").CurrentValue = "value1"; + ActDbContext.Entry(existingEntity).Property("ShadowIntProperty").CurrentValue = 42; + + var newEntity = new TestEntityWithShadowProperties { Id = new Guid("884BB11B-088D-4BA5-B75D-C7F36B88378B") }; + ActDbContext.Entry(newEntity).Property("ShadowStringProperty").CurrentValue = "value2"; + ActDbContext.Entry(newEntity).Property("ShadowIntProperty").CurrentValue = 43; + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { newEntity, existingEntity }, new SqliteBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(2); + + var loadedEntities = await AssertDbContext.TestEntitiesWithShadowProperties.ToListAsync(); + loadedEntities.Should().HaveCount(2); + + var existingEntry = AssertDbContext.Entry(loadedEntities.Single(e => e.Id == new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"))); + existingEntry.Property("ShadowStringProperty").CurrentValue.Should().Be("value1"); + existingEntry.Property("ShadowIntProperty").CurrentValue.Should().Be(42); + + var newEntry = AssertDbContext.Entry(loadedEntities.Single(e => e.Id == new Guid("884BB11B-088D-4BA5-B75D-C7F36B88378B"))); + newEntry.Property("ShadowStringProperty").CurrentValue.Should().Be("value2"); + newEntry.Property("ShadowIntProperty").CurrentValue.Should().Be(43); + } + + [Fact] + public async Task Should_insert_and_update_entities_with_auto_increment_column_having_property_selector() + { + var existingEntity = new TestEntityWithAutoIncrement { Name = "original value" }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + existingEntity.Name = "Name"; + + var newEntity = new TestEntityWithAutoIncrement { Name = "New Entity" }; + var testEntities = new[] { existingEntity, newEntity }; + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(testEntities, new SqliteBulkInsertOrUpdateOptions + { + PropertiesToInsert = IEntityPropertiesProvider.Include(entity => entity.Name) + }); + + affectedRows.Should().Be(2); + + var loadedEntities = await AssertDbContext.TestEntitiesWithAutoIncrement.ToListAsync(); + loadedEntities.Should().HaveCount(2); + loadedEntities.Should().AllSatisfy(e => e.Id.Should().NotBe(0)); + loadedEntities.Should().BeEquivalentTo(new[] + { + existingEntity, + new TestEntityWithAutoIncrement + { + Id = loadedEntities.Single(e => e.Id != existingEntity.Id).Id, + Name = "New Entity" + } + }); + } + + [Fact] + public async Task Should_insert_specified_properties_only() + { + var testEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42, + PropertyWithBackingField = 7 + }; + testEntity.SetPrivateField(3); + var testEntities = new[] { testEntity }; + + await SUT.BulkInsertOrUpdateAsync(testEntities, + new SqliteBulkInsertOrUpdateOptions + { + PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) + }); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + RequiredName = "RequiredName", + Count = 42, + PropertyWithBackingField = 7 + }); + loadedEntity.GetPrivateField().Should().Be(3); + } + + [Fact] + public async Task Should_not_throw_if_key_property_is_not_present_in_PropertiesToUpdate() + { + var propertiesProvider = IEntityPropertiesProvider.Include(entity => new { entity.Name }); + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new List(), new SqliteBulkInsertOrUpdateOptions + { + PropertiesToUpdate = propertiesProvider + }); + + affectedRows.Should().Be(0); + } + + [Fact] + public async Task Should_update_entities() + { + var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + entity_1.Name = "Name"; + entity_1.Count = 1; + entity_2.Name = "OtherName"; + entity_2.Count = 2; + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity_1, entity_2 }, new SqliteBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(2); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); + } + + [Fact] + public async Task Should_update_entities_based_on_non_pk_property() + { + var keyProperties = IEntityPropertiesProvider.Include(e => e.Name); + + await SUT.Awaiting(sut => sut.BulkInsertOrUpdateAsync(new TestEntity[0], new SqliteBulkInsertOrUpdateOptions { KeyProperties = keyProperties })) + .Should().ThrowAsync().WithMessage("Error during bulk operation on table '\"TestEntities\"'. See inner exception for more details.") + .WithInnerException().WithMessage("SQLite Error 1: 'ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint'."); + } + + [Fact] + public async Task Should_update_provided_entity_only() + { + var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + entity_1.Name = "Name"; + entity_1.Count = 1; + entity_2.Name = "OtherName"; + entity_2.Count = 2; + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity_1 }, new SqliteBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(1); + + entity_2.Name = default; + entity_2.Count = default; + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); + } + + [Fact] + public async Task Should_write_not_nullable_structs_as_is_despite_sql_default_value() + { + var entity = new TestEntityWithSqlDefaultValues + { + Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), + Int = 1, + String = "value", + NullableInt = 2, + NullableString = "otherValue" + }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Int = 0; + entity.String = "other value"; + entity.NullableInt = null; + entity.NullableString = null; + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity }, new SqliteBulkInsertOrUpdateOptions()); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntitiesWithDefaultValues.FirstOrDefaultAsync(); + loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues + { + Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), + Int = 0, // persisted as-is + NullableInt = null, // persisted as-is + String = "other value", + NullableString = null // persisted as-is + }); + } + + [Fact] + public async Task Should_update_specified_properties_only() + { + var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "original value", RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Name = "Name"; // this would not be updated + entity.Count = 42; + entity.PropertyWithBackingField = 7; + entity.SetPrivateField(3); + + var affectedRows = await SUT.BulkInsertOrUpdateAsync(new[] { entity }, + new SqliteBulkInsertOrUpdateOptions + { + PropertiesToUpdate = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) + }); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Count = 42, + PropertyWithBackingField = 7, + Name = "original value", + RequiredName = "RequiredName" + }); + loadedEntity.GetPrivateField().Should().Be(3); + } + + [Fact] + public async Task Should_skip_update_if_no_properties_to_update() + { + var existingEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + var newEntity = new TestEntity + { + Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), + Name = "new", + RequiredName = "RequiredName", + Count = 1 + }; + + existingEntity.Name = "changed"; + existingEntity.Count = 43; + + var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, + new SqliteBulkInsertOrUpdateOptions + { + PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()), + PropertiesToUpdate = IEntityPropertiesProvider.Empty + } + ); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] + { + new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }, + new TestEntity + { + Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), + Name = null, // is not a required property + RequiredName = "RequiredName", + Count = 1 + } + }); + } + + [Fact] + public async Task Should_skip_update_if_provided_key_properties_only() + { + var existingEntity = new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }; + ArrangeDbContext.Add(existingEntity); + await ArrangeDbContext.SaveChangesAsync(); + + var newEntity = new TestEntity + { + Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), + Name = "new", + RequiredName = "RequiredName", + Count = 1 + }; + + existingEntity.Name = "changed"; + existingEntity.Count = 43; + + var affectedRows = await ActDbContext.BulkInsertOrUpdateAsync(new[] { existingEntity, newEntity }, + new SqliteBulkInsertOrUpdateOptions + { + PropertiesToInsert = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()), + PropertiesToUpdate = IEntityPropertiesProvider.Include(entity => entity.Id) + } + ); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] + { + new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42 + }, + new TestEntity + { + Id = new Guid("3AA6D70D-C619-4EB5-9819-8030506EA637"), + Name = null, // is not a required property + RequiredName = "RequiredName", + Count = 1 + } + }); + } + + [Fact] + public async Task Should_insert_and_update_TestEntity_with_ComplexType() + { + // Arrange + var testEntity_1 = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + new BoundaryValueObject(2, 5)); + + ArrangeDbContext.Add(testEntity_1); + await ArrangeDbContext.SaveChangesAsync(); + + // Act + testEntity_1.Boundary = new BoundaryValueObject(10, 20); + var testEntity_2 = new TestEntityWithComplexType(new Guid("67A9500B-CF51-4A39-8C89-F2EBF7EDE84D"), + new BoundaryValueObject(3, 4)); + + await SUT.BulkInsertOrUpdateAsync(new[] { testEntity_1, testEntity_2 }, new SqliteBulkInsertOrUpdateOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity_1, testEntity_2 }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/BulkUpdateAsync.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/BulkUpdateAsync.cs index 509dd032..908b157d 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/BulkUpdateAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/BulkUpdateAsync.cs @@ -1,377 +1,377 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Thinktecture.TestDatabaseContext; - -// ReSharper disable InconsistentNaming - -namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqliteBulkOperationExecutorTests; - -// ReSharper disable once InconsistentNaming -public class BulkUpdateAsync : IntegrationTestsBase -{ - private SqliteBulkOperationExecutor? _sut; - - private SqliteBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); - - public BulkUpdateAsync(ITestOutputHelper testOutputHelper, DbContextProviderFactoryFixture providerFactoryFixture) - : base(testOutputHelper, providerFactoryFixture) - { - } - - [Fact] - public async Task Should_not_throw_if_entities_is_empty() - { - var affectedRows = await SUT.BulkUpdateAsync(new List(), new SqliteBulkUpdateOptions()); - affectedRows.Should().Be(0); - } - - [Fact] - public async Task Should_not_throw_if_key_property_is_not_part_of_PropertiesToUpdate() - { - var propertiesProvider = IEntityPropertiesProvider.Include(entity => new { entity.Name }); - - var affectedRows = await SUT.BulkUpdateAsync(new List(), new SqliteBulkUpdateOptions - { - PropertiesToUpdate = propertiesProvider - }); - - affectedRows.Should().Be(0); - } - - [Fact] - public async Task Should_update_column_with_converter() - { - var entity = new TestEntity { RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.ConvertibleClass = new ConvertibleClass(43); - - var affectedRows = await SUT.BulkUpdateAsync(new List { entity }, new SqliteBulkUpdateOptions()); - - affectedRows.Should().Be(1); - - var loadedEntity = AssertDbContext.TestEntities.Single(); - loadedEntity.ConvertibleClass.Should().NotBeNull(); - loadedEntity.ConvertibleClass!.Key.Should().Be(43); - } - - [Fact] - public async Task Should_return_0_if_no_rows_match() - { - var entity = new TestEntity { Id = new Guid("5B9587A3-2312-43DF-9681-38EC22AD8606"), RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - var affectedRows = await SUT.BulkUpdateAsync(new List { new() { Id = new Guid("506E664A-9ADC-4221-9577-71DCFD73DE64") } }, new SqliteBulkUpdateOptions()); - - affectedRows.Should().Be(0); - } - - [Fact] - public async Task Should_update_entities() - { - var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - entity_1.Name = "Name"; - entity_1.Count = 1; - entity_2.Name = "OtherName"; - entity_2.Count = 2; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity_1, entity_2 }, new SqliteBulkUpdateOptions()); - - affectedRows.Should().Be(2); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); - } - - [Fact] - public async Task Should_update_entities_based_on_non_pk_property() - { - var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "value", RequiredName = "RequiredName" }; - var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), Name = null, RequiredName = "RequiredName" }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - entity_1.Name = null; - entity_1.Count = 1; - entity_2.Name = "value"; - entity_2.Count = 2; - - var properties = IEntityPropertiesProvider.Include(e => new { e.Name, e.Count }); - var keyProperties = IEntityPropertiesProvider.Include(e => e.Name); - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity_1, entity_2 }, new SqliteBulkUpdateOptions { PropertiesToUpdate = properties, KeyProperties = keyProperties }); - - affectedRows.Should().Be(2); - - entity_1.Name = "value"; - entity_2.Name = null; - - entity_1.Count = 2; - entity_2.Count = 1; - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); - } - - [Fact] - public async Task Should_update_provided_entity_only() - { - var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; - ArrangeDbContext.AddRange(entity_1, entity_2); - await ArrangeDbContext.SaveChangesAsync(); - - entity_1.Name = "Name"; - entity_1.Count = 1; - entity_2.Name = "OtherName"; - entity_2.Count = 2; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity_1 }, new SqliteBulkUpdateOptions()); - - affectedRows.Should().Be(1); - - entity_2.Name = default; - entity_2.Count = default; - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); - } - - [Fact] - public async Task Should_update_private_property() - { - var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.SetPrivateField(1); - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqliteBulkUpdateOptions()); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntities.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - loadedEntity!.GetPrivateField().Should().Be(1); - } - - [Fact] - public async Task Should_update_shadow_properties() - { - var entity = new TestEntityWithShadowProperties { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866") }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - ActDbContext.Entry(entity).Property("ShadowStringProperty").CurrentValue = "value"; - ActDbContext.Entry(entity).Property("ShadowIntProperty").CurrentValue = 42; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqliteBulkUpdateOptions()); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntitiesWithShadowProperties.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - AssertDbContext.Entry(loadedEntity!).Property("ShadowStringProperty").CurrentValue.Should().Be("value"); - AssertDbContext.Entry(loadedEntity!).Property("ShadowIntProperty").CurrentValue.Should().Be(42); - } - - [Fact] - public async Task Should_write_not_nullable_structs_as_is_despite_sql_default_value() - { - var entity = new TestEntityWithSqlDefaultValues - { - Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), - Int = 1, - String = "value", - NullableInt = 2, - NullableString = "otherValue" - }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Int = 0; - entity.String = "other value"; - entity.NullableInt = null; - entity.NullableString = null; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqliteBulkUpdateOptions()); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntitiesWithDefaultValues.FirstOrDefaultAsync(); - loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues - { - Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), - Int = 0, // persisted as-is - NullableInt = null, // persisted as-is - String = "other value", - NullableString = null // persisted as-is - }); - } - - [Fact] - public async Task Should_update_specified_properties_only() - { - var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "original value", RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Name = "Name"; // this would not be updated - entity.Count = 42; - entity.PropertyWithBackingField = 7; - entity.SetPrivateField(3); - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, - new SqliteBulkUpdateOptions - { - PropertiesToUpdate = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) - }); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Count = 42, - PropertyWithBackingField = 7, - Name = "original value", - RequiredName = "RequiredName" - }); - loadedEntity.GetPrivateField().Should().Be(3); - } - - [Fact] - public async Task Should_update_entity_without_required_fields_if_excluded() - { - var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Name = "Name"; // this would not be updated - entity.RequiredName = null!; // this would not be updated - entity.Count = 42; - - var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity }, - new SqliteBulkUpdateOptions - { - PropertiesToUpdate = IEntityPropertiesProvider.Include(e => e.Count) - }); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Should().BeEquivalentTo(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - Count = 42, - RequiredName = "RequiredName" - }); - } - - [Fact] - public async Task Should_update_entity_with_auto_increment() - { - var entity = new TestEntityWithAutoIncrement { Name = "original value" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Name = "Name"; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, - new SqliteBulkUpdateOptions()); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntitiesWithAutoIncrement.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Id.Should().NotBe(0); - loadedEntity.Should().BeEquivalentTo(new TestEntityWithAutoIncrement - { - Id = loadedEntity.Id, - Name = "Name" - }); - } - - [Fact] - public async Task Should_update_entity_with_auto_increment_having_custom_property_selector() - { - var entity = new TestEntityWithAutoIncrement { Name = "original value" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Name = "Name"; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, - new SqliteBulkUpdateOptions - { - KeyProperties = IEntityPropertiesProvider.Include(e => e.Id), - PropertiesToUpdate = IEntityPropertiesProvider.Include(e => new - { - e.Id, - e.Name - }) - }); - - affectedRows.Should().Be(1); - - var loadedEntities = await AssertDbContext.TestEntitiesWithAutoIncrement.ToListAsync(); - loadedEntities.Should().HaveCount(1); - var loadedEntity = loadedEntities[0]; - loadedEntity.Id.Should().NotBe(0); - loadedEntity.Should().BeEquivalentTo(new TestEntityWithAutoIncrement - { - Id = entity.Id, - Name = "Name" - }); - } - - [Fact] - public async Task Should_update_property_of_base_class() - { - var entity = new TestEntityWithBaseClass { Id = new Guid("3D3AECE7-3B5A-48C6-9C8C-CAB7FFE2120D"), Name = "Initial" }; - ArrangeDbContext.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - entity.Name = "changed"; - - var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqliteBulkUpdateOptions - { - PropertiesToUpdate = IEntityPropertiesProvider.Include(e => e.Name) - }); - - affectedRows.Should().Be(1); - - var loadedEntity = await AssertDbContext.TestEntitiesWithBaseClass.FirstOrDefaultAsync(); - loadedEntity.Should().NotBeNull(); - loadedEntity!.Name.Should().Be("changed"); - } - - [Fact] - public async Task Should_insert_and_update_TestEntity_with_ComplexType() - { - // Arrange - var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - new BoundaryValueObject(2, 5)); - - ArrangeDbContext.Add(testEntity); - await ArrangeDbContext.SaveChangesAsync(); - - // Act - testEntity.Boundary = new BoundaryValueObject(10, 20); - - await SUT.BulkUpdateAsync(new[] { testEntity }, new SqliteBulkUpdateOptions()); - - var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; +using Thinktecture.TestDatabaseContext; + +// ReSharper disable InconsistentNaming + +namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqliteBulkOperationExecutorTests; + +// ReSharper disable once InconsistentNaming +public class BulkUpdateAsync : IntegrationTestsBase +{ + private SqliteBulkOperationExecutor? _sut; + + private SqliteBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); + + public BulkUpdateAsync(ITestOutputHelper testOutputHelper, DbContextProviderFactoryFixture providerFactoryFixture) + : base(testOutputHelper, providerFactoryFixture) + { + } + + [Fact] + public async Task Should_not_throw_if_entities_is_empty() + { + var affectedRows = await SUT.BulkUpdateAsync(new List(), new SqliteBulkUpdateOptions()); + affectedRows.Should().Be(0); + } + + [Fact] + public async Task Should_not_throw_if_key_property_is_not_part_of_PropertiesToUpdate() + { + var propertiesProvider = IEntityPropertiesProvider.Include(entity => new { entity.Name }); + + var affectedRows = await SUT.BulkUpdateAsync(new List(), new SqliteBulkUpdateOptions + { + PropertiesToUpdate = propertiesProvider + }); + + affectedRows.Should().Be(0); + } + + [Fact] + public async Task Should_update_column_with_converter() + { + var entity = new TestEntity { RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.ConvertibleClass = new ConvertibleClass(43); + + var affectedRows = await SUT.BulkUpdateAsync(new List { entity }, new SqliteBulkUpdateOptions()); + + affectedRows.Should().Be(1); + + var loadedEntity = AssertDbContext.TestEntities.Single(); + loadedEntity.ConvertibleClass.Should().NotBeNull(); + loadedEntity.ConvertibleClass!.Key.Should().Be(43); + } + + [Fact] + public async Task Should_return_0_if_no_rows_match() + { + var entity = new TestEntity { Id = new Guid("5B9587A3-2312-43DF-9681-38EC22AD8606"), RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + var affectedRows = await SUT.BulkUpdateAsync(new List { new() { Id = new Guid("506E664A-9ADC-4221-9577-71DCFD73DE64") } }, new SqliteBulkUpdateOptions()); + + affectedRows.Should().Be(0); + } + + [Fact] + public async Task Should_update_entities() + { + var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + entity_1.Name = "Name"; + entity_1.Count = 1; + entity_2.Name = "OtherName"; + entity_2.Count = 2; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity_1, entity_2 }, new SqliteBulkUpdateOptions()); + + affectedRows.Should().Be(2); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); + } + + [Fact] + public async Task Should_update_entities_based_on_non_pk_property() + { + var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "value", RequiredName = "RequiredName" }; + var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), Name = null, RequiredName = "RequiredName" }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + entity_1.Name = null; + entity_1.Count = 1; + entity_2.Name = "value"; + entity_2.Count = 2; + + var properties = IEntityPropertiesProvider.Include(e => new { e.Name, e.Count }); + var keyProperties = IEntityPropertiesProvider.Include(e => e.Name); + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity_1, entity_2 }, new SqliteBulkUpdateOptions { PropertiesToUpdate = properties, KeyProperties = keyProperties }); + + affectedRows.Should().Be(2); + + entity_1.Name = "value"; + entity_2.Name = null; + + entity_1.Count = 2; + entity_2.Count = 1; + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); + } + + [Fact] + public async Task Should_update_provided_entity_only() + { + var entity_1 = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + var entity_2 = new TestEntity { Id = new Guid("8AF163D7-D316-4B2D-A62F-6326A80C8BEE"), RequiredName = "RequiredName" }; + ArrangeDbContext.AddRange(entity_1, entity_2); + await ArrangeDbContext.SaveChangesAsync(); + + entity_1.Name = "Name"; + entity_1.Count = 1; + entity_2.Name = "OtherName"; + entity_2.Count = 2; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity_1 }, new SqliteBulkUpdateOptions()); + + affectedRows.Should().Be(1); + + entity_2.Name = default; + entity_2.Count = default; + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { entity_1, entity_2 }); + } + + [Fact] + public async Task Should_update_private_property() + { + var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.SetPrivateField(1); + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqliteBulkUpdateOptions()); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntities.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + loadedEntity!.GetPrivateField().Should().Be(1); + } + + [Fact] + public async Task Should_update_shadow_properties() + { + var entity = new TestEntityWithShadowProperties { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866") }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + ActDbContext.Entry(entity).Property("ShadowStringProperty").CurrentValue = "value"; + ActDbContext.Entry(entity).Property("ShadowIntProperty").CurrentValue = 42; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqliteBulkUpdateOptions()); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntitiesWithShadowProperties.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + AssertDbContext.Entry(loadedEntity!).Property("ShadowStringProperty").CurrentValue.Should().Be("value"); + AssertDbContext.Entry(loadedEntity!).Property("ShadowIntProperty").CurrentValue.Should().Be(42); + } + + [Fact] + public async Task Should_write_not_nullable_structs_as_is_despite_sql_default_value() + { + var entity = new TestEntityWithSqlDefaultValues + { + Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), + Int = 1, + String = "value", + NullableInt = 2, + NullableString = "otherValue" + }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Int = 0; + entity.String = "other value"; + entity.NullableInt = null; + entity.NullableString = null; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqliteBulkUpdateOptions()); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntitiesWithDefaultValues.FirstOrDefaultAsync(); + loadedEntity.Should().BeEquivalentTo(new TestEntityWithSqlDefaultValues + { + Id = new Guid("53A5EC9B-BB8D-4B9D-9136-68C011934B63"), + Int = 0, // persisted as-is + NullableInt = null, // persisted as-is + String = "other value", + NullableString = null // persisted as-is + }); + } + + [Fact] + public async Task Should_update_specified_properties_only() + { + var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), Name = "original value", RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Name = "Name"; // this would not be updated + entity.Count = 42; + entity.PropertyWithBackingField = 7; + entity.SetPrivateField(3); + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, + new SqliteBulkUpdateOptions + { + PropertiesToUpdate = IEntityPropertiesProvider.Include(TestEntity.GetRequiredProperties()) + }); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Count = 42, + PropertyWithBackingField = 7, + Name = "original value", + RequiredName = "RequiredName" + }); + loadedEntity.GetPrivateField().Should().Be(3); + } + + [Fact] + public async Task Should_update_entity_without_required_fields_if_excluded() + { + var entity = new TestEntity { Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), RequiredName = "RequiredName" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Name = "Name"; // this would not be updated + entity.RequiredName = null!; // this would not be updated + entity.Count = 42; + + var affectedRows = await ActDbContext.BulkUpdateAsync(new[] { entity }, + new SqliteBulkUpdateOptions + { + PropertiesToUpdate = IEntityPropertiesProvider.Include(e => e.Count) + }); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Should().BeEquivalentTo(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + Count = 42, + RequiredName = "RequiredName" + }); + } + + [Fact] + public async Task Should_update_entity_with_auto_increment() + { + var entity = new TestEntityWithAutoIncrement { Name = "original value" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Name = "Name"; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, + new SqliteBulkUpdateOptions()); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntitiesWithAutoIncrement.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Id.Should().NotBe(0); + loadedEntity.Should().BeEquivalentTo(new TestEntityWithAutoIncrement + { + Id = loadedEntity.Id, + Name = "Name" + }); + } + + [Fact] + public async Task Should_update_entity_with_auto_increment_having_custom_property_selector() + { + var entity = new TestEntityWithAutoIncrement { Name = "original value" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Name = "Name"; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, + new SqliteBulkUpdateOptions + { + KeyProperties = IEntityPropertiesProvider.Include(e => e.Id), + PropertiesToUpdate = IEntityPropertiesProvider.Include(e => new + { + e.Id, + e.Name + }) + }); + + affectedRows.Should().Be(1); + + var loadedEntities = await AssertDbContext.TestEntitiesWithAutoIncrement.ToListAsync(); + loadedEntities.Should().HaveCount(1); + var loadedEntity = loadedEntities[0]; + loadedEntity.Id.Should().NotBe(0); + loadedEntity.Should().BeEquivalentTo(new TestEntityWithAutoIncrement + { + Id = entity.Id, + Name = "Name" + }); + } + + [Fact] + public async Task Should_update_property_of_base_class() + { + var entity = new TestEntityWithBaseClass { Id = new Guid("3D3AECE7-3B5A-48C6-9C8C-CAB7FFE2120D"), Name = "Initial" }; + ArrangeDbContext.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + entity.Name = "changed"; + + var affectedRows = await SUT.BulkUpdateAsync(new[] { entity }, new SqliteBulkUpdateOptions + { + PropertiesToUpdate = IEntityPropertiesProvider.Include(e => e.Name) + }); + + affectedRows.Should().Be(1); + + var loadedEntity = await AssertDbContext.TestEntitiesWithBaseClass.FirstOrDefaultAsync(); + loadedEntity.Should().NotBeNull(); + loadedEntity!.Name.Should().Be("changed"); + } + + [Fact] + public async Task Should_insert_and_update_TestEntity_with_ComplexType() + { + // Arrange + var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + new BoundaryValueObject(2, 5)); + + ArrangeDbContext.Add(testEntity); + await ArrangeDbContext.SaveChangesAsync(); + + // Act + testEntity.Boundary = new BoundaryValueObject(10, 20); + + await SUT.BulkUpdateAsync(new[] { testEntity }, new SqliteBulkUpdateOptions()); + + var loadedEntities = await AssertDbContext.TestEntities_with_ComplexType.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/TruncateTableAsync.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/TruncateTableAsync.cs index 27cded26..89ee63b0 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/TruncateTableAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/BulkOperations/SqliteBulkOperationExecutorTests/TruncateTableAsync.cs @@ -1,40 +1,40 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqliteBulkOperationExecutorTests; - -// ReSharper disable once InconsistentNaming -public class TruncateTableAsync : IntegrationTestsBase -{ - private SqliteBulkOperationExecutor? _sut; - private SqliteBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); - - public TruncateTableAsync(ITestOutputHelper testOutputHelper, DbContextProviderFactoryFixture providerFactoryFixture) - : base(testOutputHelper, providerFactoryFixture) - { - } - - [Fact] - public async Task Should_not_throw_if_table_is_empty() - { - await SUT.Awaiting(sut => sut.TruncateTableAsync()) - .Should().NotThrowAsync(); - } - - [Fact] - public async Task Should_delete_entities() - { - ArrangeDbContext.Add(new TestEntity - { - Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), - RequiredName = "RequiredName", - Count = 42 - }); - await ArrangeDbContext.SaveChangesAsync(); - - await SUT.TruncateTableAsync(); - - var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); - loadedEntities.Should().BeEmpty(); - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.BulkOperations.SqliteBulkOperationExecutorTests; + +// ReSharper disable once InconsistentNaming +public class TruncateTableAsync : IntegrationTestsBase +{ + private SqliteBulkOperationExecutor? _sut; + private SqliteBulkOperationExecutor SUT => _sut ??= ActDbContext.GetService(); + + public TruncateTableAsync(ITestOutputHelper testOutputHelper, DbContextProviderFactoryFixture providerFactoryFixture) + : base(testOutputHelper, providerFactoryFixture) + { + } + + [Fact] + public async Task Should_not_throw_if_table_is_empty() + { + await SUT.Awaiting(sut => sut.TruncateTableAsync()) + .Should().NotThrowAsync(); + } + + [Fact] + public async Task Should_delete_entities() + { + ArrangeDbContext.Add(new TestEntity + { + Id = new Guid("40B5CA93-5C02-48AD-B8A1-12BC13313866"), + RequiredName = "RequiredName", + Count = 42 + }); + await ArrangeDbContext.SaveChangesAsync(); + + await SUT.TruncateTableAsync(); + + var loadedEntities = await AssertDbContext.TestEntities.ToListAsync(); + loadedEntities.Should().BeEmpty(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/TempTables/SqliteTempTableCreatorTests/CreateTempTableAsync.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/TempTables/SqliteTempTableCreatorTests/CreateTempTableAsync.cs index bea650d8..fb0a3b0f 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/TempTables/SqliteTempTableCreatorTests/CreateTempTableAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/EntityFrameworkCore/TempTables/SqliteTempTableCreatorTests/CreateTempTableAsync.cs @@ -1,739 +1,739 @@ -using System.Data; -using System.Text; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.ObjectPool; -using Thinktecture.EntityFrameworkCore.BulkOperations; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.EntityFrameworkCore.TempTables.SqliteTempTableCreatorTests; - -// ReSharper disable once InconsistentNaming -public class CreateTempTableAsync : SchemaChangingIntegrationTestsBase -{ - private readonly ISqlGenerationHelper _sqlGenerationHelperMock; - private readonly TempTableCreationOptions _optionsWithNonUniqueNameAndNoPrimaryKey; - - private SqliteTempTableCreator? _sut; - - private SqliteTempTableCreator SUT => _sut ??= new SqliteTempTableCreator(ActDbContext.GetService(), - ActDbContext.GetService>(), - _sqlGenerationHelperMock, - ActDbContext.GetService(), - ActDbContext.GetService>(), - new TempTableStatementCache()); - - public CreateTempTableAsync(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - _sqlGenerationHelperMock = Substitute.For(); - _sqlGenerationHelperMock.DelimitIdentifier(Arg.Any(), Arg.Any()) - .Returns(x => x[1] == null ? $"\"{x[0]}\"" : $"\"{x[1]}\".\"{x[0]}\""); - _sqlGenerationHelperMock.DelimitIdentifier(Arg.Any()) - .Returns(x => $"\"{x[0]}\""); - - _optionsWithNonUniqueNameAndNoPrimaryKey = new TempTableCreationOptions { TableNameProvider = DefaultTempTableNameProvider.Instance, PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }; - } - - [Fact] - public async Task Should_create_temp_table_for_keyless_entity() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns().ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); - } - - [Fact] - public async Task Should_delete_temp_table_on_dispose_if_DropTableOnDispose_is_true() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - _optionsWithNonUniqueNameAndNoPrimaryKey.DropTableOnDispose = true; - - // ReSharper disable once UseAwaitUsing - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) - { - } - - AssertDbContext.GetTempTableColumns().ToList() - .Should().HaveCount(0); - } - - [Fact] - public async Task Should_delete_temp_table_on_disposeAsync_if_DropTableOnDispose_is_true() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - _optionsWithNonUniqueNameAndNoPrimaryKey.DropTableOnDispose = true; - - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) - { - } - - AssertDbContext.GetTempTableColumns().ToList() - .Should().HaveCount(0); - } - - [Fact] - public async Task Should_not_delete_temp_table_on_dispose_if_DropTableOnDispose_is_false() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - _optionsWithNonUniqueNameAndNoPrimaryKey.DropTableOnDispose = false; - - // ReSharper disable once UseAwaitUsing - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) - { - } - - var columns = AssertDbContext.GetTempTableColumns().ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); - } - - [Fact] - public async Task Should_not_delete_temp_table_on_disposeAsync_if_DropTableOnDispose_is_false() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - _optionsWithNonUniqueNameAndNoPrimaryKey.DropTableOnDispose = false; - - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) - { - } - - var columns = AssertDbContext.GetTempTableColumns().ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); - } - - [Fact] - public async Task Should_create_temp_table_with_reusable_name() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance }; - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); - - var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); - } - - [Fact] - public async Task Should_reuse_name_after_it_is_freed() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance }; - - // ReSharper disable once RedundantArgumentDefaultValue - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) - { - } - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); - - var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); - } - - [Fact] - public async Task Should_reuse_name_after_it_is_freed_although_previously_not_dropped() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - var options = new TempTableCreationOptions - { - TableNameProvider = ReusingTempTableNameProvider.Instance, - DropTableOnDispose = false - }; - - // ReSharper disable once RedundantArgumentDefaultValue - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) - { - } - - options.TruncateTableIfExists = true; - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); - - var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); - - AssertDbContext.GetTempTableColumns("#CustomTempTable_2").ToList() - .Should().HaveCount(0); - } - - [Fact] - public async Task Should_not_reuse_name_before_it_is_freed() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance }; - - // ReSharper disable once RedundantArgumentDefaultValue - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) - { - await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); - } - - var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_2").ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); - } - - [Fact] - public async Task Should_reuse_name_in_sorted_order() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance }; - - // #CustomTempTable_1 - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) - { - // #CustomTempTable_2 - await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) - { - } - } - - // #CustomTempTable_1 - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); - - var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); - ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); - } - - [Fact] - public async Task Should_create_temp_table_with_provided_column_only() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - _optionsWithNonUniqueNameAndNoPrimaryKey.PropertiesToInclude = IEntityPropertiesProvider.Include(t => t.Column1); - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns().ToList(); - columns.Should().HaveCount(1); - - ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); - } - - [Fact] - public async Task Should_create_pk_if_options_flag_is_set() - { - _optionsWithNonUniqueNameAndNoPrimaryKey.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced; - - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(s => s.Column2).HasMaxLength(100)); - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var keyColumns = await AssertDbContext.GetTempTableKeyColumns>().ToListAsync(); - keyColumns.Should().HaveCount(2); - keyColumns[0].Name.Should().Be(nameof(TempTable.Column1)); - keyColumns[1].Name.Should().Be(nameof(TempTable.Column2)); - } - - [Fact] - public async Task Should_throw_if_some_pk_columns_are_missing() - { - _optionsWithNonUniqueNameAndNoPrimaryKey.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.EntityTypeConfiguration; - _optionsWithNonUniqueNameAndNoPrimaryKey.PropertiesToInclude = IEntityPropertiesProvider.Include(t => t.Column1); - - ConfigureModel = builder => builder.ConfigureTempTableEntity(false, typeBuilder => - { - typeBuilder.Property(s => s.Column2).HasMaxLength(100); - typeBuilder.HasKey(s => new { s.Column1, s.Column2 }); - }); - - // ReSharper disable once RedundantArgumentDefaultValue - await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) - .Should().ThrowAsync().WithMessage(""" - Cannot create PRIMARY KEY because not all key columns are part of the temp table. - You may use other key properties providers like 'IPrimaryKeyPropertiesProvider.AdaptiveEntityTypeConfiguration' instead of 'IPrimaryKeyPropertiesProvider.EntityTypeConfiguration' to get different behaviors. - Missing columns: Column2. - """); - } - - [Fact] - public async Task Should_not_throw_if_some_pk_columns_are_missing_and_provider_is_Adaptive() - { - _optionsWithNonUniqueNameAndNoPrimaryKey.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced; - _optionsWithNonUniqueNameAndNoPrimaryKey.PropertiesToInclude = IEntityPropertiesProvider.Include(t => t.Column1); - - ConfigureModel = builder => - { - builder.ConfigureTempTableEntity(false, typeBuilder => - { - typeBuilder.Property(s => s.Column2).HasMaxLength(100); - typeBuilder.HasKey(s => new { s.Column1, s.Column2 }); - }); - }; - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var keyColumns = await AssertDbContext.GetTempTableKeyColumns().ToListAsync(); - keyColumns.Should().HaveCount(1); - keyColumns[0].Name.Should().Be(nameof(CustomTempTable.Column1)); - } - - [Fact] - public async Task Should_open_connection() - { - await using var con = CreateConnection(); - - var options = CreateOptionsBuilder(con); - - await using var ctx = new TestDbContext(options); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await ctx.GetService() - .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Open); - } - - [Fact] - public async Task Should_return_reference_to_be_able_to_close_connection() - { - await using var con = CreateConnection(); - - var options = CreateOptionsBuilder(con); - - await using var ctx = new TestDbContext(options); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - - // ReSharper disable once RedundantArgumentDefaultValue - var tempTableReference = await ctx.GetService() - .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); - await tempTableReference.DisposeAsync(); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - } - - [Fact] - public async Task Should_return_reference_to_be_able_to_close_connection_event_if_ctx_is_disposed() - { - await using var con = CreateConnection(); - - var options = CreateOptionsBuilder(con); - - ITempTableReference tempTableReference; - - await using (var ctx = new TestDbContext(options)) - { - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - - // ReSharper disable once RedundantArgumentDefaultValue - tempTableReference = await ctx.GetService() - .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); - } - - con.State.Should().Be(ConnectionState.Open); - await tempTableReference.DisposeAsync(); - con.State.Should().Be(ConnectionState.Closed); - } - - [Fact] - public async Task Should_return_table_ref_that_does_nothing_after_connection_is_disposed() - { - await using var con = CreateConnection(); - - var options = CreateOptionsBuilder(con); - - await using var ctx = new TestDbContext(options); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - - // ReSharper disable once RedundantArgumentDefaultValue - var tempTableReference = await ctx.GetService() - .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); - con.Dispose(); - - con.State.Should().Be(ConnectionState.Closed); - tempTableReference.Dispose(); - con.State.Should().Be(ConnectionState.Closed); - } - - [Fact] - public async Task Should_return_table_ref_that_does_nothing_after_connection_is_disposedAsync() - { - await using var con = CreateConnection(); - - var options = CreateOptionsBuilder(con); - - await using var ctx = new TestDbContext(options); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - - // ReSharper disable once RedundantArgumentDefaultValue - var tempTableReference = await ctx.GetService() - .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); - await con.DisposeAsync(); - - con.State.Should().Be(ConnectionState.Closed); - await tempTableReference.DisposeAsync(); - con.State.Should().Be(ConnectionState.Closed); - } - - [Fact] - public async Task Should_return_table_ref_that_does_nothing_after_connection_is_closed() - { - await using var con = CreateConnection(); - - var options = CreateOptionsBuilder(con); - - await using var ctx = new TestDbContext(options); - - ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTableReference = await ctx.GetService() - .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); - con.Close(); - - tempTableReference.Dispose(); - con.State.Should().Be(ConnectionState.Closed); - } - - private DbContextOptions CreateOptionsBuilder(SqliteConnection connection) - { - return TestCtxProviderBuilder.CreateOptionsBuilder(connection, TestCtxProvider.ConnectionString).Options; - } - - [Fact] - public async Task Should_return_reference_to_remove_temp_table() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(); - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTableReference = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); - await tempTableReference.DisposeAsync(); - - var columns = AssertDbContext.GetTempTableColumns().ToList(); - columns.Should().BeEmpty(); - } - - [Fact] - public async Task Should_create_temp_table_for_entityType() - { - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns().OrderBy(c => c.Name).ToList(); - columns.Should().HaveCount(9); - - ValidateColumn(columns[0], nameof(TestEntity.ConvertibleClass), "INTEGER", true); - ValidateColumn(columns[1], nameof(TestEntity.Count), "INTEGER", false); - ValidateColumn(columns[2], nameof(TestEntity.Id), "TEXT", false); - ValidateColumn(columns[3], nameof(TestEntity.Name), "TEXT", true); - ValidateColumn(columns[4], nameof(TestEntity.NullableCount), "INTEGER", true); - ValidateColumn(columns[5], nameof(TestEntity.ParentId), "TEXT", true); - ValidateColumn(columns[6], nameof(TestEntity.PropertyWithBackingField), "INTEGER", false); - ValidateColumn(columns[7], nameof(TestEntity.RequiredName), "TEXT", false); - ValidateColumn(columns[8], "_privateField", "INTEGER", false); - } - - [Fact] - public async Task Should_create_temp_table_with_autoincrement_if_it_is_primary_key() - { - _optionsWithNonUniqueNameAndNoPrimaryKey.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.EntityTypeConfiguration; - - // ReSharper disable once RedundantArgumentDefaultValue - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns().OrderBy(c => c.Name).ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(TestEntity.Id), "INTEGER", false); - ValidateColumn(columns[1], nameof(TestEntity.Name), "TEXT", true); - } - - [Fact] - public async Task Should_throw_when_creating_temp_table_with_autoincrement_if_it_is_not_primary_key() - { - // ReSharper disable once RedundantArgumentDefaultValue - await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) - .Should().ThrowAsync() - .WithMessage(""" - SQLite does not allow the property 'Id' of the entity 'Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement' to be an AUTOINCREMENT column unless this column is the PRIMARY KEY. - Currently configured primary keys: [] - """); - } - - [Fact] - public async Task Should_throw_if_temp_table_is_not_introduced() - { - await SUT.Awaiting(c => c.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey)) - .Should().ThrowAsync(); - } - - [Fact] - public async Task Should_create_temp_table_with_one_column() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - AssertDbContext.GetTempTableColumns>().ToList().Should().HaveCount(1); - } - - [Fact] - public async Task Should_create_temp_table_without_primary_key() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - _optionsWithNonUniqueNameAndNoPrimaryKey.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None; - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var constraints = await AssertDbContext.GetTempTableKeyColumns>().ToListAsync(); - constraints.Should().BeEmpty(); - } - - [Fact] - public async Task Should_create_temp_table_with_int() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "INTEGER", false); - } - - [Fact] - public async Task Should_create_temp_table_with_nullable_int() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "INTEGER", true); - } - - [Fact] - public async Task Should_create_make_nullable_int_to_non_nullable_if_set_via_modelbuilder() - { - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).IsRequired()); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "INTEGER", false); - } - - [Fact] - public async Task Should_create_temp_table_with_double() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "REAL", false); - } - - [Fact] - public async Task Should_create_temp_table_with_decimal() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "TEXT", false); // decimal is stored as TEXT (see SqliteTypeMappingSource) - } - - [Fact] - public async Task Should_create_temp_table_with_bool() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "INTEGER", false); - } - - [Fact] - public async Task Should_create_temp_table_with_string() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "TEXT", true); - } - - [Fact] - public async Task Should_create_temp_table_with_converter_and_default_value() - { - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1) - .HasConversion(c => c.Key, k => new ConvertibleClass(k)) - .HasDefaultValue(new ConvertibleClass(1))); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "INTEGER", true, "1"); - } - - [Fact] - public async Task Should_create_temp_table_with_string_with_max_length() - { - ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).HasMaxLength(50)); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - ValidateColumn(columns[0], nameof(TempTable.Column1), "TEXT", true); - } - - [Fact] - public async Task Should_create_temp_table_with_2_columns() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns>().ToList(); - columns.Should().HaveCount(2); - - ValidateColumn(columns[0], nameof(TempTable.Column1), "INTEGER", false); - ValidateColumn(columns[1], nameof(TempTable.Column2), "TEXT", true); - } - - [Fact] - public async Task Should_throw_if_temp_table_entity_contains_inlined_owned_type() - { - await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) - .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); - } - - [Fact] - public async Task Should_throw_if_temp_table_contains_inlined_owned_type() - { - _optionsWithNonUniqueNameAndNoPrimaryKey.PropertiesToInclude = IEntityPropertiesProvider.Include(e => new - { - e.Id, - e.InlineEntity - }); - await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) - .Should().ThrowAsync().WithMessage("The entity 'Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline' must not contain owned entities."); - } - - [Fact] - public async Task Should_create_temp_table_for_entity_with_separated_owned_type() - { - var ownerEntityType = ActDbContext.GetTempTableEntityType(); - - await SUT.Awaiting(sut => sut.CreateTempTableAsync(ownerEntityType, _optionsWithNonUniqueNameAndNoPrimaryKey)) - .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); - - var ownedTypeEntityType = ownerEntityType.GetNavigations().Single().TargetEntityType; - await using var ownedTempTable = await SUT.CreateTempTableAsync(ownedTypeEntityType, _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns(ownedTypeEntityType).ToList(); - columns.Should().HaveCount(3); - ValidateColumn(columns[0], $"{nameof(TestEntity_Owns_SeparateOne)}{nameof(TestEntity_Owns_SeparateOne.Id)}", "TEXT", false); - ValidateColumn(columns[1], nameof(OwnedEntity.IntColumn), "INTEGER", false); - ValidateColumn(columns[2], nameof(OwnedEntity.StringColumn), "TEXT", true); - } - - [Fact] - public async Task Should_throw_when_selecting_separated_owned_type() - { - _optionsWithNonUniqueNameAndNoPrimaryKey.PropertiesToInclude = IEntityPropertiesProvider.Include(e => new - { - e.Id, - e.SeparateEntity - }); - await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) - .Should().ThrowAsync() - .WithMessage("The entity 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne' must not contain owned entities."); - } - - [Fact] - public async Task Should_throw_if_temp_table_entity_contains_many_owned_types() - { - var ownerEntityType = ActDbContext.GetTempTableEntityType(); - await SUT.Awaiting(sut => sut.CreateTempTableAsync(ownerEntityType, _optionsWithNonUniqueNameAndNoPrimaryKey)) - .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); - - var ownedTypeEntityType = ownerEntityType.GetNavigations().Single().TargetEntityType; - await using var ownedTempTable = await SUT.CreateTempTableAsync(ownedTypeEntityType, _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = AssertDbContext.GetTempTableColumns(ownedTypeEntityType).ToList(); - columns.Should().HaveCount(4); - ValidateColumn(columns[0], $"{nameof(TestEntity_Owns_SeparateMany)}{nameof(TestEntity_Owns_SeparateMany.Id)}", "TEXT", false); - ValidateColumn(columns[1], "Id", "INTEGER", false); - ValidateColumn(columns[2], nameof(OwnedEntity.IntColumn), "INTEGER", false); - ValidateColumn(columns[3], nameof(OwnedEntity.StringColumn), "TEXT", true); - } - - [Fact] - public async Task Should_create_temp_table_for_entity_with_complex_type() - { - var testEntity = ActDbContext.GetTempTableEntityType(); - - await using var tempTable = await SUT.CreateTempTableAsync(testEntity, _optionsWithNonUniqueNameAndNoPrimaryKey); - - var columns = await AssertDbContext.GetTempTableColumns(testEntity).ToListAsync(); - columns.Should().HaveCount(3); - ValidateColumn(columns[0], nameof(TestEntityWithComplexType.Id), "TEXT", false); - ValidateColumn(columns[1], $"{nameof(TestEntityWithComplexType.Boundary)}_{nameof(BoundaryValueObject.Lower)}", "INTEGER", false); - ValidateColumn(columns[2], $"{nameof(TestEntityWithComplexType.Boundary)}_{nameof(BoundaryValueObject.Upper)}", "INTEGER", false); - } - - private SqliteConnection CreateConnection() - { - return new SqliteConnection(TestCtxProvider.ConnectionString); - } - - private static void ValidateColumn(SqliteTableInfo column, string name, string type, bool isNullable, string? defaultValue = null) - { - ArgumentNullException.ThrowIfNull(column); - - column.Name.Should().Be(name); - column.Type.Should().Be(type); - column.NotNull.Should().Be(isNullable ? 0 : 1); - column.Dflt_Value.Should().Be(defaultValue); - } -} +using System.Data; +using System.Text; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.ObjectPool; +using Thinktecture.EntityFrameworkCore.BulkOperations; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.EntityFrameworkCore.TempTables.SqliteTempTableCreatorTests; + +// ReSharper disable once InconsistentNaming +public class CreateTempTableAsync : SchemaChangingIntegrationTestsBase +{ + private readonly ISqlGenerationHelper _sqlGenerationHelperMock; + private readonly TempTableCreationOptions _optionsWithNonUniqueNameAndNoPrimaryKey; + + private SqliteTempTableCreator? _sut; + + private SqliteTempTableCreator SUT => _sut ??= new SqliteTempTableCreator(ActDbContext.GetService(), + ActDbContext.GetService>(), + _sqlGenerationHelperMock, + ActDbContext.GetService(), + ActDbContext.GetService>(), + new TempTableStatementCache()); + + public CreateTempTableAsync(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + _sqlGenerationHelperMock = Substitute.For(); + _sqlGenerationHelperMock.DelimitIdentifier(Arg.Any(), Arg.Any()) + .Returns(x => x[1] == null ? $"\"{x[0]}\"" : $"\"{x[1]}\".\"{x[0]}\""); + _sqlGenerationHelperMock.DelimitIdentifier(Arg.Any()) + .Returns(x => $"\"{x[0]}\""); + + _optionsWithNonUniqueNameAndNoPrimaryKey = new TempTableCreationOptions { TableNameProvider = DefaultTempTableNameProvider.Instance, PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }; + } + + [Fact] + public async Task Should_create_temp_table_for_keyless_entity() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns().ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); + } + + [Fact] + public async Task Should_delete_temp_table_on_dispose_if_DropTableOnDispose_is_true() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + _optionsWithNonUniqueNameAndNoPrimaryKey.DropTableOnDispose = true; + + // ReSharper disable once UseAwaitUsing + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) + { + } + + AssertDbContext.GetTempTableColumns().ToList() + .Should().HaveCount(0); + } + + [Fact] + public async Task Should_delete_temp_table_on_disposeAsync_if_DropTableOnDispose_is_true() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + _optionsWithNonUniqueNameAndNoPrimaryKey.DropTableOnDispose = true; + + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) + { + } + + AssertDbContext.GetTempTableColumns().ToList() + .Should().HaveCount(0); + } + + [Fact] + public async Task Should_not_delete_temp_table_on_dispose_if_DropTableOnDispose_is_false() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + _optionsWithNonUniqueNameAndNoPrimaryKey.DropTableOnDispose = false; + + // ReSharper disable once UseAwaitUsing + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) + { + } + + var columns = AssertDbContext.GetTempTableColumns().ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); + } + + [Fact] + public async Task Should_not_delete_temp_table_on_disposeAsync_if_DropTableOnDispose_is_false() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + _optionsWithNonUniqueNameAndNoPrimaryKey.DropTableOnDispose = false; + + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) + { + } + + var columns = AssertDbContext.GetTempTableColumns().ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); + } + + [Fact] + public async Task Should_create_temp_table_with_reusable_name() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance }; + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); + + var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); + } + + [Fact] + public async Task Should_reuse_name_after_it_is_freed() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance }; + + // ReSharper disable once RedundantArgumentDefaultValue + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) + { + } + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); + + var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); + } + + [Fact] + public async Task Should_reuse_name_after_it_is_freed_although_previously_not_dropped() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + var options = new TempTableCreationOptions + { + TableNameProvider = ReusingTempTableNameProvider.Instance, + DropTableOnDispose = false + }; + + // ReSharper disable once RedundantArgumentDefaultValue + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) + { + } + + options.TruncateTableIfExists = true; + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); + + var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); + + AssertDbContext.GetTempTableColumns("#CustomTempTable_2").ToList() + .Should().HaveCount(0); + } + + [Fact] + public async Task Should_not_reuse_name_before_it_is_freed() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance }; + + // ReSharper disable once RedundantArgumentDefaultValue + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) + { + await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); + } + + var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_2").ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); + } + + [Fact] + public async Task Should_reuse_name_in_sorted_order() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + var options = new TempTableCreationOptions { TableNameProvider = ReusingTempTableNameProvider.Instance }; + + // #CustomTempTable_1 + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) + { + // #CustomTempTable_2 + await using (await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options)) + { + } + } + + // #CustomTempTable_1 + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), options); + + var columns = AssertDbContext.GetTempTableColumns("#CustomTempTable_1").ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); + ValidateColumn(columns[1], nameof(CustomTempTable.Column2), "TEXT", true); + } + + [Fact] + public async Task Should_create_temp_table_with_provided_column_only() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + _optionsWithNonUniqueNameAndNoPrimaryKey.PropertiesToInclude = IEntityPropertiesProvider.Include(t => t.Column1); + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns().ToList(); + columns.Should().HaveCount(1); + + ValidateColumn(columns[0], nameof(CustomTempTable.Column1), "INTEGER", false); + } + + [Fact] + public async Task Should_create_pk_if_options_flag_is_set() + { + _optionsWithNonUniqueNameAndNoPrimaryKey.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced; + + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(s => s.Column2).HasMaxLength(100)); + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var keyColumns = await AssertDbContext.GetTempTableKeyColumns>().ToListAsync(); + keyColumns.Should().HaveCount(2); + keyColumns[0].Name.Should().Be(nameof(TempTable.Column1)); + keyColumns[1].Name.Should().Be(nameof(TempTable.Column2)); + } + + [Fact] + public async Task Should_throw_if_some_pk_columns_are_missing() + { + _optionsWithNonUniqueNameAndNoPrimaryKey.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.EntityTypeConfiguration; + _optionsWithNonUniqueNameAndNoPrimaryKey.PropertiesToInclude = IEntityPropertiesProvider.Include(t => t.Column1); + + ConfigureModel = builder => builder.ConfigureTempTableEntity(false, typeBuilder => + { + typeBuilder.Property(s => s.Column2).HasMaxLength(100); + typeBuilder.HasKey(s => new { s.Column1, s.Column2 }); + }); + + // ReSharper disable once RedundantArgumentDefaultValue + await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) + .Should().ThrowAsync().WithMessage(""" + Cannot create PRIMARY KEY because not all key columns are part of the temp table. + You may use other key properties providers like 'IPrimaryKeyPropertiesProvider.AdaptiveEntityTypeConfiguration' instead of 'IPrimaryKeyPropertiesProvider.EntityTypeConfiguration' to get different behaviors. + Missing columns: Column2. + """); + } + + [Fact] + public async Task Should_not_throw_if_some_pk_columns_are_missing_and_provider_is_Adaptive() + { + _optionsWithNonUniqueNameAndNoPrimaryKey.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced; + _optionsWithNonUniqueNameAndNoPrimaryKey.PropertiesToInclude = IEntityPropertiesProvider.Include(t => t.Column1); + + ConfigureModel = builder => + { + builder.ConfigureTempTableEntity(false, typeBuilder => + { + typeBuilder.Property(s => s.Column2).HasMaxLength(100); + typeBuilder.HasKey(s => new { s.Column1, s.Column2 }); + }); + }; + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var keyColumns = await AssertDbContext.GetTempTableKeyColumns().ToListAsync(); + keyColumns.Should().HaveCount(1); + keyColumns[0].Name.Should().Be(nameof(CustomTempTable.Column1)); + } + + [Fact] + public async Task Should_open_connection() + { + await using var con = CreateConnection(); + + var options = CreateOptionsBuilder(con); + + await using var ctx = new TestDbContext(options); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await ctx.GetService() + .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Open); + } + + [Fact] + public async Task Should_return_reference_to_be_able_to_close_connection() + { + await using var con = CreateConnection(); + + var options = CreateOptionsBuilder(con); + + await using var ctx = new TestDbContext(options); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + + // ReSharper disable once RedundantArgumentDefaultValue + var tempTableReference = await ctx.GetService() + .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); + await tempTableReference.DisposeAsync(); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + } + + [Fact] + public async Task Should_return_reference_to_be_able_to_close_connection_event_if_ctx_is_disposed() + { + await using var con = CreateConnection(); + + var options = CreateOptionsBuilder(con); + + ITempTableReference tempTableReference; + + await using (var ctx = new TestDbContext(options)) + { + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + + // ReSharper disable once RedundantArgumentDefaultValue + tempTableReference = await ctx.GetService() + .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); + } + + con.State.Should().Be(ConnectionState.Open); + await tempTableReference.DisposeAsync(); + con.State.Should().Be(ConnectionState.Closed); + } + + [Fact] + public async Task Should_return_table_ref_that_does_nothing_after_connection_is_disposed() + { + await using var con = CreateConnection(); + + var options = CreateOptionsBuilder(con); + + await using var ctx = new TestDbContext(options); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + + // ReSharper disable once RedundantArgumentDefaultValue + var tempTableReference = await ctx.GetService() + .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); + con.Dispose(); + + con.State.Should().Be(ConnectionState.Closed); + tempTableReference.Dispose(); + con.State.Should().Be(ConnectionState.Closed); + } + + [Fact] + public async Task Should_return_table_ref_that_does_nothing_after_connection_is_disposedAsync() + { + await using var con = CreateConnection(); + + var options = CreateOptionsBuilder(con); + + await using var ctx = new TestDbContext(options); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + + // ReSharper disable once RedundantArgumentDefaultValue + var tempTableReference = await ctx.GetService() + .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); + await con.DisposeAsync(); + + con.State.Should().Be(ConnectionState.Closed); + await tempTableReference.DisposeAsync(); + con.State.Should().Be(ConnectionState.Closed); + } + + [Fact] + public async Task Should_return_table_ref_that_does_nothing_after_connection_is_closed() + { + await using var con = CreateConnection(); + + var options = CreateOptionsBuilder(con); + + await using var ctx = new TestDbContext(options); + + ctx.Database.GetDbConnection().State.Should().Be(ConnectionState.Closed); + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTableReference = await ctx.GetService() + .CreateTempTableAsync(ctx.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); + con.Close(); + + tempTableReference.Dispose(); + con.State.Should().Be(ConnectionState.Closed); + } + + private DbContextOptions CreateOptionsBuilder(SqliteConnection connection) + { + return TestCtxProviderBuilder.CreateOptionsBuilder(connection, TestCtxProvider.ConnectionString).Options; + } + + [Fact] + public async Task Should_return_reference_to_remove_temp_table() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(); + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTableReference = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); + await tempTableReference.DisposeAsync(); + + var columns = AssertDbContext.GetTempTableColumns().ToList(); + columns.Should().BeEmpty(); + } + + [Fact] + public async Task Should_create_temp_table_for_entityType() + { + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns().OrderBy(c => c.Name).ToList(); + columns.Should().HaveCount(9); + + ValidateColumn(columns[0], nameof(TestEntity.ConvertibleClass), "INTEGER", true); + ValidateColumn(columns[1], nameof(TestEntity.Count), "INTEGER", false); + ValidateColumn(columns[2], nameof(TestEntity.Id), "TEXT", false); + ValidateColumn(columns[3], nameof(TestEntity.Name), "TEXT", true); + ValidateColumn(columns[4], nameof(TestEntity.NullableCount), "INTEGER", true); + ValidateColumn(columns[5], nameof(TestEntity.ParentId), "TEXT", true); + ValidateColumn(columns[6], nameof(TestEntity.PropertyWithBackingField), "INTEGER", false); + ValidateColumn(columns[7], nameof(TestEntity.RequiredName), "TEXT", false); + ValidateColumn(columns[8], "_privateField", "INTEGER", false); + } + + [Fact] + public async Task Should_create_temp_table_with_autoincrement_if_it_is_primary_key() + { + _optionsWithNonUniqueNameAndNoPrimaryKey.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.EntityTypeConfiguration; + + // ReSharper disable once RedundantArgumentDefaultValue + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns().OrderBy(c => c.Name).ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(TestEntity.Id), "INTEGER", false); + ValidateColumn(columns[1], nameof(TestEntity.Name), "TEXT", true); + } + + [Fact] + public async Task Should_throw_when_creating_temp_table_with_autoincrement_if_it_is_not_primary_key() + { + // ReSharper disable once RedundantArgumentDefaultValue + await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) + .Should().ThrowAsync() + .WithMessage(""" + SQLite does not allow the property 'Id' of the entity 'Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement' to be an AUTOINCREMENT column unless this column is the PRIMARY KEY. + Currently configured primary keys: [] + """); + } + + [Fact] + public async Task Should_throw_if_temp_table_is_not_introduced() + { + await SUT.Awaiting(c => c.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey)) + .Should().ThrowAsync(); + } + + [Fact] + public async Task Should_create_temp_table_with_one_column() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + AssertDbContext.GetTempTableColumns>().ToList().Should().HaveCount(1); + } + + [Fact] + public async Task Should_create_temp_table_without_primary_key() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + _optionsWithNonUniqueNameAndNoPrimaryKey.PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None; + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var constraints = await AssertDbContext.GetTempTableKeyColumns>().ToListAsync(); + constraints.Should().BeEmpty(); + } + + [Fact] + public async Task Should_create_temp_table_with_int() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "INTEGER", false); + } + + [Fact] + public async Task Should_create_temp_table_with_nullable_int() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "INTEGER", true); + } + + [Fact] + public async Task Should_create_make_nullable_int_to_non_nullable_if_set_via_modelbuilder() + { + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).IsRequired()); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "INTEGER", false); + } + + [Fact] + public async Task Should_create_temp_table_with_double() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "REAL", false); + } + + [Fact] + public async Task Should_create_temp_table_with_decimal() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "TEXT", false); // decimal is stored as TEXT (see SqliteTypeMappingSource) + } + + [Fact] + public async Task Should_create_temp_table_with_bool() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "INTEGER", false); + } + + [Fact] + public async Task Should_create_temp_table_with_string() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "TEXT", true); + } + + [Fact] + public async Task Should_create_temp_table_with_converter_and_default_value() + { + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1) + .HasConversion(c => c.Key, k => new ConvertibleClass(k)) + .HasDefaultValue(new ConvertibleClass(1))); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "INTEGER", true, "1"); + } + + [Fact] + public async Task Should_create_temp_table_with_string_with_max_length() + { + ConfigureModel = builder => builder.ConfigureTempTable(typeBuilder => typeBuilder.Property(t => t.Column1).HasMaxLength(50)); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + ValidateColumn(columns[0], nameof(TempTable.Column1), "TEXT", true); + } + + [Fact] + public async Task Should_create_temp_table_with_2_columns() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await SUT.CreateTempTableAsync(ActDbContext.GetTempTableEntityType>(), _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns>().ToList(); + columns.Should().HaveCount(2); + + ValidateColumn(columns[0], nameof(TempTable.Column1), "INTEGER", false); + ValidateColumn(columns[1], nameof(TempTable.Column2), "TEXT", true); + } + + [Fact] + public async Task Should_throw_if_temp_table_entity_contains_inlined_owned_type() + { + await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) + .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); + } + + [Fact] + public async Task Should_throw_if_temp_table_contains_inlined_owned_type() + { + _optionsWithNonUniqueNameAndNoPrimaryKey.PropertiesToInclude = IEntityPropertiesProvider.Include(e => new + { + e.Id, + e.InlineEntity + }); + await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) + .Should().ThrowAsync().WithMessage("The entity 'Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline' must not contain owned entities."); + } + + [Fact] + public async Task Should_create_temp_table_for_entity_with_separated_owned_type() + { + var ownerEntityType = ActDbContext.GetTempTableEntityType(); + + await SUT.Awaiting(sut => sut.CreateTempTableAsync(ownerEntityType, _optionsWithNonUniqueNameAndNoPrimaryKey)) + .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); + + var ownedTypeEntityType = ownerEntityType.GetNavigations().Single().TargetEntityType; + await using var ownedTempTable = await SUT.CreateTempTableAsync(ownedTypeEntityType, _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns(ownedTypeEntityType).ToList(); + columns.Should().HaveCount(3); + ValidateColumn(columns[0], $"{nameof(TestEntity_Owns_SeparateOne)}{nameof(TestEntity_Owns_SeparateOne.Id)}", "TEXT", false); + ValidateColumn(columns[1], nameof(OwnedEntity.IntColumn), "INTEGER", false); + ValidateColumn(columns[2], nameof(OwnedEntity.StringColumn), "TEXT", true); + } + + [Fact] + public async Task Should_throw_when_selecting_separated_owned_type() + { + _optionsWithNonUniqueNameAndNoPrimaryKey.PropertiesToInclude = IEntityPropertiesProvider.Include(e => new + { + e.Id, + e.SeparateEntity + }); + await SUT.Awaiting(sut => sut.CreateTempTableAsync(ActDbContext.GetTempTableEntityType(), _optionsWithNonUniqueNameAndNoPrimaryKey)) + .Should().ThrowAsync() + .WithMessage("The entity 'Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne' must not contain owned entities."); + } + + [Fact] + public async Task Should_throw_if_temp_table_entity_contains_many_owned_types() + { + var ownerEntityType = ActDbContext.GetTempTableEntityType(); + await SUT.Awaiting(sut => sut.CreateTempTableAsync(ownerEntityType, _optionsWithNonUniqueNameAndNoPrimaryKey)) + .Should().ThrowAsync().WithMessage("Temp tables don't support owned entities."); + + var ownedTypeEntityType = ownerEntityType.GetNavigations().Single().TargetEntityType; + await using var ownedTempTable = await SUT.CreateTempTableAsync(ownedTypeEntityType, _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = AssertDbContext.GetTempTableColumns(ownedTypeEntityType).ToList(); + columns.Should().HaveCount(4); + ValidateColumn(columns[0], $"{nameof(TestEntity_Owns_SeparateMany)}{nameof(TestEntity_Owns_SeparateMany.Id)}", "TEXT", false); + ValidateColumn(columns[1], "Id", "INTEGER", false); + ValidateColumn(columns[2], nameof(OwnedEntity.IntColumn), "INTEGER", false); + ValidateColumn(columns[3], nameof(OwnedEntity.StringColumn), "TEXT", true); + } + + [Fact] + public async Task Should_create_temp_table_for_entity_with_complex_type() + { + var testEntity = ActDbContext.GetTempTableEntityType(); + + await using var tempTable = await SUT.CreateTempTableAsync(testEntity, _optionsWithNonUniqueNameAndNoPrimaryKey); + + var columns = await AssertDbContext.GetTempTableColumns(testEntity).ToListAsync(); + columns.Should().HaveCount(3); + ValidateColumn(columns[0], nameof(TestEntityWithComplexType.Id), "TEXT", false); + ValidateColumn(columns[1], $"{nameof(TestEntityWithComplexType.Boundary)}_{nameof(BoundaryValueObject.Lower)}", "INTEGER", false); + ValidateColumn(columns[2], $"{nameof(TestEntityWithComplexType.Boundary)}_{nameof(BoundaryValueObject.Upper)}", "INTEGER", false); + } + + private SqliteConnection CreateConnection() + { + return new SqliteConnection(TestCtxProvider.ConnectionString); + } + + private static void ValidateColumn(SqliteTableInfo column, string name, string type, bool isNullable, string? defaultValue = null) + { + ArgumentNullException.ThrowIfNull(column); + + column.Name.Should().Be(name); + column.Type.Should().Be(type); + column.NotNull.Should().Be(isNullable ? 0 : 1); + column.Dflt_Value.Should().Be(defaultValue); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/DbContextExtensions.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/DbContextExtensions.cs index 6a61965e..e2f53afd 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/DbContextExtensions.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/DbContextExtensions.cs @@ -1,20 +1,20 @@ -using Microsoft.EntityFrameworkCore.Metadata; -using Thinktecture.Internal; - -// ReSharper disable once CheckNamespace -namespace Thinktecture; - -public static class DbContextExtensions -{ - public static IEntityType GetEntityType(this DbContext ctx) - { - return ctx.Model.GetEntityType(typeof(T)); - } - - public static IEntityType GetTempTableEntityType(this DbContext ctx) - { - var name = EntityNameProvider.GetTempTableName(typeof(T)); - - return ctx.Model.GetEntityType(name, typeof(T)); - } -} +using Microsoft.EntityFrameworkCore.Metadata; +using Thinktecture.Internal; + +// ReSharper disable once CheckNamespace +namespace Thinktecture; + +public static class DbContextExtensions +{ + public static IEntityType GetEntityType(this DbContext ctx) + { + return ctx.Model.GetEntityType(typeof(T)); + } + + public static IEntityType GetTempTableEntityType(this DbContext ctx) + { + var name = EntityNameProvider.GetTempTableName(typeof(T)); + + return ctx.Model.GetEntityType(name, typeof(T)); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/DbContextExtensionsTests/BulkInsertIntoTempTableAsync.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/DbContextExtensionsTests/BulkInsertIntoTempTableAsync.cs index 5c66560c..6e22a96a 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/DbContextExtensionsTests/BulkInsertIntoTempTableAsync.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/DbContextExtensionsTests/BulkInsertIntoTempTableAsync.cs @@ -1,195 +1,195 @@ -using Microsoft.Data.Sqlite; -using Thinktecture.EntityFrameworkCore.BulkOperations; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Extensions.DbContextExtensionsTests; - -// ReSharper disable InconsistentNaming -public class BulkInsertIntoTempTableAsync : SchemaChangingIntegrationTestsBase -{ - public BulkInsertIntoTempTableAsync(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - - [Fact] - public async Task Should_insert_keyless_type() - { - ConfigureModel = builder => builder.ConfigureTempTableEntity(typeBuilder => typeBuilder.Property(t => t.Column2).HasMaxLength(100).IsRequired()); - - var entities = new List { new(1, "value") }; - await using var query = await ActDbContext.BulkInsertIntoTempTableAsync(entities); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] { new CustomTempTable(1, "value") }); - } - - [Fact] - public async Task Should_insert_entityType_without_touching_real_table() - { - var entity = new TestEntity - { - Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42, - ConvertibleClass = new ConvertibleClass(43) - }; - ArrangeDbContext.TestEntities.Add(entity); - await ArrangeDbContext.SaveChangesAsync(); - - var entities = new List { entity }; - await using var query = await ActDbContext.BulkInsertIntoTempTableAsync(entities); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] - { - new TestEntity - { - Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730"), - Name = "Name", - RequiredName = "RequiredName", - Count = 42, - ConvertibleClass = new ConvertibleClass(43) - } - }); - } - - [Fact] - public async Task Should_insert_entityType_without_required_fields_if_excluded_and_with_UsePropertiesToInsertForTempTableCreation() - { - var entity = new TestEntity - { - Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730") - }; - var entities = new List { entity }; - await using var query = await ActDbContext.BulkInsertIntoTempTableAsync(entities, new SqliteTempTableBulkInsertOptions - { - PropertiesToInsert = IEntityPropertiesProvider.Include(e => e.Id), - Advanced = { UsePropertiesToInsertForTempTableCreation = true } - }); - - var tempTable = await query.Query.Select(t => new { t.Id }).ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] - { - new { Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730") } - }); - } - - [Fact] - public async Task Should_return_disposable_query() - { - await using var tempTableQuery = await ActDbContext.BulkInsertIntoTempTableAsync(Array.Empty()); - var query = tempTableQuery.Query; - tempTableQuery.Dispose(); - - await query.Awaiting(q => q.ToListAsync()) - .Should().ThrowAsync().WithMessage("SQLite Error 1: 'no such table: TestEntities_1'."); - } - - [Fact] - public async Task Should_return_detached_entities_for_entities_with_a_primary_key() - { - var testEntity = new TestEntity - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - RequiredName = "Name1" - }; - - await using var tempTable = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity }); - - var entities = await tempTable.Query.ToListAsync(); - - ActDbContext.Entry(entities[0]).State.Should().Be(EntityState.Detached); - } - - [Fact] - public async Task Should_not_mess_up_temp_tables_with_alternating_requests_without_disposing_previous_one() - { - var testEntity1 = new TestEntity - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - RequiredName = "Name1" - }; - var testEntity2 = new TestEntity - { - Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), - RequiredName = "Name2" - }; - - await using var tempTable1_1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 }); - await using var tempTable2_1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 }); - await using var tempTable1_2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 }); - await using var tempTable2_2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 }); - - tempTable1_1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); - tempTable1_2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); - tempTable2_1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); - tempTable2_2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); - } - - [Fact] - public async Task Should_not_mess_up_temp_tables_with_alternating_requests_with_disposing_previous_one() - { - var testEntity1 = new TestEntity { Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), RequiredName = "Name1" }; - var testEntity2 = new TestEntity { Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), RequiredName = "Name1" }; - - await using (var tempTable1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 })) - await using (var tempTable2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 })) - { - tempTable1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); - tempTable2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); - } - - await using (var tempTable1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 })) - await using (var tempTable2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 })) - { - tempTable1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); - tempTable2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); - } - } - - [Fact] - public async Task Should_properly_join_real_table_with_temp_table() - { - var realEntity = new TestEntity - { - Id = new Guid("C0A98E8F-2715-4764-A02E-033FF5278B9B"), - RequiredName = "Name1" - }; - ArrangeDbContext.Add(realEntity); - await ArrangeDbContext.SaveChangesAsync(); - - var tempEntity = new TestEntity - { - Id = new Guid("C0A98E8F-2715-4764-A02E-033FF5278B9B"), - RequiredName = "Name2" - }; - - await using var tempTable = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { tempEntity }); - - var entities = await tempTable.Query - .Join(ActDbContext.TestEntities, e => e.Id, e => e.Id, (temp, real) => new { temp, real }) - .ToListAsync(); - - entities.Should().BeEquivalentTo(new[] { new { temp = tempEntity, real = realEntity } }); - - entities = await tempTable.Query - .Join(ActDbContext.TestEntities, e => e.Id, e => e.Id, (temp, real) => new { temp, real }) - .ToListAsync(); - - entities.Should().BeEquivalentTo(new[] { new { temp = tempEntity, real = realEntity } }); - } - - [Fact] - public async Task Should_insert_TestEntity_with_ComplexType() - { - var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), - new BoundaryValueObject(2, 5)); - - await using var tempTable = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity }); - - var loadedEntities = await tempTable.Query.ToListAsync(); - loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); - } -} +using Microsoft.Data.Sqlite; +using Thinktecture.EntityFrameworkCore.BulkOperations; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Extensions.DbContextExtensionsTests; + +// ReSharper disable InconsistentNaming +public class BulkInsertIntoTempTableAsync : SchemaChangingIntegrationTestsBase +{ + public BulkInsertIntoTempTableAsync(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + } + + [Fact] + public async Task Should_insert_keyless_type() + { + ConfigureModel = builder => builder.ConfigureTempTableEntity(typeBuilder => typeBuilder.Property(t => t.Column2).HasMaxLength(100).IsRequired()); + + var entities = new List { new(1, "value") }; + await using var query = await ActDbContext.BulkInsertIntoTempTableAsync(entities); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] { new CustomTempTable(1, "value") }); + } + + [Fact] + public async Task Should_insert_entityType_without_touching_real_table() + { + var entity = new TestEntity + { + Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42, + ConvertibleClass = new ConvertibleClass(43) + }; + ArrangeDbContext.TestEntities.Add(entity); + await ArrangeDbContext.SaveChangesAsync(); + + var entities = new List { entity }; + await using var query = await ActDbContext.BulkInsertIntoTempTableAsync(entities); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] + { + new TestEntity + { + Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730"), + Name = "Name", + RequiredName = "RequiredName", + Count = 42, + ConvertibleClass = new ConvertibleClass(43) + } + }); + } + + [Fact] + public async Task Should_insert_entityType_without_required_fields_if_excluded_and_with_UsePropertiesToInsertForTempTableCreation() + { + var entity = new TestEntity + { + Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730") + }; + var entities = new List { entity }; + await using var query = await ActDbContext.BulkInsertIntoTempTableAsync(entities, new SqliteTempTableBulkInsertOptions + { + PropertiesToInsert = IEntityPropertiesProvider.Include(e => e.Id), + Advanced = { UsePropertiesToInsertForTempTableCreation = true } + }); + + var tempTable = await query.Query.Select(t => new { t.Id }).ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] + { + new { Id = new Guid("577BFD36-21BC-4F9E-97B4-367B8F29B730") } + }); + } + + [Fact] + public async Task Should_return_disposable_query() + { + await using var tempTableQuery = await ActDbContext.BulkInsertIntoTempTableAsync(Array.Empty()); + var query = tempTableQuery.Query; + tempTableQuery.Dispose(); + + await query.Awaiting(q => q.ToListAsync()) + .Should().ThrowAsync().WithMessage("SQLite Error 1: 'no such table: TestEntities_1'."); + } + + [Fact] + public async Task Should_return_detached_entities_for_entities_with_a_primary_key() + { + var testEntity = new TestEntity + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + RequiredName = "Name1" + }; + + await using var tempTable = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity }); + + var entities = await tempTable.Query.ToListAsync(); + + ActDbContext.Entry(entities[0]).State.Should().Be(EntityState.Detached); + } + + [Fact] + public async Task Should_not_mess_up_temp_tables_with_alternating_requests_without_disposing_previous_one() + { + var testEntity1 = new TestEntity + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + RequiredName = "Name1" + }; + var testEntity2 = new TestEntity + { + Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), + RequiredName = "Name2" + }; + + await using var tempTable1_1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 }); + await using var tempTable2_1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 }); + await using var tempTable1_2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 }); + await using var tempTable2_2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 }); + + tempTable1_1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); + tempTable1_2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); + tempTable2_1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); + tempTable2_2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); + } + + [Fact] + public async Task Should_not_mess_up_temp_tables_with_alternating_requests_with_disposing_previous_one() + { + var testEntity1 = new TestEntity { Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), RequiredName = "Name1" }; + var testEntity2 = new TestEntity { Id = new Guid("3A1B2FFF-8E11-44E5-80E5-8C7FEEDACEB3"), RequiredName = "Name1" }; + + await using (var tempTable1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 })) + await using (var tempTable2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 })) + { + tempTable1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); + tempTable2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); + } + + await using (var tempTable1 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity1 })) + await using (var tempTable2 = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity2 })) + { + tempTable1.Query.ToList().Should().BeEquivalentTo(new[] { testEntity1 }); + tempTable2.Query.ToList().Should().BeEquivalentTo(new[] { testEntity2 }); + } + } + + [Fact] + public async Task Should_properly_join_real_table_with_temp_table() + { + var realEntity = new TestEntity + { + Id = new Guid("C0A98E8F-2715-4764-A02E-033FF5278B9B"), + RequiredName = "Name1" + }; + ArrangeDbContext.Add(realEntity); + await ArrangeDbContext.SaveChangesAsync(); + + var tempEntity = new TestEntity + { + Id = new Guid("C0A98E8F-2715-4764-A02E-033FF5278B9B"), + RequiredName = "Name2" + }; + + await using var tempTable = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { tempEntity }); + + var entities = await tempTable.Query + .Join(ActDbContext.TestEntities, e => e.Id, e => e.Id, (temp, real) => new { temp, real }) + .ToListAsync(); + + entities.Should().BeEquivalentTo(new[] { new { temp = tempEntity, real = realEntity } }); + + entities = await tempTable.Query + .Join(ActDbContext.TestEntities, e => e.Id, e => e.Id, (temp, real) => new { temp, real }) + .ToListAsync(); + + entities.Should().BeEquivalentTo(new[] { new { temp = tempEntity, real = realEntity } }); + } + + [Fact] + public async Task Should_insert_TestEntity_with_ComplexType() + { + var testEntity = new TestEntityWithComplexType(new Guid("54FF93FC-6BE9-4F19-A52E-E517CA9FEAA7"), + new BoundaryValueObject(2, 5)); + + await using var tempTable = await ActDbContext.BulkInsertIntoTempTableAsync(new[] { testEntity }); + + var loadedEntities = await tempTable.Query.ToListAsync(); + loadedEntities.Should().BeEquivalentTo(new[] { testEntity }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/DbContextExtensionsTests/BulkInsertValuesIntoTempTableAsync_1_Column.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/DbContextExtensionsTests/BulkInsertValuesIntoTempTableAsync_1_Column.cs index d4852829..80cec7f2 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/DbContextExtensionsTests/BulkInsertValuesIntoTempTableAsync_1_Column.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/DbContextExtensionsTests/BulkInsertValuesIntoTempTableAsync_1_Column.cs @@ -1,205 +1,205 @@ -using Microsoft.Data.Sqlite; -using Thinktecture.EntityFrameworkCore.BulkOperations; -using Thinktecture.EntityFrameworkCore.TempTables; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Extensions.DbContextExtensionsTests; - -// ReSharper disable once InconsistentNaming -public class BulkInsertValuesIntoTempTableAsync_1_Column : SchemaChangingIntegrationTestsBase -{ - public BulkInsertValuesIntoTempTableAsync_1_Column(ITestOutputHelper testOutputHelper) - : base(testOutputHelper) - { - } - - [Fact] - public async Task Should_insert_int() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var values = new List { 1, 2 }; - await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] { 1, 2 }); - } - - [Fact] - public async Task Should_throw_if_inserting_duplicates() - { - ConfigureModel = builder => builder.ConfigureTempTable(false); - - var values = new List { 1, 1 }; - await ActDbContext.Awaiting(ctx => ctx.BulkInsertValuesIntoTempTableAsync(values)) - .Should().ThrowAsync().Where(ex => ex.Message.StartsWith("SQLite Error 19: 'UNIQUE constraint failed: #TempTable_1.Column1'.", StringComparison.Ordinal)); - } - - [Fact] - public async Task Should_insert_nullable_int_of_a_keyless_entity() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var values = new List { 1, null }; - await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqliteTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] { 1, (int?)null }); - } - - [Fact] - public async Task Should_insert_nullable_int_of_entity_with_a_key() - { - ConfigureModel = builder => builder.ConfigureTempTable(false, - entityBuilder => - { - entityBuilder.HasKey(t => t.Column1); - entityBuilder.Property(t => t.Column1).ValueGeneratedNever(); - }); - - var values = new List { 1, 2 }; - await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqliteTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] { 1, 2 }); - } - - [Fact] - public async Task Should_insert_string() - { - ConfigureModel = builder => builder.ConfigureTempTable(entityBuilder => entityBuilder.Property(t => t.Column1).IsRequired(false)); - - var values = new List { "value1", null }; - await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqliteTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); - - var tempTable = await query.Query.ToListAsync(); - tempTable.Should().BeEquivalentTo(new[] { "value1", null }); - } - - [Fact] - public async Task Should_create_pk_by_default_on_string_column() - { - ConfigureModel = builder => builder.ConfigureTempTable(entityBuilder => entityBuilder.Property(t => t.Column1).HasMaxLength(100).IsRequired()); - - await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { "value" }, new SqliteTempTableBulkInsertOptions - { - TableNameProvider = DefaultTempTableNameProvider.Instance, - PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced - }); - - var keys = AssertDbContext.GetTempTableKeyColumns>().ToList(); - keys.Should().HaveCount(1); - keys[0].Name.Should().Be(nameof(TempTable.Column1)); - } - - [Fact] - public async Task Should_create_pk_on_nullable_column() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqliteTempTableBulkInsertOptions - { - TableNameProvider = DefaultTempTableNameProvider.Instance, - PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced - }); - - var keys = AssertDbContext.GetTempTableKeyColumns>().ToList(); - keys.Should().HaveCount(1); - keys[0].Name.Should().Be(nameof(TempTable.Column1)); - } - - [Fact] - public async Task Should_create_pk_by_default() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqliteTempTableBulkInsertOptions - { - TableNameProvider = DefaultTempTableNameProvider.Instance, - PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced - }); - - var keys = ArrangeDbContext.GetTempTableKeyColumns>().ToList(); - keys.Should().HaveCount(1); - keys[0].Name.Should().Be(nameof(TempTable.Column1)); - } - - [Fact] - public async Task Should_not_create_pk_if_entity_is_keyless_and_provider_is_AccordingToEntityTypeConfiguration() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqliteTempTableBulkInsertOptions - { - TableNameProvider = DefaultTempTableNameProvider.Instance, - PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.EntityTypeConfiguration - }); - - var keys = ArrangeDbContext.GetTempTableKeyColumns>().ToList(); - keys.Should().HaveCount(0); - } - - [Fact] - public async Task Should_not_create_pk_if_specified_in_options() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqliteTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); - - var keys = ArrangeDbContext.GetTempTableKeyColumns>().ToList(); - keys.Should().HaveCount(0); - } - - [Fact] - public async Task Should_work_with_projection_using_SplitQuery_behavior() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var parentId = new Guid("6EB48130-454B-46BC-9FEE-CDF411F69807"); - var childId = new Guid("17A2014C-F7B2-40E8-82F4-42E754DCAA2D"); - ArrangeDbContext.TestEntities.Add(new TestEntity - { - Id = parentId, - RequiredName = "RequiredName", - Children = { new() { Id = childId, RequiredName = "RequiredName" } } - }); - await ArrangeDbContext.SaveChangesAsync(); - - await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new[] { parentId }); - - var query = await ActDbContext.TestEntities - .AsSplitQuery() - .Where(c => tempTable.Query.Any(id => c.Id == id)) - .Select(c => new - { - c.Id, - ChildrenIds = c.Children.Select(o => o.Id).ToList() - }) - .ToListAsync(); - - query.Should().BeEquivalentTo(new[] - { - new - { - Id = parentId, - ChildrenIds = new List { childId } - } - }); - } - - [Fact] - public async Task Should_join_with_itself() - { - ConfigureModel = builder => builder.ConfigureTempTable(); - - var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new[] { Guid.Empty }); - - var joinQuery = tempTable.Query.LeftJoin(tempTable.Query, e => e, e => e); - - joinQuery.ToQueryString().Should().Be(""" - SELECT "#"."Column1" AS "Left", "#0"."Column1" AS "Right" - FROM "#TempTable_1" AS "#" - LEFT JOIN "#TempTable_1" AS "#0" ON "#"."Column1" = "#0"."Column1" - """.WithEnvironmentLineBreaks()); - } -} +using Microsoft.Data.Sqlite; +using Thinktecture.EntityFrameworkCore.BulkOperations; +using Thinktecture.EntityFrameworkCore.TempTables; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Extensions.DbContextExtensionsTests; + +// ReSharper disable once InconsistentNaming +public class BulkInsertValuesIntoTempTableAsync_1_Column : SchemaChangingIntegrationTestsBase +{ + public BulkInsertValuesIntoTempTableAsync_1_Column(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) + { + } + + [Fact] + public async Task Should_insert_int() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var values = new List { 1, 2 }; + await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] { 1, 2 }); + } + + [Fact] + public async Task Should_throw_if_inserting_duplicates() + { + ConfigureModel = builder => builder.ConfigureTempTable(false); + + var values = new List { 1, 1 }; + await ActDbContext.Awaiting(ctx => ctx.BulkInsertValuesIntoTempTableAsync(values)) + .Should().ThrowAsync().Where(ex => ex.Message.StartsWith("SQLite Error 19: 'UNIQUE constraint failed: #TempTable_1.Column1'.", StringComparison.Ordinal)); + } + + [Fact] + public async Task Should_insert_nullable_int_of_a_keyless_entity() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var values = new List { 1, null }; + await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqliteTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] { 1, (int?)null }); + } + + [Fact] + public async Task Should_insert_nullable_int_of_entity_with_a_key() + { + ConfigureModel = builder => builder.ConfigureTempTable(false, + entityBuilder => + { + entityBuilder.HasKey(t => t.Column1); + entityBuilder.Property(t => t.Column1).ValueGeneratedNever(); + }); + + var values = new List { 1, 2 }; + await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqliteTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] { 1, 2 }); + } + + [Fact] + public async Task Should_insert_string() + { + ConfigureModel = builder => builder.ConfigureTempTable(entityBuilder => entityBuilder.Property(t => t.Column1).IsRequired(false)); + + var values = new List { "value1", null }; + await using var query = await ActDbContext.BulkInsertValuesIntoTempTableAsync(values, new SqliteTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); + + var tempTable = await query.Query.ToListAsync(); + tempTable.Should().BeEquivalentTo(new[] { "value1", null }); + } + + [Fact] + public async Task Should_create_pk_by_default_on_string_column() + { + ConfigureModel = builder => builder.ConfigureTempTable(entityBuilder => entityBuilder.Property(t => t.Column1).HasMaxLength(100).IsRequired()); + + await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { "value" }, new SqliteTempTableBulkInsertOptions + { + TableNameProvider = DefaultTempTableNameProvider.Instance, + PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced + }); + + var keys = AssertDbContext.GetTempTableKeyColumns>().ToList(); + keys.Should().HaveCount(1); + keys[0].Name.Should().Be(nameof(TempTable.Column1)); + } + + [Fact] + public async Task Should_create_pk_on_nullable_column() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqliteTempTableBulkInsertOptions + { + TableNameProvider = DefaultTempTableNameProvider.Instance, + PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced + }); + + var keys = AssertDbContext.GetTempTableKeyColumns>().ToList(); + keys.Should().HaveCount(1); + keys[0].Name.Should().Be(nameof(TempTable.Column1)); + } + + [Fact] + public async Task Should_create_pk_by_default() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqliteTempTableBulkInsertOptions + { + TableNameProvider = DefaultTempTableNameProvider.Instance, + PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.AdaptiveForced + }); + + var keys = ArrangeDbContext.GetTempTableKeyColumns>().ToList(); + keys.Should().HaveCount(1); + keys[0].Name.Should().Be(nameof(TempTable.Column1)); + } + + [Fact] + public async Task Should_not_create_pk_if_entity_is_keyless_and_provider_is_AccordingToEntityTypeConfiguration() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqliteTempTableBulkInsertOptions + { + TableNameProvider = DefaultTempTableNameProvider.Instance, + PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.EntityTypeConfiguration + }); + + var keys = ArrangeDbContext.GetTempTableKeyColumns>().ToList(); + keys.Should().HaveCount(0); + } + + [Fact] + public async Task Should_not_create_pk_if_specified_in_options() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new List { 1 }, new SqliteTempTableBulkInsertOptions { PrimaryKeyCreation = IPrimaryKeyPropertiesProvider.None }); + + var keys = ArrangeDbContext.GetTempTableKeyColumns>().ToList(); + keys.Should().HaveCount(0); + } + + [Fact] + public async Task Should_work_with_projection_using_SplitQuery_behavior() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var parentId = new Guid("6EB48130-454B-46BC-9FEE-CDF411F69807"); + var childId = new Guid("17A2014C-F7B2-40E8-82F4-42E754DCAA2D"); + ArrangeDbContext.TestEntities.Add(new TestEntity + { + Id = parentId, + RequiredName = "RequiredName", + Children = { new() { Id = childId, RequiredName = "RequiredName" } } + }); + await ArrangeDbContext.SaveChangesAsync(); + + await using var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new[] { parentId }); + + var query = await ActDbContext.TestEntities + .AsSplitQuery() + .Where(c => tempTable.Query.Any(id => c.Id == id)) + .Select(c => new + { + c.Id, + ChildrenIds = c.Children.Select(o => o.Id).ToList() + }) + .ToListAsync(); + + query.Should().BeEquivalentTo(new[] + { + new + { + Id = parentId, + ChildrenIds = new List { childId } + } + }); + } + + [Fact] + public async Task Should_join_with_itself() + { + ConfigureModel = builder => builder.ConfigureTempTable(); + + var tempTable = await ActDbContext.BulkInsertValuesIntoTempTableAsync(new[] { Guid.Empty }); + + var joinQuery = tempTable.Query.LeftJoin(tempTable.Query, e => e, e => e); + + joinQuery.ToQueryString().Should().Be(""" + SELECT "#"."Column1" AS "Left", "#0"."Column1" AS "Right" + FROM "#TempTable_1" AS "#" + LEFT JOIN "#TempTable_1" AS "#0" ON "#"."Column1" = "#0"."Column1" + """.WithEnvironmentLineBreaks()); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/SqliteDbFunctionsExtensionsTests/RowNumber.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/SqliteDbFunctionsExtensionsTests/RowNumber.cs index e627b62a..3e3dedcd 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/SqliteDbFunctionsExtensionsTests/RowNumber.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Extensions/SqliteDbFunctionsExtensionsTests/RowNumber.cs @@ -1,363 +1,363 @@ -using System.Linq.Expressions; -using Microsoft.Data.Sqlite; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Extensions.SqliteDbFunctionsExtensionsTests; - -// ReSharper disable once InconsistentNaming -public class RowNumber : IntegrationTestsBase -{ - public RowNumber(ITestOutputHelper testOutputHelper, DbContextProviderFactoryFixture providerFactoryFixture) - : base(testOutputHelper, providerFactoryFixture) - { - } - - [Fact] - public void Generates_RowNumber_with_orderby_and_one_column() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Name, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) - }) - .ToList(); - - result.First(t => t.Name == "1").RowNumber.Should().Be(1); - result.First(t => t.Name == "2").RowNumber.Should().Be(2); - } - - [Fact] - public void Generates_RowNumber_with_orderby_and_one_column_generic_approach() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var propertyName = nameof(TestEntity.Name); - - var query = ActDbContext.TestEntities; - var result = AppendSelect(query, propertyName, new { Name = String.Empty, RowNumber = 0L }).ToList(); - - result.First(t => t.Name == "1").RowNumber.Should().Be(1); - result.First(t => t.Name == "2").RowNumber.Should().Be(2); - } - - private static IQueryable AppendSelect(IQueryable query, string propertyName, TResult anonymousTypeSample) - { - var testEntityType = typeof(T); - var propertyInfo = testEntityType.GetProperty(propertyName) ?? throw new Exception($"Property '{propertyName}' not found."); - - var returnType = new { Name = String.Empty, RowNumber = 0L }.GetType(); - var returnTypeCtor = returnType.GetConstructor(new[] { typeof(string), typeof(long) }) - ?? throw new Exception("Constructor not found."); - - var efFunctions = Expression.Constant(EF.Functions); // EF.Functions - var extensionsType = typeof(RelationalDbFunctionsExtensions); - var orderByMethod = extensionsType.GetMethod(nameof(RelationalDbFunctionsExtensions.OrderBy)) // EF.Functions.OrderBy - ?.MakeGenericMethod(propertyInfo.PropertyType) // EF.Functions.OrderBy - ?? throw new Exception("Method 'OrderBy' not found."); - var rowNumberMethod = extensionsType.GetMethods() - .Single(m => m.Name == nameof(RelationalDbFunctionsExtensions.RowNumber) && !m.IsGenericMethod); // EF.Functions.RowNumber - - var param = Expression.Parameter(testEntityType); // e - var nameAccessor = Expression.MakeMemberAccess(param, propertyInfo); // e.Name - var orderByCall = Expression.Call(null, orderByMethod, efFunctions, nameAccessor); // EF.Functions.OrderBy(e.Name) - var rowNumberCall = Expression.Call(null, rowNumberMethod, efFunctions, orderByCall); // EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) - var returnTypeCtorCall = Expression.New(returnTypeCtor, nameAccessor, rowNumberCall); // new { ... } - - var projection = Expression.Lambda>(returnTypeCtorCall, param); - - return query.Select(projection); - } - - [Fact] - public void Generates_RowNumber_with_orderby_and_one_struct_column() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB"), RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("28CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB"), RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Id, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Id)) - }) - .ToList(); - - result.First(t => t.Id == new Guid("18CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB")).RowNumber.Should().Be(1); - result.First(t => t.Id == new Guid("28CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB")).RowNumber.Should().Be(2); - } - - [Fact] - public void Generates_RowNumber_with_orderby_desc_and_one_column() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Name, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderByDescending(e.Name)) - }) - .ToList(); - - result.First(t => t.Name == "1").RowNumber.Should().Be(2); - result.First(t => t.Name == "2").RowNumber.Should().Be(1); - } - - [Fact] - public void Generates_RowNumber_with_orderby_and_two_columns() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Count, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name).ThenBy(e.Count)) - }) - .ToList(); - - result.First(t => t.Count == 1).RowNumber.Should().Be(1); - result.First(t => t.Count == 2).RowNumber.Should().Be(2); - } - - [Fact] - public void Generates_RowNumber_with_orderby_desc_and_two_columns() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Count, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name).ThenByDescending(e.Count)) - }) - .ToList(); - - result.First(t => t.Count == 1).RowNumber.Should().Be(2); - result.First(t => t.Count == 2).RowNumber.Should().Be(1); - } - - [Fact] - public void Generates_RowNumber_with_partitionby_and_orderby_and_one_column() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Name, - RowNumber = EF.Functions.RowNumber(e.Name, EF.Functions.OrderBy(e.Name)) - }) - .ToList(); - - result.First(t => t.Name == "1").RowNumber.Should().Be(1); - result.First(t => t.Name == "2").RowNumber.Should().Be(1); - } - - [Fact] - public void Generates_RowNumber_with_partitionby_and_orderby_and_two_columns() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Count, - RowNumber = EF.Functions.RowNumber(e.Name, e.Count, - EF.Functions.OrderBy(e.Name).ThenBy(e.Count)) - }) - .ToList(); - - result.First(t => t.Count == 1).RowNumber.Should().Be(1); - result.First(t => t.Count == 2).RowNumber.Should().Be(1); - } - - [Fact] - public void Throws_if_RowNumber_contains_NewExpression() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var query = ActDbContext.TestEntities - .Select(e => new - { - e.Count, - RowNumber = EF.Functions.RowNumber(new { e.Name, e.Count }, - EF.Functions.OrderBy(e.Name).ThenBy(e.Count)) - }); - query.Invoking(q => q.ToList()).Should().Throw() - .WithMessage("The window function contains some expressions not supported by the Entity Framework. One of the reason is the creation of new objects like: 'new { e.MyProperty, e.MyOtherProperty }'."); - } - - [Fact] - public void Should_throw_if_accessing_RowNumber_not_within_subquery() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var query = ActDbContext.TestEntities - .Select(e => new - { - e.Name, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) - }) - .Where(i => i.RowNumber == 1); - - query.Invoking(q => q.ToList()) - .Should() - .Throw(); - } - - [Fact] - public void Should_be_able_to_fetch_whole_entity() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var query = ActDbContext.TestEntities - .Select(e => new - { - e, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) - }); - - var entities = query.ToList(); - entities.Should().HaveCount(1); - entities[0].Should().BeEquivalentTo(new - { - e = new TestEntity - { - Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), - Name = "1", - RequiredName = "RequiredName" - }, - RowNumber = 1 - }); - } - - [Fact] - public void Should_filter_for_RowNumber_if_accessing_within_subquery() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.Name, - RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) - }) - .AsSubQuery() - .Where(i => i.RowNumber == 1) - .ToList(); - - result.Should().HaveCount(1); - result[0].Name.Should().Be("1"); - } - - [Fact] - public void Should_support_columns_with_converters() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - e.ConvertibleClass, - RowNumber = EF.Functions.RowNumber(e.Name, - EF.Functions.OrderBy(e.Name).ThenBy(e.ConvertibleClass)) - }) - .ToList(); - - result.Should().HaveCount(2); - result.Select(e => e.ConvertibleClass?.Key).Should().BeEquivalentTo(new[] { 1, 2 }); - } - - [Fact] - public void Should_support_conversion_to_underlying_column_type() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - ConvertibleClass = (int)e.ConvertibleClass!, -#pragma warning disable CS8604 - RowNumber = EF.Functions.RowNumber(e.Name, (int)e.ConvertibleClass, (int)e.Count, - EF.Functions.OrderBy(e.Name).ThenBy((int)e.ConvertibleClass).ThenBy((int)e.Count)) -#pragma warning restore CS8604 - }) - .ToList(); - - result.Should().HaveCount(2); - result.Select(e => e.ConvertibleClass).Should().AllBeOfType().And.BeEquivalentTo(new[] { 1, 2 }); - } - - [Fact] - public void Should_support_conversion_if_column_type_is_convertable_on_database() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - ConvertibleClass = (long)e.ConvertibleClass!, -#pragma warning disable CS8604 - RowNumber = EF.Functions.RowNumber(e.Name, (long)e.ConvertibleClass, (long)e.Count, - EF.Functions.OrderBy(e.Name).ThenBy((long)e.ConvertibleClass).ThenBy((long)e.Count)) -#pragma warning restore CS8604 - }) - .ToList(); - - result.Should().HaveCount(2); - result.Select(e => e.ConvertibleClass).Should().AllBeOfType().And.BeEquivalentTo(new long[] { 1, 2 }); - } - - [Fact] - public void Should_support_conversion_for_non_trivial_expressions() - { - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); - ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); - ArrangeDbContext.SaveChanges(); - - var result = ActDbContext.TestEntities - .Select(e => new - { - ConvertibleClass = (long)e.ConvertibleClass!, -#pragma warning disable CS8604 - RowNumber = EF.Functions.RowNumber(e.Name, (long)e.ConvertibleClass + 1, (long)e.Count + 1, - EF.Functions.OrderBy(e.Name).ThenBy((long)e.ConvertibleClass + 1).ThenBy((long)e.Count + 1)) -#pragma warning restore CS8604 - }) - .ToList(); - - result.Should().HaveCount(2); - result.Select(e => e.ConvertibleClass).Should().AllBeOfType().And.BeEquivalentTo(new long[] { 1, 2 }); - } -} +using System.Linq.Expressions; +using Microsoft.Data.Sqlite; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Extensions.SqliteDbFunctionsExtensionsTests; + +// ReSharper disable once InconsistentNaming +public class RowNumber : IntegrationTestsBase +{ + public RowNumber(ITestOutputHelper testOutputHelper, DbContextProviderFactoryFixture providerFactoryFixture) + : base(testOutputHelper, providerFactoryFixture) + { + } + + [Fact] + public void Generates_RowNumber_with_orderby_and_one_column() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Name, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) + }) + .ToList(); + + result.First(t => t.Name == "1").RowNumber.Should().Be(1); + result.First(t => t.Name == "2").RowNumber.Should().Be(2); + } + + [Fact] + public void Generates_RowNumber_with_orderby_and_one_column_generic_approach() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var propertyName = nameof(TestEntity.Name); + + var query = ActDbContext.TestEntities; + var result = AppendSelect(query, propertyName, new { Name = String.Empty, RowNumber = 0L }).ToList(); + + result.First(t => t.Name == "1").RowNumber.Should().Be(1); + result.First(t => t.Name == "2").RowNumber.Should().Be(2); + } + + private static IQueryable AppendSelect(IQueryable query, string propertyName, TResult anonymousTypeSample) + { + var testEntityType = typeof(T); + var propertyInfo = testEntityType.GetProperty(propertyName) ?? throw new Exception($"Property '{propertyName}' not found."); + + var returnType = new { Name = String.Empty, RowNumber = 0L }.GetType(); + var returnTypeCtor = returnType.GetConstructor(new[] { typeof(string), typeof(long) }) + ?? throw new Exception("Constructor not found."); + + var efFunctions = Expression.Constant(EF.Functions); // EF.Functions + var extensionsType = typeof(RelationalDbFunctionsExtensions); + var orderByMethod = extensionsType.GetMethod(nameof(RelationalDbFunctionsExtensions.OrderBy)) // EF.Functions.OrderBy + ?.MakeGenericMethod(propertyInfo.PropertyType) // EF.Functions.OrderBy + ?? throw new Exception("Method 'OrderBy' not found."); + var rowNumberMethod = extensionsType.GetMethods() + .Single(m => m.Name == nameof(RelationalDbFunctionsExtensions.RowNumber) && !m.IsGenericMethod); // EF.Functions.RowNumber + + var param = Expression.Parameter(testEntityType); // e + var nameAccessor = Expression.MakeMemberAccess(param, propertyInfo); // e.Name + var orderByCall = Expression.Call(null, orderByMethod, efFunctions, nameAccessor); // EF.Functions.OrderBy(e.Name) + var rowNumberCall = Expression.Call(null, rowNumberMethod, efFunctions, orderByCall); // EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) + var returnTypeCtorCall = Expression.New(returnTypeCtor, nameAccessor, rowNumberCall); // new { ... } + + var projection = Expression.Lambda>(returnTypeCtorCall, param); + + return query.Select(projection); + } + + [Fact] + public void Generates_RowNumber_with_orderby_and_one_struct_column() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB"), RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("28CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB"), RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Id, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Id)) + }) + .ToList(); + + result.First(t => t.Id == new Guid("18CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB")).RowNumber.Should().Be(1); + result.First(t => t.Id == new Guid("28CF65B3-F53D-4F45-8DF5-DD62DCC8B2EB")).RowNumber.Should().Be(2); + } + + [Fact] + public void Generates_RowNumber_with_orderby_desc_and_one_column() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Name, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderByDescending(e.Name)) + }) + .ToList(); + + result.First(t => t.Name == "1").RowNumber.Should().Be(2); + result.First(t => t.Name == "2").RowNumber.Should().Be(1); + } + + [Fact] + public void Generates_RowNumber_with_orderby_and_two_columns() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Count, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name).ThenBy(e.Count)) + }) + .ToList(); + + result.First(t => t.Count == 1).RowNumber.Should().Be(1); + result.First(t => t.Count == 2).RowNumber.Should().Be(2); + } + + [Fact] + public void Generates_RowNumber_with_orderby_desc_and_two_columns() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Count, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name).ThenByDescending(e.Count)) + }) + .ToList(); + + result.First(t => t.Count == 1).RowNumber.Should().Be(2); + result.First(t => t.Count == 2).RowNumber.Should().Be(1); + } + + [Fact] + public void Generates_RowNumber_with_partitionby_and_orderby_and_one_column() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Name, + RowNumber = EF.Functions.RowNumber(e.Name, EF.Functions.OrderBy(e.Name)) + }) + .ToList(); + + result.First(t => t.Name == "1").RowNumber.Should().Be(1); + result.First(t => t.Name == "2").RowNumber.Should().Be(1); + } + + [Fact] + public void Generates_RowNumber_with_partitionby_and_orderby_and_two_columns() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Count, + RowNumber = EF.Functions.RowNumber(e.Name, e.Count, + EF.Functions.OrderBy(e.Name).ThenBy(e.Count)) + }) + .ToList(); + + result.First(t => t.Count == 1).RowNumber.Should().Be(1); + result.First(t => t.Count == 2).RowNumber.Should().Be(1); + } + + [Fact] + public void Throws_if_RowNumber_contains_NewExpression() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", Count = 1, RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", Count = 2, RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var query = ActDbContext.TestEntities + .Select(e => new + { + e.Count, + RowNumber = EF.Functions.RowNumber(new { e.Name, e.Count }, + EF.Functions.OrderBy(e.Name).ThenBy(e.Count)) + }); + query.Invoking(q => q.ToList()).Should().Throw() + .WithMessage("The window function contains some expressions not supported by the Entity Framework. One of the reason is the creation of new objects like: 'new { e.MyProperty, e.MyOtherProperty }'."); + } + + [Fact] + public void Should_throw_if_accessing_RowNumber_not_within_subquery() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var query = ActDbContext.TestEntities + .Select(e => new + { + e.Name, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) + }) + .Where(i => i.RowNumber == 1); + + query.Invoking(q => q.ToList()) + .Should() + .Throw(); + } + + [Fact] + public void Should_be_able_to_fetch_whole_entity() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var query = ActDbContext.TestEntities + .Select(e => new + { + e, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) + }); + + var entities = query.ToList(); + entities.Should().HaveCount(1); + entities[0].Should().BeEquivalentTo(new + { + e = new TestEntity + { + Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), + Name = "1", + RequiredName = "RequiredName" + }, + RowNumber = 1 + }); + } + + [Fact] + public void Should_filter_for_RowNumber_if_accessing_within_subquery() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "2", RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.Name, + RowNumber = EF.Functions.RowNumber(EF.Functions.OrderBy(e.Name)) + }) + .AsSubQuery() + .Where(i => i.RowNumber == 1) + .ToList(); + + result.Should().HaveCount(1); + result[0].Name.Should().Be("1"); + } + + [Fact] + public void Should_support_columns_with_converters() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + e.ConvertibleClass, + RowNumber = EF.Functions.RowNumber(e.Name, + EF.Functions.OrderBy(e.Name).ThenBy(e.ConvertibleClass)) + }) + .ToList(); + + result.Should().HaveCount(2); + result.Select(e => e.ConvertibleClass?.Key).Should().BeEquivalentTo(new[] { 1, 2 }); + } + + [Fact] + public void Should_support_conversion_to_underlying_column_type() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + ConvertibleClass = (int)e.ConvertibleClass!, +#pragma warning disable CS8604 + RowNumber = EF.Functions.RowNumber(e.Name, (int)e.ConvertibleClass, (int)e.Count, + EF.Functions.OrderBy(e.Name).ThenBy((int)e.ConvertibleClass).ThenBy((int)e.Count)) +#pragma warning restore CS8604 + }) + .ToList(); + + result.Should().HaveCount(2); + result.Select(e => e.ConvertibleClass).Should().AllBeOfType().And.BeEquivalentTo(new[] { 1, 2 }); + } + + [Fact] + public void Should_support_conversion_if_column_type_is_convertable_on_database() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + ConvertibleClass = (long)e.ConvertibleClass!, +#pragma warning disable CS8604 + RowNumber = EF.Functions.RowNumber(e.Name, (long)e.ConvertibleClass, (long)e.Count, + EF.Functions.OrderBy(e.Name).ThenBy((long)e.ConvertibleClass).ThenBy((long)e.Count)) +#pragma warning restore CS8604 + }) + .ToList(); + + result.Should().HaveCount(2); + result.Select(e => e.ConvertibleClass).Should().AllBeOfType().And.BeEquivalentTo(new long[] { 1, 2 }); + } + + [Fact] + public void Should_support_conversion_for_non_trivial_expressions() + { + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("4883F7E0-FC8C-45FF-A579-DF351A3E79BF"), Name = "1", ConvertibleClass = new ConvertibleClass(1), RequiredName = "RequiredName" }); + ArrangeDbContext.TestEntities.Add(new TestEntity { Id = new Guid("18C13F68-0981-4853-92FC-FB7B2551F70A"), Name = "1", ConvertibleClass = new ConvertibleClass(2), RequiredName = "RequiredName" }); + ArrangeDbContext.SaveChanges(); + + var result = ActDbContext.TestEntities + .Select(e => new + { + ConvertibleClass = (long)e.ConvertibleClass!, +#pragma warning disable CS8604 + RowNumber = EF.Functions.RowNumber(e.Name, (long)e.ConvertibleClass + 1, (long)e.Count + 1, + EF.Functions.OrderBy(e.Name).ThenBy((long)e.ConvertibleClass + 1).ThenBy((long)e.Count + 1)) +#pragma warning restore CS8604 + }) + .ToList(); + + result.Should().HaveCount(2); + result.Select(e => e.ConvertibleClass).Should().AllBeOfType().And.BeEquivalentTo(new long[] { 1, 2 }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/IntegrationTestsBase.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/IntegrationTestsBase.cs index 02ca368a..23c90fda 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/IntegrationTestsBase.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/IntegrationTestsBase.cs @@ -1,24 +1,24 @@ -using Microsoft.Extensions.Logging; -using Thinktecture.EntityFrameworkCore.Testing; -using Thinktecture.TestDatabaseContext; -using Xunit.Extensions.AssemblyFixture; - -namespace Thinktecture; - -public abstract class IntegrationTestsBase : IAssemblyFixture -{ - protected ILoggerFactory LoggerFactory { get; } - protected SqliteTestDbContextProvider TestCtxProvider { get; } - - protected TestDbContext ArrangeDbContext => TestCtxProvider.ArrangeDbContext; - protected TestDbContext ActDbContext => TestCtxProvider.ActDbContext; - protected TestDbContext AssertDbContext => TestCtxProvider.AssertDbContext; - - protected IntegrationTestsBase( - ITestOutputHelper testOutputHelper, - DbContextProviderFactoryFixture providerFactoryFixture) - { - LoggerFactory = testOutputHelper.ToLoggerFactory(); - TestCtxProvider = providerFactoryFixture.CreateProvider(LoggerFactory); - } -} +using Microsoft.Extensions.Logging; +using Thinktecture.EntityFrameworkCore.Testing; +using Thinktecture.TestDatabaseContext; +using Xunit.Extensions.AssemblyFixture; + +namespace Thinktecture; + +public abstract class IntegrationTestsBase : IAssemblyFixture +{ + protected ILoggerFactory LoggerFactory { get; } + protected SqliteTestDbContextProvider TestCtxProvider { get; } + + protected TestDbContext ArrangeDbContext => TestCtxProvider.ArrangeDbContext; + protected TestDbContext ActDbContext => TestCtxProvider.ActDbContext; + protected TestDbContext AssertDbContext => TestCtxProvider.AssertDbContext; + + protected IntegrationTestsBase( + ITestOutputHelper testOutputHelper, + DbContextProviderFactoryFixture providerFactoryFixture) + { + LoggerFactory = testOutputHelper.ToLoggerFactory(); + TestCtxProvider = providerFactoryFixture.CreateProvider(LoggerFactory); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20190829184838_Initial_Migration.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20190829184838_Initial_Migration.Designer.cs index 673cfaa7..e5188480 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20190829184838_Initial_Migration.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20190829184838_Initial_Migration.Designer.cs @@ -1,70 +1,70 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20190829184838_Initial_Migration")] - partial class Initial_Migration - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Count"); - - b.Property("Name"); - - b.Property("PropertyWithBackingField"); - - b.Property("_privateField"); - - b.HasKey("Id"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Name"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd(); - - b.Property("Name"); - - b.Property("ShadowIntProperty"); - - b.Property("ShadowStringProperty") - .HasMaxLength(50); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20190829184838_Initial_Migration")] + partial class Initial_Migration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079"); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Count"); + + b.Property("Name"); + + b.Property("PropertyWithBackingField"); + + b.Property("_privateField"); + + b.HasKey("Id"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Name"); + + b.Property("ShadowIntProperty"); + + b.Property("ShadowStringProperty") + .HasMaxLength(50); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20190829184838_Initial_Migration.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20190829184838_Initial_Migration.cs index b319be85..6bad1d4d 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20190829184838_Initial_Migration.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20190829184838_Initial_Migration.cs @@ -1,49 +1,49 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - // ReSharper disable once InconsistentNaming - // ReSharper disable once UnusedMember.Global - public partial class Initial_Migration : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable("TestEntities", - table => new - { - Id = table.Column(), - Name = table.Column(nullable: true), - Count = table.Column(), - PropertyWithBackingField = table.Column(), - _privateField = table.Column() - }, - constraints: table => table.PrimaryKey("PK_TestEntities", x => x.Id)); - - migrationBuilder.CreateTable("TestEntitiesWithAutoIncrement", - table => new - { - Id = table.Column() - .Annotation("Sqlite:Autoincrement", true), - Name = table.Column(nullable: true) - }, - constraints: table => table.PrimaryKey("PK_TestEntitiesWithAutoIncrement", x => x.Id)); - - migrationBuilder.CreateTable("TestEntitiesWithShadowProperties", - table => new - { - Id = table.Column(), - Name = table.Column(nullable: true), - ShadowIntProperty = table.Column(nullable: true), - ShadowStringProperty = table.Column(maxLength: 50, nullable: true) - }, - constraints: table => table.PrimaryKey("PK_TestEntitiesWithShadowProperties", x => x.Id)); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable("TestEntities"); - migrationBuilder.DropTable("TestEntitiesWithAutoIncrement"); - migrationBuilder.DropTable("TestEntitiesWithShadowProperties"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + // ReSharper disable once InconsistentNaming + // ReSharper disable once UnusedMember.Global + public partial class Initial_Migration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable("TestEntities", + table => new + { + Id = table.Column(), + Name = table.Column(nullable: true), + Count = table.Column(), + PropertyWithBackingField = table.Column(), + _privateField = table.Column() + }, + constraints: table => table.PrimaryKey("PK_TestEntities", x => x.Id)); + + migrationBuilder.CreateTable("TestEntitiesWithAutoIncrement", + table => new + { + Id = table.Column() + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(nullable: true) + }, + constraints: table => table.PrimaryKey("PK_TestEntitiesWithAutoIncrement", x => x.Id)); + + migrationBuilder.CreateTable("TestEntitiesWithShadowProperties", + table => new + { + Id = table.Column(), + Name = table.Column(nullable: true), + ShadowIntProperty = table.Column(nullable: true), + ShadowStringProperty = table.Column(maxLength: 50, nullable: true) + }, + constraints: table => table.PrimaryKey("PK_TestEntitiesWithShadowProperties", x => x.Id)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("TestEntities"); + migrationBuilder.DropTable("TestEntitiesWithAutoIncrement"); + migrationBuilder.DropTable("TestEntitiesWithShadowProperties"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20191010194144_Add_ConvertibleClass.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20191010194144_Add_ConvertibleClass.Designer.cs index 52634b6c..761b73da 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20191010194144_Add_ConvertibleClass.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20191010194144_Add_ConvertibleClass.Designer.cs @@ -1,84 +1,84 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20191010194144_Add_ConvertibleClass")] - partial class Add_ConvertibleClass - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.0.0"); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ConvertibleClass") - .HasColumnType("INTEGER"); - - b.Property("Count") - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("PropertyWithBackingField") - .HasColumnType("INTEGER"); - - b.Property("_privateField") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("ShadowIntProperty") - .HasColumnType("INTEGER"); - - b.Property("ShadowStringProperty") - .HasColumnType("TEXT") - .HasMaxLength(50); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20191010194144_Add_ConvertibleClass")] + partial class Add_ConvertibleClass + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.0.0"); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConvertibleClass") + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("PropertyWithBackingField") + .HasColumnType("INTEGER"); + + b.Property("_privateField") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("ShadowIntProperty") + .HasColumnType("INTEGER"); + + b.Property("ShadowStringProperty") + .HasColumnType("TEXT") + .HasMaxLength(50); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20191010194144_Add_ConvertibleClass.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20191010194144_Add_ConvertibleClass.cs index 50776c54..3c47b940 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20191010194144_Add_ConvertibleClass.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20191010194144_Add_ConvertibleClass.cs @@ -1,18 +1,18 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - // ReSharper disable once InconsistentNaming - public partial class Add_ConvertibleClass : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn("ConvertibleClass", "TestEntities", nullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn("ConvertibleClass", "TestEntities"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + // ReSharper disable once InconsistentNaming + public partial class Add_ConvertibleClass : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn("ConvertibleClass", "TestEntities", nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn("ConvertibleClass", "TestEntities"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20200503090232_Add_tables_with_default_values.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20200503090232_Add_tables_with_default_values.Designer.cs index 5a52d214..68cc9099 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20200503090232_Add_tables_with_default_values.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20200503090232_Add_tables_with_default_values.Designer.cs @@ -1,149 +1,149 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20200503090232_Add_tables_with_default_values")] - partial class Add_tables_with_default_values - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "3.1.3"); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ConvertibleClass") - .HasColumnType("INTEGER"); - - b.Property("Count") - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("PropertyWithBackingField") - .HasColumnType("INTEGER"); - - b.Property("_privateField") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValue(1); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValue(2); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue("4"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue("3"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDotnetDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("ShadowIntProperty") - .HasColumnType("INTEGER"); - - b.Property("ShadowStringProperty") - .HasColumnType("TEXT") - .HasMaxLength(50); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValueSql("1"); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValueSql("2"); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("'4'"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("'3'"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDefaultValues"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20200503090232_Add_tables_with_default_values")] + partial class Add_tables_with_default_values + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.3"); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConvertibleClass") + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("PropertyWithBackingField") + .HasColumnType("INTEGER"); + + b.Property("_privateField") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(1); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(2); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("4"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("3"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDotnetDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("ShadowIntProperty") + .HasColumnType("INTEGER"); + + b.Property("ShadowStringProperty") + .HasColumnType("TEXT") + .HasMaxLength(50); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValueSql("1"); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValueSql("2"); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("'4'"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("'3'"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDefaultValues"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20200503090232_Add_tables_with_default_values.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20200503090232_Add_tables_with_default_values.cs index c490b417..295ce9dd 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20200503090232_Add_tables_with_default_values.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20200503090232_Add_tables_with_default_values.cs @@ -1,38 +1,38 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - public partial class Add_tables_with_default_values : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable("TestEntitiesWithDefaultValues", - table => new - { - Id = table.Column(nullable: false), - Int = table.Column(nullable: false, defaultValueSql: "1"), - NullableInt = table.Column(nullable: true, defaultValueSql: "2"), - String = table.Column(nullable: false, defaultValueSql: "'3'"), - NullableString = table.Column(nullable: true, defaultValueSql: "'4'") - }, - constraints: table => table.PrimaryKey("PK_TestEntitiesWithDefaultValues", x => x.Id)); - - migrationBuilder.CreateTable("TestEntitiesWithDotnetDefaultValues", - table => new - { - Id = table.Column(nullable: false, defaultValue: new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")), - Int = table.Column(nullable: false, defaultValue: 1), - NullableInt = table.Column(nullable: true, defaultValue: 2), - String = table.Column(nullable: false, defaultValue: "3"), - NullableString = table.Column(nullable: true, defaultValue: "4") - }, - constraints: table => table.PrimaryKey("PK_TestEntitiesWithDotnetDefaultValues", x => x.Id)); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable("TestEntitiesWithDefaultValues"); - migrationBuilder.DropTable("TestEntitiesWithDotnetDefaultValues"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + public partial class Add_tables_with_default_values : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable("TestEntitiesWithDefaultValues", + table => new + { + Id = table.Column(nullable: false), + Int = table.Column(nullable: false, defaultValueSql: "1"), + NullableInt = table.Column(nullable: true, defaultValueSql: "2"), + String = table.Column(nullable: false, defaultValueSql: "'3'"), + NullableString = table.Column(nullable: true, defaultValueSql: "'4'") + }, + constraints: table => table.PrimaryKey("PK_TestEntitiesWithDefaultValues", x => x.Id)); + + migrationBuilder.CreateTable("TestEntitiesWithDotnetDefaultValues", + table => new + { + Id = table.Column(nullable: false, defaultValue: new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")), + Int = table.Column(nullable: false, defaultValue: 1), + NullableInt = table.Column(nullable: true, defaultValue: 2), + String = table.Column(nullable: false, defaultValue: "3"), + NullableString = table.Column(nullable: true, defaultValue: "4") + }, + constraints: table => table.PrimaryKey("PK_TestEntitiesWithDotnetDefaultValues", x => x.Id)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("TestEntitiesWithDefaultValues"); + migrationBuilder.DropTable("TestEntitiesWithDotnetDefaultValues"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210128201544_Added_Parent_Children.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210128201544_Added_Parent_Children.Designer.cs index 208c7718..5a7d67a1 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210128201544_Added_Parent_Children.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210128201544_Added_Parent_Children.Designer.cs @@ -1,236 +1,236 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20210128201544_Added_Parent_Children")] - partial class Added_Parent_Children - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.2"); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteIndex", b => - { - b.Property("Name") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Origin") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Partial") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Seq") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Unique") - .IsRequired() - .HasColumnType("BLOB"); - - b.ToView("pragma temp.index_list('<>')"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteMaster", b => - { - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Rootpage") - .HasColumnType("INTEGER"); - - b.Property("Sql") - .HasColumnType("TEXT"); - - b.Property("Tbl_Name") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("TEXT"); - - b.ToView("sqlite_temp_master"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteTableInfo", b => - { - b.Property("CId") - .HasColumnType("INTEGER"); - - b.Property("Dflt_Value") - .HasColumnType("BLOB"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("NotNull") - .HasColumnType("INTEGER"); - - b.Property("PK") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("TEXT"); - - b.ToView("PRAGMA_TABLE_INFO('<>')"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ConvertibleClass") - .HasColumnType("INTEGER"); - - b.Property("Count") - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("ParentId") - .HasColumnType("TEXT"); - - b.Property("PropertyWithBackingField") - .HasColumnType("INTEGER"); - - b.Property("_privateField") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("ParentId"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValue(1); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValue(2); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue("4"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue("3"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDotnetDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("ShadowIntProperty") - .HasColumnType("INTEGER"); - - b.Property("ShadowStringProperty") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValueSql("1"); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValueSql("2"); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("'4'"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("'3'"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Navigation("Children"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20210128201544_Added_Parent_Children")] + partial class Added_Parent_Children + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.2"); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteIndex", b => + { + b.Property("Name") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Origin") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Partial") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Seq") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Unique") + .IsRequired() + .HasColumnType("BLOB"); + + b.ToView("pragma temp.index_list('<>')"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteMaster", b => + { + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Rootpage") + .HasColumnType("INTEGER"); + + b.Property("Sql") + .HasColumnType("TEXT"); + + b.Property("Tbl_Name") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("TEXT"); + + b.ToView("sqlite_temp_master"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteTableInfo", b => + { + b.Property("CId") + .HasColumnType("INTEGER"); + + b.Property("Dflt_Value") + .HasColumnType("BLOB"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NotNull") + .HasColumnType("INTEGER"); + + b.Property("PK") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("TEXT"); + + b.ToView("PRAGMA_TABLE_INFO('<>')"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConvertibleClass") + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("ParentId") + .HasColumnType("TEXT"); + + b.Property("PropertyWithBackingField") + .HasColumnType("INTEGER"); + + b.Property("_privateField") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(1); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(2); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("4"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("3"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDotnetDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("ShadowIntProperty") + .HasColumnType("INTEGER"); + + b.Property("ShadowStringProperty") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValueSql("1"); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValueSql("2"); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("'4'"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("'3'"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Navigation("Children"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210128201544_Added_Parent_Children.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210128201544_Added_Parent_Children.cs index b233285b..1d94b924 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210128201544_Added_Parent_Children.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210128201544_Added_Parent_Children.cs @@ -1,22 +1,22 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - public partial class Added_Parent_Children : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn("ParentId", "TestEntities", "TEXT", nullable: true); - migrationBuilder.CreateIndex("IX_TestEntities_ParentId", "TestEntities", "ParentId"); - - migrationBuilder.AddForeignKey("FK_TestEntities_TestEntities_ParentId", "TestEntities", "ParentId", "TestEntities", principalColumn: "Id", onDelete: ReferentialAction.Restrict); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey("FK_TestEntities_TestEntities_ParentId", "TestEntities"); - migrationBuilder.DropIndex("IX_TestEntities_ParentId", "TestEntities"); - migrationBuilder.DropColumn("ParentId", "TestEntities"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + public partial class Added_Parent_Children : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn("ParentId", "TestEntities", "TEXT", nullable: true); + migrationBuilder.CreateIndex("IX_TestEntities_ParentId", "TestEntities", "ParentId"); + + migrationBuilder.AddForeignKey("FK_TestEntities_TestEntities_ParentId", "TestEntities", "ParentId", "TestEntities", principalColumn: "Id", onDelete: ReferentialAction.Restrict); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey("FK_TestEntities_TestEntities_ParentId", "TestEntities"); + migrationBuilder.DropIndex("IX_TestEntities_ParentId", "TestEntities"); + migrationBuilder.DropColumn("ParentId", "TestEntities"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210306145353_Add_tables_with_owned_entities.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210306145353_Add_tables_with_owned_entities.Designer.cs index 83f62b24..854e00ea 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210306145353_Add_tables_with_owned_entities.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210306145353_Add_tables_with_owned_entities.Designer.cs @@ -1,894 +1,894 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20210306145353_Add_tables_with_owned_entities")] - partial class Add_tables_with_owned_entities - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "5.0.3"); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteIndex", b => - { - b.Property("Name") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Origin") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Partial") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Seq") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Unique") - .IsRequired() - .HasColumnType("BLOB"); - - b.ToView("pragma temp.index_list('<>')"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteMaster", b => - { - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Rootpage") - .HasColumnType("INTEGER"); - - b.Property("Sql") - .HasColumnType("TEXT"); - - b.Property("Tbl_Name") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("TEXT"); - - b.ToView("sqlite_temp_master"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteTableInfo", b => - { - b.Property("CId") - .HasColumnType("INTEGER"); - - b.Property("Dflt_Value") - .HasColumnType("BLOB"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("NotNull") - .HasColumnType("INTEGER"); - - b.Property("PK") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("TEXT"); - - b.ToView("PRAGMA_TABLE_INFO('<>')"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ConvertibleClass") - .HasColumnType("INTEGER"); - - b.Property("Count") - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("ParentId") - .HasColumnType("TEXT"); - - b.Property("PropertyWithBackingField") - .HasColumnType("INTEGER"); - - b.Property("_privateField") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("ParentId"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValue(1); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValue(2); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue("4"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue("3"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDotnetDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("ShadowIntProperty") - .HasColumnType("INTEGER"); - - b.Property("ShadowStringProperty") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValueSql("1"); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValueSql("2"); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("'4'"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("'3'"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_InlineId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_InlineId"); - - b1.ToTable("TestEntities_Own_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_InlineId"); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_InlineId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_Inline_InlineId"); - - b1.ToTable("TestEntities_Own_Inline_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId") - .HasColumnType("TEXT"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); - - b2.ToTable("TestEntities_Own_Inline_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_SeparateManyId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_Inline_SeparateManyId"); - - b1.ToTable("TestEntities_Own_Inline_SeparateMany"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId") - .HasColumnType("TEXT"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", "Id"); - - b2.ToTable("InlineEntities_SeparateMany"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_SeparateOneId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_Inline_SeparateOneId"); - - b1.ToTable("TestEntities_Own_Inline_SeparateOne"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId") - .HasColumnType("TEXT"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); - - b2.ToTable("InlineEntities_SeparateOne"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateManyId") - .HasColumnType("TEXT"); - - b1.Property("Id") - .HasColumnType("INTEGER"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateManyId", "Id"); - - b1.ToTable("SeparateEntitiesMany"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateManyId"); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_InlineId") - .HasColumnType("TEXT"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateMany_InlineId", "Id"); - - b1.ToTable("SeparateEntitiesMany_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId") - .HasColumnType("TEXT"); - - b2.Property("OwnedEntity_Owns_InlineId") - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); - - b2.ToTable("SeparateEntitiesMany_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_SeparateManyId") - .HasColumnType("TEXT"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateMany_SeparateManyId", "Id"); - - b1.ToTable("SeparateEntitiesMany_SeparateEntitiesMany"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId") - .HasColumnType("TEXT"); - - b2.Property("OwnedEntity_Owns_SeparateManyId") - .HasColumnType("INTEGER"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId", "Id"); - - b2.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_SeparateOneId") - .HasColumnType("TEXT"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateMany_SeparateOneId", "Id"); - - b1.ToTable("SeparateEntitiesMany_SeparateEntitiesOne"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId") - .HasColumnType("TEXT"); - - b2.Property("OwnedEntity_Owns_SeparateOneId") - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); - - b2.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOneId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateOneId"); - - b1.ToTable("SeparateEntitiesOne"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOneId"); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_InlineId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateOne_InlineId"); - - b1.ToTable("SeparateEntitiesOne_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId") - .HasColumnType("TEXT"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); - - b2.ToTable("SeparateEntitiesOne_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_SeparateManyId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateOne_SeparateManyId"); - - b1.ToTable("SeparateEntitiesOne_SeparateMany"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId") - .HasColumnType("TEXT"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", "Id"); - - b2.ToTable("SeparateEntitiesOne_SeparateMany_Inner"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_SeparateOneId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateOne_SeparateOneId"); - - b1.ToTable("SeparateEntitiesOne_SeparateOne"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId") - .HasColumnType("TEXT"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); - - b2.ToTable("SeparateEntitiesOne_SeparateOne_Inner"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Navigation("Children"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20210306145353_Add_tables_with_owned_entities")] + partial class Add_tables_with_owned_entities + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.3"); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteIndex", b => + { + b.Property("Name") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Origin") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Partial") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Seq") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Unique") + .IsRequired() + .HasColumnType("BLOB"); + + b.ToView("pragma temp.index_list('<>')"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteMaster", b => + { + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Rootpage") + .HasColumnType("INTEGER"); + + b.Property("Sql") + .HasColumnType("TEXT"); + + b.Property("Tbl_Name") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("TEXT"); + + b.ToView("sqlite_temp_master"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteTableInfo", b => + { + b.Property("CId") + .HasColumnType("INTEGER"); + + b.Property("Dflt_Value") + .HasColumnType("BLOB"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NotNull") + .HasColumnType("INTEGER"); + + b.Property("PK") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("TEXT"); + + b.ToView("PRAGMA_TABLE_INFO('<>')"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConvertibleClass") + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("ParentId") + .HasColumnType("TEXT"); + + b.Property("PropertyWithBackingField") + .HasColumnType("INTEGER"); + + b.Property("_privateField") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(1); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(2); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("4"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("3"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDotnetDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("ShadowIntProperty") + .HasColumnType("INTEGER"); + + b.Property("ShadowStringProperty") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValueSql("1"); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValueSql("2"); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("'4'"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("'3'"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_InlineId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_InlineId"); + + b1.ToTable("TestEntities_Own_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_InlineId"); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_InlineId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_Inline_InlineId"); + + b1.ToTable("TestEntities_Own_Inline_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId") + .HasColumnType("TEXT"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); + + b2.ToTable("TestEntities_Own_Inline_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_SeparateManyId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_Inline_SeparateManyId"); + + b1.ToTable("TestEntities_Own_Inline_SeparateMany"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId") + .HasColumnType("TEXT"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", "Id"); + + b2.ToTable("InlineEntities_SeparateMany"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_SeparateOneId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_Inline_SeparateOneId"); + + b1.ToTable("TestEntities_Own_Inline_SeparateOne"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId") + .HasColumnType("TEXT"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); + + b2.ToTable("InlineEntities_SeparateOne"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateManyId") + .HasColumnType("TEXT"); + + b1.Property("Id") + .HasColumnType("INTEGER"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateManyId", "Id"); + + b1.ToTable("SeparateEntitiesMany"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateManyId"); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_InlineId") + .HasColumnType("TEXT"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateMany_InlineId", "Id"); + + b1.ToTable("SeparateEntitiesMany_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId") + .HasColumnType("TEXT"); + + b2.Property("OwnedEntity_Owns_InlineId") + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); + + b2.ToTable("SeparateEntitiesMany_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_SeparateManyId") + .HasColumnType("TEXT"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateMany_SeparateManyId", "Id"); + + b1.ToTable("SeparateEntitiesMany_SeparateEntitiesMany"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId") + .HasColumnType("TEXT"); + + b2.Property("OwnedEntity_Owns_SeparateManyId") + .HasColumnType("INTEGER"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId", "Id"); + + b2.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_SeparateOneId") + .HasColumnType("TEXT"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateMany_SeparateOneId", "Id"); + + b1.ToTable("SeparateEntitiesMany_SeparateEntitiesOne"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId") + .HasColumnType("TEXT"); + + b2.Property("OwnedEntity_Owns_SeparateOneId") + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); + + b2.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOneId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateOneId"); + + b1.ToTable("SeparateEntitiesOne"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOneId"); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_InlineId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateOne_InlineId"); + + b1.ToTable("SeparateEntitiesOne_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId") + .HasColumnType("TEXT"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); + + b2.ToTable("SeparateEntitiesOne_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_SeparateManyId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateOne_SeparateManyId"); + + b1.ToTable("SeparateEntitiesOne_SeparateMany"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId") + .HasColumnType("TEXT"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", "Id"); + + b2.ToTable("SeparateEntitiesOne_SeparateMany_Inner"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_SeparateOneId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateOne_SeparateOneId"); + + b1.ToTable("SeparateEntitiesOne_SeparateOne"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId") + .HasColumnType("TEXT"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); + + b2.ToTable("SeparateEntitiesOne_SeparateOne_Inner"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Navigation("Children"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210306145353_Add_tables_with_owned_entities.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210306145353_Add_tables_with_owned_entities.cs index 92b5a21c..8a125f24 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210306145353_Add_tables_with_owned_entities.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20210306145353_Add_tables_with_owned_entities.cs @@ -1,399 +1,399 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace Thinktecture.Migrations -{ - public partial class Add_tables_with_owned_entities : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable("TestEntities_Own_Inline", - table => new - { - Id = table.Column("TEXT", nullable: false), - InlineEntity_StringColumn = table.Column("TEXT", nullable: true), - InlineEntity_IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_Inline_Inline", - table => new - { - Id = table.Column("TEXT", nullable: false), - InlineEntity_StringColumn = table.Column("TEXT", nullable: true), - InlineEntity_IntColumn = table.Column("INTEGER", nullable: false), - InlineEntity_InlineEntity_StringColumn = table.Column("TEXT", nullable: true), - InlineEntity_InlineEntity_IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline_Inline", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_Inline_SeparateMany", - table => new - { - Id = table.Column("TEXT", nullable: false), - InlineEntity_StringColumn = table.Column("TEXT", nullable: true), - InlineEntity_IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline_SeparateMany", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_Inline_SeparateOne", - table => new - { - Id = table.Column("TEXT", nullable: false), - InlineEntity_StringColumn = table.Column("TEXT", nullable: true), - InlineEntity_IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline_SeparateOne", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateMany", - table => new - { - Id = table.Column("TEXT", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateMany_Inline", - table => new - { - Id = table.Column("TEXT", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany_Inline", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateMany_SeparateMany", - table => new - { - Id = table.Column("TEXT", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany_SeparateMany", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateMany_SeparateOne", - table => new - { - Id = table.Column("TEXT", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany_SeparateOne", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateOne", - table => new - { - Id = table.Column("TEXT", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateOne_Inline", - table => new - { - Id = table.Column("TEXT", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne_Inline", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateOne_SeparateMany", - table => new - { - Id = table.Column("TEXT", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne_SeparateMany", x => x.Id)); - - migrationBuilder.CreateTable("TestEntities_Own_SeparateOne_SeparateOne", - table => new - { - Id = table.Column("TEXT", nullable: false) - }, - constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne_SeparateOne", x => x.Id)); - - migrationBuilder.CreateTable("InlineEntities_SeparateMany", - table => new - { - OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId = table.Column("TEXT", nullable: false), - Id = table.Column("INTEGER", nullable: false), - StringColumn = table.Column("TEXT", nullable: true), - IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_InlineEntities_SeparateMany", x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId, x.Id }); - table.ForeignKey( - "FK_InlineEntities_SeparateMany_TestEntities_Own_Inline_SeparateMany_OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", - x => x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId, - "TestEntities_Own_Inline_SeparateMany", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("InlineEntities_SeparateOne", - table => new - { - OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId = table.Column("TEXT", nullable: false), - StringColumn = table.Column("TEXT", nullable: true), - IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_InlineEntities_SeparateOne", x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId); - table.ForeignKey( - "FK_InlineEntities_SeparateOne_TestEntities_Own_Inline_SeparateOne_OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId", - x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId, - "TestEntities_Own_Inline_SeparateOne", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesMany", - table => new - { - TestEntity_Owns_SeparateManyId = table.Column("TEXT", nullable: false), - Id = table.Column("INTEGER", nullable: false), - StringColumn = table.Column("TEXT", nullable: true), - IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesMany", x => new { x.TestEntity_Owns_SeparateManyId, x.Id }); - table.ForeignKey( - "FK_SeparateEntitiesMany_TestEntities_Own_SeparateMany_TestEntity_Owns_SeparateManyId", - x => x.TestEntity_Owns_SeparateManyId, - "TestEntities_Own_SeparateMany", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesMany_Inline", - table => new - { - TestEntity_Owns_SeparateMany_InlineId = table.Column("TEXT", nullable: false), - Id = table.Column("INTEGER", nullable: false), - StringColumn = table.Column("TEXT", nullable: true), - IntColumn = table.Column("INTEGER", nullable: false), - InlineEntity_StringColumn = table.Column("TEXT", nullable: true), - InlineEntity_IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesMany_Inline", x => new { x.TestEntity_Owns_SeparateMany_InlineId, x.Id }); - table.ForeignKey( - "FK_SeparateEntitiesMany_Inline_TestEntities_Own_SeparateMany_Inline_TestEntity_Owns_SeparateMany_InlineId", - x => x.TestEntity_Owns_SeparateMany_InlineId, - "TestEntities_Own_SeparateMany_Inline", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesMany", - table => new - { - TestEntity_Owns_SeparateMany_SeparateManyId = table.Column("TEXT", nullable: false), - Id = table.Column("INTEGER", nullable: false), - StringColumn = table.Column("TEXT", nullable: true), - IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesMany", x => new { x.TestEntity_Owns_SeparateMany_SeparateManyId, x.Id }); - table.ForeignKey( - "FK_SeparateEntitiesMany_SeparateEntitiesMany_TestEntities_Own_SeparateMany_SeparateMany_TestEntity_Owns_SeparateMany_SeparateManyId", - x => x.TestEntity_Owns_SeparateMany_SeparateManyId, - "TestEntities_Own_SeparateMany_SeparateMany", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesOne", - table => new - { - TestEntity_Owns_SeparateMany_SeparateOneId = table.Column("TEXT", nullable: false), - Id = table.Column("INTEGER", nullable: false), - StringColumn = table.Column("TEXT", nullable: true), - IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesOne", x => new { x.TestEntity_Owns_SeparateMany_SeparateOneId, x.Id }); - table.ForeignKey( - "FK_SeparateEntitiesMany_SeparateEntitiesOne_TestEntities_Own_SeparateMany_SeparateOne_TestEntity_Owns_SeparateMany_SeparateOneId", - x => x.TestEntity_Owns_SeparateMany_SeparateOneId, - "TestEntities_Own_SeparateMany_SeparateOne", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesOne", - table => new - { - TestEntity_Owns_SeparateOneId = table.Column("TEXT", nullable: false), - StringColumn = table.Column("TEXT", nullable: true), - IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesOne", x => x.TestEntity_Owns_SeparateOneId); - table.ForeignKey( - "FK_SeparateEntitiesOne_TestEntities_Own_SeparateOne_TestEntity_Owns_SeparateOneId", - x => x.TestEntity_Owns_SeparateOneId, - "TestEntities_Own_SeparateOne", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesOne_Inline", - table => new - { - TestEntity_Owns_SeparateOne_InlineId = table.Column("TEXT", nullable: false), - StringColumn = table.Column("TEXT", nullable: true), - IntColumn = table.Column("INTEGER", nullable: false), - InlineEntity_StringColumn = table.Column("TEXT", nullable: true), - InlineEntity_IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesOne_Inline", x => x.TestEntity_Owns_SeparateOne_InlineId); - table.ForeignKey( - "FK_SeparateEntitiesOne_Inline_TestEntities_Own_SeparateOne_Inline_TestEntity_Owns_SeparateOne_InlineId", - x => x.TestEntity_Owns_SeparateOne_InlineId, - "TestEntities_Own_SeparateOne_Inline", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateMany", - table => new - { - TestEntity_Owns_SeparateOne_SeparateManyId = table.Column("TEXT", nullable: false), - StringColumn = table.Column("TEXT", nullable: true), - IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesOne_SeparateMany", x => x.TestEntity_Owns_SeparateOne_SeparateManyId); - table.ForeignKey( - "FK_SeparateEntitiesOne_SeparateMany_TestEntities_Own_SeparateOne_SeparateMany_TestEntity_Owns_SeparateOne_SeparateManyId", - x => x.TestEntity_Owns_SeparateOne_SeparateManyId, - "TestEntities_Own_SeparateOne_SeparateMany", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateOne", - table => new - { - TestEntity_Owns_SeparateOne_SeparateOneId = table.Column("TEXT", nullable: false), - StringColumn = table.Column("TEXT", nullable: true), - IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesOne_SeparateOne", x => x.TestEntity_Owns_SeparateOne_SeparateOneId); - table.ForeignKey( - "FK_SeparateEntitiesOne_SeparateOne_TestEntities_Own_SeparateOne_SeparateOne_TestEntity_Owns_SeparateOne_SeparateOneId", - x => x.TestEntity_Owns_SeparateOne_SeparateOneId, - "TestEntities_Own_SeparateOne_SeparateOne", - "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner", - table => new - { - OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId = table.Column("TEXT", nullable: false), - OwnedEntity_Owns_SeparateManyId = table.Column("INTEGER", nullable: false), - Id = table.Column("INTEGER", nullable: false), - StringColumn = table.Column("TEXT", nullable: true), - IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesMany_Inner", x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId, x.OwnedEntity_Owns_SeparateManyId, x.Id }); - table.ForeignKey( - "FK_SeparateEntitiesMany_SeparateEntitiesMany_Inner_SeparateEntitiesMany_SeparateEntitiesMany_OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId_OwnedEntity_Owns_SeparateManyId", - x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId, x.OwnedEntity_Owns_SeparateManyId }, - "SeparateEntitiesMany_SeparateEntitiesMany", - new[] { "TestEntity_Owns_SeparateMany_SeparateManyId", "Id" }, - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner", - table => new - { - OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId = table.Column("TEXT", nullable: false), - OwnedEntity_Owns_SeparateOneId = table.Column("INTEGER", nullable: false), - StringColumn = table.Column("TEXT", nullable: true), - IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesOne_Inner", x => new { x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId, x.OwnedEntity_Owns_SeparateOneId }); - table.ForeignKey( - "FK_SeparateEntitiesMany_SeparateEntitiesOne_Inner_SeparateEntitiesMany_SeparateEntitiesOne_OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId_OwnedEntity_Owns_SeparateOneId", - x => new { x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId, x.OwnedEntity_Owns_SeparateOneId }, - "SeparateEntitiesMany_SeparateEntitiesOne", - new[] { "TestEntity_Owns_SeparateMany_SeparateOneId", "Id" }, - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateMany_Inner", - table => new - { - OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId = table.Column("TEXT", nullable: false), - Id = table.Column("INTEGER", nullable: false), - StringColumn = table.Column("TEXT", nullable: true), - IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesOne_SeparateMany_Inner", x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId, x.Id }); - table.ForeignKey( - "FK_SeparateEntitiesOne_SeparateMany_Inner_SeparateEntitiesOne_SeparateMany_OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", - x => x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId, - "SeparateEntitiesOne_SeparateMany", - "TestEntity_Owns_SeparateOne_SeparateManyId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateOne_Inner", - table => new - { - OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId = table.Column("TEXT", nullable: false), - StringColumn = table.Column("TEXT", nullable: true), - IntColumn = table.Column("INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_SeparateEntitiesOne_SeparateOne_Inner", x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId); - table.ForeignKey( - "FK_SeparateEntitiesOne_SeparateOne_Inner_SeparateEntitiesOne_SeparateOne_OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId", - x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId, - "SeparateEntitiesOne_SeparateOne", - "TestEntity_Owns_SeparateOne_SeparateOneId", - onDelete: ReferentialAction.Cascade); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable("InlineEntities_SeparateMany"); - migrationBuilder.DropTable("InlineEntities_SeparateOne"); - migrationBuilder.DropTable("SeparateEntitiesMany"); - migrationBuilder.DropTable("SeparateEntitiesMany_Inline"); - migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner"); - migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner"); - migrationBuilder.DropTable("SeparateEntitiesOne"); - migrationBuilder.DropTable("SeparateEntitiesOne_Inline"); - migrationBuilder.DropTable("SeparateEntitiesOne_SeparateMany_Inner"); - migrationBuilder.DropTable("SeparateEntitiesOne_SeparateOne_Inner"); - migrationBuilder.DropTable("TestEntities_Own_Inline"); - migrationBuilder.DropTable("TestEntities_Own_Inline_Inline"); - migrationBuilder.DropTable("TestEntities_Own_Inline_SeparateMany"); - migrationBuilder.DropTable("TestEntities_Own_Inline_SeparateOne"); - migrationBuilder.DropTable("TestEntities_Own_SeparateMany"); - migrationBuilder.DropTable("TestEntities_Own_SeparateMany_Inline"); - migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesMany"); - migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesOne"); - migrationBuilder.DropTable("TestEntities_Own_SeparateOne"); - migrationBuilder.DropTable("TestEntities_Own_SeparateOne_Inline"); - migrationBuilder.DropTable("SeparateEntitiesOne_SeparateMany"); - migrationBuilder.DropTable("SeparateEntitiesOne_SeparateOne"); - migrationBuilder.DropTable("TestEntities_Own_SeparateMany_SeparateMany"); - migrationBuilder.DropTable("TestEntities_Own_SeparateMany_SeparateOne"); - migrationBuilder.DropTable("TestEntities_Own_SeparateOne_SeparateMany"); - migrationBuilder.DropTable("TestEntities_Own_SeparateOne_SeparateOne"); - } - } -} +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Thinktecture.Migrations +{ + public partial class Add_tables_with_owned_entities : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable("TestEntities_Own_Inline", + table => new + { + Id = table.Column("TEXT", nullable: false), + InlineEntity_StringColumn = table.Column("TEXT", nullable: true), + InlineEntity_IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_Inline_Inline", + table => new + { + Id = table.Column("TEXT", nullable: false), + InlineEntity_StringColumn = table.Column("TEXT", nullable: true), + InlineEntity_IntColumn = table.Column("INTEGER", nullable: false), + InlineEntity_InlineEntity_StringColumn = table.Column("TEXT", nullable: true), + InlineEntity_InlineEntity_IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline_Inline", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_Inline_SeparateMany", + table => new + { + Id = table.Column("TEXT", nullable: false), + InlineEntity_StringColumn = table.Column("TEXT", nullable: true), + InlineEntity_IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline_SeparateMany", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_Inline_SeparateOne", + table => new + { + Id = table.Column("TEXT", nullable: false), + InlineEntity_StringColumn = table.Column("TEXT", nullable: true), + InlineEntity_IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_Inline_SeparateOne", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateMany", + table => new + { + Id = table.Column("TEXT", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateMany_Inline", + table => new + { + Id = table.Column("TEXT", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany_Inline", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateMany_SeparateMany", + table => new + { + Id = table.Column("TEXT", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany_SeparateMany", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateMany_SeparateOne", + table => new + { + Id = table.Column("TEXT", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateMany_SeparateOne", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateOne", + table => new + { + Id = table.Column("TEXT", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateOne_Inline", + table => new + { + Id = table.Column("TEXT", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne_Inline", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateOne_SeparateMany", + table => new + { + Id = table.Column("TEXT", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne_SeparateMany", x => x.Id)); + + migrationBuilder.CreateTable("TestEntities_Own_SeparateOne_SeparateOne", + table => new + { + Id = table.Column("TEXT", nullable: false) + }, + constraints: table => table.PrimaryKey("PK_TestEntities_Own_SeparateOne_SeparateOne", x => x.Id)); + + migrationBuilder.CreateTable("InlineEntities_SeparateMany", + table => new + { + OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId = table.Column("TEXT", nullable: false), + Id = table.Column("INTEGER", nullable: false), + StringColumn = table.Column("TEXT", nullable: true), + IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_InlineEntities_SeparateMany", x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId, x.Id }); + table.ForeignKey( + "FK_InlineEntities_SeparateMany_TestEntities_Own_Inline_SeparateMany_OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", + x => x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId, + "TestEntities_Own_Inline_SeparateMany", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("InlineEntities_SeparateOne", + table => new + { + OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId = table.Column("TEXT", nullable: false), + StringColumn = table.Column("TEXT", nullable: true), + IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_InlineEntities_SeparateOne", x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId); + table.ForeignKey( + "FK_InlineEntities_SeparateOne_TestEntities_Own_Inline_SeparateOne_OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId", + x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId, + "TestEntities_Own_Inline_SeparateOne", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesMany", + table => new + { + TestEntity_Owns_SeparateManyId = table.Column("TEXT", nullable: false), + Id = table.Column("INTEGER", nullable: false), + StringColumn = table.Column("TEXT", nullable: true), + IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesMany", x => new { x.TestEntity_Owns_SeparateManyId, x.Id }); + table.ForeignKey( + "FK_SeparateEntitiesMany_TestEntities_Own_SeparateMany_TestEntity_Owns_SeparateManyId", + x => x.TestEntity_Owns_SeparateManyId, + "TestEntities_Own_SeparateMany", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesMany_Inline", + table => new + { + TestEntity_Owns_SeparateMany_InlineId = table.Column("TEXT", nullable: false), + Id = table.Column("INTEGER", nullable: false), + StringColumn = table.Column("TEXT", nullable: true), + IntColumn = table.Column("INTEGER", nullable: false), + InlineEntity_StringColumn = table.Column("TEXT", nullable: true), + InlineEntity_IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesMany_Inline", x => new { x.TestEntity_Owns_SeparateMany_InlineId, x.Id }); + table.ForeignKey( + "FK_SeparateEntitiesMany_Inline_TestEntities_Own_SeparateMany_Inline_TestEntity_Owns_SeparateMany_InlineId", + x => x.TestEntity_Owns_SeparateMany_InlineId, + "TestEntities_Own_SeparateMany_Inline", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesMany", + table => new + { + TestEntity_Owns_SeparateMany_SeparateManyId = table.Column("TEXT", nullable: false), + Id = table.Column("INTEGER", nullable: false), + StringColumn = table.Column("TEXT", nullable: true), + IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesMany", x => new { x.TestEntity_Owns_SeparateMany_SeparateManyId, x.Id }); + table.ForeignKey( + "FK_SeparateEntitiesMany_SeparateEntitiesMany_TestEntities_Own_SeparateMany_SeparateMany_TestEntity_Owns_SeparateMany_SeparateManyId", + x => x.TestEntity_Owns_SeparateMany_SeparateManyId, + "TestEntities_Own_SeparateMany_SeparateMany", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesOne", + table => new + { + TestEntity_Owns_SeparateMany_SeparateOneId = table.Column("TEXT", nullable: false), + Id = table.Column("INTEGER", nullable: false), + StringColumn = table.Column("TEXT", nullable: true), + IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesOne", x => new { x.TestEntity_Owns_SeparateMany_SeparateOneId, x.Id }); + table.ForeignKey( + "FK_SeparateEntitiesMany_SeparateEntitiesOne_TestEntities_Own_SeparateMany_SeparateOne_TestEntity_Owns_SeparateMany_SeparateOneId", + x => x.TestEntity_Owns_SeparateMany_SeparateOneId, + "TestEntities_Own_SeparateMany_SeparateOne", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesOne", + table => new + { + TestEntity_Owns_SeparateOneId = table.Column("TEXT", nullable: false), + StringColumn = table.Column("TEXT", nullable: true), + IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesOne", x => x.TestEntity_Owns_SeparateOneId); + table.ForeignKey( + "FK_SeparateEntitiesOne_TestEntities_Own_SeparateOne_TestEntity_Owns_SeparateOneId", + x => x.TestEntity_Owns_SeparateOneId, + "TestEntities_Own_SeparateOne", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesOne_Inline", + table => new + { + TestEntity_Owns_SeparateOne_InlineId = table.Column("TEXT", nullable: false), + StringColumn = table.Column("TEXT", nullable: true), + IntColumn = table.Column("INTEGER", nullable: false), + InlineEntity_StringColumn = table.Column("TEXT", nullable: true), + InlineEntity_IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesOne_Inline", x => x.TestEntity_Owns_SeparateOne_InlineId); + table.ForeignKey( + "FK_SeparateEntitiesOne_Inline_TestEntities_Own_SeparateOne_Inline_TestEntity_Owns_SeparateOne_InlineId", + x => x.TestEntity_Owns_SeparateOne_InlineId, + "TestEntities_Own_SeparateOne_Inline", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateMany", + table => new + { + TestEntity_Owns_SeparateOne_SeparateManyId = table.Column("TEXT", nullable: false), + StringColumn = table.Column("TEXT", nullable: true), + IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesOne_SeparateMany", x => x.TestEntity_Owns_SeparateOne_SeparateManyId); + table.ForeignKey( + "FK_SeparateEntitiesOne_SeparateMany_TestEntities_Own_SeparateOne_SeparateMany_TestEntity_Owns_SeparateOne_SeparateManyId", + x => x.TestEntity_Owns_SeparateOne_SeparateManyId, + "TestEntities_Own_SeparateOne_SeparateMany", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateOne", + table => new + { + TestEntity_Owns_SeparateOne_SeparateOneId = table.Column("TEXT", nullable: false), + StringColumn = table.Column("TEXT", nullable: true), + IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesOne_SeparateOne", x => x.TestEntity_Owns_SeparateOne_SeparateOneId); + table.ForeignKey( + "FK_SeparateEntitiesOne_SeparateOne_TestEntities_Own_SeparateOne_SeparateOne_TestEntity_Owns_SeparateOne_SeparateOneId", + x => x.TestEntity_Owns_SeparateOne_SeparateOneId, + "TestEntities_Own_SeparateOne_SeparateOne", + "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner", + table => new + { + OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId = table.Column("TEXT", nullable: false), + OwnedEntity_Owns_SeparateManyId = table.Column("INTEGER", nullable: false), + Id = table.Column("INTEGER", nullable: false), + StringColumn = table.Column("TEXT", nullable: true), + IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesMany_Inner", x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId, x.OwnedEntity_Owns_SeparateManyId, x.Id }); + table.ForeignKey( + "FK_SeparateEntitiesMany_SeparateEntitiesMany_Inner_SeparateEntitiesMany_SeparateEntitiesMany_OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId_OwnedEntity_Owns_SeparateManyId", + x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId, x.OwnedEntity_Owns_SeparateManyId }, + "SeparateEntitiesMany_SeparateEntitiesMany", + new[] { "TestEntity_Owns_SeparateMany_SeparateManyId", "Id" }, + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner", + table => new + { + OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId = table.Column("TEXT", nullable: false), + OwnedEntity_Owns_SeparateOneId = table.Column("INTEGER", nullable: false), + StringColumn = table.Column("TEXT", nullable: true), + IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesMany_SeparateEntitiesOne_Inner", x => new { x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId, x.OwnedEntity_Owns_SeparateOneId }); + table.ForeignKey( + "FK_SeparateEntitiesMany_SeparateEntitiesOne_Inner_SeparateEntitiesMany_SeparateEntitiesOne_OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId_OwnedEntity_Owns_SeparateOneId", + x => new { x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId, x.OwnedEntity_Owns_SeparateOneId }, + "SeparateEntitiesMany_SeparateEntitiesOne", + new[] { "TestEntity_Owns_SeparateMany_SeparateOneId", "Id" }, + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateMany_Inner", + table => new + { + OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId = table.Column("TEXT", nullable: false), + Id = table.Column("INTEGER", nullable: false), + StringColumn = table.Column("TEXT", nullable: true), + IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesOne_SeparateMany_Inner", x => new { x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId, x.Id }); + table.ForeignKey( + "FK_SeparateEntitiesOne_SeparateMany_Inner_SeparateEntitiesOne_SeparateMany_OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", + x => x.OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId, + "SeparateEntitiesOne_SeparateMany", + "TestEntity_Owns_SeparateOne_SeparateManyId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable("SeparateEntitiesOne_SeparateOne_Inner", + table => new + { + OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId = table.Column("TEXT", nullable: false), + StringColumn = table.Column("TEXT", nullable: true), + IntColumn = table.Column("INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SeparateEntitiesOne_SeparateOne_Inner", x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId); + table.ForeignKey( + "FK_SeparateEntitiesOne_SeparateOne_Inner_SeparateEntitiesOne_SeparateOne_OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId", + x => x.OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId, + "SeparateEntitiesOne_SeparateOne", + "TestEntity_Owns_SeparateOne_SeparateOneId", + onDelete: ReferentialAction.Cascade); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable("InlineEntities_SeparateMany"); + migrationBuilder.DropTable("InlineEntities_SeparateOne"); + migrationBuilder.DropTable("SeparateEntitiesMany"); + migrationBuilder.DropTable("SeparateEntitiesMany_Inline"); + migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner"); + migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner"); + migrationBuilder.DropTable("SeparateEntitiesOne"); + migrationBuilder.DropTable("SeparateEntitiesOne_Inline"); + migrationBuilder.DropTable("SeparateEntitiesOne_SeparateMany_Inner"); + migrationBuilder.DropTable("SeparateEntitiesOne_SeparateOne_Inner"); + migrationBuilder.DropTable("TestEntities_Own_Inline"); + migrationBuilder.DropTable("TestEntities_Own_Inline_Inline"); + migrationBuilder.DropTable("TestEntities_Own_Inline_SeparateMany"); + migrationBuilder.DropTable("TestEntities_Own_Inline_SeparateOne"); + migrationBuilder.DropTable("TestEntities_Own_SeparateMany"); + migrationBuilder.DropTable("TestEntities_Own_SeparateMany_Inline"); + migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesMany"); + migrationBuilder.DropTable("SeparateEntitiesMany_SeparateEntitiesOne"); + migrationBuilder.DropTable("TestEntities_Own_SeparateOne"); + migrationBuilder.DropTable("TestEntities_Own_SeparateOne_Inline"); + migrationBuilder.DropTable("SeparateEntitiesOne_SeparateMany"); + migrationBuilder.DropTable("SeparateEntitiesOne_SeparateOne"); + migrationBuilder.DropTable("TestEntities_Own_SeparateMany_SeparateMany"); + migrationBuilder.DropTable("TestEntities_Own_SeparateMany_SeparateOne"); + migrationBuilder.DropTable("TestEntities_Own_SeparateOne_SeparateMany"); + migrationBuilder.DropTable("TestEntities_Own_SeparateOne_SeparateOne"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20221216113801_Add_TestEntityWithBaseClass.Designer.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20221216113801_Add_TestEntityWithBaseClass.Designer.cs index 45fe45d4..0bb41023 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20221216113801_Add_TestEntityWithBaseClass.Designer.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20221216113801_Add_TestEntityWithBaseClass.Designer.cs @@ -1,922 +1,922 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -#nullable disable - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - [Migration("20221216113801_Add_TestEntityWithBaseClass")] - partial class Add_TestEntityWithBaseClass - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.8"); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.KeylessTestEntity", b => - { - b.Property("IntColumn") - .HasColumnType("INTEGER"); - - b.ToTable("KeylessEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteIndex", b => - { - b.Property("Name") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Origin") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Partial") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Seq") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Unique") - .IsRequired() - .HasColumnType("BLOB"); - - b.ToView("pragma temp.index_list('<>')"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteMaster", b => - { - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Rootpage") - .HasColumnType("INTEGER"); - - b.Property("Sql") - .HasColumnType("TEXT"); - - b.Property("Tbl_Name") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("TEXT"); - - b.ToView("sqlite_temp_master"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteTableInfo", b => - { - b.Property("CId") - .HasColumnType("INTEGER"); - - b.Property("Dflt_Value") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("NotNull") - .HasColumnType("INTEGER"); - - b.Property("PK") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("TEXT"); - - b.ToView("PRAGMA_TABLE_INFO('<>')"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ConvertibleClass") - .HasColumnType("INTEGER"); - - b.Property("Count") - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("ParentId") - .HasColumnType("TEXT"); - - b.Property("PropertyWithBackingField") - .HasColumnType("INTEGER"); - - b.Property("RequiredName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("_privateField") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("ParentId"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithBaseClass", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithBaseClass"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValue(1); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValue(2); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue("4"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue("3"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDotnetDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("ShadowIntProperty") - .HasColumnType("INTEGER"); - - b.Property("ShadowStringProperty") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValueSql("1"); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValueSql("2"); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("'4'"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("'3'"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_InlineId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_InlineId"); - - b1.ToTable("TestEntities_Own_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_InlineId"); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_InlineId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_Inline_InlineId"); - - b1.ToTable("TestEntities_Own_Inline_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId") - .HasColumnType("TEXT"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); - - b2.ToTable("TestEntities_Own_Inline_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_SeparateManyId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_Inline_SeparateManyId"); - - b1.ToTable("TestEntities_Own_Inline_SeparateMany"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId") - .HasColumnType("TEXT"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", "Id"); - - b2.ToTable("InlineEntities_SeparateMany", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_SeparateOneId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_Inline_SeparateOneId"); - - b1.ToTable("TestEntities_Own_Inline_SeparateOne"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId") - .HasColumnType("TEXT"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); - - b2.ToTable("InlineEntities_SeparateOne", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateManyId") - .HasColumnType("TEXT"); - - b1.Property("Id") - .HasColumnType("INTEGER"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateManyId", "Id"); - - b1.ToTable("SeparateEntitiesMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateManyId"); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_InlineId") - .HasColumnType("TEXT"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateMany_InlineId", "Id"); - - b1.ToTable("SeparateEntitiesMany_Inline", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId") - .HasColumnType("TEXT"); - - b2.Property("OwnedEntity_Owns_InlineId") - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); - - b2.ToTable("SeparateEntitiesMany_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_SeparateManyId") - .HasColumnType("TEXT"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateMany_SeparateManyId", "Id"); - - b1.ToTable("SeparateEntitiesMany_SeparateEntitiesMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId") - .HasColumnType("TEXT"); - - b2.Property("OwnedEntity_Owns_SeparateManyId") - .HasColumnType("INTEGER"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId", "Id"); - - b2.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_SeparateOneId") - .HasColumnType("TEXT"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateMany_SeparateOneId", "Id"); - - b1.ToTable("SeparateEntitiesMany_SeparateEntitiesOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId") - .HasColumnType("TEXT"); - - b2.Property("OwnedEntity_Owns_SeparateOneId") - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); - - b2.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOneId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateOneId"); - - b1.ToTable("SeparateEntitiesOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOneId"); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_InlineId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateOne_InlineId"); - - b1.ToTable("SeparateEntitiesOne_Inline", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId") - .HasColumnType("TEXT"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); - - b2.ToTable("SeparateEntitiesOne_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_SeparateManyId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateOne_SeparateManyId"); - - b1.ToTable("SeparateEntitiesOne_SeparateMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId") - .HasColumnType("TEXT"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", "Id"); - - b2.ToTable("SeparateEntitiesOne_SeparateMany_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_SeparateOneId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateOne_SeparateOneId"); - - b1.ToTable("SeparateEntitiesOne_SeparateOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId") - .HasColumnType("TEXT"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); - - b2.ToTable("SeparateEntitiesOne_SeparateOne_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Navigation("Children"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +#nullable disable + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + [Migration("20221216113801_Add_TestEntityWithBaseClass")] + partial class Add_TestEntityWithBaseClass + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.8"); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.KeylessTestEntity", b => + { + b.Property("IntColumn") + .HasColumnType("INTEGER"); + + b.ToTable("KeylessEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteIndex", b => + { + b.Property("Name") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Origin") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Partial") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Seq") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Unique") + .IsRequired() + .HasColumnType("BLOB"); + + b.ToView("pragma temp.index_list('<>')"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteMaster", b => + { + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Rootpage") + .HasColumnType("INTEGER"); + + b.Property("Sql") + .HasColumnType("TEXT"); + + b.Property("Tbl_Name") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("TEXT"); + + b.ToView("sqlite_temp_master"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteTableInfo", b => + { + b.Property("CId") + .HasColumnType("INTEGER"); + + b.Property("Dflt_Value") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NotNull") + .HasColumnType("INTEGER"); + + b.Property("PK") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("TEXT"); + + b.ToView("PRAGMA_TABLE_INFO('<>')"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConvertibleClass") + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("ParentId") + .HasColumnType("TEXT"); + + b.Property("PropertyWithBackingField") + .HasColumnType("INTEGER"); + + b.Property("RequiredName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("_privateField") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithBaseClass", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithBaseClass"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(1); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(2); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("4"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("3"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDotnetDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("ShadowIntProperty") + .HasColumnType("INTEGER"); + + b.Property("ShadowStringProperty") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValueSql("1"); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValueSql("2"); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("'4'"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("'3'"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_InlineId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_InlineId"); + + b1.ToTable("TestEntities_Own_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_InlineId"); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_InlineId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_Inline_InlineId"); + + b1.ToTable("TestEntities_Own_Inline_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId") + .HasColumnType("TEXT"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); + + b2.ToTable("TestEntities_Own_Inline_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_SeparateManyId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_Inline_SeparateManyId"); + + b1.ToTable("TestEntities_Own_Inline_SeparateMany"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId") + .HasColumnType("TEXT"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", "Id"); + + b2.ToTable("InlineEntities_SeparateMany", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_SeparateOneId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_Inline_SeparateOneId"); + + b1.ToTable("TestEntities_Own_Inline_SeparateOne"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId") + .HasColumnType("TEXT"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); + + b2.ToTable("InlineEntities_SeparateOne", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateManyId") + .HasColumnType("TEXT"); + + b1.Property("Id") + .HasColumnType("INTEGER"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateManyId", "Id"); + + b1.ToTable("SeparateEntitiesMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateManyId"); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_InlineId") + .HasColumnType("TEXT"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateMany_InlineId", "Id"); + + b1.ToTable("SeparateEntitiesMany_Inline", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId") + .HasColumnType("TEXT"); + + b2.Property("OwnedEntity_Owns_InlineId") + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); + + b2.ToTable("SeparateEntitiesMany_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_SeparateManyId") + .HasColumnType("TEXT"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateMany_SeparateManyId", "Id"); + + b1.ToTable("SeparateEntitiesMany_SeparateEntitiesMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId") + .HasColumnType("TEXT"); + + b2.Property("OwnedEntity_Owns_SeparateManyId") + .HasColumnType("INTEGER"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId", "Id"); + + b2.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_SeparateOneId") + .HasColumnType("TEXT"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateMany_SeparateOneId", "Id"); + + b1.ToTable("SeparateEntitiesMany_SeparateEntitiesOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId") + .HasColumnType("TEXT"); + + b2.Property("OwnedEntity_Owns_SeparateOneId") + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); + + b2.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOneId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateOneId"); + + b1.ToTable("SeparateEntitiesOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOneId"); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_InlineId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateOne_InlineId"); + + b1.ToTable("SeparateEntitiesOne_Inline", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId") + .HasColumnType("TEXT"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); + + b2.ToTable("SeparateEntitiesOne_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_SeparateManyId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateOne_SeparateManyId"); + + b1.ToTable("SeparateEntitiesOne_SeparateMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId") + .HasColumnType("TEXT"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", "Id"); + + b2.ToTable("SeparateEntitiesOne_SeparateMany_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_SeparateOneId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateOne_SeparateOneId"); + + b1.ToTable("SeparateEntitiesOne_SeparateOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId") + .HasColumnType("TEXT"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); + + b2.ToTable("SeparateEntitiesOne_SeparateOne_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Navigation("Children"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20221216113801_Add_TestEntityWithBaseClass.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20221216113801_Add_TestEntityWithBaseClass.cs index 5475a1f2..201125ed 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20221216113801_Add_TestEntityWithBaseClass.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/20221216113801_Add_TestEntityWithBaseClass.cs @@ -1,44 +1,44 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Thinktecture.Migrations -{ - public partial class Add_TestEntityWithBaseClass : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "KeylessEntities", - columns: table => new - { - IntColumn = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - }); - - migrationBuilder.CreateTable( - name: "TestEntitiesWithBaseClass", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_TestEntitiesWithBaseClass", x => x.Id); - }); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "KeylessEntities"); - - migrationBuilder.DropTable( - name: "TestEntitiesWithBaseClass"); - } - } -} +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Thinktecture.Migrations +{ + public partial class Add_TestEntityWithBaseClass : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "KeylessEntities", + columns: table => new + { + IntColumn = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + }); + + migrationBuilder.CreateTable( + name: "TestEntitiesWithBaseClass", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TestEntitiesWithBaseClass", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "KeylessEntities"); + + migrationBuilder.DropTable( + name: "TestEntitiesWithBaseClass"); + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/TestDbContextModelSnapshot.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/TestDbContextModelSnapshot.cs index ad021025..ce983d7c 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/TestDbContextModelSnapshot.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Migrations/TestDbContextModelSnapshot.cs @@ -1,954 +1,954 @@ -// -using System; -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Thinktecture.TestDatabaseContext; - -#nullable disable - -namespace Thinktecture.Migrations -{ - [DbContext(typeof(TestDbContext))] - partial class TestDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "8.0.0-rc.2.23480.1"); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.KeylessTestEntity", b => - { - b.Property("IntColumn") - .HasColumnType("INTEGER"); - - b.ToTable("KeylessEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteIndex", b => - { - b.Property("Name") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Origin") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Partial") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Seq") - .IsRequired() - .HasColumnType("BLOB"); - - b.Property("Unique") - .IsRequired() - .HasColumnType("BLOB"); - - b.ToTable((string)null); - - b.ToView("pragma temp.index_list('<>')", (string)null); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteMaster", b => - { - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Rootpage") - .HasColumnType("INTEGER"); - - b.Property("Sql") - .HasColumnType("TEXT"); - - b.Property("Tbl_Name") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("TEXT"); - - b.ToTable((string)null); - - b.ToView("sqlite_temp_master", (string)null); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteTableInfo", b => - { - b.Property("CId") - .HasColumnType("INTEGER"); - - b.Property("Dflt_Value") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("NotNull") - .HasColumnType("INTEGER"); - - b.Property("PK") - .HasColumnType("INTEGER"); - - b.Property("Type") - .HasColumnType("TEXT"); - - b.ToTable((string)null); - - b.ToView("PRAGMA_TABLE_INFO('<>')", (string)null); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("ConvertibleClass") - .HasColumnType("INTEGER"); - - b.Property("Count") - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("NullableCount") - .HasColumnType("INTEGER"); - - b.Property("ParentId") - .HasColumnType("TEXT"); - - b.Property("PropertyWithBackingField") - .HasColumnType("INTEGER"); - - b.Property("RequiredName") - .IsRequired() - .HasColumnType("TEXT"); - - b.Property("_privateField") - .HasColumnType("INTEGER"); - - b.HasKey("Id"); - - b.HasIndex("Id") - .HasDatabaseName("IX_TestEntities_Id"); - - b.HasIndex("ParentId"); - - b.ToTable("TestEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithAutoIncrement"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithBaseClass", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Name") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithBaseClass"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithComplexType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.ComplexProperty>("Boundary", "Thinktecture.TestDatabaseContext.TestEntityWithComplexType.Boundary#BoundaryValueObject", b1 => - { - b1.IsRequired(); - - b1.Property("Lower") - .HasColumnType("INTEGER"); - - b1.Property("Upper") - .HasColumnType("INTEGER"); - }); - - b.HasKey("Id"); - - b.ToTable("TestEntities_with_ComplexType"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValue(1); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValue(2); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue("4"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValue("3"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDotnetDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("ShadowIntProperty") - .HasColumnType("INTEGER"); - - b.Property("ShadowStringProperty") - .HasMaxLength(50) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithShadowProperties"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Int") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValueSql("1"); - - b.Property("NullableInt") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER") - .HasDefaultValueSql("2"); - - b.Property("NullableString") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("'4'"); - - b.Property("String") - .IsRequired() - .ValueGeneratedOnAdd() - .HasColumnType("TEXT") - .HasDefaultValueSql("'3'"); - - b.HasKey("Id"); - - b.ToTable("TestEntitiesWithDefaultValues"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_Inline_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateMany_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_Inline"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_SeparateMany"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.ToTable("TestEntities_Own_SeparateOne_SeparateOne"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_InlineId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_InlineId"); - - b1.ToTable("TestEntities_Own_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_InlineId"); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_InlineId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_Inline_InlineId"); - - b1.ToTable("TestEntities_Own_Inline_Inline"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId") - .HasColumnType("TEXT"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); - - b2.ToTable("TestEntities_Own_Inline_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_SeparateManyId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_Inline_SeparateManyId"); - - b1.ToTable("TestEntities_Own_Inline_SeparateMany"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId") - .HasColumnType("TEXT"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", "Id"); - - b2.ToTable("InlineEntities_SeparateMany", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "InlineEntity", b1 => - { - b1.Property("TestEntity_Owns_Inline_SeparateOneId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_Inline_SeparateOneId"); - - b1.ToTable("TestEntities_Own_Inline_SeparateOne"); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_Inline_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId") - .HasColumnType("TEXT"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); - - b2.ToTable("InlineEntities_SeparateOne", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("InlineEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateManyId") - .HasColumnType("TEXT"); - - b1.Property("Id") - .HasColumnType("INTEGER"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateManyId", "Id"); - - b1.ToTable("SeparateEntitiesMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateManyId"); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_InlineId") - .HasColumnType("TEXT"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateMany_InlineId", "Id"); - - b1.ToTable("SeparateEntitiesMany_Inline", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId") - .HasColumnType("TEXT"); - - b2.Property("OwnedEntity_Owns_InlineId") - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); - - b2.ToTable("SeparateEntitiesMany_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_SeparateManyId") - .HasColumnType("TEXT"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateMany_SeparateManyId", "Id"); - - b1.ToTable("SeparateEntitiesMany_SeparateEntitiesMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId") - .HasColumnType("TEXT"); - - b2.Property("OwnedEntity_Owns_SeparateManyId") - .HasColumnType("INTEGER"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId", "Id"); - - b2.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => - { - b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntities", b1 => - { - b1.Property("TestEntity_Owns_SeparateMany_SeparateOneId") - .HasColumnType("TEXT"); - - b1.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateMany_SeparateOneId", "Id"); - - b1.ToTable("SeparateEntitiesMany_SeparateEntitiesOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId") - .HasColumnType("TEXT"); - - b2.Property("OwnedEntity_Owns_SeparateOneId") - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); - - b2.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntities"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOneId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateOneId"); - - b1.ToTable("SeparateEntitiesOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOneId"); - }); - - b.Navigation("SeparateEntity"); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_InlineId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateOne_InlineId"); - - b1.ToTable("SeparateEntitiesOne_Inline", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_InlineId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => - { - b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId") - .HasColumnType("TEXT"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); - - b2.ToTable("SeparateEntitiesOne_Inline"); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); - }); - - b1.Navigation("InlineEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_SeparateManyId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateOne_SeparateManyId"); - - b1.ToTable("SeparateEntitiesOne_SeparateMany", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateManyId"); - - b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId") - .HasColumnType("TEXT"); - - b2.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", "Id"); - - b2.ToTable("SeparateEntitiesOne_SeparateMany_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId"); - }); - - b1.Navigation("SeparateEntities"); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => - { - b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntity", b1 => - { - b1.Property("TestEntity_Owns_SeparateOne_SeparateOneId") - .HasColumnType("TEXT"); - - b1.Property("IntColumn") - .HasColumnType("INTEGER"); - - b1.Property("StringColumn") - .HasColumnType("TEXT"); - - b1.HasKey("TestEntity_Owns_SeparateOne_SeparateOneId"); - - b1.ToTable("SeparateEntitiesOne_SeparateOne", (string)null); - - b1.WithOwner() - .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateOneId"); - - b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => - { - b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId") - .HasColumnType("TEXT"); - - b2.Property("IntColumn") - .HasColumnType("INTEGER"); - - b2.Property("StringColumn") - .HasColumnType("TEXT"); - - b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); - - b2.ToTable("SeparateEntitiesOne_SeparateOne_Inner", (string)null); - - b2.WithOwner() - .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); - }); - - b1.Navigation("SeparateEntity") - .IsRequired(); - }); - - b.Navigation("SeparateEntity") - .IsRequired(); - }); - - modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => - { - b.Navigation("Children"); - }); -#pragma warning restore 612, 618 - } - } -} +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Thinktecture.TestDatabaseContext; + +#nullable disable + +namespace Thinktecture.Migrations +{ + [DbContext(typeof(TestDbContext))] + partial class TestDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0-rc.2.23480.1"); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.KeylessTestEntity", b => + { + b.Property("IntColumn") + .HasColumnType("INTEGER"); + + b.ToTable("KeylessEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteIndex", b => + { + b.Property("Name") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Origin") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Partial") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Seq") + .IsRequired() + .HasColumnType("BLOB"); + + b.Property("Unique") + .IsRequired() + .HasColumnType("BLOB"); + + b.ToTable((string)null); + + b.ToView("pragma temp.index_list('<>')", (string)null); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteMaster", b => + { + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Rootpage") + .HasColumnType("INTEGER"); + + b.Property("Sql") + .HasColumnType("TEXT"); + + b.Property("Tbl_Name") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("TEXT"); + + b.ToTable((string)null); + + b.ToView("sqlite_temp_master", (string)null); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.SqliteTableInfo", b => + { + b.Property("CId") + .HasColumnType("INTEGER"); + + b.Property("Dflt_Value") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NotNull") + .HasColumnType("INTEGER"); + + b.Property("PK") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("TEXT"); + + b.ToTable((string)null); + + b.ToView("PRAGMA_TABLE_INFO('<>')", (string)null); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ConvertibleClass") + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NullableCount") + .HasColumnType("INTEGER"); + + b.Property("ParentId") + .HasColumnType("TEXT"); + + b.Property("PropertyWithBackingField") + .HasColumnType("INTEGER"); + + b.Property("RequiredName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("_privateField") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .HasDatabaseName("IX_TestEntities_Id"); + + b.HasIndex("ParentId"); + + b.ToTable("TestEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithAutoIncrement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithAutoIncrement"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithBaseClass", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithBaseClass"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithComplexType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.ComplexProperty>("Boundary", "Thinktecture.TestDatabaseContext.TestEntityWithComplexType.Boundary#BoundaryValueObject", b1 => + { + b1.IsRequired(); + + b1.Property("Lower") + .HasColumnType("INTEGER"); + + b1.Property("Upper") + .HasColumnType("INTEGER"); + }); + + b.HasKey("Id"); + + b.ToTable("TestEntities_with_ComplexType"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithDotnetDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(new Guid("0b151271-79bb-4f6c-b85f-e8f61300ff1b")); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(1); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(2); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("4"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("3"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDotnetDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithShadowProperties", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("ShadowIntProperty") + .HasColumnType("INTEGER"); + + b.Property("ShadowStringProperty") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithShadowProperties"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntityWithSqlDefaultValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Int") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValueSql("1"); + + b.Property("NullableInt") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValueSql("2"); + + b.Property("NullableString") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("'4'"); + + b.Property("String") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("'3'"); + + b.HasKey("Id"); + + b.ToTable("TestEntitiesWithDefaultValues"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_Inline_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateMany_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_Inline"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_SeparateMany"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TestEntities_Own_SeparateOne_SeparateOne"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.HasOne("Thinktecture.TestDatabaseContext.TestEntity", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_InlineId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_InlineId"); + + b1.ToTable("TestEntities_Own_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_InlineId"); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_InlineId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_Inline_InlineId"); + + b1.ToTable("TestEntities_Own_Inline_Inline"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId") + .HasColumnType("TEXT"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); + + b2.ToTable("TestEntities_Own_Inline_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_Inline_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateMany", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_SeparateManyId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_Inline_SeparateManyId"); + + b1.ToTable("TestEntities_Own_Inline_SeparateMany"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId") + .HasColumnType("TEXT"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId", "Id"); + + b2.ToTable("InlineEntities_SeparateMany", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_Inline_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_Inline_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "InlineEntity", b1 => + { + b1.Property("TestEntity_Owns_Inline_SeparateOneId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_Inline_SeparateOneId"); + + b1.ToTable("TestEntities_Own_Inline_SeparateOne"); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_Inline_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId") + .HasColumnType("TEXT"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); + + b2.ToTable("InlineEntities_SeparateOne", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_Inline_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("InlineEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateManyId") + .HasColumnType("TEXT"); + + b1.Property("Id") + .HasColumnType("INTEGER"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateManyId", "Id"); + + b1.ToTable("SeparateEntitiesMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateManyId"); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_Inline", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_InlineId") + .HasColumnType("TEXT"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateMany_InlineId", "Id"); + + b1.ToTable("SeparateEntitiesMany_Inline", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId") + .HasColumnType("TEXT"); + + b2.Property("OwnedEntity_Owns_InlineId") + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); + + b2.ToTable("SeparateEntitiesMany_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateMany_InlineId", "OwnedEntity_Owns_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateMany", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_SeparateManyId") + .HasColumnType("TEXT"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateMany_SeparateManyId", "Id"); + + b1.ToTable("SeparateEntitiesMany_SeparateEntitiesMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId") + .HasColumnType("TEXT"); + + b2.Property("OwnedEntity_Owns_SeparateManyId") + .HasColumnType("INTEGER"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId", "Id"); + + b2.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateMany_SeparateManyId", "OwnedEntity_Owns_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateMany_SeparateOne", b => + { + b.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntities", b1 => + { + b1.Property("TestEntity_Owns_SeparateMany_SeparateOneId") + .HasColumnType("TEXT"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateMany_SeparateOneId", "Id"); + + b1.ToTable("SeparateEntitiesMany_SeparateEntitiesOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateMany_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId") + .HasColumnType("TEXT"); + + b2.Property("OwnedEntity_Owns_SeparateOneId") + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); + + b2.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateMany_SeparateOneId", "OwnedEntity_Owns_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntities"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOneId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateOneId"); + + b1.ToTable("SeparateEntitiesOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOneId"); + }); + + b.Navigation("SeparateEntity"); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_Inline", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_Inline", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_InlineId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateOne_InlineId"); + + b1.ToTable("SeparateEntitiesOne_Inline", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_InlineId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "InlineEntity", b2 => + { + b2.Property("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId") + .HasColumnType("TEXT"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); + + b2.ToTable("SeparateEntitiesOne_Inline"); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_InlineTestEntity_Owns_SeparateOne_InlineId"); + }); + + b1.Navigation("InlineEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateMany", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateMany", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_SeparateManyId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateOne_SeparateManyId"); + + b1.ToTable("SeparateEntitiesOne_SeparateMany", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateManyId"); + + b1.OwnsMany("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntities", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId") + .HasColumnType("TEXT"); + + b2.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId", "Id"); + + b2.ToTable("SeparateEntitiesOne_SeparateMany_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateManyTestEntity_Owns_SeparateOne_SeparateManyId"); + }); + + b1.Navigation("SeparateEntities"); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity_Owns_SeparateOne_SeparateOne", b => + { + b.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity_Owns_SeparateOne", "SeparateEntity", b1 => + { + b1.Property("TestEntity_Owns_SeparateOne_SeparateOneId") + .HasColumnType("TEXT"); + + b1.Property("IntColumn") + .HasColumnType("INTEGER"); + + b1.Property("StringColumn") + .HasColumnType("TEXT"); + + b1.HasKey("TestEntity_Owns_SeparateOne_SeparateOneId"); + + b1.ToTable("SeparateEntitiesOne_SeparateOne", (string)null); + + b1.WithOwner() + .HasForeignKey("TestEntity_Owns_SeparateOne_SeparateOneId"); + + b1.OwnsOne("Thinktecture.TestDatabaseContext.OwnedEntity", "SeparateEntity", b2 => + { + b2.Property("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId") + .HasColumnType("TEXT"); + + b2.Property("IntColumn") + .HasColumnType("INTEGER"); + + b2.Property("StringColumn") + .HasColumnType("TEXT"); + + b2.HasKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); + + b2.ToTable("SeparateEntitiesOne_SeparateOne_Inner", (string)null); + + b2.WithOwner() + .HasForeignKey("OwnedEntity_Owns_SeparateOneTestEntity_Owns_SeparateOne_SeparateOneId"); + }); + + b1.Navigation("SeparateEntity") + .IsRequired(); + }); + + b.Navigation("SeparateEntity") + .IsRequired(); + }); + + modelBuilder.Entity("Thinktecture.TestDatabaseContext.TestEntity", b => + { + b.Navigation("Children"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/SchemaChangingIntegrationTestsBase.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/SchemaChangingIntegrationTestsBase.cs index 188b70ad..504d9c1d 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/SchemaChangingIntegrationTestsBase.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/SchemaChangingIntegrationTestsBase.cs @@ -1,40 +1,40 @@ -using Microsoft.Extensions.Logging; -using Thinktecture.EntityFrameworkCore; -using Thinktecture.EntityFrameworkCore.Testing; -using Thinktecture.TestDatabaseContext; - -namespace Thinktecture; - -public abstract class SchemaChangingIntegrationTestsBase : SqliteDbContextIntegrationTests -{ - private readonly IMigrationExecutionStrategy? _migrationExecutionStrategy; - - protected Action? ConfigureModel { get; set; } - protected Action? Configure { get; set; } - protected ILoggerFactory LoggerFactory { get; } - - protected SchemaChangingIntegrationTestsBase( - ITestOutputHelper testOutputHelper, - IMigrationExecutionStrategy? migrationExecutionStrategy = null) - : base(testOutputHelper) - { - _migrationExecutionStrategy = migrationExecutionStrategy; - LoggerFactory = testOutputHelper.ToLoggerFactory(); - } - - protected override void ConfigureTestDbContextProvider(SqliteTestDbContextProviderBuilder builder) - { - builder.UseMigrationExecutionStrategy(_migrationExecutionStrategy ?? IMigrationExecutionStrategy.Migrations) - .UseMigrationLogLevel(LogLevel.Warning) - .ConfigureSqliteOptions(optionsBuilder => optionsBuilder.AddBulkOperationSupport() - .AddWindowFunctionsSupport()) - .InitializeContext(ctx => - { - ctx.ConfigureModel = ConfigureModel; - ctx.Configure = Configure; - }); - - if (ConfigureModel is not null) - builder.DisableModelCache(); - } -} +using Microsoft.Extensions.Logging; +using Thinktecture.EntityFrameworkCore; +using Thinktecture.EntityFrameworkCore.Testing; +using Thinktecture.TestDatabaseContext; + +namespace Thinktecture; + +public abstract class SchemaChangingIntegrationTestsBase : SqliteDbContextIntegrationTests +{ + private readonly IMigrationExecutionStrategy? _migrationExecutionStrategy; + + protected Action? ConfigureModel { get; set; } + protected Action? Configure { get; set; } + protected ILoggerFactory LoggerFactory { get; } + + protected SchemaChangingIntegrationTestsBase( + ITestOutputHelper testOutputHelper, + IMigrationExecutionStrategy? migrationExecutionStrategy = null) + : base(testOutputHelper) + { + _migrationExecutionStrategy = migrationExecutionStrategy; + LoggerFactory = testOutputHelper.ToLoggerFactory(); + } + + protected override void ConfigureTestDbContextProvider(SqliteTestDbContextProviderBuilder builder) + { + builder.UseMigrationExecutionStrategy(_migrationExecutionStrategy ?? IMigrationExecutionStrategy.Migrations) + .UseMigrationLogLevel(LogLevel.Warning) + .ConfigureSqliteOptions(optionsBuilder => optionsBuilder.AddBulkOperationSupport() + .AddWindowFunctionsSupport()) + .InitializeContext(ctx => + { + ctx.ConfigureModel = ConfigureModel; + ctx.Configure = Configure; + }); + + if (ConfigureModel is not null) + builder.DisableModelCache(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/SqliteIndex.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/SqliteIndex.cs index d63cdcba..80eb00dd 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/SqliteIndex.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/SqliteIndex.cs @@ -1,13 +1,13 @@ -namespace Thinktecture.TestDatabaseContext; - -// ReSharper disable InconsistentNaming -// ReSharper disable UnusedMember.Global -// ReSharper disable IdentifierTypo -public class SqliteIndex -{ - public byte[] Seq { get; set; } - public byte[] Name { get; set; } - public byte[] Unique { get; set; } - public byte[] Origin { get; set; } - public byte[] Partial { get; set; } +namespace Thinktecture.TestDatabaseContext; + +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Global +// ReSharper disable IdentifierTypo +public class SqliteIndex +{ + public byte[] Seq { get; set; } + public byte[] Name { get; set; } + public byte[] Unique { get; set; } + public byte[] Origin { get; set; } + public byte[] Partial { get; set; } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/SqliteMaster.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/SqliteMaster.cs index 5641f987..a214e03f 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/SqliteMaster.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/SqliteMaster.cs @@ -1,13 +1,13 @@ -namespace Thinktecture.TestDatabaseContext; - -// ReSharper disable InconsistentNaming -// ReSharper disable UnusedMember.Global -// ReSharper disable IdentifierTypo -public class SqliteMaster -{ - public string? Type { get; set; } - public string? Name { get; set; } - public string? Tbl_Name { get; set; } - public long? Rootpage { get; set; } - public string? Sql { get; set; } +namespace Thinktecture.TestDatabaseContext; + +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Global +// ReSharper disable IdentifierTypo +public class SqliteMaster +{ + public string? Type { get; set; } + public string? Name { get; set; } + public string? Tbl_Name { get; set; } + public long? Rootpage { get; set; } + public string? Sql { get; set; } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/SqliteTableInfo.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/SqliteTableInfo.cs index fb8304de..57db34f7 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/SqliteTableInfo.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/SqliteTableInfo.cs @@ -1,14 +1,14 @@ -namespace Thinktecture.TestDatabaseContext; - -// ReSharper disable InconsistentNaming -// ReSharper disable UnusedMember.Global -// ReSharper disable IdentifierTypo -public class SqliteTableInfo -{ - public long CId { get; set; } - public string? Name { get; set; } - public string? Type { get; set; } - public long? NotNull { get; set; } - public string? Dflt_Value { get; set; } - public long? PK { get; set; } -} +namespace Thinktecture.TestDatabaseContext; + +// ReSharper disable InconsistentNaming +// ReSharper disable UnusedMember.Global +// ReSharper disable IdentifierTypo +public class SqliteTableInfo +{ + public long CId { get; set; } + public string? Name { get; set; } + public string? Type { get; set; } + public long? NotNull { get; set; } + public string? Dflt_Value { get; set; } + public long? PK { get; set; } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/TestDbContext.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/TestDbContext.cs index 4dec2584..3ba41d74 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/TestDbContext.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/TestDbContext.cs @@ -1,111 +1,111 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage; - -// ReSharper disable InconsistentNaming -namespace Thinktecture.TestDatabaseContext; - -public class TestDbContext : DbContext -{ -#nullable disable - public DbSet TestEntities { get; set; } - public DbSet TestEntitiesWithBaseClass { get; set; } - public DbSet KeylessEntities { get; set; } - public DbSet TestEntitiesWithAutoIncrement { get; set; } - public DbSet TestEntitiesWithShadowProperties { get; set; } - public DbSet TestEntitiesWithDefaultValues { get; set; } - public DbSet TestEntitiesWithDotnetDefaultValues { get; set; } - public DbSet TestEntities_Own_Inline { get; set; } - public DbSet TestEntities_Own_Inline_Inline { get; set; } - public DbSet TestEntities_Own_Inline_SeparateOne { get; set; } - public DbSet TestEntities_Own_Inline_SeparateMany { get; set; } - public DbSet TestEntities_Own_SeparateOne { get; set; } - public DbSet TestEntities_Own_SeparateOne_Inline { get; set; } - public DbSet TestEntities_Own_SeparateOne_SeparateOne { get; set; } - public DbSet TestEntities_Own_SeparateOne_SeparateMany { get; set; } - public DbSet TestEntities_Own_SeparateMany { get; set; } - public DbSet TestEntities_Own_SeparateMany_Inline { get; set; } - public DbSet TestEntities_Own_SeparateMany_SeparateOne { get; set; } - public DbSet TestEntities_Own_SeparateMany_SeparateMany { get; set; } - public DbSet TestEntities_with_ComplexType { get; set; } -#nullable enable - - public Action? ConfigureModel { get; set; } - public Action? Configure { get; set; } - - public TestDbContext(DbContextOptions options) - : base(options) - { - } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - base.OnConfiguring(optionsBuilder); - - Configure?.Invoke(optionsBuilder); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - TestEntity.Configure(modelBuilder); - TestEntityWithBaseClass.Configure(modelBuilder); - KeylessTestEntity.Configure(modelBuilder); - - modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); - - TestEntityWithShadowProperties.Configure(modelBuilder); - TestEntityWithSqlDefaultValues.Configure(modelBuilder); - TestEntityWithDotnetDefaultValues.Configure(modelBuilder); - TestEntity_Owns_Inline.Configure(modelBuilder); - TestEntity_Owns_SeparateOne.Configure(modelBuilder); - TestEntity_Owns_SeparateMany.Configure(modelBuilder); - modelBuilder.Entity().OwnsMany(e => e.SeparateEntities, b => b.Property("Id").ValueGeneratedNever()); - TestEntity_Owns_SeparateOne_SeparateOne.Configure(modelBuilder); - TestEntity_Owns_SeparateOne_Inline.Configure(modelBuilder); - TestEntity_Owns_SeparateOne_SeparateMany.Configure(modelBuilder); - TestEntity_Owns_Inline_SeparateOne.Configure(modelBuilder); - TestEntity_Owns_Inline_Inline.Configure(modelBuilder); - TestEntity_Owns_Inline_SeparateMany.Configure(modelBuilder); - TestEntity_Owns_SeparateMany_SeparateOne.Configure(modelBuilder); - TestEntity_Owns_SeparateMany_Inline.Configure(modelBuilder); - TestEntity_Owns_SeparateMany_SeparateMany.Configure(modelBuilder); - TestEntityWithComplexType.Configure(modelBuilder); - - ConfigureModel?.Invoke(modelBuilder); - - modelBuilder.Entity().HasNoKey().ToView("sqlite_temp_master"); - modelBuilder.Entity().HasNoKey().ToView("PRAGMA_TABLE_INFO('<>')"); - modelBuilder.Entity().HasNoKey().ToView("pragma temp.index_list('<>')"); - } - - public IQueryable GetTempTableColumns() - { - var entityType = this.GetTempTableEntityType(); - return GetTempTableColumns(entityType); - } - - public IQueryable GetTempTableColumns(IEntityType entityType) - { - var tableName = entityType.GetTableName() ?? throw new Exception($"The entity '{entityType.Name}' has no table name."); - - return GetTempTableColumns(tableName); - } - - public IQueryable GetTempTableColumns(string tableName) - { - ArgumentNullException.ThrowIfNull(tableName); - - var helper = this.GetService(); - - return Set() - .FromSqlRaw($"SELECT * FROM PRAGMA_TABLE_INFO({helper.DelimitIdentifier(tableName)})"); - } - - public IQueryable GetTempTableKeyColumns() - { - return GetTempTableColumns() - .Where(c => c.PK > 0); - } -} +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage; + +// ReSharper disable InconsistentNaming +namespace Thinktecture.TestDatabaseContext; + +public class TestDbContext : DbContext +{ +#nullable disable + public DbSet TestEntities { get; set; } + public DbSet TestEntitiesWithBaseClass { get; set; } + public DbSet KeylessEntities { get; set; } + public DbSet TestEntitiesWithAutoIncrement { get; set; } + public DbSet TestEntitiesWithShadowProperties { get; set; } + public DbSet TestEntitiesWithDefaultValues { get; set; } + public DbSet TestEntitiesWithDotnetDefaultValues { get; set; } + public DbSet TestEntities_Own_Inline { get; set; } + public DbSet TestEntities_Own_Inline_Inline { get; set; } + public DbSet TestEntities_Own_Inline_SeparateOne { get; set; } + public DbSet TestEntities_Own_Inline_SeparateMany { get; set; } + public DbSet TestEntities_Own_SeparateOne { get; set; } + public DbSet TestEntities_Own_SeparateOne_Inline { get; set; } + public DbSet TestEntities_Own_SeparateOne_SeparateOne { get; set; } + public DbSet TestEntities_Own_SeparateOne_SeparateMany { get; set; } + public DbSet TestEntities_Own_SeparateMany { get; set; } + public DbSet TestEntities_Own_SeparateMany_Inline { get; set; } + public DbSet TestEntities_Own_SeparateMany_SeparateOne { get; set; } + public DbSet TestEntities_Own_SeparateMany_SeparateMany { get; set; } + public DbSet TestEntities_with_ComplexType { get; set; } +#nullable enable + + public Action? ConfigureModel { get; set; } + public Action? Configure { get; set; } + + public TestDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); + + Configure?.Invoke(optionsBuilder); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + TestEntity.Configure(modelBuilder); + TestEntityWithBaseClass.Configure(modelBuilder); + KeylessTestEntity.Configure(modelBuilder); + + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); + + TestEntityWithShadowProperties.Configure(modelBuilder); + TestEntityWithSqlDefaultValues.Configure(modelBuilder); + TestEntityWithDotnetDefaultValues.Configure(modelBuilder); + TestEntity_Owns_Inline.Configure(modelBuilder); + TestEntity_Owns_SeparateOne.Configure(modelBuilder); + TestEntity_Owns_SeparateMany.Configure(modelBuilder); + modelBuilder.Entity().OwnsMany(e => e.SeparateEntities, b => b.Property("Id").ValueGeneratedNever()); + TestEntity_Owns_SeparateOne_SeparateOne.Configure(modelBuilder); + TestEntity_Owns_SeparateOne_Inline.Configure(modelBuilder); + TestEntity_Owns_SeparateOne_SeparateMany.Configure(modelBuilder); + TestEntity_Owns_Inline_SeparateOne.Configure(modelBuilder); + TestEntity_Owns_Inline_Inline.Configure(modelBuilder); + TestEntity_Owns_Inline_SeparateMany.Configure(modelBuilder); + TestEntity_Owns_SeparateMany_SeparateOne.Configure(modelBuilder); + TestEntity_Owns_SeparateMany_Inline.Configure(modelBuilder); + TestEntity_Owns_SeparateMany_SeparateMany.Configure(modelBuilder); + TestEntityWithComplexType.Configure(modelBuilder); + + ConfigureModel?.Invoke(modelBuilder); + + modelBuilder.Entity().HasNoKey().ToView("sqlite_temp_master"); + modelBuilder.Entity().HasNoKey().ToView("PRAGMA_TABLE_INFO('<>')"); + modelBuilder.Entity().HasNoKey().ToView("pragma temp.index_list('<>')"); + } + + public IQueryable GetTempTableColumns() + { + var entityType = this.GetTempTableEntityType(); + return GetTempTableColumns(entityType); + } + + public IQueryable GetTempTableColumns(IEntityType entityType) + { + var tableName = entityType.GetTableName() ?? throw new Exception($"The entity '{entityType.Name}' has no table name."); + + return GetTempTableColumns(tableName); + } + + public IQueryable GetTempTableColumns(string tableName) + { + ArgumentNullException.ThrowIfNull(tableName); + + var helper = this.GetService(); + + return Set() + .FromSqlRaw($"SELECT * FROM PRAGMA_TABLE_INFO({helper.DelimitIdentifier(tableName)})"); + } + + public IQueryable GetTempTableKeyColumns() + { + return GetTempTableColumns() + .Where(c => c.PK > 0); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/TestDbContextDesignTimeDbContextFactory.cs b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/TestDbContextDesignTimeDbContextFactory.cs index a3a4f124..f3060b32 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/TestDbContextDesignTimeDbContextFactory.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/TestDatabaseContext/TestDbContextDesignTimeDbContextFactory.cs @@ -1,16 +1,16 @@ -using Microsoft.EntityFrameworkCore.Design; - -namespace Thinktecture.TestDatabaseContext; - -// ReSharper disable once UnusedMember.Global -public class TestDbContextDesignTimeDbContextFactory : IDesignTimeDbContextFactory -{ - public TestDbContext CreateDbContext(string[] args) - { - var options = new DbContextOptionsBuilder() - .UseSqlite("DataSource=:memory:") - .Options; - - return new TestDbContext(options); - } +using Microsoft.EntityFrameworkCore.Design; + +namespace Thinktecture.TestDatabaseContext; + +// ReSharper disable once UnusedMember.Global +public class TestDbContextDesignTimeDbContextFactory : IDesignTimeDbContextFactory +{ + public TestDbContext CreateDbContext(string[] args) + { + var options = new DbContextOptionsBuilder() + .UseSqlite("DataSource=:memory:") + .Options; + + return new TestDbContext(options); + } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests.csproj b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests.csproj index cbd669a1..d062feaf 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests.csproj +++ b/tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests/Thinktecture.EntityFrameworkCore.Sqlite.Tests.csproj @@ -1,26 +1,26 @@ - - - - $(NoWarn);CS1591;CA2000;CA2007;CA1819;CS8618;EF1001 - - - - - - - - - - - - PreserveNewest - - - - - - - - - - + + + + $(NoWarn);CS1591;CA2000;CA2007;CA1819;CS8618;EF1001 + + + + + + + + + + + + PreserveNewest + + + + + + + + + + diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/ConvertibleClass.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/ConvertibleClass.cs index 1ecfcb1b..55492db5 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/ConvertibleClass.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/ConvertibleClass.cs @@ -1,46 +1,46 @@ -namespace Thinktecture.TestDatabaseContext; - -public class ConvertibleClass : IEquatable -{ - public int Key { get; } - - public ConvertibleClass(int key) - { - Key = key; - } - - public static implicit operator int(ConvertibleClass convertibleClass) - { - return convertibleClass.Key; - } - - public bool Equals(ConvertibleClass? other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - return Key == other.Key; - } - - public override bool Equals(object? obj) - { - if (ReferenceEquals(null, obj)) - return false; - if (ReferenceEquals(this, obj)) - return true; - if (obj.GetType() != this.GetType()) - return false; - return Equals((ConvertibleClass)obj); - } - - public override int GetHashCode() - { - return Key; - } - - public override string ToString() - { - return Key.ToString(); - } -} +namespace Thinktecture.TestDatabaseContext; + +public class ConvertibleClass : IEquatable +{ + public int Key { get; } + + public ConvertibleClass(int key) + { + Key = key; + } + + public static implicit operator int(ConvertibleClass convertibleClass) + { + return convertibleClass.Key; + } + + public bool Equals(ConvertibleClass? other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + return Key == other.Key; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + if (obj.GetType() != this.GetType()) + return false; + return Equals((ConvertibleClass)obj); + } + + public override int GetHashCode() + { + return Key; + } + + public override string ToString() + { + return Key.ToString(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/CustomTempTable.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/CustomTempTable.cs index bcbde327..aaa8d08f 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/CustomTempTable.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/CustomTempTable.cs @@ -1,17 +1,17 @@ -namespace Thinktecture.TestDatabaseContext; - -public class CustomTempTable -{ - public int Column1 { get; set; } - public string? Column2 { get; set; } - - public CustomTempTable() - { - } - - public CustomTempTable(int column1, string? column2) - { - Column1 = column1; - Column2 = column2; - } +namespace Thinktecture.TestDatabaseContext; + +public class CustomTempTable +{ + public int Column1 { get; set; } + public string? Column2 { get; set; } + + public CustomTempTable() + { + } + + public CustomTempTable(int column1, string? column2) + { + Column1 = column1; + Column2 = column2; + } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity.cs index 08d2f485..64351252 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity.cs @@ -1,7 +1,7 @@ -namespace Thinktecture.TestDatabaseContext; - -public class OwnedEntity -{ - public string? StringColumn { get; set; } - public int IntColumn { get; set; } +namespace Thinktecture.TestDatabaseContext; + +public class OwnedEntity +{ + public string? StringColumn { get; set; } + public int IntColumn { get; set; } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity_Owns_Inline.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity_Owns_Inline.cs index d96030cc..00d0a955 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity_Owns_Inline.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity_Owns_Inline.cs @@ -1,10 +1,10 @@ -// ReSharper disable InconsistentNaming -namespace Thinktecture.TestDatabaseContext; -#pragma warning disable 8618 -public class OwnedEntity_Owns_Inline -{ - public string? StringColumn { get; set; } - public int IntColumn { get; set; } - - public OwnedEntity InlineEntity { get; set; } +// ReSharper disable InconsistentNaming +namespace Thinktecture.TestDatabaseContext; +#pragma warning disable 8618 +public class OwnedEntity_Owns_Inline +{ + public string? StringColumn { get; set; } + public int IntColumn { get; set; } + + public OwnedEntity InlineEntity { get; set; } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity_Owns_SeparateMany.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity_Owns_SeparateMany.cs index 1bb16610..9ba78cdf 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity_Owns_SeparateMany.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity_Owns_SeparateMany.cs @@ -1,9 +1,9 @@ -namespace Thinktecture.TestDatabaseContext; -#pragma warning disable 8618 -public class OwnedEntity_Owns_SeparateMany -{ - public string? StringColumn { get; set; } - public int IntColumn { get; set; } - - public List SeparateEntities { get; set; } -} +namespace Thinktecture.TestDatabaseContext; +#pragma warning disable 8618 +public class OwnedEntity_Owns_SeparateMany +{ + public string? StringColumn { get; set; } + public int IntColumn { get; set; } + + public List SeparateEntities { get; set; } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity_Owns_SeparateOne.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity_Owns_SeparateOne.cs index 79665812..c46e4fa9 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity_Owns_SeparateOne.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/OwnedEntity_Owns_SeparateOne.cs @@ -1,10 +1,10 @@ -// ReSharper disable InconsistentNaming -namespace Thinktecture.TestDatabaseContext; -#pragma warning disable 8618 -public class OwnedEntity_Owns_SeparateOne -{ - public string? StringColumn { get; set; } - public int IntColumn { get; set; } - - public OwnedEntity SeparateEntity { get; set; } +// ReSharper disable InconsistentNaming +namespace Thinktecture.TestDatabaseContext; +#pragma warning disable 8618 +public class OwnedEntity_Owns_SeparateOne +{ + public string? StringColumn { get; set; } + public int IntColumn { get; set; } + + public OwnedEntity SeparateEntity { get; set; } } \ No newline at end of file diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity.cs index a185cc93..8f8cce97 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity.cs @@ -1,62 +1,62 @@ -using System.Reflection; - -namespace Thinktecture.TestDatabaseContext; -#pragma warning disable 8618 -public class TestEntity -{ - public Guid Id { get; set; } - public string? Name { get; set; } - public string RequiredName { get; set; } = String.Empty; - public int Count { get; set; } - public int? NullableCount { get; set; } - public ConvertibleClass? ConvertibleClass { get; set; } - - public Guid? ParentId { get; set; } - public TestEntity? Parent { get; set; } - - public List Children { get; set; } = new(); - - private int _propertyWithBackingField; - - public int PropertyWithBackingField - { - get => _propertyWithBackingField; - set => _propertyWithBackingField = value; - } - - private int _privateField; - - public int GetPrivateField() - { - return _privateField; - } - - public void SetPrivateField(int value) - { - _privateField = value; - } - - public static IReadOnlyList GetRequiredProperties() - { - return new MemberInfo[] - { - typeof(TestEntity).GetProperty(nameof(Id)) ?? throw new Exception($"Property {nameof(Id)} not found."), - typeof(TestEntity).GetProperty(nameof(RequiredName)) ?? throw new Exception($"Property {nameof(RequiredName)} not found."), - typeof(TestEntity).GetProperty(nameof(Count)) ?? throw new Exception($"Property {nameof(Count)} not found."), - typeof(TestEntity).GetProperty(nameof(PropertyWithBackingField)) ?? throw new Exception($"Property {nameof(PropertyWithBackingField)} not found."), - typeof(TestEntity).GetField("_privateField", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new Exception("Field _privateField not found.") - }; - } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => - { - builder.Property("_privateField"); - builder.Property(e => e.ConvertibleClass).HasConversion(c => c!.Key, k => new ConvertibleClass(k)); - - builder.HasIndex(e => e.Id) - .HasDatabaseName("IX_TestEntities_Id"); - }); - } -} +using System.Reflection; + +namespace Thinktecture.TestDatabaseContext; +#pragma warning disable 8618 +public class TestEntity +{ + public Guid Id { get; set; } + public string? Name { get; set; } + public string RequiredName { get; set; } = String.Empty; + public int Count { get; set; } + public int? NullableCount { get; set; } + public ConvertibleClass? ConvertibleClass { get; set; } + + public Guid? ParentId { get; set; } + public TestEntity? Parent { get; set; } + + public List Children { get; set; } = new(); + + private int _propertyWithBackingField; + + public int PropertyWithBackingField + { + get => _propertyWithBackingField; + set => _propertyWithBackingField = value; + } + + private int _privateField; + + public int GetPrivateField() + { + return _privateField; + } + + public void SetPrivateField(int value) + { + _privateField = value; + } + + public static IReadOnlyList GetRequiredProperties() + { + return new MemberInfo[] + { + typeof(TestEntity).GetProperty(nameof(Id)) ?? throw new Exception($"Property {nameof(Id)} not found."), + typeof(TestEntity).GetProperty(nameof(RequiredName)) ?? throw new Exception($"Property {nameof(RequiredName)} not found."), + typeof(TestEntity).GetProperty(nameof(Count)) ?? throw new Exception($"Property {nameof(Count)} not found."), + typeof(TestEntity).GetProperty(nameof(PropertyWithBackingField)) ?? throw new Exception($"Property {nameof(PropertyWithBackingField)} not found."), + typeof(TestEntity).GetField("_privateField", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new Exception("Field _privateField not found.") + }; + } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => + { + builder.Property("_privateField"); + builder.Property(e => e.ConvertibleClass).HasConversion(c => c!.Key, k => new ConvertibleClass(k)); + + builder.HasIndex(e => e.Id) + .HasDatabaseName("IX_TestEntities_Id"); + }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityBaseClass.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityBaseClass.cs index e9ee1e0e..8e3d59c3 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityBaseClass.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityBaseClass.cs @@ -1,7 +1,7 @@ -namespace Thinktecture.TestDatabaseContext; - -#pragma warning disable 8618 -public class TestEntityBaseClass -{ - public string Name { get; set; } -} +namespace Thinktecture.TestDatabaseContext; + +#pragma warning disable 8618 +public class TestEntityBaseClass +{ + public string Name { get; set; } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithAutoIncrement.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithAutoIncrement.cs index 0c174e56..c4440f99 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithAutoIncrement.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithAutoIncrement.cs @@ -1,7 +1,7 @@ -namespace Thinktecture.TestDatabaseContext; -#pragma warning disable 8618 -public class TestEntityWithAutoIncrement -{ - public int Id { get; set; } - public string? Name { get; set; } -} +namespace Thinktecture.TestDatabaseContext; +#pragma warning disable 8618 +public class TestEntityWithAutoIncrement +{ + public int Id { get; set; } + public string? Name { get; set; } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithBaseClass.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithBaseClass.cs index 51d4364f..46c30b00 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithBaseClass.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithBaseClass.cs @@ -1,12 +1,12 @@ -namespace Thinktecture.TestDatabaseContext; -#pragma warning disable 8618 - -public class TestEntityWithBaseClass : TestEntityBaseClass -{ - public Guid Id { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(); - } -} +namespace Thinktecture.TestDatabaseContext; +#pragma warning disable 8618 + +public class TestEntityWithBaseClass : TestEntityBaseClass +{ + public Guid Id { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithDotnetDefaultValues.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithDotnetDefaultValues.cs index 5b4e654f..6ac25134 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithDotnetDefaultValues.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithDotnetDefaultValues.cs @@ -1,22 +1,22 @@ -namespace Thinktecture.TestDatabaseContext; -#pragma warning disable 8618 -public class TestEntityWithDotnetDefaultValues -{ - public Guid Id { get; set; } - public int Int { get; set; } - public int? NullableInt { get; set; } - public string String { get; set; } - public string? NullableString { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => - { - builder.Property(e => e.Id).HasDefaultValue(new Guid("0B151271-79BB-4F6C-B85F-E8F61300FF1B")); - builder.Property(e => e.Int).HasDefaultValue(1); - builder.Property(e => e.NullableInt).HasDefaultValue(2); - builder.Property(e => e.String).HasDefaultValue("3"); - builder.Property(e => e.NullableString).HasDefaultValue("4"); - }); - } -} +namespace Thinktecture.TestDatabaseContext; +#pragma warning disable 8618 +public class TestEntityWithDotnetDefaultValues +{ + public Guid Id { get; set; } + public int Int { get; set; } + public int? NullableInt { get; set; } + public string String { get; set; } + public string? NullableString { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => + { + builder.Property(e => e.Id).HasDefaultValue(new Guid("0B151271-79BB-4F6C-B85F-E8F61300FF1B")); + builder.Property(e => e.Int).HasDefaultValue(1); + builder.Property(e => e.NullableInt).HasDefaultValue(2); + builder.Property(e => e.String).HasDefaultValue("3"); + builder.Property(e => e.NullableString).HasDefaultValue("4"); + }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithShadowProperties.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithShadowProperties.cs index 29a1d025..cae73d02 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithShadowProperties.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithShadowProperties.cs @@ -1,16 +1,16 @@ -namespace Thinktecture.TestDatabaseContext; -#pragma warning disable 8618 -public class TestEntityWithShadowProperties -{ - public Guid Id { get; set; } - public string? Name { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => - { - builder.Property("ShadowStringProperty").HasMaxLength(50); - builder.Property("ShadowIntProperty"); - }); - } -} +namespace Thinktecture.TestDatabaseContext; +#pragma warning disable 8618 +public class TestEntityWithShadowProperties +{ + public Guid Id { get; set; } + public string? Name { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => + { + builder.Property("ShadowStringProperty").HasMaxLength(50); + builder.Property("ShadowIntProperty"); + }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithSqlDefaultValues.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithSqlDefaultValues.cs index 64c94d8e..2fb3625a 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithSqlDefaultValues.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntityWithSqlDefaultValues.cs @@ -1,21 +1,21 @@ -namespace Thinktecture.TestDatabaseContext; -#pragma warning disable 8618 -public class TestEntityWithSqlDefaultValues -{ - public Guid Id { get; set; } - public int Int { get; set; } - public int? NullableInt { get; set; } - public string String { get; set; } - public string? NullableString { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => - { - builder.Property(e => e.Int).HasDefaultValueSql("1"); - builder.Property(e => e.NullableInt).HasDefaultValueSql("2"); - builder.Property(e => e.String).HasDefaultValueSql("'3'"); - builder.Property(e => e.NullableString).HasDefaultValueSql("'4'"); - }); - } -} +namespace Thinktecture.TestDatabaseContext; +#pragma warning disable 8618 +public class TestEntityWithSqlDefaultValues +{ + public Guid Id { get; set; } + public int Int { get; set; } + public int? NullableInt { get; set; } + public string String { get; set; } + public string? NullableString { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => + { + builder.Property(e => e.Int).HasDefaultValueSql("1"); + builder.Property(e => e.NullableInt).HasDefaultValueSql("2"); + builder.Property(e => e.String).HasDefaultValueSql("'3'"); + builder.Property(e => e.NullableString).HasDefaultValueSql("'4'"); + }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline.cs index 545d1e90..943e7896 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline.cs @@ -1,17 +1,17 @@ - - -// ReSharper disable InconsistentNaming -#pragma warning disable 8618 -namespace Thinktecture.TestDatabaseContext; - -public class TestEntity_Owns_Inline -{ - public Guid Id { get; set; } - - public OwnedEntity InlineEntity { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => builder.OwnsOne(e => e.InlineEntity)); - } -} + + +// ReSharper disable InconsistentNaming +#pragma warning disable 8618 +namespace Thinktecture.TestDatabaseContext; + +public class TestEntity_Owns_Inline +{ + public Guid Id { get; set; } + + public OwnedEntity InlineEntity { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => builder.OwnsOne(e => e.InlineEntity)); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline_Inline.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline_Inline.cs index c7aacb27..1a6448c5 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline_Inline.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline_Inline.cs @@ -1,18 +1,18 @@ - - -// ReSharper disable InconsistentNaming -#pragma warning disable 8618 -namespace Thinktecture.TestDatabaseContext; - -public class TestEntity_Owns_Inline_Inline -{ - public Guid Id { get; set; } - - public OwnedEntity_Owns_Inline InlineEntity { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => builder.OwnsOne(e => e.InlineEntity, - navigationBuilder => navigationBuilder.OwnsOne(e => e.InlineEntity))); - } -} + + +// ReSharper disable InconsistentNaming +#pragma warning disable 8618 +namespace Thinktecture.TestDatabaseContext; + +public class TestEntity_Owns_Inline_Inline +{ + public Guid Id { get; set; } + + public OwnedEntity_Owns_Inline InlineEntity { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => builder.OwnsOne(e => e.InlineEntity, + navigationBuilder => navigationBuilder.OwnsOne(e => e.InlineEntity))); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline_SeparateMany.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline_SeparateMany.cs index 65a2a179..dc8cdb60 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline_SeparateMany.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline_SeparateMany.cs @@ -1,23 +1,23 @@ - - -// ReSharper disable InconsistentNaming -#pragma warning disable 8618 -namespace Thinktecture.TestDatabaseContext; - -public class TestEntity_Owns_Inline_SeparateMany -{ - public Guid Id { get; set; } - - public OwnedEntity_Owns_SeparateMany InlineEntity { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => - { - builder.Property(e => e.Id).ValueGeneratedNever(); - builder.OwnsOne(e => e.InlineEntity, - navigationBuilder => navigationBuilder.OwnsMany(e => e.SeparateEntities, - innerBuilder => innerBuilder.ToTable("InlineEntities_SeparateMany"))); - }); - } -} + + +// ReSharper disable InconsistentNaming +#pragma warning disable 8618 +namespace Thinktecture.TestDatabaseContext; + +public class TestEntity_Owns_Inline_SeparateMany +{ + public Guid Id { get; set; } + + public OwnedEntity_Owns_SeparateMany InlineEntity { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => + { + builder.Property(e => e.Id).ValueGeneratedNever(); + builder.OwnsOne(e => e.InlineEntity, + navigationBuilder => navigationBuilder.OwnsMany(e => e.SeparateEntities, + innerBuilder => innerBuilder.ToTable("InlineEntities_SeparateMany"))); + }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline_SeparateOne.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline_SeparateOne.cs index b603844e..02140222 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline_SeparateOne.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_Inline_SeparateOne.cs @@ -1,19 +1,19 @@ - - -// ReSharper disable InconsistentNaming -#pragma warning disable 8618 -namespace Thinktecture.TestDatabaseContext; - -public class TestEntity_Owns_Inline_SeparateOne -{ - public Guid Id { get; set; } - - public OwnedEntity_Owns_SeparateOne InlineEntity { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => builder.OwnsOne(e => e.InlineEntity, - navigationBuilder => navigationBuilder.OwnsOne(e => e.SeparateEntity, - innerBuilder => innerBuilder.ToTable("InlineEntities_SeparateOne")))); - } -} + + +// ReSharper disable InconsistentNaming +#pragma warning disable 8618 +namespace Thinktecture.TestDatabaseContext; + +public class TestEntity_Owns_Inline_SeparateOne +{ + public Guid Id { get; set; } + + public OwnedEntity_Owns_SeparateOne InlineEntity { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => builder.OwnsOne(e => e.InlineEntity, + navigationBuilder => navigationBuilder.OwnsOne(e => e.SeparateEntity, + innerBuilder => innerBuilder.ToTable("InlineEntities_SeparateOne")))); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany.cs index 1656c918..e7a1d12f 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany.cs @@ -1,18 +1,18 @@ - - -// ReSharper disable InconsistentNaming -#pragma warning disable 8618 -namespace Thinktecture.TestDatabaseContext; - -public class TestEntity_Owns_SeparateMany -{ - public Guid Id { get; set; } - - public List SeparateEntities { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => builder.OwnsMany(e => e.SeparateEntities, - navigationBuilder => navigationBuilder.ToTable("SeparateEntitiesMany"))); - } -} + + +// ReSharper disable InconsistentNaming +#pragma warning disable 8618 +namespace Thinktecture.TestDatabaseContext; + +public class TestEntity_Owns_SeparateMany +{ + public Guid Id { get; set; } + + public List SeparateEntities { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => builder.OwnsMany(e => e.SeparateEntities, + navigationBuilder => navigationBuilder.ToTable("SeparateEntitiesMany"))); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany_Inline.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany_Inline.cs index d4bcf97b..718ddeb4 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany_Inline.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany_Inline.cs @@ -1,22 +1,22 @@ - - -// ReSharper disable InconsistentNaming -#pragma warning disable 8618 -namespace Thinktecture.TestDatabaseContext; - -public class TestEntity_Owns_SeparateMany_Inline -{ - public Guid Id { get; set; } - - public List SeparateEntities { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => builder.OwnsMany(e => e.SeparateEntities, - navigationBuilder => - { - navigationBuilder.ToTable("SeparateEntitiesMany_Inline"); - navigationBuilder.OwnsOne(e => e.InlineEntity); - })); - } -} + + +// ReSharper disable InconsistentNaming +#pragma warning disable 8618 +namespace Thinktecture.TestDatabaseContext; + +public class TestEntity_Owns_SeparateMany_Inline +{ + public Guid Id { get; set; } + + public List SeparateEntities { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => builder.OwnsMany(e => e.SeparateEntities, + navigationBuilder => + { + navigationBuilder.ToTable("SeparateEntitiesMany_Inline"); + navigationBuilder.OwnsOne(e => e.InlineEntity); + })); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany_SeparateMany.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany_SeparateMany.cs index 0b41f427..c70e047b 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany_SeparateMany.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany_SeparateMany.cs @@ -1,23 +1,23 @@ - - -// ReSharper disable InconsistentNaming -#pragma warning disable 8618 -namespace Thinktecture.TestDatabaseContext; - -public class TestEntity_Owns_SeparateMany_SeparateMany -{ - public Guid Id { get; set; } - - public List SeparateEntities { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => builder.OwnsMany(e => e.SeparateEntities, - navigationBuilder => - { - navigationBuilder.ToTable("SeparateEntitiesMany_SeparateEntitiesMany"); - navigationBuilder.OwnsMany(e => e.SeparateEntities, - innerBuilder => innerBuilder.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner")); - })); - } -} + + +// ReSharper disable InconsistentNaming +#pragma warning disable 8618 +namespace Thinktecture.TestDatabaseContext; + +public class TestEntity_Owns_SeparateMany_SeparateMany +{ + public Guid Id { get; set; } + + public List SeparateEntities { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => builder.OwnsMany(e => e.SeparateEntities, + navigationBuilder => + { + navigationBuilder.ToTable("SeparateEntitiesMany_SeparateEntitiesMany"); + navigationBuilder.OwnsMany(e => e.SeparateEntities, + innerBuilder => innerBuilder.ToTable("SeparateEntitiesMany_SeparateEntitiesMany_Inner")); + })); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany_SeparateOne.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany_SeparateOne.cs index e894258b..95bf9220 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany_SeparateOne.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateMany_SeparateOne.cs @@ -1,23 +1,23 @@ - - -// ReSharper disable InconsistentNaming -#pragma warning disable 8618 -namespace Thinktecture.TestDatabaseContext; - -public class TestEntity_Owns_SeparateMany_SeparateOne -{ - public Guid Id { get; set; } - - public List SeparateEntities { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => builder.OwnsMany(e => e.SeparateEntities, - navigationBuilder => - { - navigationBuilder.ToTable("SeparateEntitiesMany_SeparateEntitiesOne"); - navigationBuilder.OwnsOne(e => e.SeparateEntity, - innerBuilder => innerBuilder.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner")); - })); - } -} + + +// ReSharper disable InconsistentNaming +#pragma warning disable 8618 +namespace Thinktecture.TestDatabaseContext; + +public class TestEntity_Owns_SeparateMany_SeparateOne +{ + public Guid Id { get; set; } + + public List SeparateEntities { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => builder.OwnsMany(e => e.SeparateEntities, + navigationBuilder => + { + navigationBuilder.ToTable("SeparateEntitiesMany_SeparateEntitiesOne"); + navigationBuilder.OwnsOne(e => e.SeparateEntity, + innerBuilder => innerBuilder.ToTable("SeparateEntitiesMany_SeparateEntitiesOne_Inner")); + })); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne.cs index f660532c..98450186 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne.cs @@ -1,18 +1,18 @@ - - -// ReSharper disable InconsistentNaming -#pragma warning disable 8618 -namespace Thinktecture.TestDatabaseContext; - -public class TestEntity_Owns_SeparateOne -{ - public Guid Id { get; set; } - - public OwnedEntity? SeparateEntity { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => builder.OwnsOne(e => e.SeparateEntity, - navigationBuilder => navigationBuilder.ToTable("SeparateEntitiesOne"))); - } -} + + +// ReSharper disable InconsistentNaming +#pragma warning disable 8618 +namespace Thinktecture.TestDatabaseContext; + +public class TestEntity_Owns_SeparateOne +{ + public Guid Id { get; set; } + + public OwnedEntity? SeparateEntity { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => builder.OwnsOne(e => e.SeparateEntity, + navigationBuilder => navigationBuilder.ToTable("SeparateEntitiesOne"))); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne_Inline.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne_Inline.cs index 317be3e1..0e053609 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne_Inline.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne_Inline.cs @@ -1,18 +1,18 @@ -namespace Thinktecture.TestDatabaseContext; -#pragma warning disable 8618 -public class TestEntity_Owns_SeparateOne_Inline -{ - public Guid Id { get; set; } - - public OwnedEntity_Owns_Inline SeparateEntity { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => builder.OwnsOne(e => e.SeparateEntity, - navigationBuilder => - { - navigationBuilder.ToTable("SeparateEntitiesOne_Inline"); - navigationBuilder.OwnsOne(e => e.InlineEntity); - })); - } -} +namespace Thinktecture.TestDatabaseContext; +#pragma warning disable 8618 +public class TestEntity_Owns_SeparateOne_Inline +{ + public Guid Id { get; set; } + + public OwnedEntity_Owns_Inline SeparateEntity { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => builder.OwnsOne(e => e.SeparateEntity, + navigationBuilder => + { + navigationBuilder.ToTable("SeparateEntitiesOne_Inline"); + navigationBuilder.OwnsOne(e => e.InlineEntity); + })); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne_SeparateMany.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne_SeparateMany.cs index 551970d1..5b7ffaa7 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne_SeparateMany.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne_SeparateMany.cs @@ -1,19 +1,19 @@ -namespace Thinktecture.TestDatabaseContext; -#pragma warning disable 8618 -public class TestEntity_Owns_SeparateOne_SeparateMany -{ - public Guid Id { get; set; } - - public OwnedEntity_Owns_SeparateMany SeparateEntity { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => builder.OwnsOne(e => e.SeparateEntity, - navigationBuilder => - { - navigationBuilder.ToTable("SeparateEntitiesOne_SeparateMany"); - navigationBuilder.OwnsMany(e => e.SeparateEntities, - innerBuilder => innerBuilder.ToTable("SeparateEntitiesOne_SeparateMany_Inner")); - })); - } -} +namespace Thinktecture.TestDatabaseContext; +#pragma warning disable 8618 +public class TestEntity_Owns_SeparateOne_SeparateMany +{ + public Guid Id { get; set; } + + public OwnedEntity_Owns_SeparateMany SeparateEntity { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => builder.OwnsOne(e => e.SeparateEntity, + navigationBuilder => + { + navigationBuilder.ToTable("SeparateEntitiesOne_SeparateMany"); + navigationBuilder.OwnsMany(e => e.SeparateEntities, + innerBuilder => innerBuilder.ToTable("SeparateEntitiesOne_SeparateMany_Inner")); + })); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne_SeparateOne.cs b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne_SeparateOne.cs index 6e7525a6..5e929a2c 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne_SeparateOne.cs +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/TestDatabaseContext/TestEntity_Owns_SeparateOne_SeparateOne.cs @@ -1,22 +1,22 @@ - - -// ReSharper disable InconsistentNaming -namespace Thinktecture.TestDatabaseContext; -#pragma warning disable 8618 -public class TestEntity_Owns_SeparateOne_SeparateOne -{ - public Guid Id { get; set; } - - public OwnedEntity_Owns_SeparateOne SeparateEntity { get; set; } - - public static void Configure(ModelBuilder modelBuilder) - { - modelBuilder.Entity(builder => builder.OwnsOne(e => e.SeparateEntity, - navigationBuilder => - { - navigationBuilder.ToTable("SeparateEntitiesOne_SeparateOne"); - navigationBuilder.OwnsOne(e => e.SeparateEntity, - innerBuilder => innerBuilder.ToTable("SeparateEntitiesOne_SeparateOne_Inner")); - })); - } -} + + +// ReSharper disable InconsistentNaming +namespace Thinktecture.TestDatabaseContext; +#pragma warning disable 8618 +public class TestEntity_Owns_SeparateOne_SeparateOne +{ + public Guid Id { get; set; } + + public OwnedEntity_Owns_SeparateOne SeparateEntity { get; set; } + + public static void Configure(ModelBuilder modelBuilder) + { + modelBuilder.Entity(builder => builder.OwnsOne(e => e.SeparateEntity, + navigationBuilder => + { + navigationBuilder.ToTable("SeparateEntitiesOne_SeparateOne"); + navigationBuilder.OwnsOne(e => e.SeparateEntity, + innerBuilder => innerBuilder.ToTable("SeparateEntitiesOne_SeparateOne_Inner")); + })); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/Thinktecture.EntityFrameworkCore.TestHelpers.csproj b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/Thinktecture.EntityFrameworkCore.TestHelpers.csproj index b006af71..969068dc 100644 --- a/tests/Thinktecture.EntityFrameworkCore.TestHelpers/Thinktecture.EntityFrameworkCore.TestHelpers.csproj +++ b/tests/Thinktecture.EntityFrameworkCore.TestHelpers/Thinktecture.EntityFrameworkCore.TestHelpers.csproj @@ -1,12 +1,12 @@ - - - - $(NoWarn);CS1591;CA1063;CA1812;CA1816;CA1822;CA2000;EF1001 - false - - - - - - - + + + + $(NoWarn);CS1591;CA1063;CA1812;CA1816;CA1822;CA2000;EF1001 + false + + + + + + + diff --git a/tests/Thinktecture.EntityFrameworkCore.Testing.Tests/Extensions/EnumerableExtensionsTests/AsAsyncQueryable.cs b/tests/Thinktecture.EntityFrameworkCore.Testing.Tests/Extensions/EnumerableExtensionsTests/AsAsyncQueryable.cs index 7a9db2b7..b211f13a 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Testing.Tests/Extensions/EnumerableExtensionsTests/AsAsyncQueryable.cs +++ b/tests/Thinktecture.EntityFrameworkCore.Testing.Tests/Extensions/EnumerableExtensionsTests/AsAsyncQueryable.cs @@ -1,28 +1,28 @@ -namespace Thinktecture.Extensions.EnumerableExtensionsTests; - -public class AsAsyncQueryable -{ - [Fact] - public void Should_throw_if_collection_is_null() - { - Assert.Throws(() => ((IEnumerable)null!).AsAsyncQueryable()); - } - - [Fact] - public async Task Should_create_empty_queryable_if_collection_is_emtpy() - { - var query = Enumerable.Empty().AsAsyncQueryable(); - - (await query.ToListAsync()).Should().BeEmpty(); - query.ToList().Should().BeEmpty(); - } - - [Fact] - public async Task Should_create_queryable_from_collection() - { - var query = new[] { 1, 2, 3 }.AsAsyncQueryable(); - - (await query.ToListAsync()).Should().BeEquivalentTo(new[] { 1, 2, 3 }); - query.ToList().Should().BeEquivalentTo(new[] { 1, 2, 3 }); - } -} +namespace Thinktecture.Extensions.EnumerableExtensionsTests; + +public class AsAsyncQueryable +{ + [Fact] + public void Should_throw_if_collection_is_null() + { + Assert.Throws(() => ((IEnumerable)null!).AsAsyncQueryable()); + } + + [Fact] + public async Task Should_create_empty_queryable_if_collection_is_emtpy() + { + var query = Enumerable.Empty().AsAsyncQueryable(); + + (await query.ToListAsync()).Should().BeEmpty(); + query.ToList().Should().BeEmpty(); + } + + [Fact] + public async Task Should_create_queryable_from_collection() + { + var query = new[] { 1, 2, 3 }.AsAsyncQueryable(); + + (await query.ToListAsync()).Should().BeEquivalentTo(new[] { 1, 2, 3 }); + query.ToList().Should().BeEquivalentTo(new[] { 1, 2, 3 }); + } +} diff --git a/tests/Thinktecture.EntityFrameworkCore.Testing.Tests/Thinktecture.EntityFrameworkCore.Testing.Tests.csproj b/tests/Thinktecture.EntityFrameworkCore.Testing.Tests/Thinktecture.EntityFrameworkCore.Testing.Tests.csproj index cb846ca9..b9a87f8e 100644 --- a/tests/Thinktecture.EntityFrameworkCore.Testing.Tests/Thinktecture.EntityFrameworkCore.Testing.Tests.csproj +++ b/tests/Thinktecture.EntityFrameworkCore.Testing.Tests/Thinktecture.EntityFrameworkCore.Testing.Tests.csproj @@ -1,16 +1,16 @@ - - - - $(NoWarn);CS1591;CA1063;CA1812;CA1816;CA1822;CA2000;CA2007 - false - - - - - - - - - - - + + + + $(NoWarn);CS1591;CA1063;CA1812;CA1816;CA1822;CA2000;CA2007 + false + + + + + + + + + + +