LINQ-to-Entities 中的类型成员支持?

发布于 2024-12-09 11:30:38 字数 1935 浏览 0 评论 0原文

我有一个使用实体框架模型的 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”。 仅初始值设定项、实体成员和实体导航属性 都支持。来源=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

有一些功能我不想构建到 LINQ 查询本身中...处理这个问题的好方法是什么?

* update *

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

为什么会这样呢?

* 更新 II *

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

* 更新 III *

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

感谢大家的宝贵意见。

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

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

and which I want to use in a LINQ expression:

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

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

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

* 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:

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.

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 *

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

* update 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

哑剧 2024-12-16 11:30:38

使此“DRY”的唯一方法(避免再次重复 Where 子句中 IsShipped 内部的逻辑)并避免在应用过滤器之前将所有数据加载到内存中就是将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, 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属性和扩展方法,但是您可以重复使用它。

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:

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.

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:

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.

终陌 2024-12-16 11:30:38

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

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

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.

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

离旧人 2024-12-16 11:30:38

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

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

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

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

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.

弃爱 2024-12-16 11:30:38

有一些功能我不想构建到 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。

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

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.Products.Where(x => x.IsShipped()).Select(...);

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

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

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.

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.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文