首先是文字或常量作为EF代码中复合键的一部分

时间:2012-07-17 16:06:21

标签: entity-framework entity-framework-4.1 ef-code-first

我对实体框架的Code First方法相对较新。我已经使用了Database First方法一段时间了,但Code First似乎更适合我目前正在开发的应用程序。我正在使用现有的MS SQL数据库,我不允许对数据库进行任何更改。我使用Code First的原因是因为Fluent API允许我动态地为表分配表名。

那就是说,我有一个困境,我需要在两个表之间分配一个关系。一个表ArCodes具有由CodeType和Code组成的复合键(两者都是字符串)。 CodeType列确定代码类型,Code列是代码类型唯一的标识符。

public class ArCode {

    [Column("cod_typ", Order = 0), Key]
    public string CodeType { get; set; }

    [Column("ar_cod", Order = 1), Key]
    public string Code { get; set; }

    [Column("desc")]
    public string Description { get; set; }

}

另一张表,Invoices,需要与ArCodes表关联“ship via”代码和“条款”代码。

public class Invoice {
    [Column("pi_hist_hdr_invc_no"), Key]
    public int InvoiceNumber { get; set; }

    [Column("shp_via_cod")]
    public string ShipViaCode { get; set; }

    public ArCode ShipVia { get; set; }

    [Column("terms_cod")]
    public string TermsCode { get; set; }

    public ArCode Terms { get; set; }

}

我想设置“ShipVia”属性和“Terms”属性的关系。但是,我不知道如何对复合键的CodeType部分这样做。对于“发货通过”代码,代码类型应为“S”,代码“条款”代码,代码类型应为“T”。

我在DB Context中尝试了以下内容,但它不起作用:

    protected override void OnModelCreating(DbModelBuilder modelBuilder) {
        // setup the table names
        modelBuilder.Entity<ArCode>().ToTable("ARCODS" + CompanyCode);
        modelBuilder.Entity<Invoice>().ToTable("IHSHDR" + CompanyCode);

        //
        // setup the relationships
        //

        // 1 Invoice <--> 0-1 Ship Via AR Codes
        modelBuilder.Entity<Invoice>()
            .HasOptional(invoice => invoice.ShipVia)
            .WithMany()
            .HasForeignKey(invoice => new { TheType = "S", invoice.ShipViaCode })
            ;

        base.OnModelCreating(modelBuilder);
    }

任何帮助都将不胜感激。

更新#1

好的,我将代码缩减为最简单的形式,然后我按照@GertArnold提供的解决方案。

public abstract class ArCode {

    [Column("cod_typ")]
    public string CodeType { get; set; }

    [Column("ar_cod")]
    public string Code { get; set; }

    [Column("terms_desc")]
    public string TermsDescription { get; set; }

    [Column("terms_typ")]
    public string TermsType { get; set; }

    [Column("shp_via_desc")]
    public string ShipViaDescription { get; set; }

    [Column("tax_desc")]
    public string TaxDescription { get; set; }

}

public class TermsCode : ArCode { }
public class ShipViaCode : ArCode { }

public class Invoice {
    [Column("pi_hist_hdr_invc_no"), Key]
    public int InvoiceNumber { get; set; }

    [Column("hdr_invc_dat")]
    public DateTime InvoiceDate { get; set; }

    [Column("shp_via_cod")]
    public string ShipViaCode { get; set; }

    public ShipViaCode ShipVia { get; set; }

    [Column("terms_cod")]
    public string TermsCode { get; set; }

    public TermsCode Terms { get; set; }

    public Invoice() {
    }

}

public class PbsContext : DbContext {

    public DbSet<Invoice> Invoices { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder) {

        modelBuilder.Entity<Invoice>().ToTable("IHSHDR");

        modelBuilder.Entity<ArCode>().HasKey(r => r.Code).ToTable("ARCODS");

        modelBuilder.Entity<TermsCode>().Map(m => m.Requires("CodeType")
               .HasValue("T").HasColumnType("varchar").HasMaxLength(1).IsRequired())
               .ToTable("ARCODS");
        modelBuilder.Entity<ShipViaCode>().Map(m => m.Requires("CodeType")
               .HasValue("S").HasColumnType("varchar").HasMaxLength(1).IsRequired())
               .ToTable("ARCODS");

        base.OnModelCreating(modelBuilder);
    }

    public PbsContext()
        : base("name=PbsDatabase") {
    }
}

但是,以下代码返回错误:

PbsContext context = new PbsContext();
var invoice = context.Invoices.OrderByDescending(r => r.InvoiceDate).FirstOrDefault();
  

