Entity Framework Many to Many relationship with multiple keys - entity-framework

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.

Related

EF Core join table for one-to-many relationship

I have the following classes: User, Post and UserPost. User and Post have a 1-to-many relationship. There's a third join table called UserPost that tracks the up/down vote each post gets. To ensure each user can only upvote/downvote once, the ID (PK) of this table is a composite key of User and Post ID.
public class User {
public Guid Id {get; set;}
public string UserName {get; set;}
public ICollection<Post> Posts {get; set;}
}
public class Post {
public Guid Id {get; set;}
public string Content {get;set;}
public User User {get; set;}
}
public UserPost {
public Guid Id {get; set;} // This should be a composite key of User ID and Post ID
public Guid PostId {get;set;}
public Guid UserId {get;set;}
public VoteType VoteType {get; set;}
}
public enum VoteType {
Up = 1,
Down = 0
}
In my DB Context class I defined the User/Post relationship like this:
builder.Entity<User>()
.HasMany(u => u.Posts)
.WithOne(p => p.User)
Now I want to define the relationship for the UserPost model but not sure how to go about it. So far I have:
builder.Entity<UserPost>()
.HasKey(x => new { x.UserId, x.PostId })
Does it require anything further?
Write your whole relationship as follows:
public class User
{
public Guid Id {get; set;}
public string UserName {get; set;}
public ICollection<Post> Posts {get; set;}
}
public class Post
{
public Guid Id {get; set;}
public string Content {get;set;}
public Guid UserId {get; set;}
public User User {get; set;}
}
public UserVote // Rename this from UserPost to UserVote to keep naming consistency.
{
public Guid PostId {get;set;}
public Guid UserId {get;set;}
public VoteType VoteType {get; set;}
public Post Post {get; set;}
public User User {get; set;}
}
public enum VoteType {
Up = 1,
Down = 0
}
Now, Fluent API configuration for the UserVote as follows:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<UserVote>(e =>
{
e.HasKey(uv => new { uv.PostId, uv.UserId}); //<-- Here is the composite key.
e.HasOne(uv => uv.Post).WithMany().HasForeignKey(uv => uv.PostId).OnDelete(DeleteBehavior.Restrict);
e.HasOne(uv => uv.User).WithMany().HasForeignKey(uv => uv.UserId).OnDelete(DeleteBehavior.Restrict);
});
}

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

Bidirectional links to the same class in Entity Framework (code first)

I would like to create bidirectional links to the same class. Id like for the relationship class to have the attributes that would explain how the two classes are related. It may be a parent-child relationship or it be a simple "reference" relationship.
Currently, if I use the setup below, Entity Framework will automatically create a 3rd foreign key in the link table for the "myChildNodes" relationship. The only way I can get Entity Framework to understand what I am trying to do on the link class is to create two collections I.E. (childOf and ParentOf).
I would like to dynamically add relationship types and not need to create a collection representing that relationship type. I would rather handle that in the repository for the node object.
Node
{
Public int id {get; set;}
Public datetime createDate {get; set;}
Public bool isModified {get; set;}
//I would like just one collection for all links from this node as the source node
Public virtual ICollection<Link> myChildNodes{get; set;}
//I don't want to use something like this that explicitly defines the relationship
//Public virtual ICollection<Node> parentOf{get; set;}
//Public virtual ICollection<Node> childOf{get; set;}
Public Node() {
}
}
Link {
Public int id {get; set;}
Public datetime createdDate {get; set;}
Public string linkType {get; set;}
[ForeignKey("SourceNode")]
Public int? SourceNodeId { get; set;}
Public Node SourceNode {get; set;}
[ForeignKey("TargetNode")]
Public int? TargetNodeId { get; set;}
Public Node TargetNode {get; set;}
Public Link() {
}
}
Has anyone had success with this design before?

Code first mapping for self-related xref table

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.

Entity Framework code first not working

I have a Class like this:
class ClassA
{
public long classAID {get; set;}
public string Description {get; set;}
public IEnumerable<ClassB> ClassBs {get; set;}
}
class ClassB
{
public long classBID {get; set;}
public string SomeOtherDescription {get; set;}
public IEnumerable<ClassA> {get; set;}
}
class TestContext: DBContext
{
public DbSet<ClassA> ClassAs {get; set;}
public DbSet<ClassB> ClassBs {get; set;}
}
H have the DB with same column names and table names as the classes and properties.
I have done the web.config configuration as required. When i try to use above to retrieve the data i get the error
"System.Data.Edm.EdmEntityType: : EntityType 'ClassA' has no key defined. Define the key for this EntityType."
and
"System.Data.Edm.EdmEntityType: : EntityType 'ClassB' has no key defined. Define the key for this EntityType."
I tired multiple approaches such as setting the key attribute, Foreign key attribute etc. but nothing worked. Please let me know what am i missing.
I use C# 4 and i have verified with following URLs:
http://www.asp.net/mvc/tutorials/mvc-music-store-part-4
http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx
Use this:
public class ClassA
{
public long ClassAID {get; set;}
public string Description {get; set;}
public virtual ICollection<ClassB> ClassBs {get; set;}
}
public class ClassB
{
public long ClassBID {get; set;}
public string SomeOtherDescription {get; set;}
public virtual ICollection<ClassA> {get; set;}
}
public class TestContext: DBContext
{
public DbSet<ClassA> ClassAs { get; set; }
public DbSet<ClassB> ClassBs { get; set; }
}
As you can see navigation properties are marked as virtual. It will allow lazy loading = navigation property will be loaded separately first time your code access the property. It is not always the best way to load navigation properties this way because it causes additional roundtrips to the database. Because of that EF also offers eager loading where you explicitly tell EF to load your navigation property:
var query = context.ClassAs.Include(c => ClassBs);