首页 > 其他分享 >彻底理解EF Core(5)的运行机制,万字长文带你成长为团队中的EF Core专家

彻底理解EF Core(5)的运行机制,万字长文带你成长为团队中的EF Core专家

时间:2023-05-21 21:05:08浏览次数:52  
标签:Core EF 查询 文带 command SQL Id


彻底理解EF Core的运行机制,万字长文带你成长为团队中的EF Core专家

  • 1、将EF的ToTraceString移植为EF Core的ToQueryString
  • 2、从EF Core记录详细信息
  • 2.1、 简单的日志记录
  • 2.2、响应EF Core 事件
  • 2.3、使用事件计数器访问指标
  • 3、拦截EF Core的数据——拦截器
  • 4、查询拦截
  • 5、EF Core 5中的Sleeper功能:调试视图
  • 6、利用

  在EF Core 5中,有很多方式可以窥察工作流程中发生的事情,并与该信息进行交互。这些功能点包括日志记录,拦截,事件处理程序和一些超酷的最新出现的调试功能。EF团队甚至从Entity Framework的第一个版本中恢复了一些有用的旧的功能。
  本文带你更深入地研究访问EF Core 5的一些元数据和其有趣的使用方式。

1、将EF的ToTraceString移植为EF Core的ToQueryString

  在Entity Framework的第一个迭代版本中,没有内置的日志记录。但是有ObjectQuery.ToTraceString(),这是一种运行时方法,可以动态计算LINQ或Entity SQL查询的SQL,尽管这不是一个很好的日志记录方法,但它毕竟可以输出SQL,即使在今天,也有一些有用的场景。

  直到最新版本EF Core 5,该功能才成为EF Core的一部分,并且已重命名为ToQueryString()

  如果要查看实体类People的简单查询所生成的SQL,只需将 ToQueryString 附加到查询中即可。不涉及LINQ执行方法。

  换句话说,将查询本身与执行方法分开,仅仅针对查询。
  var sqlFromQuery=context.People.ToQueryString();

  ToQueryString()的一个有趣用例是在调试时查看其结果,不必等到运行方法即可检查日志中的SQL。例如我可以构建查询,捕获字符串,然后执行查询。

private static void GetAllPeople()
{
    using var context = new PeopleContext();
    var query = context.People.Where(p=>p.FirstName=="Julie");
    var sqlFromQuery = query.ToQueryString();
    var people = query.ToList();
}

  然后在调试时,可以看到sqlFromQuery变量的预期SQL。当然您不需要将此代码嵌入生产代码中。实际上,也非常不建议这样做,因为当EF Core进行SQL编制过程时,它很容易影响性能

您应该可以在调试器中调用ToQueryString(),如图所示。

彻底理解EF Core(5)的运行机制,万字长文带你成长为团队中的EF Core专家_C#


  在调用ToQueryString之前,查询变量已经作为 DbQuery 进行了评估,因此可以正常工作。

  调试上下文并在调试器中直接显示DbSet,例如在调试器中成功运行context.People.ToQueryString(),但是您不能直接评估LINQ表达式。换句话说,如果要调试上下文变量,然后在调试器中使用Where方法,它将失败。这并不是什么新鲜事物,也不是ToQueryString的限制。

  关于ToQueryString()的最后一个要点是对它的评估基于最简单的执行:ToList()。使用诸如FirstOrDefault()之类的 LINQ 执行查询会影响SQL的呈现方式,因此,在使用FirstOrDefault()执行查询时,ToQueryString()呈现的SQL与发送给数据库的SQL不同。这种情况下需要 EF Core日志记录来打印准确的 SQL,而不是还执拗于ToQueryString()

  我发现在集成测试场景下,ToQueryString()特别有用。如果您需要编写测试,测试的成功取决于生成的SQL表达式的某些部分,那么ToQueryString()是比日志记录更简单的路径。使用日志记录时,您必须将日志捕获到文本编写器中,然后读取该文本。尽管使用InMemory提供程序可能很诱人,但请记住,InMemory提供程序不会生成SQL。您需要为真实数据库使用提供程序,数据库不需要存在即可使用ToQueryString()。EF Core在内存中才能确定SQL。

  这是一个演示测试示例,旨在证明EF Core编写的智能SQL比我编写的更为智能。请注意,我在测试项目中引用了Microsoft.EntityFrameworkCore.Sqlite提供程序。如您所知,EF和EF Core总是投影与实体属性相关的列。它不写SELECT *。

