1

I have read through several threads on StackOverflow and have not been able to figure this out. I am hoping someone can offer some advice. I have some POCO classes that look like this:

Person
{
  int PersonCode {get; set;}
  ...
  virtual List<PersonContact> {get; set;}
}

PersonContact
{
  int PersonPersonCode {get; set;}
  int ContactPersonCode {get; set;}
  int PersonContactTypeCode {get; set;}

  virtual PersonContactType {get; set;}
  virtual Person Person {get; set;}    // not sure I really need this one
  virtual Person Contact {get; set;}
}

Each Person record will have zero to many PersonContact records. Each PersonContact record links one Person record to one other Person record and indicates the type of relationship between the two Person records with the PersonContactTypeCode.

I need to be able to map this so that a Person record can be navigated to his related PersonContact records. Something like this:

var john = new Person(...);
var david = new Person(...);
john.PersonContacts.Add(new PersonContact
  {
    Contact = david,
    PersonContactType = ... // manager
  });

and then

john.PersonContacts
  .Where(c => c.PersonContactType.PersonContactTypeCode == "manager")
  .FirstOrDefault();

would return

david

I have tried so many combinations of Data Annotations and Fluent API that I can hardly remember where I started. I seemed to have the best luck with this combination:

modelBuilder.Entity<Person>()
    .HasMany(entity => entity.PersonContacts)
        .WithRequired(person => person.Person)
        .HasForeignKey(xref => xref.PersonPersonCode)
        .WillCascadeOnDelete(false);

modelBuilder.Entity<Person>()
    .HasMany(entity => entity.PersonContacts)
        .WithRequired(xref => xref.Contact)
        .HasForeignKey(entity => entity.ContactPersonCode)
        .WillCascadeOnDelete(false);

But, when I try to add more than one PersonContact to a Person, I get this error:

Multiplicity constraint violated. The role 'Person_PersonContacts_Source' of the
relationship '...Entities.Person_PersonContacts' has multiplicity
1 or 0..1.

I really appreciate any help, I am just completely stumped right now. By the way, I am open to changing these POCOs if necessary.

John Adams
  • 115
  • 6

3 Answers3

1

Try this:

public class Person
{
   [Key]
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public int PersonId {get; set;}
   ...
   public virtual ICollection<PersonContact> PersonContacts {get; set;}
}


public class PersonContact
{
   [Key]
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public int ContactId {get; set;}

   [ForeignKey("Person"), DatabaseGenerated(DatabaseGeneratedOption.None)]
   public int PersonId {get; set;}       

   public virtual Person Person {get; set;}         
}

I have used Property Mapping instead of Fluent Mapping like you tried in your attempt. If you have any questions let me know. As far as the relationship between your two Entities, this is what you need.

Komengem
  • 3,662
  • 7
  • 33
  • 57
  • how do you handle the cascade delete? – Kirsten Apr 08 '13 at 23:45
  • @kirsteng `Entity Framework` ensures that `Objects` are created with `cascade delete`. Follow the link on the matter http://stackoverflow.com/questions/5520418/entity-framework-code-first-delete-with-cascade – Komengem Apr 09 '13 at 00:08
  • Oops, i meant how do you turn cascade delete off? – Kirsten Apr 09 '13 at 00:25
  • @kirsteng change `public int PersonId {get; set;} ` to `public int PersonId? {get; set;} ` makes the foreign keys to allow `null`. Never tested it but logically makes sense. – Komengem Apr 09 '13 at 00:48
  • 1
    true, but I don't think that affects cascade deletes – Kirsten Apr 09 '13 at 00:53
  • @kirsteng it does and that may work - but still those two things are not the same. OP may not want a nullable / non-required `Person` - actually I'm almost certain he wouldn't. Your structure is good - just make it in fluent so OP can control things. – NSGaga-mostly-inactive Apr 09 '13 at 01:11
  • I had the code arranged similar to this at one point, but this does not have a navigation property for the Contact reference, only the Person. Without the Contact navigation, the relationship is not useful. Also, I have no problem with the Data Annotations, either way is fine as long as it works. – John Adams Apr 09 '13 at 01:59
  • @JohnAdams What do you mean? To my understanding this code has `Bi-Directional Reference`. Thats why i have `PersonId` in `PersonContact` – Komengem Apr 09 '13 at 04:49
  • @JohnAdams try this link if you prefer `Fluent Mapping`. It has the exact setup you are trying to do. http://agilenet.wordpress.com/2011/04/18/entity-framework-code-first-specify-foreign-key-name-in-one-to-many-relationship-with-fluent-api/ – Komengem Apr 09 '13 at 06:53
