【问题标题】:Type member support in LINQ-to-Entities?LINQ-to-Entities 中的类型成员支持?
【发布时间】:2011-10-17 06:05:21
【问题描述】:

我有一个使用实体框架模型的 MVC3 项目,我在其中标记了这样的类:

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

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

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

但是,我收到以下错误:

System.NotSupportedException 未被用户代码处理 Message=The LINQ to Entities 不支持指定类型成员“IsShipped”。 只有初始化器、实体成员和实体导航属性 支持。 Source=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()' 方法, 而且这个方法不能翻译成商店表达式。
Source=System.Data.Entity

那里有一些我不想内置到 LINQ 查询本身的功能......有什么好的方法来处理这个问题?

* 更新 *

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

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

但它会产生这个错误:

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

虽然奇怪的是这行得通:

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

为什么会这样?

* 更新二 *

抱歉,最后一条语句也不起作用...

* 更新 III *

我将结束这个问题,支持按照此处的建议寻求解决方案,以将我的逻辑扁平化为查询 - 讨论将移至 this new post。第二种选择,将整个原始查询检索到内存中,可能是不可接受的,但第三种将逻辑实现为对数据库的直接查询仍有待探索。

感谢大家的宝贵意见。

【问题讨论】:

  • “处理这个问题的好方法”完全取决于“做事”到底是什么。问题是是否有可以翻译成 SQL 的东西。如果“做事”是:return MyMappedProp1 &amp;&amp; MyMappedProp2,那么就有希望(不是你现在做的方式,而是另一种“干”的方式)。如果您在“do stuff”中打开磁盘上的文件并从中读取一个值,那么可能没有希望。所有通用解决方案都将强制您使用 LINQ to Objects 进行查询,即在过滤之前将所有内容加载到内存中。
  • @Slauma 不,我不会做任何事情,只是在“东西”中进行数据库查询......但是,请参阅我最近对这篇文章的更新
  • 你在“更新”中的例外:当你只使用 IEnumerable&lt;Product&gt; xp = db.Quizes.ToList(); 时你也会得到这个吗?我相信这个问题与您的 IsShipped 属性无关。模型中还有其他问题。
  • @Slauma,这与达林首先获取所有内容的想法有关。我在测试中使用了IEnumerable,但我真正想要的是一个列表,因此请考虑List&lt;Product&gt; p1 = db.Quizes.Select(x =&gt; x.Component.Product).ToList(); 工作正常,但List&lt;Product&gt; p1 = db.Quizes.ToList().Select(x =&gt; x.Component.Product).ToList(); 会产生该异常。我需要最后的.ToList(),因为我要分配给一个列表...

标签: c# .net asp.net-mvc linq entity-framework


【解决方案1】:

使这个“DRY”(避免在Where 子句中再次重复IsShipped 内部的逻辑)并避免在应用过滤器之前将所有数据加载到内存中的唯一方法是提取@ 的内容987654324@ 变成一个表达式。然后,您可以将此表达式用作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, 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 属性和扩展方法,但是你可以重用它。

【讨论】:

  • 哇@Slauma!你给了我很多东西要消化,谢谢。让我考虑一下。一会儿回来。
  • 逻辑不是很复杂,但它需要实例化一个新的 EF 容器副本(进行查找)和一个循环,我不知道如何将它嵌入到我的顶级查询中,这就是为什么我把它拉到类的属性中
  • 参考我的最后一条评论,我喜欢你的查询扩展方法,但是,我处于同样的情况,我不知道如何将循环嵌入到表达式中......例如,如果一个产品有许多组件,我需要遍历它们以寻找特定的属性……我该怎么做? (我正在试验,看看我能不能弄明白)
  • @ekkis:也许打开另一个问题更好。听起来您必须更详细地解释什么是“做事”以及Component 到底是什么。 “......寻找特定的属性......”(在运行时)让我认为会涉及一些反思,但对我来说,你的问题不足以给出答案。显然我上面的示例模型对于您真正想要做的事情来说太简单了。
  • 是的,你是对的。这是新问题:stackoverflow.com/questions/7787625/…
【解决方案2】:

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

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

【讨论】:

  • 正确,它没有映射到数据库。正如我提到的,我想避免将功能写入查询,因为它不是很干燥
【解决方案3】:

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

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

当然,您应该知道,这样做可能会降低应用程序的性能,因为数据库在这方面做得最好。

【讨论】:

  • 警告:如果我没记错的话,这会将整个表加载到内存中。
  • 我明白你的意思。 EF 无法弄清楚如何为 .IsShipped 所做的任何事情编写 SQL。所以本质上我必须获取所有昂贵的东西;还有其他想法吗?
  • @ekkis,是的,这可能很昂贵。正如我在回答中所说,最好的解决方案是在数据库内部执行查询(即将此逻辑实现为查询)或至少部分查询,以便在应用第二个过滤器之前减少返回结果集的数量在客户端上。
  • @mxmissile 你没有弄错。在表上执行 .ToList() 通常是个坏主意。
【解决方案4】:

那里有我不想内置到 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 之后执行进一步的 DB 操作(例如编辑实体上的 DB 属性、查询导航属性等),那么您将返回 Linq to Entities 土地并且不再能够执行 Linq to Objects 操作。两者不能直接混用。

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

【讨论】:

  • 不,/* do stuff */ 只是一系列数据库查询。我需要计算一些东西,计算一些比率并做出返回为布尔值的决定。此外,我不愿意将所有产品都放在内存中(因为没有其他标准需要考虑),因为这会给服务器带来沉重的负担。发货的产品数量将只是总产品的一小部分……所以我有点不知所措。 Darin 建议我改为执行数据库查询,但我不太确定这是如何完成的。
  • @ekkis:那么IsShipped的实现就变得很重要了。如果是“一系列数据库查询”,怎么定义?在幕后,这应该是某种扩展连接,可能使用子查询/临时表。您可能需要向我们提供您的 IsShipped 实现,可能还有您的一些数据库架构,以便我们能够提供进一步的帮助。
  • @ekkis:另外,我在查看问题时注意到了这一点:“那里有我不想构建到 LINQ 查询本身的功能”。您可以查看允许该子句仍然是 Linq 查询的一部分但在其他地方定义的解决方案,例如将 WhereIsShipped 扩展方法设置为 IQueryable&lt;Product&gt;。我之前也采用过这种方式来封装分页逻辑,这只是一种不同类型的标准。
  • 我正在寻求您对扩展方法的建议(和@Slauma 的)。我在与 Slauma 的交流中发布了一个链接,指向一个包含所涉及逻辑的新问题。感谢您的帮助。
猜你喜欢
  • 2015-01-27
  • 2012-07-17
  • 1970-01-01
  • 2013-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-28
  • 2018-02-19
相关资源
最近更新 更多