ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

System.NotSupportedException:“无法在 LINQ to Entities 查询中构造实体或复杂类型 'StudentInfoAjax.Models.Student'。”
您可以按照下面接受的答案将其映射到 dto,但还要确保此模型未在 DB 上下文中使用,否则您将收到相同的错误。

O
Ogglas

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

我不明白为什么我不能这样做......这将非常有用......
嗯,EF 中的映射实体基本上代表数据库表。如果你投影到一个映射实体上,你基本上做的是部分加载一个实体,这不是一个有效的状态。 EF 不知道将来如何处理此类实体的更新(默认行为可能是用空值或对象中的任何内容覆盖未加载的字段)。这将是一个危险的操作,因为您可能会丢失数据库中的一些数据,因此不允许在 EF 中部分加载实体(或投影到映射实体上)。
@Yakimych 这很有意义,除非您有一些通过查询生成/创建的聚合实体,因此完全了解/打算创建一个全新的实体,然后您将对其进行操作并稍后添加。在这种情况下,您要么必须强制运行查询,要么推入 dto 并返回要添加的实体——这令人沮丧
@Cargowire - 我同意,这种情况存在,当您知道自己在做什么但由于限制而不允许这样做时,这令人沮丧。但是,如果允许这样做,将会有很多沮丧的开发人员抱怨他们的数据在尝试保存部分加载的实体时丢失。 IMO,一个伴随着大量噪音(抛出异常等)的错误比可能导致难以追踪和解释的隐藏错误的行为更好(在你开始注意到丢失数据之前,事情会很好地工作)。
F
Farzad Karimi

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

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


当您不需要/不关心要投影的选定实体的状态时,这是最干净的解决方案。
当你不关心你是否返回 IEnumerable 或 IQueryable 时;)。但是你仍然得到我的支持,因为这个解决方案现在对我有用。
从技术上讲,模型类型的投影发生在查询之外,我相信还需要对列表进行额外的迭代。我不会将此解决方案用于我的代码,但它是问题的解决方案。上升。
与公认的 DTO 解决方案相比,我更喜欢这个 - 更加优雅和干净
除了,尊重,它实际上不是问题的答案。这是关于如何进行 Linq To Objects 投影而不是 Linq to Entities 查询投影的答案。所以 DTO 选项是唯一的选项:Linq to Entities。
T
Tomasz Iniewicz

我发现还有另一种可行的方法,您必须构建一个派生自您的 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};
}

不确定这是否“允许”,但它有效。


聪明的!现在试过了,它可以工作。我相信它会以某种方式烧伤我。
顺便说一句,如果您尝试保留 GetProducts() 的结果,这确实会咬您一口,因为 EF 找不到 PseudoProduct 的映射,例如“System.InvalidOperationException:找不到 EntityType 'blah.PseudoProduct' 的映射和元数据信息”。
最佳答案,也是唯一在问题参数范围内回答的答案。所有其他答案都会更改返回类型或过早执行 IQueryable 并使用 linq 到对象
100% 震惊了它的工作......在 EF 6.1 中这是有效的。
@mejobloggs 在派生类上尝试 [NotMapped] 属性,或者 .Ignore 如果您使用的是流利的 API。
B
Bojan Hrnkas

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

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

S
Suren

另一种简单的方法:)

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() 。
你完全没问题,我的朋友。我只是模仿问题方法签名,因为我将它转换为可查询的......;)
这行得通,在 ToList() 之后 productList 变得不可编辑。我怎样才能使它可编辑?
如果您将 .ToList 放入查询中,它会被执行并从服务器中提取数据,那么再次创建 AsQueryable 有什么意义?
@Moshii 只是为了满足方法返回类型签名,(正如我在答案中所说,它不再有用了)。
D
Draken

你可以使用它,它应该可以工作-->在使用 select 创建新列表之前,您必须使用 toList

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

然而,这仍然会执行“SELECT * FROM [..]”,而不是“SELECT name FROM [..]”
C
Community

针对另一个标记为重复的问题(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()”。


J
Jelman

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

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

使用 DTO,您可以:

使数据可序列化(Json)

摆脱循环引用

通过保留不需要的属性来减少网络流量(viewmodelwise)

使用对象展平

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


v
vikas suhag

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

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

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


B
Basheer AL-MOMANI

如果您正在执行 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 上也会出现此错误

所以从 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


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

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

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

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

v
vapcguy

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

H
HamidReza

只添加 AsEnumerable() :

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

永远不要这样做!这将从数据库中获取所有数据,然后进行选择。
这就是为什么有些公司禁止使用 Linq。
H
HamidReza

您可以将 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/…
这样做的问题是,这只是一个 select * from... 执行,而不是 select new Product { Name = p.Name},因为您还将获得一个循环引用。而你只想要名字。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