Entity Framework Code First Many to Many Setup For Existing Tables - entity-framework

I have the following tables Essence, EssenseSet, and Essense2EssenceSet
Essense2EssenceSet is the linking table that creates the M:M relationship.
I've been unable to get the M:M relationship working though in EF code first though.
Here's my code:
[Table("Essence", Schema = "Com")]
public class Essence
{
public int EssenceID { get; set; }
public string Name { get; set; }
public int EssenceTypeID { get; set; }
public string DescLong { get; set; }
public string DescShort { get; set; }
public virtual ICollection<EssenceSet> EssenceSets { get; set; }
public virtual EssenceType EssenceType { get; set; }
}
[Table("EssenceSet", Schema = "Com")]
public class EssenceSet
{
public int EssenceSetID { get; set; }
public int EssenceMakerID { get; set; }
public string Name { get; set; }
public string DescLong { get; set; }
public string DescShort { get; set; }
public virtual ICollection<Essence> Essences { get; set; }
}
[Table("Essence2EssenceSet", Schema = "Com")]
public class Essence2EssenceSet
{
//(PK / FK)
[Key] [Column(Order = 0)] [ForeignKey("Essence")] public int EssenceID { get; set; }
[Key] [Column(Order = 1)] [ForeignKey("EssenceSet")] public int EssenceSetID { get; set; }
//Navigation
public virtual Essence Essence { get; set; }
public virtual EssenceSet EssenceSet { get; set; }
}
public class EssenceContext : DbContext
{
public DbSet<Essence> Essences { get; set; }
public DbSet<EssenceSet> EssenceSets { get; set; }
public DbSet<Essence2EssenceSet> Essence2EssenceSets { get; set; }
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<Essence>()
.HasMany(e => e.EssenceSets)
.WithMany(set => set.Essences)
.Map(mc =>
{
mc.ToTable("Essence2EssenceSet");
mc.MapLeftKey("EssenceID");
mc.MapRightKey("EssenceSetID");
});
}
}
This is the code I'm trying to run:
Essence e = new Essence();
e.EssenceTypeID = (int)(double)dr[1];
e.Name = dr[2].ToString();
e.DescLong = dr[3].ToString();
//Get Essence Set
int setID = (int)(double)dr[0];
var set = ctx.EssenceSets.Find(setID);
e.EssenceSets = new HashSet<EssenceSet>();
e.EssenceSets.Add(set);
ctx.Essences.Add(e);
ctx.SaveChanges();
And here's the error:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception.
I'm not able to find the problem. I'd greatly appreciate help setting this up right.
Thanks!

Remove your Essence2EssenceSet model class. If junction table contains only keys of related entities participating in many-to-many relations it is not needed to map it as entity. Also make sure that your fluent mapping of many-to-many relations specifies schema for table:
mb.Entity<Essence>()
.HasMany(e => e.EssenceSets)
.WithMany(set => set.Essences)
.Map(mc =>
{
mc.ToTable("Essence2EssenceSet", "Com");
mc.MapLeftKey("EssenceID");
mc.MapRightKey("EssenceSetID");
});

Related

Entity Framework Core multiple relationships to same table

