【问题标题】:Entity Framework with an Intersect table and correspond Linq query具有相交表和对应 Linq 查询的实体框架
【发布时间】:2021-12-03 23:31:45
【问题描述】:

我是 Entity Framework 和 Linq 的新手。我正在使用 .Net Core 和 EF Core 5。到目前为止我很喜欢它,但遇到了一些我正在努力解决的问题。这是我真正感到困惑和不理解的。

我的课程中有一些产品。我有客户在另一个类别中购买这些产品。每个客户都可以在他们的业务中使用不同的名称来称呼我的一种产品,因此我需要允许他们定义他们用于我的产品的别名。

这是我的两个父类(产品和客户)

    public class Product
    {
        public int Id {get; set;}
        public Guid IdGuid {get; set;}
        public string Name {get; set;}
    }

    public class Customer
    {
        public int Id {get; set;}
        public Guid IdGuid {get; set;}
        public string Name {get; set;}
    }

在这些类中,我有一个 Id 列,数据库使用它来实现引用完整性。我不会将此 ID 传回给用户。相反,每当用户获得其中一个对象时,他们都会返回 Guid,以便他们可以唯一地标识数据库中的行。连接这两个表的别名类如下:

    public class ProductCustomerAlias
    {
        public Product Product {get; set;}
        public Customer Customer {get; set;}
        public string Alias {get; set;}
    }

由于我需要数据库表使用由产品 ID 和客户 ID 组成的复杂键,我必须重写上下文对象的 OnModelCreating 方法:

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<ProductCustomerAlias>()
                .HasKey("ProductId", "CustomerId");
        }
'''
This ends up creating a Database table that has the following structure (in Oracle)
'''
CREATE TABLE "RTS"."ProductCustomerAlias" 
(
"ProductId" NUMBER(10,0), 
"CustomerId" NUMBER(10,0), 
"Alias" NVARCHAR2(100)
)

到目前为止一切顺利。我现在有一个相交表来存储 Alias 的主键,其中 ProductId 和 CustomerId 都是数据库上下文中 Products 和 Customers 的键整数值。

所以我的下一步是开始创建从这些对象中检索数据的 Repo 类。请记住,提交请求的最终用户只能将 Product.IdGuid 和 Customer.IdGuid 传递给我,因为这就是他们所拥有的一切。在我的回购中,我有以下代码:

public async Task<ProductCustomerAlias> GetProductCustomerAliasAsync(Guid pProductId, Guid pCustomerId)
{
   var alias = await (from ali in _context.ProductCustomerAlias
                      join prod in _context.Products on ali.Product.Id equals prod.Id
                      join cust in _context.Customers on ali.Customer.Id equals cust.Id
                      where cust.IdGuid == pCustomerId && prod.IdGuid == pProductId
                      select new {Product = prod,
                                  Customer = cust,
                                  Alias = ali.Alias}
                      ).FirstOrDefaultAsync();

   return (IEnumerable<ProductCustomerAlias>)alias;
} 

我的问题是它给了我以下错误:

Cannot convert type '<anonymous type: Models.Product Product, Models.Customer Customer, string Alias>' to 'System.Collections.Generic.IEnumerable<Models.ProductCustomerAlias>' 

请不要告诉我错误是在告诉我到底哪里出了问题。我敢肯定,但如果我明白我在哪里搞砸了,我就不会浪费时间输入这个可笑的冗长解释。那么如何将 Linq 查询的结果转换为指定的类型呢?还有什么我做错了吗?任何帮助将不胜感激。

【问题讨论】:

  • 因为您选择的内容无法转换为所需的类型。
  • 好的。那么如何从该表中获取数据并将其转换为正确的类型呢?该类永远不会匹配表结构。我确信这只是我需要用 EF 学习但还不能掌握的东西。
  • 您可以创建一个具有 Product、Customer 和 Alias 属性的 DTO 类,然后选择并返回此 DTO 对象
  • 我很抱歉太密集了。我可以创建该 DTO 对象,但您建议的三个属性与我试图传回的类中已经存在的属性完全相同。我认为问题在于我不明白如何从 Linq 查询的选择部分正确获取对象。请记住,数据库表只有 ID。我假设上下文会将这些 ID 解析回产品和客户对象。就像我说的我是 EF 和 Linq 的新手,所以请温柔一点。 :-)
  • 顺便感谢您抽出宝贵时间提供帮助。非常感谢。

标签: c# linq entity-framework-core


【解决方案1】:

回答您的具体问题。

那么如何将 Linq 查询的结果转换为指定的类型?

不能,因为 LINQ 查询结果是匿名类型,与所需的结果类型(具体实体类型)不兼容,因此无法强制转换为它。

还有什么我做错了吗?

很抱歉,但基本上一切都错了。

  • 不需要Select,因为所需的结果类型是被查询实体的确切类型。即ali 变量在这里
from ali in _context.ProductCustomerAlias

结果正是您所需要的(在应用过滤器和限制运算符之后)

  • 也不需要手动连接,因为它们是由导航属性自动提供的,即此处
join prod in _context.Products on ali.Product.Id equals prod.Id

prodali.Product 完全相同

  • 尝试将单个对象转换为可枚举是错误的
return (IEnumerable<ProductCustomerAlias>)alias

即使alias 变量的类型正确,这也会失败,因为它是单个对象而不是集合。


因此,解决方案非常简单 - 使用相应的 DbSet,应用过滤器 (Where),将结果限制为 0 或 1 (FirstOrDefault{Async}),然后就完成了。

只有一个小细节。由于您正在查询并返回一个完整的实体,因此它的导航属性(如ProductCustomer)被认为是一个相关数据,EF Core 不会自动填充(加载)。您必须明确选择加入,这称为 eager loading,并在官方 EF Core 文档的 Loading Related Data 部分中进行了解释(我还建议熟悉 导航属性 em> 和整个Relationships 概念)。简单来说,这需要使用专门提供的 EF Core 扩展方法,称为 IncludeThenInclude

说了这么多,解决方案是这样的:

public async Task<ProductCustomerAlias> GetProductCustomerAliasAsync(
    Guid pProductId, Guid pCustomerId)
{
    return await _context.ProductCustomerAlias
        .Include(a => a.Product)
        .Include(a => a.Customer)
        .Where(a => a.Customer.IdGuid == pCustomerId && a.Product.IdGuid == pProductId)
        .FirstOrDefaultAsync();
} 

您甚至可以用对谓词重载FirstOrDefaultAsync 的一次调用来替换最后两行,但这不是必需的,因为它只是上述的快捷方式


return await _context.ProductCustomerAlias
    .Include(a => a.Product)
    .Include(a => a.Customer)
    .FirstOrDefaultAsync(a => a.Customer.IdGuid == pCustomerId && a.Product.IdGuid == pProductId);

【讨论】:

  • 感谢您的简明解释。我发现了一个事实,即我在返回中有 IEnumerable,但其余的对我来说仍然是个谜。非常感谢您抽出时间来帮助我。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多