EFCore - How to exclude owned objects from automatic loading? - c#

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:

Related

EF Core: One to many relationship without navigation property in domain

Hi good people of Internet :)
I am trying to use EF Core 5 entities as domain entities in a sense of DDD.
I have a case of two entities, each with their own identity (meaning they are Entity type of DDD objects, not ValueObjects): Country and Currency.
Currency can belong to many Countries (for example EUR).
Country, on the other hand, can have only one 'currently active' Currency, which isn't necessarily the same Currency at all times (such example would be EU country, abandoning their own national currency for the EUR).
In a specific domain bounded context, I would need only:
public class Country : Entity<Guid>
{
public string Name { get; private set; }
// the rest is omitted for readability
public Currency Currency { get; private set; }
}
and
public class Currency : Entity<Guid>
{
public string Name { get; set; }
public string Code { get; set; }
}
I don't want to have navigation property: public ICollection<Country> Countries { get; private set; } on the Currency entity, just to be able to define 1:N relationship, because this would only pollute my domain.
I tried to add navigation property as EF shadow property, but EF doesn't allow it, with thrown exception:
The navigation 'Countries' cannot be added to the entity type
'Currency' because there is no corresponding CLR property on the
underlying type and navigations properties cannot be added in shadow
state.
Currency can't be owned (in a EF sense as OwnsOne) by Country, because that would mean that Currency would have to have composite {Id, IdCountry} PK in the database, which would violate the requirement of being able to assign one currency to multiple countries.
Is there a solution to establish a relationship between Currency and Country (or the other way around), which doesn't pollute the domain with navigation property and allows the same CLR object to be used as a domain and EF entity?
I am trying to use EF Core 5 entities as domain entities in a sense of DDD.
EF entities represent the so called data model, which in general is different from domain/business model, has its own requirements/modelling rules which are different from the other models, and navigation properties are great relationship representation which allow producing different type of queries without using manual joins etc.
So in general you should use separate models and map between the two where needed, thus not "polluting" your domain mode or breaking its rules. Simply the same way you follow the domain model rules, you should follow the data model rules - I don't understand why the people think EF should follow their rules rather than they following EF rules.
Anyway, with that being said, while super useful, EF Core navigation properties are not mandatory (except currently for many-to-many via implicit junction entity and skip navigations) - you can have both, just principal, just dependent or even neither ends.
All you need is to define the relationship with the proper Has / With pair. Proper means to use pass the navigation property when exists, and omit it when it doesn't.
It this case, you could use something like this:
modelBuilder.Entity<Country>()
.HasOne(e => e.Currency) // reference navigation property
.WithMany() // no collection navigation property
.IsRequired(); // omit this for optional relationship (to allow null FK)
The same can be achieved if you start the configuration from the other side. Just in that case you have to provide explicitly the generic type argument since it cannot be inferred automatically:
modelBuilder.Entity<Currency>()
.HasMany<Country>() // no collection navigation property
.HasOne(e => e.Currency) // reference navigation property
.IsRequired(); // omit this for optional relationship (to allow null FK)
You can use either way, just don't do both, since it is one and the same relationship, hence should be configured only once to avoid conflicting configurations (in case one is using separate IEnityTypeConfiguration<TEntity> classes which don't really fit well with relationships - one relationship with two ends).

EF6: Multiple added entities may have the same primary key

I've inherited a database and I need to insert data using EF6. I get the error:
DbUpdateException: Unable to determine the principal end of the 'POSModel.FK_KitMemberTaxRaw_KitMemberSaleReturnRaw_KitMemberSaleReturnRowId' relationship. Multiple added entities may have the same primary key.
I deserialize XML to the POCO objects using DataContractSerializer.
I'm using the object references from the xml document's structure to define the relationships. The POCO objects are generated using a t4 script provided from the NuGet package (which does not work with either deserializer well at all!)
I've decorated KitMemberTaxRaw like so:
[ForeignKey("KitMemberSaleReturnRaw")]
public virtual KitMemberSaleReturnRaw KitMemberSaleReturnRaw { get; set; }
[ForeignKey("KitMemberKitMemberSaleReturnRaw")]
public virtual KitMemberKitMemberSaleReturnRaw KitMemberKitMemberSaleReturnRaw { get; set; }
The KitMemberTaxRaw table may be joined to either table KitMemberKitMemberSaleReturnRaw or KitMemberSaleReturnRaw (but not both).
How does EF determine 'the principal end of the relationship'?
The issue turned out to be EF6 was not able to automatically understand a table with links to a parent and an optional grand parent. The navigation properties generated by the Microsoft provided template were correct but insufficient.
To cure the issue I manually created temporary primary keys for the relationship it did not understand.
Note: The DataContractSerializer class I used created the POCO objects creates an array for the instantiated navigation properties. I had to change the template to generate IList<> properties instead of ICollection<>. At run time there were errors because the array could not be dynamically resized.

Saving OData entities from api with id to database in entity framework

