Add/Insert entity with inherited nested object graph (one to many relationship) - c#

Adding an entity with 1 level of one to many relationship is pretty straight forward.
using (var dbCtx = new DbContext())
{
dbCtx.Stuff.Add(myObject);
dbCtx.SaveChanges();
}
But how do you add an object with 2 levels? Adding it in the same way omits the 2. level. Which means that the Bar objects (in the example below) isn't saved. What am I doing wrong?
Object graph
Inherited objects
public class BaseEntity
{
public int Id;
// Omitted properites...
}
public class MyEntity : BaseEntity
{
// Omitted properites...
// Navigation properties
public virtual ICollection<Foo> Foos { get; set; }
}
Nested objects (1:M)
public class Foo // 1. level
{
public int Id;
public int MyEntityId;
// Omitted properites...
// Navigation properties
public virtual ICollection<Bar> Bars { get; set; }
public virutal MyEntity MyEntity { get; set; }
}
public class Bar // 2. level
{
public int Id;
public int FooId;
// Omitted properites...
// Navigation properties
public virutal Foo Foo { get; set; }
}
Mapping setup using fluent API
Inherited objects
public class BaseEntityMap : EntityTypeConfiguration<BaseEntity>
{
public BaseEntityMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Properties
// Table & Column Mappings
this.ToTable("BaseEntitySet");
this.Property(t => t.Id).HasColumnName("Id");
// ...
}
}
public class MyEntityMap : EntityTypeConfiguration<MyEntity>
{
public MyEntityMap()
{
// Table & Column Mappings
this.ToTable("BaseEntitySet_MyEntities");
}
}
Nested objects (1:M)
public class FooMap : EntityTypeConfiguration<Foo>
{
public FooMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Table & Column Mappings
this.ToTable("FooSet");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.MyEntityId).HasColumnName("MyEntity_Id");
// Relationships
this.HasRequired(t => t.MyEntity)
.WithMany(t => t.Foos)
.HasForeignKey(d => d.MyEntityId);
}
}
public class BarMap : EntityTypeConfiguration<Bar>
{
public BarMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Table & Column Mappings
this.ToTable("BarSet");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.FooId).HasColumnName("Bar_Id");
// Relationships
this.HasRequired(t => t.Foo)
.WithMany(t => t.Bars)
.HasForeignKey(d => d.FooId);
}
}
Repository
public void Add(BaseEntity item)
{
using (var ctx = new DbContext())
{
ctx.BaseEntities.Add(item);
ctx.SaveChanges();
}
}

\Wanted to post my answer in case it helps others, and so the experts can tear it to pieces and post the real answer. For me it did not start working out of the blue :)
If I understand your object graph correctly, it's MyEntity has Foos which has Bars. I had a similar structure, but when calling "SaveChanges" a DbUpdateException would be thrown with a message that
"multiple entities may have the same primary key."
Here's how I made it work for me:
Step 1: Change the Id properties from int to int? and initialize them to null. To me this is a more accurate model than a plain integer. When an entity is new, the ID is literally "undefined or unknown". 0 is a defined number and for some reason EF has a problem with the ID's being the same, even when the records are being added.
public class BaseEntity
{
public BaseEntity()
{
this.Id = null;
}
public int? Id;
// Omitted properites...
}
public class Foo
{
public Foo()
{
this.Id = null;
}
public int? Id;
}
public class Bar
{
public Bar()
{
this.Id = null;
}
public int? Id;
}
Step 2: Add the "DatabaseGeneratedOption.Identity" flag to the Id properties in the mapping. I believe this prevents it from being "required" in the case the entity is added to the datacontext.
public class BaseEntityMap : EntityTypeConfiguration<BaseEntity>
{
public BaseEntityMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Properties
// Table & Column Mappings
this.ToTable("BaseEntitySet");
this.Property(t => t.Id).HasColumnName("Id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
// ...
}
}
public class FooMap : EntityTypeConfiguration<Foo>
{
public FooMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Table & Column Mappings
this.ToTable("FooSet");
this.Property(t => t.Id).HasColumnName("Id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(t => t.MyEntityId).HasColumnName("MyEntity_Id");
// Relationships
this.HasRequired(t => t.MyEntity)
.WithMany(t => t.Foos)
.HasForeignKey(d => d.MyEntityId);
}
}
public class BarMap : EntityTypeConfiguration<Bar>
{
public BarMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Table & Column Mappings
this.ToTable("BarSet");
this.Property(t => t.Id).HasColumnName("Id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(t => t.FooId).HasColumnName("Bar_Id");
// Relationships
this.HasRequired(t => t.Foo)
.WithMany(t => t.Bars)
.HasForeignKey(d => d.FooId);
}
}

