实体不能在LINQ到实体的查询中构造

有一个名为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 to Entities query"
var products = productRepository.GetProducts(1).Tolist();

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

如何执行自定义选择节?

282137 次浏览

您不能(也不应该)将项目投射到映射实体上。然而,你可以投射到匿名类型或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();
}

另一种简单的方法:)

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

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

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

这里有一种不声明额外类的方法:

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

但是,只有当您想将多个实体组合到一个实体中时,才可以使用这种方法。上面的功能(简单的产品到产品映射)是这样完成的:

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

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

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()被省略,它可能会抛出与现在抛出的相同的异常,以警告我们需要对我们想要的结果足够具体。

为了回应另一个被标记为重复的问题(在这里看到的),我根据Soren的答案想出了一个快速而简单的解决方案:

data.Tasks.AddRange(
data.Task.AsEnumerable().Select(t => new Task{
creator_id   = t.ID,
start_date   = t.Incident.DateOpened,
end_date     = t.Incident.DateCLosed,
product_code = t.Incident.ProductCode
// so on...
})
);
data.SaveChanges();
< p >注意: 这个解决方案只适用于Task类上有一个导航属性(外键)(这里称为“Incident”)。 如果你没有,你可以使用其他发布的解决方案“AsQueryable()”
如果你正在使用实体框架,那么尝试从DbContext中删除属性,它使用你的复杂模型作为实体 当将多个模型映射到名为Entity

的视图模型时,我也有同样的问题
public DbSet<Entity> Entities { get; set; }

从DbContext中删除条目修复了我的错误。

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

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

如果你正在执行Linq to Entity,你不能在查询only anonymous types are allowed (new without type)select闭包中使用ClassTypenew

看看我的项目的这个片段

//...
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 keyword,即使在complex properties上,你也会得到这个错误

所以remove Linq to Entity查询的ClassTypes from new关键字,

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

所以什么时候我可以在__ABC1闭包上使用new with types ?

如果你在处理LINQ to Object (in memory collection),你可以使用它

//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后,它变成了in memory collection,所以我们可以在select中使用new ClassTypes

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

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

使用DTO,你可以:

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

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

只添加AsEnumerable():

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

你可以像下面这样添加AsEnumerable到你的集合:

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

在许多情况下,不需要转换。考虑一下您需要强类型List的原因,并评估您是否只是需要数据,例如,在web服务中或用于显示数据。这与类型无关。 您只需要知道如何读取它,并检查它是否与您定义的匿名类型中定义的属性相同。这是最优的情况,因为你不需要实体的所有字段,这就是匿名类型存在的原因。< / p >

一个简单的方法是这样做:

IEnumerable<object> list = dataContext.Table.Select(e => new { MyRequiredField = e.MyRequiredField}).AsEnumerable();

它不会让您映射回Product,因为这是您正在查询的表。你需要一个匿名函数,然后你可以将它添加到ViewModel,并将每个ViewModel添加到List<MyViewModel>并返回这些。这是一个轻微的题外话,但我包含了关于处理可为空日期的警告,因为这是一个难以处理的问题,以防您有任何问题。我是这样处理的。

希望你有一个ProductViewModel:

public class ProductViewModel
{
[Key]
public string ID { get; set; }
public string Name { get; set; }
}

我有依赖注入/存储库框架,我调用一个函数来获取我的数据。以你的帖子为例,在你的Controller函数调用中,它看起来是这样的:

int categoryID = 1;
var prods = repository.GetProducts(categoryID);

在存储库类中:

public IEnumerable<ProductViewModel> GetProducts(int categoryID)
{
List<ProductViewModel> lstPVM = new List<ProductViewModel>();


var anonymousObjResult = from p in db.Products
where p.CategoryID == categoryID
select new
{
CatID = p.CategoryID,
Name = p.Name
};


// NOTE: If you have any dates that are nullable and null, you'll need to
// take care of that:  ClosedDate = (DateTime?)p.ClosedDate ?? DateTime.Now


// If you want a particular date, you have to define a DateTime variable,
// assign your value to it, then replace DateTime.Now with that variable. You
// cannot call a DateTime.Parse there, unfortunately.
// Using
//    new Date("1","1","1800");
// works, though. (I add a particular date so I can edit it out later.)


// I do this foreach below so I can return a List<ProductViewModel>.
// You could do: return anonymousObjResult.ToList(); here
// but it's not as clean and is an anonymous type instead of defined
// by a ViewModel where you can control the individual field types


foreach (var a in anonymousObjResult)
{
ProductViewModel pvm = new ProductViewModel();
pvm.ID = a.CatID;
pvm.Name = a.Name;
lstPVM.Add(rvm);
}


// Obviously you will just have ONE item there, but I built it
// like this so you could bring back the whole table, if you wanted
// to remove your Where clause, above.


return lstPVM;
}

回到控制器,你需要:

 List<ProductViewModel> lstProd = new List<ProductViewModel>();


if (prods != null)
{
// For setting the dates back to nulls, I'm looking for this value:
// DateTime stdDate = DateTime.Parse("01/01/1800");


foreach (var a in prods)
{
ProductViewModel o_prod = new ReportViewModel();
o_prod.ID = a.ID;
o_prod.Name = a.Name;
// o_prod.ClosedDate = a.ClosedDate == stdDate ? null : a.ClosedDate;
lstProd.Add(o_prod);
}
}
return View(lstProd);  // use this in your View as:   @model IEnumerable<ProductViewModel>