我有一个ExpressionVisitor
,我将其添加到EF Core的IQueryable<T>
中。除了Include方法之外,其他所有方法都工作正常。可能是因为它们将您的IQueryable<T>.Provider
强制为EntityQueryProvider
。
每当我尝试现在包括它时,都会导致多个查询,进而导致错误“在上一个操作完成之前在此上下文上启动了第二个操作。不保证任何实例成员都是线程安全的。” >
如何连接ExpressionVisitor
,使其仍然可以与EF Core的包含功能一起使用?
我的问题类似于this one,除了EF Core而不是EF。
我通过在DbSet上调用ExpressionVisitor
来连接它:
return new Translator<TEntity>(
_dbSet
.AsNoTracking());
这是我的Translator
班:
public class Translator<T> : IOrderedQueryable<T>
{
private readonly Expression _expression;
private readonly TranslatorProvider<T> _provider;
public Translator(IQueryable source)
{
_expression = Expression.Constant(this);
_provider = new TranslatorProvider<T>(source);
}
public Translator(IQueryable source, Expression expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
_expression = expression;
_provider = new TranslatorProvider<T>(source);
}
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)_provider.ExecuteEnumerable(_expression)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _provider.ExecuteEnumerable(_expression).GetEnumerator();
}
public Type ElementType => typeof(T);
public Expression Expression => _expression;
public IQueryProvider Provider => _provider;
}
这是我的TranslatorProvider<T>
类(我删除了不相关的Visit方法来缩短帖子):
public class TranslatorProvider<T> : ExpressionVisitor, IQueryProvider
{
private readonly IQueryable _source;
public TranslatorProvider(IQueryable source)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
_source = source;
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
return new Translator<TElement>(_source, expression);
}
public IQueryable CreateQuery(Expression expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
var elementType = expression.Type.GetGenericArguments().First();
var result = (IQueryable) Activator.CreateInstance(typeof(Translator<>).MakeGenericType(elementType),
_source, expression);
return result;
}
public TResult Execute<TResult>(Expression expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
var result = (this as IQueryProvider).Execute(expression);
return (TResult) result;
}
public object Execute(Expression expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
var translated = Visit(expression);
return _source.Provider.Execute(translated);
}
internal IEnumerable ExecuteEnumerable(Expression expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}
var translated = Visit(expression);
return _source.Provider.CreateQuery(translated);
}
protected override Expression VisitConstant(ConstantExpression node)
{
if (node.Type == typeof(Translator<T>))
{
return _source.Expression;
}
else
{
return base.VisitConstant(node);
}
}
}
答案 0 :(得分:2)
显然,自定义查询提供程序不适合当前的EF Core可查询管道,因为几种方法(Include
,AsNoTracking
等)要求提供程序必须为EntityQueryProvider
。
在撰写本文时(EF Core 2.1.2),查询翻译过程涉及多种服务-IAsyncQueryProvider
,IQueryCompiler
,IQueryModelGenerator
等。它们都是可替换的,但是我看到的最容易拦截的地方是IQueryModelGenerator
服务-ParseQuery
方法。
因此,不用考虑自定义IQueryable
/ IQueryProvider
的实现,请使用以下类,并将表达式访问者插入Preprocess
方法内:
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Remotion.Linq;
using Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation;
class CustomQueryModelGenerator : QueryModelGenerator
{
public CustomQueryModelGenerator(INodeTypeProviderFactory nodeTypeProviderFactory, IEvaluatableExpressionFilter evaluatableExpressionFilter, ICurrentDbContext currentDbContext)
: base(nodeTypeProviderFactory, evaluatableExpressionFilter, currentDbContext)
{ }
public override QueryModel ParseQuery(Expression query) => base.ParseQuery(Preprocess(query));
private Expression Preprocess(Expression query)
{
// return new YourExpressionVisitor().Visit(query);
return query;
}
}
并在派生上下文OnConfiguring
中替换相应的EF Core服务覆盖:
optionsBuilder.ReplaceService<IQueryModelGenerator, CustomQueryModelGenerator>();
缺点是它使用的是EF Core“内部”内容,因此您应继续监视将来的更新中的更改。