无法在LINQ to Entities查询中构造实体

Gho*_*ngi 373 c# entity-framework

存在由实体框架生成的称为产品的实体类型.我写了这个查询

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}
Run Code Online (Sandbox Code Playgroud)

下面的代码引发以下错误:

"无法在LINQ to Entities查询中构造实体或复杂类型Shop.Product"

var products = productRepository.GetProducts(1).Tolist();
Run Code Online (Sandbox Code Playgroud)

但是,当我使用select p而不是select new Product { Name = p.Name};它正常工作.

如何预先形成自定义选择部分?

Yak*_*ych 376

您不能(也不应该)投影到映射的实体.但是,您可以投影到匿名类型或DTO:

public class ProductDTO
{
    public string Name { get; set; }
    // Other field you may need from the Product entity
}
Run Code Online (Sandbox Code Playgroud)

并且您的方法将返回DTO列表.

public List<ProductDTO> GetProducts(int categoryID)
{
    return (from p in db.Products
            where p.CategoryID == categoryID
            select new ProductDTO { Name = p.Name }).ToList();
}
Run Code Online (Sandbox Code Playgroud)

  • 我不明白为什么我不能这样做......这将非常有用...... (143认同)
  • 好吧,EF中的映射实体基本上代表数据库表.如果你投影到一个映射的实体,你基本上做的是**部分**加载一个实体,这不是一个有效的状态.EF将不会有任何线索如何在将来处理这样一个实体的更新(默认行为可能是用空值或对象中的任何内容覆盖未加载的字段).这将是一个危险的操作,因为您可能会丢失数据库中的某些数据,因此不允许在EF中部分加载实体(或投影到映射的实体). (115认同)
  • @Yakimych有意义,除非你有一些通过查询生成/创建的聚合实体,因此完全了解/打算创建一个全新的实体,然后你将操作并稍后添加.在这种情况下,您必须强制运行查询或推入dto并返回到要添加的实体 - 这令人沮丧 (26认同)
  • @Cargowire - 我同意,这种情况存在,当你知道自己在做什么但由于限制而不允许这样做时,这是令人沮丧的.然而,如果允许这样做,那么会有很多沮丧的开发者抱怨他们的数据在例如试图保存部分加载的实体时会丢失.IMO,一个带有大量噪音(抛出异常等)的错误比可能导致难以追踪和解释的隐藏错误的行为更好(在开始注意丢失数据之前,事情很好地工作). (15认同)
  • DTO - [数据传输对象](http://msdn.microsoft.com/en-us/magazine/ee236638.aspx#id0080022) (14认同)
  • 这对我来说毫无意义..我认为POCO对象的重点在于持久性无知和重用性? (2认同)
  • 立即投票阅读"不应该".这是居高临下并且显然是错误的.EntityFramework应该允许使用C#惯用法进行关系CRUD操作.一个例外,"对不起,你不能以这种方式使用这个对象,因为它以不同的方式在其他地方使用"是非常反本能的C#. (2认同)

Gor*_*ran 263

您可以投影到匿名类型,然后从它投射到模型类型

public IEnumerable<Product> GetProducts(int categoryID)
{
    return (from p in Context.Set<Product>()
            where p.CategoryID == categoryID
            select new { Name = p.Name }).ToList()
           .Select(x => new Product { Name = x.Name });
}
Run Code Online (Sandbox Code Playgroud)

编辑:由于这个问题得到了很多关注,我将会更具体一点.

你不能直接投射到模型类型(EF限制),所以没有办法解决这个问题.唯一的方法是投射到匿名类型(第一次迭代),然后投射到模型类型(第二次迭代).

另请注意,当您以这种方式部分加载实体时,它们无法更新,因此它们应保持分离状态.

我从来没有完全理解为什么这是不可能的,并且这个线程的答案没有给出强有力的理由(主要是关于部分加载的数据).在部分加载的状态实体无法更新是正确的,但是,此实体将被分离,因此不可能意外地尝试保存它们.

考虑我上面使用的方法:作为结果,我们仍然有一个部分加载的模型实体.该实体是分离的.

考虑这个(希望存在的)可能的代码:

return (from p in Context.Set<Product>()
        where p.CategoryID == categoryID
        select new Product { Name = p.Name }).AsNoTracking().ToList();
Run Code Online (Sandbox Code Playgroud)

这也可能导致分离实体列表,因此我们不需要进行两次迭代.编译器会很聪明地看到AsNoTracking()已被使用,这将导致分离的实体,因此它可以允许我们这样做.但是,如果省略了AsNoTracking(),它可能会抛出与现在抛出相同的异常,以警告我们需要对我们想要的结果做出足够的具体说明.

  • 从技术上讲,对模型类型的投影发生在查询之外,我相信还需要在列表中进行额外的迭代.我不会将此解决方案用于我的代码,但它是问题的解决方案.节节攀升. (10认同)
  • 除此之外,它实际上并不是问题的答案.这是关于如何执行Linq To Objects投影而不是Linq to Entities查询投影的答案.所以DTO选项是唯一的选择:Linq to Entities. (7认同)
  • 我更喜欢这个被接受的DTO解决方案 - 更加优雅和干净 (4认同)
  • 当您不需要/不关心要投影的所选实体的状态时,这是最干净的解决方案. (3认同)
  • 如果你不关心你是否返回IEnumerable或IQueryable;).但是你仍然得到了我的支持,因为这个解决方案现在适合我. (2认同)

