引言
书接上回,【源码解读(一)】EFCORE源码解读之创建DBContext查询拦截 ,在上一篇文章中,主要讲了DBContext的构造函数,以及如何缓存查询方法提升查询性能,还有最重要的拦截查询,托管IOC到web程序,在上一文章中,最后关于DBContext的构造函数的内容没有讲完,在本章中我会讲以下几部分,会将上篇没有讲完的部分讲完,会讲关于一条查询语句普普通通的一生,如何自定义批量增删改查的方式,以及在SaveChanges的时候会做什么,AddRange,UpdateRange会做什么等等。
一:DBContext构造函数获取的IDbSetInitializer的InitializeSets方法做了什么;
二:一条查询语句悲惨而高昂的一生;
三:如何自定义批量增删改查替换自带的;
四:SaveChanges,AddRange,UpdateRange等相关的其他操作会做什么;
以上作为本篇文章的所有内容,接下来,我们来开始讲解源码,动手实践。
IDbSetInitializer
在DBContext构造函数调用ServiceProviderCache.Instance.GetOrAdd的方法之后,去获取了一个IDbSetInitializer的服务,调用了InitializeSets方法,顾名思义,这个方法其实就是去加载我们的DBSet的,以下是这个接口的实现,从下面的源码中,我们不难看出,这里就是通过IDbSetFinder去查找DBContext里面的所有的DBSet属性,然后创建DBSetProperty,这个DBSet属性必须要有Set方法,这样才会去调用Factory.Create创建一个Set方法的委托,在下面的Finder里面可以看到最终是调用了GenericCreate的方法创建一个方法委托,然后去调用,而这个抽象方法的实现是在ClrPropertySetterFactory里面,最终是创建了一个Action的委托传入到ClrPropertySetter里面去了,这样就创建了DBContext里面的所有的DbSet的Set方法,,但是呢这里是只给构建了DBSet的Set方法,但是还没有调用,相当于此时的DBSet还是null,所以还要继续看DbSetInitializer下面的方法,可以看到调用了一个FindSet方法之后,我们执行了构建DbSet的Set方法之后,下面调用了我们构建的ClrPropertySetter,调用了它的SetClrValue方法,这个方法内部很简单了,实际上就是去调用我们的Setter方法,去创建我们的DBSet对象。而创建DBSet对象,是先要调用DBSetSource的GetOrAdd方法的,这个方法代码没有贴出来,内部其实就是调用IDBSetSource的Create方法,创建一个InternalDbSet的对象,这个对象继承了DBSet,所以我们的所有的DBSet,其实都是InternalDbSet,在下面的代码,我们可以看到,最终都是返回了一个这个。
代码语言:javascript复制 public DbSetInitializer(
IDbSetFinder setFinder,
IDbSetSource setSource)
{
_setFinder = setFinder;
_setSource = setSource;
}
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual void InitializeSets(DbContext context)
{
foreach (var setInfo in _setFinder.FindSets(context.GetType()).Where(p => p.Setter != null))
{
setInfo.Setter!.SetClrValue(
context,
((IDbSetCache)context).GetOrAddSet(_setSource, setInfo.Type));
}
}
IDbSetFinder
代码语言:javascript复制public class DbSetFinder : IDbSetFinder
{
private readonly ConcurrentDictionary<Type, IReadOnlyList<DbSetProperty>> _cache = new();
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IReadOnlyList<DbSetProperty> FindSets(Type contextType)
=> _cache.GetOrAdd(contextType, FindSetsNonCached);
private static DbSetProperty[] FindSetsNonCached(Type contextType)
{
var factory = new ClrPropertySetterFactory();
return contextType.GetRuntimeProperties()
.Where(
p => !p.IsStatic()
&& !p.GetIndexParameters().Any()
&& p.DeclaringType != typeof(DbContext)
&& p.PropertyType.GetTypeInfo().IsGenericType
&& p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
.OrderBy(p => p.Name)
.Select(
p => new DbSetProperty(
p.Name,
p.PropertyType.GenericTypeArguments.Single(),
p.SetMethod == null ? null : factory.Create(p)))
.ToArray();
}
}
IDbSetSource
代码语言:javascript复制public class DbSetSource : IDbSetSource
{
private static readonly MethodInfo GenericCreateSet
= typeof(DbSetSource).GetTypeInfo().GetDeclaredMethod(nameof(CreateSetFactory))!;
private readonly ConcurrentDictionary<(Type Type, string? Name), Func<DbContext, string?, object>> _cache = new();
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual object Create(DbContext context, Type type)
=> CreateCore(context, type, null, GenericCreateSet);
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual object Create(DbContext context, string name, Type type)
=> CreateCore(context, type, name, GenericCreateSet);
private object CreateCore(DbContext context, Type type, string? name, MethodInfo createMethod)
=> _cache.GetOrAdd(
(type, name),
static (t, createMethod) => (Func<DbContext, string?, object>)createMethod
.MakeGenericMethod(t.Type)
.Invoke(null, null)!,
createMethod)(context, name);
[UsedImplicitly]
private static Func<DbContext, string?, object> CreateSetFactory<TEntity>()
where TEntity : class
=> (c, name) => new InternalDbSet<TEntity>(c, name);
}
ClrPropertySetterFactory
代码语言:javascript复制 public override IClrPropertySetter Create(IPropertyBase property)
=> property as IClrPropertySetter ?? Create(property.GetMemberInfo(forMaterialization: false, forSet: true), property);
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected override IClrPropertySetter CreateGeneric<TEntity, TValue, TNonNullableEnumValue>(
MemberInfo memberInfo,
IPropertyBase? propertyBase)
{
var entityParameter = Expression.Parameter(typeof(TEntity), "entity");
var valueParameter = Expression.Parameter(typeof(TValue), "value");
var memberType = memberInfo.GetMemberType();
var convertedParameter = memberType == typeof(TValue)
? (Expression)valueParameter
: Expression.Convert(valueParameter, memberType);
Expression writeExpression;
if (memberInfo.DeclaringType!.IsAssignableFrom(typeof(TEntity)))
{
writeExpression = CreateMemberAssignment(entityParameter);
}
else
{
// This path handles properties that exist only on proxy types and so only exist if the instance is a proxy
var converted = Expression.Variable(memberInfo.DeclaringType, "converted");
writeExpression = Expression.Block(
new[] { converted },
new List<Expression>
{
Expression.Assign(
converted,
Expression.TypeAs(entityParameter, memberInfo.DeclaringType)),
Expression.IfThen(
Expression.ReferenceNotEqual(converted, Expression.Constant(null)),
CreateMemberAssignment(converted))
});
}
var setter = Expression.Lambda<Action<TEntity, TValue>>(
writeExpression,
entityParameter,
valueParameter).Compile();
var propertyType = propertyBase?.ClrType ?? memberInfo.GetMemberType();
return propertyType.IsNullableType()
&& propertyType.UnwrapNullableType().IsEnum
? new NullableEnumClrPropertySetter<TEntity, TValue, TNonNullableEnumValue>(setter)
: new ClrPropertySetter<TEntity, TValue>(setter);
Expression CreateMemberAssignment(Expression parameter)
=> propertyBase?.IsIndexerProperty() == true
? Expression.Assign(
Expression.MakeIndex(
entityParameter, (PropertyInfo)memberInfo, new List<Expression> { Expression.Constant(propertyBase.Name) }),
convertedParameter)
: Expression.MakeMemberAccess(parameter, memberInfo).Assign(convertedParameter);
}
到这里,关于DBContext构造函数的基本上就已经完成了,回顾一下,结合上篇文章中,我们可以知道DBContext里面在刚进来的时候,就去判断有没有托管IOC到其他的InternalServiceProvider,然后判断了有没有自己实现了IDBContextOptionsExtension接口,然后去调用ApplyService方法注入EF所需要用到的一些服务,同时调用ReplaceService替换的服务也会替换,最终调用到了我们今天讲的这部分,关于DBSet的初始化的操作。
一条查询语句悲惨的一生
我们在创建好了DBContext之后呢,就需要去做一些增删改查的操作了,在这里我就以一个简单的查询语句为例子,代码都是和上篇文章中一样的,var res= DbContext.Contacts.Take(10).ToList();这个语句的执行,都经历了哪些,众所周知,DBSet实现了IQueryable的接口,所以我们在调用的时候是可以使用Queryable里面的扩展方法的,例如上面的语句中,Take(10).ToList(); Take调用的就是这个类里面的方法,我们看一下Take方法的调用,在上篇文章的自定义拦截里面,我们是自己实现了IAsyncQueryProvider,这里的source.Provider就是我们自定义的Provider,实际上最终调用的都是到了IQueryCompiler接口里面。所以接下来让我们看看CreateQuery里面具体做了哪些事情。
代码语言:javascript复制public abstract class DbSet<TEntity> : IQueryable<TEntity>, IInfrastructure<IServiceProvider>, IListSource
代码语言:javascript复制 public static IQueryable<TSource> Take<TSource>(this IQueryable<TSource> source!!, int count) =>
source.Provider.CreateQuery<TSource>(
Expression.Call(
null,
CachedReflectionInfo.Take_Int32_TSource_2(typeof(TSource)),
source.Expression, Expression.Constant(count)
));
IAsyncQueryProvider
下面是自带的一个IAsyncQueryProvider的实现,按照我们上面的代码来看,实际上最终返回的是EntityQueryable的一个类型,在上一文章中,我们实现过自定义的IQueryable的一个类型,最终自定义的实现的这个Queryable,里面的Expression就存储着我们组装的所有的表达式,相当于每次我们调用Queryable的方法的时候都会构建一个新的EntityQueryable传入组装好的表达式,只要返回的类型是IQueryable接口或者子类接口都会这样。
代码语言:javascript复制public class EntityQueryProvider : IAsyncQueryProvider
{
private static readonly MethodInfo GenericCreateQueryMethod
= typeof(EntityQueryProvider).GetRuntimeMethods()
.Single(m => (m.Name == "CreateQuery") && m.IsGenericMethod);
private readonly MethodInfo _genericExecuteMethod;
private readonly IQueryCompiler _queryCompiler;
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public EntityQueryProvider(IQueryCompiler queryCompiler)
{
_queryCompiler = queryCompiler;
_genericExecuteMethod = queryCompiler.GetType()
.GetRuntimeMethods()
.Single(m => (m.Name == "Execute") && m.IsGenericMethod);
}
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IQueryable<TElement> CreateQuery<TElement>(Expression expression)
=> new EntityQueryable<TElement>(this, expression);
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IQueryable CreateQuery(Expression expression)
=> (IQueryable)GenericCreateQueryMethod
.MakeGenericMethod(expression.Type.GetSequenceType())
.Invoke(this, new object[] { expression })!;
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual TResult Execute<TResult>(Expression expression)
=> _queryCompiler.Execute<TResult>(expression);
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual object Execute(Expression expression)
=> _genericExecuteMethod.MakeGenericMethod(expression.Type)
.Invoke(_queryCompiler, new object[] { expression })!;
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = default)
=> _queryCompiler.ExecuteAsync<TResult>(expression, cancellationToken);
}
IQueryable
代码语言:javascript复制public class EntityQueryable<TResult>
: IOrderedQueryable<TResult>,
IAsyncEnumerable<TResult>,
IListSource
{
private readonly IAsyncQueryProvider _queryProvider;
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public EntityQueryable(IAsyncQueryProvider queryProvider, IEntityType entityType)
: this(queryProvider, new QueryRootExpression(queryProvider, entityType))
{
}
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public EntityQueryable(IAsyncQueryProvider queryProvider, Expression expression)
{
_queryProvider = queryProvider;
Expression = expression;
}
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual Type ElementType
=> typeof(TResult);
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual Expression Expression { get; }
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IQueryProvider Provider
=> _queryProvider;
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IEnumerator<TResult> GetEnumerator()
=> _queryProvider.Execute<IEnumerable<TResult>>(Expression).GetEnumerator();
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
IEnumerator IEnumerable.GetEnumerator()
=> _queryProvider.Execute<IEnumerable>(Expression).GetEnumerator();
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IAsyncEnumerator<TResult> GetAsyncEnumerator(CancellationToken cancellationToken = default)
=> _queryProvider
.ExecuteAsync<IAsyncEnumerable<TResult>>(Expression, cancellationToken)
.GetAsyncEnumerator(cancellationToken);
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
IList IListSource.GetList()
=> throw new NotSupportedException(CoreStrings.DataBindingWithIListSource);
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
bool IListSource.ContainsListCollection
=> false;
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual QueryDebugView DebugView
=> new(() => Expression.Print(), this.ToQueryString);
}
ToList
我们都知道,在调用了ToList才会去真正的执行我们的Sql查询,在下面,我们看到,ToList返回了一个new List,因为我们的source并没有继承IIListProvider接口,所以到了List的构造函数,在上面的代码中,默认自带的EntityQueryable也没有实现ICollection接口,所以就到了else代码段,会最终执行EntityQueryable的GetEnumerator方法,最终是调用了EntityQueryableProvider的Execute方法,在调用QueryCompiler的Execute方法中
代码语言:javascript复制 public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
return source is IIListProvider<TSource> listProvider ? listProvider.ToList() : new List<TSource>(source);
}
代码语言:javascript复制 public List(IEnumerable<T> collection)
{
if (collection == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
if (collection is ICollection<T> c)
{
int count = c.Count;
if (count == 0)
{
_items = s_emptyArray;
}
else
{
_items = new T[count];
c.CopyTo(_items, 0);
_size = count;
}
}
else
{
_items = s_emptyArray;
using (IEnumerator<T> en = collection!.GetEnumerator())
{
while (en.MoveNext())
{
Add(en.Current);
}
}
}
}
IQueryCompiler
在Execute方法,首先创建了一个查询上下文,这个RelationalQueryContext对象,这个对象里面的构造函数的两个参数,一个包括了关于查询的时候的异常处理策略,以及当前的DBContext,并发处理,异常处理,还有一个是不同数据库的字符串查询构建,还有DBConnecion。创建完这个对象之后,下面就到了提取参数环节咯。提取参数结束后会调用CompileQueryCore方法,这里通过IDataBase去构建查询的委托,并且缓存起来,在上一章节中,我们也使用了database.CompileQuery去创建委托实现。这个接口源码有四个实现,我除了InMemory 和cosmos可能用的自己实现,剩下的一个DataBase是抽象的,我们默认用的是RelationalDatabase实现DataBase的抽象类,但是CompileQuery是在DataBase抽象类下的,还记得我们需要在EF执行的时候打印Sql语句需要UseLogger吗,我没记错的话,日志是在这个构建里面去开始触发写Sql的事件的,这里的Logger,再看下去,就会看到EventId,EventData,包括了执行的类型,数据语句都可以获取的到,在往下面走,就是表达式的遍历,以及不同数据库的需要做不同的处理,这里很多我没细看,感兴趣的可以自己去看看。最终会构建一个入参是QueryContext的委托,返回我们的查询对象。最终调用结束在List的构造函数里去创建一个新的List,GetEnumerable返回了我们本次的查询结果。
代码语言:javascript复制 public virtual Func<QueryContext, TResult> CompileQuery<TResult>(Expression query, bool async)
=> Dependencies.QueryCompilationContextFactory
.Create(async)
.CreateQueryExecutor<TResult>(query);
代码语言:javascript复制 public virtual Func<QueryContext, TResult> CreateQueryExecutor<TResult>(Expression query)
{
Logger.QueryCompilationStarting(_expressionPrinter, query);
query = _queryTranslationPreprocessorFactory.Create(this).Process(query);
// Convert EntityQueryable to ShapedQueryExpression
query = _queryableMethodTranslatingExpressionVisitorFactory.Create(this).Visit(query);
query = _queryTranslationPostprocessorFactory.Create(this).Process(query);
// Inject actual entity materializer
// Inject tracking
query = _shapedQueryCompilingExpressionVisitorFactory.Create(this).Visit(query);
// If any additional parameters were added during the compilation phase (e.g. entity equality ID expression),
// wrap the query with code adding those parameters to the query context
query = InsertRuntimeParameters(query);
var queryExecutorExpression = Expression.Lambda<Func<QueryContext, TResult>>(
query,
QueryContextParameter);
try
{
return queryExecutorExpression.Compile();
}
finally
{
Logger.QueryExecutionPlanned(_expressionPrinter, queryExecutorExpression);
}
}
以上是一条简单的查询执行的一个过程,只是一个大概的一个方向,但总体没错,可能有些地方不是很细致,因为,这里东西太多了,就简略讲一下。
如何自定义批量增删改查替换自带的
在以前记得使用批量插入的时候,总觉得EF自带的很慢,3.1的时候用的,到现在都这么久了,不知道提升性能了没得,不过它的内部依旧和我写的例子 原理差不多,内部开启一个事物,然后循环添加,这里只是一个简单的例子,感兴趣的朋友,可以自己去进行扩展,在AddRange,还有UpdateRange等批量操作的都会进去到这里,commandBatches是我们所有需要进行批量操作的记录,connection是我们当前数据库的连接,最终只在Execute和ExecuteAsync里面去写自己的批量逻辑就行了。在上一章的代码中,还需要添加builder.Services.AddScoped<IBatchExecutor, Batch>();就可以实现自定义批量操作。
代码语言:javascript复制 public class Batch : IBatchExecutor
{
public Batch()
{
}
public int Execute(IEnumerable<ModificationCommandBatch> commandBatches, IRelationalConnection connection)
{
int res = 0;
using (var begin=connection.BeginTransaction())
{
try
{
foreach (var item in commandBatches)
{
item.Execute(connection);
}
begin.Commit();
res = commandBatches.Count();
}
catch (Exception)
{
begin.Rollback();
res= 0;
}
}
return res;
}
public Task<int> ExecuteAsync(IEnumerable<ModificationCommandBatch> commandBatches, IRelationalConnection connection, CancellationToken cancellationToken = default)
{
return Task.FromResult(0);
}
}
SaveChanges,AddRange,UpdateRange等相关的其他操作会做什么
我们都知道,EF是有上下文的,所以对于每个实体的状态都有自己的管理,我们的操作是有一个状态管理的,而所有增删改查都会调用SetEntityStates方法,然后如下面代码,去调用SetEntityState方法,在此之前会先获取一下状态管理。调用GetOriCreateEntry方法,然后TryGetEntry判断实体在不在上下文,在下面折叠的代码看到,内部是维护了一个五个字典类型的变量,分别对于detached,add,delete,modified,unchanged,分别对应实体的状态,通过去获取存在不存在当前的Entry,在什么状态,不存在的话就去查找runtimetype是否存在,然后调用SetEntityState方法,内部通过IStateManager去进行协调,通过这个接口,去进行Entry的状体管理,以及更改等操作,StateManager篇幅较多,这里只做一个简单的介绍,后续继续会针对状态管理,更改,新增,等进行详细的讲解,此处 只讲一下大概、
代码语言:javascript复制 private void SetEntityStates(IEnumerable<object> entities, EntityState entityState)
{
var stateManager = DbContextDependencies.StateManager;
foreach (var entity in entities)
{
SetEntityState(stateManager.GetOrCreateEntry(entity), entityState);
}
}
代码语言:javascript复制 public virtual InternalEntityEntry GetOrCreateEntry(object entity)
{
var entry = TryGetEntry(entity);
if (entry == null)
{
var entityType = _model.FindRuntimeEntityType(entity.GetType());
if (entityType == null)
{
if (_model.IsShared(entity.GetType()))
{
throw new InvalidOperationException(
CoreStrings.UntrackedDependentEntity(
entity.GetType().ShortDisplayName(),
"." nameof(EntityEntry.Reference) "()." nameof(ReferenceEntry.TargetEntry),
"." nameof(EntityEntry.Collection) "()." nameof(CollectionEntry.FindEntry) "()"));
}
throw new InvalidOperationException(CoreStrings.EntityTypeNotFound(entity.GetType().ShortDisplayName()));
}
if (entityType.FindPrimaryKey() == null)
{
throw new InvalidOperationException(CoreStrings.KeylessTypeTracked(entityType.DisplayName()));
}
entry = new InternalEntityEntry(this, entityType, entity);
UpdateReferenceMaps(entry, EntityState.Detached, null);
}
return entry;
}
代码语言:javascript复制 public virtual bool TryGet(
object entity,
IEntityType? entityType,
[NotNullWhen(true)] out InternalEntityEntry? entry,
bool throwOnNonUniqueness)
{
entry = null;
var found = _unchangedReferenceMap?.TryGetValue(entity, out entry) == true
|| _modifiedReferenceMap?.TryGetValue(entity, out entry) == true
|| _addedReferenceMap?.TryGetValue(entity, out entry) == true
|| _deletedReferenceMap?.TryGetValue(entity, out entry) == true
|| _detachedReferenceMap?.TryGetValue(entity, out entry) == true;
if (!found
&& _hasSubMap
&& _sharedTypeReferenceMap != null)
{
if (entityType != null)
{
if (_sharedTypeReferenceMap.TryGetValue(entityType, out var subMap))
{
return subMap.TryGet(entity, entityType, out entry, throwOnNonUniqueness);
}
}
else
{
var type = entity.GetType();
foreach (var (key, entityReferenceMap) in _sharedTypeReferenceMap)
{
// ReSharper disable once CheckForReferenceEqualityInstead.2
if (key.ClrType.IsAssignableFrom(type)
&& entityReferenceMap.TryGet(entity, entityType, out var foundEntry, throwOnNonUniqueness))
{
if (found)
{
if (!throwOnNonUniqueness)
{
entry = null;
return false;
}
throw new InvalidOperationException(
CoreStrings.AmbiguousDependentEntity(
entity.GetType().ShortDisplayName(),
"." nameof(EntityEntry.Reference) "()." nameof(ReferenceEntry.TargetEntry)));
}
entry = foundEntry;
found = true;
}
}
}
}
return found;
}
结束
关于EFCore的源码讲解,有时候也不知道怎么讲,因为它不像asp.net core的有序,所以导致讲的时候不知道怎么讲,后续,会继续出关于对EFCORE的源码讲解,可能有的地方依旧会讲得多一点,有的会提供一个大概的类,或者方法名称,如果有阅读过源码的大佬有什么建议,欢迎大佬们提出你们宝贵的意见。