Mapping unusual table relationships - c#

I wondered if anyone can advise me on how to resolve a problem with regards to using FluentAPI to map a couple of tables.
I have Parent table that has our key called ID
Then a Child table with two fields idA & idB.
The primary key in the parent table links to either idA or idB, not both.
public Parent()
{
this.ChildA = new HashSet<Child>();
this.ChildA = new HashSet<Child>();
}
public virtual ICollection<Child> ChildA { get; set; }
public virtual ICollection<Child> ChildB{ get; set; }
}
public Child()
public virtual Parent parent { get; set; }
}
There is much I can do about the relationship/table design because it is legacy and cannot be changed. Just need to understand the correct FluentAPI to use to account for this issue. Above example it what I envisaged would be needed along with something like...
modelBuilder.Entity<Child>().HasRequired<Parent>(p => p.parent).WithMany(q => q.childs).HasForeignKey(r => r.idA);
modelBuilder.Entity<Child>().HasRequired<Parent>(p => p.parent).WithMany(q => q.childs).HasForeignKey(r => r.idB);

I believe I was able to get the correct mapping you are looking for. I added naviation properties to the POCO which allows Entity Framework to know how to use the foreign keys in code.
public class Child
{
public int Id { get; set; }
public virtual Parent ParentA { get; set; }
public virtual Parent ParentB { get; set; }
public Child() { }
}
To map these navigation properties to you already existing foreign key columns, I used the FluentAPI Map method.
modelBuilder.Entity<Child>().HasRequired<Parent>(p => p.ParentA).WithMany(q => q.ChildA).Map(m => m.MapKey("idA")).WillCascadeOnDelete(false);
modelBuilder.Entity<Child>().HasRequired<Parent>(p => p.ParentB).WithMany(q => q.ChildB).Map(m => m.MapKey("idB")).WillCascadeOnDelete(false);
With this, I have indicated ParentA populates the ChildAcollection, and ParentB populates the ChildB collection. The Map method is what allows me to map to your already existing FKs, and I don't have to include them with the POCO as a property.
Note that each POCO that maps to a table must have a primary key. Does your already existing child table have a PK? If not, you may have some further trouble. I recommend reading this SO post about it. Entity Framework: table without primary key

Related

EF Core - how to model relation of Grandparent - Parent - Child on same model

