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
}
Related
I use the following Model to create a self-referencing table:
public class Part
{
[Key]
public long PartId { get; set; }
[Required]
public string PartName { get; set; }
public long? ParentPartId { get; set; }
public Part Parentpart { get; set; }
//Navigation properties
public ICollection<Part> ChildParts { get; set; } //For self-referencing
}
The Fluent API is:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//For parts self-referencing
modelBuilder.Entity<Part>(entity =>
{
entity.HasKey(x => x.PartId);
entity.Property(x => x.PartName);
entity.HasOne(e => e.Parentpart)
.WithMany(e => e.ChildParts)
.HasForeignKey(e => e.ParentPartId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
});
}
I can use the following code to get all the parents of a given part:
[HttpGet]
public async Task<IActionResult> GetParentPartsList(string partName)
{
var q = _sqlServerContext.Parts.Where(x => x.PartName == partName).Include(x => x.Parentpart);
while (q.Select(x => x.ParentPartId) == null)
{
q = q.ThenInclude(x => x.Parentpart);
}
q = q.ThenInclude(x => x.Parentpart);
return Ok(await q.ToListAsync());
}
I want to get a list of all children for a given part. I searched some threads such as this link, but they have a one-one relationship between child and parent while I need a one-many relationship. How can I achieve this?
My problem is similar to Is it possible to have a relation where the foreign key is also the primary key? but I have to do this with Fluent API.
I have basically the same situation as described in the question, but I cannot use annotations on my domain models due to our coding standards. Here is a bit of code (Clarified):
Domain Classes:
public class Table1
{
public long ID { get; set; }
public int SubTableType { get; set; }
...
public Table2 Table2 { get; set; }
public Table3 Table3 { get; set; }
public List<Table4> Table4s { get; set; }
public List<Table5> Table5s { get; set; }
}
public class Table2
{
public long ID { get; set; }
public string Location { get; set; }
public string Task { get; set; }
...
public Table1 Table1 { get; set; }
public Table6 Table6 { get; set; }
public List<Table7> Table7s { get; set; }
}
public class Table3
{
public long ID { get; set; }
public string DescriptionAndLocation { get; set; }
...
public Table1 Table1 { get; set; }
}
Configuration Classes:
internal class Table1Configuration : EntityTypeConfiguration<Table1>
{
public Table1Configuration()
{
ToTable("Table1");
HasKey(so => so.ID);
Property(so => so.SubTableType)
.IsRequired();
Property(so => so.ID)
.IsRequired()
.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
...
}
}
internal class Table2Configuration : EntityTypeConfiguration<Table2>
{
public Table2Configuration()
{
ToTable("Table2");
HasKey(bc => bc.ID);
Property(bc => bc.ID)
.IsRequired();
Property(bc => bc.Location)
.IsOptional()
.HasColumnType("nvarchar")
.HasMaxLength(50);
Property(bc => bc.Task)
.IsOptional()
.HasColumnType("nvarchar")
.HasMaxLength(4000);
...
HasRequired(bc => bc.Table1)
.WithOptional(so => so.Table2);
HasRequired(bc => bc.Table8)
.WithMany(bot => bot.Table2s)
.HasForeignKey(bc => bc.Tabe8ID);
}
}
internal class Table3Configuration : EntityTypeConfiguration<Table3>
{
public Table3Configuration()
{
ToTable("Table3");
HasKey(hic => hic.ID);
Property(hic => hic.DescriptionAndLocation)
.IsOptional()
.HasColumnType("nvarchar")
.HasMaxLength(4000);
Property(hic => hic.ID)
.IsRequired()
.HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
HasRequired(hic => hic.Table1)
.WithOptional(so => so.Table3);
}
}
When I run this code I get the error:
Invalid column name 'Table2_ID'.
What you are asking is the so called Shared Primary Key Associations, which is the standard (and better supported) EF6 model for one-to-one relationships.
Rather than removing the ID property, you should remove the MapKey call which is used to define a shadow FK property (which you don't need).
Since the property called ID by convention is a PK and required, basically all you need is this:
HasRequired(hic => hic.Table1)
.WithOptional(so => so.Table2); // or Table3
or the explicit equivalent of [Key] / [ForeignKey] combination:
HasKey(hic => hic.ID);
HasRequired(hic => hic.Table1)
.WithOptional(so => so.Table2); // or Table3
Exactly as the example for Configuring a Required-to-Optional Relationship (One-to–Zero-or-One) from the documentation.
I would try something like this:
modelBuilder.Entity<Table1>().HasKey(t => t.ID);
modelBuilder.Entity<Table1>().Property(t =>t.ID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Table1>()
.HasOptional(t1 => t1.Table2)
.WithRequired(t2 => t2.Table1).Map(m => m.MapKey("ID"));
I have a double relationship between the Person and PersonCompany tables where a Person can be any individual or legal person or a Person who is registered as a Company.
When I need to fetch a Person (Person table) with Id 2 from the bank, the EF should return the People and PersonsCompany that relate to the Person table, but this is not happening ... I believe the problem occurs because the Person and PersonCompany properties are from same type as Person. This makes EF understand that they are the same thing and returns values that do not match the related PersonCompany.
Do I have to do some sort of "Select" within the PersonsCompan navigation property? Does anyone know how to help me?
//Get value of table Person
public Pessoa GetById(int id)
{
return DbSet
.Include(pe => pe.Persons)
.ThenInclude(p => p.Person)
.Include(pe => pe.PersonsCompany)
.ThenInclude(pe => pe.PersonCmpany)
//(... other related tables )
.FirstOrDefault(x => x.PersonId == id);
}
public void Configure(EntityTypeBuilder<PersonEntity> builder)
{
builder.ToTable("PersonEntity");
builder.HasKey(pg => new { pg.PersonId, pg.PersonType});
builder
.HasOne(p => p.Person)
.WithMany(pg => pg.Persons)
.HasForeignKey(pg => pg.PersonId)
.OnDelete(DeleteBehavior.ClientSetNull);
builder.Property(pg => pg.PersonId)
.HasColumnName("PersonId")
.HasColumnType("integer")
.IsRequired();
builder.Property(pg => pg.PersonType)
.HasColumnName("PersonTypeId")
.HasColumnType("integer")
.IsRequired();
builder.Property(pg => pg.IdGeneral)
.HasColumnName("IdGeneral")
.HasColumnType("integer")
.IsRequired();
builder
.HasOne(f => f.PersonCompany)
.WithMany(pg => pg.PersonsCompany)
.HasForeignKey(pg => pg.PersonCompanyId)
.OnDelete(DeleteBehavior.ClientSetNull);
builder.Property(pg => pg.PersonCompanyId)
.HasColumnName("PersonCompanyId")
.HasColumnType("integer")
.IsRequired();
}
public class Person : Entity
{
public virtual ICollection<PersonEntity> Persons { get; private set; }
public virtual ICollection<PersonEntity> PersonsCompany { get; private set; }
}
public class PersonEntity
{
public int Id { get; private set; }
public int PersonId { get; private set; }
public int PersonCompanyId { get; private set; }
public virtual PersonType PersonType { get; private set; }
public virtual Person Person { get; private set; }
public virtual Person PersonCompany { get; private set; }
}
If I understand correctly, the problem is similar to Entity Framework Core: many-to-many self referencing relationship, so is the solution.
The confusion in your case comes from the collection navigation property names and their mappings to the reference navigation properties:
Person.Persons -> PersonEntity.Person
and
Person.PersonsCompany -> PersonEntity.PersonCompany
You should probably rename them like this:
Person.PersonCompanies -> PersonEntity.Person
and
Person.CompanyPersons -> PersonEntity.PersonCompany
so the other reference navigation property represents the intended link.
E.g.
Model:
public class Person : Entity
{
public virtual ICollection<PersonEntity> PersonCompanies { get; private set; }
public virtual ICollection<PersonEntity> CompanyPersons { get; private set; }
}
Relationship configuration:
builder
.HasOne(p => p.Person)
.WithMany(pg => pg.PersonCompanies)
.HasForeignKey(pg => pg.PersonId)
.OnDelete(DeleteBehavior.ClientSetNull);
builder
.HasOne(f => f.PersonCompany)
.WithMany(pg => pg.CompanyPersons)
.HasForeignKey(pg => pg.PersonCompanyId)
.OnDelete(DeleteBehavior.ClientSetNull);
Usage:
.Include(p => p.PersonCompanies)
.ThenInclude(pe => pe.PersonCompany)
.Include(p => p.CompanyPersons)
.ThenInclude(pe => pe.Person)
In this question: Ef Many To Many, an answer came up on how to manually specify a linking table. But I have a slightly unique situation (which I'm sure isn't really unique).
My two tables each have an Id field. E.G.: [dbo].[Account].[Id] and [dbo].[Person].[Id]. Each of these tables in my Code-First has the following OnModelCreating:
modelBuilder.Entity<Account>.HasKey(x => x.Id);
modelBuilder.Entity<Person>.HasKey(x => x.Id);
But my [dbo].[AccountsToPersons]... table has fields E.G.: [AccountId] and [PersonId]
The AccountsToPersons table is not represented by a class in code.
I obviously already have an existing Model, but we are using EF Code-First Fluent API instead of updating model from database.
So how do I change this code to make it work with mapping different ID columns names?
public DbSet<Person> Persons { get; set; }
public DbSet<Account> Accounts { get; set; }
. . .
modelBuilder.Entity<Account>()
.HasMany(a => a.Persons)
.WithMany()
.Map(x =>
{
x.MapLeftKey("AccountId"); // <-- Account.Id to AccountsToPersons.AccountId??
x.MapRightKey("PersonId"); // <-- Person.Id to AccountsToPersons.PersonId??
x.ToTable("AccountsToPersons");
});
When running a basic Linq To EF Query (from x in context.Accounts select x).ToList();, the query fails with the following error:
"Invalid Column Name 'Person_Id'."
But when running the Query (from x in context.Persons select x).ToList();, I get no error.
Other than basic typed columns, my models have these added to them:
// In my Account Model, I also have this property:
public IList<Person> Persons { get; set; }
// In my Person Model, I also have this property:
public IList<Account> Accounts { get; set; } // <-- in the Person model
And please note that even though my Accounts query passes and has field information, the Persons field is always null, even though I'm sure there are links in my AccountsToPersons table.
Try adding p => p.Accounts to your WithMany clause:
modelBuilder.Entity<Account>()
.HasMany(a => a.Persons)
.WithMany(p => p.Accounts) // <-- I think this should fix it
.Map(x =>
{
x.MapLeftKey("AccountId"); // <-- Account.Id to AccountsToPersons.AccountId??
x.MapRightKey("PersonId"); // <-- Person.Id to AccountsToPersons.PersonId??
x.ToTable("AccountsToPersons");
});
I just built up a test solution for your problem and for me it looks that is working.
One thing that i see you did different than me is:
public IList<Person> Persons { get; set; } // <-- in the Account model
public IList<Account> Accounts { get; set; } // <-- in the Person model
Try modifying into this:
public DbSet<Person> Persons { get; set; }
public DbSet<Account> Accounts { get; set; }
If this doesn't work i`ll post my entire setup.
My structure looks like this and it works for the queries you displayed:
public class Person
{
public int ID { get; set; }
public string Name { get; set; }
public IList<Account> Accounts { get; set; }
}
public class Account
{
public int ID { get; set; }
public string Name { get; set; }
public IList<Person> Persons { get; set; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public DbSet<Account> AccountSet { get; set; }
public DbSet<Person> PersonSet { get; set; }
public ApplicationDbContext()
: base("DefaultConnection")
{
this.Database.Log = (msg) => { Debug.Write(msg); };
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Account>().HasKey(x => x.ID);
modelBuilder.Entity<Person>().HasKey(x => x.ID);
modelBuilder.Entity<Account>()
.HasMany(a => a.Persons)
.WithMany()
.Map(w =>
{
w.MapLeftKey("AccountId");
w.MapRightKey("PersonId");
w.ToTable("AccountsToPersons");
});
}
}
Have you tried modifying your AccountsToPersons mapping slightly:
modelBuilder.Entity<Account>()
.HasMany(a => a.Persons)
.WithMany(p => p.Accounts) <-- Change
.Map(x =>
{
x.MapLeftKey("AccountId"); // <-- Account.Id to AccountsToPersons.AccountId??
x.MapRightKey("PersonId"); // <-- Person.Id to AccountsToPersons.PersonId??
x.ToTable("AccountsToPersons");
});
The Customer can have only one Language. I don't find the right way to create the key. When I get an object Customer the property LanguageId has a content but not the property Language. I use EF 6.1
This Language object will be use in other object.
I did this :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Configurations.Add(new CustomerMap());
modelBuilder.Configurations.Add(new LanguageMap());
}
public class Customer
{
public int CustomerID { get; set; }
public string Code { get; set; }
public int LanguageId { get; set; }
[ForeignKey("LanguageId")]
public Language Language { get; set; }
}
public class CustomerMap : EntityTypeConfiguration<Customer>
{
public CustomerMap()
{
this.HasKey(t => t.CustomerID);
// Properties
this.Property(t => t.CustomerID).IsRequired();
this.Property(t => t.Code).IsRequired();
// Table & Column Mappings
this.ToTable("Customer");
this.Property(t => t.CustomerID).HasColumnName("CustomerID");
this.Property(t => t.Code).HasColumnName("Code");
}
}
public class Language
{
public int LanguageID { get; set; }
public string Code { get; set; }
}
public class LanguageMap : EntityTypeConfiguration<Language>
{
public LanguageMap()
{
this.HasKey(t => t.LanguageID);
this.Property(t => t.Code).IsRequired();
}
}
Update (Language will be used in other object)
You can achieve one to one with two options, but first you have to remove the foreign key value in the principal. Principal means that the record must exist first, that's why this entity doesn't need to have foreign key value, just foreign key reference.
Remove this code.
public int LanguageId { get; set; }
[ForeignKey("LanguageId")]
First. After removing above code add this configuration.
modelBuilder.Entity<Customer>()
.HasRequired(a => a.Language)
.WithRequiredPrincipal();
Second, also add the foreign key reference on dependent (Language).
public Customer Customer { get; set; }
Then mention the principal reference in WithRequiredPrincipal.
modelBuilder.Entity<Customer>()
.HasRequired(a => a.Language)
.WithRequiredPrincipal(x => x.Customer);
update
To load language you can do it with lazy loading by adding virtual keyword (the context configuration must also enable lazy loading and proxy).
public virtual Language Language { get; set; }
Or do with eager loading.
var customerID = 5;
var customer = db.Set<Customer>().Include(c => c.Language)
.FirstOrDefault(c => c.CustomerID == customerID);