有一个名为 Product
的实体类型由实体框架生成。我写了这个查询
public IQueryable<Product> GetProducts(int categoryID)
{
return from p in db.Products
where p.CategoryID== categoryID
select new Product { Name = p.Name};
}
下面的代码引发以下错误:
“无法在 LINQ to Entities 查询中构造实体或复杂类型 Shop.Product”
var products = productRepository.GetProducts(1).Tolist();
但是当我使用 select p
而不是 select new Product { Name = p.Name};
时,它可以正常工作。
如何执行自定义选择部分?
您不能(也不应该)投影到映射的实体上。但是,您可以投影到匿名类型或 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 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(),它可能会抛出与现在相同的异常,以警告我们需要对我们想要的结果足够具体。
我发现还有另一种可行的方法,您必须构建一个派生自您的 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 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;
}
.ToList
放入查询中,它会被执行并从服务器中提取数据,那么再次创建 AsQueryable
有什么意义?
你可以使用它,它应该可以工作-->在使用 select 创建新列表之前,您必须使用 toList
:
db.Products
.where(x=>x.CategoryID == categoryID).ToList()
.select(x=>new Product { Name = p.Name}).ToList();
针对另一个标记为重复的问题(see here),我根据 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();
注意:此解决方案仅适用于您在任务类(此处称为“事件”)上有导航属性(外键)的情况。如果您没有,您可以使用其他已发布的解决方案之一与“AsQueryable()”。
您可以使用数据传输对象 (DTO) 解决此问题。
这些有点像视图模型,您可以在其中放入所需的属性,您可以在控制器中手动映射它们或使用 AutoMapper 等第三方解决方案。
使用 DTO,您可以:
使数据可序列化(Json)
摆脱循环引用
通过保留不需要的属性来减少网络流量(viewmodelwise)
使用对象展平
我今年在学校一直在学习这个,它是一个非常有用的工具。
如果您使用的是实体框架,请尝试从 DbContext 中删除属性,该属性将您的复杂模型用作实体我在将多个模型映射到名为 Entity 的视图模型时遇到了同样的问题
public DbSet<Entity> Entities { get; set; }
从 DbContext 中删除条目修复了我的错误。
如果您正在执行 Linq to Entity
,则不能在查询 only anonymous types are allowed (new without type)
的 select
闭包中将 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 keyword
,即使在 complex properties
上也会出现此错误
所以从 Linq to Entity 查询的新关键字中删除 ClassTypes ,,
因为它会转化为sql语句并在SqlServer上执行
那么我什么时候可以在 select
关闭时使用 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
在许多情况下,不需要转换。想一想您想要强类型 List 的原因,并评估您是否只想要数据,例如,在 Web 服务中或用于显示它。类型无关紧要。您只需要知道如何阅读它并检查它是否与您定义的匿名类型中定义的属性相同。那是最理想的情况,因为您不需要实体的所有字段,这就是匿名类型存在的原因。
一个简单的方法是这样做:
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>
只添加 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};
}