Related

One to one relationship Entity Framework foreign key mapping

I am working on assigning a relationship between two objects but I am getting an error on the mapping.
I have the following objects:
public abstract class EntityBase : IEntity
{
public int Id { get; set; }
}
public class ManagerUser : EntityBase
{
public string UserCode { get; set; }
public string Title { get; set; }
public virtual Staff StaffDetails { get; set; }
}
public class Staff : EntityBase
{
public string UserCode { get; set; }
public string DOB { get; set; }
}
public class ManagerUserMap : EntityMapBase<ManagerUser>
{
protected override void SetupMappings()
{
base.SetupMappings();
//this.HasKey(t => t.UserCode);
this.ToTable("ManagerUsers");
this.Property(t => t.Id).HasColumnName("ManagerUsersID")
.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
this.Property(t => t.Title).HasColumnName("txtTitle");
this.HasRequired(x => x.StaffDetails)
.WithMany()
.HasForeignKey(x => x.UserCode);
}
}
public class StaffMap : EntityMapBase<Staff>
{
protected override void SetupMappings()
{
base.SetupMappings();
//this.HasKey(t => t.UserCode);
this.ToTable("TblStaff");
this.Property(t => t.Id).HasColumnName("StaffID")
.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
this.Property(t => t.UserCode).HasColumnName("User_Code");
this.Property(t => t.DateOfBirth).HasColumnName("dob");
}
}
I am getting the following error:
The type of property 'UserCode' on entity 'ManagerUser' does not match
the type of property 'Id' on entity 'Staff' in the referential
constraint 'ManagerUser_StaffDetails'
I have searched around and failed to find a solution to get it to compare the the foreign key in ManagerUser with the UserCode property in Staff instead of the ID property.
Your keys are a bit messed up in the fluent config, HasKey sets the primary key for the entity. Below I have set the primary key to be Id for both entities. Then use the Usercode as a FK:
public class ManagerUserMap : EntityMapBase<ManagerUser>
{
protected override void SetupMappings()
{
base.SetupMappings();
this.HasKey(t => t.Id);
this.ToTable("ManagerUsers");
this.Property(t => t.Id).HasColumnName("ManagerUsersID")
.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
this.Property(t => t.Title).HasColumnName("txtTitle");
this.HasRequired(x => x.StaffDetails)
.WithMany()
.HasForeignKey(x => x.UserCode);
}
}
public class StaffMap : EntityMapBase<Staff>
{
protected override void SetupMappings()
{
base.SetupMappings();
this.HasKey(t => t.Id);
this.ToTable("TblStaff");
this.Property(t => t.Id).HasColumnName("StaffID")
.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
this.Property(t => t.UserCode).HasColumnName("User_Code");
this.Property(t => t.DateOfBirth).HasColumnName("dob");
}
}

Violation of PRIMARY KEY in entity with existing values in collection property

