【问题标题】:Entity framework map entity to multiple tables in cascade实体框架将实体级联映射到多个表
【发布时间】:2014-08-21 04:40:17
【问题描述】:

我在使用 EF 时遇到问题。 我有以下情况:

从这个数据库架构中,我想通过合并表数据来生成以下实体:

// Purchases
    public class Purchase
    {
        //Fields related to Purchases
        public int IdPurchase { get; set; }
        public string CodPurchase { get; set; }
        public int IdCustomer { get; set; }
        public decimal Total { get; set; }

        //Fields related to Customers table
        public string CodCustomer { get; protected set; }
        public string CompanyTitle { get; protected set; }
        public string CodType { get; protected set; }

        //Fields related to CustomersType table
        public string DescrType { get; protected set; }
    }

如您所见,在我的上下文中,我不希望每个表有 3 个单独的实体。我想要一个与所有表相关的字段。客户和客户类型表的所有字段都必须是只读的(因此我已将相关设置器设置为受保护),其他字段必须是可编辑的,以便 EF 可以跟踪更改。特别是,我希望能够更改“IdCustomer”字段并让 EF 通过交叉表选择自动更新“CodCustomer”、“CompanyTitle”、“DescrType”......等等。

为此,我编写了这个配置类:

 internal class PurchaseConfiguration : EntityTypeConfiguration<Purchase>
    {
        public PurchaseConfiguration(string schema = "dbo")
        {
            ToTable(schema + ".Purchases");
            HasKey(x => x.IdPurchase);

            Property(x => x.IdPurchase).HasColumnName("IdPurchase").IsRequired().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
            Property(x => x.IdCustomer).HasColumnName("IdCustomer").IsRequired();
            Property(x => x.Total).HasColumnName("Total").IsRequired().HasPrecision(19, 4);

            Map(mc =>
             {
                 mc.Properties(n => new
                 {
                     n.CodCustomer,
                     n.CompanyTitle,
                     n.CodType
                 });
                 mc.ToTable("Customers");
             });

            Map(mc =>
            {
                mc.Properties(n => new
                {
                    n.DescrType,
                });
                mc.ToTable("CustomersType");
            });

        }
    }

我已经对其进行了测试,但它没有按预期工作。我总是收到这条消息:

“购买”类型的属性只能映射一次。非关键 属性“CodCustomer”被多次映射。确保 Properties 方法只指定每个非键属性一次。

也许有什么问题或者我忘记了一些东西(例如我不知道在哪里指定它们的 Map 的连接字段)。 我怎样才能以正确的方式完成这项任务? 我不想在我的上下文中使用“Customers”和“CustomersType”DBSet。 有办法避免吗?

我什至想在“IdCustomer”设置器中添加一个自定义查询来手动更新“Customers”和“CustomersType”相关字段,但我不想这样做有两个原因:

  1. 我没有任何 DbConnection 可用于“Purchases”类,因此我无法创建 DbCommand 来从 DB 读取数据。
  2. 我希望实体类是持久无知的
  3. EF 似乎是一个强大的工具,可以做这些事情,我不想通过编写自定义程序来重新发明轮子。

我已经上传了示例 C# 源代码和表 CREATE 脚本 (MS SQLServer) here。 所有实体都由“EF 反向 POCO 生成器”T4 模板自动生成(T4 模板被禁用,激活它设置 CustomTool = TextTemplatingFileGenerator)。 不要忘记更新 app.config 中的 ConnectionString。

提前致谢。

【问题讨论】:

    标签: c# entity-framework orm mapping entity


    【解决方案1】:

    不是正确的映射

    恐怕坏消息是这种表结构无法实现这种映射。您在此处尝试实现的目标称为实体拆分。但是,实体拆分需要 1:1 关联,因为相关表中的记录集代表一个实体。使用此映射,您不能有一个Customer 属于多个Purchase。这意味着您可以修改多个Purchase 实体,只需修改其中一个实体的Customer 属性。

    也许这个消息还不错,因为我认为你实际上想要有 1-n 个关联。但是你不能在Purchase 中拥有这些“扁平化”属性。

    作为替代方案,您可以像这样创建委托属性:

    public string CodCustomer
    {
        get { return this.Customer.CodCustomer; }
        set { this.Customer.CodCustomer = value; }
    }
    

    当你获取Purchases 时,你必须Include()Customers 和CustomersTypes。

    另一种选择是使用 AutoMapper 之类的工具将 Purchase 映射到具有扁平属性的 DTO 类型。

    但是异常告诉我什么?

    您将Purchase 实体映射到Purchases 表。但是您没有指定要将哪些属性映射到该表。所以 EF 假设 all 属性应该映射到它。这就是CodCustomer 的第一个(隐式)映射。第二个是mc.ToTable 语句中的那个。 (EF 只报告第一个问题。)

    要解决此问题,您应该为剩余的Purchase 属性添加映射语句:

    Map(mc =>
    {
        mc.Properties(n => new
        {
            n.IdPurchase,
            n.CodPurchase,
            n.IdCustomer,
            n.Total,
        });
        mc.ToTable("Purchases");
    });
    

    顺便说一下,CustomerCustomersType的映射配置类也要去掉,它们是多余的。

    但是,如上所述,数据库架构与所需的结构不匹配。如果您尝试保存Purchase,您将获得外键约束异常。这是因为 EF 需要以下表结构:

    其中CustomerCustomersType 中的列IdPurchasePurchase 的主键和外键。我不认为这是您在设计数据库时的想法。

    【讨论】:

    • 是的,知道了。我指定了错误的映射关联。按照你的建议,我达到了我的目的。使用适当的映射指定所有 3 个实体,并通过在内部设置“客户”和“客户类型”,可以按照示例中的建议创建委托属性,而不会暴露所有不必要的类型,并且它可以工作!
    • 不幸的是,EF 有很大的限制。假设我有一个“Purchases”类的新实例,并将其添加到 DBSet(因此状态为“已添加”)。如果我设置“IdCustomer”属性,我的导航属性将不会加载。仅当实体未处于“已添加”状态时才会加载它们。我发现的唯一方法是调用 SaveChanges() 来持久化它们。这里有解决方法吗?
    • context.Purchases.Create() 创建Purchase。现在Purchase 是一个启用了延迟加载的动态代理(如果没有关闭延迟加载)。
    • 哦,是的!创建一个虚拟代理听起来不错:) 但是 create() 是一个 DbSet 方法。通常数据级别不会直接公开 DbSet 对象......在存储库场景中,我应该使用 Create() 方法扩展接口吗?这是一种正确的设计方法吗?
    • 是的,您可以将Create 包装在存储库中,这是一种常见的方法(一旦选择了存储库)。
    猜你喜欢
    • 1970-01-01
    • 2015-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-04
    • 1970-01-01
    • 2013-02-18
    • 2014-04-01
    相关资源
    最近更新 更多