错误3032:从第28行开始映射片段时出现问题:映射了条件成员'ArCode.cod_typ',其条件不是'IsNull = False'。删除ArCode.cod_typ上的条件或从映射中删除它。

如果从ArCode类中删除“CodeType”列并将所有“CodeType”引用更改为OnModelCreating事件中“cod_typ”的数据库列名,则上述语句将无错执行。但是,invoice.ShipVia和invoice.Terms都是null事件,尽管数据库中有匹配的记录。

更新#2

public abstract class ArCode {

    [Column("ar_cod")]
    public string Code { get; set; }

    [Column("terms_desc")]
    public string TermsDescription { get; set; }

    [Column("terms_typ")]
    public string TermsType { get; set; }

    [Column("shp_via_desc")]
    public string ShipViaDescription { get; set; }

    [Column("tax_desc")]
    public string TaxDescription { get; set; }

}

public class TermsCode : ArCode { }
public class ShipViaCode : ArCode { }

public class Invoice {
    [Column("pi_hist_hdr_invc_no"), Key]
    public int InvoiceNumber { get; set; }

    [Column("hdr_invc_dat")]
    public DateTime InvoiceDate { get; set; }

    [Column("shp_via_cod")]
    public ShipViaCode ShipVia { get; set; }

    [Column("terms_cod")]
    public TermsCode Terms { get; set; }

    public Invoice() {
    }

}

public class PbsContext : DbContext {

    public DbSet<Invoice> Invoices { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder) {

        modelBuilder.Entity<Invoice>().ToTable("IHSHDR");

        modelBuilder.Entity<ArCode>().HasKey(r => r.Code).ToTable("ARCODS");

        modelBuilder.Entity<TermsCode>().Map(m => m.Requires("CodeType")
               .HasValue("T").HasColumnType("varchar").HasMaxLength(1).IsRequired())
               .ToTable("ARCODS");
        modelBuilder.Entity<ShipViaCode>().Map(m => m.Requires("CodeType")
               .HasValue("S").HasColumnType("varchar").HasMaxLength(1).IsRequired())
               .ToTable("ARCODS");

        base.OnModelCreating(modelBuilder);
    }

    public PbsContext()
        : base("name=PbsDatabase") {
    }
}

现在,以下代码返回错误:

PbsContext context = new PbsContext();
var invoice = context.Invoices.OrderByDescending(r => r.InvoiceDate).FirstOrDefault();
  

EntityCommandExecutionException - 无效的列名称“ShipVia_Code”。列名称'Terms_Code'无效。

1 个答案:

答案 0 :(得分:2)

EF想要的是不可能的。 ArCode有一个复合键,因此与它的任何关联都必须使用两个属性。这意味着在Invoice中你需要四个属性(两对)来引用两个ArCode对象。但是其中两个属性(CodeType的属性)不会被数据库中的列备份,因此EF无法映射它们。

但是...有一种方法可以帮助你。您可以从ArCode创建两个派生类,让Invoice通过单属性关联引用它们。但是你必须转移模型,并通过定义一个密钥来愚弄EF:

public abstract class ArCode { ... } // abstract!

public class TermsCode : ArCode { }

public class ShipViaCode : ArCode { }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Invoice>().ToTable("IHSHDR");

    modelBuilder.Entity<Invoice>().HasOptional(i => i.Terms).WithOptionalDependent().Map(m => m.MapKey("terms_cod"));
    modelBuilder.Entity<Invoice>().HasOptional(i => i.ShipVia).WithOptionalDependent().Map(m => m.MapKey("shp_via_cod"));

    modelBuilder.Entity<ArCode>().HasKey(a => a.Code).ToTable("ARCODS");
    modelBuilder.Entity<TermsCode>().Map(m => m.Requires("CodeType")
        .HasValue("T").HasColumnType("varchar").HasMaxLength(1).IsRequired())
        .ToTable("ARCODS");
    modelBuilder.Entity<ShipViaCode>().Map(m => m.Requires("CodeType")
        .HasValue("S").HasColumnType("varchar").HasMaxLength(1).IsRequired())
        .ToTable("ARCODS");

    base.OnModelCreating(modelBuilder);
}

public class Invoice
{
    [Column("pi_hist_hdr_invc_no"), Key]
    public int InvoiceNumber { get; set; }

    public ShipViaCode ShipVia { get; set; }

    public TermsCode Terms { get; set; }
}

如果您不必通过EF插入ARCODS条记录,这可能对您有用。虽然数据库允许,但它不允许您插入具有相同Code s的记录。但我希望ARCODS的内容非常稳定,也许足以用脚本填充它。