有一种名为Product的实体类型是由实体框架生成的。 我写了这个问题

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

下面的代码抛出以下错误:

实体或复杂类型的Shop。产品不能构造在 LINQ到实体查询"

var products = productRepository.GetProducts(1).Tolist();

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

如何执行自定义选择节?


当前回答

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

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

编辑:因为这个问题引起了很多关注,所以我要讲得更具体一些。

你不能直接投射到模型类型中(EF限制),所以没有办法绕过这个。唯一的方法是投射到匿名类型(第一次迭代),然后投射到建模类型(第二次迭代)。

还请注意,当您以这种方式部分加载实体时,它们不能被更新,因此它们应该保持分离状态。

我从来没有完全理解为什么这是不可能的,这个帖子上的答案也没有给出强烈的反对理由(主要是关于部分加载的数据)。在部分加载状态下实体不能被更新,这是正确的,但是之后,该实体将被分离,因此不可能意外地尝试保存它们。

考虑一下我上面使用的方法:结果我们仍然有一个部分加载的模型实体。该实体已分离。

考虑以下(希望存在)可能的代码:

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

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

其他回答

你必须在使用select创建新列表之前使用toList:

db.Products
    .where(x=>x.CategoryID == categoryID).ToList()
    .select(x=>new Product { Name = p.Name}).ToList(); 

您可以通过使用数据传输对象(DTO)来解决这个问题。

这有点像视图模型,你可以在视图模型中输入你需要的属性,你可以在控制器中手动映射它们,也可以使用第三方解决方案,如AutoMapper。

使用DTO,你可以:

使数据可序列化(Json) 摆脱循环引用 通过留下你不需要的属性来减少网络流量(viewmodelwise) 使用objectflattening

我今年在学校学过这个,这是一个非常有用的工具。

我还发现了另一种可行的方法,你必须从你的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};
}

不确定这是否“被允许”,但这是可行的。

如果你正在执行实体的Linq,你不能在查询的选择闭包中使用带有new的ClassType,只允许匿名类型(new没有类型)

看看我的项目的这个片段

//...
var dbQuery = context.Set<Letter>()
                .Include(letter => letter.LetterStatus)
                .Select(l => new {Title =l.Title,ID = l.ID, LastModificationDate = l.LastModificationDate, DateCreated = l.DateCreated,LetterStatus = new {ID = l.LetterStatusID.Value,NameInArabic = l.LetterStatus.NameInArabic,NameInEnglish = l.LetterStatus.NameInEnglish} })
                               ^^ without type__________________________________________________________________________________________________________^^ without type

如果你在选择闭包中添加了new关键字,即使在复杂的属性上,你也会得到这个错误

因此,从Linq上的new关键字删除类类型到实体查询,

因为它将转换为sql语句并在SqlServer上执行

什么时候我可以使用new with types on select closure?

如果你在处理LINQ to Object(内存收集),你可以使用它

//opecations in tempList , LINQ to Entities; so we can not use class types in select only anonymous types are allowed
var tempList = dbQuery.Skip(10).Take(10).ToList();// this is list of <anonymous type> so we have to convert it so list of <letter>

//opecations in list , LINQ to Object; so we can use class types in select
list = tempList.Select(l => new Letter{ Title = l.Title, ID = l.ID, LastModificationDate = l.LastModificationDate, DateCreated = l.DateCreated, LetterStatus = new LetterStatus{ ID = l.LetterStatus.ID, NameInArabic = l.LetterStatus.NameInArabic, NameInEnglish = l.LetterStatus.NameInEnglish } }).ToList();
                                ^^^^^^ with type 

在我对查询执行了ToList后,它变成了内存集合,所以我们可以在选择中使用新的classttypes

您不能(也不应该)将项目投射到映射实体上。但是,你可以投射到匿名类型或DTO上:

public class ProductDTO
{
    public string Name { get; set; }
    // Other field you may need from the Product entity
}

您的方法将返回DTO的列表。

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