Code first mapping for self-related xref table - entity-framework

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.

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

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.

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.

Related

Mapping Relationships with EF Core Fluent API with two entities of the same type

I have been trying to use the fluent api to configure appropriate mapping for the image below. (If someone marks this as a duplicate, for the love of all that is holy, please include the relevant link! I've spent days combing stackoverflow.)
A main thought I am aiming for is that all entities will have an EnterpriseID that would be used as a sharding key.
The Enterprise table contains two Contacts, a PrimaryContact and a BillingContact.
What I would like to do is create a new Enterprise with a code generated GUID ID as well as two contacts (Primary and Billing), assign the Enterprise ID to those and call SaveChanges on the TrackingState.Added object hierarchy (which at this point is Enterprise->Contacts->Addresses.
Without any Fluent mapping, EF Core 2.1 says.. "Both relationships between 'Contact' and 'Enterprise.BillingContact' and between 'Contact' and 'Enterprise.PrimaryContact' could use {'EnterpriseID'} as the foreign key. To resolve this configure the foreign key properties explicitly on at least one of the relationships."
I have attempted many configuratons and either wind up with a DB that only has one of the Contact properties in the Enterprise table defined, or the whole mess devolves into FK / cyclical hell.
Here are current class stubs..
public class Enterprise
{
public Guid ID {get; set;}
public Contact PrimaryContact {get; set;}
public Contact BillingContact {get; set;}
}
public class Contact
{
public Guid ID {get; set;}
public Guid EnterpriseID {get; set;}
public string FName {get; set;}
public string LName {get; set;}
public Address Address {get; set;}
}
public class Store
{
public Guid ID {get; set;}
public Guid EnterpriseID {get; set;}
public Contact PrimaryContact {get; set;}
}
public class Order
{
public Guid ID {get; set;}
public Guid EnterpriseID {get; set;}
public Guid StoreID {get; set;}
public Contact CustomerContact {get; set;}
}
public class Address
{
public Guid ID {get; set;}
public Guid EnterpriseID {get; set;}
public string Lines {get; set;}
}
I would really appreciate some advice on how to configure this.
The Enterprise table contains two Contacts, a PrimaryContact and a BillingContact.
Then the relationship among Enterprise, Contact and Address should be as follows:
public class Enterprise
{
[Key]
public Guid ID { get; set; }
public Guid PrimaryContactId { get; set; }
public Contact PrimaryContact { get; set; }
public Guid BillingContactId { get; set; }
public Contact BillingContact { get; set; }
}
public class Contact
{
[Key]
public Guid ID { get; set; }
public string FName { get; set; }
public string LName { get; set; }
public Address Address {get; set;}
}
public class Address
{
[Key]
public Guid ContactId {get; set;}
public string Lines {get; set;}
}
Then in the Fluent API configuration:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Enterprise>().HasOne(e => e.PrimaryContact)
.WithOne()
.HasForeignKey<Enterprise>(e => e.PrimaryContactId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Enterprise>().HasOne(e => e.BillingContact)
.WithOne()
.HasForeignKey<Enterprise>(e => e.BillingContactId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Contact>().HasOne(c => c.Address)
.WithOne().HasForeignKey<Address>(a => a.ContactId);
}

Same Entity referred twice in another Entity is creating issue while saving parent with children

I am having the data model as follows.
class KnowledgeDocument
{
public int? Id {get; set;}
public virtual ICollection<KDValueCreation> KDValueCreations { get; set; }
}
class KDValueCreation
{
public int? Id{get; set;}
public int? KDReferenceId { get; set; }
public virtual KnowledgeDocument KDReference { get; set; }
public int KnowledgeDocumentId { get; set; }
public virtual KnowledgeDocument KnowledgeDocument { get; set; }
public decimal Amount {get; set;}
}
Now, when I am trying to create a new KnowledgeDocument along with KDValueCreations as follows.
KnowledgeDocument kd = new KnowledgeDocument();
kd.KDValueCreations.Add(new KDValueCreation{ Amount = "500000"});
When I save the kd, kd is saved without any issue and in KDValueCreation, 1 record is created and both KDReferenceId and KnowledgeDocumentId are populated with the same kdId. But, I want to populate only KnowledgeDocumentId and stop KDReferenceId from populating and set it to null.
As both the fields are pointing to the same  reference, Entity framework is populating the Id on both the fields.
How can I achieve this still by saving the KnowledgeDocument with its children?
Please suggest. Thanks in advance. 
As I can see, the reason is in two identical relationships. You need to specify what properties are related. You can do it by InverseProperty attribute or by FluentAPI like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// configures one-to-many relationship
modelBuilder.Entity<KDValueCreation>()
.HasRequired<KnowledgeDocument>(c => c.KnowledgeDocument)
.WithMany(d => d.KDValueCreations)
.HasForeignKey(c => c.KnowledgeDocumentId);
}
}
or this:
modelBuilder.Entity<KnowledgeDocument>()
.HasMany<KDValueCreation>(d => d.KDValueCreations)
.WithRequired(c => c.KnowledgeDocument)
.HasForeignKey(c => c.KnowledgeDocumentId);

Automapper with reciprocal navigation properties

need a bit of help here.
I have a couple of classes that I am trying to map using Automapper. I am using EF core.
The basic domain is like this:
Public class A
{
public string Id {get; set;}
public string Name {get; set;}
public virtual Icollection<AB> AB {get; set;}
}
Public class B
{
public string Id {get; set;}
public string Name {get; set;}
public virtual ICollection<AB> AB {get; set;}
}
Public class AB
{
public string A_Id {get; set;}
public string B_Id {get; set;}
public virtual A A {get; set;}
}
My DTOs are like this:
Public class A_DTO
{
public string Id {get; set;}
public string Name {get; set;}
public ICollection<B> Bs {get; set;}
}
Public class B_DTO
{
public string Id {get; set;}
public string Name {get; set;}
public ICollection<A> As {get; set;}
}
Now where I get stuck is:
How to set up the mapping so that Automapper automatically retrieves the list of children (e.g the relevant 'Bs' for the current 'A')
How to configure my DTOs so that, for example, the retrieved 'Bs' for an 'A' do not expose the 'A's navigation property to prevent infinite recursion.
Thank you!
Partial answer here. I was researching and stumbled upon https://www.exceptionnotfound.net/entity-framework-and-wcf-loading-related-entities-with-automapper-and-reflection/
So I changed my DTOs by removing the navigation properties when the DTO is not the principal.
Public class A_DTO
{
public string Id {get; set;}
public string Name {get; set;}
}
Public class A_Nav_DTO: A_DTO
{
public ICollection<B> Bs {get; set;}
}
and in my mappings I did
CreateMap<A, A_DTO>();
CreateMap<A, A_Nav_DTO>()
.ForMember(dto => dto.B, map =>
map.MapFrom(model =>
model.AB.Select(ab => ab.B).ToList()));
Now this works, but obviously now I have to map three classes instead of two. Any suggestions on how to improve this solution?
I know it's an old question but hopefully this helps someone.
You could call Automapper's ResolveUsing method like so :
cfg.CreateMap<A, A_DTO>()
.ForMember(x => x.Bs,
opt => opt.ResolveUsing(src => RemoveInclusions(src)));
And in RemoveInclusions method you can manually set B's navigation of A's to null like :
private B RemoveInclusions(A src)
{
src.Bs.A = null;
return src.Bs;
}

Entity Framework Many to Many relationship with multiple keys

I'd like to add a table to define permission object than can be applied to many models.
To do this, I create a Permission class:
public class Permission
{
[Key]
int PermissionID {get; set;}
string User {get; set;}
bool Read {get; set;}
bool Write {get; set;}
}
And then other object classes than can have a List of Permission:
public class ObjectModel1
{
[Key]
int idObject1 {get; set;}
... Other properties ...
public virtual ICollection<Permission> Permission {get; set;}
}
public class ObjectModel2
{
[Key]
int idObject2 {get; set;}
... Other properties ...
public virtual ICollection<Permission> Permission {get; set;}
}
How can I obtaint a multiple many to many relationship between Permission and other Object classes without defining Foreign Keys in Permission class for each Object?
A many to many relationship will have an xref table between the two entities:
// because Permission has a collection to ObjectModel1 and ObjectModel1 has a collection
// to permission, it is treated as a many to many relationship with an implicit
// xref between the tables. The xref will contain a foreign key to each entity that is
// also a composite primary key
public class Permission
{
[Key]
int PermissionID {get; set;}
string User {get; set;}
bool Read {get; set;}
bool Write {get; set;}
public virtual ICollection<ObjectModel1> ObjectModel1s { get; set; }
public virtual ICollection<ObjectModel2> ObjectModel2s { get; set; }
}
public class ObjectModel1
{
[Key]
int idObject1 {get; set;}
... Other properties ...
public virtual ICollection<Permission> Permission {get; set;}
}
public class ObjectModel2
{
[Key]
int idObject2 {get; set;}
... Other properties ...
public virtual ICollection<Permission> Permission {get; set;}
}
Entity Framework will create a table that is something like PermissionObjectModel1 that has a composite primary key with two foreign keys (one to Permission, one to ObjectModel1). It will create another table for ObjectModel2 with similar keys. The foreign key doesn't exist on Permission itself.
If you don't want to have the navigation property on permission, then I think you will need to use the Fluent API:
public class MyDbContext : DbContext
{
public DbSet<Permission> Permissions { get; set; }
public DbSet<ObjectModel1> ObjectModel1s { get; set; }
public DbSet<ObjectModel2> ObjectModel2s { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ObjectModel1>()
.HasMany(many => many.Permissions)
.WithMany() // dont want navigation property on Permission
.Map(xref => xref.MapLeftKey("ObjectModel1Id")
.MapRightKey("PermissionId")
.ToTable("ObjectModel1PermissionXref"));
modelBuilder.Entity<ObjectModel2>()
.HasMany(many => many.Permissions)
.WithMany() // dont want navigation property on Permission
.Map(xref => xref.MapLeftKey("ObjectModel2Id")
.MapRightKey("PermissionId")
.ToTable("ObjectModel2PermissionXref"));
}
}
Something similar to the above code would still give you a many to many relationship, but the navigation property would not be defined on Permission.

Entity Framework .Where nested in .Include

I'm attempting to perform a db lookup using EF5 code-first. The basic structure and table relationships are as follows;
public partial class Member
{
public int RecordID {get; set;}
public string Name {get; set;}
...etc.
public virtual ICollection<MemberLink> MasterLinks {get; set;}
public virtual ICollection<MemberLink> SlaveLinks {get; set;}
public virtual ICollection<Message> ReceivedMessages {get; set;}
public virtual ICollection<Message> SentMessages {get; set;}
}
public partial class MemberLink
{
public int RecordID {get; set;}
public virtual Member MasterMember {get; set;}
public virtual Member SlaveMember {get; set;}
...etc.
}
public partial class Message
{
public int RecordID {get; set;}
public virtual Member Sender {get; set;}
public virtual Member Recipient {get; set;}
...etc.
}
Now, the query I'm trying to perform is using the MemberLinkRepository, and looks like;
public IList<MemberLink> GetMasterLinks(int p_MemberID)
{
return Get()
.Include ( memberLink => memberLink.MasterMember )
.Include ( memberLink => memberLink.SlaveMember )
.Include ( memberLink => memberLink.MasterMember.ReceivedMessages
.Where(
msg => msg.Sender.RecordID == memberLink.SlaveMember.RecordID) )
.Where ( memberLink => memberLink.MasterMember.RecordID == p_MemberID)
.ToList();
Except EF doesn't seem to like the nested Where. I could split this out into 2 separate repository calls (and indeed, it's looking like I might have to do that) but in the interest of reducing calls to the db I'm trying to do it in one foul swoop. Does anyone know how I can achieve this in one single query?
I hope the code illustrates what I'm trying to do... If not, I'll try and explain a little better.
The short answer is no, EF will not let you do that using Include().
Think about the result if it let you do this: in one case your MemberLink.MasterMember.ReceivedMessages will be fully populated, on another identical looking object MemberLink.MasterMember.ReceivedMessages is actually a sub-set of messages! What happens if you try to add to the ReceivedMessages? What if the addition doesn't match the filter? It is a bag of hurt.
The answer is to use projections:
public IList<MemberLinkWithFiltereredMessages> GetMasterLinks(int p_MemberID)
{
return Get()
.Include(memberLink => memberLink.MasterMember)
.Include(memberLink => memberLink.SlaveMember)
.Where(memberLink => memberLink.MasterMember.RecordID == p_MemberID)
.Select(memberLink => new MemberLinkWithFilteredMessages
{
MemberLink = memberLink,
FilteredMessages = memberLink.MasterMember.ReceivedMessages
.Where(msg => msg.Sender.RecordID == memberLink.SlaveMember.RecordID)
})
.ToList();
}
What you are really doing is asking for a specific sub-set of information, so be explicit about it.