简体   繁体   中英

EF exception, how to model addresses in c# and entity framework code first

EF is throwing the following exception:

System.InvalidOperationException: Referential integrity constraint violation. A Dependent Role has multiple principals with different values.

I have two entities, StateProvince and Country:

public class StateProvince
{
    public long Id { get; set; }

    [StringLength(100)]
    public string Name { get; set; }

    public long CountryId { get; set; }
    public Country Country { get; set; }
}

public class Country
{
    public long Id { get; set; }

    [StringLength(2)]
    public string Code { get; set; }

    public IEnumerable<StateProvince> StatesProvinces { get; set; }
}

and two entities that use them:

public class Customer
{
    public long Id { get; set; }

    public long BillingStateProvinceId { get; set; }
    [ForeignKey("Id")]
    public StateProvince BillingStateProvince { get; set; }

    public long BillingCountryId { get; set; }
    [ForeignKey("Id")]
    public Country BillingCountry { get; set; }

    public long ShippingStateProvinceId { get; set; }
    [ForeignKey("Id")]
    public StateProvince ShippingStateProvince { get; set; }

    public long ShippingCountryId { get; set; }
    [ForeignKey("Id")]
    public Country ShippingCountry { get; set; }
}

public class Vendor
{
    public long Id { get; set; }

    public long StateProvinceId { get; set; }
    public StateProvince StateProvince { get; set; }

    public long CountryId { get; set; }
    public Country Country { get; set; }
}

And in my OnModelCreating:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Customer>()
            .HasRequired(m => m.ShippingStateProvince)
            .WithOptional().WillCascadeOnDelete(false);

        modelBuilder.Entity<Customer>()
            .HasRequired(m => m.ShippingCountry)
            .WithOptional().WillCascadeOnDelete(false);

        modelBuilder.Entity<Customer>()
            .HasRequired(m => m.BillingStateProvince)
            .WithOptional().WillCascadeOnDelete(false);

        modelBuilder.Entity<Customer>()
            .HasRequired(m => m.BillingCountry)
            .WithOptional().WillCascadeOnDelete(false);

        modelBuilder.Entity<Vendor>()
            .HasRequired(m => m.StateProvince)
            .WithOptional().WillCascadeOnDelete(false);

        modelBuilder.Entity<Vendor>()
            .HasRequired(m => m.Country)
            .WithOptional().WillCascadeOnDelete(false);

        base.OnModelCreating(modelBuilder);
    }

Finally, the code that throws the exception:

[TestMethod]
    public void TestMethod1()
    {
        Db db = new Db();

        #region Set/reset items

        Country us = db.Countries.FirstOrDefault();
        if (us == null)
        {
            us = new Country { Code = "US" };
            db.Countries.Add(us);
            db.SaveChanges();
        }
        long usId = us.Id;

        List<StateProvince> states = db.StateProvinces.ToList();
        StateProvince mass = states.Where(m => m.Name == "MA").FirstOrDefault();
        StateProvince ct = states.Where(m => m.Name == "CT").FirstOrDefault();
        if (mass == null)
        {
            mass = new StateProvince { Name = "MA", CountryId = usId };
            ct = new StateProvince { Name = "CT", CountryId = usId };
            db.StateProvinces.Add(mass);
            db.StateProvinces.Add(ct);
            db.SaveChanges();
        }
        long massId = mass.Id;
        long ctId = ct.Id;

        List<Customer> customersToRemove = db.Customers.ToList();
        db.Customers.RemoveRange(customersToRemove);
        db.SaveChanges(); 

        #endregion

        Customer customerToAdd = new Customer { 
            BillingStateProvinceId = massId, 
            ShippingStateProvinceId = ctId, 

            ShippingCountryId = usId, 
            BillingCountryId = usId,

            BillingStateProvince = mass,
            ShippingStateProvince = ct,

            BillingCountry = us,
            ShippingCountry = us
        };

        db.Customers.Add(customerToAdd);

        try
        {
            //exception thrown here
            db.SaveChanges();
        }
        catch (Exception e)
        {
            throw;
        }

        db.Dispose();

        Db dbCheck = new Db();

        Customer customer = dbCheck.Customers.Include(m => m.BillingStateProvince).Include(m => m.ShippingStateProvince).FirstOrDefault();

        dbCheck.Dispose();
    }

I suspect the issue is either I'm not using the [ForeignKey] attribute correctly or I'm doing something wrong in OnModelCreating. I can make it work if I only have one StateProvince and one Country entity in the Customer entity, however, once I have two, can't figure it out. Any help?

On a broader note does anyone have any links to blogs/posts/articles/tutorials on how to model addresses with c# EF code first? I came across this one , which uses the [ComplexType] attribute, the problem there is that I can't encapsulate the StateRegion and Country classes within it. My current thinking is to just bake in the various address fields into each class, ie MailingStreetAddress, BillingStreetAddress, etc. I would like to see how others have handled addresses to gain a better understanding on how to do my own.

