Подключите ExpressionVisitor с EF Core Включить - PullRequest
0 голосов
/ 28 августа 2018

У меня есть ExpressionVisitor, который я добавляю к EF Core's IQueryable<T>. Все отлично работает, кроме методов Include. Возможно, потому что они заставляют ваш IQueryable<T>.Provider быть EntityQueryProvider.

Всякий раз, когда я пытаюсь включить параметр «Включить сейчас», это приводит к нескольким запросам, что, в свою очередь, приводит к ошибке «Вторая операция была запущена в этом контексте до завершения предыдущей операции. Любые члены экземпляра не гарантированно защищены от потоков».

Как подключить мой ExpressionVisitor, чтобы он по-прежнему работал с функцией включения EF Core?

Моя проблема похожа на эту за исключением EF Core вместо EF.

Я подключаю свой ExpressionVisitor, вызывая его на DbSet:

        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);
        }
    }
}

1 Ответ

0 голосов
/ 28 августа 2018

Очевидно, что пользовательские поставщики запросов не вписываются в текущий запрашиваемый конвейер 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;
    }
}

и замените соответствующую службу EF Core в производном контексте. OnConfiguring override:

optionsBuilder.ReplaceService<IQueryModelGenerator, CustomQueryModelGenerator>();

Недостатком является то, что в нем используется «внутреннее» содержимое EF Core, поэтому вы должны следить за изменениями в будущих обновлениях.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...