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

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

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

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

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

但是,我收到以下错误:

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

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

 public partial class Product { public bool IsShipped() { /* do stuff */ } } db.Products.Where(x => x.IsShipped()).Select(...); 

但后来我得到:

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

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

*更新*

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

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

但它会生成此错误:

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

虽然奇怪的是这有效:

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

为什么会这样?

*更新II *

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

*更新III *

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

感谢大家的宝贵意见。

使这种“DRY”(避免再次在Where子句中重复IsShipped中的逻辑)并避免在应用filter之前将所有数据加载到内存中的唯一方法是将IsShipped的内容提取到表达式中。 然后,您可以将此表达式用作WhereIsShipped参数。 例:

 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, ie translatable into SQL public static Expression> IsShippedExpression { get { return p => p.ShippingDate.HasValue && p.ShippedQuantity > 0; } } public bool IsShipped // <- not mapped to DB because readonly { // Compile expression into delegate Func // and execute delegate get { return Product.IsShippedExpression.Compile()(this); } } } 

您可以像这样执行查询:

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

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

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

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

然后以这种方式使用它:

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

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

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

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

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

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

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

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

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

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

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

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

 List 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 land并且将无法再对Linq执行操作操作。 你不能直接混合两者。

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