Введите поддержку членов в LINQ-to-Entities? - PullRequest
9 голосов
/ 12 октября 2011

У меня есть проект MVC3, использующий модель Entity Framework, в которой я пометил класс следующим образом:

public partial class Product
{
    public bool IsShipped
    {
        get { /* do stuff */ }
    }
}

и который я хочу использовать в выражении LINQ:

db.Products.Where(x => x.IsShipped).Select(...);

однако я получаю следующую ошибку:

Исключение System.NotSupportedException не было обработано кодом пользователя. Message = член указанного типа 'IsShipped' не поддерживается в LINQ to Entities. Только инициализаторы, члены сущности и свойства навигации сущности поддерживаются. Источник = System.Data.Entity

Я гуглил, но не нашел ничего определенного об этом использовании, чтобы попытаться:

public partial class Product
{
    public bool IsShipped()
    {
        /* do stuff */
    }
}

db.Products.Where(x => x.IsShipped()).Select(...);

но тогда я получаю:

Исключение System.NotSupportedException не было обработано кодом пользователя Message = LINQ Объектам не распознается метод метода Boolean IsShipped (), и этот метод не может быть переведен в выражение хранилища.
Источник = System.Data.Entity

там есть функциональность, которую я не хочу встраивать в сам запрос LINQ ... какой хороший способ справиться с этим?

* обновление *

Дарин делает правильное замечание, что все, что сделано в реализации IsShipped, должно быть преобразовано в SQL-запрос, и компилятор, вероятно, не знает, как это сделать, поэтому извлечение всех объектов в память кажется единственным выбор (если не сделан прямой запрос к базе данных). Я попробовал это так:

IEnumerable<Product> xp = db.Quizes
    .ToList()
    .Where(x => !x.IsShipped)
    .Select(x => x.Component.Product);

но генерирует эту ошибку:

Произошло нарушение ограничения множественности отношений: An EntityReference может иметь не более одного связанного объекта, но запрос вернул более одного связанного объекта. Это невосстановимое ошибка.

хотя любопытно, что это работает:

IEnumerable<Product> xp = db.Quizes
    .ToList()
    .Where(x => x.Skill.Id == 3)
    .Select(x => x.Component.Product);

с чего бы это?

* обновление II *

извините, последнее утверждение тоже не работает ...

* обновление III *

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

Спасибо всем за ценный вклад.

Ответы [ 4 ]

13 голосов
/ 15 октября 2011

Единственный способ сделать это "СУХОЙ" (избегать повторения логики внутри IsShipped в предложении Where снова) и не загружать все данные в память перед применением фильтра, это извлечь содержимое IsShipped в выражение. Затем вы можете использовать это выражение в качестве параметра для Where, а также для IsShipped. Пример:

public partial class Product
{
    public int ProductId { get; set; }           // <- mapped to DB
    public DateTime? ShippingDate { get; set; }  // <- mapped to DB
    public int ShippedQuantity { get; set; }     // <- mapped to DB

    // Static expression which must be understood
    // by LINQ to Entities, i.e. translatable into SQL
    public static Expression<Func<Product, bool>> IsShippedExpression
    {
        get { return p => p.ShippingDate.HasValue && p.ShippedQuantity > 0; }
    }

    public bool IsShipped // <- not mapped to DB because readonly
    {
        // Compile expression into delegate Func<Product, bool>
        // and execute delegate
        get { return Product.IsShippedExpression.Compile()(this); }
    }
}

Вы можете выполнить запрос следующим образом:

var result = db.Products.Where(Product.IsShippedExpression).Select(...).ToList();

Здесь у вас будет только одно место для размещения логики (IsShippedExpression) и последующего ее использования для запросов к базе данных, а также для вашего свойства IsShipped.

Буду ли я это делать? В большинстве случаев, вероятно, нет, потому что компиляция выражения идет медленно. Если логика не очень сложна, вероятно, она может измениться, и я нахожусь в ситуации, когда производительность использования IsShipped не имеет значения, я бы повторил логику. Всегда можно извлечь часто используемые фильтры в метод расширения:

public static class MyQueryExtensions
{
    public static IQueryable<Product> WhereIsShipped(
        this IQueryable<Product> query)
    {
        return query.Where(p => p.ShippingDate.HasValue && p.ShippedQuantity >0);
    }
}

А затем используйте это так:

var result = db.Products.WhereIsShipped().Select(...).ToList();

У вас будет два места для сохранения логики: свойство IsShipped и метод расширения, но затем вы можете использовать его повторно.

1 голос
/ 12 октября 2011

Я предполагаю, IsShipped не отображается на поле в базе данных?Это объясняет, почему Linq to Entities жалуется - он не может построить SQL-оператор на основе этого свойства.

Находятся ли ваши /* do stuff */ в собственности на основе полей, которые являются в базе данных?Если это так, вы можете использовать эту логику в вашем .Where().

0 голосов
/ 12 октября 2011

там есть функциональность, которую я не хочу встраивать в сам запрос LINQ ... какой хороший способ справиться с этим?

Полагаю, вы имеете в виду, что хотите выполнять запросы, которые не имеют ничего общего с БД. Но ваш код не соответствует вашим намерениям. Посмотрите на эту строку:

db.Products.Where(x => x.IsShipped()).Select(...);

Часть, которая говорит db.Products, означает, что вы хотите запросить БД.

Чтобы исправить это, сначала запишите объект в память. Тогда вы можете использовать Linq to Objects вместо него:

List<Product> products = db.Products
    .Where(x => x.SomeDbField == someValue)
    .ToList();

// Todo: Since the DB doesn't know about IsShipped, set that info here

// ...

var shippedProducts = products
    .Where(x => x.IsShipped())
    .Select(...);

.ToList() завершает ваш начальный запрос к БД и дает вам представление в памяти для работы и изменения по вашему вкусу. После этого вы можете работать со свойствами, не относящимися к БД.

Будьте внимательны, если после ToList вы будете выполнять дальнейшие операции с БД (например, редактировать свойства БД для сущностей, запрашивать свойства навигации и т. Д.), Вы вернетесь на землю Linq to Entities и больше не сможете делать операции Linq to Objects. Вы не можете напрямую смешать два.

И обратите внимание, что если public bool IsShipped() читает или записывает свойства БД или свойства навигации, вы можете снова оказаться в Linq to Entities, если не будете осторожны.

0 голосов
/ 12 октября 2011

Вы можете сначала получить результат, вызвав .ToList(), а затем выполнить фильтр на стороне клиента:

var result = db.Products.ToList().Where(x => x.IsShipped).Select(...);

Конечно, вы должны знать, что, делая это, вы, вероятно, снижаете производительность своего приложения, поскольку базы данных делают это лучше всего.

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