Foreign key one to one with Entity Framework - c#

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

Related

Entity Framework Core - Inserting One-Directional Parent Child Relationship

Since Table Per Type isn't available in Core, I had to do a bit of a workaround to get my entities how I like them. Essentially I have a base class with its properties, and a navigation property to its parent:
public class Provision
{
public Guid ProvisionId { get; set; }
public string ProvisionName { get; set; }
public string ProvisionDescription { get; set; }
public Provision(){}
}
public class CompanyLeaveProvision
{
public Guid ProvisionId { get; set; }
public int CompanyId { get; set; }
public Provision Provision { get; set; }
public CompanyLeaveProvision() { }
}
Configurations:
public void Configure(EntityTypeBuilder<Provision> builder)
{
// Primary Key
builder.HasKey(t => t.ProvisionId);
// Properties
builder.Property(t => t.ProvisionName)
.IsRequired()
.HasMaxLength(40);
builder.Property(t => t.ProvisionDescription)
.HasMaxLength(500);
// Table & Column Mappings
builder.Property(t => t.ProvisionId).HasColumnName("ProvisionID");
builder.Property(t => t.ProvisionName).HasColumnName("ProvisionName");
builder.Property(t => t.ProvisionDescription).HasColumnName("ProvisionDescription");
builder.ToTable("Provision", "Organization");
}
public void Configure(EntityTypeBuilder<CompanyLeaveProvision> builder)
{
// Primary Key
builder.HasKey(t => t.ProvisionId);
// Properties
builder.Property(t => t.ProvisionId)
.IsRequired();
builder.Property(t => t.CompanyId)
.IsRequired();
// Table & Column Mappings
builder.ToTable("CompanyLeaveProvision", "Organization");
builder.Property(t => t.ProvisionId).HasColumnName("ProvisionID");
builder.Property(t => t.CompanyId).HasColumnName("CompanyID");
builder.HasOne(t => t.Provision).WithOne().HasForeignKey<Provision>(t => t.ProvisionId);
}
My context:
ProvisionContext: DbContext, IContext {
public DbSet<Provision> Provisions { get; set; }
public DbSet<CompanyLeaveProvision> CompanyLeaveProvisions { get; set;}
// OnModelCreating and other code below
}
I have a foreign key constraint on the the Organization.CompanyProvision table that references the ProvisionId property on the Organization.Provision table.
What is happening is the CompanyProvision is being inserted before the base Provision, resulting in this error:
The INSERT statement conflicted with the FOREIGN KEY constraint
"fk_CompanyLeaveProvision_Provision". The conflict occurred in
database "Dev", table "Organization.Provision", column 'ProvisionID'.
To attempt to save, here is the code I am calling:
_context.Entry(command.Provision.Provision).State = EntityState.Added;
_context.Entry(command.Provision).State = EntityState.Added;
await _context.SaveChangesAsync();
Aside from calling SaveChanges() after each _context.Entry(MyEntity).State = EntityState.Added, is there any way around this issue? I would prefer to have these save at once. I know a stored procedure is also an option, but I would prefer not to do that.
Thank you for your help!
It's because this fluent mapping
.HasForeignKey<Provision>(t => t.ProvisionId)
is telling EF Core that Provision is the dependent entity and has FK to the principal entity CompanyLeaveProvision, while the database model is the opposite.
So simply change Provision to CompanyLeaveProvision
.HasForeignKey<CompanyLeaveProvision>(t => t.ProvisionId)

How to define a domain model where the Primary Key is also the Foreign Key with fluent api

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

EntityFramework - Table per Type (TPT) Inheritance and Mapping Relationships with CodeFirst

