使用EF Core Include连接ExpressionVisitor [英] Wire up ExpressionVisitor with EF Core Include
问题描述
我有一个 ExpressionVisitor
,它已添加到EF Core的 IQueryable< T>
中.除了Include方法之外,其他所有方法都工作正常.可能是因为它们将您的 IQueryable< T> .Provider
强制为 EntityQueryProvider
.
每当我尝试现在包括它时,都会导致多个查询,进而导致错误在上一个操作完成之前在此上下文上启动了第二个操作.不保证任何实例成员都是线程安全的." >
如何连接 ExpressionVisitor
,使其仍可与EF Core的Include功能配合使用?
我的问题类似于解决方案
更新(EF Core 3.x):
内部查询管道基础结构已更改.新的查询表达式预处理扩展点为处理方法.插入它需要替换IQueryTranslationPreprocessorFactory .例如
使用System.Linq.Expressions;命名空间Microsoft.EntityFrameworkCore.Query{公共类CustomQueryTranslationPreprocessor:RelationalQueryTranslationPreprocessor{公共CustomQueryTranslationPreprocessor(QueryTranslationPreprocessorDependencies依赖关系,RelationalQueryTranslationPreprocessorDependencies关系依赖关系,QueryCompilationContext queryCompilationContext):基础(依赖项,relationalDependencies,queryCompilationContext){}公共重写Expression Process(Expression query)=>base.Process(Preprocess(query));私有表达式预处理(表达式查询){//query = new YourExpressionVisitor().Visit(query);返回查询;}}公共类CustomQueryTranslationPreprocessorFactory:IQueryTranslationPreprocessorFactory{公共CustomQueryTranslationPreprocessorFactory(QueryTranslationPreprocessorDependencies依赖关系,RelationalQueryTranslationPreprocessorDependencies RelationalDependencies){依赖关系=依赖关系;RelationalDependencies = RelationalDependencies;}受保护的QueryTranslationPreprocessorDependencies依赖关系{}受保护的RelationalQueryTranslationPreprocessorDependencies RelationalDependencies;公共QueryTranslationPreprocessor创建(QueryCompilationContext queryCompilationContext)=>新的CustomQueryTranslationPreprocessor(Dependencies,RelationalDependencies,queryCompilationContext);}}
和
optionsBuilder.ReplaceService< IQueryTranslationPreprocessorFactory,CustomQueryTranslationPreprocessorFactory>();
原始:
显然,自定义查询提供程序不适合当前的EF Core可查询管道,因为几种方法( Include
, AsNoTracking
等)要求提供程序为EntityQueryProvider
.
在撰写本文时(EF Core 2.1.2),查询翻译过程涉及多种服务- IAsyncQueryProvider
, IQueryCompiler
, IQueryModelGenerator
和更多.它们都是可替换的,但是我看到的最容易拦截的地方是 IQueryModelGenerator
服务- ParseQuery
方法.
因此,请忽略自定义的 IQueryable
/ IQueryProvider
实现,使用以下类并将您的表达式访问者插入 Preprocess
方法内:
使用Microsoft.EntityFrameworkCore.Internal;使用Microsoft.EntityFrameworkCore.Query.Internal;使用Remotion.Linq;使用Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation;类CustomQueryModelGenerator:QueryModelGenerator{公共CustomQueryModelGenerator(INodeTypeProviderFactory nodeTypeProviderFactory,IEvaluatableExpressionFilter evaluatableExpressionFilter,ICurrentDbContext currentDbContext):base(nodeTypeProviderFactory,evaluatableExpressionFilter,currentDbContext){}公共重写QueryModel ParseQuery(Expression query)=>base.ParseQuery(Preprocess(query));私有表达式预处理(表达式查询){//返回新的YourExpressionVisitor().Visit(query);返回查询;}}
并在派生上下文 OnConfiguring
覆盖范围内替换相应的EF Core服务:
optionsBuilder.ReplaceService< IQueryModelGenerator,CustomQueryModelGenerator>();
缺点是它使用的是EF Core内部"内容,因此您应继续监视将来的更新中的更改.
I have an ExpressionVisitor
which I add to EF Core's IQueryable<T>
. Everything works fine except the Include methods. Probably because they enforce your IQueryable<T>.Provider
to be an EntityQueryProvider
.
Whenever I try to Include now it results in multiple queries which in turn results in the error "A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.".
How can I wire up my ExpressionVisitor
so it still works with EF Core's Include functionality?
My issue is similar to this one except for EF Core instead of EF.
I hook up my ExpressionVisitor
by calling it on the DbSet:
return new Translator<TEntity>(
_dbSet
.AsNoTracking());
This is my Translator
class:
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;
}
And this is my TranslatorProvider<T>
class (I've taken out the non-relevant Visit methods to shorten the post):
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);
}
}
}
Update (EF Core 3.x):
The internal query pipeline infrastructure has changed. The new query expression preprocessing extension point is QueryTranslationPreprocessor class - Process method. Plugging it in requires replacing the IQueryTranslationPreprocessorFactory. e.g.
using System.Linq.Expressions;
namespace Microsoft.EntityFrameworkCore.Query
{
public class CustomQueryTranslationPreprocessor : RelationalQueryTranslationPreprocessor
{
public CustomQueryTranslationPreprocessor(QueryTranslationPreprocessorDependencies dependencies, RelationalQueryTranslationPreprocessorDependencies relationalDependencies, QueryCompilationContext queryCompilationContext)
: base(dependencies, relationalDependencies, queryCompilationContext) { }
public override Expression Process(Expression query) => base.Process(Preprocess(query));
private Expression Preprocess(Expression query)
{
// query = new YourExpressionVisitor().Visit(query);
return query;
}
}
public class CustomQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory
{
public CustomQueryTranslationPreprocessorFactory(QueryTranslationPreprocessorDependencies dependencies, RelationalQueryTranslationPreprocessorDependencies relationalDependencies)
{
Dependencies = dependencies;
RelationalDependencies = relationalDependencies;
}
protected QueryTranslationPreprocessorDependencies Dependencies { get; }
protected RelationalQueryTranslationPreprocessorDependencies RelationalDependencies;
public QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext)
=> new CustomQueryTranslationPreprocessor(Dependencies, RelationalDependencies, queryCompilationContext);
}
}
and
optionsBuilder.ReplaceService<IQueryTranslationPreprocessorFactory, CustomQueryTranslationPreprocessorFactory>();
Original:
Apparently custom query providers don't fit in the current EF Core queryable pipeline, since several methods (Include
, AsNoTracking
etc.) require provider to be EntityQueryProvider
.
At the time of writing (EF Core 2.1.2), the query translation process involves several services - IAsyncQueryProvider
, IQueryCompiler
, IQueryModelGenerator
and more. All they are replaceable, but the easiest place for interception I see is the IQueryModelGenerator
service - ParseQuery
method.
So, forget about custom IQueryable
/ IQueryProvider
implementation, use the following class and plug your expression visitor inside Preprocess
method:
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;
}
}
and replace the corresponding EF Core service inside your derived context OnConfiguring
override:
optionsBuilder.ReplaceService<IQueryModelGenerator, CustomQueryModelGenerator>();
The drawback is that this is using EF Core "internal" stuff, so you should keep monitoring for changes in the future updates.
这篇关于使用EF Core Include连接ExpressionVisitor的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!