[TestMethod]
public void SQLDoesNotContainSelectStar()
{
   var builder = new DbContextOptionsBuilder();
   builder.UseSqlite("Data Source=testdb.db");
   using var context = new PeopleContext(builder.Options);
   var sql=context.People.ToQueryString();
   Assert.IsFalse(sql.ToUpper().Contains("SELECT *"));
}

  如果您使用拦截器来执行软删除,并且使用全局查询过滤器来始终过滤出这些行。例如,这是我DbContext OnModelBuildling()方法中的一个查询过滤器,它告诉EF Core过滤掉IsDeleted属性为true的Person行。
  modelBuilder.Entity<Person>().HasQueryFilter(p => !p.IsDeleted);

  有了这个,我可以编写与上面类似的测试,但是将断言更改为以下内容,以确保我不会破坏全局查询过滤器逻辑。
  Assert.IsTrue(sql.ToUpper().Contains("WHERE NOT (\"p\".\"IsDeleted\")"));

2、从EF Core记录详细信息

  • 共有三种方法可以利用EF Core的日志管道。

2.1、 简单的日志记录

  可以与.NET的日志记录API结合使用,所有的繁重辛苦的工作都是在后台进行的。您可以使用LogTo方法轻松配置DbContext,将.NET日志记录输出。

EF Core将输出很多事件。分为以下类,这些类从DbCloggerCategory派生。

  • 变更追踪,ChangeTracking
  • 数据库命令,Database.Command
  • 数据库连接,Database.Connection
  • 数据库事务,Database.Transaction
  • 数据库,Database
  • 基础设施,Infrastructure
  • 移居,Migrations
  • 模型验证,Model.Validation
  • 模型,Model
  • 询问,Query
  • 脚手架,Scaffolding
  • 更新,Update

    您可以使用这些类别将输出筛选为要记录的信息类型

  LogTo的一个参数指定目标为控制台窗口、文件或调试窗口。
然后,第二个参数允许您通过.NET LogLevel 以及您感兴趣的任何 DLoggerCategoy 进行筛选。

  此示例配置DbContext将日志输出到控制台,并过滤掉所有DbLoggerCategory类型LogLevel.Information组。
  optionsBuilder.UseSqlServer(myConnectionString).LogTo(Console.WriteLine,LogLevel.Information);

  下面一个LogTo方法添加了第三个参数-DbLoggerCatetory数组(仅包含一个数组),以便仅对EF Core的数据库命令进行进一步过滤。

  与LogTo方法一起,我添加了EnableSensitiveDataLogging()方法以在SQL中显示传入参数。这将捕获所有发送到数据库的SQL:查询,更新,原始SQL甚至通过迁移发送的更改。

info: 27/5/2021 20:34:09.935
    RelationalEventId.CommandExecuted[20101]
    (Microsoft.EntityFrameworkCore.Database.Command)

Executed DbCommand (22ms) [Parameters=[ 
    @p0='Julie' (Size = 4000), @p1='False', 
    @p2='Lerman' (Size = 4000)],CommandType='Text',
    CommandTimeout='30']

    SET NOCOUNT ON;
    INSERT INTO [People] ([FirstName],[IsDeleted], [LastName])
    VALUES (@p0, @p1, @p2);
    SELECT [Id]
    FROM [People]
    WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();

  日志记录显示信息类型、EventId、以及请求的记录器类别的详细信息。接下来,日志名称,执行时间和参数列表。由于启用了敏感数据记录,因此将显示参数。最后,它列出了发送到数据库的SQL。

  LogTo使EF Core输出基本日志记录变得容易。您可以在DbContext,ASP.NET Core应用程序的启动文件或ASP.NET Core应用程序的应用程序配置文件中对其进行配置。

  注意顶部的EventId。您甚至可以定义日志记录以使用这些ID过滤特定事件。您还可以过滤出特定的日志类别,并且可以控制格式。查看有关这些各种功能的更多详细信息的文档

  简单日志记录是记录EF Core的高级方法,是的,高级的就是简单的,这就是计算机世界的定义!

  您也可以通过直接与Microsoft.Extensions.Logging一起,以对EF Core的日志方式进行更多控制。检查EF Core文档,以获取更多有关使用此更高级用法的详细信息

