使用EF Core Include连接ExpressionVisitor

编程入门 行业动态 更新时间:2024-10-09 18:18:36
本文介绍了使用EF Core Include连接ExpressionVisitor的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

我有一个 ExpressionVisitor ,它已添加到EF Core的 IQueryable< T> 中.除了Include方法之外,其他所有方法都工作正常.可能是因为它们将您的 IQueryable< T> .Provider 强制为 EntityQueryProvider .

每当我尝试现在包括它时,都会导致多个查询,进而导致错误在上一个操作完成之前在此上下文上启动了第二个操作.不保证任何实例成员都是线程安全的."

如何连接 ExpressionVisitor ,使其仍可与EF Core的Include功能配合使用?

我的问题类似于这一个,除了EF Core而不是EF.

我通过在DbSet上调用 ExpressionVisitor 来连接它:

返回新的Translator< TEntity>(_dbSet.AsNoTracking());

这是我的 Translator 类:

公共类翻译器< T>:IOrderedQueryable< T>{私有只读表达式_expression;私有只读TranslatorProvider< T>_provider;公共翻译器(IQueryable源){_expression = Expression.Constant(this);_provider = new TranslatorProvider< T>(源);}公共翻译器(IQueryable源,表达式表达式){如果(表达式== null){抛出新的ArgumentNullException(nameof(expression));}_expression =表达式;_provider = new TranslatorProvider< T>(源);}公共IEnumerator< T>GetEnumerator(){返回((IEnumerable< T>)_ provider.ExecuteEnumerable(_expression)).GetEnumerator();}IEnumerator IEnumerable.GetEnumerator(){返回_provider.ExecuteEnumerable(_expression).GetEnumerator();}public Type ElementType =>typeof(T);public Expression Expression =>_表达;public IQueryProvider Provider =>_provider;}

这是我的 TranslatorProvider< T> 类(我删除了不相关的Visit方法以缩短帖子):

公共类TranslatorProvider< T>:ExpressionVisitor,IQueryProvider{私有只读IQueryable _source;公共TranslatorProvider(IQueryable源){如果(来源==空){抛出新的ArgumentNullException(nameof(source));}_source =源;}公共IQueryable< TElement>CreateQuery< TElement>(表达式){如果(表达式== null){抛出新的ArgumentNullException(nameof(expression));}返回新的Translator< TElement>(_ source,expression);}公共IQueryable CreateQuery(表达式){如果(表达式== null){抛出新的ArgumentNullException(nameof(expression));}var elementType = expression.Type.GetGenericArguments().First();var result =(IQueryable)Activator.CreateInstance(typeof(Translator<>).MakeGenericType(elementType),_source,表达式);返回结果;}公共TResult Execute< TResult>(表达式){如果(表达式== null){抛出新的ArgumentNullException(nameof(expression));}var结果=(此为IQueryProvider).Execute(表达式);返回(TResult)结果;}公共对象Execute(表达式表达式){如果(表达式== null){抛出新的ArgumentNullException(nameof(expression));}var translation = Visit(expression);返回_source.Provider.Execute(翻译);}内部IEnumerable ExecuteEnumerable(表达式表达式){如果(表达式== null){抛出新的ArgumentNullException(nameof(expression));}var translation = Visit(expression);返回_source.Provider.CreateQuery(translated);}受保护的重写Expression VisitConstant(ConstantExpression节点){如果(node.Type == typeof(Translator< T>))){返回_source.Expression;}别的{返回base.VisitConstant(node);}}}

解决方案

更新(EF Core 3.x):

内部查询管道基础结构已更改.新的查询表达式预处理扩展点为 QueryTranslationPreprocessor 类-处理方法.插入它需要替换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

本文发布于:2023-11-15 06:24:08,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1591930.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:Core   EF   ExpressionVisitor   Include

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!