I'm trying to project a hierarchy of entities to some DTOs using AutoMapper. I'm using EntityFramework Core (latest stable version to date).
The hierarchy is really simple:
I have a Calculation that has a collection of Parameters. Parameters optionally have a Calculation, hence the recursion.
class Calculation
{
int Id {get; set; }
ICollection<Parameter> Parameters { get; set }
}
class Parameter
{
int Id {get; set; }
Calculation Calculation { get; set }
}
I would like to project this to a tree like
Calc1
Param1
Param2
Param3
Calc2
Param4
Param4
I've defined AutoMapper maps for both Calculation and Parameter, but when I project the root Entity, I only get the first level of the hierarchy.
How do I make AutoMapper recurse projections automatically? Is there a way to do that?
Hierarchical queries are possible with ProjectTo but generally discouraged. Remember, ProjectTo only creates a LINQ Select expression. We can't create an infinite depth of projections, so you'll want to tell AutoMapper a MaxDepth when you configure the map for building out the hierarchy.
In practice, we do one of two things:
Explicitly model the hierarchy levels (ParentDto, GrandParentDto) for the depth we want
Use recursive CTEs to create a flat list of the entire hierarchy, then re-assemble the hierarchy in-memory
The latter won't use AutoMapper at all, but at least results in SQL that won't make your eyes bleed.
Related
I have a table for a spot. This spot can have different categories. For example: restaurant, hotel, park.
Each individual category has additional and different details.
My questions now:
Is it possible to create a column (FK) in the spot table that is independent of the category?
I mean when I create a new spot with the category Resteraunt and the next one is with the category park.
If I now select a spot that I get the correct data from the respective spot category
And if possible, how can I write that in C# EF Core Models?
Or is this not possible and i have to take another way?
Thanks in advance
Database Design for Example:
No, you can't have an FK column that is independent of the table it's referencing (the manifestations of your category). What you're looking for is inheritance. EF Core 5 currently supports two types of inheritance - Table Per Hierarcy (TPH) and Table Per Type. Either can work for your purposes.
First, you implement your entity Classe in an inheritance hierarchy to be mapped to the database with the derived classes representing the "category" of Spot:
public abstract class Spot
{
public Guid Id { get; set; }
// common spot properties here
// (i.e. props shared by different types of spots like address)
}
public class Restaurant : Spot
{
// restaurant specific properties here
}
public class Park : Spot
{
// park specific properties here
}
Then you map the entities in one of two ways - either to a single table (TPH) which will use a discriminator column to type each record in the DB (would be considered your category) and each property for the derived types would also be included but only populated for the specific type of record (i.e. Park properties will be null when the record in the DB represents a Restaurant and vice versa). Faster query performance with this method but all type-specific columns must be nullable and this implementation violates 3NF.
TPH is the default inheritance implementation but you can configure how it handles the discriminator like any other property (name, datatype) and specify the values to use for each derived type:
modelBuilder.Entity<Spot>()
.HasDescriminator("Category") // name it what you want
.HasValue<Restaurant>("R") // value for restaurants
.HasValue<Park>("P") // value for parks
;
In TPT each type in the inheritance hierarchy is mapped to its own table which contains their specific properties. The tables for the derived types use shared primary keys to reference their corresponding Spot record. Query performance can be slower and while it doesn't violate 3NF, its possible for manual data manipulation operations to mess things up (e.g. a Park and a Restaurant can reference the same Spot record).
For this configuration, merely map each type in the entity hierarchy to its own table:
modelBulider.Entity<Restaurant>().ToTable("Restaurant");
modelBuilder.Entity<Park>().ToTable("Park");
For both implementations, you can implement the DbSet properties normally:
public DbSet<Spot> Spots { get; set; }
public DbSet<Restaurant> Restaurants { get; set; }
public DbSet<Park> Parks { get; set; }
You can get specific types of Spots from Spots by using .OfType<T>()
var parks = dbContext.Spots.OfType<Park>();
So you do not need the Restaurants or Parks DbSet<T>s if you include the Spots DbSet<T>. Alternatively, Spots is optional if you include DbSet<T>s for the derived types.
I encourage you to model your entities both ways to see how EF models the DB and choose which you prefer.
https://learn.microsoft.com/en-us/ef/core/modeling/inheritance
With DDD there is a distinction between an Entity and an Aggregate. In EFCore I’m happy with the approach of using private collections and using a specification pattern to populate child entities when required. Similarly Julie Lerman and others have written really good articles on adapting EF for DDD.
An Aggregate, as opposed to an Entity, can/should only hold an Id reference to another Aggregate, rather than navigation property, which we can use to pull back via it’s Repository with a GetById() call.
What is more of a problem is determine how an Aggregate could hold a collection of Id references to a collection of Aggregates and how this can be achieved with Entity Framework?
Typically you’d set it up with navigation properties and foreign keys.
public Class Foo
(
public List<Bar> Bars { get; set; }
)
However, what I’m looking for to be in keeping with DDD is.
public Class Foo
(
public List<int> BarIds { get; set; }
)
What I don’t want to have is the Bar aggregate knowing that it has a relationship to Foo as it could be referenced by many other Aggregates.
Is it perhaps the case that you can’t have a collection of Aggregate Ids on an Aggregate?
How to correctly reference a collection of Aggregates?
You reference an aggregate by its id. So you reference a collection of aggregates by a collection of ids (each id references an aggregate).
how an Aggregate can hold an Id reference to another Aggregate
It's simple: the id of the another aggregate is a field in the aggregate that holds the reference.
how this can be achieved with Entity Framework?
I don't know EF, but an aggregate holds an id reference the same way it holds any other field of the aggregate.
I don't know the reason of the struggle.
Say you have 2 aggregates: Foo and Bar, so that Foo references Bar (Foo ==> Bar). If the client of Foo wants to get the instance of Bar referenced by an instance of Foo:
String aFooId = ...
Foo aFoo = fooRepository.getById ( aFooId );
String aBarId = aFoo.barId();
Bar aBar = barRepository.getById ( aBarId );
I'm using Entity Framework - Database First in my project. My model has a view with only one VARCHAR column:
CREATE VIEW MyView
AS
SELECT 'Eris' Eris
FROM MyTable
By default, this view gets mapped to its own entity with just one property:
public virtual DbSet<MyView> MyViews { get; set; }
How can I change this so that the view gets mapped to a List of strings instead:
public virtual List<string> Eris { get; set; }
Unfortunately EF does not support mapping collections of value types. If you really want to implement this scenario then you might want to look into other ORM frameworks that have this feature like NHibernate.
If that's not an option and you have to stick to EF then you're forced to create an entity with one property like you mentioned in your question.
The entity model represents one element in the table.
When you retrieve data from the table, you will get a list of entity model objects.
I have an application that I developed standalone and now am trying to integrate into a much larger model. Currently, on the server side, there are 11 tables and an average of three navigation properties per table. This is working well and stable.
The larger model has 55 entities and 180+ relationships and includes most of my model (less the relationships to tables in the larger model). Once integrated, a very strange thing happens: the server sends the same data, the same number of entities are returned, but the exportEntities function returns a string of about 150KB (rather than the 1.48 MB it was returning before) and all queries show a tenth of the data they were showing before.
I followed the troubleshooting information on the Breeze website. I looked through the Breeze metadata and the entities and relationships seem defined correctly. I looked at the data that was returned and 9 out of ten entities did not appear as an object, but as a function: function (){return e.refMap[t]} which, when I expand it, has an 'arguments' property: Exception: TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them.
For reference, here are the two entities involved in the breaking change.
The Repayments Entity
public class Repayment
{
[Key, Column(Order = 0)]
public int DistrictId { get; set; }
[Key, Column(Order = 1)]
public int RepaymentId { get; set; }
public int ClientId { get; set; }
public int SeasonId { get; set; }
...
#region Navigation Properties
[InverseProperty("Repayments")]
[ForeignKey("DistrictId")]
public virtual District District { get; set; }
// The three lines below are the lines I added to break the results
// If I remove them again, the results are correct again
[InverseProperty("Repayments")]
[ForeignKey("DistrictId,ClientId")]
public virtual Client Client { get; set; }
[InverseProperty("Repayments")]
[ForeignKey("DistrictId,SeasonId,ClientId")]
public virtual SeasonClient SeasonClient { get; set; }
The Client Entity
public class Client : IClient
{
[Key, Column(Order = 0)]
public int DistrictId { get; set; }
[Key, Column(Order = 1)]
public int ClientId { get; set; }
....
// This Line lines were in the original (working) model
[InverseProperty("Client")]
public virtual ICollection<Repayment> Repayments { get; set; }
....
}
The relationship that I restored was simply the inverse of a relationship that was already there, which is one of the really weird things about it. I'm sure I'm doing something terribly wrong, but I'm not even sure at this point what information might be helpful in debugging this.
For defining foreign keys and inverse properties, I assume I must use either data annotations or the FluentAPI even if the tables follow all the EF conventions. Is either one better than the other? Is it necessary to consistently choose one approach and stay with it? Does the error above provide any insight as to what I might be doing wrong? Is there any other information I could post that might be helpful?
Breeze is an excellent framework and has the potential to really increase our reach providing assistance to small farmers in rural East Africa, and I'd love to get this prototype working.
THanks
Ok, some of what you are describing can be explained by breeze's default behavior of compressing the payload of any query results that return multiple instances of the same entity. If you are using something like the default 'json.net' assembly for serialization, then each entity is sent with an extra '$id' property and if the same entity is seen again it gets serialized via a simple '$ref' property with the value of the previously mentioned '$id'.
On the breeze client during deserialization these '$refs' get resolved back into full entities. However, because the order in which deserialization is performed may not be the same as the order that serialization might have been performed, breeze internally creates deferred closure functions ( with no arguments) that allow for the deferred resolution of the compressed results regardless of the order of serialization. This is the
function (){return e.refMap[t]}
that you are seeing.
If you are seeing this value as part of the actual top level query result, then we have a bug, but if you are seeing this value while debugging the results returned from your server, before they have been returned to the calling function, then this is completely expected ( especially if you are viewing the contents of the closure before it should be executed.)
So a couple of questions and suggestions
Are you are actually seeing an error processing the result of your query or are simply surprised that the results are so small? If it's just a size issue, check and see if you can identify data that should have been sent to the client and is missing. It is possible that the reference compression is simply very effective in your case.
take a look at the 'raw' data returned from your web service. It should look something like this, with '$id' and '$ref' properties.
[{
'$id': '1',
'Name': 'James',
'BirthDate': '1983-03-08T00:00Z',
},
{
'$ref': '1'
}]
if so, then look at the data and make sure that an '$'id' exists that correspond to each of your '$refs'. If not, something is wrong with your server side serialization code. If the data does not look like this, then please post back with a small example of what the 'raw' data does look like.
After looking at your Gist, I think I see the issue. Your metadata is out of sync with the actual results returned by your query. In particular, if you look for the '$id' value of "17" in your actual results you'll notice that it is first found in the 'Client' property of the 'Repayment' type, but your metadata doesn't have 'Client' navigation property defined for the 'Repayment' type ( there is a 'ClientId' ). My guess is that you are reusing an 'older' version of your metadata.
The reason that this results in incomplete results is that once breeze determines that it is deserializing an 'entity' ( i.e. a json object that has $type property that maps to an actual entityType), it only attempts to deserialize the 'known' properties of this type, i.e. those found in the metadata. In your case, the 'Client' navigation property on the 'Repayment' type was never being deserialized, and any refs to the '$id' defined there are therefore not available.
Entity Framework 6 by default when it meets inheritance creates a special entity hierarchy with either TPH, TPT or TPC. But I would like EF to treat my classes as completely separate entities.
I have the following classes hierarchy where each class is mapped to a View in DB:
[Table("v_Item")]
class Item
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
}
[Table("v_ItemWithDescription")]
class ItemWithDescription : Item
{
public string Description { get; set; }
}
This design makes it possible to get more detailed info when needed. And it is so DRY. It is also nice to cast IQueryable:
IQueryable<ItemWithDescription> query = ...;
((IQueryable<Item>) query).Where(i => i.Name == "Foo")
But EF insists on adding discriminator column and badly distorts the queries. And there seems to be no way to make EF just forget that these classes have inheritance hierarchy!
Adding discriminator column to views and switching to TPC does not help as there appear lots of UNIONs in a query. It seems that my only option is to modify EF source code if I want to stick to my inherited approach. Are there any simpler solutions?
Thanks in advance!
If you don't map the base entity, it ignores the inheritance. So here's a trick you can do:
Create a base class for your Item class and pull all members up. Let's call it ItemBase. Do not map this class, and do not add it to your DbContext.
Then Item will be a mapped class without any properties of its own (will inherit everything from the base class). Make the rest of the classes (like ItemWithDescription) extend the ItemBase too.
This way, you'll have the code re-use, but lose the inheritance relationship between Item and its children, which depending on your case, may or may not be acceptable.