Imagine a model of User that can have Parents and also can have Children.
How would you model such a case in EF Core?
I tried with something like that (pseudo-code)
public class User
{
public ICollection<Relation> Relations {get;set;}
public ICollection<User> Parents => Relation.Where(r => r.Relation == 'Parents')
public ICollection<User> Children => Relation.Where(r => r.Relation == 'Children')
}
public class Relaction
{
public User User1 {get;set;}
public Guid User1Id {get;set;}
public User User2 {get;set;}
public Guid User2Id {get;set;}
public Relation Relation {get;set;} //some enum or sth to indicate relation type
}
But in such modeling, I'm not able to force EF DbContext to fetch into User.Relations data where UserId is in User1Id and in User2Id.
Any idea?
What you are asking for is a classic many-to-many self relationship - (1) user as parent can have many users as children, and (2) user as child can have many users as parents.
Thus it is modelled with one main entity and one join (linking) entity similar to what you have shown. The linking entity does not need special indicator because the two FKs determine the role. i.e. lets change your example with more descriptive names:
public class User
{
public Guid Id { get; set; }
}
public class UserRelation
{
public User Parent { get; set; }
public User Child { get; set; }
public Guid ParentId { get; set; }
public Guid ChildId { get; set; }
}
Now, in pseudo code, given User user, then
user.Parents = db.Users.Where(u => user == u.Child)
user.Children = db.Users.Where(u => user == u.Parent)
EF Core 5.0+ allows you to hide the join entity (it still exists, but is maintained implicitly) and model the relationship with the so called skip navigations, which are the natural OO way of representing such relationship, e.g. the model becomes simply
public class User
{
public Guid Id { get; }
public ICollection<User> Parents { get; set; }
public ICollection<User> Children { get; set; }
}
This is all needed to create such relationship.
However the name of the join table and its columns by convention won't be what normally you would do - in this case, they would be "UserUser" table with "ParentsId" and "ChildrenId" columns.
If you use migrations and don't care about the names, then you are done and can safely skip the rest.
If you do care though, luckily EF Core allows you to change the defaults with fluent configuration (even though in a not so intuitive way):
modelBuilder.Entity<User>()
.HasMany(e => e.Parents)
.WithMany(e => e.Children)
.UsingEntity<Dictionary<string, object>>("UserRelation",
je => je.HasOne<User>().WithMany()
.HasForeignKey("ParentId").IsRequired().OnDelete(DeleteBehavior.Restrict),
je => je.HasOne<User>().WithMany()
.HasForeignKey("ChildId").IsRequired().OnDelete(DeleteBehavior.Restrict),
je => je.ToTable("UserRelations")
.HasKey("ParentId", "ChildId")
);
Here Dictionary<string, object> is the shared type EF Core will use to maintain the join entity in memory (change tracker). And is the most annoying thing in the above configuration since in a future they might change their minds and use different type (there are actually plans to do that in EF Core 6.0), so you'll have to update your mappings. Note that this does not affect the database design, just the memory storage type in EF Core change tracker.
So, because of that and the fact that in some scenarios it is better to work directly with the join entity, you could actually combine both approaches (explicit join entity and skip navigations) and get the best of both worlds.
To do than, you add the explicit entity and (optionally) navigations from/to it. The next is w/o collection navigations from User to UserRelation (with fully defined navigation you would need two ICollection<UserRelation> properties there):
public class User
{
public Guid Id { get; }
public ICollection<User> Parents { get; set; }
public ICollection<User> Children { get; set; }
}
public class UserRelation
{
public User Parent { get; set; }
public User Child { get; set; }
public Guid ParentId { get; set; }
public Guid ChildId { get; set; }
}
and required minimal fluent configuration
modelBuilder.Entity<User>()
.HasMany(e => e.Parents)
.WithMany(e => e.Children)
.UsingEntity<UserRelation>(
je => je.HasOne(e => e.Parent).WithMany(), // <-- here you would specify the corresponding collection nav property when exists
je => je.HasOne(e => e.Child).WithMany(), // <-- here you would specify the corresponding collection nav property when exists
je => je.ToTable("UserRelations")
);
The result is the same database model, but with different in-memory representation of the join entity and ability to query/manipulate it directly. Actually you can do the same with implicit entity, but in type unsafe way using sting names and object values which need to be cast to the appropriate type. This probably will improve in the future if they replace Dictionary<string, object> with some generic type, but for now explicit entity combined with skip navigations looks the best.
You can find (I guess better than mine) explanation of all this in the official EF Core documentation - Many-to-many and the whole Relationships section in general.

EntityFramework Include Without Guarantee of Existence