2.2、响应EF Core 事件

  EF Core 2.1在EF Core管道中引入了.NET事件。开始只有两个:ChangeTracker.Tracked(在DbContext开始跟踪实体时引发)和ChangeTracker.StateChanged(在已跟踪的实体的状态改变时引发)

  有了基本逻辑,团队向EF Core 5添加三个事件:SaveChangesFailedSaveChangesSaveChangesAsync

  当上下文将要保存更改时,将引发DbContext.SavingChanges
在两个保存更改方法中的任何一个成功完成之后,将引发DbContext.SavedChanges
DbContext.SaveChangesFailed用于捕获和检查故障。

  能够分离此逻辑,而不是全部填充到SaveChanges()方法的中,这是一个很好的选择。

  可以使用这些事件来记录未跟踪的备用信息,您甚至可以使用事件来发出记录器无法跟踪的备用信息。

  如果要使用影子属性跟踪审核数据,则可以在构造SQL并将其发送到数据库之前,使用SavingChanges事件更新这些属性。

  例如,我将应用程序设置为向每个实体添加UserId阴影属性(不包括那些属性包和拥有的实体)。当用户登录时,我的应用程序有一个名为Globals.CurrentUserId的静态变量。此外,在我的DbContext类中,我创建了一个名为SetUserId的私有方法,该方法将我的shadow属性(存在的地方)的值设置为CurrentUserId

private void SetUserId(object sender, SavingChangesEventArgs e)
{
   foreach (var entry in ChangeTracker.Entries()
   .Where(entry => entry.Metadata
   .GetProperty("UserId") != null))
   {
       entry.Property("UserId").CurrentValue = Globals.CurrentUserId;
   }
}

  最后,我可以将SetUserId方法连接到DbContext的构造函数中的SavingChanges事件:

public PeopleContext()
{
   SavingChanges += SetUserId;
}

  现在,每当我调用SaveChanges时,UserId就会与其他数据一起持久保存到表中。
这是一些日志数据:

Executed DbCommand (29ms) [Parameters=[
   @p0='Julie' (Size = 4000), @p1='False',  
   @p2='Lerman' (Size = 4000), @p3='101'], 
   CommandType='Text', CommandTimeout='30']

SET NOCOUNT ON;
INSERT INTO [People] ([FirstName],[IsDeleted], [LastName], [UserId])
VALUES (@p0, @p1, @p2, @p3);
SELECT [Id]
FROM [People]
WHERE @@ROWCOUNT = 1
   AND [Id] = scope_identity();

这只是利用这些事件的一种简单方法。

2.3、使用事件计数器访问指标

  EF Core 5利用了.NET Core 3.0中.NET引入的一项很酷的功能-dotnet-counters (调查性能计数器)。计数器是一个全局命令行工具。您可以使用dotnet CLI安装此工具。dotnet tool install --global dotnet-counters

  安装完成后,您可以告诉它监视在dotnet环境中运行的进程。您需要提供正在运行的.NET应用程序的进程ID

  System.Diagnostics.Process.GetCurrentProcess().Id  在Visual Studio中,我无法简单地在调试器中调试此值。调试器只会告诉您“此表达式会产生副作用,因此不会被评估。” 因此,我将其嵌入到我的代码中并获得了值313131

  在拥有ID且应用仍在运行的情况下,然后可以触发计数器,开始监视来自 Microsoft.EntityFramework 命名空间的事件。如下:
  dotnet counters monitor Microsoft.EntityFrameworkCore -p 313131

  然后,当您遍历应用程序时,计数器将显示EF Core统计信息的特定列表,如图所示,然后在应用程序执行其功能时更新计数。

  我仅监视了一个小型演示应用程序,因此计数并不是很好看,但是您可以看到我正在运行一个DbContext实例(Active DbContexts),我已经运行了三个查询,并利用了查询缓存(因为我运行了其中一些查询不止一次),并两次调用SaveChanges。

彻底理解EF Core(5)的运行机制,万字长文带你成长为团队中的EF Core专家_EF5_02


  这看起来像您的代码分析工具,但是当针对更密集的解决方案运行时,它肯定会更有用。EF团队建议您在文档中仔细阅读 dotnet-counters 功能,以便正确使用EF Core。

