引言
書接上回,【原始碼解讀(一)】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的原始碼講解,可能有的地方依舊會講得多一點,有的會提供一個大概的類,或者方法名稱,如果有閱讀過原始碼的大佬有什麼建議,歡迎大佬們提出你們寶貴的意見。