在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
的内容提取到表达式中。 然后,您可以将此表达式用作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, 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。