在LINQ-to-Entities中键入成员支持?

时间:2022-11-30 18:01:32

I have an MVC3 project using the Entity Framework model in which I've marked up a class like this:

我有一个使用Entity Framework模型的MVC3项目,我在其中标记了这样一个类:

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

and which I want to use in a LINQ expression:

我想在LINQ表达式中使用它:

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

however, I get the following error:

但是,我收到以下错误:

System.NotSupportedException was unhandled by user code Message=The specified type member 'IsShipped' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported. Source=System.Data.Entity

用户代码未处理System.NotSupportedException消息= LINQ to Entities中不支持指定的类型成员'IsShipped'。仅支持初始化程序,实体成员和实体导航属性。来源= System.Data.Entity的

I've googled but not found anything definitive about this usage to I tried:

我用谷歌搜索,但没有找到任何关于这种用法的确切的尝试:

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

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

but then I get:

但后来我得到:

System.NotSupportedException was unhandled by user code Message=LINQ to Entities does not recognize the method 'Boolean IsShipped()' method, and this method cannot be translated into a store expression.
Source=System.Data.Entity

System.NotSupportedException未由用户代码处理Message = LINQ to Entities无法识别方法'Boolean IsShipped()'方法,并且此方法无法转换为商店表达式。来源= System.Data.Entity的

there's functionality there that I don't want to build into the LINQ query itself... what's a good way to handle this?

有那里的功能,我不想构建LINQ查询本身...什么是处理这个的好方法?

* update *

*更新*

Darin makes the valid point that whatever is done in the implementation of IsShipped would need to be converted to a SQL query and the compiler probably doesn't know how to do it, thus retrieving all objects into memory seems the only choice (unless a direct query to the database is made). I tried it like this:

Darin提出了一个有效的观点,即在IsShipped的实现中所做的任何事情都需要转换为SQL查询,并且编译器可能不知道如何执行它,因此将所有对象检索到内存中似乎是唯一的选择(除非直接查询到数据库)。我试过这样的:

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

but it generates this error:

但它会生成此错误:

A relationship multiplicity constraint violation occurred: An EntityReference can have no more than one related object, but the query returned more than one related object. This is a non-recoverable error.

发生了关系多重性约束违规:EntityReference只能有一个相关对象,但查询返回了多个相关对象。这是一个不可恢复的错误。

though curiously this works:

虽然奇怪的是这有效:

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

why would that be?

为什么会这样?

* update II *

*更新II *

sorry, that last statement doesn't work either...

对不起,最后一句话也不起作用......

* update III *

*更新III *

I'm closing this question in favour of pursuing a solution as suggested here to flatten my logic into a query - the discussion will move to this new post. The second alternative, to retrieve the entire original query into memory, is likely unacceptable, but the third, of implementing the logic as a direct query to the database, remain to be explored.

我正在关闭这个问题,转而采用这里建议的解决方案,将我的逻辑压缩成一个查询 - 讨论将转移到这个新帖子。将整个原始查询检索到内存中的第二种方法可能是不可接受的,但是第三种将逻辑实现为对数据库的直接查询仍有待探索。

Thanks everyone for the valuable input.

感谢大家的宝贵意见。

4 个解决方案

#1


13  

The only way to make this "DRY" (avoid repeating the logic inside of IsShipped in the Where clause again) and to avoid loading all data into memory before you apply the filter is to extract the content of IsShipped into an expression. You can then use this expression as parameter to Where and in IsShipped as well. Example:

使这种“DRY”(避免再次在Where子句中重复IsShipped中的逻辑)并避免在应用过滤器之前将所有数据加载到内存中的唯一方法是将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); }
    }
}

The you can perform the query like so:

您可以像这样执行查询:

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

Here you would have only one place to put the logic in (IsShippedExpression) and then use it for database queries and in your IsShipped property as well.

在这里,您只有一个地方可以将逻辑放入(IsShippedExpression),然后将其用于数据库查询和IsShipped属性。

Would I do this? In most cases probably no, because compiling the expression is slow. Unless the logic is very complex, likely a subject to change and I am in a situation where the performance of using IsShipped doesn't matter, I would repeat the logic. It's always possible to extract often used filters into an extension method:

我会这样做吗?在大多数情况下可能没有,因为编译表达式很慢。除非逻辑非常复杂,否则可能会发生变化,而且我处于使用IsShipped的性能无关紧要的情况下,我会重复逻辑。始终可以将常用的过滤器提取到扩展方法中:

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

And then use it this way:

然后以这种方式使用它:

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

You would have two places though the maintain the logic: the IsShipped property and the extension method, but then you can reuse it.

虽然维护逻辑有两个地方:IsShipped属性和扩展方法,但是你可以重用它。

#2


1  

I'm guessing IsShipped is not mapped to a field in the database? That would explain why Linq to Entities complains - it cannot construct a sql statement based on this property.

我猜IsShipped没有映射到数据库中的字段?这可以解释为什么Linq to Entities抱怨 - 它无法构建基于此属性的sql语句。

Is your /* do stuff */ within the property based on fields that are in the database? If so, you could use that logic in your .Where().

您的/ *是否根据数据库中的字段在属性中执行了*?如果是这样,您可以在.Where()中使用该逻辑。

#3


0  

You could first consume the result by calling .ToList() and then perform the filter on the client side:

您可以先通过调用.ToList()来使用结果,然后在客户端执行过滤:

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

Of course you should be aware that by doing this you are probably slowing the performance of your application down as databases are doing this best.

当然,您应该意识到,通过这样做,您可能会降低应用程序的性能,因为数据库正在做得最好。

#4


0  

there's functionality there that I don't want to build into the LINQ query itself... what's a good way to handle this?

