【原始碼解讀(二)】EFCORE原始碼解讀之查詢都做了什麼以及如何自定義批次插入

四處觀察發表於2023-11-10

引言

    書接上回,【原始碼解讀(一)】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);
       }
   }

 

【原始碼解讀(二)】EFCORE原始碼解讀之查詢都做了什麼以及如何自定義批次插入
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);
    }
}
View Code

  以上是一條簡單的查詢執行的一個過程,只是一個大概的一個方向,但總體沒錯,可能有些地方不是很細緻,因為,這裡東西太多了,就簡略講一下。

如何自定義批次增刪改查替換自帶的

    在以前記得使用批次插入的時候,總覺得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;
 }
【原始碼解讀(二)】EFCORE原始碼解讀之查詢都做了什麼以及如何自定義批次插入
  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;
  }
View Code

結束

    關於EFCore的原始碼講解,有時候也不知道怎麼講,因為它不像asp.net core的有序,所以導致講的時候不知道怎麼講,後續,會繼續出關於對EFCORE的原始碼講解,可能有的地方依舊會講得多一點,有的會提供一個大概的類,或者方法名稱,如果有閱讀過原始碼的大佬有什麼建議,歡迎大佬們提出你們寶貴的意見。

【原始碼解讀(一)】EFCORE原始碼解讀之建立DBContext查詢攔截 

相關文章