I have a problem with two references to the same table with different columns:
public class MainApplicationContext : DbContext
{
public MainApplicationContext(MainSqlDbContext mainSqlDbContext)
{
MainSqlDbContext = mainSqlDbContext;
this.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
public DbSet<Organisation> Organisations { get; set; }
public DbSet<OrganisationContact> OrganisationContacts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Organisation>()
.HasKey(t => new { t.OrgId, t.OrgType, });
modelBuilder.Entity<OrganisationContact>().Property(p => p.OcsId).HasValueGenerator<SequenceNumberValueGenerator>().ValueGeneratedOnAdd();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(MainSqlDbContext.Database.GetDbConnection());
base.OnConfiguring(optionsBuilder);
}
private MainSqlDbContext MainSqlDbContext;
}
[SequenceNameAttribute("ORGANISATIONCONTACTS", "web")]
[Table("ORGANISATIONCONTACTS", Schema = "dbo")]
[Serializable]
public partial class OrganisationContact
{
[Column("OCS_ACTIVE")]
[MaxLength(1)]
public string OcsActive { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Key]
[Column("OCS_ID")]
public int OcsId { get; set; }
[Column("OCS_NAME")]
[MaxLength(255)]
public string OcsName { get; set; }
[Column("OCS_ORGANISATION_KEY")]
[RelationshipTableAttribue("ORGANISATIONS", "dbo")]
//Relationships
public int OcsOrganisationKey { get; set; }
[ForeignKey("OcsOrganisationKey")]
public Organisation Organisation { get; set; }
[Column("OCS_TYPE")]
[MaxLength(20)]
[RelationshipTableAttribue("ORGANISATIONS", "dbo")]
// Relationships
public string OcsType { get; set; }
[ForeignKey("OCS_TYPE")]
public Organisation Organisation1 { get; set; }
public OrganisationContact()
{
}
}
[SequenceNameAttribute("ORGANISATIONS", "web")]
[Table("ORGANISATIONS", Schema = "dbo")]
[Serializable]
public partial class Organisation
{
[Column("ORG_EMAIL")]
[MaxLength(255)]
public string OrgEmail { get; set; }
[Range(0, int.MaxValue)]
[Column("ORG_ID")]
public int OrgId { get; set; }
[Required]
[Column("ORG_NAME")]
[MaxLength(255)]
public string OrgName { get; set; }
[Required]
[Column("ORG_TYPE")]
[MaxLength(20)]
public string OrgType { get; set; }
[InverseProperty("Organisation")]
public ICollection<OrganisationContact> OrganisationContacts { get; set; }
[InverseProperty("Organisation1")]
public ICollection<OrganisationContact> ORGANISATIONCONTACTS1 { get; set; }
public Organisation()
{
this.OrganisationContacts = new HashSet<OrganisationContact>();
this.ORGANISATIONCONTACTS1 = new HashSet<OrganisationContact>();
}
}
I get this error:
System.InvalidOperationException: 'The property 'OCS_TYPE' cannot be added to the type 'OrganisationContact' because there was no property type specified and there is no corresponding CLR property or field. To add a shadow state property the property type must be specified.
The core issue here is that you define a composite primary key in table Organisation but you try to use single fields as foreign keys in table OrganisationContact.
If the primary key of the referenced table is composite, the foreign keys referencing it must be composite, as well, consisting of fields of the same number and type:
[Table("ORGANISATIONCONTACTS", Schema = "dbo")]
public partial class OrganisationContact
{
// irrelevant declarations omitted for brevity...
[Column("OCS_ORGANISATION_ORG_ID")]
public int Organisation_OrgId { get; set; }
[Column("OCS_ORGANISATION_ORG_TYPE")]
public string Organisation_OrgType { get; set; }
[ForeignKey(nameof(Organisation_OrgId) + "," + nameof(Organisation_OrgType))]
public Organisation Organisation { get; set; }
[Column("OCS_ORGANISATION1_ORG_ID")]
public int Organisation1_OrgId { get; set; }
[Column("OCS_ORGANISATION1_ORG_TYPE")]
public string Organisation1_OrgType { get; set; }
[ForeignKey(nameof(Organisation1_OrgId) + "," + nameof(Organisation1_OrgType))]
public Organisation Organisation1 { get; set; }
}
[Table("ORGANISATIONS", Schema = "dbo")]
public partial class Organisation
{
// irrelevant declarations omitted for brevity...
[InverseProperty(nameof(OrganisationContact.Organisation))]
public ICollection<OrganisationContact> OrganisationContacts { get; set; }
[InverseProperty(nameof(OrganisationContact.Organisation1))]
public ICollection<OrganisationContact> ORGANISATIONCONTACTS1 { get; set; }
}
Some suggestions:
Please post MCV code. There are some exotic attributes (like RelationshipTableAttribue) and unknown type references (MainSqlDbContext) which has nothing to do with the problem but makes more cumbersome to review the issue.
Try to avoid hardcoded strings as much as possible. The nameof operator has been available for quite a while (since C# 6.0).
The preferred way to configure your DB mappings is fluent API in EF Core. Data annotation attributes are pretty limited in functionality. (E.g. you cannot define a composite primary key using attributes in EF Core.)

Referenced object is not loaded from database

This the table structure I have:
#region Tables
public class WorkoutProfile
{
public WorkoutProfile()
{
WorkoutExercises = new List<WorkoutExercise>();
}
[Key]
public int ProfileId { get; set; }
public string Name { get; set; }
public int Sets { get; set; }
public int RestAfterSetInSeconds { get; set; }
public virtual User User { get; set; }
public virtual ICollection<WorkoutExercise> WorkoutExercises { get; set; }
}
public class WorkoutExercise
{
[Key]
public int WorkoutId { get; set; }
public virtual Exercise Exercise { get; set; }
public int Order { get; set; }
public int WorkoutTimeInSeconds { get; set; }
public int RestAfterInSeconds { get; set; }
}
public class Exercise
{
[Key]
public long ExerciseId { get; set; }
public string Title { get; set; }
public string Visualisation { get; set; }
public bool IsDefault { get; set; } // Is exersice should be included when user first registers
}
public class User
{
[Key]
public long UserId { get; set; }
public string Email { get; set; }
public DateTime Registered { get; set; }
}
#endregion Tables
In the repository class I run the following linq query:
return context
.WorkoutProfiles.Include(w => w.WorkoutExercises)
.Where(q => q.User.UserId == userId && q.ProfileId == profileId)
.FirstOrDefault();
and I receive the good and old "Object reference not set to an instance of an object". When examining the result, see that Exercises property in WorkoutExercises is null.
This is how the database is created using code first approach:
So, the question is: why Exercises not included in WorkoutExercises object? Do I need to include it somehow? I am using .NET Core 2
The simple answer would be no lazy loading in EFCore. Not Released yet but if you want to dabble with alpha code, its in the repository. Based on your classes there are no collections for exercises in WorkoutExcercise.
Then you need to ThenInclude(w => w.Exercises) following your Include clause since EFCore doesn't do lazy loading.
I found a solution following this post
Altered my code as following:
var top = context
.Set<WorkoutProfile>()
.Where(q => q.ProfileId == profileId && q.User.UserId == userId)
.Include(q => q.WorkoutExercises)
.SingleOrDefault();
context
.Entry(top)
.Collection(e => e.WorkoutExercises)
.Query()
.OfType<WorkoutExercise>()
.Include(e => e.Exercise)
.Load();
And it worked

EF issues with 2 foreign keys going to same table

Using the new ASP.NET Core and Entity Framework 7.0 RC1 Final. I have two fields with a one-to-many relationship between Standards and Students. If I just have the one FK and Navigation Key the code works just fine, but when I add in the second FK (Standard2) and Nav field (Students2) I get the following error message:
InvalidOperationException: The navigation 'Students' on entity type 'TestProject.Models.Standard' has not been added to the model, or ignored, or target entityType ignored.
public class Standard
{
public Standard()
{
}
public int StandardId { get; set; }
public string StandardName { get; set; }
public IList<Student> Students { get; set; }
public IList<Student> Students2 { get; set; }
}
public Student()
{
}
public int StudentID { get; set; }
public string StudentName { get; set; }
public DateTime DateOfBirth { get; set; }
public byte[] Photo { get; set; }
public decimal Height { get; set; }
public float Weight { get; set; }
//Foreign key for Standard
public int StandardId { get; set; }
public int StandardId2 { get; set; }
[ForeignKey("StandardId")]
public Standard Standard { get; set; }
[ForeignKey("StandardId2")]
public Standard Standard2 { get; set; }
}
How do I have two FK's to the same table in EF 7?
The problem is that you need to specify the other end of your relationships by using InverseProperty attribute, something that EF cannot infer on its own and hence throws an exception:
public class Standard
{
public int StandardId { get; set; }
public string StandardName { get; set; }
[InverseProperty("Standard")]
public IList<Student> Students { get; set; }
[InverseProperty("Standard2")]
public IList<Student> Students2 { get; set; }
}
Or you can achieve the same results by using fluent API:
modelBuilder.Entity<Standard>()
.HasMany(s => s.Students)
.WithOne(s => s.Standard)
.HasForeignKey(s => s.StandardId);
modelBuilder.Entity<Standard>()
.HasMany(s => s.Students2)
.WithOne(s => s.Standard2)
.HasForeignKey(s => s.StandardId2);