I have multiple projects that return the same OData entities through a API endpoint. Now i want to call all of the projects and store them in my calling projects database with entity framework.
To add them to the db the ID gets overwritten but i want to save the id that the entity has in the projects database as well. so i can still access them if need be and to check if the data isn't already in my database. Because of this i need to add another MainProjectID and projectID column to the entity.
I tried making a new class that has a reference to the entity i want to save but this used new id's for the entities. I also tried inheriting the class but this gave me key conflict issues, and generics don't work either in entity framework(i'm not saying they should). So i'm kinda at a loss right now.
I basically want to save the id as a non-key. Is there any way i can do this without writing entirely new classes and parsing them manually ?
Any help would be greatly appreciated.
We have multiple alternatives here:
In a distributed system, best way to cope with these kinds of ID clashes is to make IDs globally unique. If you can modify how IDs are generated, that would be my choice to go. You can use a UUID (or Microsoft implementation GUID) that will produce a universal unique identifier. Or if that seems like an overkill you can devise a simple mechanism that combines ID with projectID. However you should ensure that the method you will use will not produce any collisions (no two different id-projectId pair will map to same value).
This will ensure that same entity is used throughout your application and no overlaps occur if you try to put records from different sources into the same table. You only need to implement a mechanism to record which ID originated from which source. You can use a reference entity at aggregator for this purpose. You also need to disable auto increment nature of the ID column so that your global unique values are used in table.
You can use different entities for producing and aggregating applications. I don't know your application, but that seems like an OK approach to me since the aggregating application has a different idea about the entity. The aggregating application cares for which application produced the entity, that might make putting the source application identifier into the entry justifiable. Your entities will only differ in that and when you receive the OData object from API you'll need copy all other properties and put project identifier yourself.
You can use the previous solution, but you can use a derived class in order to not to repeat your object properties. This is a better design alternative. However with this method you'll have some problems with the primary key (as you've stated you had). Consider this example
public class Base {
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Name")]
public string Name { get; set; }
}
public class Derived : Base {
[Key]
public int projectId {get; set; }
}
If you don't put [Key] to Derived then you'll have only ID as primary key. When you put [Key] to Derived then you'll have only projectId as primary key. You need to define a composite key and you can do this by removing the [Key] annotation from projectId and using the onModelCreating override of DbContext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Derived>().HasKey(a => new { a.ID, a.projectId })
.Property(c => c.ID).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None);
modelBuilder.Entity<Derived>().Property(c => c.projectId).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None);
}
You can mix these alternatives. You can drop the primary key on ID field, and then you can insert a new Entity that will model 1-M relationship between ID's and project ID's.

Entity Framework 6 Code First - Comments on a Tree Structure

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);
}
}

Entity Framework - Manually Add Properties to Many-to-Many Relationship - Model First

Scenario:
As mentioned here, once you add additional properties to a simple join table (many-to-many relationship), it's no longer a hidden association. These questions also address this:
Many-to-Many relationship in Entity Framework with relationship informantion
How can I add properties to an association (relationship) using the Entity Framework
The existing code already uses the simple, automatically hidden navigation properties, and there are some minor customizations to the autogenerated tables, and so I'd like to avoid refactoring the entire project when I alter the underlying relationship table.
Question:
Is there a way so that both the automatic navigation (many-to-many) accessors can remain, but I can also access the relationship entity directly?
I could just write my own accessors selecting from the relationship table, but then they're no longer EntityCollections and thus I'm concerned that I lose whatever magic happens under the hood like tracking, etc.
Can I manually add EntityCollections to entities?
Expectation:
Originally: Product* <-> *Offer
a Product has many Offers (like 50% off, BOGO)
the same Offer could apply to many Products ("Red Shirt" and "Blue Pants" are BOGO)
Desired: Product* <-[sort]-> *Offer
When I list Offers for a Product, I can sort them independently
i.e. "Red Shirt" has "50% off" then "BOGO", but "Blue Pants" shows "BOGO" then "50% off"
then I would want to be able to do:
// original access, do stuff
List<Offer> applicableOffers = currentProduct.Offers.Where(...);
// hit up the join table directly for properties
var applicableOffersInOrder = applicableOffers.OrderBy(o => o.ProductOffers.Sort);
rather than
var applicableOffersInOrder = currentProduct.ProductOffers
.OrderBy(o => o.Sort)
.Offers.Where(...);
I think the easiest way to do it is simply add two properties manually in a non-autogenerated partial class:
partial class Offer
{
public IQueryable<Product> Products
{
get { return this.ProductOffers.Select(x => x.Product); }
}
}
partial class Product
{
public IQueryable<Offer> Offers
{
get { return this.ProductOffers.OrderBy(x => x.Sort).Select(x => x.Offer); }
}
}
This won't help for when you want to add a new ProductOffer, but since you actually have extra data (Sort) you should be doing that via the ProductOffers collection anyway.

Categories

Resources