有那里的功能,我不想构建LINQ查询本身...什么是处理这个的好方法?

I assume you mean you want to perform queries that don't have anything to do with the DB. But your code doesn't match your intention. Look at this line:

我假设您的意思是要执行与DB无关的查询。但是你的代码与你的意图不符。看看这一行:

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

The part that says db.Products means you want to query the DB.

说db.Products的部分意味着您要查询数据库。

To fix this, get an entity set in memory first. Then you can use Linq to Objects on it instead:

要解决此问题,请先在内存中设置实体。然后你可以使用Linq来对象:

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(...);

The .ToList() finishes off your initial DB query, and gives you an in-memory representation to work with and modify to your liking. After that point you can work with non-DB properties.

.ToList()完成初始数据库查询,并为您提供内存表示,以便根据您的喜好进行操作和修改。在那之后,您可以使用非数据库属性。

Be careful that if you do further DB operations after ToList (such as editing DB properties on entities, querying off navigation properties, etc), then you'll be back in Linq to Entities land and will no longer be able to do Linq to Objects operations. You can't directly mix the two.

请注意,如果您在ToList之后执行进一步的数据库操作(例如编辑实体上的数据库属性,查询导航属性等),那么您将返回到Linq to Entities land并且将无法再对Linq执行操作操作。你不能直接混合两者。

And note that if public bool IsShipped() reads or writes DB properties or navigation properties, you might end up in Linq to Entities again if you're not careful.

请注意,如果公共bool IsShipped()读取或写入数据库属性或导航属性,如果您不小心,可能会再次使用Linq to Entities。

#1


13  

The only way to make this "DRY" (avoid repeating the logic inside of IsShipped in the Where clause again) and to avoid loading all data into memory before you apply the filter is to extract the content of IsShipped into an expression. You can then use this expression as parameter to Where and in IsShipped as well. Example:

使这种“DRY”(避免再次在Where子句中重复IsShipped中的逻辑)并避免在应用过滤器之前将所有数据加载到内存中的唯一方法是将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); }
    }
}

The you can perform the query like so:

您可以像这样执行查询:

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

Here you would have only one place to put the logic in (IsShippedExpression) and then use it for database queries and in your IsShipped property as well.

在这里,您只有一个地方可以将逻辑放入(IsShippedExpression),然后将其用于数据库查询和IsShipped属性。

Would I do this? In most cases probably no, because compiling the expression is slow. Unless the logic is very complex, likely a subject to change and I am in a situation where the performance of using IsShipped doesn't matter, I would repeat the logic. It's always possible to extract often used filters into an extension method:

我会这样做吗?在大多数情况下可能没有,因为编译表达式很慢。除非逻辑非常复杂,否则可能会发生变化,而且我处于使用IsShipped的性能无关紧要的情况下,我会重复逻辑。始终可以将常用的过滤器提取到扩展方法中:

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

And then use it this way:

然后以这种方式使用它:

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

You would have two places though the maintain the logic: the IsShipped property and the extension method, but then you can reuse it.

虽然维护逻辑有两个地方:IsShipped属性和扩展方法,但是你可以重用它。

#2


1  

I'm guessing IsShipped is not mapped to a field in the database? That would explain why Linq to Entities complains - it cannot construct a sql statement based on this property.

我猜IsShipped没有映射到数据库中的字段?这可以解释为什么Linq to Entities抱怨 - 它无法构建基于此属性的sql语句。

Is your /* do stuff */ within the property based on fields that are in the database? If so, you could use that logic in your .Where().

您的/ *是否根据数据库中的字段在属性中执行了*?如果是这样,您可以在.Where()中使用该逻辑。

#3


0  

You could first consume the result by calling .ToList() and then perform the filter on the client side:

您可以先通过调用.ToList()来使用结果,然后在客户端执行过滤:

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

Of course you should be aware that by doing this you are probably slowing the performance of your application down as databases are doing this best.

当然,您应该意识到,通过这样做,您可能会降低应用程序的性能,因为数据库正在做得最好。

#4


0  

there's functionality there that I don't want to build into the LINQ query itself... what's a good way to handle this?

有那里的功能,我不想构建LINQ查询本身...什么是处理这个的好方法?

I assume you mean you want to perform queries that don't have anything to do with the DB. But your code doesn't match your intention. Look at this line:

我假设您的意思是要执行与DB无关的查询。但是你的代码与你的意图不符。看看这一行:

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

The part that says db.Products means you want to query the DB.

说db.Products的部分意味着您要查询数据库。

To fix this, get an entity set in memory first. Then you can use Linq to Objects on it instead:

要解决此问题,请先在内存中设置实体。然后你可以使用Linq来对象:

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(...);

The .ToList() finishes off your initial DB query, and gives you an in-memory representation to work with and modify to your liking. After that point you can work with non-DB properties.

.ToList()完成初始数据库查询,并为您提供内存表示,以便根据您的喜好进行操作和修改。在那之后,您可以使用非数据库属性。

Be careful that if you do further DB operations after ToList (such as editing DB properties on entities, querying off navigation properties, etc), then you'll be back in Linq to Entities land and will no longer be able to do Linq to Objects operations. You can't directly mix the two.

请注意,如果您在ToList之后执行进一步的数据库操作(例如编辑实体上的数据库属性,查询导航属性等),那么您将返回到Linq to Entities land并且将无法再对Linq执行操作操作。你不能直接混合两者。

And note that if public bool IsShipped() reads or writes DB properties or navigation properties, you might end up in Linq to Entities again if you're not careful.

请注意,如果公共bool IsShipped()读取或写入数据库属性或导航属性,如果您不小心,可能会再次使用Linq to Entities。