One to zero-or-one with HasForeignKey - c#

I have two models:
public class Person
{
public virtual int Id { get; set; }
public virtual Employee Employee { get; set; } // optional
}
public class Employee
{
public virtual int Id { get; set; }
public virtual int PersonId { get; set; }
public virtual Person Person {get; set; } // required
}
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
Property(e=>e.PersonId) // I need this property mapped
.HasColumnName("person_id")
.HasColumnType("int");
}
}
I want to map them using fluent mapping. Employee table has column 'person_id' which is non-nullable. I tried following:
HasRequired(e => e.Person)
.WithOptional(p => p.Employee)
.Map(m => m.MapKey("person_id"));
But it fails with:
System.Data.Entity.ModelConfiguration.ModelValidationException : One
or more validation errors were detected during model generation:
person_id: Name: Each property name in a type must be unique. Property
name 'person_id' is already defined.
I need PersonId property on its own, so what I basically want is:
HasRequired(e => e.Person)
.WithOptional(p => p.Employee)
.HasForeignKey(e => e.PersonId); // there is no such method
But there is no such method here as HasForeignKey

OK, I figured that out - you should use WithMany (yep, not obvious) in order to store foreign key in some property:
Property(e => e.PersonId).HasColumnName("person_id");
HasRequired(e => e.Person)
.WithMany()
.HasForeignKey(p => p.PersonId);
See One-to-One Foreign Key Associations article for details. BTW this will create employee foreign key column for person's table, but there is no other way to have navigation property and foreign key separately.
If you need foreign key read-only, you can change PersonId property to:
public int PersonId { get { return Person.Id; } }
And use your original mapping
HasRequired(e => e.Person)
.WithOptional(p => p.Employee)
.Map(m => m.MapKey("person_id"));

There's actually a better way to do this:
HasKey(e => e.PersonId);
HasRequired(e => e.Person)
.WithOptional(p => p.Employee);
Note that you no longer need the Employee.Id property with this approach, as it was superfluous in terms of a 1 to 0 or 1 relationship in the first place.

Related

Entity Framework 6 foreign key and target column of different types

recently a sql server GIS database was updated which changed several data types.
I now have table named Trail with a Column named Status of type Numeric(40,0) this is to be a foreign key to Table named Trail_Status with column named Status_Key (int)
here was my attempt.
public class TrailConfiguration : EntityTypeConfiguration<TrailModel>
{
public TrailConfiguration()
{
this.ToTable("PA_TRAILS", "dbo");
this.Property(p => p.StatusKey)
.HasColumnName("STATUS")
.HasColumnType("numeric")
.IsRequired();
this.HasRequired(p => p.Status)
.WithMany()
.HasForeignKey(k => k.StatusKey);
}
}
public class TrailModel
{
[Column(TypeName = "numeric")]
public int StatusKey { get; set; }
public virtual TrailStatusModel Status { get; set; }
}
public TrailStatusConfiguration()
{
this.ToTable("trail_status", "dbo");
this.HasKey(k => k.StatusKey);
this.Property(p => p.StatusKey)
.HasColumnName("status_key")
.HasColumnType("int")
.IsRequired();
this.Property(p => p.Name)
.HasColumnName("name")
.HasColumnType("nvarchar")
.IsRequired();
}
public class TrailStatusModel
{
public int StatusKey { get; set; }
public string Name { get; set; }
}
I receive the following error.
TrailStatusModel_TrailModel: : The types of all properties in the
Dependent Role of a referential constraint must be the same as the
corresponding property types in the Principal Role. The type of
property 'STATUS' on entity 'TrailModel' does not match the type of
property 'status_key' on entity 'TrailStatusModel' in the referential
constraint 'TrailModel_Status'.

EF6 fluent relation with custom foreign key and FK property

