7

I have a class that have a relationship with itself:

public class Person
{
    public long ID { get; set; }
    public string Name { get; set; }

    public virtual Person Mother { get; set; }
    public virtual Person Father { get; set; } 
}

When EF 4.1 is trying to map this class I'm getting the follow error: 'Unable to determine the principal end of an association between the types 'Model.Person' and 'Model.person'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.'

I already tried the solution of the topic EF 4.1 - Model Relationships without success.

How I can fix that?

Thanks!

Community
  • 1
  • 1
UbirajaraMNJ
  • 95
  • 1
  • 4

3 Answers3

9

Because it's naturally a one-to-many relationship (a person must have one father and one mother but can have many sons and daughters) I'd model it like this:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .HasRequired(p => p.Father)
        .WithMany();

    modelBuilder.Entity<Person>()
        .HasRequired(p => p.Mother)
        .WithMany();
}

This will create two required foreign keys in the database table with name Mother_ID and Father_ID by convention.

Edit

If you want to be able to create persons without Mother and Father you can make the relation optional instead of required:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .HasOptional(p => p.Father)
        .WithMany();

    modelBuilder.Entity<Person>()
        .HasOptional(p => p.Mother)
        .WithMany();
}

Then the foreign keys in the database table are nullable.

If you don't like the default column names for your foreign keys (Mother_ID and Father_ID) you can customize the column names in your mapping:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .HasOptional(p => p.Father)
        .WithMany()
        .Map(m => m.MapKey("FatherID"));

    modelBuilder.Entity<Person>()
        .HasOptional(p => p.Mother)
        .WithMany()
        .Map(m => m.MapKey("MotherID"));
}
Slauma
  • 175,098
  • 59
  • 401
  • 420
  • I think it will also require turning off cascade delete because from database perspective one person can be assigned as both Father and Mother which will result in multiple cascade delete paths. – Ladislav Mrnka May 22 '11 at 18:16
  • @Ladislav: Yes, I also thought it would be necessary to switch off cascading delete, but it turned out that the mapping above actually does this by default. It looks like the convention that required-to-many relationships are modeled with cascading delete is not applied to self-referencing associations. – Slauma May 22 '11 at 18:29
  • 1
    Another surprise from default conventions. MS should invest more effort to documenting exactly how and when they do something. – Ladislav Mrnka May 22 '11 at 18:33
  • Hi Slauma, but in this case I always will need to have the father and mother objects to create a new person, I can have cases where a person object could not have father and mother, how can I map this possibility? – UbirajaraMNJ May 23 '11 at 21:46
  • @UbirajaraMNJ: I have added a quick Edit section to my answer. – Slauma May 23 '11 at 22:31
  • @UbirajaraMNJ: If I think about it, the "required" mapping even doesn't make sense. Somewhere must be the root of the family tree (the first person which I insert into the DB) and this root cannot have father and mother. To fulfill the database constraints I could only create a circle which even makes less sense. So, "optional" is the right way, yes. – Slauma May 23 '11 at 22:52
  • Hi Slauma, Thanks for the help. Your answer was perfect. – UbirajaraMNJ May 24 '11 at 01:32
  • Those "WithMany()" dont tell much about the inverse property. That is: what if a Person had a List Children? – sports Apr 24 '13 at 17:29
  • 1
    @sports: I think you would actually need *two* collections `ChildrenAsMother` and `ChildrenAsFather` or so. One would be always empty for a given person because a person can either be female or male. But the problem is that you cannot map a single collection `Children` to both `Person.Mother` and `Person.Father` as inverse property. `Children` could only be a not mapped and readonly property which is the concatenation of `ChildrenAsMother` and `ChildrenAsFather`. Not a very intuitive model but I believe there is no other way with EF. WithMany would be `WithMany(p => p.ChildrenAsMother/Father)` – Slauma Apr 24 '13 at 18:15
  • @Slauma It would be more helpful with Data Annotations illustrated in same way. – Rahul Uttarkar Mar 13 '16 at 20:43
0

For anyone else wondering, this apparently can't be done via DataAnnotation attributes, but can only be configured fluently. http://social.msdn.microsoft.com/Forums/en/adodotnetentityframework/thread/08bba96a-20b2-4a3c-9e0e-a5475b703dfe

pwhe23
  • 1,196
  • 1
  • 14
  • 15
0

It's a tad weird but you can do this without the modelBuilder gunk like so:

public class RecursiveModel
{
    public long Id { get; set; }
    public string Name { get; set; }

    public RecursiveModel Father { get; set; }
    public RecursiveModel Mother { get; set; }
    public ICollection<RecursiveModel> Children { get; set; }
}

So basically one of these models will have a list of its children, and if for example this is a mother, each of them will have both their Mother_Id and RecursiveModel_Id set to this object. You'll need to explictly wire it up each time, ideally via a helper method like:

    public void AddAsMotherOfChild(RecursiveModel child)
    {
        Children.Add(child);
        child.Mother = this;
    }

    public void AddAsFatherOfChild(RecursiveModel child)
    {
        Children.Add(child);
        child.Father = this;
    }

Not perfect but it does avoid the modelBuilder code that for me at least gets to be sort of brittle and legion over time.

Chris Moschini
  • 36,764
  • 19
  • 160
  • 190