3、拦截EF Core的数据——拦截器

  EF Core的拦截器是一项功能,该功能始于EF6,并在EF Core 3中重新引入。EF Core 5中引入了SaveChanges的新拦截器。

  由于此功能已经存在很长时间了(尽管它对于EF Core是相当新的),因此应该有很多文章介绍。即使这样,我还是觉得很神奇。

  共有三种不同的拦截器类来拦截 命令,连接和事务,以及用于SaveChanges的新拦截器类。每个类都有自己相关的虚拟方法(和相关对象)。例如,DbCommandInterceptor公开了ReaderExecutingReaderExecutingAsync,它们在命令即将发送到数据库时被触发。

public override InterceptionResult<DbDataReader>
   ReaderExecuting(
       DbCommand command,
       CommandEventData eventData,
       InterceptionResult<DbDataReader> result)
{
	//例如,webmote支持你干点啥?
	return result;
}

它的参数之一是DbCommand,其CommandText属性保存SQL。

如果要修改SQL,添加查询提示或其他任务,则可以更改命令,然后使用新CommandText值的命令将继续进行。

从数据库返回任何结果数据时,将触发ReaderExecuted / Async方法。

public override DbDataReader ReaderExecuted(
    DbCommand command,
    CommandExecutedEventData eventData,
    DbDataReader result)
	{
        return base.ReaderExecuted
        (command, eventData, result);
    }

例如,在这里您可以捕获DbDataReader,并对该数据进行某些处理,然后再继续执行EF Core实现。一个示例是记录一些记录器无法捕获的内容,例如:

彻底理解EF Core(5)的运行机制,万字长文带你成长为团队中的EF Core专家_EF_03

4、查询拦截

  EF Core 公开的 DbCommandInterceptor拦截器提供查询拦截功能,查询拦截是在数据库上执行查询之前插入逻辑,或者在查询执行之后(以及控制返回到调用代码之前)立即插入逻辑的能力。

此功能在现实世界中有多种使用案例:

  • 延长具有某些特征的命令的超时
  • 查询失败并记录异常时诊断信息
  • 当读取到内存的行数超过特定阈值时,记录警告

一个小例子:

public class TestQueryInterceptor : DbCommandInterceptor
{
	 // runs before a query is executed
	public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
	{
	    command.CommandText += " OPTION (OPTIMIZE FOR UNKNOWN)";   
   		command.CommandTimeout = 12345;   
  		 return result;
	}
	
	// runs after a query is excuted
	public override DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result)
	{
	    if (this.ShouldChangeResult(command, out var changedResult))
	    {
	        return changedResult;
	    }
	    
	    return result;
	}
}

注意: 大多数方法都有同步和异步版本。令人讨厌的是,异步查询仅触发异步方法(反之亦然),因此在编写拦截器时必须覆盖两者。

安装拦截器是很简单的。

public class SampleDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlite(@"Data Source=Sample.db;")
            .AddInterceptors(new TestQueryInterceptor (), new SampleInterceptor2());
    }
}

  通过返回InterceptionResult<T>.SuppressWithResult()禁止执行。重要的是要注意,DbCommandInterceptor安装的其他所有组件仍将执行,并且可以通过上的HasResult属性检查其他拦截器是否已禁止执行result。

public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> result)
{
    if (this.ShouldSuppressExecution(command))
    {
        return InterceptionResult.SuppressWithResult<object>(null);
    }    
	return result;
}

方法中引发的异常从技术上将阻止执行,不要利用这个事实,将异常用于控制流几乎总是很糟糕的设计。

你可以拦截如下清单的操作:

方法

操作

CommandCreating

在创建命令之前(注意:一切都是命令,因此它将拦截所有查询)

CommandCreated

创建命令之后但执行之前

CommandFailed[Async]

在执行过程中命令失败并出现异常后

ReaderExecuting[Async]

在执行“查询”命令之前

ReaderExecuted[Async]

执行“查询”命令后

NonQueryExecuting[Async]

在执行“非查询”命令之前(注意:非查询的一个示例是 ExecuteSqlRaw)

NonQueryExecuted[Async]

执行“非查询”命令后

ScalarExecuting [Async]

在执行“标量”命令之前(注意:“标量”是存储过程的同义词)

ScalarExecuted [Async]

执行“标量”命令后

DataReaderDispose

执行命令后

这是一个耗时命令拦截