I'm trying to migrate existing project to EF and already have strict database structure and mandatory set of properties. The problem I now fight with is on the following.
I have two classes:
public class Entity
{
public virtual long ID {get;set;}
public virtual long ContragentID {get;set;}
public virtual Contragent {get;set;}
}
public class Contragent
{
public virtual long ID {get;set;}
public virtual long EntityID {get;set;}
public virtual Entity {get;set;}
}
With the following entity mappings:
public class ClassContragentAccountMap : EntityTypeConfiguration<Contragent>
{
public ClassContragentAccountMap()
{
// Primary Key
HasKey(t => t.ID);
// Table & Column Mappings
ToTable("contragent");
Property(t => t.ID).HasColumnName("id");
Property(t => t.EntityID).HasColumnName("e_id");
HasOptional(t => t.Entity).WithOptionalDependent(t => t.Contragent);
}
}
public class ClassLegalEntityAccountMap : EntityTypeConfiguration<Entity>
{
public ClassLegalEntityAccountMap()
{
// Primary Key
HasKey(t => t.ID);
// Table & Column Mappings
ToTable("entity");
Property(t => t.ID).HasColumnName("id");
Property(t => t.ContragentID).HasColumnName("contragentid");
}
}
The problem is that relation throws an exception that Entity_ID column is not found. Is there a way to specify FK for the relation as attributes seems to not work here? I've also tried to use:
HasOptional(t => t.Entity).WithOptionalDependent(t => t.Contragent).Map(t=> t.MapKey("e_id"));
But then it conflicts with the already defined 'e_id' property. And I need both relation and the property present.
Is there any way to specify FK for relation and preserve ID properties in classes?
Probably I can go with remaking relation ID properties:
public virtual long ContragentID => Contragent?.ID;
public virtual long EntityID => Entity?.ID;
But I want to know is there any other way to do it.

Entity Framework "A relationship multiplicity constraint violation occurred"

