EF6 fluent relation with custom foreign key and FK property - c#

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.

Related

Composite key entity and dont want to declare PK keys

OK so this should be simple. I have a class
public class ProductConfig
{
public Category { get;set; }
public Product { get;set; }
}
These two navigation properties are also primary keys for the table.
Declaring PRoductId and CategoryIds are redundat. How can get configure the primary keys using the nav properties?
edit: Stupid me. I forgot something very important in my question above. Those two above are to point out the config. Then we have a third fk thats the selected config for the combination of Product and category. So above entity must be a materialized entity
public class ProductConfig
{
public Category { get;set; }
public Product { get;set; }
public ProductCategoryType { get; set; }
}
Declaring ProductId and CategoryId are redundant. How can get configure the primary keys using the nav properties?
Shortly - you can't. While EF6 supports shadow property based FKs, it does not provide a way to configure the PK (and many other column related settings) using the shadow property names - [Key], [Column]data annotations cannot be applied on navigation property and HasKey fluent API requires primitive property selector expression. In general EF6 does not support shadow properties in PK.
All these limitations have been removed in EF Core. But in EF6, redundant or not, you must define the actual primitive properties in the entity and map them to the composite PK.
You have only to set up a relationship between Product and Category entities by navigation properties. EF will set up the correct table structure by its own as many-to-many relationship. So no own relationship entity is needed.
Please check this out: many-to-many-relationship in EF
e.g.:
Product class:
public class Product
{
// other properties
public virtual ICollection<Category> Categories { get; set; }
}
Category class:
public class Category
{
// other properties
public virtual ICollection<Product> Products { get; set; }
}
Or did I misunderstood your question?
EDIT:
IF you need an separate entity like your ProductConfig, than you should try to set it as a unique index constraint by following:
modelBuilder
.Entity<ProductConfig>()
.HasIndex(pc => new {pc.Category, pc.Product})
.IsUnique();
For further information you should read this: HasIndex - Fluent API
EDIT 2 (after getting info solution is for EF < 6.2 needed):
Well after your last question edit, another solution approach is needed.
Here we go...
You need a structure like followed:
Product
public class Product
{
// other properties
public virtual ICollection<ProductConfig> ProductConfigs { get; set; }
}
Category
public class Category
{
// other properties
public virtual ICollection<ProductConfig> ProductConfigs { get; set; }
}
ProductConfig
public class ProductConfig
{
// other properties
public virtual Category { get; set; }
public virtual Product { get; set; }
public virtual ProductCategoryType { get; set; }
}
To set up a unique constraint in EF < 6.2 you have to do it like that way:
modelBuilder.Entity<ProductConfig>()
.Property(e => e.Category)
.HasColumnAnnotation(
IndexAnnotation.AnnotationName,
new IndexAnnotation(new IndexAttribute("YourIndex", 1) { IsUnique = true }));
modelBuilder.Entity<ProductConfig>()
.Property(e => e.Product)
.HasColumnAnnotation(
IndexAnnotation.AnnotationName,
new IndexAnnotation(new IndexAttribute("YourIndex", 2) { IsUnique = true }));
modelBuilder.Entity<ProductConfig>()
.Property(e => e.ProductCategoryType)
.HasColumnAnnotation(
IndexAnnotation.AnnotationName,
new IndexAnnotation(new IndexAttribute("YourIndex", 3) { IsUnique = true }));
in EF 6.2:
modelBuilder.Entity<Person>()
.HasIndex(p => new { p.Category, p.Product, p.ProductCategoryType })
.IsUnique();
EDIT 3
If you have no primary key in your ProductConfig class or you used mine in the example where I added none, because I thought you already have that class.
It is possible to set up multiple properties as key. That will result in unique combinations too.
You would archive that with the following - instead of the index stuff:
modelBuilder.Entity<ProductConfig>()
.HasKey(pc => new { pc.Category, pc.Product, pc.ProductCategoryType });
For further information check out the MS docs.
You could also add an Id as primary key, than the indexes are needed.

Mapping foreign key in HasOptional().WithOptionalDependent() relation in Entity Framework 6

