首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用EF核心连接ExpressionVisitor包括

用EF核心连接ExpressionVisitor包括
EN

Stack Overflow用户
提问于 2018-08-28 11:05:23
回答 1查看 1.8K关注 0票数 4

我有一个ExpressionVisitor,我添加到EF核心的IQueryable<T>。除了包含方法之外,一切都很好。可能是因为他们强制您的IQueryable<T>.Provider成为EntityQueryProvider

每当我尝试现在包含时,它就会导致多个查询,从而导致错误“在以前的操作完成之前,在此上下文上启动了第二个操作。任何实例成员都不能保证线程安全”。

我如何连接我的ExpressionVisitor,使它仍然与EF核心的包括功能?

我的问题类似于这一个,除了EF核心而不是EF。

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

代码语言:javascript
复制
        return new Translator<TEntity>(
            _dbSet
                .AsNoTracking());

这是我的Translator课程:

代码语言:javascript
复制
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>类(我已经拿出了一些不相关的访问方法来缩短帖子):

代码语言:javascript
复制
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);
        }
    }
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-08-28 13:39:54

更新(EF核心3.x):

内部查询管道基础结构已经更改。新的查询表达式预处理扩展点是QueryTranslationPreprocessor类- 流程方法.插入它需要替换IQueryTranslationPreprocessorFactory。例如:

代码语言:javascript
复制
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);
    }
}

代码语言:javascript
复制
optionsBuilder.ReplaceService<IQueryTranslationPreprocessorFactory, CustomQueryTranslationPreprocessorFactory>();

原版:

显然,自定义查询提供程序不适合当前的EF核心查询管道,因为有几种方法(IncludeAsNoTracking等)。要求提供程序为EntityQueryProvider

在编写本报告时(EFCore2.1.2),查询翻译过程涉及几个服务-- IAsyncQueryProviderIQueryCompilerIQueryModelGenerator等。它们都是可替换的,但我看到的最容易拦截的地方是IQueryModelGenerator服务- ParseQuery方法。

因此,忘记定制的IQueryable / IQueryProvider实现,使用以下类并在Preprocess方法中插入表达式访问者:

代码语言:javascript
复制
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核心服务:

代码语言:javascript
复制
optionsBuilder.ReplaceService<IQueryModelGenerator, CustomQueryModelGenerator>();

缺点是,这是使用EF核心“内部”的东西,所以你应该继续监测变化,在未来的更新。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52056354

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档