2011-05-09 66 views
6

我有一个客户实体引用一组地址。这里的复杂之处在于我希望能够识别特定地址作为默认地址。EF Code First 4.1 - 如何配置默认的一对多关系

如果可能,我想在客户表中保存默认地址的FK。这似乎比在地址表中使用列来标识默认值更为优雅。

我在定义这种关系方面遇到了流畅的API问题。当我运行下面的代码时,我得到一个异常,它说: “保存不会为其关系公开外键属性的实体时发生错误。EntityEntries属性将返回null,因为无法将单个实体标识为例外情况:通过在实体类型中公开外键属性,可以更轻松地处理保存时的异常,详情请参阅InnerException。 “无法确定依赖操作的有效排序。由于外键约束,模型要求或存储生成的值,可能存在依赖关系。”

我创建了一个控制台应用程序来显示确切的问题。在这个测试应用程序中,我有一个Customer实体,一个Address和api api配置。

任何帮助将非常感激:

using System; 
using System.Collections.Generic; 
using System.Data.Entity.ModelConfiguration; 
using System.ComponentModel.DataAnnotations; 
using System.Data.Entity; 

namespace OneToManyWithDefault 
{ 

    public class Customer 
    { 
     private ICollection<Address> m_Addresses; 

     public Customer() 
     { 
      Addresses = new List<Address>(); 
     } 

     public int Id { get; set; } 
     public string CompanyName { get; set; } 
     public virtual ICollection<Address> Addresses 
     { 
      get 
      { 
       if (m_Addresses == null) 
       { 
        m_Addresses = new List<Address>(); 
       } 
       return m_Addresses; 
      } 
      set 
      { 
       m_Addresses = value; 
      } 
     } 
     public Address DefaultAddress { get; set; } 
     public int DefaultAddressId { get; set; } 

    } 

    public class Address 
    { 
     public int Id { get; set; } 
     public string Town { get; set; } 
     public Customer Customer { get; set; } 
    } 

    public class MyContext 
     : DbContext 
    { 
     public DbSet<Customer> Customers { get; set; } 

     public MyContext(string connectionString) 
      : base(connectionString) 
     { 

     } 

     protected override void OnModelCreating(DbModelBuilder modelBuilder) 
     { 
      modelBuilder.Configurations.Add(new CustomerConfiguration()); 
      modelBuilder.Configurations.Add(new AddressConfiguration()); 
      base.OnModelCreating(modelBuilder); 
     } 
    } 

    public class CustomerConfiguration 
     : EntityTypeConfiguration<Customer> 
    { 
     public CustomerConfiguration() 
      : base() 
     { 
      HasKey(p => p.Id); 
      Property(p => p.Id) 
       .HasColumnName("Id") 
       .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity) 
       .IsRequired(); 
      Property(p => p.CompanyName) 
       .HasColumnName("Name") 
       .IsRequired(); 

      // Configure the mapping for the Default Address (this is likely to be wrong!): 
      HasRequired(p => p.DefaultAddress).WithMany() 
       .Map(x => x.MapKey("DefaultAddressId")) 
       .WillCascadeOnDelete(false); 
      HasRequired(p => p.DefaultAddress) 
       .WithMany() 
       .HasForeignKey(x => x.DefaultAddressId); 

      ToTable("Customers"); 
     } 
    } 

    public class AddressConfiguration 
     : EntityTypeConfiguration<Address> 
    { 
     public AddressConfiguration() 
      : base() 
     { 
      HasKey(p => p.Id); 
      Property(p => p.Id) 
       .HasColumnName("Id") 
       .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity) 
       .IsRequired(); 
      Property(p => p.Town) 
       .HasColumnName("Town") 
       .IsRequired(); 

      HasRequired(p => p.Customer) 
       .WithMany(c => c.Addresses) 
       .Map(x => x.MapKey("CustomerId")); 

      ToTable("Addresses"); 
     } 
    } 

    class Program 
    { 
     private const string ConnectionString = 
      @"Server=.\sql2005;Database=OneToManyWithDefault;integrated security=SSPI;"; 

     static void Main(string[] args) 
     { 
      Customer headOffice = new Customer(); 
      headOffice.CompanyName = "C1"; 

      Address address = new Address(); 
      address.Town = "Colchester"; 
      headOffice.Addresses.Add(address); 

      address = new Address(); 
      address.Town = "Norwich"; 
      headOffice.Addresses.Add(address); 
      headOffice.DefaultAddress = address; 

      MyContext context = new MyContext(ConnectionString); 
      context.Customers.Add(headOffice); 
      context.SaveChanges(); 

      Console.WriteLine("Done."); 
      Console.ReadLine(); 
     } 
    } 
} 

