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


389

由实体框架生成的实体类型称为产品。我已经写了这个查询

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};它时,它可以正常工作。

如何执行自定义选择部分?


System.NotSupportedException:'不能在LINQ to Entities查询中构造实体或复杂类型'StudentInfoAjax.Models.Student'。
Wahid博士

Answers:


390

您不能(也应该不能)投影到映射的实体上。但是,您可以投影到匿名类型或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();
}

152
我不明白为什么我不应该这样做...这将非常有用...
Jonx 2011年

118
好吧,EF中的映射实体基本上代表数据库表。如果投影到映射的实体,则基本上要做的是部分加载一个实体,这不是有效状态。EF没有任何线索,例如,将来如何处理此类实体的更新(默认行为可能是使用null或对象中包含的任何内容覆盖未加载的字段)。这将是危险的操作,因为您可能会丢失数据库中的某些数据,因此不允许在EF中部分加载实体(或将项目投影到映射的实体)。
Yakimych

26
@Yakimych很有用,除非您有一些通过查询生成/创建的聚合实体,因此完全了解/打算创建一个全新的实体,然后您将对其进行操作并随后添加。在这种情况下,您要么必须强制运行查询,要么将其推入dto并返回到实体中以进行添加-这很令人沮丧
Cargowire 2011年

16
@Cargowire-我同意,这种情况是存在的,当您知道自己在做什么但由于限制而被禁止时,这种情况令人沮丧。但是,如果允许这样做,将会有许多沮丧的开发人员抱怨他们在尝试保存部分加载的实体时丢失了数据。IMO,一个会引起很多噪音(引发异常等)的错误胜于会导致难以跟踪和解释的隐藏错误的行为(在您开始注意到丢失的数据之前,事情做得很好)。
Yakimych


275

您可以将项目投影为匿名类型,然后再将其转换为模型类型

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


3
当您不需要/不关心要投影的所选实体的状态时,这是最干净的解决方案。
2013年

2
而当您不在乎是否返回IEnumerable或IQueryable时;)。但是您仍然得到我的支持,因为此解决方案现在对我有效。
Michael Brennt 2013年

10
从技术上讲,对模型类型的投影是在查询之外进行的,我认为还需要对列表进行其他迭代。我不会在我的代码中使用此解决方案,但这是解决该问题的方法。提起
1c1cle 2014年

4
我更喜欢这种方式而不是公认的DTO解决方案-更优雅,更干净
Adam Hey

7
除此之外,就其而言,这实际上不是该问题的答案。这是关于如何执行Linq To Objects投影而不是Linq to Entities查询投影的答案。因此,DTO选项是re:Linq to Entities的唯一选项。
rism

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

不知道这是否是“允许的”,但是它可以工作。


3
聪明!现在尝试了一下,它就可以了。我相信它会以某种方式烧伤我。
丹尼尔(Daniel)

5
顺便说一句,如果您尝试保留GetProducts()的结果,这确实会给您带来困扰,因为EF无法找到PseudoProduct的映射,例如““ System.InvalidOperationException:找不到EntityType'blah.PseudoProduct'的映射和元数​​据信息”。
广播

4
最佳答案,也是唯一在问题参数范围内回答的答案。所有其他答案都会更改返回类型或过早执行IQueryable并使用linq来处理对象
rdans,2014年

2
100%震惊了它的工作...在EF 6.1中,它正在工作。
TravisWhidden

2
@mejobloggs尝试对派生类使用[NotMapped]属性,如果使用的是流畅的API,请尝试.Ignore <T>。
顿克

37

这是不声明附加类的一种方法:

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

23

另一种简单的方法:)

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

好的一点是,我刚刚通过您的好评得知了IQueryable的知识。但是,如果您要解释为什么会很好,那在ToList()之后就没有用了,原因是您不能在LINQ-to-SQL查询中使用泛型列表。因此,如果您知道您总是会把结果推到调用者的另一个查询中,那么肯定可以使用IQueryable。但是如果没有...如果您以后将其用作通用列表,请在方法内部使用ToList(),这样就不会在IQueryable的每次调用中都进行ToList()了。
PositiveGuy

您完全好吧我的朋友。我只是模仿问题方法的签名,因此将其转换为可查询的...;)
Soren 2012年

1
这样可以正常工作,在ToList()之后,productList将变得不可编辑。如何使其可编辑?
doncadavona 2015年

如果您.ToList输入查询,它将被执行并从服务器中提取数据,那么再次进行查询又有AsQueryable什么用呢?
Moshii

1
@Moshii只是为了满足方法返回类型签名,(正如我在回答中所说,它不再有用了)。
索伦

4

您可以使用它,它应该可以正常工作->您必须toList先使用select来创建新列表,然后使用:

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

3
但是,这仍然会执行'SELECT * FROM [..]',而不是'SELECT name FROM [..]'
Timo Hermans

1

为了回答另一个被标记为重复的问题(请参阅此处),我根据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();

注意:仅当您在Task类(这里称为“事件”)上具有导航属性(外键)时,此解决方案才有效。如果没有,则可以将其他发布的解决方案之一与“ AsQueryable()”一起使用。


1

您可以使用数据传输对象(DTO)解决此问题。

这些有点像在视图模型中放置所需的属性,您可以在控制器中手动映射它们,也可以使用第三方解决方案(例如AutoMapper)进行映射。

使用DTO,您可以:

  • 使数据可序列化(Json)
  • 摆脱循环引用
  • 通过保留不需要的属性来减少网络流量(按视图模型)
  • 使用对象展平

我今年在学校里一直在学习它,这是一个非常有用的工具。


0

如果您使用的是实体框架,则尝试从将复杂模型用作实体的DbContext中删除属性,将多个模型映射到名为Entity的视图模型时遇到相同的问题

public DbSet<Entity> Entities { get; set; }

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


0

如果您正在执行Linq to Entity,则不能在查询结束时使用ClassTypewithnewselectonly anonymous types are allowed (new without type)

看一下我的项目的这个片段

//...
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甚至在上添加了Select闭包,complex properties您将收到此错误

所以查询中removeClassTypes from new关键字Linq to Entity

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

所以我什么时候可以用new with typesselect封闭?

如果您正在处理,则可以使用它 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 如此,我们可以new ClassTypes在选择中使用


当然,您可以使用匿名类型,但是您不能在LINQ查询中创建实体,甚至不能设置匿名成员,因为LINQ-to-Entities仍然会抛出相同的异常。
Suncat2000

0

在许多情况下,不需要进行转换。考虑您要使用强类型List的原因,并评估您是否只想要数据,例如,在Web服务中还是用于显示数据。没关系,类型。您只需要知道如何读取它,并检查它是否与您在定义的匿名类型中定义的属性相同即可。那是最佳方案,导致某些事情不需要实体的所有字段,这就是存在匿名类型的原因。

一种简单的方法是执行此操作:

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

0

它不会让您映射回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>

-1

只添加AsEnumerable():

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

8
永远不要做!这将从数据库中获取所有数据,然后进行选择。
Gh61'9

1
这就是为什么某些公司禁止使用Linq的原因。
哈坎

-2

您可以将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将linq终止于实体。Where子句以及其他所有内容都在linq to Entities之外处理。即检索每个产品,然后通过linq过滤到对象。除此之外,它与上面的.ToList答案几乎完全相同。 stackoverflow.com/questions/5311034/…–
KenF

1
问题在于,这只是从执行中选择*,而不是选择新产品{Name = p.Name},因为您还将获得循环引用。而您只需要名称。
Sterling Diaz
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.