Entity Framework 6 Code First - Comments on a Tree Structure - c#

I'm attempting to implement heterogeneous association in my Data Model (Entity Framework 6, Code-First approach).
I have an existing structure of classes, let us call them Tree, Branch and Leaf. A Tree may have many Branch objects, and a Branch may hold many Leaf objects. The relationships between the three levels have a cascade-delete behavior (delete a branch and you also delete the leaves, etc.).
Now I'm trying to let users add a comment-like object on each of those levels. I had a few problems related to data-modelling, as I want each of the 3 entity types to be able to have many comments and each comment to belong to one and only one entry. I'd also like for all comments to be in the same table. I've tried two different approaches:
Alt. 1
Implement inheritance so that the Comment (abstract) can be a TreeComment, BranchComment or LeafComment, following the Table per Hierarchy (TPH) approach (as seen, for example, here) of having an abstract class (Comment) for comments and then derive it to TreeComment, BranchComment, etc. That is achieved by coding the models like this:
public abstract class Comment
{
// ID
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
}
public class TreeComment: Comment
{
// Foreign Keys
public Guid TreeID { get; set; }
// Navigation Properties
public virtual Tree Tree { get; set; }
}
(... BranchComment and LeafComment ...)
(... add virtual ICollection<TreeComment> to Tree, virtual ICollection<BranchComment> to Branch, etc.)
...which can be expressed with this diagram:
The problem with this approach is that the relationship between the Comment table and the other 3 doesn't have ON DELETE CASCADE or ON DELETE SET NULL set. If I try to change that to more than one table, I get a:
Introducing FOREIGN KEY constraint 'FK_Comment_Branch_BranchID' on
table 'Comment' may cause cycles or multiple cascade paths. Specify ON
DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY
constraints.
I understand that this is because SQL Server "doesn't know" that only one of the FK's in the Comment table is supposed to be used at any time.
Alt. 2
Generalize the Tree/Branch/Leaf trio into a CommentableEntity using the Table per Type (TPT) approach and connect the Comment table to that abstract one. This can be achieved by implementing inheritance in the model classes (just like I did before) and adding the annotations [Table("Tree")], [Table("Branch")] and [Table("Leaf")] to each of the subclasses to make sure we get a table for each (and not a single table like in TPH). The Model, then looks like this:
This approach has two problems:
Deleting a concrete object (e.g. a branch) will not delete the base entry in the abstract table, leaving "garbage" (abstract entities and their comments) behind.
The FK relationship between the abstract and concrete classes lacks a cascade delete. So I can't really delete the base object. If I try to add one I get another complaint on how introducing such rule would cause cycles of multiple cascade paths.
I've also tried using DB triggers (CREATE TRIGGER ... INSTEAD OF DELETE...) on both approaches but they seem to be a big no-no as EF can't track the changes done by them.
This is frustrating and I'm sure this (comments on a tree structure) is a very typical scenario in Web development; but I can't seem to find a way to allow it. I'm looking for all advice I can get on how to effectively model these relationships (EF 6 Code First) without placing too much weight on the Business Logic layer.
EDIT:
I believe this is what user #Deepak Sharma mentioned in his comment: TPH inheritance in the node classes. If so, this also doesn't work for the same reason: cycles of multiple cascade paths.

