Условно игнорировать поля с выбором LINQ

Мне не удалось найти примеры, в которых вы можете условно исключить поля на основе переменной в проекции выбора в LINQ, см. также LINQ: выберите объект и измените некоторые свойства без создания нового объекта.

Позвольте мне немного рассказать о том, чего я пытаюсь достичь. Я хочу ограничить некоторые поля в DTO, устанавливаемые из модели, в зависимости от того, может ли пользователь редактировать данные (например, поле комментария). Например, следующий выбор с делегатом с именем CustomerView.

var qry = _ctx.Customer.Select(CustomerView(User.IsInRole("Editor")));

Модель Customer имеет свойство навигации Orders, и следующая функция преобразует данные в объект DTO CusomerViewModel.

private Expression<Func<Customer, CustomerViewModel>> CustomerView(bool isEditor) {
    return c => new CustomerViewModel
    {
        Id = c.Id,
        Name = c.Name,
        Comment = isEditor ? c.Comment : null,
        OrderCount = c.Orders.Count()
    };
}

Это сгенерирует SQL, например CASE WHEN @__isEditor_0 = TRUE THEN Comment ELSE NULL, который работает, но я бы предпочел, чтобы выражение даже не генерировалось, т.е. поле оставалось по умолчанию. Это простой вариант использования, но если бы я хотел сделать то же самое с полем OrderCount, подзапрос SQL все равно был бы включен.

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

Я вижу вопросы, в которых динамический LINQ используется для предложений where, но не так много для выбора. Возможен ли такой подход?

Изменить: можно ли вручную удалить поля из дерева выражений после использования выбора, возможно, с помощью метода расширения?


person Mark G    schedule 11.03.2020    source источник
comment
Я не думаю, что это возможно, вам, скорее всего, потребуется использовать сторонние инструменты, такие как AutoMapper, для условного сопоставления.   -  person hallie    schedule 11.03.2020
comment
@hallie Знаете ли вы какие-нибудь примеры? Аналогичный вопрос был задан в разделе Как условно игнорировать сопоставление для члена без отвечать.   -  person Mark G    schedule 11.03.2020
comment
docs.automapper.org/en/latest/   -  person Lucian Bargaoanu    schedule 11.03.2020
comment
@LucianBargaoanu Спасибо за документы по явному расширению для AutoMapper. Это похоже на создание другой проекции, такой как _ctx.Customer.Select(CustomerView(User.IsInRole("Editor"))) .Select(c => new { c.Id, c.Name, c.OrderCount }); для этого примера.   -  person Mark G    schedule 11.03.2020
comment
Это более гибко, потому что вы можете решить во время выполнения, что извлекать. Но да, похожи :)   -  person Lucian Bargaoanu    schedule 11.03.2020
comment
Возможно, вы сможете использовать метод .Concat LINQ, который преобразуется в UNION ALL; в первую последовательность включают поля, а во вторую последовательность не включают поля.   -  person Zev Spitz    schedule 11.03.2020


Ответы (1)


Используя LINQKit, я смог добиться желаемого результата, добавив AsExpandable() для выбора.

var qry = _ctx.Customer.AsExpandable().Select(CustomerView(User.IsInRole("Editor")));

Затем добавьте выражение для поля комментария и вызовите Invoke() в назначении поля.

private Expression<Func<Customer, CustomerViewModel>> CustomerView(bool isEditor) {
    Expression<Func<Customer, string>> exprComment;
    if (isEditor)
        exprComment = c => c.Comment;
    else
        exprComment = c => null;

    return c => new CustomerViewModel
    {
        Id = c.Id,
        Name = c.Name,
        Comment = exprComment.Invoke(c),
        OrderCount = c.Orders.Count()
    };
}

Похоже, это работает, но мне все равно было бы интересно услышать о любых альтернативных подходах.

person Mark G    schedule 12.03.2020