Заявление LINQ не переводится - PullRequest
2 голосов
/ 29 января 2020

У меня есть следующий код, содержащий операторы LINQ:

public async Task<HashSet<long>> GetMembersRecursive(IEnumerable<long> groupIds)
{
    var containsGroupId = InExpression<Group>("Id", groupIds);
    var containsParentId = InExpression<RecursiveGroupModel>("ParentId", groupIds);

    var groupIdsArray = groupIds as long[] ?? groupIds.ToArray();
    return new HashSet<long>(await MyContext
        .Groups
        .Where(containsGroupId)
        .Select(a => new
        {
            Members = MyContext
                .ViewWithRecursiveGroups
                .Where(containsParentId)
                .SelectMany(c => c.Group.Members)
                .Union(a.Members)
                .Where(b => !b.User.IsActive)
        })
        .SelectMany(a => a.Members.Select(b => b.MemberId))
        .Distinct()
        .ToListAsync());
}

private static Expression<Func<T, bool>> InExpression<T>(string propertyName, IEnumerable<long> array)
{
    var p = Expression.Parameter(typeof(T), "x");
    var contains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
        .Single(x => x.Name == "Contains" && x.GetParameters().Length == 2)
        .MakeGenericMethod(typeof(long));
    var property = Expression.PropertyOrField(p, propertyName);
    var body = Expression.Call(
        contains
        , Expression.Constant(array)
        , property
    );

    return Expression.Lambda<Func<T, bool>>(body, p);
}

Я получаю ошибку:

Microsoft.EntityFrameworkCore: Processing of the LINQ expression 'DbSet<RecursiveGroupModel>
     .Where(b => __groupIdsArray_1
         .Contains(b.ParentId))
     .SelectMany(c => c.Group.GroupMembers)
     .Union((MaterializeCollectionNavigation(
         navigation: Navigation: Group.GroupMembers,
         subquery: (NavigationExpansionExpression
             Source: DbSet<GroupMember>
                 .Where(l0 => EF.Property<Nullable<long>>(l, "Id") != null && EF.Property<Nullable<long>>(l, "Id") == EF.Property<Nullable<long>>(l0, "GroupId1"))
             PendingSelector: l0 => (NavigationTreeExpression
                 Value: (EntityReference: GroupMember)
                 Expression: l0)
         )
             .Where(i => EF.Property<Nullable<long>>((NavigationTreeExpression
                 Value: (EntityReference: Group)
                 Expression: l), "Id") != null && EF.Property<Nullable<long>>((NavigationTreeExpression
                 Value: (EntityReference: Group)
                 Expression: l), "Id") == EF.Property<Nullable<long>>(i, "GroupId1"))))' 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.

Представление:

CREATE VIEW [dbo].[View_WithRecursiveGroups] AS
     WITH RecursiveGroups (GroupId, ParentId) AS
    (
        SELECT Id, ParentId
        FROM Group
        WHERE ParentId IS NOT NULL
        UNION ALL
        SELECT Group.Id, t.ParentId
        FROM GroupTree t
        JOIN Group ON t.GroupId = Group.ParentId
    )

    SELECT * FROM RecursiveGroups

Извинения в заранее, если некоторые имена переменных не совпадают - мне пришлось провести санитарную обработку перед публикацией.

Я понимаю, что он не может преобразовать код в SQL, и поэтому он просит меня перечислить рано или переписать так, чтобы он мог быть переведен. Я устал переставлять запрос и разбивать его на более мелкие запросы, но SelectMany в рекурсивном представлении, по-видимому, невозможно преобразовать в SQL.

Есть ли способ заставить это работать в база данных? Или я поступаю совершенно неправильно?

Ответы [ 3 ]

0 голосов
/ 29 января 2020

Да, добро пожаловать в удивительный мир EfCore 3.1, где все, что вы можете сделать, это «Hello world».

Ваш запрос имеет различные «проблемы», потому что EfCore на самом деле не выполняет обработку LINQ, за исключением очень простых случаев .

.Union (a.Members)

Невозможно преобразовать для запуска на стороне сервера, а обработка на стороне клиента не включена. Ваш единственный выбор:

  • Принудительное выполнение сервера для обеих частей (с использованием AsEnumerable), а затем Union на клиенте. Это работает только в том случае, если вы не используете его как часть более крупного оператора (т. Е. Пересекаете), в противном случае это «время извлечения всех данных для клиента», и это не хорошо.

В текущем на данный момент я могу только посоветовать вам выбросить EfCore и использовать EntityFramework, который - согласно framework 3.1 - снова доступен. Или используйте Entity Framework Classi c, который является портом, который работает в Netstandard 2.0 и имеет глобальные фильтры запросов (которые являются ОДНОЙ особенностью EfCore, которая мне нравится). Наконец-то это то, к чему я сейчас клоню, потому что - хорошо - «лучше, но без каких-либо функций и не работает» для меня это не урезание.

Будет ли расширен EfCore (кажется, они не видят это как исправление) обрабатывать что-либо, кроме самых основных c LINQ-операторов (а иногда даже не тех), неизвестно на данный момент - многие изменения в 3.1 весьма обескураживают.

Вы МОЖЕТЕ быть в состоянии переместить это в представления и т. д. c. - но вы можете довольно быстро выяснить, что EfCore имеет еще больше ограничений, и поддержание всех представлений также становится довольно напряженным. Я сталкиваюсь с серьезными проблемами из-за того, что не могу поставить любое условие перед любой проекцией даже в самых простых случаях. И даже простые ошибки комментируются тем, что «нам неудобно менять конвейер, пожалуйста, подождите 5-ю версию в ноябре». Пример? https://github.com/dotnet/efcore/issues/15279.

0 голосов
/ 29 января 2020

дают, если вы хотите преобразовать это представление в Linq ...


CREATE VIEW [dbo].[View_WithRecursiveGroups] AS
     WITH RecursiveGroups (GroupId, ParentId) AS
    (
        SELECT Id, ParentId
        FROM Group
        WHERE ParentId IS NOT NULL
        UNION ALL
        SELECT Group.Id, t.ParentId
        FROM GroupTree t
        JOIN Group ON t.GroupId = Group.ParentId
    )

var data1 = db.Group.where(x=>x.ParentId != nul)
            .Select(x=>new {x.Id, x.ParentId})
            .Tolist()

var data2 = (from g in db.Groups
            join gt in db.GroupTree on g.ParentId equals gt.GroupId
            select new { d.Id, ParentId })
            .ToList();

, создайте класс, перепроверяющий данные, и запрос будет возвращен как Список известных введите и просто объедините два списка.

linqpad - очень полезный инструмент для изучения того, как создать linq, который даст вам sql, который вы хотите.

0 голосов
/ 29 января 2020

В качестве альтернативы вы можете использовать необработанный запрос sql. В Entity Framework Code нам нужно определить класс POCO и DbSet для этого класса. В вашем случае вам нужно будет определить YourClass:

public DbQuery<YourClass> YourClasses { get; set; }

и код для выполнения:

var result = context.YourClasses.FromSql("YOURSQL_SCRIPT").ToList();
var asyncresult = await context.YourClasses.FromSql("YOURSQL_SCRIPT").ToListAsync();
...