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

在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,如图所示。

在调用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甚至通过迁移发送的更改。

.LogTo(Console.WriteLine, LogLevel.Information,new[]{DbLoggerCategory.Database.Command.Name},
)
.EnableSensitiveDataLogging();

上面包含IsDeleted属性的“Person”类型也具有FirstName和LastName属性。这是添加新的Person对象后调用SaveChanges的日志。

info: 1/4/2021 17:56:09.935RelationalEventId.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过滤特定事件。您还可以过滤出特定的日志类别,并且可以控制格式。在https://docs.microsoft.com/zh-cn/ef/core/logging-events-diagnostics/simple-logging上查看有关这些各种功能的更多详细信息的文档。

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

您也可以通过直接与Microsoft.Extensions.Logging一起,以对EF Core的日志方式进行更多控制。检查EF Core文档,以获取更多有关使用此更高级用法的详细信息:https://docs.microsoft.com/zh-cn/ef/core/logging-events-diagnostics/extensions-logging。

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 = 1AND [Id] = scope_identity();

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

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

EF Core 5利用了.NET Core 3.0中.NET引入的一项很酷的功能-dotnet-counters(https://docs.microsoft.com/zh-cn/dotnet/core/diagnostics/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团队建议您在文档中仔细阅读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实现。一个示例是记录一些记录器无法捕获的内容,例如:

4、查询拦截

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

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

  • 延长具有某些特征的命令的超时

  • 查询失败并记录异常时诊断信息

  • 当读取到内存的行数超过特定阈值时,记录警告

一个小例子:

public class TestQueryInterceptor : DbCommandInterceptor
{// runs before a query is executedpublic 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 excutedpublic 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, outvar commandStartTime)) {var commandDuration = DateTime.Now - commandStartTime;CommandDurations.AddOrUpdate(command.CommandText, commandDuration, (_, accumulated) => commandDuration + accumulated);}}
}

有关使用EF Core文档中的拦截器的大量指导,请访问https://docs.microsoft.com/en-us/ef/core/logging-events-diagnostics/interceptors。

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} UnchangedId: 1 PKFirstName: 'Julie'IsDeleted: 'False'LastName: 'Lerman'UserId: 101Addresses: []

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

Person {Id: 1} ModifiedId: 1 PKFirstName: 'Julie'IsDeleted: 'False'LastName: 'Lermantov' ModifiedOriginally 'Lerman'UserId: 101Addresses: []

您可以在此视图中看到一个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扩展来可视化模型。

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:ThrowResidentsId (no field, int) Indexer Required PK FK Index AfterSave:ThrowKeys:AddressesId, ResidentsId PKForeign keys:AddressPerson (Dictionary<string, object>) {'AddressesId'} -> Address {'Id'} CascadeAddressPerson (Dictionary<string, object>) {'ResidentsId'} -> Person {'Id'} CascadeIndexes:ResidentsIdEntityType: AddressProperties:Id (int) Required PK AfterSave:Throw ValueGenerated.OnAddPostalCode (string)Street (string)UserId (no field, int) Shadow RequiredNavigations:WildlifeSightings (List<WildlifeSighting>) Collection ToDependent WildlifeSightingSkip navigations:Residents (List<Person>) CollectionPerson        Inverse: AddressesKeys:Id PKEntityType: PersonProperties:Id (int) Required PK AfterSave:Throw ValueGenerated.OnAddFirstName (string)IsDeleted (bool) RequiredLastName (string)UserId (no field, int) Shadow RequiredSkip navigations:Addresses (List<Address>) CollectionAddress        Inverse: ResidentsKeys:Id PKEntityType: WildlifeSightingProperties:Id (int) Required PK AfterSave:Throw ValueGenerated.OnAddAddressId (int) Required FK IndexDateTime (DateTime) RequiredDescription (string)UserId (no field, int) Shadow RequiredKeys:Id PKForeign keys:WildlifeSighting {'AddressId'} -> Address {'Id'}        ToDependent: WildlifeSightings CascadeIndexes:AddressId

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

  • 清单2:在Model.DebugView的LongView中描述的Person实体

EntityType: PersonProperties:Id (int) Required PK AfterSave:Throw ValueGenerated.OnAddAnnotations: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: IdentityColumnFirstName (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) RequiredAnnotations: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 RequiredAnnotations: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: ResidentsKeys:Id PKAnnotations:Relational:UniqueConstraintMappings: System.Collections.Generic.SortedSet`1[Microsoft.EntityFrameworkCore.Metadata.Internal.UniqueConstraint]Annotations:ConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBindingQueryFilter: 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: PeopleServiceOnlyConstructorBinding: Microsoft.EntityFrameworkCore.Metadata.ConstructorBinding

6、利用

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

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

当然,不用多久,你就会升职加薪、当上总经理、出任CEO、迎娶白富美、走上人生巅峰,想想是不是还有点小激动?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/303129.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

重学数据结构004——栈的基本操作及实现(数组实现)

上文提到过栈以及栈的基本操作。上文中是基于链表做的实现。但是这种方法会出现大量的malloc()和free()操作&#xff0c;这种开销是非常昂贵的。 另外一种实现方式是基于数组的实现。这种实现方式需要预先制定一个栈的大小&#xff0c;此外还需要一个Top来记录栈顶元素下一个位…

C#使用线程窗口调试多线程程序

调试多线程程序一般有以下几种办法1、在日志的某个地方写日志文件。优点&#xff1a;不会干扰程序的执行&#xff0c;特别是对网络的多线程通信。缺点&#xff1a;每次都需要打开日志文件以查看进程运行的信息。2、利用断点进行调试。优点&#xff1a;直观&#xff0c;可以直接…

窥探渣男天才爱因斯坦的一生

本文授权转自微信公众号超级数学建模&#xff08;ID&#xff1a;supermodeling&#xff09;----------------------------------提起爱因斯坦&#xff0c;你最先想到什么&#xff1f;相对论&#xff1f;原子弹&#xff1f;物理天才&#xff1f;Emc?然而&#xff0c;2017年由美…

在 Azure App Service 上运行 .NET 6 预览版

点击上方蓝字关注“汪宇杰博客”原文&#xff1a;Jeff Martinez翻译&#xff1a;Edi Wang导语.NET 6 是最新的 .NET 版本&#xff0c;它最终将.NET Core&#xff0c;Framework&#xff0c;Xamarin和Mono的精华带入以 .NET 5 开始的统一平台。该版本目前为预览版&#xff0c;用于…

R还能这样玩!

R作为一种统计分析软件&#xff0c;广泛应用于生物、医学、电商、新闻等数据相关行业&#xff0c;是目前主流数据应用软件之一。那么&#xff0c;R到底有哪些特别之处呢?实际上&#xff0c;R是统计领域广泛使用的S语言的一个分支&#xff0c;两者在程序语法上几乎一样&#xf…

百度网页移动端html,百度移动端开始用网站品牌名代替网址显示

最近&#xff0c;有站长发现&#xff0c;百度移动端最近做了部分改版&#xff1a;移动端部分网站域名开始逐渐被网站相关名称代替&#xff0c;PC端还是用域名展示&#xff0c;卢松松博客网站域名也被替换成网站品牌名显示!不知道站长们&#xff0c;最近有没有注意到&#xff0c…

每天6亿人在看《延禧攻略》?大数据告诉你哪家视频网站VIP值得买(附代码)

导读&#xff1a;随着《延禧攻略》的播出&#xff0c;魏璎珞、富察皇后等各位后宫小主的命运时刻牵动着各位观众的心。同时爱奇艺也因为该剧的大火&#xff0c;收获了单日超过6亿的播放量。我们此次将对比各大视频网站2018年截止到8月18号的电视剧和综艺节目的播放情况&#xf…

大厂面试都爱问这4个问题,.NET开发必看!

金三银四已进入尾声&#xff0c;身边不少从事.NET开发的朋友有了更好的去处&#xff01;大家日常在交流群里也常有分享经验。在面试时&#xff0c;大厂面试官都喜欢问什么&#xff1b;提问的形式和特点&#xff0c;大家又该如何应对等问题格外引起注意。今天就以腾讯公司的招聘…

参加计算机竞赛需要学什么知识,数学和计算机专业,我应该参加那些比赛?

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼美国数学竞赛美国数学竞赛 AmericanMathematical Competition&#xff0c;简称 AMC&#xff0c;原是于 1950 年起由美国数学协会 (MathematicsAssociation of America &#xff0c;简称 MAA) 开始举办的美国高中数学考试 (AHSME)。…

程序员年入50万,我们该如何努力达到这个目标?

先说明&#xff0c;这里是指税后。年收入50万在上海算多吗&#xff1f;比上不足&#xff0c;比下有余。不过在上海达到这个水平&#xff0c;至少可以说是能达到财务自由了吧。况且&#xff0c;根据马太效应的说法&#xff0c;和年入20万到50万这个增加阶段相比&#xff0c;年入…

昆明学院计算机二级报名时间,2017年3月昆明学院计算机等级考试报名时间(云南)...

考试时间&#xff1a;2017年3月25日至3月27日。网上报名及费用支付时间&#xff1a;2016年12月19日至12月28日。报名及准考证打印网址&#xff1a;http://222.221.5.208/NCRE_EMS/StudentLogin.aspx请各位考生注意&#xff1a;考生只能选择一个考点报名&#xff1b;本校考生只能…

OrchardCore 如何动态加载模块?

【导读】今天&#xff0c;我们再次讨论下OrchardCore&#xff0c;通过初期调研&#xff0c;我们项目采用OrchardCore底层设施支持模块化&#xff0c;同时根据业务场景&#xff0c;额外还需支持二次开发&#xff0c;于是有了本文&#xff0c;若有不同解决方案&#xff0c;欢迎留…

sed之G、H、g、h使用

前言 作者不善言谈&#xff0c;如有错误请指正&#xff01;&#xff01;&#xff01; 转载请注明出处&#xff01;&#xff01;&#xff01; sed之G、H、g、h使用 什么是sed&#xff1f; sed是面向流的行编辑器&#xff0c;所谓面向流&#xff0c;是指接受标准输入的输入&#…

人工智能路上,怎么能少了它!

目前&#xff0c;人工智能的应用日渐广泛。而作为人工智能核心的机器学习&#xff0c;是一门多领域的交叉学科&#xff0c;专门研究计算机模拟或实现人类学习行为的方法&#xff0c;以获取新的知识或技能&#xff0c;重新组织已有的知识结构使之不断改善自身的性能。简单来说&a…

墙裂推荐:这可能是CAP理论的最好解释

> 英文蓝本&#xff1a;http://ksat.me/a-plain-english-introduction-to-cap-theorem 经过小码甲意译、原创配图, 建议收藏。你可能经常听到CAP定理&#xff0c; 这个定理描述了在设计分布式系统时的天然约束。就像其他文章一样&#xff0c; 本文以现实场景对比理解CAP定理…

台湾 计算机术语,快取,陣列,程式,这些台湾的计算机术语,你知道几个?|冷知识...

原标题&#xff1a;快取&#xff0c;陣列&#xff0c;程式&#xff0c;这些台湾的计算机术语&#xff0c;你知道几个&#xff1f;|冷知识作者 | 楼下小黑哥来源 | 程序通事今天就不写技术文了&#xff0c;写点轻松的&#xff0c;带大家涨点知识。最近闲来无聊的时候&#xff0c…

计算机原理试题b,计算机组成原理试题B答案

计算机组成原理试题B答案 (3页)本资源提供全文预览&#xff0c;点击全文预览即可全文预览,如果喜欢文档就下载吧&#xff0c;查找使用更方便哦&#xff01;9.9 积分计算机组成原理试题B答案一、 选择题1. D 2. A 3. A&#xff0c;C 4. B 5. B 6. B 7. B 8. B 9. A 10. D二、 填…

趣图:各种程序员的键盘热力图

图0&#xff1a;不保存不舒服斯基型图1&#xff1a;复制粘贴型 图2&#xff1a;复制粘贴型专用键盘图3&#xff1a;Vim 党新手&#xff08;内心OS&#xff1a;门在哪里&#xff0c;我要退出&#xff09;图4&#xff1a;Windows 程序员图5&#xff1a;01 党终极程序员 来源&am…

创建支持依赖注入、Serilog 日志和 AppSettings 的 .NET 5 控制台应用

翻译自 Mohamad Lawand 2021年3月24日的文章 《.NET 5 Console App with Dependency Injection, Serilog Logging, and AppSettings》 [1]在本文中&#xff0c;我们将构建一个 .NET 5 控制台应用程序&#xff0c;该应用程序支持依赖注入、日志记录和 appsettings 配置。你也可以…

作为一个前端,可以如何机智地弄坏一台电脑?

有人说&#xff0c;前端的界限就在浏览器那儿。无论你触发了多少bug&#xff0c;最多导致浏览器崩溃&#xff0c;对系统影响不到哪去。这就像二次元各种炫酷的毁灭世界&#xff0c;都不会导致三次元的世界末日。然而&#xff0c;作为一个前端&#xff0c;我发现是有方式打开次元…