Tom*_*icz 78

我找到了另一种方法,你必须构建一个派生自Product类的类并使用它.例如:

public class PseudoProduct : Product { }

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products
           where p.CategoryID== categoryID
           select new PseudoProduct() { Name = p.Name};
}
Run Code Online (Sandbox Code Playgroud)

不确定这是否"允许",但它确实有效.

  • 顺便说一句,如果你试图保持GetProducts()的结果,这会咬你,因为EF无法找到PseudoProduct的映射,例如"System.InvalidOperationException:找不到EntityType'blah.PseudoProduct''的映射和元数​​据信息." (5认同)
  • 最佳答案,也是唯一一个在问题参数范围内回答的答案.所有其他答案都会更改返回类型或过早执行IQueryable并将linq用于对象 (4认同)
  • 聪明!现在试过这个并且它有效.我相信它会以某种方式烧我. (3认同)
  • 非常完美,我将其添加到.tt代码生成文件中 (3认同)
  • 似乎不适用于最新的EF. (3认同)
  • 100%震惊了它的工作...在EF 6.1中,它正在工作。 (2认同)
  • @mejobloggs在派生类上尝试[NotMapped]属性,如果您使用的是流畅的API,则尝试.Ignore <T>. (2认同)
  • 我发现这是最好的答案。我很惊讶其他两个答案有这么多的赞成票:1.使用Dto和2.使用匿名,然后使用ToList()。我不会推荐这些解决方案中的任何一个。然而,这个效果最好。首先,因为子类型不是映射类型,所以不会出现错误。其次,子类型可以充当映射类型以对其应用其他表达式或可查询操作。 (2认同)

Boj*_*kas 37

这是一种在不声明aditional类的情况下执行此操作的方法:

public List<Product> GetProducts(int categoryID)
{
    var query = from p in db.Products
            where p.CategoryID == categoryID
            select new { Name = p.Name };
    var products = query.ToList().Select(r => new Product
    {
        Name = r.Name;
    }).ToList();

    return products;
}
Run Code Online (Sandbox Code Playgroud)

但是,仅当您要在单个实体中组合多个实体时才使用此选项.上述功能(简单的产品到产品映射)是这样完成的:

public List<Product> GetProducts(int categoryID)
{
    var query = from p in db.Products
            where p.CategoryID == categoryID
            select p;
    var products = query.ToList();

    return products;
}
Run Code Online (Sandbox Code Playgroud)


Sor*_*ren 22

另一种简单方法:)

public IQueryable<Product> GetProducts(int categoryID)
{
    var productList = db.Products
        .Where(p => p.CategoryID == categoryID)
        .Select(item => 
            new Product
            {
                Name = item.Name
            })
        .ToList()
        .AsQueryable(); // actually it's not useful after "ToList()" :D

    return productList;
}
Run Code Online (Sandbox Code Playgroud)