I have the following Entities which I am persisting using EntityFramework CodeFirst:
public class User {
RedGroup RedGroup { get; protected set; }
virtual ICollection<GreenGroup> GreenGroups { get; }
int Id { get; protected set; }
int? RedGroupId { get; protected set; }
}
public abstract class Group {
int Id { get; protected set; }
virtual ICollection<User> Users { get; protected set; }
}
public class RedGroup : Group {
// Other properties
}
public class GreenGroup : Group {
// Other properties
}
Essentially, the user can belong to zero or one red groups, and more than one green group. Each group has a collection of users that belong to it.
I am trying to set up EF using CodeFirst with TPT and am having trouble sorting the mappings. At the moment, I have the following in OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new RedGroupMap());
modelBuilder.Configurations.Add(new GreenGroupMap());
modelBuilder.Configurations.Add(new UserMap());
}
These are the mapping classes:
public abstract class GroupMap<T> : EntityTypeConfiguration<T>
where T : Group {
public GroupMap() {
this.ToTable("Groups");
this.HasKey(t => t.Id);
this.Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("Id");
// Also has other non-relationship mappings
}
}
public class RedGroupMap() : GroupMap<RedGroup> {
public RedGroupMap() {
this.ToTable("RedGroups");
// Also has other non-relationship mappings
}
}
public class GreenGroupMap() : GroupMap<GreenGroup> {
public GreenGroupMap() {
this.ToTable("GreenGroups");
this.HasMany(c => c.Users)
.WithMany(p => p.GreenGroups)
.Map(m =>
{
m.MapLeftKey("GreenGroupId");
m.MapRightKey("UserId");
m.ToTable("Users_GreenGroups");
});
// Also has other non-relationship mappings
}
}
public class UserMap() : EntityTypeConfiguration<User> {
this.ToTable("Users");
this.HasKey(t => t.Id);
this.Property(t => t.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("Id");
this.HasOptional(t => t.RedGroup)
.WithMany(t => t.Users)
.Map(x => x.MapKey("RedGroupId"))
.WillCascadeOnDelete(false);
}
I am getting the following runtime error:
Users: FromRole: NavigationProperty 'Users' is not valid. Type 'RedGroup' of FromRole 'User_RedGroup_Target' in AssociationType 'User_RedGroup' must exactly match with the type 'GreenGroup' on which this NavigationProperty is declared on.
Afraid I'm stumped on how to set up this.
How can I set up the EntityFramework mappings to allow a Table per Type hierarchy?
I created a context without your mappings, but with a much simpler configuration and everything appeared to create OK:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Group>().ToTable("Groups");
modelBuilder.Entity<RedGroup>().ToTable("RedGroups");
modelBuilder.Entity<GreenGroup>().ToTable("GreenGroups");
}
I've noticed that you've defined [User].HasOptional(t => t.RedGroup), but the RedGroupId field on User is defined as int and not int? - perhaps this is related?
public class User {
int? RedGroupId { get; protected set; } // int -> int?
RedGroup RedGroup { get; protected set; } // virtual missing, but shouldn't matter
// Other properties
}
If RedGroup is required, try using .HasRequired instead.

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

Entity Framework 4 - Many-To-Many Relationship with a view

I have an entity Group which has a many to many relationship with a view VesselInfo. In database, the relationship is stored in a table, GroupVessel.
public class Group
{
public Group()
{
GroupVessels = new List<GroupVessel>();
}
public int GroupId { get; set; }
public virtual ICollection<GroupVessel> GroupVessels { get; set; }
}
public class GroupVessel
{
public int GroupVesselId { get; set; }
public int GroupId { get; set; }
public virtual Group Group { get; set; }
public int VesselId { get; set; }
public virtual VesselView Vessel { get; set; }
}
public class VesselView
{
public int VesselId { get; set; }
}
The entities are mapped in the context like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new VesselViewMapping());
modelBuilder.Configurations.Add(new GroupMapping());
modelBuilder.Configurations.Add(new GroupVesselMapping());
base.OnModelCreating(modelBuilder);
}
public class VesselViewMapping : EntityTypeConfiguration<VesselView>
{
public VesselViewMapping()
{
// Primary Key
HasKey(t => t.VesselId);
// Properties
Property(t => t.VesselId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
// Table & Column Mappings
ToTable("v_VesselInfo");
Property(t => t.VesselId).HasColumnName("VesselId");
}
}
public class GroupMapping : EntityTypeConfiguration<Group>
{
public GroupMapping()
{
// Primary Key
HasKey(group => group.GroupId);
// Properties
// Table & Column Mappings
ToTable("Group");
Property(t => t.GroupId)
.HasColumnName("GroupId")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
public class GroupVesselMapping : EntityTypeConfiguration<GroupVessel>
{
public GroupVesselMapping()
{
// Primary Key
HasKey(group => group.GroupVesselId);
// Properties
// Table & Column Mappings
ToTable("GroupVessel");
Property(t => t.GroupVesselId)
.HasColumnName("GroupVesselId")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(t => t.VesselId).HasColumnName("VesselId").IsRequired();
Property(t => t.GroupId).HasColumnName("GroupId");
// Relationships
HasRequired(t => t.Group).WithMany(g => g.GroupVessels)
.HasForeignKey(t => t.GroupVesselId);
}
}
When I try to instantiate the context, I get the following error:
Exception Details: System.Data.Entity.ModelConfiguration.ModelValidationException: One or more validation errors were detected during model generation:
\tSystem.Data.Entity.Edm.EdmAssociationEnd: : Multiplicity is not valid in Role 'GroupVessel_Group_Source' in relationship 'GroupVessel_Group'. Because the Dependent Role refers to the key properties, the upper bound of the multiplicity of the Dependent Role must be '1'.
Some additional info:
In database, the GroupVessel.VesselId column is a foreign key to a table Vessels, which is the underlying table of the v_VesselInfo view. There is no navigation property from VesselView back to GroupVessel as there is no need to traverse the graph that way in the application.
Finally figured it out, I used the wrong field as the foreign key field, changed to GroupId and it works

Categories

Resources