Ok, so here's how I'm currently solving the problem:
I chose the second alternative - generalize the Tree/Branch/Leaf trio (let's call these "nodes" for simplification) into a CommentableEntity (the base class) using the TPT approach - as seen above. I end up with one table for each of the three node classes + one base class that holds the relationship to a Comment table.
Then, in my InitializeDatabase(MuDbContext context), I added one Stored Procedure and Trigger for each of the three tables to the database using the context.Database.ExecuteSqlCommand() method.
1) The Stored Procedure has to be mapped in EF like this:
this.MapToStoredProcedures(s => s.Delete(d => d.HasName("TriggerName").Parameter(b => b.ID, "parameter_name")));
... for each of the three models and basically is a replacement for the default delete. In my case, I wrote it so that first it deletes the actual node in its table (Tree/Branch/Leaf) and then the corresponding base object (CommentableEntity).
2) The Trigger fires after a node is deleted and makes sure that the corresponding base object is also deleted.
In case you're wondering why do I have such redundancy (a Trigger and a Stored Proc. that do almost the same thing) it's because whenever a node is deleted (say, a tree), EF calls its Stored Proc. in order to delete it. Then, the nested nodes (the tree's branches) are deleted via the DB's cascade-delete, which doesn't delete the base objects, and not via the Stored Proc.. Thus, the Trigger. On the other hand, if I only had the trigger (no Stored Proc.), EF would freak out after the deletion because it wouldn't be able to track its changes.
I could, of course, just change each of the Stored Proc. for each of the tables so that they also delete all of the nested objects as well and remove the cascade-delete setting. But the current solution seems to be working and good enough for me.
I will test this out and delete this answer if I find out that this doesn't actually work. If you see any disadvantages in this approach (and know how to avoid them) please leave a comment.

Possibly the answer is to augment Alt 1 by declaring some rules within the OnModelCreating Method. Also this assumes that the the Tree, Branch and Leaf classes have a Comments collection against them.
Within your DbContext you could do the following ...
public class YourDbContext : DbContext
{
... your DbSet properties
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Tree>()
.HasMany<TreeComment>(o => o.Comments)
.WithRequired(com => com.Tree)
.HasForeignKey(com => ds.TreeID)
.WillCascadeOnDelete(false);
}
}

Related

Orchard model inheritance with Table-Per-Concrete-Type?

I know that the underlying ORM used in Orchard is NHibernate and it does support the so-called ClassMapping which may help customize the mappings the way we want.
However I'm not sure about how Orchard utilizes the mapping methods supported by NHibernate. In this case it seems to always use the strategy similar to Table Per Type in EF as well as some other ORMs. With that strategy, the base class will be mapped to some common table whereas the derived class will be mapped to another table which contains all properties of its own (not declared in the base class). Those 2 tables will have a one-one relationship.
Now I really want to make it use the strategy similar to Table Per Concrete Type in which the base and derived classes will be mapped to 2 different tables with all properties (including inherited properties) being mapped to columns. Those 2 tables will not have any relationship, so querying for columns in just one table will not unexpectedly generate an inner JOIN (for one-one relationship).
Actually that requirement makes sense in case we just need to partition our data (from 1 big table to 2 or more small tables that have the same schema). We don't want to re-declare or use some kind of duplicate model classes (with different names), instead we just need to create a new model class and let it inherit from one base model class containing all necessary properties.
With the current code like this:
public class ARecord {
//properties ...
}
public class BRecord : ARecord {
//empty here
}
Currently we cannot use BRecord because it is understood as another part of the ARecord, the auto-generated query (always with INNER JOIN) will be failed because of some does-not-exist table or column names.
How can I solve this?
You're not going to like it ;) In a nutshell, the answer is don't do inheritance at all. Orchard was very deliberately designed around the idea of composition, steering well clear of inheritance in its content models. Maybe the central idea in Orchard is to make the concept of content part the "atom of content", and to design those basic units as very simple and composable pieces of functionality that do one thing well.
After all these years, the concept has held remarkably well, and I've yet to see an example of a content model where inheritance would have been more elegant and appropriate. This is reflected in the way nHibernate is customized and used in Orchard, as you've discovered.
So the solution to your problem can likely be one of two things:
You're modeling contents, and you should re-think your approach towards composition of parts. If you give a little more details about your specific scenario (maybe in a new question), I'm happy to help specifically in this direction.
You're modeling non-content data, in which case you might want to consider opting out of Orchard's specific nHibernate content-specialized idiosyncrasies and do things closer to the metal. Again, if you give more specifics about your scenario, I'm happy to take a look and give some pointers.

EFCore - How to exclude owned objects from automatic loading?

