引言

    书接上回,【源码解读(一)】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里面去写自己的批量逻辑就行了。

 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的源码讲解,可能有的地方依旧会讲得多一点,有的会提供一个大概的类,或者方法名称,如果有阅读过源码的大佬有什么建议,欢迎大佬们提出你们宝贵的意见。

【源码解读(一)】EFCORE源码解读之创建DBContext查询拦截

【源码解读(二)】EFCORE源码解读之查询都做了什么以及如何自定义批量插入的更多相关文章

  1. spring源码解析(二) 结合源码聊聊FactoryBean

    一.什么是FactoryBean FactoryBean是由spring提供的用来让用户可以自定bean创建的接口:实现该接口可以让你的bean不用经过spring复杂的bean创建过程,但同时也能做 ...

  2. RxJava2源码解析(二)

    title: RxJava2源码解析(二) categories: 源码解析 tags: 源码解析 rxJava2 前言 本篇主要解析RxJava的线程切换的原理实现 subscribeOn 首先, ...

  3. jQuery.Callbacks 源码解读二

    一.参数标记 /* * once: 确保回调列表仅只fire一次 * unique: 在执行add操作中,确保回调列表中不存在重复的回调 * stopOnFalse: 当执行回调返回值为false,则 ...

  4. DRF(1) - REST、DRF(View源码解读、APIView源码解读)

    一.REST 1.什么是编程? 数据结构和算法的结合. 2.什么是REST? 首先回顾我们曾经做过的图书管理系统,我们是这样设计url的,如下: /books/ /get_all_books/ 访问所 ...

  5. REST、DRF(View源码解读、APIView源码解读)

    一 . REST            前言 1 . 编程 : 数据结构和算法的结合 .小程序如简单的计算器,我们输入初始数据,经过计算,得到最终的数据,这个过程中,初始数据和结果数据都是数据,而计算 ...

  6. Restful 1 -- REST、DRF(View源码解读、APIView源码解读)及框架实现

    一.REST 1.什么是编程? 数据结构和算法的结合 2.什么是REST? - url用来唯一定位资源,http请求方式来区分用户行为 首先回顾我们曾经做过的图书管理系统,我们是这样设计url的,如下 ...

  7. 通过解读 WPF 触摸源码,分析 WPF 插拔设备触摸失效的问题(问题篇)

    在 .NET Framework 4.7 以前,WPF 程序的触摸处理是基于操作系统组件但又自成一套的,这其实也为其各种各样的触摸失效问题埋下了伏笔.再加上它出现得比较早,触摸失效问题也变得更加难以解 ...

  8. 【一起学源码-微服务】Nexflix Eureka 源码十三:Eureka源码解读完结撒花篇~!

    前言 想说的话 [一起学源码-微服务-Netflix Eureka]专栏到这里就已经全部结束了. 实话实说,从最开始Eureka Server和Eureka Client初始化的流程还是一脸闷逼,到现 ...

  9. 解读 v8 排序源码

    前言 v8 是 Chrome 的 JavaScript 引擎,其中关于数组的排序完全采用了 JavaScript 实现. 排序采用的算法跟数组的长度有关,当数组长度小于等于 10 时,采用插入排序,大 ...

  10. 手牵手,从零学习Vue源码 系列二(变化侦测篇)

    系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...

随机推荐

  1. [爬虫]1.2.2 CSS选择器

    CSS (Cascading Style Sheets) 是一种样式表语言,用于描述HTML元素的样式.CSS选择器是CSS规则的一部分,它决定了CSS规则应用于哪些元素.在网络爬虫的开发中,我们经常 ...

  2. 【JMeter】JMeter添加插件

    JMeter添加插件 目录 JMeter添加插件 一.前言 二.插件管理器 三.推荐插件 1.Custom Thread Groups (1)Ultmate Thread Group (2)Stepp ...

  3. LCD与OLED的相爱相杀

    目前市面的显示技术主要分为LCD与OLED. 本文主要记录对LCD与OLED的学习. 导言:介绍一些专业名词和术语. 像素点:是指在由一个数字序列表示的图像中的一个最小单位,称为像素. 一张图片在显示 ...

  4. vue + canvas 实现涂鸦面板

    前言 专栏分享:vue2源码专栏,vue router源码专栏,玩具项目专栏,硬核 推荐 欢迎各位 ITer 关注点赞收藏 此篇文章用于记录柏成从零开发一个canvas涂鸦面板的历程,最终效果如下: ...

  5. buu-(ACTF新生赛2020)usualCrypt

    base64的常用套路了 文件直接给base,我大胆盲猜base64: 先进sub-401080函数康康: 先看byte-40e0a0 这个很明显了,然后看上面的函数 进这连个地址发现是base64加 ...

  6. 使用C#创建安装Windows服务程序(最全教程)

    开发语言:C# 开发环境: Visual Studio 2022 微软官方文档:https://learn.microsoft.com/zh-cn/dotnet/framework/windows-s ...

  7. Linux 内核音频子系统调试

    debugfs 文件系统 debugfs 可以为 Linux 内核各个模块的分析调试,提供许多信息,如音频子系统的 ASoC,以及 tracing 等.debugfs 文件系统可以通过命令行工具挂载, ...

  8. 《Kali渗透基础》01. 介绍

    @ 目录 1:渗透测试 1.1:安全问题的根源 1.2:安全目标 1.3:渗透测试 1.4:标准 2:Kali 2.1:介绍 2.2:策略 2.3:安装 3:Kali 初步设置 3.1:远程连接 3. ...

  9. C# object类型与dynamic类型的使用

    获取动态变化的类型属性 例: var类型的参数a中包含属性Name或Age 获取这个不固定的数据 首先验证a中存在的是什么属性 /// <summary> /// 验证object类型是否 ...

  10. 接口自动化测试项目 | IHRM登录接口自动化测试

    项目内容如下: ### 需求- 地址:http://ihrm-java.itheima.net/#/login- 测试接口: - 登录接口:针对登录的13个cases### 技术 - V1:pytho ...