public class MyDBCommandInterceptor: DbCommandInterceptor
{
	public static ConcurrentDictionary CommandStartTimes = new ConcurrentDictionary();
	public static ConcurrentDictionary CommandDurations = new ConcurrentDictionary();

	public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) {
		CommandStartTimes.TryAdd(command, DateTime.Now);
		base.NonQueryExecuting(command, interceptionContext);
	}

	public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) {
		CommandStartTimes.TryAdd(command, DateTime.Now);
		base.ReaderExecuting(command, interceptionContext);
	}

	public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) {
		CommandStartTimes.TryAdd(command, DateTime.Now);
		base.ScalarExecuting(command, interceptionContext);
	}

	public override void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) {
		base.NonQueryExecuted(command, interceptionContext);
		AccumulateTime(command);
	}

	public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) {
		base.ReaderExecuted(command, interceptionContext);
		AccumulateTime(command);
	}

	public override void ScalarExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) {
		base.ScalarExecuted(command, interceptionContext);
		AccumulateTime(command);
	}

	private void AccumulateTime(DbCommand command) {
		if (CommandStartTimes.TryRemove(command, out
		var commandStartTime)) {
			var commandDuration = DateTime.Now - commandStartTime;
			CommandDurations.AddOrUpdate(command.CommandText, commandDuration, (_, accumulated) => commandDuration + accumulated);
		}
	}
}

有关使用EF Core文档中的拦截器的大量指导

5、EF Core 5中的Sleeper功能:调试视图

就是ChangeTracker.DebugViewModel.DebugView

  DebugViews输出格式正确的字符串,其中有ChangeTracker的状态或模型中的元数据的信息。DebugView提供了一个漂亮的文档,您可以捕获和打印该文档,并真正了解其幕后情况。

  我在调试器上花费了大量时间,以探索有关变更跟踪器了解的内容或EF Core如何解释我所描述的模型的各种详细信息。能够以这种文本格式读取此信息,甚至将其保存在文件中,因此您无需反复调试即可收集详细信息,这是EF Core 5的一项神奇功能。

  确保您了解DebugViews是撰写本文的目的。

  在DbContext.ChangeTracker.DebugView中,您将找到ShortView和LongView属性。例如,这里是我刚查询一个Person对象时的视图,而我的上下文仅包含一个人。
  Person {Id: 1} Unchanged

  这是最常用的信息-在我的上下文中,只有一个未更改的Person的ID为1。

  LongView提供了有关被跟踪实体的更多详细信息。

Person {Id: 1} Unchanged
    Id: 1 PK
    FirstName: 'Julie'
    IsDeleted: 'False'
    LastName: 'Lerman'
    UserId: 101
    Addresses: []

  如果要在跟踪的Person上对其进行编辑并强制上下文检测更改,则LongView除了将状态显示为Modified之外,还对LastName属性所做的更改进行记录。

Person {Id: 1} Modified
   Id: 1 PK
   FirstName: 'Julie'
   IsDeleted: 'False'
   LastName: 'Lermantov' Modified
   Originally 'Lerman'
   UserId: 101
   Addresses: []

  您可以在此视图中看到一个Addresses属性。实际上,使用导航,“人”和“地址”之间存在多对多关系。EF Core在运行时推断内存中的PersonAddress实体,以便将关系数据持久化到联接表中。

  当我在其“地址”集合中创建一个具有一个地址的人的图形时,您可以在ShortView中看到一个“人”,一个地址和一个推断的PersonAddress对象。长视图显示了这些对象的属性。

AddressPerson (Dictionary<string, object>)
    {AddressesId: 1, ResidentsId: 1} Unchanged FK
    {AddressesId: 1} FK {ResidentsId: 1}
Address {Id: 1} Unchanged
Person {Id: 1} Modified

  我喜欢这些调试视图,这些视图可以在调试时帮助我发现被跟踪对象的状态和关系,无论我是在解决问题还是在学习它的工作方式。

  让我们转到Model.DebugViews看看您可以从中学到什么。

  首先,我应该阐明我的模型。使用 Visual Studio中的EF Core Power Tools 扩展来可视化模型。

彻底理解EF Core(5)的运行机制,万字长文带你成长为团队中的EF Core专家_EF5_04


DbContext.Model.DebugView也具有ShortView和LongView。它们都包含很多信息。

