【源码解读(二)】EFCORE源码解读之查询都做了什么以及如何自定义批量插入
引言
书接上回,【源码解读(一)】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,在下面的代码,我们可以看到,最终都是返回了一个这个。
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
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(); } }
public virtual TAccessor Create(MemberInfo memberInfo) => Create(memberInfo, null); /// <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 virtual TAccessor Create(MemberInfo memberInfo, IPropertyBase? propertyBase) { var boundMethod = propertyBase != null ? GenericCreate.MakeGenericMethod( propertyBase.DeclaringType.ClrType, propertyBase.ClrType, propertyBase.ClrType.UnwrapNullableType()) : GenericCreate.MakeGenericMethod( memberInfo.DeclaringType!, memberInfo.GetMemberType(), memberInfo.GetMemberType().UnwrapNullableType()); try { return (TAccessor)boundMethod.Invoke( this, new object?[] { memberInfo, propertyBase })!; } catch (TargetInvocationException e) when (e.InnerException != null) { ExceptionDispatchInfo.Capture(e.InnerException).Throw(); throw; } }
IDbSetSource
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
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里面具体做了哪些事情。
public abstract class DbSet<TEntity> : IQueryable<TEntity>, IInfrastructure<IServiceProvider>, IListSource
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接口或者子类接口都会这样。
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
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方法中
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); }
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返回了我们本次的查询结果。
public virtual Func<QueryContext, TResult> CompileQuery<TResult>(Expression query, bool async) => Dependencies.QueryCompilationContextFactory .Create(async) .CreateQueryExecutor<TResult>(query);
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); } }
public class QueryCompiler : IQueryCompiler { private readonly IQueryContextFactory _queryContextFactory; private readonly ICompiledQueryCache _compiledQueryCache; private readonly ICompiledQueryCacheKeyGenerator _compiledQueryCacheKeyGenerator; private readonly IDatabase _database; private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger; private readonly Type _contextType; private readonly IEvaluatableExpressionFilter _evaluatableExpressionFilter; private readonly IModel _model; /// <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 QueryCompiler( IQueryContextFactory queryContextFactory, ICompiledQueryCache compiledQueryCache, ICompiledQueryCacheKeyGenerator compiledQueryCacheKeyGenerator, IDatabase database, IDiagnosticsLogger<DbLoggerCategory.Query> logger, ICurrentDbContext currentContext, IEvaluatableExpressionFilter evaluatableExpressionFilter, IModel model) { _queryContextFactory = queryContextFactory; _compiledQueryCache = compiledQueryCache; _compiledQueryCacheKeyGenerator = compiledQueryCacheKeyGenerator; _database = database; _logger = logger; _contextType = currentContext.Context.GetType(); _evaluatableExpressionFilter = evaluatableExpressionFilter; _model = model; } /// <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 query) { var queryContext = _queryContextFactory.Create(); query = ExtractParameters(query, queryContext, _logger); var compiledQuery = _compiledQueryCache .GetOrAddQuery( _compiledQueryCacheKeyGenerator.GenerateCacheKey(query, async: false), () => CompileQueryCore<TResult>(_database, query, _model, false)); return compiledQuery(queryContext); } /// <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 Func<QueryContext, TResult> CompileQueryCore<TResult>( IDatabase database, Expression query, IModel model, bool async) => database.CompileQuery<TResult>(query, async); /// <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 Func<QueryContext, TResult> CreateCompiledQuery<TResult>(Expression query) { query = ExtractParameters(query, _queryContextFactory.Create(), _logger, parameterize: false); return CompileQueryCore<TResult>(_database, query, _model, 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 TResult ExecuteAsync<TResult>(Expression query, CancellationToken cancellationToken = default) { var queryContext = _queryContextFactory.Create(); queryContext.CancellationToken = cancellationToken; query = ExtractParameters(query, queryContext, _logger); var compiledQuery = _compiledQueryCache .GetOrAddQuery( _compiledQueryCacheKeyGenerator.GenerateCacheKey(query, async: true), () => CompileQueryCore<TResult>(_database, query, _model, true)); return compiledQuery(queryContext); } /// <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 Func<QueryContext, TResult> CreateCompiledAsyncQuery<TResult>(Expression query) { query = ExtractParameters(query, _queryContextFactory.Create(), _logger, parameterize: false); return CompileQueryCore<TResult>(_database, query, _model, true); } /// <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 ExtractParameters( Expression query, IParameterValues parameterValues, IDiagnosticsLogger<DbLoggerCategory.Query> logger, bool parameterize = true, bool generateContextAccessors = false) { var visitor = new ParameterExtractingExpressionVisitor( _evaluatableExpressionFilter, parameterValues, _contextType, _model, logger, parameterize, generateContextAccessors); return visitor.ExtractParameters(query); } }
以上是一条简单的查询执行的一个过程,只是一个大概的一个方向,但总体没错,可能有些地方不是很细致,因为,这里东西太多了,就简略讲一下。
如何自定义批量增删改查替换自带的
在以前记得使用批量插入的时候,总觉得EF自带的很慢,3.1的时候用的,到现在都这么久了,不知道提升性能了没得,不过它的内部依旧和我写的例子 原理差不多,内部开启一个事物,然后循环添加,这里只是一个简单的例子,感兴趣的朋友,可以自己去进行扩展,在AddRange,还有UpdateRange等批量操作的都会进去到这里,commandBatches是我们所有需要进行批量操作的记录,connection是我们当前数据库的连接,最终只在Execute和ExecuteAsync里面去写自己的批量逻辑就行了。在上一章的代码中,还需要添加builder.Services.AddScoped<IBatchExecutor, Batch>();就可以实现自定义批量操作。
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篇幅较多,这里只做一个简单的介绍,后续继续会针对状态管理,更改,新增,等进行详细的讲解,此处 只讲一下大概、
private void SetEntityStates(IEnumerable<object> entities, EntityState entityState) { var stateManager = DbContextDependencies.StateManager; foreach (var entity in entities) { SetEntityState(stateManager.GetOrCreateEntry(entity), entityState); } }
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; }
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的源码讲解,可能有的地方依旧会讲得多一点,有的会提供一个大概的类,或者方法名称,如果有阅读过源码的大佬有什么建议,欢迎大佬们提出你们宝贵的意见。