I have an issue I am unsure how to solve. I have three models, similar to as follows.
public class Parent : BaseEntity {
[Key]
public string Guid { get; set; }
[NotMapped]
public List<Child> Childs { get; set; }
}
public class Child : BaseEntity {
[Key]
public string Guid { get; set; }
public string ParentGuid { get; set; }
public List<Detail> Details { get; set; }
}
public class Detail : BaseEntity {
[Key]
public string Guid { get; set; }
[ForeignKey(nameof(Child))]
public string ChildGuid { get; set; }
public Child Child { get; set }
}
I'm attempting to include both the children and the details. However, I am not guaranteed the Guid on the child exists in the parent table. That's why I went with NotMapped, but am willing and able to change that if need be. Right now I have this:
query.GroupJoin(context.Parents,
parent => parent.ChildGuid,
child => child.Guid,
(parent, childs) => new
{
Parent = parent,
Childs = childs
}
)
.AsEnumerable()
.Select(combos =>
{
combos.Parent.Childs = combos.Childs.ToList();
return combos.Parent;
})
.AsQueryable();
But that, of course, does not include the Details. Not sure if I'm heading the right direction here or not, but could use some direction if anyone has run into this before.
A typical parent-child relationship has the ParentID on the child table/entity. Your entity definitions seem to reflect that, but then your Linq expression refers to some parent.ChildGuid that isn't mentioned in your entity.
First let's correct your entity definition relationships. Childs shouldn't be excluded. A parent can have 0 or multiple children. Collections should be declared as virtual ICollection<T> rather than concrete List<T>. virtual enables lazy loading and helps ensure that EF proxies are fully functional for tracking changes of items in the collections. We should also initialize those collections with a new concrete list. This helps ensure that any new entity we create is ready to go accepting children.
public class Parent : BaseEntity
{
[Key]
public string Guid { get; set; }
public virtual ICollection<Child> Childs { get; set; } = new List<Child>();
}
public class Child : BaseEntity
{
[Key]
public string Guid { get; set; }
public string ParentGuid { get; set; }
public virtual ICollection<Detail> Details { get; set; } = new List<Detail>();
}
public class Detail : BaseEntity
{
[Key]
public string Guid { get; set; }
[ForeignKey(nameof(Child))]
public string ChildGuid { get; set; }
public virtual Child Child { get; set }
}
EF can automatically map relationships provided you follow supported conventions in your naming. Chances are with the "Guid" syntax for your ID columns, EF won't work these out automatically so you will probably need to give it some help through configuration. For example in the OnModelCreating of the DbContext:
// EF Core
modelBuilder.Entity<Parent>()
.HasMany(x => x.Childs)
.WithOne()
.IsRequired() // If a Child must have a ParentGuid
.HasForeignKey(x => x.ParentGuid);
Since Child has a ParentGuid but no Parent reference declared, we use the WithOne() with no mapping, then use the HasForeignKey to set up the relationship. Similarly we set up the relationship between Child and Details:
// EF Core
modelBuilder.Entity<Child>()
.HasMany(x => x.Details)
.WithOne(x => x.Child)
.IsRequired() // If a Detail must have a Child
.HasForeignKey(x => x.ChildGuid);
In this case since we have a Child reference on Detail so we map to that.
Now regarding this statement:
However, I am not guaranteed the Guid on the child exists in the parent table.
From this it somewhat implies that a ParentGuid on the Child table might not exist in the Parents table. This would be a fairly serious concern from a database normalization and ensuring a valid data state. I would highly recommend to avoid trying to use a non-normalized schema such as attempting to "share" a Child table between multiple Parent tables. A classic example where this is tried is having an Address table (Child) referencing a ParentId that could point at a Customer (Parent) or Site (Parent) and/or other parent-like tables. Instead, you should consider a more normalized approach which would use a CustomerAddress and SiteAddress etc. linking table to ensure these relationships can be mapped out.
If a Child can exist without a Parent then you just need to remove the .IsRequired().
Now when querying, you don't need to worry specifically about joining and grouping, just query through the navigation properties and either eager-load the related data you want when working with the Entity graph, or project the data using Select when querying for details:
var query = context.Parents
.Include(x => x.Childs)
.ThenInclude(x => x.Details)
To get the children with their associated Parent:
var query = context.Parents
.Include(x => x.Childs)
.ThenInclude(x => x.Details)
.SelectMany(x => x.Childs.Select(c => new {Parent = x, Child = c})
.ToList();
If you want to include children that have no parent:
var query = context.Parents
.Include(x => x.Childs)
.ThenInclude(x => x.Details)
.SelectMany(x => x.Childs.Select(c => new {Parent = x, Child = c})
.Union(context.Childs.Where(x => x.ParentGuid == null))
.ToList();
These are very rough guesses as the type of query you want to perform. Again, if the ParentGuid could be referring to a non-existent row in the DB or a different table I would really look at correcting that to ensure the data maintains referential integrity. I don't recommend trying to "break" EF behavior to work with an effectively broken schema. You may get it to have a semblance of working, but it could very easily lead to exceptions or unexpected behaviour.

How to map child entities to same table in EF Core

I am using EF Core and I need to map child entities to the same table, because they are of the same type.
Imagine the following scenario of class hierarchy:
public class Grandpa
{
public Guid Id { get; set; }
public Father Robert { get; set; }
public Father Jack { get; set; }
}
public class Father
{
public Guid Id { get; set; }
// I use this to reference Grandpa
public Guid GrandpaId { get; set; }
public List<Son> MySons { get; set; }
}
public class Son
{
public Guid Id { get; set; }
// I use this to reference Father
public Guid FatherId { get; set; }
public string Name { get; set; }
}
Now the issue is the following: in my scenario (the above is the abstraction replicating my real scenario), I cannot make Robert and Jack an array - they need to be there separately. The other thing is, the Grandpa entity owns 2 Father entities, and each Father entity owns a list of Son entities.
I want to tell ModelBuilder to generate my tables in such a way that there is ONE TABLE for all Father entities, and ONE TABLE for all Son entities - because they are all the same entities, and they should never be placed in different tables as their primary keys and foreign keys (IDs) clearly reference who they belong to.
Now, you know, in plain SQL, it's easy to achieve such a structure, very simple to write the SQL code for this. However, how do I do it with EF Core?
I've tried:
1) Specifying all primary keys and foreign keys in model builder, per entity
2) Setup the owned property hierarchy in ModelBuilder using
modelBuilder.Entity<Father>()
.ToTable("Fathers");
modelBuilder.Entity<Son>()
.ToTable("Sons");
modelBuilder.Entity<Grandpa>(g =>
{
x.OwnsOne(y => y.Robert, z =>
{
z.OwnsMany(a => a.MySons, b =>
{
b.HasKey(k=>k.Id);
b.ToTable("Sons");
});
z.ToTable("Fathers");
});
x.OwnsOne(y => y.Jack, z =>
{
z.OwnsMany(a => a.MySons, b =>
{
b.HasKey(k=>k.Id);
b.ToTable("Sons");
});
z.ToTable("Fathers");
});
});
However, there are errors saying:
Cannot use table 'Fathers' for entity type 'Grandpa.Father#Robert' since it is being used for entity type 'Grandpa.Father#Jack' and there is no relationship between their primary keys.
I must mention that these entities are part of the same DbContext - and they need to remain so, preferably.
The way EF Core maps or fails to map entities is wrong - if I remove the table name specifications, it just creates separate tables for my entities of the same type, which is wrong. I am not even in the case of having the same entity declared in a different namespace/duplicated, but it's actually the same namespace, the same entity, the Grandpa entity has two Father entities, and each Father entity has a List<Son>.

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.