Edit

Per @Sam I am, I tried to change attribute to:

[ForeignKey("BillingStateProvinceId")]

for each property. Upon dropping/recreating the db, EF threw this error:

Customer_BillingCountry_Source: : Multiplicity is not valid in Role 'Customer_BillingCountry_Source' in relationship 'Customer_BillingCountry'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be ' '. Customer_BillingStateProvince_Source: : Multiplicity is not valid in Role 'Customer_BillingStateProvince_Source' in relationship 'Customer_BillingStateProvince'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be ' '. Customer_ShippingCountry_Source: : Multiplicity is not valid in Role 'Customer_ShippingCountry_Source' in relationship 'Customer_ShippingCountry'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be ' '. Customer_ShippingStateProvince_Source: : Multiplicity is not valid in Role 'Customer_ShippingStateProvince_Source' in relationship 'Customer_ShippingStateProvince'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be ' '.

Edit 2

Per @Steve Green, changed my StateRegion, Country entities, and OnModelCreating, which now throws this error when I drop recreate the db:

Schema specified is not valid. Errors: The relationship 'StackOverflow.Customer_ShippingCountry' was not loaded because the type 'StackOverflow.Country' is not available.

My edited code:

public class StateProvince
{
    public long Id { get; set; }

    [StringLength(100)]
    public string Name { get; set; }

    //new properties
    public ICollection<Customer> Customers { get; set; }
    public ICollection<Vendor> Vendors { get; set; }

    public long CountryId { get; set; }
    public Country Country { get; set; }
}

public class Country
{
    public long Id { get; set; }

    [StringLength(2)]
    public string Code { get; set; }

    //new properties
    public ICollection<Customer> Customers { get; set; }
    public ICollection<Vendor> Vendors { get; set; }

    public IEnumerable<StateProvince> StatesProvinces { get; set; }
}

And my OnModelCreating to mirror his:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Customer>()
            .HasRequired(m => m.ShippingStateProvince)
            .WithMany(m => m.Customers)
            .HasForeignKey(m => m.ShippingStateProvinceId);

        modelBuilder.Entity<Customer>()
            .HasRequired(m => m.ShippingCountry)
            .WithMany(m => m.Customers)
            .HasForeignKey(m => m.ShippingCountryId)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<Customer>()
            .HasRequired(m => m.BillingStateProvince)
            .WithMany(m => m.Customers)
            .HasForeignKey(m => m.BillingStateProvinceId)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<Customer>()
            .HasRequired(m => m.BillingCountry)
            .WithMany(m => m.Customers)
            .HasForeignKey(m => m.BillingCountryId)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<Vendor>()
            .HasRequired(m => m.StateProvince)
            .WithMany(m => m.Vendors)
            .HasForeignKey(m => m.StateProvinceId)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<Vendor>()
            .HasRequired(m => m.Country)
            .WithMany(m => m.Vendors)
            .HasForeignKey(m => m.CountryId)
            .WillCascadeOnDelete(false);

        base.OnModelCreating(modelBuilder);
    }

I believe that you're using the ForeignKeyAttribute incorrectly. I normally do it like this

[ForeignKey("BillingStateProvince")]
public long BillingStateProvinceId { get; set; }
public StateProvince BillingStateProvince { get; set; }

although I believe this is also valid,

public long BillingStateProvinceId { get; set; }
[ForeignKey("BillingStateProvinceId")]
public StateProvince BillingStateProvince { get; set; }

'BillingStateProvinceId' is the foreign key. when you do [ForeignKey("Id")] , you're pointing it to Customer.Id , not StateProvince.Id

You also probably want to mark you Ids with the KeyAttribute

[Key]
public long Id { get; set; }

Since you already have a fluent config, you could go with something like this and forgo the annotations:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Customer>()
            .HasRequired(m => m.StateProvince)
            .WithMany() 
            .HasForeignKey(c => c.ShippingStateProvinceId)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<Customer>()
            .HasRequired(m => m.Country)
            .WithMany() 
            .HasForeignKey(c => c.ShippingCountryId)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<Customer>()
            .HasRequired(m => m.StateProvince)
            .WithMany() 
            .HasForeignKey(c => c.BillingStateProvinceId)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<Customer>()
            .HasRequired(m => m.Country)
            .WithMany() 
            .HasForeignKey(c => c.BillingCountryId)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<Vendor>()
            .HasRequired(m => m.StateProvince)
            .WithMany() 
            .HasForeignKey(c => c.StateProvinceId)
            .WillCascadeOnDelete(false);

        modelBuilder.Entity<Vendor>()
            .HasRequired(m => m.StateProvince)
            .WithMany() 
            .HasForeignKey(c => c.CountryId)
            .WillCascadeOnDelete(false);


        base.OnModelCreating(modelBuilder);
    }

https://msdn.microsoft.com/en-us/data/jj591620.aspx#Model

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM