Multiple DB tables for one property in model - c#

I have the following classes:
public class Customer {
public virtual int Id {get; set;}
public virtual string Surname {get; set;}
public virtual string Prename {get; set;}
public virtual Location Location {get; set;}
}
public class Location {
public virtual int Id {get; set;}
public virtual string ZipCode {get; set;}
public virtual string Name {get; set;}
}
public class CustomLocation : Location {
}
And the following mapping:
public class CustomerMapping : ClassMapping<Customer> {
public CustomerMapping(){
Table("Customer");
Property(a => a.Surname, b =>
{
b.NotNullable(true);
});
Property(a => a.Prename, b =>
{
b.NotNullable(true);
});
ManyToOne(a => a.Location, b =>
{
b.Column($"FK_Location_Id");
});
}}
public class LocationMapping : ClassMapping<Location> {
public LocationMapping(){
Table("Location");
Property(a => a.ZipCode, b =>
{
b.NotNullable(true);
});
Property(a => a.Name, b =>
{
b.NotNullable(true);
});
}}
public class CustomLocationMapping : ClassMapping<CustomLocation>{
public CustomLocationMapping(){
Table("CustomLocation");
Property(a => a.ZipCode, b =>
{
b.NotNullable(true);
});
Property(a => a.Name, b =>
{
b.NotNullable(true);
});
}}
My target is that I have a Location table that is being updated automatically by a script and a CustomLocation table where the user can add locations if there are some missing (or outside the country).
My problem is, that I don't know how to properly map this to the Customer that it can be both Location or CustomLocation.
Any ideas? Thanks in advance.

You cannot map one property to two columns in two different tables. How would NHibernate know, which table to use? You would also lose the referential integrity for that column.
However, your goal can be achieved by using a slightly different approach: Instead of using two tables for the same structure, just use one Location-table with the following class representation:
public class Location
{
public virtual int Id { get; set; }
public virtual string ZipCode { get; set; }
public virtual string Name { get; set; }
public virtual bool IsSystem { get; set; } // just use a boolean column in the table for this one
}
Your script can insert / update / delete all the rows where IsSystem == true and ignore the rest. When the user adds an entry, just set IsSystem = false.

Related

Efcore issue with many 2 many on same entity (Products & RelatedProducts)

So i want to add RelatedProducts to my products. So i applied the same relationship type as i did for category. The only difference there is no category class, but we target the same entity. (product). Because the regular many2many works fine, i removed that from my example.
Goal:
Any suggestions?
Exception:
"Exception occured: Cannot create a relationship between 'Product.RelatedProducts' and 'RelatedProduct.Related' because a relationship already exists between 'Product.RelatedProducts' and 'RelatedProduct.Product'. Navigation properties can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation 'RelatedProduct.Related' first in 'OnModelCreating'."
Product.cs:
public class Product : IExportable, IEntityBase
{
public int Id { get; set; }
public string Name { get; set;}
public ICollection<RelatedProduct> RelatedProducts { get; set; }
}
ProductEntityTypeConfiguration.cs
internal class ProductEntityTypeConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
config.HasKey(p => p.Id);
}
}
RelatedProducts.cs
public class RelatedProduct
{
public int Id { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
public int RelatedId { get; set; }
public virtual Product Related { get; set; }
}
RelatedProductEntityTypeConfiguration.cs
public class RelatedProductsEntityConfiguration : IEntityTypeConfiguration<RelatedProduct>
{
public void Configure(EntityTypeBuilder<RelatedProduct> builder)
{
builder.HasKey(rp => rp.Id);
builder.ToTable("RelatedProducts");
builder
.HasOne(rp => rp.Product)
.WithMany(p => p.RelatedProducts)
.HasForeignKey(rp => rp.ProductId)
.OnDelete(DeleteBehavior.Restrict);
builder
.HasOne(rp => rp.Related)
.WithMany(p => p.RelatedProducts)
.HasForeignKey(rp => rp.RelatedId)
.OnDelete(DeleteBehavior.Restrict);
}
}
I am unable to test this suggestion but I am sure it might work. Why don't you just define your RelatedProduct as:
public class RelatedProduct
{
public int Id { get; set; }
public virtual ICollection<Product> Related { get; set; }
}
I also suggest you comment out the code in your RelatedProductsEntityConfiguration Configure method

Entity Framework Core: Many To Many relationship with some null values for some records

Let's say I have the following 3 classes - a Company that employs ConstructionWorkers and TruckDrivers. Let's say those people can be employed at many companies (as they work part time) - so it's a many to many relationship.
Some companies will only employ TruckDrivers, others will only employ ConstructionWorkers and others yet will employ both. This means in my CompanyEmployeeMapping table, the record could look like this:
CompanyEmployeeMapping Table:
+-------------+------------------+------------------------+
| CompanyId | TruckDriverId | ConstructionWorkerId |
+-------------+------------------+------------------------+
| 1 | 10 | NULL |
+-------------+------------------+------------------------+
(ie - Company only employs Truck Drivers)
This is how I set up my Class Structure + Fluent API:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<CompanyEmployeeMapping> Employees { get; set; }
}
public class ConstructionWorker
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<CompanyEmployeeMapping> Companies { get; set; }
}
public class TruckDriver
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<CompanyEmployeeMapping> Companies { get; set; }
}
public class CompanyEmployeeMapping
{
public int CompanyId { get; set; }
public int TruckDriverId { get; set; }
public int ConstructionWorkerId { get; set; }
[JsonIgnore]
public Company LinkedCompany { get; set; }
[JsonIgnore]
public TruckDriver LinkedTruckDriver { get; set; }
[JsonIgnore]
public ConstructionWorker LinkedConstructionWorker { get; set; }
}
Fluent API setup:
builder.Entity<CompanyEmployeeMapping>()
.HasKey(x => new { x.CompanyId, x.TruckDriverId, x.ConstructionWorkerId });
builder.Entity<CompanyEmployeeMapping>()
.HasOne(c => c.LinkedCompany)
.WithMany(m => m.LinkedEmployees)
.HasForeignKey(x => x.CompanyId)
.HasPrincipalKey(x => x.Id);
builder.Entity<CompanyEmployeeMapping>()
.HasOne(c => c.LinkedTruckDriver)
.WithMany(m => m.LinkedCompanies)
.HasForeignKey(x => x.TruckDriverId)
.HasPrincipalKey(x => x.Id);
builder.Entity<CompanyEmployeeMapping>()
.HasOne(c => c.LinkedConstructionWorker)
.WithMany(m => m.LinkedCompanies)
.HasForeignKey(x => x.ConstructionWorkerId)
.HasPrincipalKey(x => x.Id);
This seems to work correctly for me - I get an object with companies, containing a list of TruckDrivers and a list of ConstructionWorkers. Each of those also have their corresponding companies.
QUESTION
When I try to add a new company, which only employs TruckDrivers I get an exception:
The value of CompanyEmployeeMapping.ConstructionWorkerId is unknown when attempting to save changes. This is because the property is also part of a foreign key for which the principal entity in the relationship is not known.
What am I doing wrong here?
It looks like each instance of the mapping entity will only map to either TruckDriver or ConstructionWorker.
Therefore, your TruckDriver and ConstructionWorker ids need to be nullable.
This will then create a new challenge that your composite key cannot contain a nullable property.
I think the way forward is to make those ints nullable and then create a new single property EmployeeMapping Id for the mapping entity key.

