在LINQ to Entities查询中无法构建实体。
在LINQ to Entities查询中无法构建实体。
有一种名为Product
的实体类型,由实体框架生成。\n我编写了以下查询:\n
public IQueryableGetProducts(int categoryID) { return from p in db.Products where p.CategoryID== categoryID select new Product { Name = p.Name}; }
\n以下代码抛出以下错误:\n
\n无法在LINQ to Entities查询中构造实体或复杂类型Shop.Product\"\n
\n
var products = productRepository.GetProducts(1).Tolist();
\n但是当我使用select p
而不是select new Product { Name = p.Name};
时,它可以正常工作。\n如何执行自定义选择部分?
在LINQ to Entities查询中出现(The entity cannot be constructed in a LINQ to Entities query)这个问题是由于使用LINQ to Entities查询时,不能直接构造实体的原因。解决这个问题的方法是创建一个派生自实体类的类,并使用该类来构造查询结果。
下面是一个解决方法的示例代码:
public class PseudoProduct : Product { } public IQueryableGetProducts(int categoryID) { return from p in db.Products where p.CategoryID == categoryID select new PseudoProduct() { Name = p.Name}; }
这种解决方法可能是被允许的,但可能存在一些潜在问题。例如,如果尝试持久化GetProducts()方法的结果,Entity Framework可能无法找到PseudoProduct的映射,从而导致异常。
除了上述解决方法外,还有其他一些解决方案会改变返回类型或过早地执行IQueryable,并使用LINQ to Objects。但上述解决方法是唯一一个符合问题要求的答案。
另外,有用户提到在EF 6.1版本中尝试了这种解决方法并成功。还有用户提到尝试这种解决方法时遇到了“model backing context has changed”的错误,他解决了这个错误的方法是在派生类上使用[NotMapped]属性或使用fluent API中的.Ignore
这种解决方法简单且有效。它通过创建派生类来避免了LINQ to Entities查询中实体无法构造的问题,并可以在派生类上应用额外的表达式或查询操作。如果对查询结果的持久化感到担心,可以使用db.Products.AsNoTracking()方法来确保安全。
在LINQ to Entities查询中出现(The entity cannot be constructed in a LINQ to Entities query)的问题主要是由于无法直接在查询中构造实体类型。解决方法是通过投影到匿名类型,然后再从匿名类型转换为模型类型。
具体代码如下:
public IEnumerableGetProducts(int categoryID) { return (from p in Context.Set () where p.CategoryID == categoryID select new { Name = p.Name }).ToList() .Select(x => new Product { Name = x.Name }); }
这段代码先将查询结果投影到匿名类型,然后再通过ToList()方法将结果转换为List集合。最后通过Select()方法将匿名类型转换为模型类型。
需要注意的是,通过这种方式部分加载的实体无法进行更新操作,因此它们应该保持游离状态。
对于为什么不能直接投影到模型类型,目前还没有明确的解释。部分加载的实体无法进行更新是正确的,但是这些实体是游离状态,因此不会出现意外的保存尝试。
如果希望有一种方式可以直接投影到模型类型,可以考虑以下代码(希望实现的代码):
return (from p in Context.Set() where p.CategoryID == categoryID select new Product { Name = p.Name }).AsNoTracking().ToList();
这段代码使用AsNoTracking()方法,返回一个游离状态的实体列表,从而避免了两次迭代。编译器会智能地发现使用了AsNoTracking(),这将导致返回游离状态的实体,所以可以允许我们这样做。如果省略了AsNoTracking(),则可能会抛出与当前相同的异常,以警告我们需要对结果进行更具体的指定。
这种解决方法是在不需要或不关心所选实体状态的情况下最干净的解决方法。
最后,需要注意的是这种解决方法适用于返回IEnumerable或IQueryable的情况。
在LINQ to Entities查询中无法构建实体的问题是由于EF的限制而产生的。EF中的映射实体代表数据库表,如果在查询中对映射实体进行投影,就是部分加载实体,这是一个无效的状态。EF无法处理这样的实体更新操作,因为默认行为可能会将未加载的字段覆盖为null或其他对象。这可能会导致数据丢失,因此在EF中不允许部分加载实体或对映射实体进行投影。
然而,如果确实有一些通过查询生成/创建的聚合实体,并且您完全了解/意图创建一个全新的实体,然后进行操作并稍后添加,这种情况下,您要么需要强制运行查询,要么将其推入DTO并返回实体以进行添加操作。
尽管这种情况确实存在,并且对于了解自己在做什么但由于限制而无法执行的开发人员来说确实很让人沮丧,但如果允许这样做,就会有很多沮丧的开发人员抱怨尝试保存部分加载的实体时丢失了数据。在我看来,出现错误并引发大量噪音(抛出异常等)比导致难以跟踪和解释的隐藏错误更好(在开始注意到缺失数据之前,一切都正常运行)。
另外,还有一个有用的场景是当您只想加载但不想更改实体时。对于不想使用DTO但想返回模型实体列表的人,可以参考下面的答案。
此外,如果在返回的IEnumerable<T>结果上使用投影,您可以使用一个投影将实体(甚至是子实体)填充到实体中,从而不会将实体或实体集合附加到上下文中。
有人指出使用DTO不是一个推荐的解决方案,因为DTO与实体没有关系。如果代码需要一个IQueryable<Entity>类型以便稍后添加额外的表达式,返回IQueryable<EntityDto>是行不通的,这甚至不是一个选项。
总之,EF限制了在LINQ to Entities查询中对映射实体进行投影的操作,以避免潜在的数据丢失。解决方案是使用匿名类型或DTO进行投影,并返回一个DTO的列表。