您可以看到属性,主键和外键,索引以及级联删除规则,多对多关系,甚至指定了它使用跳过导航。还描述了继承。您可以从这份文件中学到很多东西。

  • 清单1:数据模型的Model.DebugView.ShortView
Model:
    EntityType: AddressPerson (Dictionary<string, object>)    
        CLR Type: Dictionary<string, object>
            Properties:
                AddressesId (no field, int) Indexer Required PK FK AfterSave:Throw
                ResidentsId (no field, int) Indexer Required PK FK Index AfterSave:Throw
           Keys:
               AddressesId, ResidentsId PK
           Foreign keys:
               AddressPerson (Dictionary<string, object>) {'AddressesId'} -> Address {'Id'} Cascade
               AddressPerson (Dictionary<string, object>) {'ResidentsId'} -> Person {'Id'} Cascade
           Indexes:
               ResidentsId
    EntityType: Address
        Properties:
            Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
            PostalCode (string)
            Street (string)
            UserId (no field, int) Shadow Required
        Navigations:
            WildlifeSightings (List<WildlifeSighting>) Collection ToDependent WildlifeSighting
        Skip navigations:
            Residents (List<Person>) CollectionPerson        
                Inverse: Addresses
        Keys:
            Id PK
    EntityType: Person
        Properties:
            Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
            FirstName (string)
            IsDeleted (bool) Required
            LastName (string)
            UserId (no field, int) Shadow Required
        Skip navigations:
            Addresses (List<Address>) CollectionAddress        
            Inverse: Residents
        Keys:
            Id PK
    EntityType: WildlifeSighting
        Properties:
            Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
            AddressId (int) Required FK Index
            DateTime (DateTime) Required
            Description (string)
            UserId (no field, int) Shadow Required
        Keys:
            Id PK
        Foreign keys:
            WildlifeSighting {'AddressId'} -> Address {'Id'}        
                ToDependent: WildlifeSightings Cascade
        Indexes:
            AddressId

Model.DebugView.LongView包含更多详细信息,它们描述了注释,数据库映射等。您可以从LongView中学到更多,但并不是每个人都希望看到这种细节,如果您需要,它就在那里。

  • 清单2:在Model.DebugView的LongView中描述的Person实体
EntityType: Person
    Properties:
        Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
            Annotations:
                Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1   
                [Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase]
                Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1   
                [Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]
                    SqlServer:ValueGenerationStrategy: IdentityColumn
        FirstName (string)
            Annotations:
                Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1
                [Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase]
                Relational:TableColumnMappings:System.Collections.Generic.SortedSet`1
                [Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]
         IsDeleted (bool) Required
              Annotations:
                  Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1
                  [Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase]
                  Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1
                  [Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]
         LastName (string)
              Annotations:
                  Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1
                  [Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase]
                  Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1
                  [Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]
          UserId (no field, int) Shadow Required
              Annotations:
                  Relational:DefaultColumnMappings: System.Collections.Generic.SortedSet`1
                  [Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMappingBase]
                  Relational:TableColumnMappings: System.Collections.Generic.SortedSet`1
                  [Microsoft.EntityFrameworkCore.Metadata.Internal.ColumnMapping]
          Skip navigations:
              Addresses (List<Address>) CollectionAddress        
                  Inverse: Residents
          Keys:
              Id PK
                 Annotations:
                     Relational:UniqueConstraintMappings: System.Collections.Generic.SortedSet`1
                     [Microsoft.EntityFrameworkCore.Metadata.Internal.UniqueConstraint]
                 Annotations:
                     ConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding
                         QueryFilter: p => Not(p.IsDeleted)
                     Relational:DefaultMappings: System.Collections.Generic.List`1
                     [Microsoft.EntityFrameworkCore.Metadata.Internal.TableMappingBase]
                     Relational:TableMappings: System.Collections.Generic.List`1
                     [Microsoft.EntityFrameworkCore.Metadata.Internal.TableMapping]
                     Relational:TableName: People
                     ServiceOnlyConstructorBinding: 
                     Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding

6、利用

  有关EF Core,您对幕后情况了解得越多,对该工具的掌控力就越大。

  了解EF Core如何解释您的类和映射,你可以控制这些模型并按照您希望的方式持久保存数据,也可以根据需要修改SQL甚至结果。学习如何利用本文中介绍的各种调试,日志记录,侦听和事件处理方法,希望能帮助您成为团队中的EF Core专家。