1

I'd guess it's because you are using the same navigation property to link to PersonContact.Person and PersonContact.Contact.

Assuming this:

Person
{
  int PersonCode {get; set;}
  ...
  virtual ICollection<PersonContact> PersonContacts {get; set;}
}

Try something like:

modelBuilder.Entity<PersonContact>()
    .HasRequired(x => x.Person)
        .WithMany(x => x.PersonContacts)
        .HasForeignKey(x => x.PersonPersonCode)
        .WillCascadeOnDelete(false);

modelBuilder.Entity<PersonContact>()
    .HasRequired(x => x.Contact)
        .WithMany()
        .HasForeignKey(x => x.ContactPersonCode)
        .WillCascadeOnDelete(false);
Chris Curtis
  • 1,478
  • 1
  • 10
  • 21
0

I managed to get something similar working using something like this

in my domain classes

public class PersonContact
{
    public int Id { get; set; }

    [Required]
    public virtual Person ContactPerson { get; set; }

    [Required]
    public virtual Person Person { get; set; }

[Required]
public int ContactType { get; set; }

}

public class Person
{
int Id { get; set; }

    private readonly  List<PersonContact> _contacts = new  List<Contact>();

    public virtual  List<PersonContact> Contacts
    {
        get
        {
            return this._contacts;
        }
    }
}

in my context

 public DbSet<Person> People { get; set; }
 public DbSet<PersonContact> Contacts { get; set; }

I made this change in a migration , and had to edit the generated create table code to set Cascade delete to false for the fwo Foreign Keys to the person table, inside the PersonContact Table.

I get an extra Person_Id1 column in the PersonContact table. It seems to populate with the same data as Person_Id. This seems to be needed by EF when I create a bindingsource - as I get errors without it.

I wouldn't put explicit Foreign keys in, let the migration create them.

Kirsten
  • 15,730
  • 41
  • 179
  • 318
  • 1
    I made some changes to your answer since you asked for improvement. The reason why you are getting `an extra Person_Id1 column in the PersonContact table` is because you didn't map `PersonId` foreign key in your POCO class. Also the approach below would solve this issue – Komengem Apr 08 '13 at 23:02
  • Thanks, I usually seem to be able to get away without defining FK's explicitly in the POCO class. Should I always define them explicitly or only in scenarios like this? – Kirsten Apr 08 '13 at 23:25
  • You either do property mapping or fluent mapping. In the question, he tried fluent mapping and in my answer belong i did property mapping which i prefere, easier to follow in my case. Note: The edit in your answer wasn't approved but you can relate to my answer – Komengem Apr 08 '13 at 23:39
  • I cant see where to approve your edit - did you delete it? I am actually using a bindingList instead of a list in my class. Could that be the cause of the extra field? I tried adding the FK - but the extra field is generated. The extra field is not generated if I use your code. – Kirsten Apr 08 '13 at 23:54
  • Mhmmm looks like mod didn't allow my edit, look into my answer on this question. – Komengem Apr 08 '13 at 23:59
  • I will take a look at this tomorrow and let you know if I have any luck. Why did you break the Contacts collection into a private backing-store? – John Adams Apr 09 '13 at 02:08
  • someone told me to do it this way, I think it is so it cant be set. I am having trouble reproducing the creation of the extra column ( which i never wanted ) I must have been mistaken that my code was caused it. I have deleted the column and creating a new migration does not try to add it back. I do know that at one point the creation of this column was driving me crazy. I must have fixed it without fixing the old migration code. – Kirsten Apr 09 '13 at 04:01
  • Oh dear the extra column must be needed somehow - as I get errors creating a binding source without it. I seems to be a bad idea to explicitly map the FK... better to let EF generate it – Kirsten Apr 09 '13 at 22:05