Выполнить IQueryable на любом dbContext EF 3.1 - PullRequest
0 голосов
/ 18 марта 2020

Изначально: у меня есть несколько контекстов разных баз данных. Каждый из них представляет собой отдельную базу данных на определенный период времени. Чтобы отделить исторические данные от фактических данных для ускорения запросов.

Мне нужно как-то унифицировать эти контексты для одного запроса. Чтобы все запросы работали как раньше. Для этого я создал класс, который хранит в себе контексты, реализует все методы Linq. В каждом методе входящий фильтр применяется к каждому запросу в каждом контексте. После ToList() я делаю SelectMany(x => x).

Например:

Где:

public IFewQueryable<TSource> Where(Expression<Func<TSource, bool>> predicate)
{
    var dictionary = new Dictionary<IQueryable<TSource>, DbContext>();
    foreach (var (query, context) in QueriesToContexts)
    {
        dictionary.Add(query.Where(predicate), context);
    }

    return new FewQueryable<TSource>(dictionary);
}

GroupJoin:

public IFewQueryable<TResult> GroupJoin<TInner, TKey, TResult>(IFewQueryable<TInner> inner, Expression<Func<TSource, TKey>> outerKeySelector, Expression<Func<TInner, TKey>> innerKeySelector, Expression<Func<TSource, IEnumerable<TInner>, TResult>> resultSelector) 
            where TResult : class 
            where TInner : class
{
    var dictionary = new Dictionary<IQueryable<TResult>, DbContext>();
    var innerCasted = inner as FewQueryable<TInner>;

    for (var i = 0; i < QueriesToContexts.Count; i++)
    {
        var (queryOriginal, contextOriginal) = QueriesToContexts.Skip(i).First();

        dictionary.Add(queryOriginal.GroupJoin(contextOriginal.Set<TInner>(), outerKeySelector, innerKeySelector, resultSelector), contextOriginal);
    }

    return new FewQueryable<TResult>(dictionary);
}

И у меня есть одна проблема. В ядре 3.1 Union может работать на SQL сервере, поэтому я пробую использовать метод Union. но обычно Union включает запросы, которые состоят из других запросов, и я больше не могу их использовать:

contextOriginal.Set<TInner>()

Но мне нужно каким-то образом выполнять запросы в одном контексте. В EF версии 6 и ранее можно было изменить контекст выполнения, но в ядре EF, насколько я знаю, нет. Поэтому я попытался сделать это через Expression:

public IFewQueryable<TSource> Union(IQueryable<TSource> inner)
{
    var dictionary = new Dictionary<IQueryable<TSource>, DbContext>();

    foreach (var (query, context) in QueriesToContexts)
    {
        var expression = ConvertQuery(inner.Expression as MethodCallExpression, context);
        var newQuery = query.Union(query.Provider.CreateQuery<TSource>(expression));

        dictionary.Add(query.Union(newQuery), context);
    }

    return new FewQueryable<TSource>(dictionary);
}

private MethodCallExpression ConvertQuery(MethodCallExpression expression, DbContext context)
{
    var args = new Expression[expression.Arguments.Count];
    expression.Arguments.CopyTo(args, 0);

    for (var i = 0; i<args.Length; i++)
    {
        if (args[i].GetType().BaseType == typeof(MethodCallExpression))
        {
            args[i] = ConvertQuery(args[i] as MethodCallExpression, context);
        }

        if (args[i].GetType() == typeof(ConstantExpression))
        {
            var castedExpression = args[i] as ConstantExpression;

            var setMethod = typeof(DbContext).GetMethod("Set").MakeGenericMethod(castedExpression.Type.GenericTypeArguments[0]);
            var valueDbSet = setMethod.Invoke(context, null);

            var asQueryable = typeof(System.Linq.Queryable).GetMethods().Where(x => x.Name == "AsQueryable")
                .ToList()[0].MakeGenericMethod(castedExpression.Type.GenericTypeArguments[0]);
            var casted = asQueryable.Invoke(null, new[] {valueDbSet });
            var constant = Expression.Constant(casted);

            args[i] = constant;
        }
    }
}

Но я получаю исключение:

{{query}}  by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
       at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
       at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
       at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
       at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
       at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
       at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
       at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
       at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
       at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
       at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
       at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
       at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
       at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
       at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
       at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
       at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
       at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
       at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
       at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
       at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
       at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
       at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
       at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
       at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
       at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.Expand(Expression query)
       at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.Process(Expression query)
       at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
       at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
       at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
       at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
       at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
       at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
       at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
       at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
       at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
       at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
       at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
...