I have this entities:
public class Partner
{
public string ID { get; set; }
public string Name { get; set; }
public string PersonId { get; set; }
public int ProjectId { get; set; }
#region Navigation Properties
public virtual Person Person { get; set; }
public virtual Project Project { get; set; }
#endregion
}
public class Person
{
public string ID { get; set; }
public string Username { get; set; }
#region Navigation Properties
public virtual ICollection<Partner> Partners { get; set; }
#endregion
}
public class Project
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime DateStart { get; set; }
public DateTime DateEnd { get; set; }
public string Client { get; set; }
#region Navigation Properties
public ICollection<Partner> Partners { get; set; }
#endregion
}
When I insert a new project, I have in the Partners property (Project class) existing partners, or in Person property (Partner class) existing persons. When this happens, the exception "Violation of PRIMARY KEY" occurs.
My code to sabe Projects is this:
//GetAllProject return IList<ProjectModel>
var projects = this.findProjectService.GetAllProjects();
foreach(var project in projects)
{
var projectDb = context.Project.Where(e => e.Id == project.Id).FirstOrDefault();
if (projectDb == null)
{
logger.Debug("Try add new project {0}", project.Name);
var newProject = Mapper.Map<ProjectModel, Project>(project);
context.Project.Add(newProject);
context.SaveChanges();
}
}
How I can prevent this problem?
Edit for include mapping:
internal PartnerMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Id)
.IsUnicode(false);
this.Ignore(t => t.Name);
this.Property(t => t.PersonId)
.IsUnicode(false);
// Table
this.ToTable("Partner");
// Relations
this.HasRequired(t => t.Project)
.WithMany(r => r.Partners)
.HasForeignKey(t => t.ProjectId)
.WillCascadeOnDelete(false);
this.HasRequired(t => t.Person)
.WithMany(r => r.Partners)
.HasForeignKey(t => t.PersonId)
.WillCascadeOnDelete(false);
}
internal PersonMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Id)
.IsUnicode(false);
this.Ignore(t => t.Username);
// Table
this.ToTable("Person");
// Relations
}
internal ProjectMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Name)
.IsUnicode(false);
this.Property(t => t.Client)
.IsUnicode(false);
// Table
this.ToTable("Project");
// Relations
}
this is because of the mapping because all of your entities in the project Partners & Persons are detached from the context ... please check below the right approach to use the mapping ... (this is only a hint not the full solution)
you should also check the DatabaseGeneratedOption for your entities keys to make them Identity or you have to submit them with each insert.
//GetAllProject return IList<ProjectModel>
var projects = this.findProjectService.GetAllProjects();
foreach(var project in projects)
{
var projectDb = context.Project.Where(e => e.Id == project.Id).FirstOrDefault();
if (projectDb == null)
{
// the next line will map you a newproject and all of the partners and persons inside the each parnter if found ...
// so you will get some entities which should be attached to the context in order for the ef to regonise that you mean not to insert new, but just to map them to the new project .. so
//var newProject = Mapper.Map<ProjectModel, Project>(project);
var newProject = context.Project.Create();
// you should setup your mapping to not map the ID and let each mapsetup to map an entity itself not it's child entities
newProject = Mapper.Map<ProjectModel, Project>(project);
// loop all partners in the PROJECT MODEL
foreach(var partner in project.PartnerModels)
ToPartners(partner, newProject.Partners);
context.Project.Add(newProject);
context.SaveChanges();
}
}
public void ToPartners(PartnerModel model, ICollection<Partner> partners)
{
var partnerDb = context.Partner.Where(e => e.Id == model.Id).FirstOrDefault();
if(parterDb == null)
{
var newPartner = context.Partner.Create();
newPartner = Mapper.Map<PartnerModel, Partner>(model);
// loop all persons in the PARTNER MODEL
foreach(var person in model.PersonsModel)
ToPersons(person, newPartner.Persons);
partners.Add(newPartner);
}
else
{
// loop all persons in the PARTNER MODEL
foreach(var person in model.PersonsModel)
ToPersons(person, partnerDb.Persons);
// here the partner is attached to the context so he will not insert a new one, it will just add (map) it to the project.
partners.Add(parterDb);
}
}
public void ToPersons(PersonModel model, ICollection<Person> persons)
{
// MAP IT
}

Entity Framework TPH bug

I have this Entity :
public class Soggetti
{
public Soggetti()
{
}
public int sersog { get; set; }
string descri { get; set; }
}
public class SoggettiMap : EntityTypeConfiguration<Soggetti>
{
public SoggettiMap()
{
// Primary Key
this.HasKey(t => t.sersog);
this.Property(t => t.descri)
.IsFixedLength()
.HasMaxLength(200);
this.ToTable("soggetti");
this.Property(t => t.sersog).HasColumnName("sersog");
this.Property(t => t.descri).HasColumnName("descri");
}
}
And this:
public class Enti : Soggetti
{
public Enti()
{
}
public int serent { get; set; }
public string denuff { get; set; }
}
public class entiMap : EntityTypeConfiguration<Enti>
{
public entiMap()
{
// Primary Key
this.HasKey(t => t.serent);
// Properties
this.Property(t => t.denuff)
.HasMaxLength(150);
// Table & Column Mappings
this.ToTable("enti");
this.Property(t => t.denuff).HasColumnName("denuff");
this.Property(t => t.serent).HasColumnName("serent");
}
}
If try this linq code :
from e in Enti select e
result is ok, nut if try this :
Enti.Find(1)
I have an error because where's sql code goes by sersog and not serent
WHERE [Extent1].[sersog] = #p0
In the Enti's entity map i've defined serent with HasKey property and not sersog that is defined with HasKey property in soggetti class