I received this error from Entity Framework this morning:
A relationship multiplicity constraint violation occurred: An EntityReference can have no more than one related object, but the query returned more than one related object. This is a non-recoverable error.
The error refers to a very simple One-To-Many relationship between User and Address
public class User
{
public Guid Id { get; set; }
public virtual IList<Address> Addresses { get; set; }
}
public class Address
{
public Guid Id { get; set; }
public User User { get; set; }
}
I'm using Fluent API to configure the relationship between entities, but in this case the relationship seems to simple that at first I didn't specify any particular rule and I let EF "deduce" the relationship:
public class AddressConfiguration : EntityTypeConfiguration<Address>
{
public AddressConfiguration()
{
HasKey(a => a.Id);
Property(a => a.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
}
}
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
HasKey(u => u.Id);
Property(u => u.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
}
}
But after receiving the error I tried to specify the following rule in the AddressConfiguration:
public class AddressConfiguration : EntityTypeConfiguration<Address>
{
public AddressConfiguration()
{
HasKey(a => a.Id);
Property(a => a.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
HasOptional(x => x.User).WithMany(); // I tried adding this
}
}
After doing that, I tried generating a new automatic migration, and this is what I obtained:
public partial class AddressFixMigration : DbMigration
{
public override void Up()
{
DropForeignKey("dbo.Addresses", "User_Id", "dbo.Users");
AddColumn("dbo.Addresses", "User_Id1", c => c.Guid());
CreateIndex("dbo.Addresses", "User_Id1");
AddForeignKey("dbo.Addresses", "User_Id1", "dbo.Users", "Id");
}
public override void Down()
{
DropForeignKey("dbo.Addresses", "User_Id1", "dbo.Users");
DropIndex("dbo.Addresses", new[] { "User_Id1" });
DropColumn("dbo.Addresses", "User_Id1");
AddForeignKey("dbo.Addresses", "User_Id", "dbo.Users", "Id");
}
}
I found this very odd. The Db seemed ok even before, with the Addresses Table having a Foreign Key "User_Id", but after specifying the one-to-many relationship in the Configuration file EF wants to create a different Foreign Key. Why??
I also tried to specify HasOptional(x => x.User).WithMany().Map(x => x.MapKey("User_Id"));, but in that case when I try to create the automatic migration I receive the following error:
User_Id: Name: Each property name in a type must be unique. Property name 'User_Id' is already defined.
It seems like something is clearly wrong with my DB, but I can't see what and why.
There are other techniques, but I prefer to put the FK in explicitly:
public class Address
{
public Guid Id { get; set; }
public Int? User_Id { get; set; }
public User User { get; set; }
}
Then use this fluent code:
HasOptional(x => x.User).WithMany(x => x.Addresses).HasForeignKey(x => x.User_Id);
I realized that I needed to change my Configuration file as it follows:
public class AddressConfiguration : EntityTypeConfiguration<Address>
{
public AddressConfiguration()
{
HasKey(a => a.Id);
Property(a => a.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).IsRequired();
HasOptional(x => x.User).WithMany(x => x.Addresses).Map(x => x.MapKey("User_Id"));
}
}
.WithMany() (without parameter) is for when there is no navigation property on the other side of the relationship, while in this case I needed to spacify .WithMany(x => x.Addresses) because User actually contains a list of Addresses.

Code-first 1..n mapping error

I have these 2 models:
public class Product
{
public int Id {get; set;}
public int ProductGroupId {get; set;}
}
public class ProductGroup
{
public int Id {get; set;}
public virtual ICollection<Product> Products {get; set;}
}
And the mappings:
public class ProductMap
{
this.ToTable("products");
this.HasKey(t => t.Id).Property(t => t.Id).HasColumnName("id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(t => t.Key).HasColumnName("id_product_group")
.IsRequired();
}
public class ProductGroupMap
{
{
this.ToTable("product_groups");
this.HasKey(t => t.Id).Property(t => t.Id).HasColumnName("id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.HasOptional(t => t.Products)
.WithMany()
.WillCascadeOnDelete();
}
The code compiles but when I start the app, I get the following exception:
Invalid column "Products_Id"
Is the mapping incorrect?
Your foreign key mapping is incorrect.
Replace HasOptional code in ProductGroupMap with:
HasMany(_ => _.Products)
.WithRequired()
.HasForeignKey(_ => _.ProductGroupId);
This should be read as "ProductGroup has a collection of Products, where product must belong to a ProductGroup, and the relation is controlled by a foreign key property Product.ProductGroupId".
HasOptional/HasRequired are intended to use for entities, having required/optional reference navigation property (e.g., Product.CountryOfOrigin), not a collection navigation property.
In other words, 1-* association between entities could be configured from both sides: either from principal entity (ProductGroup in your case), or from dependent entity (Product).
Use HasMany, when configuring from the side of principal entity.
Use HasOptional/HasRequired, when configuring from the side of dependent entity:
HasOptional when the reference to a principal entity is optional;
HasRequired - when reference is required.

Entity Framework: How to specify the name of Foreign Key column on a self-referencing Parent-Child relationship?

I am trying to specify a column name to map a "Foreign Key" to using the Fluent API. I am connecting to an instance of SQL Express. I have searched Stack Overflow and Google, but many of the different configuration examples gave me the same result.
Product Class
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public virtual Product ParentProduct { get; set; }
public virtual ICollection<Product> ChildProducts { get; set; }
}
Product Map to Entity Framework
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap() {
HasKey(p => p.ProductId);
Property(p => p.Name)
.IsRequired();
// Self referencing foreign key association
Property(c => c.ParentId)
.IsOptional();
HasMany(c => c.ChildProducts)
.WithOptional(c => c.ParentProduct)
.HasForeignKey(c => c.ParentId);
}
}
The Issue
The result when I run the program and EF creates the db is that the ParentID column comes out NULL and it creates a column that's called ParentProduct_ProductId. This column contains the correct values for the ParentId.
I'm new to using the Fluent API with EF, so I'm chalking this one up to inexperience. How do I get the auto-generated column to fill the ParentId column instead?
Try this solution:
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap() {
HasKey(p => p.ProductId);
Property(p => p.Name)
.IsRequired();
// Self referencing foreign key association
Property(c => c.ParentId)
.IsOptional();
HasOptional(x => x.Parent)
.WithMany(x => x.ChildProducts)
.HasForeignKey(x => x.ParentId)
.WillCascadeOnDelete(false);
}
}

Categories