非常感谢,

保罗。

回答

7

我不明白什么EF在那里谈论异常中的“未暴露的外键”。我会考虑内部例外作为重要部分:

无法确定依赖操作的有效排序 。由于外键 约束,模型要求或 存储生成的值,可能存在依存关系 。

我想在你的模型问题是,你有CustomerAddress之间的相互依存:一个地址需要一个客户(你已经将其标记为在映射代码需要)和另一只手客户需要一个地址(默认地址是要求都是由于不可空的外键和由于您的映射代码)。因此,EF不知道您的示例代码中首先要保存哪个实体 - 默认地址还是客户?两个实体都需要使用有效的FK限制保存另一个实体的主键。

我能看到的最简单的方法是使默认地址在模型可选,然后保存两次(我忽略其工作按照约定的映射反正):

public class Customer 
{ 
    private ICollection<Address> m_Addresses; 

    public Customer() { Addresses = new List<Address>(); } 

    public int Id { get; set; } 
    public string CompanyName { get; set; } 
    public virtual ICollection<Address> Addresses { get { ... } set { ... } } 
    public Address DefaultAddress { get; set; } 
    public int? DefaultAddressId { get; set; } // FK for optional relationship 
} 

public class Address 
{ 
    public int Id { get; set; } 
    public string Town { get; set; } 
    public Customer Customer { get; set; } 
} 

// ... 

public class CustomerConfiguration : EntityTypeConfiguration<Customer> 
{ 
    public CustomerConfiguration() : base() 
    { 
     Property(p => p.CompanyName) 
      .HasColumnName("Name") 
      .IsRequired(); 

     HasMany(c => c.Addresses) 
      .WithRequired(a => a.Customer) 
      .Map(x => x.MapKey("CustomerId")); 
    } 
} 

public class AddressConfiguration : EntityTypeConfiguration<Address> 
{ 
    public AddressConfiguration() : base() 
    { 
     Property(p => p.Town) 
      .HasColumnName("Town") 
      .IsRequired(); 
    } 
} 

然后你的程序看起来像这样的:

static void Main(string[] args) 
{ 
    Customer headOffice = new Customer(); 
    headOffice.CompanyName = "C1"; 

    Address address = new Address(); 
    address.Town = "Colchester"; 
    headOffice.Addresses.Add(address); 

    address = new Address(); 
    address.Town = "Norwich"; 
    headOffice.Addresses.Add(address); 

    //headOffice.DefaultAddress = address; 
    //We don't set the default address here as SaveChanges would throw an 
    //exception. But because it is optional now we are allowed to leave it null. 

    MyContext context = new MyContext(ConnectionString); 
    context.Customers.Add(headOffice); 
    context.SaveChanges(); 

    headOffice.DefaultAddress = address; // headoffice and address have now PKs 
    context.SaveChanges(); // Updates headoffice in the DB with default address 
} 

这双SaveChanges是丑陋的,但我没有看到另一种方式。

+0

谢谢你,这是有道理的,你在这里说。我会试试这个,并在这里更新我如何继续。 – P2l 2011-05-10 09:32:48

+0

我现在已经测试过了,它适用于我们。正如你所说,它很丑,但我看不到另一种方法。谢谢你的帮助。 – P2l 2011-05-12 12:55:49

相关问题