彻底理解EF Core(5)的运行机制,万字长文带你成长为团队中的EF Core专家_EF Core_05


标签:Core,EF,查询,文带,command,SQL,Id
From: https://blog.51cto.com/u_14316538/6320194

相关文章

  • [React] useEffect
    purefunction:单纯返回jsx元素的组件在使用react函数组件时,理论上函数组件只会进行不改变内部状态值的计算,以及返回html代码。一个pure函数就是如此,例如一个函数组件接受一个id作为传入属性,注意这里传入的id并没有发生变化,我们只是简单进行了输出和数学计算://thisisapurefu......
  • Nas Docker 安装个人记账web项目:firefly_iii &beancount-gs
    NasDocker安装个人记账web项目:firefly_iii&beancount-gs1.经过搜索以及GPT的询问,通过预览界面感觉firefly_iii官方示例demo:https://demo.firefly-iii.org/官方安装文档:https://docs.firefly-iii.org/firefly-iii/installation/docker/本人采用的是群晖Nasdocker安装:这个......
  • ASP.NET Core之由配置系统与创建app所想到的
    先看文件配置的代码:ConfigurationBuilderconfigBuilder=newConfigurationBuiler();//典型的创建者模式configBuilder.AddJsonFile("config.json",option:false,reloadChange:false);//添加json配置文件,属于是创建者模式环节中的“配置创建者条件”的环节IconfigurationRootc......
  • Undefined 不是 Null
    在JavaScript中,null用于对象,undefined用于变量,属性和方法。对象只有被定义才有可能为null,否则为undefined。如果我们想测试对象是否存在,在对象还没定义时将会抛出一个错误。错误的使用方式:if(myObj!==null&&typeofmyObj!=="undefined")正确的方式是我们......
  • Makefile学习笔记
    ​目录一、概述1.1 Makefile介绍1.2规则1.3核心1.4示例1.5定义命令1.6 make是如何工作的1.7、makefile中使用变量1.8让make自动推导1.9、另类风格的makefile1.10、清空目标文件的规则二、Makefile总述2.1、Makefile里有什么?2.2、 makefile文件名2.3、引用其......
  • June 2021-Continuous Transition: Improving Sample Efficiency for Continuous Cont
    摘要:尽管深度强化学习(RL)已成功应用于各种机器人控制任务,但由于样本效率较差,将其应用于现实世界任务仍然具有挑战性。为了克服这一缺点,一些工作侧重于在训练过程中重用收集的轨迹数据,将其分解为一组策略无关的离散变迁。然而,它们的改进有些边际,因为i)转换的数量通常很小,ii)值分......
  • Codeforces Round 874 (Div. 3) A-G
    比赛地址A.MusicalPuzzle题意:给出一个字符串,求有多少个不同的长度为2的子串Solution直接set存即可voidsolve(){ intn;cin>>n; strings;cin>>s; set<string>st; for(inti=0;i<n-1;i++) { st.insert(s.substr(i,2)); } cout<<st.size()<<"\n"......
  • Codeforces Round 874 (Div. 3)
    A.MusicalPuzzle题意:用最少的长度为2的字符串按一定规则拼出s。规则是:前一个字符串的尾与后一个字符串的首相同。分析:统计s中长度为2的不同字符串数量。代码:#include<bits/stdc++.h>usingnamespacestd;typedeflonglongLL;constintN=1e5;intmain(){......
  • CodeForces1061C Multiplicity
    题面翻译从序列\(\{a_1,\a_2,\..\,\a_n\}\)中选出非空子序列\(\{b_1,\b_2,\..\,\b_k\}\),一个子序列合法需要满足\(\forall\i\in[1,\k],\i\|\b_i\)。求有多少互不相等的合法子序列,答案对\(10^9+7\)取模。序列\(\{1,\1\}\)有\(2\)种选法得到子序列\(......
  • Fortran程序的Makefile文件
      qqqq #获取文件夹中所有.f90文件列表notdir把展开的文件去除掉路径信息SRCS_F90=$(wildcard*.f90)SRCS_F=$(wildcard./*.f)SRCS_DIR=$(notdir$(SRCS_F))#替换.f90后缀为.o后缀得到.o文件列表OBJS_F90=$(patsubst%.f90,%.o,$(SRCS_F90))OBJS_F=$......