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

时间:2011-03-16 13:12:58

标签: 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};
}

以下代码会引发以下错误:

  

“实体或复杂类型Shop.Product不能构建在   LINQ to Entities查询“

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

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

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

14 个答案:

答案 0 :(得分:358)

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

答案 1 :(得分:255)

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

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

答案 2 :(得分:72)

我找到了另一种方法,你必须构建一个派生自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 :(得分:34)

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

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

答案 4 :(得分: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;
}

答案 5 :(得分:3)

你可以使用它,它应该工作 - &gt;在使用select:

创建新列表之前,您必须使用toList
db.Products
    .where(x=>x.CategoryID == categoryID).ToList()
    .select(x=>new Product { Name = p.Name}).ToList(); 

答案 6 :(得分:1)

在回答另一个被标记为重复的问题(see here)时,我根据Soren的答案找到了一个快速简便的解决方案:

onClicked

注意: 此解决方案仅在Task类上具有导航属性(外键)时才有效(此处称为&#39; Incident&#39;)。 如果你没有这个,你可以使用其他一个已发布的解决方案&#34; AsQueryable()&#34;。

答案 7 :(得分:1)

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

这些有点像viewmodel,您可以在其中放入所需的属性,您可以在控制器中手动映射它们,也可以使用AutoMapper等第三方解决方案。

使用DTO&#39>你可以:

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

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

答案 8 :(得分:0)

如果您正在使用Entity框架,请尝试从DbContext中删除属性,该属性将您的复杂模型用作实体 将多个模型映射到名为Entity

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

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

答案 9 :(得分:0)

如果您正在执行Linq to Entity,则ClassType关闭查询new

时,selectonly 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中添加了complex properties选项封闭,你会收到此错误

  

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

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

所以我什么时候可以在select关闭时使用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,因此我们可以在选择中使用{{1}}

答案 10 :(得分:0)

只添加AsEnumerable():

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

答案 11 :(得分:0)

在许多情况下,不需要转换。想想你想要强类型List的原因,并评估你是否只想要数据,例如,在Web服务中或显示它。这种类型无关紧要。 您只需要知道如何阅读它并检查它是否与您定义的匿名类型中定义的属性相同。这是最佳情况,导致您不需要实体的所有字段,这就是匿名类型存在的原因。

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

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

答案 12 :(得分: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>

答案 13 :(得分:-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};
}