Entity Framework Code first creates unexpected Tables and Relationships

Using EntityFramework 6.1.3, I've got the following
public class RacesContext:DbContext
{
public DbSet<Race> Races { get; set; }
public DbSet<Sailboat> Sailboats { get; set; }
public DbSet<VenueParticipation> VenueParticipations { get; set; }
}
public class Crew
{
public int CrewId { get; set; }
public string Name { get; set; }
}
public class Sailboat
{
[Key]
public int SailboatId { get; set; }
public string Name { get; set; }
public string Skipper { get; set; }
public virtual ICollection<Crew> BoatCrew { get; set; }
}
public class VenueParticipation
{
[Key]
public int Id { get; set; }
public virtual ICollection<Sailboat> Boats { get; set; }
public virtual ICollection<Race> Races { get; set; }
}
public class Race
{
[Key]
public int RaceId { get; set; }
public string Venue { get; set; }
public DateTime Occurs { get; set; }
}
EF creates the Creates the Crews table with the proper PK and FK as I would expect. But creates the Races Sailboats, VenueParticipations tables in an unexpected way. Sailboats get's the expected PK but the unexpected FK VenueParticipation_Id as does Races. I was expecting the VenueParticipations table to get FKs to the others allowing a many to many relationship.. I'm sure I'm missing something here. Any advice would be great.
You can either configure the joining tables VenueParticipationSailboat, VenueParticipationRace with the proper FKs or you can use the fluent API:
modelBuilder.Entity<VenueParticipation>()
.HasMany(t => t.Sailboats)
.WithMany(t => t.VenueParticipations)
.Map(m =>
{
m.ToTable("VenueParticipationSailboat");
m.MapLeftKey("VenueParticipationID");
m.MapRightKey("SailboatID");
});
https://msdn.microsoft.com/en-us/data/jj591620.aspx#ManyToMany