I have the following data-model in Entity Framework 6.1.3:
using System.Data.Entity;
public class Student
{
public int Id { get; set; }
public virtual Contact Contact { get; set; }
}
public class Contact
{
public int Id { get; set; }
public virtual Student Student { get; set; }
}
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder builder)
{
builder.Entity<Contact>()
.HasOptional(x => x.Student)
.WithOptionalDependent(x => x.Contact)
.WillCascadeOnDelete(true);
}
}
public static class Program
{
private static void Main()
{
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
using (var context = new MyContext())
context.Database.Initialize(force: true);
}
}
When I launch this code, I get exactly the right table structure I am aiming for:
dbo.Contacts
Id (PK)
Student_Id (FK, NULL, CASCADE ON DELETE)
dbo.Students
Id (PK)
However, now I would like to add the Student_Id property to be available in the Contact entity. So I can read the Student_Id without needing to join the other table through .Student.Id navigation.
If I add the property to the Contact entity, I end up either with two columns Student_Id and Student_Id1, or I end up with an error message saying Each property name in a type must be unique..
The column is already in the database, all I need is to have it in the entity as well, why is it so much trouble? Is there a solution?
I managed to get a response from the Entity Framework Program Manager after asking on GitHub.
Unfortunately this is a limitation of EF6. You can not have a foreign key property in a one-to-one relationship, unless it is also the primary key property. This is essentially because EF6 doesn't support alternate keys/unique indexes, so you can't enforce that a non-primary key property is unique. The fact that you can do it when the foreign key property isn't in the entity is a bit of a quirk... but obviously not something we would remove 😄.
BTW alternate keys (and therefore this scenario) is supported in EF Core.
– Rowan Miller #
https://github.com/aspnet/EntityFramework6/issues/159#issuecomment-274889438
If you want to declare the FK property in the dependent entity in an one to one relationship, I'm afraid you must use it as a PK too. EF Code First requires that PK of the dependent entity must be FK of the relationship too:
public class Contact
{
[Key,ForeignKey("Student")]
public int StudentId { get; set; }
public virtual Student Student { get; set; }
}
But I think this is not what you are looking for. So, I think you have three options here:
You preserve your current relationship configuration.
Create an authentic one to one relationship.
Create an one to many relationship
By my experience the last one is the most adjusted to what are you trying to achieve (but that is my opinion). In this case you can work with the Fk property as you want, the only is you need to change the Contact navigation property on Student by a collection (or omit this nav. property and create an unidirectional relationship):
public class Student
{
public int Id { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
}
The configuration would be this way:
builder.Entity<Contact>()
.HasOptional(x => x.Student)
.WithMany(x => x.Contacts)
.HasForeignKey(x => x.StudentId)
.WillCascadeOnDelete(true);
Update
A fourth option could be create two unidirectional relationships:
builder.Entity<Contact>()
.HasOptional(x => x.Student)
.WithMany()
.HasForeignKey(x => x.StudentId)
.WillCascadeOnDelete(true);
builder.Entity<Student>()
.HasOptional(x => x.Contact)
.WithMany()
.HasForeignKey(x => x.ContactId)
.WillCascadeOnDelete(true);
But this option breaks the real relation between the two tables.

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.

One to zero-or-one with HasForeignKey

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.

Code first 2 columns as primarykey/index

So I got a class called ObjectA it has many ObjectsB, the objects got a Id, to identify them but ALSO a language (enum, but it is actually saved as and INT and doesn t matter either for this case).
public class ObjectA
{
public int Id {get;set;}
public Language Language {get;set;}
... // and a list of objectA properties
public virtual ICollection<ObjectB> ObjectBs {get;set;}
}
public class ObjectB
{
public int Id {get;set;}
public Language Language {get;set;}
... // and a list of objectB properties
public ObjectA TheObjectA {get;set;}
}
Now I learned how to map them both as a primary key, I use fluent API for this. I know you can also use [Key] and [Column] attributes (which I don t use):
modelBuilder.Entity<ObjectA>().HasKey(a => new {a.Id, a.Languague})
modelBuilder.Entity<ObjectB>().HasKey(a => new {a.Id, a.Languague})
Now I tried a lot of stuff but I can't seem to be able to connect them with each other. Anyone has an idea how I can fix this?
If the principal (ObjectA) in a one-to-many relationship has a composite primary key the dependent (ObjectB) must have a foreign key that is composed of the same number of columns as well.
I assume that the related objects A and objects B must have the same language. In that case you can make ObjectB.Language the first part of the foreign key. (It's part of the primary and foreign key in ObjectB at the same time.) If you expose the second part of the FK as property in your model, it would look like this:
(I believe you must swap Id and Language for the following to work.)
public class ObjectA
{
public Languague Languague {get;set;}
public int Id {get;set;}
... // and a list of objectA properties
public virtual ICollection<ObjectB> ObjectBs {get;set;}
}
public class ObjectB
{
public Languague Languague {get;set;}
public int Id {get;set;}
... // and a list of objectB properties
public int TheObjectAId {get;set;}
public ObjectA TheObjectA {get;set;}
}
And the mapping with Fluent API:
modelBuilder.Entity<ObjectA>().HasKey(a => new { a.Languague, a.Id })
modelBuilder.Entity<ObjectB>().HasKey(b => new { b.Languague, b.Id })
modelBuilder.Entity<ObjectA>()
.HasMany(a => a.ObjectBs)
.WithRequired(b => b.ObjectA)
.HasForeignKey(b => new { b.Language, b.TheObjectAId });
If the languages can be different introduce a separate FK property for the language:
public Language TheObjectALanguage {get;set;}
public int TheObjectAId {get;set;}
public ObjectA TheObjectA {get;set;}
...and map:
modelBuilder.Entity<ObjectA>()
.HasMany(a => a.ObjectBs)
.WithRequired(b => b.ObjectA)
.HasForeignKey(b => new { b.TheObjectALanguage, b.TheObjectAId });
If the relationship is optional use WithOptional instead of WithRequired and make the FK properties nullable:
public Language? TheObjectALanguage {get;set;}
public int? TheObjectAId {get;set;}
I you don't want to have FK properties in your model you can use MapKey with Fluent API:
modelBuilder.Entity<ObjectA>()
.HasMany(a => a.ObjectBs)
.WithRequired(b => b.ObjectA)
.Map(m => m.MapKey("TheObjectALanguage, TheObjectAId")); // FK columns

Categories