├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── dependabot.yml ├── workflows │ ├── merge-dependabot.yml │ └── on-push-do-docs.yml └── stale.yml ├── src ├── Verify.EntityFramework.Tests │ ├── CoreTests.ShouldIgnoreDbContext.verified.txt │ ├── CoreTests.ShouldIgnoreDbFactory.verified.txt │ ├── CoreTests.ScrubInlineEfDateTimes.verified.txt │ ├── CoreTests.ShouldIgnoreDbFactoryInterface.verified.txt │ ├── CoreTests.ScrubInlineEfDateTimesFluent.verified.txt │ ├── CoreTests.SetSelect.verified.sql │ ├── CoreTests.SetSelect.verified.txt │ ├── CoreTests.IgnoreNavigationProperties.verified.txt │ ├── CoreTests.IgnoreNavigationPropertiesExplicit.verified.txt │ ├── CoreTests.Queryable.verified.txt │ ├── CoreTests.SingleMissingOrder.verified.txt │ ├── CoreTests.Deleted.verified.txt │ ├── CoreTests.Queryable.verified.sql │ ├── CoreTests.Added.verified.txt │ ├── CoreTests.NestedMissingOrderBy.verified.txt │ ├── CoreTests.MissingOrderBy.verified.txt │ ├── CoreTests.Modified.verified.txt │ ├── CoreTests.UpdateEntity.verified.txt │ ├── CoreTests.SomePropsModified.verified.txt │ ├── CoreTests.RecordingTest.verified.txt │ ├── CoreTests.RecordingWebApplicationFactory.verified.txt │ ├── CoreTests.RecordingWebApplicationFactory9.verified.txt │ ├── CoreTests.RecordingDisabledTest.verified.txt │ ├── CoreTests.NestedQueryable.verified.txt │ ├── Snippets │ │ ├── DataContext │ │ │ ├── Company.cs │ │ │ ├── Employee.cs │ │ │ └── SampleDbContext.cs │ │ └── DbContextBuilder.cs │ ├── CoreTests.WithOrderBy.verified.txt │ ├── CoreTests.RecordingWebApplicationFactory_run=0.verified.txt │ ├── CoreTests.RecordingWebApplicationFactory_run=1.verified.txt │ ├── CoreTests.RecordingWebApplicationFactory_run=2.verified.txt │ ├── CoreTests.RecordingWebApplicationFactory_run=3.verified.txt │ ├── CoreTests.RecordingWebApplicationFactory_run=4.verified.txt │ ├── CoreTests.RecordingSpecific.verified.txt │ ├── CoreTests.Parameters.verified.txt │ ├── CoreTests.WithNavigationProp.verified.txt │ ├── GlobalUsings.cs │ ├── CoreTests.MultiDbContexts.verified.txt │ ├── CoreTests.WithNestedOrderBy.verified.txt │ ├── CoreTests.AllData.verified.txt │ ├── ModuleInitializer.cs │ ├── DbUpdateExceptionTests.Run.verified.txt │ ├── Verify.EntityFramework.Tests.csproj │ ├── DbUpdateExceptionTests.cs │ ├── CoreTests.cs │ └── CoreTests.MultiRecording.verified.txt ├── key.snk ├── icon.png ├── global.json ├── Verify.EntityFrameworkClassic.Tests │ ├── ClassicTests.Deleted.verified.txt │ ├── ClassicTests.Added.verified.txt │ ├── GlobalUsings.cs │ ├── ClassicTests.Queryable.verified.txt │ ├── ClassicTests.Modified.verified.txt │ ├── ClassicTests.UpdateEntity.verified.txt │ ├── Snippets │ │ ├── DataContext │ │ │ ├── Company.cs │ │ │ ├── Employee.cs │ │ │ └── SampleDbContext.cs │ │ └── DbContextBuilder.cs │ ├── ModuleInitializer.cs │ ├── ClassicTests.WithNavigationProp.verified.txt │ ├── Verify.EntityFrameworkClassic.Tests.csproj │ └── ClassicTests.cs ├── Verify.EntityFrameworkClassic │ ├── GlobalUsings.cs │ ├── ChangePair.cs │ ├── Verify.EntityFrameworkClassic.csproj │ ├── Converters │ │ ├── QueryableConverter.cs │ │ └── TrackerConverter.cs │ ├── VerifyEntityFrameworkClassic.cs │ ├── QueryableSerializer.cs │ └── Extensions.cs ├── mdsnippets.json ├── Verify.EntityFramework │ ├── RemoveSquareBracketVisitor.cs │ ├── MissingOrder │ │ ├── RelationalFactory.cs │ │ ├── RelationalVisitor.cs │ │ └── MissingOrderByVisitor.cs │ ├── Verify.EntityFramework.csproj │ ├── GlobalUsings.cs │ ├── LogData.cs │ ├── Converters │ │ ├── LogEntryConverter.cs │ │ ├── DbUpdateExceptionConverter.cs │ │ ├── QueryableConverter.cs │ │ └── TrackerConverter.cs │ ├── SqlFormatter.cs │ ├── LogCommandInterceptor.cs │ ├── Extensions.cs │ └── VerifyEntityFramework.cs ├── appveyor.yml ├── Directory.Build.props ├── Verify.EntityFramework.slnx ├── Verify.EntityFramework.slnx.DotSettings ├── nuget.md ├── nuget.config ├── Directory.Packages.props └── Shared.sln.DotSettings ├── docs ├── zzz.png ├── intro.include.md └── zzz.include.md ├── .gitignore ├── code_of_conduct.md ├── .gitattributes ├── license.txt ├── .editorconfig └── readme.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: VerifyTests 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.ShouldIgnoreDbContext.verified.txt: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.ShouldIgnoreDbFactory.verified.txt: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.ScrubInlineEfDateTimes.verified.txt: -------------------------------------------------------------------------------- 1 | DateTime_1 -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.ShouldIgnoreDbFactoryInterface.verified.txt: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /src/key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VerifyTests/Verify.EntityFramework/HEAD/src/key.snk -------------------------------------------------------------------------------- /docs/zzz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VerifyTests/Verify.EntityFramework/HEAD/docs/zzz.png -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.ScrubInlineEfDateTimesFluent.verified.txt: -------------------------------------------------------------------------------- 1 | DateTime_1 -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VerifyTests/Verify.EntityFramework/HEAD/src/icon.png -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.SetSelect.verified.sql: -------------------------------------------------------------------------------- 1 | SELECT [c].[Id] 2 | FROM [Companies] AS [c] -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.SetSelect.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | 1, 3 | 4, 4 | 6, 5 | 7 6 | ] -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.IgnoreNavigationProperties.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Name: employee 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.user 3 | bin/ 4 | obj/ 5 | .vs/ 6 | *.DotSettings.user 7 | .idea/ 8 | *.received.* 9 | nugets/ -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.IgnoreNavigationPropertiesExplicit.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Name: employee 3 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.Queryable.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Name: company name 4 | } 5 | ] -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.SingleMissingOrder.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Id: 1, 3 | Name: Company1 4 | } -------------------------------------------------------------------------------- /docs/intro.include.md: -------------------------------------------------------------------------------- 1 | Extends [Verify](https://github.com/VerifyTests/Verify) to allow snapshot testing with EntityFramework. -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.Deleted.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Deleted: { 3 | Company: { 4 | Id: 0 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "10.0.101", 4 | "allowPrerelease": true, 5 | "rollForward": "latestFeature" 6 | } 7 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic.Tests/ClassicTests.Deleted.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Deleted: { 3 | Company: { 4 | Id: 0 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.Queryable.verified.sql: -------------------------------------------------------------------------------- 1 | SELECT [c].[Id], [c].[Name] 2 | FROM [Companies] AS [c] 3 | WHERE [c].[Name] = N'company name' -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.Added.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Added: { 3 | Company: { 4 | Id: 0, 5 | Name: company name 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic.Tests/ClassicTests.Added.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Added: { 3 | Company: { 4 | Id: 0, 5 | Content: before 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: nuget 4 | directory: "/src" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Data.Entity; 2 | global using System.Data.Entity.Core.Objects; 3 | global using System.Data.Entity.Infrastructure; -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic.Tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.ComponentModel.DataAnnotations.Schema; 2 | global using System.Data.Common; 3 | global using System.Data.Entity; -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.NestedMissingOrderBy.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Type: Exception, 3 | Message: 4 | TableExpression must have at least one ordering. 5 | Expression: 6 | Employees AS e 7 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.MissingOrderBy.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Type: Exception, 3 | Message: 4 | SelectExpression must have at least one ordering. 5 | Expression: 6 | SELECT c.Id, c.Name 7 | FROM Companies AS c 8 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.Modified.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Modified: { 3 | Company: { 4 | Id: 0, 5 | Name: { 6 | Original: old name, 7 | Current: new name 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.UpdateEntity.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Modified: { 3 | Employee: { 4 | Id: 0, 5 | Name: { 6 | Original: before, 7 | Current: after 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic.Tests/ClassicTests.Queryable.verified.txt: -------------------------------------------------------------------------------- 1 | SELECT 2 | [Extent1].[Id] AS [Id], 3 | [Extent1].[Content] AS [Content] 4 | FROM [dbo].[Companies] AS [Extent1] 5 | WHERE N'value' = [Extent1].[Content] -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.SomePropsModified.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Modified: { 3 | Employee: { 4 | Id: 0, 5 | Name: { 6 | Original: before, 7 | Current: after 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic.Tests/ClassicTests.Modified.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Modified: { 3 | Company: { 4 | Id: 0, 5 | Content: { 6 | Original: before, 7 | Current: after 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic.Tests/ClassicTests.UpdateEntity.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Modified: { 3 | Company: { 4 | Id: 0, 5 | Content: { 6 | Original: before, 7 | Current: after 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic/ChangePair.cs: -------------------------------------------------------------------------------- 1 | class ChangePair(string key, object? original, object? current) 2 | { 3 | public string Key { get; } = key; 4 | public object? Original { get; } = original; 5 | public object? Current { get; } = current; 6 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.RecordingTest.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | ef: { 3 | Type: ReaderExecutedAsync, 4 | HasTransaction: false, 5 | Text: 6 | select c.Id, 7 | c.Name 8 | from Companies as c 9 | where c.Name = N'Title' 10 | } 11 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.RecordingWebApplicationFactory.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | target: 1, 3 | sql: [ 4 | { 5 | Type: ReaderExecutedAsync, 6 | Text: 7 | SELECT "c"."Id", "c"."Content" 8 | FROM "Companies" AS "c" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.RecordingWebApplicationFactory9.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | target: 1, 3 | sql: [ 4 | { 5 | Type: ReaderExecutedAsync, 6 | Text: 7 | SELECT "c"."Id", "c"."Content" 8 | FROM "Companies" AS "c" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.RecordingDisabledTest.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | ef: { 3 | Type: ReaderExecutedAsync, 4 | HasTransaction: false, 5 | Text: 6 | select c.Id, 7 | c.Name 8 | from Companies as c 9 | where c.Name = N'Title' 10 | } 11 | } -------------------------------------------------------------------------------- /src/mdsnippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/SimonCropp/MarkdownSnippets/master/schema.json", 3 | "TocExcludes": [ "NuGet package", "Release Notes", "Icon" ], 4 | "MaxWidth": 90, 5 | "ValidateContent": true, 6 | "Convention": "InPlaceOverwrite" 7 | } -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This project has adopted the code of conduct defined by the Contributor Covenant 4 | to clarify expected behavior in our community. 5 | For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/about/code-of-conduct). 6 | -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.NestedQueryable.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | queryable: { 3 | Sql: 4 | select c.Id, 5 | c.Name 6 | from Companies as c 7 | where c.Name = N'value', 8 | Result: [ 9 | { 10 | Name: value 11 | } 12 | ] 13 | } 14 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/Snippets/DataContext/Company.cs: -------------------------------------------------------------------------------- 1 | public class Company 2 | { 3 | [DatabaseGenerated(DatabaseGeneratedOption.None)] 4 | public int Id { get; set; } 5 | 6 | public required string Name { get; set; } 7 | public List Employees { get; set; } = null!; 8 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.WithOrderBy.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Id: 1, 4 | Name: Company1 5 | }, 6 | { 7 | Id: 4, 8 | Name: Company2 9 | }, 10 | { 11 | Id: 6, 12 | Name: Company3 13 | }, 14 | { 15 | Id: 7, 16 | Name: Company4 17 | } 18 | ] -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic.Tests/Snippets/DataContext/Company.cs: -------------------------------------------------------------------------------- 1 | public class Company 2 | { 3 | [DatabaseGenerated(DatabaseGeneratedOption.None)] 4 | public int Id { get; set; } 5 | 6 | public string? Content { get; set; } 7 | public List Employees { get; set; } = null!; 8 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.RecordingWebApplicationFactory_run=0.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | target: 1, 3 | sql: [ 4 | { 5 | ef: { 6 | Type: ReaderExecutedAsync, 7 | HasTransaction: false, 8 | Text: 9 | SELECT "c"."Id", "c"."Name" 10 | FROM "Companies" AS "c" 11 | } 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.RecordingWebApplicationFactory_run=1.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | target: 1, 3 | sql: [ 4 | { 5 | ef: { 6 | Type: ReaderExecutedAsync, 7 | HasTransaction: false, 8 | Text: 9 | SELECT "c"."Id", "c"."Name" 10 | FROM "Companies" AS "c" 11 | } 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.RecordingWebApplicationFactory_run=2.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | target: 1, 3 | sql: [ 4 | { 5 | ef: { 6 | Type: ReaderExecutedAsync, 7 | HasTransaction: false, 8 | Text: 9 | SELECT "c"."Id", "c"."Name" 10 | FROM "Companies" AS "c" 11 | } 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.RecordingWebApplicationFactory_run=3.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | target: 1, 3 | sql: [ 4 | { 5 | ef: { 6 | Type: ReaderExecutedAsync, 7 | HasTransaction: false, 8 | Text: 9 | SELECT "c"."Id", "c"."Name" 10 | FROM "Companies" AS "c" 11 | } 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.RecordingWebApplicationFactory_run=4.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | target: 1, 3 | sql: [ 4 | { 5 | ef: { 6 | Type: ReaderExecutedAsync, 7 | HasTransaction: false, 8 | Text: 9 | SELECT "c"."Id", "c"."Name" 10 | FROM "Companies" AS "c" 11 | } 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.RecordingSpecific.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | target: 5, 3 | entries: [ 4 | { 5 | ef: { 6 | Type: ReaderExecutedAsync, 7 | HasTransaction: false, 8 | Text: 9 | select c.Id, 10 | c.Name 11 | from Companies as c 12 | where c.Name = N'Title' 13 | } 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework/RemoveSquareBracketVisitor.cs: -------------------------------------------------------------------------------- 1 | class RemoveSquareBracketVisitor : TSqlFragmentVisitor 2 | { 3 | public override void Visit(Identifier node) 4 | { 5 | if (node.QuoteType == QuoteType.SquareBracket) 6 | { 7 | node.QuoteType = QuoteType.NotQuoted; 8 | } 9 | 10 | base.Visit(node); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/Snippets/DataContext/Employee.cs: -------------------------------------------------------------------------------- 1 | public class Employee 2 | { 3 | [DatabaseGenerated(DatabaseGeneratedOption.None)] 4 | public int Id { get; set; } 5 | 6 | public int CompanyId { get; set; } 7 | public Company Company { get; set; } = null!; 8 | public required string Name { get; set; } 9 | public int Age { get; set; } 10 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic.Tests/Snippets/DataContext/Employee.cs: -------------------------------------------------------------------------------- 1 | public class Employee 2 | { 3 | [DatabaseGenerated(DatabaseGeneratedOption.None)] 4 | public int Id { get; set; } 5 | 6 | public int CompanyId { get; set; } 7 | public Company Company { get; set; } = null!; 8 | public string? Content { get; set; } 9 | public int Age { get; set; } 10 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.Parameters.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | ef: { 3 | Type: ReaderExecutedAsync, 4 | HasTransaction: false, 5 | Parameters: { 6 | @p0 (Int32): 0, 7 | @p1 (String): Guid_1 8 | }, 9 | Text: 10 | set implicit_transactions off; 11 | 12 | set nocount on; 13 | 14 | insert into Companies (Id, Name) 15 | values (@p0, @p1) 16 | } 17 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.WithNavigationProp.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Modified: { 3 | Company: { 4 | Id: 0, 5 | Name: { 6 | Original: companyBefore, 7 | Current: companyAfter 8 | } 9 | }, 10 | Employee: { 11 | Id: 0, 12 | Name: { 13 | Original: employeeBefore, 14 | Current: employeeAfter 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic.Tests/ModuleInitializer.cs: -------------------------------------------------------------------------------- 1 | public static class ModuleInitializer 2 | { 3 | #region EnableClassic 4 | 5 | [ModuleInitializer] 6 | public static void Init() => 7 | VerifyEntityFrameworkClassic.Initialize(); 8 | 9 | #endregion 10 | 11 | [ModuleInitializer] 12 | public static void InitOther() => 13 | VerifierSettings.InitializePlugins(); 14 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic.Tests/ClassicTests.WithNavigationProp.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Modified: { 3 | Company: { 4 | Id: 0, 5 | Content: { 6 | Original: companyBefore, 7 | Current: companyAfter 8 | } 9 | }, 10 | Employee: { 11 | Id: 0, 12 | Content: { 13 | Original: employeeBefore, 14 | Current: employeeAfter 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /.github/workflows/merge-dependabot.yml: -------------------------------------------------------------------------------- 1 | name: merge-dependabot 2 | on: 3 | pull_request: 4 | jobs: 5 | automerge: 6 | runs-on: ubuntu-latest 7 | if: github.actor == 'dependabot[bot]' 8 | steps: 9 | - name: Dependabot Auto Merge 10 | uses: ahmadnassri/action-dependabot-auto-merge@v2.6.6 11 | with: 12 | target: minor 13 | github-token: ${{ secrets.dependabot }} 14 | command: squash and merge -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic.Tests/Snippets/DataContext/SampleDbContext.cs: -------------------------------------------------------------------------------- 1 | public class SampleDbContext(DbConnection connection) : 2 | DbContext(connection, false) 3 | { 4 | public DbSet Employees { get; set; } = null!; 5 | public DbSet Companies { get; set; } = null!; 6 | 7 | protected override void OnModelCreating(DbModelBuilder model) 8 | { 9 | model.Entity(); 10 | model.Entity(); 11 | } 12 | } -------------------------------------------------------------------------------- /docs/zzz.include.md: -------------------------------------------------------------------------------- 1 | ### Entity Framework Extensions 2 | 3 | [Entity Framework Extensions](https://entityframework-extensions.net/?utm_source=simoncropp&utm_medium=Verify.EntityFramework) is a major sponsor and is proud to contribute to the development this project. 4 | 5 | [![Entity Framework Extensions](https://raw.githubusercontent.com/VerifyTests/Verify.EntityFramework/refs/heads/main/docs/zzz.png)](https://entityframework-extensions.net/?utm_source=simoncropp&utm_medium=Verify.EntityFramework) -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.snk binary 3 | *.png binary 4 | 5 | *.verified.sql text eol=lf working-tree-encoding=UTF-8 6 | *.verified.txt text eol=lf working-tree-encoding=UTF-8 7 | *.verified.xml text eol=lf working-tree-encoding=UTF-8 8 | *.verified.json text eol=lf working-tree-encoding=UTF-8 9 | 10 | .editorconfig text eol=lf working-tree-encoding=UTF-8 11 | *.sln.DotSettings text eol=lf working-tree-encoding=UTF-8 12 | *.slnx.DotSettings text eol=lf working-tree-encoding=UTF-8 -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.ComponentModel.DataAnnotations.Schema; 2 | global using System.Net.Http.Json; 3 | global using Argon; 4 | global using Microsoft.AspNetCore.Builder; 5 | global using Microsoft.AspNetCore.Hosting; 6 | global using Microsoft.AspNetCore.Mvc.Testing; 7 | global using Microsoft.AspNetCore.TestHost; 8 | global using Microsoft.Data.Sqlite; 9 | global using Microsoft.EntityFrameworkCore; 10 | global using Microsoft.Extensions.DependencyInjection; 11 | global using Microsoft.Extensions.Hosting; -------------------------------------------------------------------------------- /src/Verify.EntityFramework/MissingOrder/RelationalFactory.cs: -------------------------------------------------------------------------------- 1 | class RelationalFactory( 2 | ShapedQueryCompilingExpressionVisitorDependencies dependencies, 3 | RelationalShapedQueryCompilingExpressionVisitorDependencies relational) : 4 | RelationalShapedQueryCompilingExpressionVisitorFactory(dependencies, relational) 5 | { 6 | public override ShapedQueryCompilingExpressionVisitor Create(QueryCompilationContext context) 7 | => new RelationalVisitor( 8 | Dependencies, 9 | RelationalDependencies, 10 | context); 11 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic/Verify.EntityFrameworkClassic.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net48;net8.0;net9.0;net10.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/Snippets/DataContext/SampleDbContext.cs: -------------------------------------------------------------------------------- 1 | public class SampleDbContext(DbContextOptions options) : 2 | DbContext(options) 3 | { 4 | public DbSet Employees { get; set; } = null!; 5 | public DbSet Companies { get; set; } = null!; 6 | 7 | protected override void OnModelCreating(ModelBuilder model) 8 | { 9 | model 10 | .Entity() 11 | .HasMany(_ => _.Employees) 12 | .WithOne(_ => _.Company) 13 | .IsRequired(); 14 | model.Entity(); 15 | } 16 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework/MissingOrder/RelationalVisitor.cs: -------------------------------------------------------------------------------- 1 | class RelationalVisitor( 2 | ShapedQueryCompilingExpressionVisitorDependencies dependencies, 3 | RelationalShapedQueryCompilingExpressionVisitorDependencies relational, 4 | QueryCompilationContext context) : 5 | RelationalShapedQueryCompilingExpressionVisitor(dependencies, relational, context) 6 | { 7 | [return: NotNullIfNotNull(nameof(node))] 8 | public override Expression? Visit(Expression? node) 9 | { 10 | new MissingOrderByVisitor().Visit(node); 11 | return base.Visit(node); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.MultiDbContexts.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | ef: [ 3 | { 4 | Type: ReaderExecutedAsync, 5 | HasTransaction: false, 6 | Parameters: { 7 | @p0 (Int32): 0, 8 | @p1 (String): Title 9 | }, 10 | Text: 11 | set implicit_transactions off; 12 | 13 | set nocount on; 14 | 15 | insert into Companies (Id, Name) 16 | values (@p0, @p1) 17 | }, 18 | { 19 | Type: ReaderExecutedAsync, 20 | HasTransaction: false, 21 | Text: 22 | select c.Id, 23 | c.Name 24 | from Companies as c 25 | where c.Name = N'Title' 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework/Verify.EntityFramework.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net10.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.WithNestedOrderBy.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | Id: 1, 4 | Name: Company1, 5 | Employees: [ 6 | { 7 | Id: 2, 8 | CompanyId: 1, 9 | Name: Employee1, 10 | Age: 25 11 | }, 12 | { 13 | Id: 3, 14 | CompanyId: 1, 15 | Name: Employee2, 16 | Age: 31 17 | } 18 | ] 19 | }, 20 | { 21 | Id: 4, 22 | Name: Company2, 23 | Employees: [ 24 | { 25 | Id: 5, 26 | CompanyId: 4, 27 | Name: Employee4, 28 | Age: 34 29 | } 30 | ] 31 | }, 32 | { 33 | Id: 6, 34 | Name: Company3 35 | }, 36 | { 37 | Id: 7, 38 | Name: Company4 39 | } 40 | ] -------------------------------------------------------------------------------- /src/appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | environment: 3 | DOTNET_NOLOGO: true 4 | DOTNET_CLI_TELEMETRY_OPTOUT: true 5 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 6 | build_script: 7 | - pwsh: | 8 | Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile "./dotnet-install.ps1" 9 | ./dotnet-install.ps1 -JSonFile src/global.json -Architecture x64 -InstallDir 'C:\Program Files\dotnet' 10 | - dotnet build src --configuration Release 11 | - dotnet test src --configuration Release --no-build --no-restore --filter Category!=Integration 12 | test: off 13 | on_failure: 14 | - ps: Get-ChildItem *.received.* -recurse | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } 15 | artifacts: 16 | - path: nugets\*.nupkg -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.AllData.verified.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | $type: Company, 4 | Id: 1, 5 | Name: Company1 6 | }, 7 | { 8 | $type: Company, 9 | Id: 4, 10 | Name: Company2 11 | }, 12 | { 13 | $type: Company, 14 | Id: 6, 15 | Name: Company3 16 | }, 17 | { 18 | $type: Company, 19 | Id: 7, 20 | Name: Company4 21 | }, 22 | { 23 | $type: Employee, 24 | Id: 2, 25 | CompanyId: 1, 26 | Name: Employee1, 27 | Age: 25 28 | }, 29 | { 30 | $type: Employee, 31 | Id: 3, 32 | CompanyId: 1, 33 | Name: Employee2, 34 | Age: 31 35 | }, 36 | { 37 | $type: Employee, 38 | Id: 5, 39 | CompanyId: 4, 40 | Name: Employee4, 41 | Age: 34 42 | } 43 | ] -------------------------------------------------------------------------------- /src/Verify.EntityFramework/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Data; 2 | global using System.Data.Common; 3 | global using System.Diagnostics.CodeAnalysis; 4 | global using System.Globalization; 5 | global using System.Linq.Expressions; 6 | global using Argon; 7 | global using Microsoft.EntityFrameworkCore; 8 | global using Microsoft.EntityFrameworkCore.ChangeTracking; 9 | global using Microsoft.EntityFrameworkCore.Diagnostics; 10 | global using Microsoft.EntityFrameworkCore.Metadata; 11 | global using Microsoft.EntityFrameworkCore.Query; 12 | global using Microsoft.EntityFrameworkCore.Query.Internal; 13 | global using Microsoft.EntityFrameworkCore.Query.SqlExpressions; 14 | global using Microsoft.SqlServer.TransactSql.ScriptDom; 15 | global using VerifyTests.EntityFramework; -------------------------------------------------------------------------------- /src/Verify.EntityFramework/LogData.cs: -------------------------------------------------------------------------------- 1 | namespace VerifyTests.EntityFramework; 2 | 3 | public class LogEntry( 4 | string type, 5 | DbCommand command, 6 | CommandEndEventData data, 7 | Exception? exception = null) 8 | { 9 | public string Type { get; } = type; 10 | public DateTimeOffset StartTime { get; } = data.StartTime; 11 | internal bool IsSqlServer { get; } = command.GetType().Name == "SqlCommand"; 12 | public TimeSpan Duration { get; } = data.Duration; 13 | public bool HasTransaction { get; } = command.Transaction != null; 14 | public Exception? Exception { get; } = exception; 15 | public IDictionary Parameters { get; } = command.Parameters.ToDictionary(); 16 | public string Text { get; } = command.CommandText.Trim(); 17 | } -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CS1591;CS0649;CS8632;EF1001;NU1608;NU1109 5 | 14.0.0 6 | preview 7 | 1.0.0 8 | EntityFramework, Verify 9 | Extends Verify (https://github.com/VerifyTests/Verify) to allow verification of EntityFramework bits. 10 | true 11 | true 12 | true 13 | true 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic.Tests/Verify.EntityFrameworkClassic.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net10.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.github/workflows/on-push-do-docs.yml: -------------------------------------------------------------------------------- 1 | name: on-push-do-docs 2 | on: 3 | push: 4 | jobs: 5 | docs: 6 | runs-on: windows-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | - name: Run MarkdownSnippets 10 | run: | 11 | dotnet tool install --global MarkdownSnippets.Tool 12 | mdsnippets ${GITHUB_WORKSPACE} 13 | shell: bash 14 | - name: Push changes 15 | run: | 16 | git config --local user.email "action@github.com" 17 | git config --local user.name "GitHub Action" 18 | git commit -m "Docs changes" -a || echo "nothing to commit" 19 | remote="https://${GITHUB_ACTOR}:${{secrets.GITHUB_TOKEN}}@github.com/${GITHUB_REPOSITORY}.git" 20 | branch="${GITHUB_REF:11}" 21 | git push "${remote}" ${branch} || echo "nothing to push" 22 | shell: bash -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/ModuleInitializer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Metadata; 2 | 3 | public static class ModuleInitializer 4 | { 5 | #region EnableCore 6 | 7 | static IModel GetDbModel() 8 | { 9 | var options = new DbContextOptionsBuilder(); 10 | options.UseSqlServer("fake"); 11 | using var data = new SampleDbContext(options.Options); 12 | return data.Model; 13 | } 14 | 15 | [ModuleInitializer] 16 | public static void Init() 17 | { 18 | var model = GetDbModel(); 19 | VerifyEntityFramework.Initialize(model); 20 | } 21 | 22 | #endregion 23 | 24 | [ModuleInitializer] 25 | public static void InitOther() 26 | { 27 | VerifierSettings.InitializePlugins(); 28 | Recording.IgnoreNames("sql"); 29 | } 30 | } -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 7 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Set to true to ignore issues in a milestone (defaults to false) 6 | exemptMilestones: true 7 | # Comment to post when marking an issue as stale. Set to `false` to disable 8 | markComment: > 9 | This issue has been automatically marked as stale because it has not had 10 | recent activity. It will be closed if no further activity occurs. Thank you 11 | for your contributions. 12 | # Comment to post when closing a stale issue. Set to `false` to disable 13 | closeComment: false 14 | # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': 15 | pulls: 16 | daysUntilStale: 30 17 | exemptLabels: 18 | - Question 19 | - Bug 20 | - Feature 21 | - Improvement -------------------------------------------------------------------------------- /src/Verify.EntityFramework.slnx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Verify.EntityFramework/Converters/LogEntryConverter.cs: -------------------------------------------------------------------------------- 1 | class LogEntryConverter : WriteOnlyJsonConverter 2 | { 3 | public override void Write(VerifyJsonWriter writer, LogEntry logEntry) 4 | { 5 | writer.WriteStartObject(); 6 | 7 | writer.WriteMember(logEntry, logEntry.Type, "Type"); 8 | writer.WriteMember(logEntry, logEntry.HasTransaction, "HasTransaction"); 9 | writer.WriteMember(logEntry, logEntry.Exception, "Exception"); 10 | writer.WriteMember(logEntry, logEntry.Parameters, "Parameters"); 11 | if (logEntry.IsSqlServer) 12 | { 13 | var text = SqlFormatter.Format(logEntry.Text); 14 | writer.WriteMember(logEntry, text.ToString(), "Text"); 15 | } 16 | else 17 | { 18 | writer.WriteMember(logEntry, logEntry.Text, "Text"); 19 | } 20 | 21 | writer.WriteEndObject(); 22 | } 23 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.slnx.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | ..\Shared.sln.DotSettings 3 | True 4 | True 5 | 1 6 | -------------------------------------------------------------------------------- /src/nuget.md: -------------------------------------------------------------------------------- 1 | [Documentation](https://github.com/VerifyTests/Verify.EntityFramework) 2 | 3 | Extends [Verify](https://github.com/VerifyTests/Verify) to allow snapshot testing with EntityFramework. 4 | 5 | **See [Milestones](https://github.com/VerifyTests/Verify.EntityFramework/milestones?state=closed) for release notes.** 6 | 7 | 8 | ## Sponsors 9 | 10 | 11 | ### Entity Framework Extensions 12 | 13 | [Entity Framework Extensions](https://entityframework-extensions.net/?utm_source=simoncropp&utm_medium=Verify.EntityFramework) is a major sponsor and is proud to contribute to the development this project. 14 | 15 | [![Entity Framework Extensions](https://raw.githubusercontent.com/VerifyTests/Verify.EntityFramework/refs/heads/main/docs/zzz.png)](https://entityframework-extensions.net/?utm_source=simoncropp&utm_medium=Verify.EntityFramework) 16 | -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic/Converters/QueryableConverter.cs: -------------------------------------------------------------------------------- 1 | class QueryableConverter : 2 | WriteOnlyJsonConverter 3 | { 4 | public override void Write(VerifyJsonWriter writer, object data) 5 | { 6 | var sql = QueryToSql(data); 7 | writer.Serialize(sql); 8 | } 9 | 10 | public static string QueryToSql(object data) 11 | { 12 | var entityType = data 13 | .GetType() 14 | .GetGenericArguments() 15 | .Single(); 16 | var queryableSerializer = typeof(QueryableSerializer<>).MakeGenericType(entityType); 17 | return (string) queryableSerializer.InvokeMember( 18 | name: "ToSql", 19 | invokeAttr: BindingFlags.InvokeMethod, 20 | binder: null, 21 | target: null, 22 | args: [data])!; 23 | } 24 | 25 | public override bool CanConvert(Type type) 26 | => IsQueryable(type); 27 | 28 | public static bool IsQueryable(object target) 29 | => target is IQueryable; 30 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic/VerifyEntityFrameworkClassic.cs: -------------------------------------------------------------------------------- 1 | namespace VerifyTests; 2 | 3 | public static class VerifyEntityFrameworkClassic 4 | { 5 | public static bool Initialized { get; private set; } 6 | 7 | public static void Initialize() 8 | { 9 | if (Initialized) 10 | { 11 | throw new("Already Initialized"); 12 | } 13 | 14 | Initialized = true; 15 | 16 | VerifierSettings.RegisterFileConverter( 17 | QueryableToSql, 18 | (target, _) => QueryableConverter.IsQueryable(target)); 19 | VerifierSettings.AddExtraSettings(serializer => 20 | { 21 | var converters = serializer.Converters; 22 | converters.Add(new TrackerConverter()); 23 | converters.Add(new QueryableConverter()); 24 | }); 25 | } 26 | 27 | static ConversionResult QueryableToSql(object arg, IReadOnlyDictionary context) 28 | { 29 | var sql = QueryableConverter.QueryToSql(arg); 30 | return new(null, "txt", sql); 31 | } 32 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/DbUpdateExceptionTests.Run.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | Message: An error occurred while saving the entity changes. See the inner exception for details., 3 | Type: DbUpdateException, 4 | InnerException: { 5 | Errors: [ 6 | { 7 | Message: Violation of PRIMARY KEY constraint 'PK_TestEntities'. Cannot insert duplicate key in object 'dbo.TestEntities'. The duplicate key value is (Guid_1)., 8 | Number: 2627, 9 | Line: 3 10 | }, 11 | { 12 | Message: The statement has been terminated., 13 | Number: 3621, 14 | Line: 3 15 | } 16 | ] 17 | }, 18 | Entries: [ 19 | { 20 | EntryProperties: { 21 | Id: { 22 | OriginalValue: Guid_1, 23 | CurrentValue: Guid_1, 24 | IsTemporary: false, 25 | IsModified: false 26 | }, 27 | Property: { 28 | OriginalValue: Item1, 29 | CurrentValue: Item1, 30 | IsTemporary: false, 31 | IsModified: false 32 | } 33 | }, 34 | State: Added 35 | } 36 | ] 37 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic/QueryableSerializer.cs: -------------------------------------------------------------------------------- 1 | static class QueryableSerializer 2 | where TEntity : class 3 | { 4 | public static string ToSql(IQueryable query) 5 | { 6 | var linq = GetObjectQuery((DbQuery) query); 7 | var sql = linq.ToTraceString(); 8 | return linq.Parameters.Aggregate(sql, (current, p) => current.Replace("@" + p.Name, "\'" + p.Value + "\'")); 9 | } 10 | 11 | static object Private(object obj, string privateField) 12 | => obj 13 | .GetType() 14 | .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(obj)!; 15 | 16 | static T Private(object obj, string privateField) 17 | => (T) obj 18 | .GetType() 19 | .GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(obj)!; 20 | 21 | public static ObjectQuery GetObjectQuery(DbQuery query) 22 | { 23 | var internalQuery = Private(query, "_internalQuery"); 24 | 25 | return Private>(internalQuery, "_objectQuery"); 26 | } 27 | } -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) .NET Foundation and Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 20 | 21 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/Verify.EntityFramework.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net10.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Verify.EntityFramework/Converters/DbUpdateExceptionConverter.cs: -------------------------------------------------------------------------------- 1 | class DbUpdateExceptionConverter : 2 | WriteOnlyJsonConverter 3 | { 4 | public override void Write(VerifyJsonWriter writer, DbUpdateException exception) 5 | { 6 | writer.WriteStartObject(); 7 | 8 | writer.WriteMember(exception, exception.Message, "Message"); 9 | writer.WriteMember(exception, exception.GetType(), "Type"); 10 | writer.WriteMember(exception, exception.InnerException, "InnerException"); 11 | 12 | var entries = exception 13 | .Entries 14 | .Select(entry => new 15 | { 16 | EntryProperties = entry.Properties 17 | .ToDictionary( 18 | _ => _.Metadata.Name, 19 | _ => new 20 | { 21 | _.OriginalValue, 22 | _.CurrentValue, 23 | _.IsTemporary, 24 | _.IsModified 25 | }), 26 | entry.State 27 | }); 28 | 29 | writer.WriteMember(exception, entries, "Entries"); 30 | writer.WriteMember(exception, exception.StackTrace, "StackTrace"); 31 | 32 | writer.WriteEndObject(); 33 | } 34 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/DbUpdateExceptionTests.cs: -------------------------------------------------------------------------------- 1 | [TestFixture] 2 | public class DbUpdateExceptionTests 3 | { 4 | [Test] 5 | public async Task Run() 6 | { 7 | var instance = new SqlInstance(builder => new(builder.Options)); 8 | var id = Guid.NewGuid(); 9 | var entity = new TestEntity 10 | { 11 | Id = id, 12 | Property = "Item1" 13 | }; 14 | 15 | await using var database = await instance.Build([entity]); 16 | 17 | var duplicate = new TestEntity 18 | { 19 | Id = id, 20 | Property = "Item1" 21 | }; 22 | var testDbContext = database.NewDbContext(); 23 | testDbContext.Add(duplicate); 24 | await ThrowsTask(() => testDbContext.SaveChangesAsync()) 25 | .IgnoreStackTrace() 26 | .ScrubInlineGuids(); 27 | } 28 | 29 | public class TestEntity 30 | { 31 | public Guid Id { get; set; } 32 | public string? Property { get; set; } 33 | } 34 | 35 | public class TestDbContext(DbContextOptions options) : 36 | DbContext(options) 37 | { 38 | public DbSet TestEntities { get; set; } = null!; 39 | 40 | protected override void OnModelCreating(ModelBuilder model) => model.Entity(); 41 | } 42 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework/SqlFormatter.cs: -------------------------------------------------------------------------------- 1 | static class SqlFormatter 2 | { 3 | public static StringBuilder Format(string input) 4 | { 5 | var parser = new TSql170Parser(false); 6 | using var reader = new StringReader(input); 7 | var fragment = parser.Parse(reader, out var errors); 8 | 9 | if (errors.Count > 0) 10 | { 11 | throw new( 12 | $""" 13 | Failed to parse sql. 14 | 15 | Errors: 16 | {string.Join(Environment.NewLine, errors.Select(_ => _.Message))} 17 | 18 | Sql input: 19 | {input} 20 | """); 21 | } 22 | 23 | var visitor = new RemoveSquareBracketVisitor(); 24 | fragment.Accept(visitor); 25 | 26 | var generator = new Sql170ScriptGenerator( 27 | new() 28 | { 29 | SqlVersion = SqlVersion.Sql170, 30 | KeywordCasing = KeywordCasing.Lowercase, 31 | IndentationSize = 2, 32 | AlignClauseBodies = true 33 | }); 34 | 35 | var builder = new StringBuilder(); 36 | using (var writer = new StringWriter(builder, CultureInfo.InvariantCulture)) 37 | { 38 | generator.GenerateScript(fragment, writer); 39 | } 40 | 41 | builder.TrimEnd(); 42 | if (builder[^1] == ';') 43 | { 44 | builder.Length--; 45 | } 46 | 47 | return builder; 48 | } 49 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic/Extensions.cs: -------------------------------------------------------------------------------- 1 | static class Extensions 2 | { 3 | public static IEnumerable ChangedProperties(this DbEntityEntry entry) 4 | { 5 | foreach (var name in entry.CurrentValues.PropertyNames) 6 | { 7 | var current = entry.CurrentValues[name]; 8 | var original = entry.OriginalValues[name]; 9 | if (ReferenceEquals(original, current)) 10 | { 11 | continue; 12 | } 13 | 14 | if (original is not null) 15 | { 16 | if (original.Equals(current)) 17 | { 18 | continue; 19 | } 20 | } 21 | 22 | yield return new(name, original, current); 23 | } 24 | } 25 | 26 | public static string Name(this DbEntityEntry entry) => 27 | entry.Entity 28 | .GetType() 29 | .Name; 30 | 31 | public static IEnumerable<(string name, object value)> FindPrimaryKeyValues(this DbContext context, DbEntityEntry entry) 32 | { 33 | var objectContext = ((IObjectContextAdapter) context).ObjectContext; 34 | 35 | var setBase = objectContext.ObjectStateManager 36 | .GetObjectStateEntry(entry.Entity) 37 | .EntitySet; 38 | 39 | foreach (var property in setBase.ElementType.KeyMembers) 40 | { 41 | var name = property.Name; 42 | var value = entry.Property(name) 43 | .CurrentValue; 44 | yield return (name, value); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: How to raise feature requests 4 | --- 5 | 6 | 7 | Note: New issues raised, where it is clear the submitter has not read the issue template, are likely to be closed with "please read the issue template". Please don't take offense at this. It is simply a time management decision. If someone raises an issue, and can't be bothered to spend the time to read the issue template, then the project maintainers should not be expected to spend the time to read the submitted issue. Often too much time is spent going back and forth in issue comments asking for information that is outlined in the issue template. 8 | 9 | If you are certain the feature will be accepted, it is better to raise a [Pull Request (PR)](https://help.github.com/articles/about-pull-requests/). 10 | 11 | If you are uncertain if the feature will be accepted, outline the proposal below to confirm it is viable, prior to raising a PR that implements the feature. 12 | 13 | Note that even if the feature is a good idea and viable, it may not be accepted since the ongoing effort in maintaining the feature may outweigh the benefit it delivers. 14 | 15 | 16 | #### Is the feature request related to a problem 17 | 18 | A clear and concise description of what the problem is. 19 | 20 | 21 | #### Describe the solution 22 | 23 | A clear and concise proposal of how you intend to implement the feature. 24 | 25 | 26 | #### Describe alternatives considered 27 | 28 | A clear and concise description of any alternative solutions or features you've considered. 29 | 30 | 31 | #### Additional context 32 | 33 | Add any other context about the feature request here. 34 | -------------------------------------------------------------------------------- /src/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | true 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug fix 3 | about: Create a bug fix to help us improve 4 | --- 5 | 6 | Note: New issues raised, where it is clear the submitter has not read the issue template, are likely to be closed with "please read the issue template". Please don't take offense at this. It is simply a time management decision. If someone raises an issue, and can't be bothered to spend the time to read the issue template, then the project maintainers should not be expected to spend the time to read the submitted issue. Often too much time is spent going back and forth in issue comments asking for information that is outlined in the issue template. 7 | 8 | 9 | #### Preamble 10 | 11 | General questions may be better placed [StackOveflow](https://stackoverflow.com/). 12 | 13 | Where relevant, ensure you are using the current stable versions on your development stack. For example: 14 | 15 | * Visual Studio 16 | * [.NET SDK or .NET Core SDK](https://www.microsoft.com/net/download) 17 | * Any related NuGet packages 18 | 19 | Any code or stack traces must be properly formatted with [GitHub markdown](https://guides.github.com/features/mastering-markdown/). 20 | 21 | 22 | #### Describe the bug 23 | 24 | A clear and concise description of what the bug is. Include any relevant version information. 25 | 26 | A clear and concise description of what you expected to happen. 27 | 28 | Add any other context about the problem here. 29 | 30 | 31 | #### Minimal Repro 32 | 33 | Ensure you have replicated the bug in a minimal solution with the fewest moving parts. Often this will help point to the true cause of the problem. Upload this repro as part of the issue, preferably a public GitHub repository or a downloadable zip. The repro will allow the maintainers of this project to smoke test the any fix. 34 | 35 | #### Submit a PR that fixes the bug 36 | 37 | Submit a [Pull Request (PR)](https://help.github.com/articles/about-pull-requests/) that fixes the bug. Include in this PR a test that verifies the fix. If you were not able to fix the bug, a PR that illustrates your partial progress will suffice. 38 | -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic.Tests/Snippets/DbContextBuilder.cs: -------------------------------------------------------------------------------- 1 | // LocalDb is used to make the sample simpler. 2 | // Replace with a real DbContext 3 | 4 | public static class DbContextBuilder 5 | { 6 | static DbContextBuilder() => 7 | sqlInstance = new( 8 | buildTemplate: CreateDb, 9 | constructInstance: connection => new(connection), 10 | storage: Storage.FromSuffix("Classic")); 11 | 12 | static SqlInstance sqlInstance; 13 | 14 | static async Task CreateDb(SampleDbContext data) 15 | { 16 | await data.CreateOnExistingDb(); 17 | 18 | var company1 = new Company 19 | { 20 | Id = 1, 21 | Content = "Company1" 22 | }; 23 | var employee1 = new Employee 24 | { 25 | Id = 2, 26 | CompanyId = company1.Id, 27 | Content = "Employee1", 28 | Age = 25 29 | }; 30 | var employee2 = new Employee 31 | { 32 | Id = 3, 33 | CompanyId = company1.Id, 34 | Content = "Employee2", 35 | Age = 31 36 | }; 37 | var company2 = new Company 38 | { 39 | Id = 4, 40 | Content = "Company2" 41 | }; 42 | var employee4 = new Employee 43 | { 44 | Id = 5, 45 | CompanyId = company2.Id, 46 | Content = "Employee4", 47 | Age = 34 48 | }; 49 | var company3 = new Company 50 | { 51 | Id = 6, 52 | Content = "Company3" 53 | }; 54 | var company4 = new Company 55 | { 56 | Id = 7, 57 | Content = "Company4" 58 | }; 59 | data.Companies.Add(company1); 60 | data.Companies.Add(company2); 61 | data.Companies.Add(company3); 62 | data.Companies.Add(company4); 63 | data.Employees.Add(employee1); 64 | data.Employees.Add(employee2); 65 | data.Employees.Add(employee4); 66 | await data.SaveChangesAsync(); 67 | } 68 | 69 | public static Task> GetDatabase(string suffix) 70 | => sqlInstance.Build(suffix); 71 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework/Converters/QueryableConverter.cs: -------------------------------------------------------------------------------- 1 | class QueryableConverter(bool formatSql) : 2 | WriteOnlyJsonConverter 3 | { 4 | static MethodInfo executeQueryableDefinition; 5 | 6 | static QueryableConverter() => 7 | executeQueryableDefinition = typeof(QueryableConverter).GetMethod("ExecuteQueryable", BindingFlags.NonPublic | BindingFlags.Static)!; 8 | 9 | public override void Write(VerifyJsonWriter writer, object data) 10 | { 11 | var queryable = (IQueryable) data; 12 | var sql = queryable.ToQueryString(); 13 | if (formatSql) 14 | { 15 | //TODO: add span support to Serialize 16 | sql = SqlFormatter.Format(sql).ToString(); 17 | } 18 | if (!TryExecuteQueryable(queryable, out var result)) 19 | { 20 | writer.Serialize(sql); 21 | return; 22 | } 23 | 24 | writer.WriteStartObject(); 25 | writer.WriteMember(data, sql, "Sql"); 26 | writer.WriteMember(data, result, "Result"); 27 | writer.WriteEndObject(); 28 | } 29 | 30 | public static bool TryExecuteQueryable(IQueryable queryable, [NotNullWhen(true)] out IList? result) 31 | { 32 | var entityType = queryable 33 | .GetType() 34 | .GenericTypeArguments.First(); 35 | 36 | var executeQueryable = executeQueryableDefinition.MakeGenericMethod(entityType); 37 | try 38 | { 39 | result = (IList) executeQueryable.Invoke(null, [queryable])!; 40 | return true; 41 | } 42 | catch 43 | { 44 | result = null; 45 | return false; 46 | } 47 | } 48 | 49 | static List ExecuteQueryable(IQueryable queryable) => 50 | queryable.ToList(); 51 | 52 | public override bool CanConvert(Type type) 53 | => IsQueryable(type); 54 | 55 | public static bool IsQueryable(object target) 56 | { 57 | var type = target.GetType(); 58 | return IsQueryable(type); 59 | } 60 | 61 | static bool IsQueryable(Type type) 62 | { 63 | if (!type.IsGenericType) 64 | { 65 | return false; 66 | } 67 | 68 | var genericType = type.GetGenericTypeDefinition(); 69 | return genericType == typeof(EntityQueryable<>) || 70 | genericType == typeof(IQueryable<>); 71 | } 72 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework/LogCommandInterceptor.cs: -------------------------------------------------------------------------------- 1 | class LogCommandInterceptor(string? identifier) : 2 | DbCommandInterceptor 3 | { 4 | public override void CommandFailed(DbCommand command, CommandErrorEventData data) 5 | => Add("CommandFailed", command, data, data.Exception); 6 | 7 | public override Task CommandFailedAsync(DbCommand command, CommandErrorEventData data, Cancel cancel = default) 8 | { 9 | Add("CommandFailedAsync", command, data, data.Exception); 10 | return Task.CompletedTask; 11 | } 12 | 13 | public override DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData data, DbDataReader result) 14 | { 15 | Add("ReaderExecuted", command, data); 16 | return result; 17 | } 18 | 19 | public override object? ScalarExecuted(DbCommand command, CommandExecutedEventData data, object? result) 20 | { 21 | Add("ScalarExecuted", command, data); 22 | return result; 23 | } 24 | 25 | public override int NonQueryExecuted(DbCommand command, CommandExecutedEventData data, int result) 26 | { 27 | Add("NonQueryExecuted", command, data); 28 | return result; 29 | } 30 | 31 | public override ValueTask ReaderExecutedAsync(DbCommand command, CommandExecutedEventData data, DbDataReader result, Cancel cancel = default) 32 | { 33 | Add("ReaderExecutedAsync", command, data); 34 | return new(result); 35 | } 36 | 37 | public override ValueTask ScalarExecutedAsync(DbCommand command, CommandExecutedEventData data, object? result, Cancel cancel = default) 38 | { 39 | Add("ScalarExecutedAsync", command, data); 40 | return new(result); 41 | } 42 | 43 | public override ValueTask NonQueryExecutedAsync(DbCommand command, CommandExecutedEventData data, int result, Cancel cancel = default) 44 | { 45 | Add("NonQueryExecutedAsync", command, data); 46 | return new(result); 47 | } 48 | 49 | void Add(string type, DbCommand command, CommandEndEventData data, Exception? exception = null) 50 | { 51 | var context = data.Context; 52 | if (context != null && 53 | context.IsRecordingDisabled()) 54 | { 55 | return; 56 | } 57 | 58 | var entry = new LogEntry(type, command, data, exception); 59 | if (identifier is null) 60 | { 61 | Recording.TryAdd("ef", entry); 62 | } 63 | else 64 | { 65 | Recording.TryAdd(identifier, "ef", entry); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/Snippets/DbContextBuilder.cs: -------------------------------------------------------------------------------- 1 | // LocalDb is used to make the sample simpler. 2 | // Replace with a real DbContext 3 | 4 | public static class DbContextBuilder 5 | { 6 | static DbContextBuilder() 7 | { 8 | sqlInstance = new( 9 | buildTemplate: CreateDb, 10 | constructInstance: builder => 11 | { 12 | builder.EnableRecording(); 13 | return new(builder.Options); 14 | }); 15 | orderRequiredSqlInstance = new( 16 | buildTemplate: CreateDb, 17 | storage: Storage.FromSuffix("ThrowForMissingOrderBy"), 18 | constructInstance: builder => 19 | { 20 | builder.EnableRecording(); 21 | builder.ThrowForMissingOrderBy(); 22 | return new(builder.Options); 23 | }); 24 | } 25 | 26 | static SqlInstance sqlInstance; 27 | static SqlInstance orderRequiredSqlInstance; 28 | 29 | static async Task CreateDb(SampleDbContext data) 30 | { 31 | await data.Database.EnsureCreatedAsync(); 32 | 33 | var company1 = new Company 34 | { 35 | Id = 1, 36 | Name = "Company1" 37 | }; 38 | var employee1 = new Employee 39 | { 40 | Id = 2, 41 | CompanyId = company1.Id, 42 | Name = "Employee1", 43 | Age = 25 44 | }; 45 | var employee2 = new Employee 46 | { 47 | Id = 3, 48 | CompanyId = company1.Id, 49 | Name = "Employee2", 50 | Age = 31 51 | }; 52 | var company2 = new Company 53 | { 54 | Id = 4, 55 | Name = "Company2" 56 | }; 57 | var employee4 = new Employee 58 | { 59 | Id = 5, 60 | CompanyId = company2.Id, 61 | Name = "Employee4", 62 | Age = 34 63 | }; 64 | var company3 = new Company 65 | { 66 | Id = 6, 67 | Name = "Company3" 68 | }; 69 | var company4 = new Company 70 | { 71 | Id = 7, 72 | Name = "Company4" 73 | }; 74 | data.AddRange(company1, employee1, employee2, company2, company3, company4, employee4); 75 | await data.SaveChangesAsync(); 76 | } 77 | 78 | public static Task> GetDatabase([CallerMemberName] string suffix = "") 79 | => sqlInstance.Build(suffix); 80 | 81 | public static Task> GetOrderRequiredDatabase([CallerMemberName] string suffix = "") 82 | => orderRequiredSqlInstance.Build(suffix); 83 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework/Extensions.cs: -------------------------------------------------------------------------------- 1 | static class Extensions 2 | { 3 | static MethodInfo setMethod = typeof(DbContext) 4 | .GetMethod("Set", [])!; 5 | 6 | static MethodInfo asNoTracking = typeof(EntityFrameworkQueryableExtensions).GetMethod("AsNoTracking")!; 7 | 8 | public static IQueryable AsNoTracking(this IQueryable set, Type clrType) 9 | { 10 | var genericNoTracking = asNoTracking.MakeGenericMethod(clrType); 11 | return (IQueryable) genericNoTracking.Invoke(null, [set])!; 12 | } 13 | 14 | public static string? NameOrAlias(this TableExpressionBase tableExpressionBase) 15 | { 16 | if (tableExpressionBase.Alias != null) 17 | { 18 | return tableExpressionBase.Alias; 19 | } 20 | 21 | if (tableExpressionBase is TableExpression tableExpression) 22 | { 23 | return tableExpression.Name; 24 | } 25 | 26 | return null; 27 | } 28 | 29 | public static IQueryable Set(this DbContext data, Type t) => 30 | (IQueryable) setMethod 31 | .MakeGenericMethod(t) 32 | .Invoke(data, null)!; 33 | 34 | public static IOrderedEnumerable EntityTypes(this DbContext data) => 35 | data 36 | .Model 37 | .GetEntityTypes() 38 | .OrderBy(_ => _.Name); 39 | 40 | public static IEnumerable ChangedProperties(this EntityEntry entry) => 41 | entry.Properties 42 | .Where(entry => 43 | { 44 | if (!entry.IsModified) 45 | { 46 | return false; 47 | } 48 | 49 | var original = entry.OriginalValue; 50 | var current = entry.CurrentValue; 51 | if (ReferenceEquals(original, current)) 52 | { 53 | return false; 54 | } 55 | 56 | if (original is null) 57 | { 58 | return true; 59 | } 60 | 61 | return !original.Equals(current); 62 | }); 63 | 64 | public static IEnumerable<(string name, object? value)> FindPrimaryKeyValues(this EntityEntry entry) 65 | { 66 | var primaryKey = entry.Metadata.FindPrimaryKey(); 67 | if (primaryKey == null) 68 | { 69 | yield break; 70 | } 71 | 72 | foreach (var property in primaryKey.Properties) 73 | { 74 | var name = property.Name; 75 | var value = entry.Property(name) 76 | .CurrentValue; 77 | yield return (name, value); 78 | } 79 | } 80 | 81 | public static Dictionary ToDictionary(this DbParameterCollection collection) 82 | { 83 | var dictionary = new Dictionary(); 84 | foreach (DbParameter parameter in collection) 85 | { 86 | var direction = parameter.Direction; 87 | if (direction is ParameterDirection.Output or ParameterDirection.ReturnValue) 88 | { 89 | continue; 90 | } 91 | 92 | var nullChar = ""; 93 | if (parameter.IsNullable) 94 | { 95 | nullChar = "?"; 96 | } 97 | 98 | var key = $"{parameter.ParameterName} ({parameter.DbType}{nullChar})"; 99 | dictionary[key] = parameter.Value!; 100 | } 101 | 102 | return dictionary; 103 | } 104 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework/Converters/TrackerConverter.cs: -------------------------------------------------------------------------------- 1 | class TrackerConverter : 2 | WriteOnlyJsonConverter 3 | { 4 | public override void Write(VerifyJsonWriter writer, ChangeTracker tracker) 5 | { 6 | writer.WriteStartObject(); 7 | 8 | var entries = tracker 9 | .Entries() 10 | .ToList(); 11 | HandleAdded(entries, writer); 12 | HandleModified(entries, writer); 13 | HandleDeleted(entries, writer); 14 | 15 | writer.WriteEndObject(); 16 | } 17 | 18 | static void HandleDeleted(List entries, VerifyJsonWriter writer) 19 | { 20 | var deleted = entries 21 | .Where(_ => _.State == EntityState.Deleted) 22 | .ToList(); 23 | if (deleted.Count == 0) 24 | { 25 | return; 26 | } 27 | 28 | writer.WritePropertyName("Deleted"); 29 | writer.WriteStartObject(); 30 | foreach (var entry in deleted) 31 | { 32 | writer.WritePropertyName(entry.Metadata.DisplayName()); 33 | writer.WriteStartObject(); 34 | WriteId(writer, entry); 35 | writer.WriteEndObject(); 36 | } 37 | 38 | writer.WriteEndObject(); 39 | } 40 | 41 | static void HandleAdded(List entries, VerifyJsonWriter writer) 42 | { 43 | var added = entries 44 | .Where(_ => _.State == EntityState.Added) 45 | .ToList(); 46 | if (added.Count == 0) 47 | { 48 | return; 49 | } 50 | 51 | writer.WritePropertyName("Added"); 52 | writer.WriteStartObject(); 53 | foreach (var entry in added) 54 | { 55 | writer.WritePropertyName(entry.Metadata.DisplayName()); 56 | writer.WriteStartObject(); 57 | 58 | foreach (var property in entry.Properties) 59 | { 60 | writer.WriteMember(entry, property.OriginalValue, property.Metadata.Name); 61 | } 62 | 63 | writer.WriteEndObject(); 64 | } 65 | 66 | writer.WriteEndObject(); 67 | } 68 | 69 | static void HandleModified(List entries, VerifyJsonWriter writer) 70 | { 71 | var modified = entries 72 | .Where(_ => _.State == EntityState.Modified) 73 | .ToList(); 74 | if (modified.Count == 0) 75 | { 76 | return; 77 | } 78 | 79 | writer.WritePropertyName("Modified"); 80 | writer.WriteStartObject(); 81 | foreach (var entry in modified) 82 | { 83 | HandleModified(writer, entry); 84 | } 85 | 86 | writer.WriteEndObject(); 87 | } 88 | 89 | static void HandleModified(VerifyJsonWriter writer, EntityEntry entry) 90 | { 91 | writer.WritePropertyName(entry.Metadata.DisplayName()); 92 | writer.WriteStartObject(); 93 | 94 | WriteId(writer, entry); 95 | foreach (var property in entry.ChangedProperties()) 96 | { 97 | writer.WritePropertyName(property.Metadata.Name); 98 | writer.Serialize( 99 | new 100 | { 101 | Original = property.OriginalValue, 102 | Current = property.CurrentValue 103 | }); 104 | } 105 | 106 | writer.WriteEndObject(); 107 | } 108 | 109 | static void WriteId(VerifyJsonWriter writer, EntityEntry entry) 110 | { 111 | var ids = entry 112 | .FindPrimaryKeyValues() 113 | .ToList(); 114 | if (ids.Count == 0) 115 | { 116 | return; 117 | } 118 | 119 | if (ids.Count == 1) 120 | { 121 | var (name, value) = ids.Single(); 122 | writer.WriteMember(entry, value, name); 123 | } 124 | else 125 | { 126 | writer.WriteMember(entry, ids, "Ids"); 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework/MissingOrder/MissingOrderByVisitor.cs: -------------------------------------------------------------------------------- 1 | sealed class MissingOrderByVisitor : 2 | ExpressionVisitor 3 | { 4 | List orderedTables = []; 5 | 6 | public override Expression? Visit(Expression? expression) 7 | { 8 | if (expression is null) 9 | { 10 | return null; 11 | } 12 | 13 | switch (expression) 14 | { 15 | case ShapedQueryExpression shaped: 16 | if(shaped.ResultCardinality != ResultCardinality.Enumerable) 17 | { 18 | return null; 19 | } 20 | Visit(shaped.QueryExpression); 21 | return shaped; 22 | 23 | case RelationalSplitCollectionShaperExpression split: 24 | foreach (var table in split.SelectExpression.Tables) 25 | { 26 | Visit(table); 27 | } 28 | 29 | Visit(split.InnerShaper); 30 | 31 | return split; 32 | 33 | case TableExpression tableExpression: 34 | { 35 | foreach (var table in orderedTables) 36 | { 37 | if (table == tableExpression) 38 | { 39 | return base.Visit(expression); 40 | } 41 | 42 | if (table is PredicateJoinExpressionBase join) 43 | { 44 | if (join.Table == tableExpression) 45 | { 46 | return base.Visit(expression); 47 | } 48 | } 49 | } 50 | 51 | throw new( 52 | $""" 53 | TableExpression must have at least one ordering. 54 | Expression: 55 | {ExpressionPrinter.Print(tableExpression)} 56 | """); 57 | } 58 | case SelectExpression select: 59 | { 60 | var orderings = select.Orderings; 61 | if (orderings.Count == 0) 62 | { 63 | throw new( 64 | $""" 65 | SelectExpression must have at least one ordering. 66 | Expression: 67 | {PrintShortSql(select)} 68 | """); 69 | } 70 | 71 | foreach (var ordering in orderings) 72 | { 73 | if (ordering.Expression is not ColumnExpression column) 74 | { 75 | continue; 76 | } 77 | 78 | if (!TryFindTable(select.Tables, column.TableAlias, out var table)) 79 | { 80 | continue; 81 | } 82 | 83 | orderedTables.Add(table); 84 | } 85 | 86 | return base.Visit(expression); 87 | } 88 | 89 | default: 90 | return base.Visit(expression); 91 | } 92 | } 93 | 94 | static bool TryFindTable(IReadOnlyList tables, string name, [NotNullWhen(true)] out TableExpressionBase? result) 95 | { 96 | foreach (var table in tables) 97 | { 98 | if (table.NameOrAlias() == name) 99 | { 100 | result = table; 101 | return true; 102 | } 103 | 104 | if (table is LeftJoinExpression join && 105 | join.Table.NameOrAlias() == name) 106 | { 107 | result = join; 108 | return true; 109 | } 110 | } 111 | 112 | result = null; 113 | return false; 114 | } 115 | 116 | [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "PrintShortSql")] 117 | static extern string PrintShortSql(SelectExpression expression); 118 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic.Tests/ClassicTests.cs: -------------------------------------------------------------------------------- 1 | [TestFixture] 2 | public class ClassicTests 3 | { 4 | static SqlInstance sqlInstance; 5 | 6 | #region AddedClassic 7 | 8 | [Test] 9 | public async Task Added() 10 | { 11 | using var database = await sqlInstance.Build(); 12 | var data = database.Context; 13 | data.Companies.Add(new() 14 | { 15 | Content = "before" 16 | }); 17 | await Verify(data.ChangeTracker); 18 | } 19 | 20 | #endregion 21 | 22 | #region DeletedClassic 23 | 24 | [Test] 25 | public async Task Deleted() 26 | { 27 | using var database = await sqlInstance.Build(); 28 | var data = database.Context; 29 | data.Companies.Add(new() 30 | { 31 | Content = "before" 32 | }); 33 | await data.SaveChangesAsync(); 34 | 35 | var company = data.Companies.Single(); 36 | data.Companies.Remove(company); 37 | await Verify(data.ChangeTracker); 38 | } 39 | 40 | #endregion 41 | 42 | #region ModifiedClassic 43 | 44 | [Test] 45 | public async Task Modified() 46 | { 47 | using var database = await sqlInstance.Build(); 48 | var data = database.Context; 49 | var company = new Company 50 | { 51 | Content = "before" 52 | }; 53 | data.Companies.Add(company); 54 | await data.SaveChangesAsync(); 55 | 56 | data.Companies.Single() 57 | .Content = "after"; 58 | await Verify(data.ChangeTracker); 59 | } 60 | 61 | #endregion 62 | 63 | [Test] 64 | public async Task WithNavigationProp() 65 | { 66 | using var database = await sqlInstance.Build(); 67 | var data = database.Context; 68 | var company = new Company 69 | { 70 | Content = "companyBefore" 71 | }; 72 | data.Companies.Add(company); 73 | var employee = new Employee 74 | { 75 | Content = "employeeBefore", 76 | Company = company 77 | }; 78 | data.Employees.Add(employee); 79 | await data.SaveChangesAsync(); 80 | 81 | data.Companies.Single() 82 | .Content = "companyAfter"; 83 | data.Employees.Single() 84 | .Content = "employeeAfter"; 85 | await Verify(data.ChangeTracker); 86 | } 87 | 88 | [Test] 89 | [Explicit] 90 | public async Task SomePropsModified() 91 | { 92 | using var database = await sqlInstance.Build(); 93 | var data = database.Context; 94 | var company = new Company 95 | { 96 | Content = "before" 97 | }; 98 | data.Companies.Add(company); 99 | await data.SaveChangesAsync(); 100 | var entity = data.Companies.Attach(new() 101 | { 102 | Id = company.Id 103 | }); 104 | entity.Content = "after"; 105 | data 106 | .Entry(entity) 107 | .Property(_ => _.Content) 108 | .IsModified = true; 109 | data.Configuration.ValidateOnSaveEnabled = false; 110 | await data.SaveChangesAsync(); 111 | await Verify(data); 112 | } 113 | 114 | [Test] 115 | public async Task UpdateEntity() 116 | { 117 | using var database = await sqlInstance.Build(); 118 | var data = database.Context; 119 | 120 | data.Companies.Add(new() 121 | { 122 | Content = "before" 123 | }); 124 | await data.SaveChangesAsync(); 125 | 126 | var company = data.Companies.Single(); 127 | company.Content = "after"; 128 | await Verify(data.ChangeTracker); 129 | } 130 | 131 | #region QueryableClassic 132 | 133 | [Test] 134 | public async Task Queryable() 135 | { 136 | var database = await DbContextBuilder.GetDatabase("Queryable"); 137 | var data = database.Context; 138 | var queryable = data.Companies.Where(_ => _.Content == "value"); 139 | await Verify(queryable); 140 | } 141 | 142 | #endregion 143 | 144 | static ClassicTests() => 145 | sqlInstance = new( 146 | constructInstance: connection => new(connection), 147 | storage: Storage.FromSuffix("Tests")); 148 | } -------------------------------------------------------------------------------- /src/Verify.EntityFrameworkClassic/Converters/TrackerConverter.cs: -------------------------------------------------------------------------------- 1 | class TrackerConverter : 2 | WriteOnlyJsonConverter 3 | { 4 | static Func func; 5 | 6 | static TrackerConverter() 7 | { 8 | const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; 9 | var internalContextField = typeof(DbChangeTracker).GetField("_internalContext", flags)!; 10 | var ownerField = internalContextField.FieldType.GetField("_owner", flags)!; 11 | 12 | func = tracker => 13 | { 14 | var value = internalContextField.GetValue(tracker); 15 | return (DbContext) ownerField.GetValue(value)!; 16 | }; 17 | } 18 | 19 | public override void Write(VerifyJsonWriter writer, DbChangeTracker tracker) 20 | { 21 | writer.WriteStartObject(); 22 | var entries = tracker 23 | .Entries() 24 | .ToList(); 25 | HandleAdded(entries, writer); 26 | var data = func(tracker); 27 | HandleModified(entries, writer, data); 28 | HandleDeleted(entries, writer, data); 29 | 30 | writer.WriteEndObject(); 31 | } 32 | 33 | static void HandleDeleted(List entries, VerifyJsonWriter writer, DbContext data) 34 | { 35 | var deleted = entries 36 | .Where(_ => _.State == EntityState.Deleted) 37 | .ToList(); 38 | if (deleted.Count == 0) 39 | { 40 | return; 41 | } 42 | 43 | writer.WritePropertyName("Deleted"); 44 | writer.WriteStartObject(); 45 | foreach (var entry in deleted) 46 | { 47 | writer.WritePropertyName(entry.Name()); 48 | writer.WriteStartObject(); 49 | WriteId(writer, entry, data); 50 | writer.WriteEndObject(); 51 | } 52 | 53 | writer.WriteEndObject(); 54 | } 55 | 56 | static void HandleAdded(List entries, VerifyJsonWriter writer) 57 | { 58 | var added = entries 59 | .Where(_ => _.State == EntityState.Added) 60 | .ToList(); 61 | if (added.Count == 0) 62 | { 63 | return; 64 | } 65 | 66 | writer.WritePropertyName("Added"); 67 | writer.WriteStartObject(); 68 | foreach (var entry in added) 69 | { 70 | writer.WritePropertyName(entry.Name()); 71 | writer.WriteStartObject(); 72 | 73 | foreach (var propertyName in entry.CurrentValues.PropertyNames) 74 | { 75 | var value = entry.CurrentValues[propertyName]; 76 | writer.WriteMember(entry, value, propertyName); 77 | } 78 | 79 | writer.WriteEndObject(); 80 | } 81 | 82 | writer.WriteEndObject(); 83 | } 84 | 85 | static void HandleModified(List entries, VerifyJsonWriter writer, DbContext context) 86 | { 87 | var modified = entries 88 | .Where(_ => _.State == EntityState.Modified) 89 | .ToList(); 90 | if (modified.Count == 0) 91 | { 92 | return; 93 | } 94 | 95 | writer.WritePropertyName("Modified"); 96 | writer.WriteStartObject(); 97 | foreach (var entry in modified) 98 | { 99 | HandleModified(writer, entry, context); 100 | } 101 | 102 | writer.WriteEndObject(); 103 | } 104 | 105 | static void HandleModified(VerifyJsonWriter writer, DbEntityEntry entry, DbContext context) 106 | { 107 | writer.WritePropertyName(entry.Name()); 108 | writer.WriteStartObject(); 109 | 110 | WriteId(writer, entry, context); 111 | foreach (var property in entry.ChangedProperties()) 112 | { 113 | writer.WriteMember( 114 | entry, 115 | new 116 | { 117 | property.Original, 118 | property.Current 119 | }, 120 | property.Key); 121 | } 122 | 123 | writer.WriteEndObject(); 124 | } 125 | 126 | static void WriteId(VerifyJsonWriter writer, DbEntityEntry entry, DbContext context) 127 | { 128 | var ids = context 129 | .FindPrimaryKeyValues(entry) 130 | .ToList(); 131 | if (ids.Count == 0) 132 | { 133 | return; 134 | } 135 | 136 | if (ids.Count == 1) 137 | { 138 | var (name, value) = ids.Single(); 139 | writer.WriteMember(entry, value, name); 140 | } 141 | else 142 | { 143 | writer.WriteMember(entry, ids, "Ids"); 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /src/Verify.EntityFramework/VerifyEntityFramework.cs: -------------------------------------------------------------------------------- 1 | namespace VerifyTests; 2 | 3 | public static class VerifyEntityFramework 4 | { 5 | static List<(Type type, string name)>? modelNavigations; 6 | 7 | public static async IAsyncEnumerable AllData(this DbContext data) 8 | { 9 | foreach (var entityType in data 10 | .EntityTypes() 11 | .OrderBy(_ => _.Name) 12 | .Where(_ => !_.IsOwned())) 13 | { 14 | var clrType = entityType.ClrType; 15 | var set = data.Set(clrType); 16 | var queryable = set.AsNoTracking(clrType); 17 | 18 | IEnumerable list = await queryable.ToListAsync(); 19 | var idProperty = clrType.GetProperty("Id", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 20 | 21 | if (idProperty != null) 22 | { 23 | list = list.OrderBy(_ => idProperty.GetValue(_)); 24 | } 25 | 26 | foreach (var entity in list) 27 | { 28 | yield return entity; 29 | } 30 | } 31 | } 32 | 33 | public static void IgnoreNavigationProperties(this VerifySettings settings, DbContext context) => 34 | settings.IgnoreNavigationProperties(context.Model); 35 | 36 | public static SettingsTask IgnoreNavigationProperties(this SettingsTask settings, DbContext context) => 37 | settings.IgnoreNavigationProperties(context.Model); 38 | 39 | public static SettingsTask IgnoreNavigationProperties(this SettingsTask settings, IModel? model = null) 40 | { 41 | foreach (var (type, name) in model.GetNavigationsOrShared()) 42 | { 43 | settings.IgnoreMember(type, name); 44 | } 45 | 46 | return settings; 47 | } 48 | 49 | public static void IgnoreNavigationProperties(this VerifySettings settings, IModel? model = null) 50 | { 51 | foreach (var (type, name) in model.GetNavigationsOrShared()) 52 | { 53 | settings.IgnoreMember(type, name); 54 | } 55 | } 56 | 57 | public static void ScrubInlineEfDateTimes() => 58 | VerifierSettings.ScrubInlineDateTimes("yyyy-MM-ddTHH:mm:ss.fffffffZ"); 59 | 60 | public static SettingsTask ScrubInlineEfDateTimes(this SettingsTask settings) 61 | { 62 | settings.CurrentSettings.ScrubInlineEfDateTimes(); 63 | return settings; 64 | } 65 | 66 | public static void ScrubInlineEfDateTimes(this VerifySettings settings) => 67 | settings.ScrubInlineDateTimes("yyyy-MM-ddTHH:mm:ss.fffffffZ"); 68 | 69 | public static void IgnoreNavigationProperties(IModel? model = null) 70 | { 71 | foreach (var (type, name) in model.GetNavigationsOrShared()) 72 | { 73 | VerifierSettings.IgnoreMember(type, name); 74 | } 75 | } 76 | 77 | static IEnumerable<(Type type, string name)> GetNavigationsOrShared(this IModel? model) 78 | { 79 | if (model == null) 80 | { 81 | if (modelNavigations != null) 82 | { 83 | return modelNavigations; 84 | } 85 | 86 | throw new("The `model` parameter must be provided wither on this method or on VerifyEntityFramework.Enable()"); 87 | } 88 | 89 | return GetNavigations(model); 90 | } 91 | 92 | static IEnumerable<(Type type, string name)> GetNavigations(this IModel model) 93 | { 94 | foreach (var type in model.GetEntityTypes()) 95 | { 96 | foreach (var navigation in type.GetNavigations()) 97 | { 98 | yield return new(type.ClrType, navigation.Name); 99 | } 100 | } 101 | } 102 | 103 | public static void Initialize(DbContext context) => 104 | Initialize(context.Model); 105 | 106 | public static bool Initialized { get; private set; } 107 | 108 | public static void Initialize(IModel? model = null) 109 | { 110 | if (Initialized) 111 | { 112 | throw new("Already Initialized"); 113 | } 114 | 115 | Initialized = true; 116 | 117 | InnerVerifier.ThrowIfVerifyHasBeenRun(); 118 | if (model != null) 119 | { 120 | modelNavigations = model 121 | .GetNavigations() 122 | .ToList(); 123 | } 124 | 125 | VerifierSettings.RegisterFileConverter( 126 | QueryableToSql, 127 | (target, _) => QueryableConverter.IsQueryable(target)); 128 | var formatSql = model != null && model.IsSqlServer(); 129 | VerifierSettings.IgnoreMembersWithType(typeof(IDbContextFactory<>)); 130 | VerifierSettings.IgnoreMembersWithType(); 131 | var converters = DefaultContractResolver.Converters; 132 | converters.Add(new DbUpdateExceptionConverter()); 133 | converters.Add(new TrackerConverter()); 134 | converters.Add(new QueryableConverter(formatSql)); 135 | converters.Add(new LogEntryConverter()); 136 | } 137 | static bool IsSqlServer(this IModel model) 138 | { 139 | var dependencies = model.ModelDependencies; 140 | var mappingSourceName = dependencies?.TypeMappingSource.GetType().Name; 141 | return mappingSourceName == "SqlServerTypeMappingSource"; 142 | } 143 | static ConversionResult QueryableToSql(object arg, IReadOnlyDictionary context) 144 | { 145 | var queryable = (IQueryable) arg; 146 | 147 | var sql = queryable.ToQueryString(); 148 | QueryableConverter.TryExecuteQueryable(queryable, out var result); 149 | return new(result, [new("sql", sql)]); 150 | } 151 | 152 | public static DbContextOptionsBuilder ThrowForMissingOrderBy(this DbContextOptionsBuilder builder) 153 | where TContext : DbContext => 154 | builder.ReplaceService(); 155 | 156 | public static DbContextOptionsBuilder EnableRecording(this DbContextOptionsBuilder builder) 157 | where TContext : DbContext 158 | => builder.EnableRecording(null); 159 | 160 | public static DbContextOptionsBuilder EnableRecording(this DbContextOptionsBuilder builder, string? identifier) 161 | where TContext : DbContext => 162 | builder.AddInterceptors(new LogCommandInterceptor(identifier)); 163 | 164 | static ConcurrentBag recordingDisabledContextIds = []; 165 | 166 | public static void DisableRecording(this TContext context) 167 | where TContext : DbContext => 168 | recordingDisabledContextIds.Add(context.ContextId.InstanceId); 169 | 170 | internal static bool IsRecordingDisabled(this TContext context) 171 | where TContext : DbContext => 172 | recordingDisabledContextIds.Contains(context.ContextId.InstanceId); 173 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | insert_final_newline = true 7 | 8 | [*.cs] 9 | indent_size = 4 10 | charset = utf-8 11 | 12 | # Redundant accessor body 13 | resharper_redundant_accessor_body_highlighting = error 14 | 15 | # Replace with field keyword 16 | resharper_replace_with_field_keyword_highlighting = error 17 | 18 | # Replace with single call to Single(..) 19 | resharper_replace_with_single_call_to_single_highlighting = error 20 | 21 | # Replace with single call to SingleOrDefault(..) 22 | resharper_replace_with_single_call_to_single_or_default_highlighting = error 23 | 24 | # Replace with single call to LastOrDefault(..) 25 | resharper_replace_with_single_call_to_last_or_default_highlighting = error 26 | 27 | # Element is localizable 28 | resharper_localizable_element_highlighting = none 29 | 30 | # Replace with single call to Last(..) 31 | resharper_replace_with_single_call_to_last_highlighting = error 32 | 33 | # Replace with single call to First(..) 34 | resharper_replace_with_single_call_to_first_highlighting = error 35 | 36 | # Replace with single call to FirstOrDefault(..) 37 | resharper_replace_with_single_call_to_first_or_default_highlighting = error 38 | 39 | # Replace with single call to Any(..) 40 | resharper_replace_with_single_call_to_any_highlighting = error 41 | 42 | # Simplify negative equality expression 43 | resharper_negative_equality_expression_highlighting = error 44 | 45 | # Replace with single call to Count(..) 46 | resharper_replace_with_single_call_to_count_highlighting = error 47 | 48 | # Declare types in namespaces 49 | dotnet_diagnostic.CA1050.severity = none 50 | 51 | # Use Literals Where Appropriate 52 | dotnet_diagnostic.CA1802.severity = error 53 | 54 | # Template should be a static expression 55 | dotnet_diagnostic.CA2254.severity = error 56 | 57 | # Potentially misleading parameter name in lambda or local function 58 | resharper_all_underscore_local_parameter_name_highlighting = none 59 | 60 | # Redundant explicit collection creation in argument of 'params' parameter 61 | resharper_redundant_explicit_params_array_creation_highlighting = error 62 | 63 | # Do not initialize unnecessarily 64 | dotnet_diagnostic.CA1805.severity = error 65 | 66 | # Avoid unsealed attributes 67 | dotnet_diagnostic.CA1813.severity = error 68 | 69 | # Test for empty strings using string length 70 | dotnet_diagnostic.CA1820.severity = none 71 | 72 | # Remove empty finalizers 73 | dotnet_diagnostic.CA1821.severity = error 74 | 75 | # Mark members as static 76 | dotnet_diagnostic.CA1822.severity = error 77 | 78 | # Avoid unused private fields 79 | dotnet_diagnostic.CA1823.severity = error 80 | 81 | # Avoid zero-length array allocations 82 | dotnet_diagnostic.CA1825.severity = error 83 | 84 | # Use property instead of Linq Enumerable method 85 | dotnet_diagnostic.CA1826.severity = error 86 | 87 | # Do not use Count()/LongCount() when Any() can be used 88 | dotnet_diagnostic.CA1827.severity = error 89 | dotnet_diagnostic.CA1828.severity = error 90 | 91 | # Use Length/Count property instead of Enumerable.Count method 92 | dotnet_diagnostic.CA1829.severity = error 93 | 94 | # Prefer strongly-typed Append and Insert method overloads on StringBuilder 95 | dotnet_diagnostic.CA1830.severity = error 96 | 97 | # Use AsSpan instead of Range-based indexers for string when appropriate 98 | dotnet_diagnostic.CA1831.severity = error 99 | 100 | # Use AsSpan instead of Range-based indexers for string when appropriate 101 | dotnet_diagnostic.CA1831.severity = error 102 | dotnet_diagnostic.CA1832.severity = error 103 | dotnet_diagnostic.CA1833.severity = error 104 | 105 | # Use StringBuilder.Append(char) for single character strings 106 | dotnet_diagnostic.CA1834.severity = error 107 | 108 | # Prefer IsEmpty over Count when available 109 | dotnet_diagnostic.CA1836.severity = error 110 | 111 | # Prefer IsEmpty over Count when available 112 | dotnet_diagnostic.CA1836.severity = error 113 | 114 | # Use Environment.ProcessId instead of Process.GetCurrentProcess().Id 115 | dotnet_diagnostic.CA1837.severity = error 116 | 117 | # Use Environment.ProcessPath instead of Process.GetCurrentProcess().MainModule.FileName 118 | dotnet_diagnostic.CA1839.severity = error 119 | 120 | # Use Environment.CurrentManagedThreadId instead of Thread.CurrentThread.ManagedThreadId 121 | dotnet_diagnostic.CA1840.severity = error 122 | 123 | # Prefer Dictionary Contains methods 124 | dotnet_diagnostic.CA1841.severity = error 125 | 126 | # Do not use WhenAll with a single task 127 | dotnet_diagnostic.CA1842.severity = error 128 | 129 | # Do not use WhenAll/WaitAll with a single task 130 | dotnet_diagnostic.CA1842.severity = error 131 | dotnet_diagnostic.CA1843.severity = error 132 | 133 | # Use span-based 'string.Concat' 134 | dotnet_diagnostic.CA1845.severity = error 135 | 136 | # Prefer AsSpan over Substring 137 | dotnet_diagnostic.CA1846.severity = error 138 | 139 | # Use string.Contains(char) instead of string.Contains(string) with single characters 140 | dotnet_diagnostic.CA1847.severity = error 141 | 142 | # Prefer static HashData method over ComputeHash 143 | dotnet_diagnostic.CA1850.severity = error 144 | 145 | # Possible multiple enumerations of IEnumerable collection 146 | dotnet_diagnostic.CA1851.severity = error 147 | 148 | # Unnecessary call to Dictionary.ContainsKey(key) 149 | dotnet_diagnostic.CA1853.severity = error 150 | 151 | # Prefer the IDictionary.TryGetValue(TKey, out TValue) method 152 | dotnet_diagnostic.CA1854.severity = error 153 | 154 | # Use Span.Clear() instead of Span.Fill() 155 | dotnet_diagnostic.CA1855.severity = error 156 | 157 | # Incorrect usage of ConstantExpected attribute 158 | dotnet_diagnostic.CA1856.severity = error 159 | 160 | # The parameter expects a constant for optimal performance 161 | dotnet_diagnostic.CA1857.severity = error 162 | 163 | # Use StartsWith instead of IndexOf 164 | dotnet_diagnostic.CA1858.severity = error 165 | 166 | # Avoid using Enumerable.Any() extension method 167 | dotnet_diagnostic.CA1860.severity = error 168 | 169 | # Avoid constant arrays as arguments 170 | dotnet_diagnostic.CA1861.severity = error 171 | 172 | # Use the StringComparison method overloads to perform case-insensitive string comparisons 173 | dotnet_diagnostic.CA1862.severity = error 174 | 175 | # Prefer the IDictionary.TryAdd(TKey, TValue) method 176 | dotnet_diagnostic.CA1864.severity = error 177 | 178 | # Use string.Method(char) instead of string.Method(string) for string with single char 179 | dotnet_diagnostic.CA1865.severity = error 180 | dotnet_diagnostic.CA1866.severity = error 181 | dotnet_diagnostic.CA1867.severity = error 182 | 183 | # Unnecessary call to 'Contains' for sets 184 | dotnet_diagnostic.CA1868.severity = error 185 | 186 | # Cache and reuse 'JsonSerializerOptions' instances 187 | dotnet_diagnostic.CA1869.severity = error 188 | 189 | # Use a cached 'SearchValues' instance 190 | dotnet_diagnostic.CA1870.severity = error 191 | 192 | # Microsoft .NET properties 193 | trim_trailing_whitespace = true 194 | csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:suggestion 195 | resharper_namespace_body = file_scoped 196 | dotnet_naming_rule.private_constants_rule.severity = warning 197 | dotnet_naming_rule.private_constants_rule.style = lower_camel_case_style 198 | dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols 199 | dotnet_naming_rule.private_instance_fields_rule.severity = warning 200 | dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style 201 | dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols 202 | dotnet_naming_rule.private_static_fields_rule.severity = warning 203 | dotnet_naming_rule.private_static_fields_rule.style = lower_camel_case_style 204 | dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols 205 | dotnet_naming_rule.private_static_readonly_rule.severity = warning 206 | dotnet_naming_rule.private_static_readonly_rule.style = lower_camel_case_style 207 | dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols 208 | dotnet_naming_style.lower_camel_case_style.capitalization = camel_case 209 | dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case 210 | dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private 211 | dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field 212 | dotnet_naming_symbols.private_constants_symbols.required_modifiers = const 213 | dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private 214 | dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field 215 | dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private 216 | dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field 217 | dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static 218 | dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private 219 | dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field 220 | dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly 221 | dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none 222 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none 223 | dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none 224 | 225 | # ReSharper properties 226 | resharper_object_creation_when_type_not_evident = target_typed 227 | 228 | # ReSharper inspection severities 229 | resharper_arrange_object_creation_when_type_evident_highlighting = error 230 | resharper_arrange_object_creation_when_type_not_evident_highlighting = error 231 | resharper_arrange_redundant_parentheses_highlighting = error 232 | resharper_arrange_static_member_qualifier_highlighting = error 233 | resharper_arrange_this_qualifier_highlighting = error 234 | resharper_arrange_type_member_modifiers_highlighting = none 235 | resharper_built_in_type_reference_style_for_member_access_highlighting = hint 236 | resharper_built_in_type_reference_style_highlighting = hint 237 | resharper_check_namespace_highlighting = none 238 | resharper_convert_to_using_declaration_highlighting = error 239 | resharper_field_can_be_made_read_only_local_highlighting = none 240 | resharper_merge_into_logical_pattern_highlighting = warning 241 | resharper_merge_into_pattern_highlighting = error 242 | resharper_method_has_async_overload_highlighting = warning 243 | # because stop rider giving errors before source generators have run 244 | resharper_partial_type_with_single_part_highlighting = warning 245 | resharper_redundant_base_qualifier_highlighting = warning 246 | resharper_redundant_cast_highlighting = error 247 | resharper_redundant_empty_object_creation_argument_list_highlighting = error 248 | resharper_redundant_empty_object_or_collection_initializer_highlighting = error 249 | resharper_redundant_name_qualifier_highlighting = error 250 | resharper_redundant_suppress_nullable_warning_expression_highlighting = error 251 | resharper_redundant_using_directive_highlighting = error 252 | resharper_redundant_verbatim_string_prefix_highlighting = error 253 | resharper_redundant_lambda_signature_parentheses_highlighting = error 254 | resharper_replace_substring_with_range_indexer_highlighting = warning 255 | resharper_suggest_var_or_type_built_in_types_highlighting = error 256 | resharper_suggest_var_or_type_elsewhere_highlighting = error 257 | resharper_suggest_var_or_type_simple_types_highlighting = error 258 | resharper_unnecessary_whitespace_highlighting = error 259 | resharper_use_await_using_highlighting = warning 260 | resharper_use_deconstruction_highlighting = warning 261 | 262 | # Sort using and Import directives with System.* appearing first 263 | dotnet_sort_system_directives_first = true 264 | 265 | # Avoid "this." and "Me." if not necessary 266 | dotnet_style_qualification_for_field = false:error 267 | dotnet_style_qualification_for_property = false:error 268 | dotnet_style_qualification_for_method = false:error 269 | dotnet_style_qualification_for_event = false:error 270 | 271 | # Use language keywords instead of framework type names for type references 272 | dotnet_style_predefined_type_for_locals_parameters_members = true:error 273 | dotnet_style_predefined_type_for_member_access = true:error 274 | 275 | # Suggest more modern language features when available 276 | dotnet_style_object_initializer = true:error 277 | dotnet_style_collection_initializer = true:error 278 | dotnet_style_coalesce_expression = false:error 279 | dotnet_style_null_propagation = true:error 280 | dotnet_style_explicit_tuple_names = true:error 281 | 282 | # Use collection expression syntax 283 | resharper_use_collection_expression_highlighting = error 284 | 285 | # Prefer "var" everywhere 286 | csharp_style_var_for_built_in_types = true:error 287 | csharp_style_var_when_type_is_apparent = true:error 288 | csharp_style_var_elsewhere = true:error 289 | 290 | # Prefer method-like constructs to have a block body 291 | csharp_style_expression_bodied_methods = true:error 292 | csharp_style_expression_bodied_local_functions = true:error 293 | csharp_style_expression_bodied_constructors = true:error 294 | csharp_style_expression_bodied_operators = true:error 295 | resharper_place_expr_method_on_single_line = false 296 | 297 | # Prefer property-like constructs to have an expression-body 298 | csharp_style_expression_bodied_properties = true:error 299 | csharp_style_expression_bodied_indexers = true:error 300 | csharp_style_expression_bodied_accessors = true:error 301 | 302 | # Suggest more modern language features when available 303 | csharp_style_pattern_matching_over_is_with_cast_check = true:error 304 | csharp_style_pattern_matching_over_as_with_null_check = true:error 305 | csharp_style_inlined_variable_declaration = true:suggestion 306 | csharp_style_throw_expression = true:suggestion 307 | csharp_style_conditional_delegate_call = true:suggestion 308 | 309 | # Newline settings 310 | #csharp_new_line_before_open_brace = all:error 311 | resharper_max_array_initializer_elements_on_line = 1 312 | csharp_new_line_before_else = true 313 | csharp_new_line_before_catch = true 314 | csharp_new_line_before_finally = true 315 | csharp_new_line_before_members_in_object_initializers = true 316 | csharp_new_line_before_members_in_anonymous_types = true 317 | resharper_wrap_before_first_type_parameter_constraint = true 318 | resharper_wrap_extends_list_style = chop_always 319 | resharper_wrap_after_dot_in_method_calls = false 320 | resharper_wrap_before_binary_pattern_op = false 321 | resharper_wrap_object_and_collection_initializer_style = chop_always 322 | resharper_place_simple_initializer_on_single_line = false 323 | 324 | # space 325 | resharper_space_around_lambda_arrow = true 326 | 327 | dotnet_style_require_accessibility_modifiers = never:error 328 | resharper_place_type_constraints_on_same_line = false 329 | resharper_blank_lines_inside_namespace = 0 330 | resharper_blank_lines_after_file_scoped_namespace_directive = 1 331 | resharper_blank_lines_inside_type = 0 332 | 333 | resharper_place_attribute_on_same_line = false 334 | 335 | #braces https://www.jetbrains.com/help/resharper/EditorConfig_CSHARP_CSharpCodeStylePageImplSchema.html#Braces 336 | resharper_braces_for_ifelse = required 337 | resharper_braces_for_foreach = required 338 | resharper_braces_for_while = required 339 | resharper_braces_for_dowhile = required 340 | resharper_braces_for_lock = required 341 | resharper_braces_for_fixed = required 342 | resharper_braces_for_for = required 343 | 344 | resharper_return_value_of_pure_method_is_not_used_highlighting = error 345 | 346 | 347 | resharper_misleading_body_like_statement_highlighting = error 348 | 349 | resharper_redundant_record_class_keyword_highlighting = error 350 | 351 | resharper_redundant_extends_list_entry_highlighting = error 352 | 353 | resharper_redundant_type_arguments_inside_nameof_highlighting = error 354 | 355 | # Xml files 356 | [*.{xml,config,nuspec,resx,vsixmanifest,csproj,targets,props,fsproj}] 357 | indent_size = 2 358 | # https://www.jetbrains.com/help/resharper/EditorConfig_XML_XmlCodeStylePageSchema.html#resharper_xml_blank_line_after_pi 359 | resharper_blank_line_after_pi = false 360 | resharper_space_before_self_closing = true 361 | ij_xml_space_inside_empty_tag = true 362 | 363 | [*.json] 364 | indent_size = 2 365 | 366 | # Verify settings 367 | [*.{received,verified}.{txt,xml,json,md,sql,csv,html,htm,nuspec,rels}] 368 | charset = utf-8-bom 369 | end_of_line = lf 370 | indent_size = unset 371 | indent_style = unset 372 | insert_final_newline = false 373 | tab_width = unset 374 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.cs: -------------------------------------------------------------------------------- 1 | [TestFixture] 2 | [Parallelizable(ParallelScope.All)] 3 | public class CoreTests 4 | { 5 | [Test] 6 | public async Task MissingOrderBy() 7 | { 8 | await using var database = await DbContextBuilder.GetOrderRequiredDatabase(); 9 | var data = database.Context; 10 | await ThrowsTask( 11 | () => data.Companies 12 | .ToListAsync()) 13 | .IgnoreStackTrace(); 14 | } 15 | 16 | [Test] 17 | public async Task NestedMissingOrderBy() 18 | { 19 | await using var database = await DbContextBuilder.GetOrderRequiredDatabase(); 20 | var data = database.Context; 21 | await ThrowsTask( 22 | () => data.Companies 23 | .Include(_ => _.Employees) 24 | .OrderBy(_ => _.Name) 25 | .ToListAsync()) 26 | .IgnoreStackTrace(); 27 | } 28 | 29 | [Test] 30 | public async Task ScrubInlineEfDateTimes() 31 | { 32 | var target = "2024-09-05T06:59:16.1018211Z"; 33 | 34 | #region ScrubInlineEfDateTimesInstance 35 | 36 | var settings = new VerifySettings(); 37 | settings.ScrubInlineEfDateTimes(); 38 | await Verify(target, settings); 39 | 40 | #endregion 41 | } 42 | 43 | [Test] 44 | public async Task ScrubInlineEfDateTimesFluent() 45 | { 46 | var target = "2024-09-05T06:59:16.1018211Z"; 47 | 48 | #region ScrubInlineEfDateTimesFluent 49 | 50 | await Verify(target) 51 | .ScrubInlineEfDateTimes(); 52 | 53 | #endregion 54 | } 55 | 56 | [Test] 57 | public async Task WithOrderBy() 58 | { 59 | await using var database = await DbContextBuilder.GetOrderRequiredDatabase(); 60 | var data = database.Context; 61 | await Verify( 62 | data.Companies 63 | .OrderBy(_ => _.Name) 64 | .ToListAsync()); 65 | } 66 | 67 | [Test] 68 | public async Task SingleMissingOrder() 69 | { 70 | await using var database = await DbContextBuilder.GetOrderRequiredDatabase(); 71 | var data = database.Context; 72 | await Verify(data.Companies.Where(_ => _.Name == "Company1").SingleAsync()); 73 | } 74 | 75 | [Test] 76 | public async Task WithNestedOrderBy() 77 | { 78 | await using var database = await DbContextBuilder.GetOrderRequiredDatabase(); 79 | var data = database.Context; 80 | await Verify( 81 | data.Companies 82 | .Include(_ => _.Employees.OrderBy(_ => _.Age)) 83 | .OrderBy(_ => _.Name) 84 | .ToListAsync()); 85 | } 86 | 87 | #region Added 88 | 89 | [Test] 90 | public async Task Added() 91 | { 92 | var options = DbContextOptions(); 93 | 94 | await using var data = new SampleDbContext(options); 95 | var company = new Company 96 | { 97 | Name = "company name" 98 | }; 99 | data.Add(company); 100 | await Verify(data.ChangeTracker); 101 | } 102 | 103 | #endregion 104 | 105 | #region Deleted 106 | 107 | [Test] 108 | public async Task Deleted() 109 | { 110 | var options = DbContextOptions(); 111 | 112 | await using var data = new SampleDbContext(options); 113 | data.Add(new Company 114 | { 115 | Name = "company name" 116 | }); 117 | await data.SaveChangesAsync(); 118 | 119 | var company = data.Companies.Single(); 120 | data.Companies.Remove(company); 121 | await Verify(data.ChangeTracker); 122 | } 123 | 124 | #endregion 125 | 126 | #region Modified 127 | 128 | [Test] 129 | public async Task Modified() 130 | { 131 | var options = DbContextOptions(); 132 | 133 | await using var data = new SampleDbContext(options); 134 | var company = new Company 135 | { 136 | Name = "old name" 137 | }; 138 | data.Add(company); 139 | await data.SaveChangesAsync(); 140 | 141 | data.Companies.Single() 142 | .Name = "new name"; 143 | await Verify(data.ChangeTracker); 144 | } 145 | 146 | #endregion 147 | 148 | #region IgnoreNavigationProperties 149 | 150 | [Test] 151 | public async Task IgnoreNavigationProperties() 152 | { 153 | var options = DbContextOptions(); 154 | 155 | await using var data = new SampleDbContext(options); 156 | 157 | var company = new Company 158 | { 159 | Name = "company" 160 | }; 161 | var employee = new Employee 162 | { 163 | Name = "employee", 164 | Company = company 165 | }; 166 | await Verify(employee) 167 | .IgnoreNavigationProperties(); 168 | } 169 | 170 | #endregion 171 | 172 | #region IgnoreNavigationPropertiesExplicit 173 | 174 | [Test] 175 | public async Task IgnoreNavigationPropertiesExplicit() 176 | { 177 | var options = DbContextOptions(); 178 | 179 | await using var data = new SampleDbContext(options); 180 | 181 | var company = new Company 182 | { 183 | Name = "company" 184 | }; 185 | var employee = new Employee 186 | { 187 | Name = "employee", 188 | Company = company 189 | }; 190 | await Verify(employee) 191 | .IgnoreNavigationProperties(data); 192 | } 193 | 194 | #endregion 195 | 196 | static void IgnoreNavigationPropertiesGlobal() 197 | { 198 | #region IgnoreNavigationPropertiesGlobal 199 | 200 | var options = DbContextOptions(); 201 | using var data = new SampleDbContext(options); 202 | VerifyEntityFramework.IgnoreNavigationProperties(); 203 | 204 | #endregion 205 | } 206 | 207 | static void IgnoreNavigationPropertiesGlobalExplicit() 208 | { 209 | #region IgnoreNavigationPropertiesGlobalExplicit 210 | 211 | var options = DbContextOptions(); 212 | using var data = new SampleDbContext(options); 213 | VerifyEntityFramework.IgnoreNavigationProperties(data.Model); 214 | 215 | #endregion 216 | } 217 | 218 | [Test] 219 | public async Task WithNavigationProp() 220 | { 221 | var options = DbContextOptions(); 222 | 223 | await using var data = new SampleDbContext(options); 224 | var company = new Company 225 | { 226 | Name = "companyBefore" 227 | }; 228 | data.Add(company); 229 | var employee = new Employee 230 | { 231 | Name = "employeeBefore", 232 | Company = company 233 | }; 234 | data.Add(employee); 235 | await data.SaveChangesAsync(); 236 | 237 | data.Companies.Single() 238 | .Name = "companyAfter"; 239 | data.Employees.Single() 240 | .Name = "employeeAfter"; 241 | await Verify(data.ChangeTracker); 242 | } 243 | 244 | [Test] 245 | public async Task SomePropsModified() 246 | { 247 | var options = DbContextOptions(); 248 | 249 | await using var data = new SampleDbContext(options); 250 | var employee = new Employee 251 | { 252 | Name = "before", 253 | Age = 10 254 | }; 255 | data.Add(employee); 256 | await data.SaveChangesAsync(); 257 | 258 | data.Employees.Single() 259 | .Name = "after"; 260 | await Verify(data.ChangeTracker); 261 | } 262 | 263 | [Test] 264 | public Task ShouldIgnoreDbFactory() => 265 | Verify(new MyDbContextFactory()); 266 | 267 | [Test] 268 | public Task ShouldIgnoreDbFactoryInterface() 269 | { 270 | var target = new TargetWithFactoryInterface 271 | { 272 | Factory = new MyDbContextFactory() 273 | }; 274 | return Verify(target); 275 | } 276 | 277 | class TargetWithFactoryInterface 278 | { 279 | public IDbContextFactory Factory { get; set; } = null!; 280 | } 281 | 282 | [Test] 283 | public Task ShouldIgnoreDbContext() => 284 | Verify( 285 | new 286 | { 287 | Factory = new SampleDbContext(new DbContextOptions()) 288 | }); 289 | 290 | class MyDbContextFactory : IDbContextFactory 291 | { 292 | public SampleDbContext CreateDbContext() => 293 | throw new NotImplementedException(); 294 | } 295 | 296 | [Test] 297 | public async Task UpdateEntity() 298 | { 299 | var options = DbContextOptions(); 300 | 301 | await using var data = new SampleDbContext(options); 302 | data.Add(new Employee 303 | { 304 | Name = "before" 305 | }); 306 | await data.SaveChangesAsync(); 307 | 308 | var employee = data.Employees.Single(); 309 | data.Update(employee) 310 | .Entity.Name = "after"; 311 | await Verify(data.ChangeTracker); 312 | } 313 | 314 | [Test] 315 | public async Task AllData() 316 | { 317 | var database = await DbContextBuilder.GetDatabase(); 318 | var data = database.Context; 319 | 320 | #region AllData 321 | 322 | await Verify(data.AllData()) 323 | .AddExtraSettings( 324 | serializer => 325 | serializer.TypeNameHandling = TypeNameHandling.Objects); 326 | 327 | #endregion 328 | } 329 | 330 | [Test] 331 | public async Task Queryable() 332 | { 333 | var database = await DbContextBuilder.GetDatabase(); 334 | await database.AddData( 335 | new Company 336 | { 337 | Name = "company name" 338 | }); 339 | var data = database.Context; 340 | 341 | #region Queryable 342 | 343 | var queryable = data.Companies 344 | .Where(_ => _.Name == "company name"); 345 | await Verify(queryable); 346 | 347 | #endregion 348 | } 349 | 350 | [Test] 351 | public async Task SetSelect() 352 | { 353 | var database = await DbContextBuilder.GetDatabase(); 354 | var data = database.Context; 355 | 356 | var query = data 357 | .Set() 358 | .Select(_ => _.Id); 359 | await Verify(query); 360 | } 361 | 362 | [Test] 363 | public async Task NestedQueryable() 364 | { 365 | var database = await DbContextBuilder.GetDatabase(); 366 | await database.AddData( 367 | new Company 368 | { 369 | Name = "value" 370 | }); 371 | var data = database.Context; 372 | var queryable = data.Companies 373 | .Where(_ => _.Name == "value"); 374 | await Verify( 375 | new 376 | { 377 | queryable 378 | }); 379 | } 380 | 381 | // ReSharper disable once UnusedVariable 382 | static void Build(string connection) 383 | { 384 | #region EnableRecording 385 | 386 | var builder = new DbContextOptionsBuilder(); 387 | builder.UseSqlServer(connection); 388 | builder.EnableRecording(); 389 | var data = new SampleDbContext(builder.Options); 390 | 391 | #endregion 392 | } 393 | 394 | [Test] 395 | public async Task Parameters() 396 | { 397 | var database = await DbContextBuilder.GetDatabase(); 398 | var data = database.Context; 399 | data.Add( 400 | new Company 401 | { 402 | Name = Guid 403 | .NewGuid() 404 | .ToString() 405 | } 406 | ); 407 | Recording.Start(); 408 | await data.SaveChangesAsync(); 409 | await Verify(); 410 | } 411 | 412 | [Test] 413 | public async Task MultiRecording() 414 | { 415 | var database = await DbContextBuilder.GetDatabase(); 416 | var data = database.Context; 417 | Recording.Start(); 418 | var company = new Company 419 | { 420 | Name = "Title" 421 | }; 422 | data.Add(company); 423 | await data.SaveChangesAsync(); 424 | 425 | for (var i = 0; i < 100; i++) 426 | { 427 | var s = i.ToString(); 428 | await data 429 | .Companies 430 | .Where(_ => _.Name == s) 431 | .ToListAsync(); 432 | } 433 | 434 | var company2 = new Company 435 | { 436 | Id = 2, 437 | Name = "Title2" 438 | }; 439 | data.Add(company2); 440 | await data.SaveChangesAsync(); 441 | 442 | await Verify(); 443 | } 444 | 445 | [Test] 446 | public async Task MultiDbContexts() 447 | { 448 | var database = await DbContextBuilder.GetDatabase(); 449 | var connectionString = database.ConnectionString; 450 | 451 | #region MultiDbContexts 452 | 453 | var builder = new DbContextOptionsBuilder(); 454 | builder.UseSqlServer(connectionString); 455 | builder.EnableRecording(); 456 | 457 | await using var data1 = new SampleDbContext(builder.Options); 458 | Recording.Start(); 459 | var company = new Company 460 | { 461 | Name = "Title" 462 | }; 463 | data1.Add(company); 464 | await data1.SaveChangesAsync(); 465 | 466 | await using var data2 = new SampleDbContext(builder.Options); 467 | await data2 468 | .Companies 469 | .Where(_ => _.Name == "Title") 470 | .ToListAsync(); 471 | 472 | await Verify(); 473 | 474 | #endregion 475 | } 476 | 477 | [Test] 478 | public async Task RecordingTest() 479 | { 480 | var database = await DbContextBuilder.GetDatabase(); 481 | var data = database.Context; 482 | 483 | #region Recording 484 | 485 | var company = new Company 486 | { 487 | Name = "Title" 488 | }; 489 | data.Add(company); 490 | await data.SaveChangesAsync(); 491 | 492 | Recording.Start(); 493 | 494 | await data 495 | .Companies 496 | .Where(_ => _.Name == "Title") 497 | .ToListAsync(); 498 | 499 | await Verify(); 500 | 501 | #endregion 502 | } 503 | 504 | [Test] 505 | public async Task RecordingDisabledTest() 506 | { 507 | var database = await DbContextBuilder.GetDatabase(); 508 | var data = database.Context; 509 | 510 | #region RecordingDisableForInstance 511 | 512 | var company = new Company 513 | { 514 | Name = "Title" 515 | }; 516 | data.Add(company); 517 | await data.SaveChangesAsync(); 518 | 519 | Recording.Start(); 520 | 521 | await data 522 | .Companies 523 | .Where(_ => _.Name == "Title") 524 | .ToListAsync(); 525 | data.DisableRecording(); 526 | await data 527 | .Companies 528 | .Where(_ => _.Name == "Disabled") 529 | .ToListAsync(); 530 | 531 | await Verify(); 532 | 533 | #endregion 534 | } 535 | 536 | [DatapointSource] 537 | public IEnumerable runs = Enumerable.Range(0, 5); 538 | 539 | [Theory] 540 | public async Task RecordingWebApplicationFactory(int run) 541 | { 542 | // Not actually the test name, the variable name is for README.md to make sense 543 | var testName = nameof(RecordingWebApplicationFactory) + run; 544 | 545 | await using var connection = new SqliteConnection($"Data Source={testName};Mode=Memory;Cache=Shared"); 546 | await connection.OpenAsync(); 547 | 548 | var factory = new CustomWebApplicationFactory(testName); 549 | 550 | await using (var scope = factory.Services.CreateAsyncScope()) 551 | { 552 | var context = scope.ServiceProvider.GetRequiredService(); 553 | 554 | await context.Database.EnsureCreatedAsync(); 555 | 556 | context.Add( 557 | new Company 558 | { 559 | Id = 1, 560 | Name = "Foo" 561 | }); 562 | 563 | await context.SaveChangesAsync(); 564 | } 565 | 566 | #region RecordWithIdentifier 567 | 568 | var httpClient = factory.CreateClient(); 569 | 570 | Recording.Start(testName); 571 | 572 | var companies = await httpClient.GetFromJsonAsync("/companies"); 573 | 574 | var entries = Recording.Stop(testName); 575 | 576 | #endregion 577 | 578 | #region VerifyRecordedCommandsWithIdentifier 579 | 580 | await Verify( 581 | new 582 | { 583 | target = companies!.Length, 584 | sql = entries 585 | }); 586 | 587 | #endregion 588 | } 589 | 590 | class CustomWebApplicationFactory(string name) : 591 | WebApplicationFactory 592 | { 593 | #region EnableRecordingWithIdentifier 594 | 595 | protected override void ConfigureWebHost(IWebHostBuilder webBuilder) 596 | { 597 | var dataBuilder = new DbContextOptionsBuilder() 598 | .EnableRecording(name) 599 | .UseSqlite($"Data Source={name};Mode=Memory;Cache=Shared"); 600 | webBuilder.ConfigureTestServices( 601 | _ => _.AddScoped( 602 | _ => dataBuilder.Options)); 603 | } 604 | 605 | #endregion 606 | 607 | protected override IHostBuilder CreateHostBuilder() => 608 | Host 609 | .CreateDefaultBuilder() 610 | .ConfigureWebHostDefaults(_ => _.UseStartup()); 611 | } 612 | 613 | #pragma warning disable CA1822 614 | public class Startup 615 | { 616 | public void ConfigureServices(IServiceCollection services) => 617 | services 618 | .AddDbContext( 619 | _ => _.UseInMemoryDatabase("")); 620 | 621 | public void Configure(IApplicationBuilder app) 622 | { 623 | app.UseRouting(); 624 | 625 | app.UseEndpoints(endpoints 626 | => endpoints.MapGet( 627 | "/companies", 628 | (SampleDbContext data) => data.Companies.ToListAsync())); 629 | } 630 | } 631 | #pragma warning restore CA1822 632 | 633 | [Test] 634 | public async Task RecordingSpecific() 635 | { 636 | var database = await DbContextBuilder.GetDatabase(); 637 | var data = database.Context; 638 | 639 | #region RecordingSpecific 640 | 641 | var company = new Company 642 | { 643 | Name = "Title" 644 | }; 645 | data.Add(company); 646 | await data.SaveChangesAsync(); 647 | 648 | Recording.Start(); 649 | 650 | await data 651 | .Companies 652 | .Where(_ => _.Name == "Title") 653 | .ToListAsync(); 654 | 655 | var entries = Recording.Stop(); 656 | //TODO: optionally filter the results 657 | await Verify( 658 | new 659 | { 660 | target = data.Companies.Count(), 661 | entries 662 | }); 663 | 664 | #endregion 665 | } 666 | 667 | static DbContextOptions DbContextOptions( 668 | [CallerMemberName] string databaseName = "") => 669 | new DbContextOptionsBuilder() 670 | .UseInMemoryDatabase(databaseName) 671 | .Options; 672 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Verify.EntityFramework 2 | 3 | [![Discussions](https://img.shields.io/badge/Verify-Discussions-yellow?svg=true&label=)](https://github.com/orgs/VerifyTests/discussions) 4 | [![Build status](https://ci.appveyor.com/api/projects/status/g6njwv0aox62atu0?svg=true)](https://ci.appveyor.com/project/SimonCropp/verify-entityframework) 5 | [![NuGet Status](https://img.shields.io/nuget/v/Verify.EntityFramework.svg?label=Verify.EntityFramework)](https://www.nuget.org/packages/Verify.EntityFramework/) 6 | [![NuGet Status](https://img.shields.io/nuget/v/Verify.EntityFrameworkClassic.svg?label=Verify.EntityFrameworkClassic)](https://www.nuget.org/packages/Verify.EntityFrameworkClassic/) 7 | 8 | Extends [Verify](https://github.com/VerifyTests/Verify) to allow snapshot testing with EntityFramework. 9 | 10 | **See [Milestones](../../milestones?state=closed) for release notes.** 11 | 12 | 13 | ## Sponsors 14 | 15 | 16 | ### Entity Framework Extensions 17 | 18 | [Entity Framework Extensions](https://entityframework-extensions.net/?utm_source=simoncropp&utm_medium=Verify.EntityFramework) is a major sponsor and is proud to contribute to the development this project. 19 | 20 | [![Entity Framework Extensions](https://raw.githubusercontent.com/VerifyTests/Verify.EntityFramework/refs/heads/main/docs/zzz.png)](https://entityframework-extensions.net/?utm_source=simoncropp&utm_medium=Verify.EntityFramework) 21 | 22 | 23 | ## NuGet 24 | 25 | * https://nuget.org/packages/Verify.EntityFramework/ 26 | * https://nuget.org/packages/Verify.EntityFrameworkClassic/ 27 | 28 | 29 | ## Enable 30 | 31 | Enable VerifyEntityFramework once at assembly load time: 32 | 33 | 34 | ### EF Core 35 | 36 | 37 | 38 | ```cs 39 | static IModel GetDbModel() 40 | { 41 | var options = new DbContextOptionsBuilder(); 42 | options.UseSqlServer("fake"); 43 | using var data = new SampleDbContext(options.Options); 44 | return data.Model; 45 | } 46 | 47 | [ModuleInitializer] 48 | public static void Init() 49 | { 50 | var model = GetDbModel(); 51 | VerifyEntityFramework.Initialize(model); 52 | } 53 | ``` 54 | snippet source | anchor 55 | 56 | 57 | The `GetDbModel` pattern allows an instance of the `IModel` to be stored for use when `IgnoreNavigationProperties` is called inside tests. This is optional, and instead can be passed explicitly to `IgnoreNavigationProperties`. 58 | 59 | 60 | ### EF Classic 61 | 62 | 63 | 64 | ```cs 65 | [ModuleInitializer] 66 | public static void Init() => 67 | VerifyEntityFrameworkClassic.Initialize(); 68 | ``` 69 | snippet source | anchor 70 | 71 | 72 | 73 | ## Recording 74 | 75 | Recording allows all commands executed by EF to be captured and then (optionally) verified. 76 | 77 | 78 | ### Enable 79 | 80 | Call `EfRecording.EnableRecording()` on `DbContextOptionsBuilder`. 81 | 82 | 83 | 84 | ```cs 85 | var builder = new DbContextOptionsBuilder(); 86 | builder.UseSqlServer(connection); 87 | builder.EnableRecording(); 88 | var data = new SampleDbContext(builder.Options); 89 | ``` 90 | snippet source | anchor 91 | 92 | 93 | `EnableRecording` should only be called in the test context. 94 | 95 | 96 | ### Usage 97 | 98 | To start recording call `EfRecording.StartRecording()`. The results will be automatically included in verified file. 99 | 100 | 101 | 102 | ```cs 103 | var company = new Company 104 | { 105 | Name = "Title" 106 | }; 107 | data.Add(company); 108 | await data.SaveChangesAsync(); 109 | 110 | Recording.Start(); 111 | 112 | await data 113 | .Companies 114 | .Where(_ => _.Name == "Title") 115 | .ToListAsync(); 116 | 117 | await Verify(); 118 | ``` 119 | snippet source | anchor 120 | 121 | 122 | Will result in the following verified file: 123 | 124 | 125 | 126 | ```txt 127 | { 128 | ef: { 129 | Type: ReaderExecutedAsync, 130 | HasTransaction: false, 131 | Text: 132 | select c.Id, 133 | c.Name 134 | from Companies as c 135 | where c.Name = N'Title' 136 | } 137 | } 138 | ``` 139 | snippet source | anchor 140 | 141 | 142 | 143 | Sql entries can be explicitly read using `EfRecording.FinishRecording`, optionally filtered, and passed to Verify: 144 | 145 | 146 | 147 | ```cs 148 | var company = new Company 149 | { 150 | Name = "Title" 151 | }; 152 | data.Add(company); 153 | await data.SaveChangesAsync(); 154 | 155 | Recording.Start(); 156 | 157 | await data 158 | .Companies 159 | .Where(_ => _.Name == "Title") 160 | .ToListAsync(); 161 | 162 | var entries = Recording.Stop(); 163 | //TODO: optionally filter the results 164 | await Verify( 165 | new 166 | { 167 | target = data.Companies.Count(), 168 | entries 169 | }); 170 | ``` 171 | snippet source | anchor 172 | 173 | 174 | 175 | ### DbContext spanning 176 | 177 | `StartRecording` can be called on different DbContext instances (built from the same options) and the results will be aggregated. 178 | 179 | 180 | 181 | ```cs 182 | var builder = new DbContextOptionsBuilder(); 183 | builder.UseSqlServer(connectionString); 184 | builder.EnableRecording(); 185 | 186 | await using var data1 = new SampleDbContext(builder.Options); 187 | Recording.Start(); 188 | var company = new Company 189 | { 190 | Name = "Title" 191 | }; 192 | data1.Add(company); 193 | await data1.SaveChangesAsync(); 194 | 195 | await using var data2 = new SampleDbContext(builder.Options); 196 | await data2 197 | .Companies 198 | .Where(_ => _.Name == "Title") 199 | .ToListAsync(); 200 | 201 | await Verify(); 202 | ``` 203 | snippet source | anchor 204 | 205 | 206 | 207 | 208 | ```txt 209 | { 210 | ef: [ 211 | { 212 | Type: ReaderExecutedAsync, 213 | HasTransaction: false, 214 | Parameters: { 215 | @p0 (Int32): 0, 216 | @p1 (String): Title 217 | }, 218 | Text: 219 | set implicit_transactions off; 220 | 221 | set nocount on; 222 | 223 | insert into Companies (Id, Name) 224 | values (@p0, @p1) 225 | }, 226 | { 227 | Type: ReaderExecutedAsync, 228 | HasTransaction: false, 229 | Text: 230 | select c.Id, 231 | c.Name 232 | from Companies as c 233 | where c.Name = N'Title' 234 | } 235 | ] 236 | } 237 | ``` 238 | snippet source | anchor 239 | 240 | 241 | 242 | ### Disabling Recording for an instance 243 | 244 | 245 | 246 | ```cs 247 | var company = new Company 248 | { 249 | Name = "Title" 250 | }; 251 | data.Add(company); 252 | await data.SaveChangesAsync(); 253 | 254 | Recording.Start(); 255 | 256 | await data 257 | .Companies 258 | .Where(_ => _.Name == "Title") 259 | .ToListAsync(); 260 | data.DisableRecording(); 261 | await data 262 | .Companies 263 | .Where(_ => _.Name == "Disabled") 264 | .ToListAsync(); 265 | 266 | await Verify(); 267 | ``` 268 | snippet source | anchor 269 | 270 | 271 | 272 | 273 | ```txt 274 | { 275 | ef: { 276 | Type: ReaderExecutedAsync, 277 | HasTransaction: false, 278 | Text: 279 | select c.Id, 280 | c.Name 281 | from Companies as c 282 | where c.Name = N'Title' 283 | } 284 | } 285 | ``` 286 | snippet source | anchor 287 | 288 | 289 | 290 | ## ChangeTracking 291 | 292 | Added, deleted, and Modified entities can be verified by performing changes on a DbContext and then verifying the instance of ChangeTracking. This approach leverages the [EntityFramework ChangeTracker](https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.changetracking.changetracker). 293 | 294 | 295 | ### Added entity 296 | 297 | This test: 298 | 299 | 300 | 301 | ```cs 302 | [Test] 303 | public async Task Added() 304 | { 305 | var options = DbContextOptions(); 306 | 307 | await using var data = new SampleDbContext(options); 308 | var company = new Company 309 | { 310 | Name = "company name" 311 | }; 312 | data.Add(company); 313 | await Verify(data.ChangeTracker); 314 | } 315 | ``` 316 | snippet source | anchor 317 | 318 | 319 | Will result in the following verified file: 320 | 321 | 322 | 323 | ```txt 324 | { 325 | Added: { 326 | Company: { 327 | Id: 0, 328 | Name: company name 329 | } 330 | } 331 | } 332 | ``` 333 | snippet source | anchor 334 | 335 | 336 | 337 | ### Deleted entity 338 | 339 | This test: 340 | 341 | 342 | 343 | ```cs 344 | [Test] 345 | public async Task Deleted() 346 | { 347 | var options = DbContextOptions(); 348 | 349 | await using var data = new SampleDbContext(options); 350 | data.Add(new Company 351 | { 352 | Name = "company name" 353 | }); 354 | await data.SaveChangesAsync(); 355 | 356 | var company = data.Companies.Single(); 357 | data.Companies.Remove(company); 358 | await Verify(data.ChangeTracker); 359 | } 360 | ``` 361 | snippet source | anchor 362 | 363 | 364 | Will result in the following verified file: 365 | 366 | 367 | 368 | ```txt 369 | { 370 | Deleted: { 371 | Company: { 372 | Id: 0 373 | } 374 | } 375 | } 376 | ``` 377 | snippet source | anchor 378 | 379 | 380 | 381 | ### Modified entity 382 | 383 | This test: 384 | 385 | 386 | 387 | ```cs 388 | [Test] 389 | public async Task Modified() 390 | { 391 | var options = DbContextOptions(); 392 | 393 | await using var data = new SampleDbContext(options); 394 | var company = new Company 395 | { 396 | Name = "old name" 397 | }; 398 | data.Add(company); 399 | await data.SaveChangesAsync(); 400 | 401 | data.Companies.Single() 402 | .Name = "new name"; 403 | await Verify(data.ChangeTracker); 404 | } 405 | ``` 406 | snippet source | anchor 407 | 408 | 409 | Will result in the following verified file: 410 | 411 | 412 | 413 | ```txt 414 | { 415 | Modified: { 416 | Company: { 417 | Id: 0, 418 | Name: { 419 | Original: old name, 420 | Current: new name 421 | } 422 | } 423 | } 424 | } 425 | ``` 426 | snippet source | anchor 427 | 428 | 429 | 430 | ## Queryable 431 | 432 | This test: 433 | 434 | 435 | 436 | ```cs 437 | var queryable = data.Companies 438 | .Where(_ => _.Name == "company name"); 439 | await Verify(queryable); 440 | ``` 441 | snippet source | anchor 442 | 443 | 444 | Will result in the following verified files: 445 | 446 | 447 | ### EF Core 448 | 449 | 450 | #### CoreTests.Queryable.verified.txt 451 | 452 | 453 | 454 | ```txt 455 | [ 456 | { 457 | Name: company name 458 | } 459 | ] 460 | ``` 461 | snippet source | anchor 462 | 463 | 464 | 465 | #### CoreTests.Queryable.verified.sql 466 | 467 | 468 | 469 | ```sql 470 | SELECT [c].[Id], [c].[Name] 471 | FROM [Companies] AS [c] 472 | WHERE [c].[Name] = N'company name' 473 | ``` 474 | snippet source | anchor 475 | 476 | 477 | 478 | ### EF Classic 479 | 480 | 481 | #### ClassicTests.Queryable.verified.txt 482 | 483 | 484 | 485 | ```txt 486 | SELECT 487 | [Extent1].[Id] AS [Id], 488 | [Extent1].[Content] AS [Content] 489 | FROM [dbo].[Companies] AS [Extent1] 490 | WHERE N'value' = [Extent1].[Content] 491 | ``` 492 | snippet source | anchor 493 | 494 | 495 | 496 | ## AllData 497 | 498 | This test: 499 | 500 | 501 | 502 | ```cs 503 | await Verify(data.AllData()) 504 | .AddExtraSettings( 505 | serializer => 506 | serializer.TypeNameHandling = TypeNameHandling.Objects); 507 | ``` 508 | snippet source | anchor 509 | 510 | 511 | Will result in the following verified file with all data in the database: 512 | 513 | 514 | 515 | ```txt 516 | [ 517 | { 518 | $type: Company, 519 | Id: 1, 520 | Name: Company1 521 | }, 522 | { 523 | $type: Company, 524 | Id: 4, 525 | Name: Company2 526 | }, 527 | { 528 | $type: Company, 529 | Id: 6, 530 | Name: Company3 531 | }, 532 | { 533 | $type: Company, 534 | Id: 7, 535 | Name: Company4 536 | }, 537 | { 538 | $type: Employee, 539 | Id: 2, 540 | CompanyId: 1, 541 | Name: Employee1, 542 | Age: 25 543 | }, 544 | { 545 | $type: Employee, 546 | Id: 3, 547 | CompanyId: 1, 548 | Name: Employee2, 549 | Age: 31 550 | }, 551 | { 552 | $type: Employee, 553 | Id: 5, 554 | CompanyId: 4, 555 | Name: Employee4, 556 | Age: 34 557 | } 558 | ] 559 | ``` 560 | snippet source | anchor 561 | 562 | 563 | 564 | ## IgnoreNavigationProperties 565 | 566 | `IgnoreNavigationProperties` extends `SerializationSettings` to exclude all navigation properties from serialization: 567 | 568 | 569 | 570 | ```cs 571 | [Test] 572 | public async Task IgnoreNavigationProperties() 573 | { 574 | var options = DbContextOptions(); 575 | 576 | await using var data = new SampleDbContext(options); 577 | 578 | var company = new Company 579 | { 580 | Name = "company" 581 | }; 582 | var employee = new Employee 583 | { 584 | Name = "employee", 585 | Company = company 586 | }; 587 | await Verify(employee) 588 | .IgnoreNavigationProperties(); 589 | } 590 | ``` 591 | snippet source | anchor 592 | 593 | 594 | 595 | ### Ignore globally 596 | 597 | 598 | 599 | ```cs 600 | var options = DbContextOptions(); 601 | using var data = new SampleDbContext(options); 602 | VerifyEntityFramework.IgnoreNavigationProperties(); 603 | ``` 604 | snippet source | anchor 605 | 606 | 607 | 608 | ## WebApplicationFactory 609 | 610 | To be able to use [WebApplicationFactory](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing.webapplicationfactory-1) for [integration testing](https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests) an identifier must be used to be able to retrieve the recorded commands. Start by enable recording with a unique identifier, for example the test name or a GUID: 611 | 612 | 613 | 614 | ```cs 615 | protected override void ConfigureWebHost(IWebHostBuilder webBuilder) 616 | { 617 | var dataBuilder = new DbContextOptionsBuilder() 618 | .EnableRecording(name) 619 | .UseSqlite($"Data Source={name};Mode=Memory;Cache=Shared"); 620 | webBuilder.ConfigureTestServices( 621 | _ => _.AddScoped( 622 | _ => dataBuilder.Options)); 623 | } 624 | ``` 625 | snippet source | anchor 626 | 627 | 628 | Then use the same identifier for recording: 629 | 630 | 631 | 632 | ```cs 633 | var httpClient = factory.CreateClient(); 634 | 635 | Recording.Start(testName); 636 | 637 | var companies = await httpClient.GetFromJsonAsync("/companies"); 638 | 639 | var entries = Recording.Stop(testName); 640 | ``` 641 | snippet source | anchor 642 | 643 | 644 | The results will not be automatically included in verified file so it will have to be verified manually: 645 | 646 | 647 | 648 | ```cs 649 | await Verify( 650 | new 651 | { 652 | target = companies!.Length, 653 | sql = entries 654 | }); 655 | ``` 656 | snippet source | anchor 657 | 658 | 659 | 660 | ## ScrubInlineEfDateTimes 661 | 662 | In some scenarios EntityFrmaeowrk does not parameterise DateTimes. For example when querying [temporal tables](https://learn.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables). 663 | 664 | `ScrubInlineEfDateTimes()` is a convenience method that calls `.ScrubInlineDateTimes("yyyy-MM-ddTHH:mm:ss.fffffffZ")`. 665 | 666 | 667 | ### Static usage 668 | 669 | ``` 670 | VerifyEntityFramework.ScrubInlineEfDateTimes(); 671 | ``` 672 | 673 | 674 | ### Instance usage 675 | 676 | 677 | 678 | ```cs 679 | var settings = new VerifySettings(); 680 | settings.ScrubInlineEfDateTimes(); 681 | await Verify(target, settings); 682 | ``` 683 | snippet source | anchor 684 | 685 | 686 | 687 | ### Fluent usage 688 | 689 | 690 | 691 | ```cs 692 | await Verify(target) 693 | .ScrubInlineEfDateTimes(); 694 | ``` 695 | snippet source | anchor 696 | 697 | 698 | 699 | ## Icon 700 | 701 | [Database](https://thenounproject.com/term/database/310841/) designed by [Creative Stall](https://thenounproject.com/creativestall/) from [The Noun Project](https://thenounproject.com). 702 | -------------------------------------------------------------------------------- /src/Shared.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | False 3 | Quiet 4 | True 5 | True 6 | True 7 | DO_NOT_SHOW 8 | ERROR 9 | ERROR 10 | ERROR 11 | WARNING 12 | ERROR 13 | ERROR 14 | ERROR 15 | ERROR 16 | ERROR 17 | ERROR 18 | ERROR 19 | ERROR 20 | ERROR 21 | ERROR 22 | ERROR 23 | ERROR 24 | ERROR 25 | ERROR 26 | ERROR 27 | ERROR 28 | ERROR 29 | ERROR 30 | ERROR 31 | ERROR 32 | ERROR 33 | ERROR 34 | ERROR 35 | ERROR 36 | ERROR 37 | ERROR 38 | ERROR 39 | ERROR 40 | ERROR 41 | ERROR 42 | ERROR 43 | DO_NOT_SHOW 44 | DO_NOT_SHOW 45 | ERROR 46 | ERROR 47 | ERROR 48 | ERROR 49 | ERROR 50 | ERROR 51 | ERROR 52 | ERROR 53 | ERROR 54 | ERROR 55 | ERROR 56 | ERROR 57 | C90+,E79+,S14+ 58 | ERROR 59 | ERROR 60 | ERROR 61 | ERROR 62 | ERROR 63 | ERROR 64 | ERROR 65 | ERROR 66 | ERROR 67 | ERROR 68 | ERROR 69 | ERROR 70 | ERROR 71 | ERROR 72 | ERROR 73 | ERROR 74 | ERROR 75 | ERROR 76 | ERROR 77 | ERROR 78 | ERROR 79 | ERROR 80 | ERROR 81 | ERROR 82 | ERROR 83 | ERROR 84 | ERROR 85 | ERROR 86 | ERROR 87 | ERROR 88 | ERROR 89 | ERROR 90 | ERROR 91 | ERROR 92 | ERROR 93 | ERROR 94 | ERROR 95 | ERROR 96 | ERROR 97 | ERROR 98 | ERROR 99 | ERROR 100 | ERROR 101 | ERROR 102 | ERROR 103 | ERROR 104 | ERROR 105 | ERROR 106 | ERROR 107 | ERROR 108 | ERROR 109 | ERROR 110 | ERROR 111 | ERROR 112 | ERROR 113 | ERROR 114 | ERROR 115 | ERROR 116 | ERROR 117 | ERROR 118 | ERROR 119 | ERROR 120 | ERROR 121 | ERROR 122 | DO_NOT_SHOW 123 | *.received.* 124 | *.verified.* 125 | ERROR 126 | ERROR 127 | DO_NOT_SHOW 128 | ECMAScript 2016 129 | <?xml version="1.0" encoding="utf-16"?><Profile name="c# Cleanup"><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><CSCodeStyleAttributes ArrangeVarStyle="True" ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeCodeBodyStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JSStringLiteralQuotesDescriptor>True</JSStringLiteralQuotesDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsInsertSemicolon>True</JsInsertSemicolon><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><HtmlReformatCode>True</HtmlReformatCode><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><IDEA_SETTINGS>&lt;profile version="1.0"&gt; 130 | &lt;option name="myName" value="c# Cleanup" /&gt; 131 | &lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt; 132 | &lt;Language id="EditorConfig"&gt; 133 | &lt;Reformat&gt;false&lt;/Reformat&gt; 134 | &lt;/Language&gt; 135 | &lt;Language id="HTML"&gt; 136 | &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; 137 | &lt;Reformat&gt;false&lt;/Reformat&gt; 138 | &lt;Rearrange&gt;false&lt;/Rearrange&gt; 139 | &lt;/Language&gt; 140 | &lt;Language id="JSON"&gt; 141 | &lt;Reformat&gt;false&lt;/Reformat&gt; 142 | &lt;/Language&gt; 143 | &lt;Language id="RELAX-NG"&gt; 144 | &lt;Reformat&gt;false&lt;/Reformat&gt; 145 | &lt;/Language&gt; 146 | &lt;Language id="XML"&gt; 147 | &lt;OptimizeImports&gt;false&lt;/OptimizeImports&gt; 148 | &lt;Reformat&gt;false&lt;/Reformat&gt; 149 | &lt;Rearrange&gt;false&lt;/Rearrange&gt; 150 | &lt;/Language&gt; 151 | &lt;/profile&gt;</RIDER_SETTINGS></Profile> 152 | ExpressionBody 153 | ExpressionBody 154 | ExpressionBody 155 | False 156 | NEVER 157 | NEVER 158 | False 159 | False 160 | False 161 | True 162 | False 163 | CHOP_ALWAYS 164 | False 165 | False 166 | RemoveIndent 167 | RemoveIndent 168 | False 169 | True 170 | True 171 | True 172 | True 173 | True 174 | ERROR 175 | DoNothing 176 | -------------------------------------------------------------------------------- /src/Verify.EntityFramework.Tests/CoreTests.MultiRecording.verified.txt: -------------------------------------------------------------------------------- 1 | { 2 | ef: [ 3 | { 4 | Type: ReaderExecutedAsync, 5 | HasTransaction: false, 6 | Parameters: { 7 | @p0 (Int32): 0, 8 | @p1 (String): Title 9 | }, 10 | Text: 11 | set implicit_transactions off; 12 | 13 | set nocount on; 14 | 15 | insert into Companies (Id, Name) 16 | values (@p0, @p1) 17 | }, 18 | { 19 | Type: ReaderExecutedAsync, 20 | HasTransaction: false, 21 | Parameters: { 22 | @s (String?): 0 23 | }, 24 | Text: 25 | select c.Id, 26 | c.Name 27 | from Companies as c 28 | where c.Name = @s 29 | }, 30 | { 31 | Type: ReaderExecutedAsync, 32 | HasTransaction: false, 33 | Parameters: { 34 | @s (String?): 1 35 | }, 36 | Text: 37 | select c.Id, 38 | c.Name 39 | from Companies as c 40 | where c.Name = @s 41 | }, 42 | { 43 | Type: ReaderExecutedAsync, 44 | HasTransaction: false, 45 | Parameters: { 46 | @s (String?): 2 47 | }, 48 | Text: 49 | select c.Id, 50 | c.Name 51 | from Companies as c 52 | where c.Name = @s 53 | }, 54 | { 55 | Type: ReaderExecutedAsync, 56 | HasTransaction: false, 57 | Parameters: { 58 | @s (String?): 3 59 | }, 60 | Text: 61 | select c.Id, 62 | c.Name 63 | from Companies as c 64 | where c.Name = @s 65 | }, 66 | { 67 | Type: ReaderExecutedAsync, 68 | HasTransaction: false, 69 | Parameters: { 70 | @s (String?): 4 71 | }, 72 | Text: 73 | select c.Id, 74 | c.Name 75 | from Companies as c 76 | where c.Name = @s 77 | }, 78 | { 79 | Type: ReaderExecutedAsync, 80 | HasTransaction: false, 81 | Parameters: { 82 | @s (String?): 5 83 | }, 84 | Text: 85 | select c.Id, 86 | c.Name 87 | from Companies as c 88 | where c.Name = @s 89 | }, 90 | { 91 | Type: ReaderExecutedAsync, 92 | HasTransaction: false, 93 | Parameters: { 94 | @s (String?): 6 95 | }, 96 | Text: 97 | select c.Id, 98 | c.Name 99 | from Companies as c 100 | where c.Name = @s 101 | }, 102 | { 103 | Type: ReaderExecutedAsync, 104 | HasTransaction: false, 105 | Parameters: { 106 | @s (String?): 7 107 | }, 108 | Text: 109 | select c.Id, 110 | c.Name 111 | from Companies as c 112 | where c.Name = @s 113 | }, 114 | { 115 | Type: ReaderExecutedAsync, 116 | HasTransaction: false, 117 | Parameters: { 118 | @s (String?): 8 119 | }, 120 | Text: 121 | select c.Id, 122 | c.Name 123 | from Companies as c 124 | where c.Name = @s 125 | }, 126 | { 127 | Type: ReaderExecutedAsync, 128 | HasTransaction: false, 129 | Parameters: { 130 | @s (String?): 9 131 | }, 132 | Text: 133 | select c.Id, 134 | c.Name 135 | from Companies as c 136 | where c.Name = @s 137 | }, 138 | { 139 | Type: ReaderExecutedAsync, 140 | HasTransaction: false, 141 | Parameters: { 142 | @s (String?): 10 143 | }, 144 | Text: 145 | select c.Id, 146 | c.Name 147 | from Companies as c 148 | where c.Name = @s 149 | }, 150 | { 151 | Type: ReaderExecutedAsync, 152 | HasTransaction: false, 153 | Parameters: { 154 | @s (String?): 11 155 | }, 156 | Text: 157 | select c.Id, 158 | c.Name 159 | from Companies as c 160 | where c.Name = @s 161 | }, 162 | { 163 | Type: ReaderExecutedAsync, 164 | HasTransaction: false, 165 | Parameters: { 166 | @s (String?): 12 167 | }, 168 | Text: 169 | select c.Id, 170 | c.Name 171 | from Companies as c 172 | where c.Name = @s 173 | }, 174 | { 175 | Type: ReaderExecutedAsync, 176 | HasTransaction: false, 177 | Parameters: { 178 | @s (String?): 13 179 | }, 180 | Text: 181 | select c.Id, 182 | c.Name 183 | from Companies as c 184 | where c.Name = @s 185 | }, 186 | { 187 | Type: ReaderExecutedAsync, 188 | HasTransaction: false, 189 | Parameters: { 190 | @s (String?): 14 191 | }, 192 | Text: 193 | select c.Id, 194 | c.Name 195 | from Companies as c 196 | where c.Name = @s 197 | }, 198 | { 199 | Type: ReaderExecutedAsync, 200 | HasTransaction: false, 201 | Parameters: { 202 | @s (String?): 15 203 | }, 204 | Text: 205 | select c.Id, 206 | c.Name 207 | from Companies as c 208 | where c.Name = @s 209 | }, 210 | { 211 | Type: ReaderExecutedAsync, 212 | HasTransaction: false, 213 | Parameters: { 214 | @s (String?): 16 215 | }, 216 | Text: 217 | select c.Id, 218 | c.Name 219 | from Companies as c 220 | where c.Name = @s 221 | }, 222 | { 223 | Type: ReaderExecutedAsync, 224 | HasTransaction: false, 225 | Parameters: { 226 | @s (String?): 17 227 | }, 228 | Text: 229 | select c.Id, 230 | c.Name 231 | from Companies as c 232 | where c.Name = @s 233 | }, 234 | { 235 | Type: ReaderExecutedAsync, 236 | HasTransaction: false, 237 | Parameters: { 238 | @s (String?): 18 239 | }, 240 | Text: 241 | select c.Id, 242 | c.Name 243 | from Companies as c 244 | where c.Name = @s 245 | }, 246 | { 247 | Type: ReaderExecutedAsync, 248 | HasTransaction: false, 249 | Parameters: { 250 | @s (String?): 19 251 | }, 252 | Text: 253 | select c.Id, 254 | c.Name 255 | from Companies as c 256 | where c.Name = @s 257 | }, 258 | { 259 | Type: ReaderExecutedAsync, 260 | HasTransaction: false, 261 | Parameters: { 262 | @s (String?): 20 263 | }, 264 | Text: 265 | select c.Id, 266 | c.Name 267 | from Companies as c 268 | where c.Name = @s 269 | }, 270 | { 271 | Type: ReaderExecutedAsync, 272 | HasTransaction: false, 273 | Parameters: { 274 | @s (String?): 21 275 | }, 276 | Text: 277 | select c.Id, 278 | c.Name 279 | from Companies as c 280 | where c.Name = @s 281 | }, 282 | { 283 | Type: ReaderExecutedAsync, 284 | HasTransaction: false, 285 | Parameters: { 286 | @s (String?): 22 287 | }, 288 | Text: 289 | select c.Id, 290 | c.Name 291 | from Companies as c 292 | where c.Name = @s 293 | }, 294 | { 295 | Type: ReaderExecutedAsync, 296 | HasTransaction: false, 297 | Parameters: { 298 | @s (String?): 23 299 | }, 300 | Text: 301 | select c.Id, 302 | c.Name 303 | from Companies as c 304 | where c.Name = @s 305 | }, 306 | { 307 | Type: ReaderExecutedAsync, 308 | HasTransaction: false, 309 | Parameters: { 310 | @s (String?): 24 311 | }, 312 | Text: 313 | select c.Id, 314 | c.Name 315 | from Companies as c 316 | where c.Name = @s 317 | }, 318 | { 319 | Type: ReaderExecutedAsync, 320 | HasTransaction: false, 321 | Parameters: { 322 | @s (String?): 25 323 | }, 324 | Text: 325 | select c.Id, 326 | c.Name 327 | from Companies as c 328 | where c.Name = @s 329 | }, 330 | { 331 | Type: ReaderExecutedAsync, 332 | HasTransaction: false, 333 | Parameters: { 334 | @s (String?): 26 335 | }, 336 | Text: 337 | select c.Id, 338 | c.Name 339 | from Companies as c 340 | where c.Name = @s 341 | }, 342 | { 343 | Type: ReaderExecutedAsync, 344 | HasTransaction: false, 345 | Parameters: { 346 | @s (String?): 27 347 | }, 348 | Text: 349 | select c.Id, 350 | c.Name 351 | from Companies as c 352 | where c.Name = @s 353 | }, 354 | { 355 | Type: ReaderExecutedAsync, 356 | HasTransaction: false, 357 | Parameters: { 358 | @s (String?): 28 359 | }, 360 | Text: 361 | select c.Id, 362 | c.Name 363 | from Companies as c 364 | where c.Name = @s 365 | }, 366 | { 367 | Type: ReaderExecutedAsync, 368 | HasTransaction: false, 369 | Parameters: { 370 | @s (String?): 29 371 | }, 372 | Text: 373 | select c.Id, 374 | c.Name 375 | from Companies as c 376 | where c.Name = @s 377 | }, 378 | { 379 | Type: ReaderExecutedAsync, 380 | HasTransaction: false, 381 | Parameters: { 382 | @s (String?): 30 383 | }, 384 | Text: 385 | select c.Id, 386 | c.Name 387 | from Companies as c 388 | where c.Name = @s 389 | }, 390 | { 391 | Type: ReaderExecutedAsync, 392 | HasTransaction: false, 393 | Parameters: { 394 | @s (String?): 31 395 | }, 396 | Text: 397 | select c.Id, 398 | c.Name 399 | from Companies as c 400 | where c.Name = @s 401 | }, 402 | { 403 | Type: ReaderExecutedAsync, 404 | HasTransaction: false, 405 | Parameters: { 406 | @s (String?): 32 407 | }, 408 | Text: 409 | select c.Id, 410 | c.Name 411 | from Companies as c 412 | where c.Name = @s 413 | }, 414 | { 415 | Type: ReaderExecutedAsync, 416 | HasTransaction: false, 417 | Parameters: { 418 | @s (String?): 33 419 | }, 420 | Text: 421 | select c.Id, 422 | c.Name 423 | from Companies as c 424 | where c.Name = @s 425 | }, 426 | { 427 | Type: ReaderExecutedAsync, 428 | HasTransaction: false, 429 | Parameters: { 430 | @s (String?): 34 431 | }, 432 | Text: 433 | select c.Id, 434 | c.Name 435 | from Companies as c 436 | where c.Name = @s 437 | }, 438 | { 439 | Type: ReaderExecutedAsync, 440 | HasTransaction: false, 441 | Parameters: { 442 | @s (String?): 35 443 | }, 444 | Text: 445 | select c.Id, 446 | c.Name 447 | from Companies as c 448 | where c.Name = @s 449 | }, 450 | { 451 | Type: ReaderExecutedAsync, 452 | HasTransaction: false, 453 | Parameters: { 454 | @s (String?): 36 455 | }, 456 | Text: 457 | select c.Id, 458 | c.Name 459 | from Companies as c 460 | where c.Name = @s 461 | }, 462 | { 463 | Type: ReaderExecutedAsync, 464 | HasTransaction: false, 465 | Parameters: { 466 | @s (String?): 37 467 | }, 468 | Text: 469 | select c.Id, 470 | c.Name 471 | from Companies as c 472 | where c.Name = @s 473 | }, 474 | { 475 | Type: ReaderExecutedAsync, 476 | HasTransaction: false, 477 | Parameters: { 478 | @s (String?): 38 479 | }, 480 | Text: 481 | select c.Id, 482 | c.Name 483 | from Companies as c 484 | where c.Name = @s 485 | }, 486 | { 487 | Type: ReaderExecutedAsync, 488 | HasTransaction: false, 489 | Parameters: { 490 | @s (String?): 39 491 | }, 492 | Text: 493 | select c.Id, 494 | c.Name 495 | from Companies as c 496 | where c.Name = @s 497 | }, 498 | { 499 | Type: ReaderExecutedAsync, 500 | HasTransaction: false, 501 | Parameters: { 502 | @s (String?): 40 503 | }, 504 | Text: 505 | select c.Id, 506 | c.Name 507 | from Companies as c 508 | where c.Name = @s 509 | }, 510 | { 511 | Type: ReaderExecutedAsync, 512 | HasTransaction: false, 513 | Parameters: { 514 | @s (String?): 41 515 | }, 516 | Text: 517 | select c.Id, 518 | c.Name 519 | from Companies as c 520 | where c.Name = @s 521 | }, 522 | { 523 | Type: ReaderExecutedAsync, 524 | HasTransaction: false, 525 | Parameters: { 526 | @s (String?): 42 527 | }, 528 | Text: 529 | select c.Id, 530 | c.Name 531 | from Companies as c 532 | where c.Name = @s 533 | }, 534 | { 535 | Type: ReaderExecutedAsync, 536 | HasTransaction: false, 537 | Parameters: { 538 | @s (String?): 43 539 | }, 540 | Text: 541 | select c.Id, 542 | c.Name 543 | from Companies as c 544 | where c.Name = @s 545 | }, 546 | { 547 | Type: ReaderExecutedAsync, 548 | HasTransaction: false, 549 | Parameters: { 550 | @s (String?): 44 551 | }, 552 | Text: 553 | select c.Id, 554 | c.Name 555 | from Companies as c 556 | where c.Name = @s 557 | }, 558 | { 559 | Type: ReaderExecutedAsync, 560 | HasTransaction: false, 561 | Parameters: { 562 | @s (String?): 45 563 | }, 564 | Text: 565 | select c.Id, 566 | c.Name 567 | from Companies as c 568 | where c.Name = @s 569 | }, 570 | { 571 | Type: ReaderExecutedAsync, 572 | HasTransaction: false, 573 | Parameters: { 574 | @s (String?): 46 575 | }, 576 | Text: 577 | select c.Id, 578 | c.Name 579 | from Companies as c 580 | where c.Name = @s 581 | }, 582 | { 583 | Type: ReaderExecutedAsync, 584 | HasTransaction: false, 585 | Parameters: { 586 | @s (String?): 47 587 | }, 588 | Text: 589 | select c.Id, 590 | c.Name 591 | from Companies as c 592 | where c.Name = @s 593 | }, 594 | { 595 | Type: ReaderExecutedAsync, 596 | HasTransaction: false, 597 | Parameters: { 598 | @s (String?): 48 599 | }, 600 | Text: 601 | select c.Id, 602 | c.Name 603 | from Companies as c 604 | where c.Name = @s 605 | }, 606 | { 607 | Type: ReaderExecutedAsync, 608 | HasTransaction: false, 609 | Parameters: { 610 | @s (String?): 49 611 | }, 612 | Text: 613 | select c.Id, 614 | c.Name 615 | from Companies as c 616 | where c.Name = @s 617 | }, 618 | { 619 | Type: ReaderExecutedAsync, 620 | HasTransaction: false, 621 | Parameters: { 622 | @s (String?): 50 623 | }, 624 | Text: 625 | select c.Id, 626 | c.Name 627 | from Companies as c 628 | where c.Name = @s 629 | }, 630 | { 631 | Type: ReaderExecutedAsync, 632 | HasTransaction: false, 633 | Parameters: { 634 | @s (String?): 51 635 | }, 636 | Text: 637 | select c.Id, 638 | c.Name 639 | from Companies as c 640 | where c.Name = @s 641 | }, 642 | { 643 | Type: ReaderExecutedAsync, 644 | HasTransaction: false, 645 | Parameters: { 646 | @s (String?): 52 647 | }, 648 | Text: 649 | select c.Id, 650 | c.Name 651 | from Companies as c 652 | where c.Name = @s 653 | }, 654 | { 655 | Type: ReaderExecutedAsync, 656 | HasTransaction: false, 657 | Parameters: { 658 | @s (String?): 53 659 | }, 660 | Text: 661 | select c.Id, 662 | c.Name 663 | from Companies as c 664 | where c.Name = @s 665 | }, 666 | { 667 | Type: ReaderExecutedAsync, 668 | HasTransaction: false, 669 | Parameters: { 670 | @s (String?): 54 671 | }, 672 | Text: 673 | select c.Id, 674 | c.Name 675 | from Companies as c 676 | where c.Name = @s 677 | }, 678 | { 679 | Type: ReaderExecutedAsync, 680 | HasTransaction: false, 681 | Parameters: { 682 | @s (String?): 55 683 | }, 684 | Text: 685 | select c.Id, 686 | c.Name 687 | from Companies as c 688 | where c.Name = @s 689 | }, 690 | { 691 | Type: ReaderExecutedAsync, 692 | HasTransaction: false, 693 | Parameters: { 694 | @s (String?): 56 695 | }, 696 | Text: 697 | select c.Id, 698 | c.Name 699 | from Companies as c 700 | where c.Name = @s 701 | }, 702 | { 703 | Type: ReaderExecutedAsync, 704 | HasTransaction: false, 705 | Parameters: { 706 | @s (String?): 57 707 | }, 708 | Text: 709 | select c.Id, 710 | c.Name 711 | from Companies as c 712 | where c.Name = @s 713 | }, 714 | { 715 | Type: ReaderExecutedAsync, 716 | HasTransaction: false, 717 | Parameters: { 718 | @s (String?): 58 719 | }, 720 | Text: 721 | select c.Id, 722 | c.Name 723 | from Companies as c 724 | where c.Name = @s 725 | }, 726 | { 727 | Type: ReaderExecutedAsync, 728 | HasTransaction: false, 729 | Parameters: { 730 | @s (String?): 59 731 | }, 732 | Text: 733 | select c.Id, 734 | c.Name 735 | from Companies as c 736 | where c.Name = @s 737 | }, 738 | { 739 | Type: ReaderExecutedAsync, 740 | HasTransaction: false, 741 | Parameters: { 742 | @s (String?): 60 743 | }, 744 | Text: 745 | select c.Id, 746 | c.Name 747 | from Companies as c 748 | where c.Name = @s 749 | }, 750 | { 751 | Type: ReaderExecutedAsync, 752 | HasTransaction: false, 753 | Parameters: { 754 | @s (String?): 61 755 | }, 756 | Text: 757 | select c.Id, 758 | c.Name 759 | from Companies as c 760 | where c.Name = @s 761 | }, 762 | { 763 | Type: ReaderExecutedAsync, 764 | HasTransaction: false, 765 | Parameters: { 766 | @s (String?): 62 767 | }, 768 | Text: 769 | select c.Id, 770 | c.Name 771 | from Companies as c 772 | where c.Name = @s 773 | }, 774 | { 775 | Type: ReaderExecutedAsync, 776 | HasTransaction: false, 777 | Parameters: { 778 | @s (String?): 63 779 | }, 780 | Text: 781 | select c.Id, 782 | c.Name 783 | from Companies as c 784 | where c.Name = @s 785 | }, 786 | { 787 | Type: ReaderExecutedAsync, 788 | HasTransaction: false, 789 | Parameters: { 790 | @s (String?): 64 791 | }, 792 | Text: 793 | select c.Id, 794 | c.Name 795 | from Companies as c 796 | where c.Name = @s 797 | }, 798 | { 799 | Type: ReaderExecutedAsync, 800 | HasTransaction: false, 801 | Parameters: { 802 | @s (String?): 65 803 | }, 804 | Text: 805 | select c.Id, 806 | c.Name 807 | from Companies as c 808 | where c.Name = @s 809 | }, 810 | { 811 | Type: ReaderExecutedAsync, 812 | HasTransaction: false, 813 | Parameters: { 814 | @s (String?): 66 815 | }, 816 | Text: 817 | select c.Id, 818 | c.Name 819 | from Companies as c 820 | where c.Name = @s 821 | }, 822 | { 823 | Type: ReaderExecutedAsync, 824 | HasTransaction: false, 825 | Parameters: { 826 | @s (String?): 67 827 | }, 828 | Text: 829 | select c.Id, 830 | c.Name 831 | from Companies as c 832 | where c.Name = @s 833 | }, 834 | { 835 | Type: ReaderExecutedAsync, 836 | HasTransaction: false, 837 | Parameters: { 838 | @s (String?): 68 839 | }, 840 | Text: 841 | select c.Id, 842 | c.Name 843 | from Companies as c 844 | where c.Name = @s 845 | }, 846 | { 847 | Type: ReaderExecutedAsync, 848 | HasTransaction: false, 849 | Parameters: { 850 | @s (String?): 69 851 | }, 852 | Text: 853 | select c.Id, 854 | c.Name 855 | from Companies as c 856 | where c.Name = @s 857 | }, 858 | { 859 | Type: ReaderExecutedAsync, 860 | HasTransaction: false, 861 | Parameters: { 862 | @s (String?): 70 863 | }, 864 | Text: 865 | select c.Id, 866 | c.Name 867 | from Companies as c 868 | where c.Name = @s 869 | }, 870 | { 871 | Type: ReaderExecutedAsync, 872 | HasTransaction: false, 873 | Parameters: { 874 | @s (String?): 71 875 | }, 876 | Text: 877 | select c.Id, 878 | c.Name 879 | from Companies as c 880 | where c.Name = @s 881 | }, 882 | { 883 | Type: ReaderExecutedAsync, 884 | HasTransaction: false, 885 | Parameters: { 886 | @s (String?): 72 887 | }, 888 | Text: 889 | select c.Id, 890 | c.Name 891 | from Companies as c 892 | where c.Name = @s 893 | }, 894 | { 895 | Type: ReaderExecutedAsync, 896 | HasTransaction: false, 897 | Parameters: { 898 | @s (String?): 73 899 | }, 900 | Text: 901 | select c.Id, 902 | c.Name 903 | from Companies as c 904 | where c.Name = @s 905 | }, 906 | { 907 | Type: ReaderExecutedAsync, 908 | HasTransaction: false, 909 | Parameters: { 910 | @s (String?): 74 911 | }, 912 | Text: 913 | select c.Id, 914 | c.Name 915 | from Companies as c 916 | where c.Name = @s 917 | }, 918 | { 919 | Type: ReaderExecutedAsync, 920 | HasTransaction: false, 921 | Parameters: { 922 | @s (String?): 75 923 | }, 924 | Text: 925 | select c.Id, 926 | c.Name 927 | from Companies as c 928 | where c.Name = @s 929 | }, 930 | { 931 | Type: ReaderExecutedAsync, 932 | HasTransaction: false, 933 | Parameters: { 934 | @s (String?): 76 935 | }, 936 | Text: 937 | select c.Id, 938 | c.Name 939 | from Companies as c 940 | where c.Name = @s 941 | }, 942 | { 943 | Type: ReaderExecutedAsync, 944 | HasTransaction: false, 945 | Parameters: { 946 | @s (String?): 77 947 | }, 948 | Text: 949 | select c.Id, 950 | c.Name 951 | from Companies as c 952 | where c.Name = @s 953 | }, 954 | { 955 | Type: ReaderExecutedAsync, 956 | HasTransaction: false, 957 | Parameters: { 958 | @s (String?): 78 959 | }, 960 | Text: 961 | select c.Id, 962 | c.Name 963 | from Companies as c 964 | where c.Name = @s 965 | }, 966 | { 967 | Type: ReaderExecutedAsync, 968 | HasTransaction: false, 969 | Parameters: { 970 | @s (String?): 79 971 | }, 972 | Text: 973 | select c.Id, 974 | c.Name 975 | from Companies as c 976 | where c.Name = @s 977 | }, 978 | { 979 | Type: ReaderExecutedAsync, 980 | HasTransaction: false, 981 | Parameters: { 982 | @s (String?): 80 983 | }, 984 | Text: 985 | select c.Id, 986 | c.Name 987 | from Companies as c 988 | where c.Name = @s 989 | }, 990 | { 991 | Type: ReaderExecutedAsync, 992 | HasTransaction: false, 993 | Parameters: { 994 | @s (String?): 81 995 | }, 996 | Text: 997 | select c.Id, 998 | c.Name 999 | from Companies as c 1000 | where c.Name = @s 1001 | }, 1002 | { 1003 | Type: ReaderExecutedAsync, 1004 | HasTransaction: false, 1005 | Parameters: { 1006 | @s (String?): 82 1007 | }, 1008 | Text: 1009 | select c.Id, 1010 | c.Name 1011 | from Companies as c 1012 | where c.Name = @s 1013 | }, 1014 | { 1015 | Type: ReaderExecutedAsync, 1016 | HasTransaction: false, 1017 | Parameters: { 1018 | @s (String?): 83 1019 | }, 1020 | Text: 1021 | select c.Id, 1022 | c.Name 1023 | from Companies as c 1024 | where c.Name = @s 1025 | }, 1026 | { 1027 | Type: ReaderExecutedAsync, 1028 | HasTransaction: false, 1029 | Parameters: { 1030 | @s (String?): 84 1031 | }, 1032 | Text: 1033 | select c.Id, 1034 | c.Name 1035 | from Companies as c 1036 | where c.Name = @s 1037 | }, 1038 | { 1039 | Type: ReaderExecutedAsync, 1040 | HasTransaction: false, 1041 | Parameters: { 1042 | @s (String?): 85 1043 | }, 1044 | Text: 1045 | select c.Id, 1046 | c.Name 1047 | from Companies as c 1048 | where c.Name = @s 1049 | }, 1050 | { 1051 | Type: ReaderExecutedAsync, 1052 | HasTransaction: false, 1053 | Parameters: { 1054 | @s (String?): 86 1055 | }, 1056 | Text: 1057 | select c.Id, 1058 | c.Name 1059 | from Companies as c 1060 | where c.Name = @s 1061 | }, 1062 | { 1063 | Type: ReaderExecutedAsync, 1064 | HasTransaction: false, 1065 | Parameters: { 1066 | @s (String?): 87 1067 | }, 1068 | Text: 1069 | select c.Id, 1070 | c.Name 1071 | from Companies as c 1072 | where c.Name = @s 1073 | }, 1074 | { 1075 | Type: ReaderExecutedAsync, 1076 | HasTransaction: false, 1077 | Parameters: { 1078 | @s (String?): 88 1079 | }, 1080 | Text: 1081 | select c.Id, 1082 | c.Name 1083 | from Companies as c 1084 | where c.Name = @s 1085 | }, 1086 | { 1087 | Type: ReaderExecutedAsync, 1088 | HasTransaction: false, 1089 | Parameters: { 1090 | @s (String?): 89 1091 | }, 1092 | Text: 1093 | select c.Id, 1094 | c.Name 1095 | from Companies as c 1096 | where c.Name = @s 1097 | }, 1098 | { 1099 | Type: ReaderExecutedAsync, 1100 | HasTransaction: false, 1101 | Parameters: { 1102 | @s (String?): 90 1103 | }, 1104 | Text: 1105 | select c.Id, 1106 | c.Name 1107 | from Companies as c 1108 | where c.Name = @s 1109 | }, 1110 | { 1111 | Type: ReaderExecutedAsync, 1112 | HasTransaction: false, 1113 | Parameters: { 1114 | @s (String?): 91 1115 | }, 1116 | Text: 1117 | select c.Id, 1118 | c.Name 1119 | from Companies as c 1120 | where c.Name = @s 1121 | }, 1122 | { 1123 | Type: ReaderExecutedAsync, 1124 | HasTransaction: false, 1125 | Parameters: { 1126 | @s (String?): 92 1127 | }, 1128 | Text: 1129 | select c.Id, 1130 | c.Name 1131 | from Companies as c 1132 | where c.Name = @s 1133 | }, 1134 | { 1135 | Type: ReaderExecutedAsync, 1136 | HasTransaction: false, 1137 | Parameters: { 1138 | @s (String?): 93 1139 | }, 1140 | Text: 1141 | select c.Id, 1142 | c.Name 1143 | from Companies as c 1144 | where c.Name = @s 1145 | }, 1146 | { 1147 | Type: ReaderExecutedAsync, 1148 | HasTransaction: false, 1149 | Parameters: { 1150 | @s (String?): 94 1151 | }, 1152 | Text: 1153 | select c.Id, 1154 | c.Name 1155 | from Companies as c 1156 | where c.Name = @s 1157 | }, 1158 | { 1159 | Type: ReaderExecutedAsync, 1160 | HasTransaction: false, 1161 | Parameters: { 1162 | @s (String?): 95 1163 | }, 1164 | Text: 1165 | select c.Id, 1166 | c.Name 1167 | from Companies as c 1168 | where c.Name = @s 1169 | }, 1170 | { 1171 | Type: ReaderExecutedAsync, 1172 | HasTransaction: false, 1173 | Parameters: { 1174 | @s (String?): 96 1175 | }, 1176 | Text: 1177 | select c.Id, 1178 | c.Name 1179 | from Companies as c 1180 | where c.Name = @s 1181 | }, 1182 | { 1183 | Type: ReaderExecutedAsync, 1184 | HasTransaction: false, 1185 | Parameters: { 1186 | @s (String?): 97 1187 | }, 1188 | Text: 1189 | select c.Id, 1190 | c.Name 1191 | from Companies as c 1192 | where c.Name = @s 1193 | }, 1194 | { 1195 | Type: ReaderExecutedAsync, 1196 | HasTransaction: false, 1197 | Parameters: { 1198 | @s (String?): 98 1199 | }, 1200 | Text: 1201 | select c.Id, 1202 | c.Name 1203 | from Companies as c 1204 | where c.Name = @s 1205 | }, 1206 | { 1207 | Type: ReaderExecutedAsync, 1208 | HasTransaction: false, 1209 | Parameters: { 1210 | @s (String?): 99 1211 | }, 1212 | Text: 1213 | select c.Id, 1214 | c.Name 1215 | from Companies as c 1216 | where c.Name = @s 1217 | }, 1218 | { 1219 | Type: ReaderExecutedAsync, 1220 | HasTransaction: false, 1221 | Parameters: { 1222 | @p0 (Int32): 2, 1223 | @p1 (String): Title2 1224 | }, 1225 | Text: 1226 | set implicit_transactions off; 1227 | 1228 | set nocount on; 1229 | 1230 | insert into Companies (Id, Name) 1231 | values (@p0, @p1) 1232 | } 1233 | ] 1234 | } --------------------------------------------------------------------------------