M:M Mapping - EF 4.3 CodeFirst (Existing Database)

I have two tables (Table A, Table B) joined with a join table (TableAB) with 3 payload columns. By Payload I mean columns apart from Id, TableAId, and TableBId.
I can insert into all tables successfully, but I need to insert data into one of the payload columns on Insert. I'm using EF 4.3, Fluent API. Can anyone help? Thanks in advance.
public class Organisation : EntityBase<int>, IAggregateRoot
{
public string Name { get; set; }
public string Url { get; set; }
public int CountryId { get; set; }
public int? OwnershipTypeId { get; set; }
public int OrganisationStatusId { get; set; }
public virtual ICollection<Feature> Features { get; set; }
public virtual ICollection<OrganisationType> OrganisationTypes { get; set; }
public virtual ICollection<PricePlan> PricePlans { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User: EntityBase<Guid>, IAggregateRoot
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string JobTitle { get; set; }
public int? PhoneCallingCodeId { get; set; }
public int? PhoneAreaCode{ get; set; }
public string PhoneLocal { get; set; }
public int? MobileCallingCodeId { get; set; }
public int? MobileAreaCode { get; set; }
public string MobileLocal { get; set; }
public virtual ICollection<Organisation.Organisation> Organisations { get; set; }
}
public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public int OrganisationRoleId {get; set;}//Foreign Key - have tried leaving it out, tried it as public virtual Organisation Organisation {get;set;
public bool IsApproved { get; set; }
}
public class SDContext : DbContext
{
public ObjectContext Core
{
get
{
return (this as IObjectContextAdapter).ObjectContext;
}
}
public IDbSet<User> User { get; set; }
public IDbSet<Organisation> Organisation { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Organisation>().HasMany(u => u.Users).WithMany(o => o.Organisations).Map(m =>
{
m.MapLeftKey("OrganisationId");
m.MapRightKey("UserId");
m.ToTable("OrganisationUser");
});
//I have tried specifically defining the foreign key in fluent, but I really need to understand how I can add the payload properties once I access and edit them.
Your mapping is not correct for your purpose. If you want to treat OrganisationUser as an intermediate entity between Organisation and User you must create relationships between Organisation and OrganisationUser and between User and OrganisationUser, not directly between Organisation and User.
Because of the intermediate entity which contains its own scalar properties you cannot create a many-to-many mapping. EF does not support many-to-many relationships with "payload". You need two one-to-many relationships:
public class Organisation : EntityBase<int>, IAggregateRoot
{
// ...
// this replaces the Users collection
public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}
public class User : EntityBase<Guid>, IAggregateRoot
{
// ...
// this replaces the Organisations collection
public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}
public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
public int OrganisationId { get; set; }
public Organisation Organisation { get; set; }
public Guid UserId { get; set; }
public User User { get; set; }
// ... "payload" properties ...
}
In Fluent API you must replace the many-to-many mapping by the following:
modelBuilder.Entity<Organisation>()
.HasMany(o => o.OrganisationUsers)
.WithRequired(ou => ou.Organisation)
.HasForeignKey(ou => ou.OrganisationId);
modelBuilder.Entity<User>()
.HasMany(u => u.OrganisationUsers)
.WithRequired(ou => ou.User)
.HasForeignKey(ou => ou.UserId);
Your derived DbContext may also contain a separate set for the OrganisationUser entity:
public IDbSet<OrganisationUser> OrganisationUsers { get; set; }
It's obvious now how you write something into the intermediate table:
var newOrganisationUser = new OrganisastionUser
{
OrganisationId = 5,
UserId = 8,
SomePayLoadProperty = someValue,
// ...
};
context.OrganisastionUsers.Add(newOrganisastionUser);
context.SaveChanges();
If you want to make sure that each pair of OrganisationId and UserId can only exist once in the link table, it would be better to make a composite primary key of those two columns to ensure uniqueness in the database instead of using a separate Id. In Fluent API it would be:
modelBuilder.Entity<OrganisationUser>()
.HasKey(ou => new { ou.OrganisationId, ou.UserId });
More details about such a type of model and how to work with it is here:
Create code first, many to many, with additional fields in association table