EF Core: The INSERT statement conflicted with the FOREIGN KEY constraint

I'm new in Entity Framework Core and I have a problem with the next situation.
I have three objects, like this:
TableA {
public int Id {get; set;}
public string Name {get; set;}
public IList<TableA_TableB> TableA_TableBList {get; set;}
}
TableA_TableB {
public int Id {get; set;}
public TableA TableA {get; set;}
public int TableAId {get; set;}
public TableB TableB {get; set;}
public int TableBId {get; set;}
}
TableB {
public int Id {get; set;}
public string Name {get; set;}
public IList<TableA_TableB> TableA_TableBList {get; set;}
}
I'm using the Fluent Api to map the relationship:
builder
.HasOne(a_b => a_b.TableA)
.WithMany(a => a.TableA_TableBList)
.HasForeignKey(a_b => a_b.TableAId);
builder
.HasOne(a_b => a_b.TableB)
.WithMany(b => b.TableA_TableBList)
.HasForeignKey(a_b => a_b.TableBId);
My problem is when I try to insert a new value in TableA with a new value in TableA_TablesB, but there is an existent value TableB. The code that I used is:
var entity = (TableA)tableADto; //From controller and convert to TableA using explict operator
entity.TableA_TableBList = new List<TableA_TableBList>();
entity.TableA_TableBList.Add(new TableA_TableB()
{
TableAId = entity.Id,
TableBId = 1 // I put a fix value just to test
});
_tableARepository.Add(entity);
_unitOfWork.Commit();
When I call SaveChanges(), this error is shown:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_TableA_TableB_TableB_TableBId".
The conflict occurred in database "tenantteste", table "dbo.TableB", column 'Id".
I have tried to get TableB value and add a navigation property (TableB property). But, instead of creating a relationship, the value was added when I saved.
I have read the documentation and some questions on stackoverflow, and I believe using FK should work, but it didn't work. I don't know what I'm doing wrong.
There are a couple of things wrong. Your linking table should not have a surrogate key, for one. Here's the Many-to-Many sample from the docs:
class MyContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PostTag>()
.HasKey(t => new { t.PostId, t.TagId });
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId);
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId);
}
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class Tag
{
public string TagId { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class PostTag
{
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
https://learn.microsoft.com/en-us/ef/core/modeling/relationships#other-relationship-patterns
Note that the linking entity has a compound key of (PostId,TabId).

Why MapInheritedProperties() maps my entity into two tables?

I'm trying to map my entities following the TPC pattern.
I have an abstract base class Agent :
public abstract class Agent
{
public int Id { get; set; }
public DateTime? ChangeDate { get; set; }
public DateTime CreationDate { get; set; }
public string insuranceCompanyPolicyNumber { get; set; }
public int? KeySys { get; set; }
public int RoleId { get; set; }
public Role Role { get; set; }
public string Status { get; set; }
public int? Transactionid { get; set; }
}
I have a child entity Suscriber :
public partial class Suscriber : Agent
{
public int? id_federateur { get; set; }
public string Number { get; set; }
public bool? suivi_client { get; set; }
public virtual ICollection<Contract> Contracts { get; set; }
}
When i try to configure the Suscriber like the following, i've noticed that the request generated by Entity Framework is wrong :
public class SuscriberConfiguration : EntityTypeConfiguration<Suscriber>
{
public SuscriberConfiguration()
{
Map(m =>
{
m.MapInheritedProperties();
m.Property(s => s.Number).HasColumnName("numero_souscripteur");
// Property(s => s.Number).HasColumnName("numero_souscripteur");
m.ToTable("T_ACTEUR_SOUSCRIPTEUR");
});
}
}
The request generated by Entity Framework is (The table Agent2 doesn't exist):
SELECT E'0X0X' AS "C1",
"Extent1"."id_personne",
"Extent1"."suivi_client",
"Extent1"."id_federateur",
"Extent2"."date_mod",
"Extent2"."date_cre",
"Extent2"."insuranceCompanyPolicyNumber",
"Extent2"."keySys",
"Extent2"."id_role",
"Extent2"."statut",
"Extent2"."vsc_transactionid",
"Extent2"."numero_souscripteur"
FROM "atlas"."Agent2" AS "Extent1"
INNER JOIN "atlas"."T_ACTEUR_SOUSCRIPTEUR" AS "Extent2" ON
"Extent1"."id_personne" = "Extent2"."id_personne"
I know that if i changed the configuration of suscriber like this, it will work
public class SuscriberConfiguration : EntityTypeConfiguration<Suscriber>
{
public SuscriberConfiguration()
{
Map(m =>
{
m.MapInheritedProperties();
// m.Property(s => s.Number).HasColumnName("numero_souscripteur");
Property(s => s.Number).HasColumnName("numero_souscripteur");
m.ToTable("T_ACTEUR_SOUSCRIPTEUR");
});
}
}
In other words, if i configure the property Number outside the scope of the method Map or i don't apply the method Property to the parameter of the lambda, it works. (Property(s => s.Number).HasColumnName("numero_souscripteur");)
Could somebody explain me how the method MapInheritedProperties() works?
Why Entity Framework generate a non existing table?
Thanks
This is because everything that you put in one mapping fragment...
Map(m => { ... })
...is mapped to the table that the fragment applies to. In fact it's saying: map all properties from Agent and property Number from Subsriber to table "T_ACTEUR_SOUSCRIPTEUR". EF silently assumes that the leftovers are mapped to another table it devices by naming conventions. This happens as soon as you map one property from the derived type. If you only use MapInheritedProperties(), all properties, including the derived ones, are mapped to the table in the mapping fragment.
I don't know if this is a bug or a feature. It's not very clear at least. Anyway, you should map other property names outside the mapping fragment, as you already found out. Of course it's it more clear if you do that in a separate statement:
Map(m =>
{
m.MapInheritedProperties();
m.ToTable("T_ACTEUR_SOUSCRIPTEUR");
});
Property(s => s.Number).HasColumnName("numero_souscripteur");
Or if you like:
Map(m => m.MapInheritedProperties());
ToTable("T_ACTEUR_SOUSCRIPTEUR");
Property(s => s.Number).HasColumnName("numero_souscripteur");

Avoid circular dependency in Entity Framework Code First

Assume I have two entities: Nationality and Employee with relationship 1:n.
public class Employee
{
public int Id { get; set; }
// More Properties
public virtual Nationality Nationality { get; set; }
}
public class Nationality
{
public int Id { get; set; }
public string Name { get; set; }
}
In order to use Code-First with Entity Framework, I have to add one more property: Employees which I don't expect into Nationality (it creates circular dependency):
public class Nationality
{
public int Id { get; set; }
public string Name { get; set; }
// How to avoid this property
public virtual List<Employee> Employees { get; set; }
}
So that I can configure the relationship 1: n in Configuration class:
internal class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
HasKey(f => f.Id);
HasRequired(e => e.Nationality)
.WithMany(e => e.Employees)
.Map(c => c.MapKey("NationalityId"));
ToTable("Employees");
}
}
Is there any other approach which I can avoid circular dependency and eliminate property Employees out of Nationality class?
Use the WithMany() overload to configure the mapping.
internal class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
HasKey(f => f.Id);
HasRequired(e => e.Nationality)
.WithMany()
.Map(c => c.MapKey("NationalityId"));
ToTable("Employees");
}
}

Categories