【源码解读(二)】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);
    }
}
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;
 }
  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查询拦截 

posted @ 2023-11-10 15:03  四处观察  阅读(454)  评论(1编辑  收藏  举报