在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添加三個事件:SaveChangesFailed
、SaveChanges
和SaveChangesAsync
。
當上下文將要保存更改時,將引發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
公開了ReaderExecuting
和ReaderExecutingAsync
,它們在命令即將發送到數據庫時被觸發。
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.DebugView
和Model.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、迎娶白富美、走上人生巔峰,想想是不是還有點小激動?