萬字長文,帶你徹底理解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.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過濾特定事件。您還可以過濾出特定的日志類別,並且可以控制格式。在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 = 1
   AND [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 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文檔中的攔截器的大量指導,請訪問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} 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擴展來可視化模型。
在這里插入圖片描述
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專家。

當然,不用多久,你就會升職加薪、當上總經理、出任CEO、迎娶白富美、走上人生巔峰,想想是不是還有點小激動?

在這里插入圖片描述


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM