EF代码首先4.1 - 如何配置默认的一对多关系 [英] EF Code First 4.1 - How to configure one to many relationship with default

查看:88
本文介绍了EF代码首先4.1 - 如何配置默认的一对多关系的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个Customer实体引用一个地址集合。这里的复杂情况是,我希望能够识别一个特定的地址作为默认地址。



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



在定义这种关系方面,我在流畅的API方面遇到困难。当我运行以下代码时,我收到一个异常,它说:
在保存不暴露其关系的外键属性的实体时出错,EntityEntries属性将返回null,因为单个实体不能被识别为异常的源,通过在您的实体类型中暴露外键属性,可以更容易地处理异常处理,有关详细信息,请参阅InnerException。
无法确定依赖操作的有效排序。由于外键约束,模型要求或存储生成值,可能存在依赖关系。



我创建了一个控制台应用程序来显示精确的问题。在这个测试应用程序中,我有一个客户实体,一个地址和flient api配置。



任何帮助将不胜感激:

 使用系统; 
使用System.Collections.Generic;
使用System.Data.Entity.ModelConfiguration;
使用System.ComponentModel.DataAnnotations;
使用System.Data.Entity;

命名空间OneToManyWithDefault
{

public class Customer
{
private ICollection< Address> m_Addresses;

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

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

}

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

public class MyContext
:DbContext
{
public DbSet< Customer>客户{get;组;

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);
属性(p => p.Id)
.HasColumnName(Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
.IsRequired();
属性(p => p.CompanyName)
.HasColumnName(Name)
.IsRequired();

//配置默认地址的映射(这可能是错误的!):
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);
属性(p => p.Id)
.HasColumnName(Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
.IsRequired();
属性(p => p.Town)
.HasColumnName(Town)
.IsRequired();

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

ToTable(Addresses);
}
}

类程序
{
private const string ConnectionString =
@Server = .\sql2005; Database = OneToManyWithDefault ;综合安全= SSPI;;

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

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

address = new Address();
address.Town =诺维奇;
headOffice.Addresses.Add(address);
headOffice.DefaultAddress = address;

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

Console.WriteLine(完成);
Console.ReadLine();
}
}
}

非常感谢,



Paul。

解决方案

我不明白EF在说什么不暴露外键的例外。我认为内部例外是重要的部分:


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




我认为您的模型中的问题是您在客户地址之间有相互依赖关系:地址需要一个客户(您在映射代码中将其标记为 必需),另一方面,客户需要一个地址(默认地址需要 - 可用的外键,并由于您的映射代码)。那么,EF不知道您的示例代码中最先保存哪个实体 - 默认地址还是客户?两个实体都需要使用有效的FK约束来保存另一个主键。



我可以看到的最简单的解决方案是在模型中使默认地址是可选的,然后保存两次(我忽略了按惯例工作的映射):

  public class Customer 
{
私人ICollection<地址> m_Addresses;

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

public int Id {get;组; }
public string CompanyName {get;组; }
public virtual ICollection< Address>地址{get {...} set {...}}
public Address DefaultAddress {get;组; }
public int? DefaultAddressId {get;组; } // FK为可选关系
}

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

// ...

public class CustomerConfiguration:EntityTypeConfiguration< Customer>
{
public CustomerConfiguration():base()
{
属性(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()
{
属性(p => p.Town)
.HasColumnName(Town)
.IsRequired();
}
}

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

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

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

address = new Address();
address.Town =诺维奇;
headOffice.Addresses.Add(address);

//headOffice.DefaultAddress = address;
//我们不设置默认地址,因为SaveChanges会抛出
//异常。但是因为现在是可选的,我们被允许将它留空。

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

headOffice.DefaultAddress = address; // headoffice and address have now PKs
context.SaveChanges(); //更新数据库中的headoffice,默认地址为
}

这个双重 SaveChanges 是丑陋的,但我看不到另一种方式。


I have a Customer entity which references a collection of Addresses. The complication here is that I want to be able to identify a particular address as the default address.

If possible I would like to hold the FK of the default address in the Customer table. This seems more elegant than having a column in the addresses table to identify the default.

I am having difficulty with the fluent API in terms of defining this relationship. When I run the following code I get an exception which says: "An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details." "Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values."

I created a console app to show the precise problem. In this test app I have a Customer entity, an Address and the flient api configuration.

Any help would be much appreciated:

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

Many thanks,

Paul.

解决方案

I don't understand what EF is talking there about "not exposed foreign keys" in the exception. I would consider the inner exception as the important part:

Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.

I think the problem in your model is that you have a mutual dependency between Customer and Address: An address needs a customer (you have marked it as required in your mapping code) and on the other hand a customer needs an address (the default address is required both due to the non-nullable foreign key and due to your mapping code). So, EF doesn't know which entity to save first in your example code - the default address or the customer? Both entities need the primary key of the other to be saved with valid FK contraints.

The easiest solution I can see is to make the default address optional in your model and then save twice (I omit the mappings which work by convention anyway):

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

And then your program would look like this:

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
}

This double SaveChanges is ugly, but I don't see another way.

这篇关于EF代码首先4.1 - 如何配置默认的一对多关系的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