I'm trying to wrap my head around EF Cores owned objects and how i can control when to load certain chunks of data.
Basically i'm having a bunch of old legacy tables (some with ~150 columns) and want to model them using a root entity and several owned objects per table to achieve better segmentation and bundle certain functionalities. Example: There is an "article" entity containing ~20 properties for the most important fields of the underlying table. That entity also contains an OwnedObject "StorageDetails" wrapping a dozen more fields (and all the functions concerned with storing stuff).
Problem: I can't find a way to control if an owned object should be loaded immediatly or not. For some of them i would prefer to load them explicitly using Include() ...
public class Article : EntityBase
{
public string ArticleNumber { get;set; }
// Owned object, shares article number as key.
public StorageDetails StorageStuff { get; set; }
// An Entity from another table having a foreign key reference
public SomeOtherEntity OtherStuff { get; set; }
}
public class StorageDetails : OwnedObject<Article>
{
public Article Owner { get; set; }
}
// Somewhere during model creation ...
builder.OwnsOne(article => article.StorageStuff);
builder.HasOne(article => article.OtherStuff )
...
Defining the model with OwnsOne and loading an article immediatly loads the StorageStuff. To load the OtherThing i have to Inlcude() it in a query, which is basically what i want to achieve for the owned object.
Is that possible? If not, what other approach could you point me to?
Update: I wasn't exact right - the implicit eager loading of owned entity types is really a default and can be changed via SetIsEagerLoaded metadata API (EF Core 3.0+)
modelBuilder.Entity<Article>().OwnsOne(e => e.StorageStuff)
.Metadata.PrincipalToDependent?.SetIsEagerLoaded(false);
or AutoInclude fluent API (EF Core 5.0+)
modelBuilder.Entity<Article>().Navigation(e => e.StorageStuff)
.AutoInclude(false);
This allows eager loading them via Include / ThenInclude. But due to EF Core implementation specifics, owned entity types cannot use explicit/lazy loading. Trying to do so leads to runtime exception. So my recommendation from the original answer still applies.
Original:
With owned types - it's not possible (currently), because this behavior is "by design". And is documented in Querying owned types section of the EF Core documentation:
When querying the owner the owned types will be included by default. It is not necessary to use the Include method, even if the owned types are stored in a separate table.
It's a bit vague by saying "by default", but you can safely read it as "always", because there is no option or Exclude method.
Since currently the only way to control loading related data is navigation property to a real entity, make the types you want to control a "real entities", i.e. don't mark them as owned, define explicit or shadow PK, and map these "entities" with Table Splitting:
It is now possible to map two or more entity types to the same table where the primary key column(s) will be shared and each row will correspond to two or more entities.
To use table splitting an identifying relationship (where foreign key properties form the primary key) must be configured between all of the entity types sharing the table:

EF DB-first mapping mess

I have a frustrating situation owing to this little quirk of EF. Here's a simple demo of the behavior. First the DB schema:
As you see, RestrictedProduct is a special case of product, which I'm intending to make a subclass of Product with some special code.
Now I import to an EF data model:
Oops! EF saw that RestrictedProduct had only 2 fields, both FKs, so it mapped it as a one-to-many relationship between Product and Restriction. So I go back to the database and add a Dummy field to RestrictedProduct, and now my EF model looks much better:
But that Dummy field is silly and pointless. Maybe I could delete it? I blow away the field from the DB table and the entity model, then refresh the model from the DB...
Oh, no! The Product-Restriction association is back, under a new name (RestrictedProduct1)! Plus, it won't compile:
Error 3034: Problem in mapping fragments starting at lines (x, y) :Two entities with possibly different keys are mapped to the same row. Ensure these two mapping fragments map both ends of the AssociationSet to the corresponding columns.
Is there any way to prevent this behavior, short of keeping the Dummy field on the RestrictedProduct table?
I just came across the same issue, and as an alternative to putting the dummy field in your RestrictedProduct table to force the creation of an entity you can also make your RestrictedProduct.RestrictionId field nullable and EF will then generate an entity for it. You can then modify it to use inheritance and any subsequent "Update model from database" will not cause undesired nav properties. Not really a nice solution but a work around.
Let's walk slowly into your problem.
1st thing you need to decide is if the restricted product is
really a special case of product or is it a possible extension
to each product.
From your original DB Scheme it seems that any product may have
a relation to a single restriction however a single restriction
can be shared among many products.. so this is a simple 1 to many
situation which means that restricted product is NOT a special case
of product! Restriction is an independent entity which has nothing
to do with product in a specific way.
Therefore EF is correct in the 1st importation of your scheme:
1. a product can have 0 or 1 restrictions.
2. a restriction is another entity which can be related to many products.
I do not see your problem.