Entity Framework 6 cross reference table

To all, thank you for your help in advance.
I am unable to correctly update the cross reference table using my Data-context in Entity Framework 6 that links the two entity tables .
Within my ObjectRepository, when I make the call to my datacontext to update the database, i.e. "db.SaveChanges(); , I get an INNER EXCEPTION
"Cannot insert explicit value for identity column in table 'SN_Tags' when IDENTITY_INSERT is set to OFF."
That error makes sense because, as it is defined now, my Datacontext is attempting to insert records into my SN_Tags table, which is a table that is already populated with Tags that I want to link to my SN_Objects. The table that should get a record, or records inserted into it when an SN_Object is added to the database, is the SN_ObjectTags link table. I've attempted to explain the structure below. I hope this isn't too long.
Thank you for the help.
E_Rog
SN_Objects An SN_Object entity can have many tags.
Columns: ObjectID, ObjectDesc, ModuleID.. etc
public partial class SNObject
{
public SNObject()
{
this.SN_Tags = new List<Tag>();
}
public int ObjectID { get; set; }
public string ObjectDesc { get; set; }
public int ModuleID { get; set; }
......
public virtual ICollection<Tag> SN_Tags { get; set; }
}
SN_Tags A Tag entity can be linked to many SN_Objects This table is already populated with the list of tags that will be checked to see if an SN_Object should be linked to a particular Tag or Tags.
Columns: TagID, TagDesc, IsActive
public partial class Tag
{
public Tag()
{
//this.SN_Objects = new List<SNObject>();
}
public int TagID { get; set; }
public string TagDesc { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<SNObject> SN_Objects { get; set; }
}
SN_ObjectTags Cross reference table for the many to many relationship
Columns: ObjectID, TagID
As you know, no Entity exists for this Cross Reference table.
NOW,, here's the Mapping class files, with a few extra details referencing properties I didn't include in the Entity class files above, but the key stuff is there.
SN_ObjectsMap
public class SN_ObjectsMap : EntityTypeConfiguration<SNObject>
{
public SN_ObjectsMap()
{
// Primary Key
this.HasKey(t => t.ObjectID);
// Properties
this.Property(t => t.ObjectDesc)
.IsRequired()
.HasMaxLength(200);
this.Property(t => t.BankNameShort)
.IsRequired()
.HasMaxLength(255);
// Table & Column Mappings
this.ToTable("SN_Objects");
this.Property(t => t.ObjectID).HasColumnName("ObjectID");
this.Property(t => t.ObjectDesc).HasColumnName("ObjectDesc");
this.Property(t => t.ModuleID).HasColumnName("ModuleID");
this.Property(t => t.BankNameShort).HasColumnName("BankNameShort");
this.Property(t => t.SubModuleID).HasColumnName("SubModuleID");
// Relationships
this.HasMany(t => t.SN_Tags)
.WithMany(t => t.SN_Objects)
.Map(m =>
{
m.ToTable("SN_ObjectTags");
m.MapLeftKey("ObjectID");
m.MapRightKey("TagID");
});
this.HasRequired(t => t.TN_Banks)
.WithMany(t => t.SN_Objects)
.HasForeignKey(d => d.BankNameShort);
this.HasRequired(t => t.TN_Modules)
.WithMany(t => t.SN_Objects)
.HasForeignKey(d => d.ModuleID);
}
}
SN_TagsMap
public class SN_TagsMap : EntityTypeConfiguration<Tag>
{
public SN_TagsMap()
{
// Primary Key
this.HasKey(t => t.TagID);
// Properties
this.Property(t => t.TagID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.Property(t => t.TagDesc)
.IsRequired()
.HasMaxLength(256);
// Table & Column Mappings
this.ToTable("SN_Tags");
this.Property(t => t.TagID).HasColumnName("TagID");
this.Property(t => t.TagDesc).HasColumnName("TagDesc");
this.Property(t => t.IsActive).HasColumnName("IsActive");
}
}
And Finally an abbreviated DataContext
public partial class MyContext : DbContext
{
static MyContext()
{
Database.SetInitializer<MyContext>(null);
}
public MyContext() : base("Name=MyContext")
{
}
public DbSet<CPR> SN_ObjectCPRs { get; set; }
public DbSet<File> SN_ObjectFiles { get; set; }
public DbSet<SNObject> SN_Objects { get; set; }
public DbSet<Tag> SN_Tags { get; set; }
public DbSet<sysdiagram> sysdiagrams { get; set; }
public DbSet<Bank> TN_Banks { get; set; }
public DbSet<Module> TN_Modules { get; set; }
public DbSet<SubModule> TN_SubModules { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new SN_ObjectCPRsMap());
modelBuilder.Configurations.Add(new SN_ObjectFilesMap());
modelBuilder.Configurations.Add(new SN_ObjectsMap());
modelBuilder.Configurations.Add(new SN_TagsMap());
modelBuilder.Configurations.Add(new sysdiagramMap());
modelBuilder.Configurations.Add(new TN_BanksMap());
modelBuilder.Configurations.Add(new TN_ModulesMap());
modelBuilder.Configurations.Add(new TN_SubModulesMap());
}
}

EF MVC4 C#: Many to Many Relationship with "relationship property" DB First

I've already spent the last few days trying to fix my problem, sadly without any result. I've already read countless post on here on this subject, but I keep getting the same error. "Unknown column 'Extent1.foo_id' in 'field list'"... What am I doing wrong? My mapping has to be wrong some way, but I fail to see how...
Edit: It's database first!
I also have another class "Doo" which has a many to many relationship with "Foo" but that one is working fine.
Thanks in advance!
public class Foo
{
public Foo()
{
this.FooBoo = new Collection<FooBoo>();
}
public String FooId { get; set; }
public virtual ICollection<FooBoo> FooBoo { get; set; }
}
public class Boo
{
public Boo()
{
this.FooBoo = new Collection<FooBoo>();
}
public String BooId { get; set; }
public virtual ICollection<FooBoo> FooBoo { get; set; }
}
public class FooBoo
{
public String Fooid { get; set; }
public virtual Foo Foo { get; set; }
public String Booid { get; set; }
public virtual Boo Boo { get; set; }
public Boolean RandomProperty { get; set; }
}
public class BooMapper : EntityTypeConfiguration<Boo>
{
public BooMapper()
{
this.HasKey(t => t.BooId);
this.Property(t => t.BooId).HasColumnName("booid");
this.ToTable("boo", "fooboodb");
this.HasMany(t => t.FooBoo)
.WithRequired()
.HasForeignKey(t => t.Booid);
}
}
public class FooMapper : EntityTypeConfiguration<Foo>
{
public FooMapper()
{
this.HasKey(t => t.FooId);
this.Property(t => t.FooId).HasColumnName("fooid");
.
this.ToTable("foo", "fooboodb");
this.HasMany(t => t.FooBoo)
.WithRequired()
.HasForeignKey(t => t.Booid);
}
}
public class FooBooMapper : EntityTypeConfiguration<FooBoo>
{
public FooBooMapper()
{
this.HasKey(t => new {t.Fooid, t.Booid});
this.Property(t => t.Fooid);
this.Property(t => t.Booid);
this.Property(t => t.RandomProperty);
this.ToTable("fooboo", "fooboodb");
this.Property(t => t.Fooid).HasColumnName("Fooid");
this.Property(t => t.Booid).HasColumnName("Booid");
this.Property(t => t.RandomProperty).HasColumnName("randomproperty");
}
}
You must provide a lambda expression for the two WithRequired calls in order to specify the inverse navigation properties. Otherwise EF will assume that they belong to another additional relationship which is causing those foreign keys with underscores:
public class BooMapper : EntityTypeConfiguration<Boo>
{
public BooMapper()
{
//...
this.HasMany(t => t.FooBoo)
.WithRequired(fb => fb.Boo)
.HasForeignKey(t => t.Booid);
}
}
public class FooMapper : EntityTypeConfiguration<Foo>
{
public FooMapper()
{
//...
this.HasMany(t => t.FooBoo)
.WithRequired(fb => fb.Foo)
.HasForeignKey(t => t.Booid);
}
}

Categories

Resources