万字长文,带你彻底理解EF Core5的运行机制,让你成为团队中的EF Core专家
DotNet NB
共 19000字,需浏览 38分钟
·
2021-07-10 07:51
1、将EF的ToTraceString移植为EF Core的ToQueryString
var sqlFromQuery=context.People.ToQueryString();
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();
}
[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 *"));
}
modelBuilder.Entity<Person>().HasQueryFilter(p => !p.IsDeleted);
Assert.IsTrue(sql.ToUpper().Contains("WHERE NOT (\"p\".\"IsDeleted\")"));
2、从EF Core记录详细信息
2.1、 简单的日志记录
optionsBuilder.UseSqlServer(myConnectionString)
.LogTo(Console.WriteLine,LogLevel.Information);
.LogTo(Console.WriteLine, LogLevel.Information,
new[]{DbLoggerCategory.Database.Command.Name},
)
.EnableSensitiveDataLogging();
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();
2.2、响应EF Core 事件
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;
}
}
public PeopleContext()
{
SavingChanges += SetUserId;
}
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、使用事件计数器访问指标
System.Diagnostics.Process.GetCurrentProcess().Id
dotnet counters monitor Microsoft.EntityFrameworkCore -p 313131
3、拦截EF Core的数据——拦截器
public override InterceptionResult<DbDataReader>
ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
//例如,webmote支持你干点啥?
return result;
}
public override DbDataReader ReaderExecuted(
DbCommand command,
CommandExecutedEventData eventData,
DbDataReader result)
{
return base.ReaderExecuted
(command, eventData, result);
}
4、查询拦截
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());
}
}
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);
}
}
}
5、EF Core 5中的Sleeper功能:调试视图
Person {Id: 1} Unchanged
Person {Id: 1} Unchanged
Id: 1 PK
FirstName: 'Julie'
IsDeleted: 'False'
LastName: 'Lerman'
UserId: 101
Addresses: []
Person {Id: 1} Modified
Id: 1 PK
FirstName: 'Julie'
IsDeleted: 'False'
LastName: 'Lermantov' Modified
Originally 'Lerman'
UserId: 101
Addresses: []
AddressPerson (Dictionary<string, object>)
{AddressesId: 1, ResidentsId: 1} Unchanged FK
{AddressesId: 1} FK {ResidentsId: 1}
Address {Id: 1} Unchanged
Person {Id: 1} Modified
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
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、利用
评论