Map Multiple Tables to One Table Produces Foreign Key Column for each in Fluent NHibernate

I want to Point 3 tables Property, Business, Automobile to single Table named Utility.
Using Fluent NHibernate, I followed
public UtilityMap()
{
// some Mappings
References<Automobile>(x => x.Automobile, "LeaseRefId").Cascade.None();
References<Business>(x => x.Business, "LeaseRefId").Cascade.None();
References<Property>(x => x.Property, "LeaseRefId").Cascade.None();
}
and in Each table that maps to the Utility, I followed
public AutomobileMap()
{
//Some Mappings
HasOne<Utility>(x => x.CommonDatas)
.Cascade.All();
}
"I want to have a single column in Utility Table that can store the
references of all the three (Automobile, Business, Property) tables.
But fluent Nhibernate is creating foreing key columns for each of the
table referenced. I want to avoid this."
Is there any way to achieve this??
Please suggest
Thanks in Advance.
For this to work as you expect, you'll need to have a base class for Automobile, Business and Property classes. Let's call it LeaseSubject. Presumably, you already have something like that in your domain model.
Your Utility class should have only one property, instead of three you have now:
public virtual LeaseSubject LeaseSubject { get; set; }
This way you not only have easier mapping, but also promote the maintainability of your code. What if at some later time you decide to have one more lease subject, i.e. Yacht? Instead of adding another Yacht property to your Utility class, you just need to inherit from LeaseSubject, and it could be contained within the LeaseSubject property on Utility class.
Now you could map your Utility simply as:
public UtilityMap()
{
// Other mappings...
References(x => x.LeaseSubject, "LeaseRefId");
}
For mapping Automobile, Business and Property classes, you would use one of three available NHibernate inheritance strategies:
table per class hierarchy
table per subclass
table per concrete class
For FluentNHibernate inheritance mapping, take a look here.
Since you already have separate tables, I believe the correct strategy for your case would be Table per concrete class. You can find some more info in this article.

Many to Many (self related) specific order entity framework

Hello I'm trying to do the impossible apparently.
I need a self referenced table with a many to many relationship to itself that also has a specific order in c# entity framework (4.2) database first.
Think of it like Friends having Friends in which they order their friendship > Best Friend to Worst Friend.
Is there anyway to do this without using the "FriendToFriend" relationship entity? I would like to be able to use Friend.Friends (removing the order column creates it), but I would have a default order based on their friendshipOrder. My work around is looking like extending the generated classes to have a new property for Friends in order.
Any one else have any better ideas?
Entity framework does not support ordered collections. This is one of many situations where EF shows its immaturity.
Try nHibernate if it is a viable option. It supports ordered collections.
With EF you will have to map the intermediate table with extra column and manually adjust the ordering according to your logic.
I know I'm late to this, but when designing this as a data model, I would prefer to add a relationship table, and that relationship table should have a property that defines the order (for example, worst friend is 0, best is 100).
Then, in EF, I would explicitly order by that property, if the list I'm retrieving should be of that order.
That means that whatever method you use to query the data, that relationship can be consistently used. So if you were using EF, you could use it (although, yes, it's not as handy as Friend.Friends, but the code would be clearer as to its intention - Friend.FriendRelationships.Select(p => p.Friend).OrderBy(p => p.OrderValue)), and if you were using direct SQL, then you could use it too.
If I came across Friend.Friends in code, I would have no idea what ordering would be applied to it.
If you must have it though, you could always add it as a non-db property -
public class Friend
{
public virtual List<FriendRelationship> UnorderedFriendList { get; set; }
[NotMapped]
public IEnumerable<Friend> Friends
{
get
{
return UnorderedFriendList.Select(p => p.Friend).OrderByDescending(p => p.OrderValue);
}
}
}

Categories

Resources