Nhibernate : Updating a child entity directly without inverse

I'm using fluent nhibernate to map a parent child relationship to the sql database.
Most of the times i let the parent entity save it's child entities,they are inserted in 1 transaction and if i'm not mistaken this wouldn't be possible if i used .Inverse() and sql identity columns.
The problem i have is that in 1 specific case i want to update the child entity and ONLY the child entity.
When i do this with my current setup the child record will lose the relationship to it's parent(if the parent object is null) or will replace my parent object completely(if i insert a dummy parent with just an id).
Does anyone know a way to achieve a single record update without affecting the foreign key?
I can think of a manual sql statement or a stored procedure but i'm hoping there is an nhibernate way.
I have the following setup (simplified for your convenience) :
public ProjectMap()
{
Table("Project");
Id(p=> p.Id);
HasMany(p => p.Risks).Not.LazyLoad();
}
public RiskMap()
{
Table("ProjectRisk");
Id(r=> r.Id);
References(r => r.Project).Column("ProjectId");
Map(r => r.Description);
}
public class Project
{
public virtual int Id { get; set; }
public virtual IList<Risk> Risks { get; set; }
}
public class Risk
{
public virtual int Id { get; set; }
public virtual string Description{ get; set; }
public virtual Project Project { get; set; }
}
As Miroslav suggested i'm using an HQL update statement now.
It's a little messy for my taste but it gets the job done.
Session.CreateQuery("update Risk set Closed = :completed where Id = :id")
.SetInt32("id", id)
.SetBoolean("completed", completed)
.ExecuteUpdate();

Categories