The declared type of navigation property Project.Models.Customer.SubCustomers is not compatible with the result of the specified navigation

c# ef-code-first ef-fluent-api ef-migrations entity-framework-6

Question

I have had a look at answers to similar errors, but I can't seem to find an answer to my particular error.

I am running a migration and the error happens when the seed method is run (migration has worked fine).

The declared type of navigation property Project.Models.Customer.SubCustomers is not compatible with the result of the specified navigation.

This is for a circular reference, i.e. a Customer can have 0..* SubCustomers and 0..1 ParentCustomers.

Customer model:

public class Customer : Entity
{
    public int CustomerId { get; set;}
    public string Name { get; set; }

    // Relationships

    public int? ParentCustomerId { get; set; }
    public virtual ICollection<Customer> SubCustomers { get; set; } // a Customer has many Customers as SubCustomers, a Customer has zero or one ParentCustomer

    // Other relationships
}

From context fluent api:

modelBuilder.Entity<Customer>()
            .HasKey(customer => customer.CustomerId);
modelBuilder.Entity<Customer>()
            .Property(customer => customer.Name)
            .IsRequired()
            .HasColumnType("nvarchar")
            .HasMaxLength(500);
modelBuilder.Entity<Customer>() // a Customer has many Customers as SubCustomers, a Customer has zero or one ParentCustomer
            .HasOptional(customer => customer.SubCustomers)
            .WithMany()
            .HasForeignKey(customer => customer.ParentCustomerId);

From seed method: (the customers are created in the database, sub-customers does not work)

// Default Customers - create and save
var customers = new[]{
    new Customer { Name = "Custa" },
    new Customer { Name = "Custb" },
    new Customer { Name = "Custc" }
};
context.Customers.AddOrUpdate(r => r.Name, customers[0], customers[1], customers[2]);
context.SaveChanges();

// Add SubCustomers b & c to Customer a (ids calculated beforehand, e.g. aId, as linq does not support array index)
var aId = customers[0].CustomerId;
var a = context.Customers.Include(c => c.SubCustomers).SingleOrDefault(c => c.CustomerId == aId);
if (a.SubCustomers == null)
    a.SubCustomers = new List<Customer>();
var bId = customers[1].CustomerId;
var b = a.SubCustomers.SingleOrDefault(c => c.CustomerId == bId);
if (b == null)
    a.SubCustomers.Add(context.Customers.Single(c => c.CustomerId == bId));
var cId = customers[2].CustomerId;
var c = a.SubCustomers.SingleOrDefault(c => c.CustomerId == cId);
if (c == null)
    a.SubCustomers.Add(context.Customers.Single(c => c.CustomerId == cId));
context.SaveChanges();

If anyone can spot what is causing the error I would be very grateful. Many thanks!

1
2
11/14/2014 4:52:04 PM

Accepted Answer

So I finally figured this out after a bit of reading and trial and error. I was getting confused, because it was self referencing, and leaving out a crucial element.

Normally, when creating a one-to-many relationship, between two different objects, you would have something like the following:

public class Foo {
    public int FooId {get; set;}
    public string Name {get; set;}

    // Relationships
    public virtual ICollection<Bar> Bars {get; set;} // a Foo has many bars, a Bar has one optional Foo
}

public class Bar {
    public int BarId {get; set;}
    public string Name {get; set;}

    // Relationships
    public int? FooId {get; set;} // a Foo has many bars, a Bar has one optional Foo
    public virtual Foo Foo {get; set;}
}

Then in the context (fluent API):

modelBuilder.Entity<Bar>() // a Foo has many bars, a Bar has one optional Foo
    .HasRequired(bar => bar.Foo)
    .WithMany(foo => foo.Bars)
    .HasForeignKey(bar => bar.FooId);
modelBuilder.Entity<Bar>()
            .HasKey(bar => bar.BarId);

modelBuilder.Entity<Foo>()
            .HasKey(foo => foo.FooId);

In my code, in my original question, I had left out the equivalent of public virtual Foo Foo {get; set;} in this case.

So to create a self-referencing Foo you need all three elements of the relationship, that are split between two objects in the example above, to be in the Foo object (pretty logical really!):

public class Foo {
    public int FooId {get; set;}
    public string Name {get; set;}

    // Relationships
    public int? ParentFooId {get; set;} // a Foo has many SubFoos, a Foo has one optional ParentFoo
    public virtual Foo ParentFoo {get; set;}
    public virtual ICollection<Foo> SubFoos {get; set;}
}

And the context (fluent API):

modelBuilder.Entity<Foo>() // a Foo has many SubFoos, a Foo has one optional ParentFoo
    .HasOptional(foo => foo.ParentFoo)
    .WithMany(foo => foo.SubFoos)
    .HasForeignKey(foo => foo.ParentCustomerId);
modelBuilder.Entity<Foo>()
            .HasKey(foo => foo.FooId);

To seed data into this self referencing relationship I would need the following in Configuration.cs:

// Default Foos - create
var foos = new[]{
        new Foo { Name = "foo1", SubFoos = new List<Foo>() },
        new Foo { Name = "foo2", SubFoos = new List<Foo>() },
        new Foo { Name = "foo3", SubFoos = new List<Foo>() },           
context.Tabs.AddOrUpdate(t => t.View, foos[0], foos[1], foos[2]);
context.SaveChanges();

// Add a SubFoo to a Foo
var parentFooId = foos[0].FooId;
var parentFoo = context.Foos.Include(f => f.SubFoos).SingleOrDefault(f => f.FooId == parentFooId);
// If no current SubFoos initiate an empty list
if (parentFoo.SubFoos == null)
    parentFoo.SubFoos = new List<Foo>();
// Get another Foo to add as a SubFoo
var childFooId = foos[1].FooId;
// Check if child foo already exists and add if not
var childFoo = parentFoo.SubFoos.SingleOrDefault(f => f.FooId == childFooId);
if (childFoo == null)
    parentFoo.SubFoos.Add(context.Foos.Single(f => f.FooId == childFooId));
context.SaveChanges();
4
11/18/2014 2:37:31 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow