efcore
efcore copied to clipboard
Use C# structs or classes as value objects
I was really looking forward to the new Owned Entities feature until I found out that struct types are not supported.
Common examples of struct types:
struct Vector3 {
float X { get; set; }
float Y { get; set; }
float Z { get; set; }
}
struct TimeRange {
DateTime StartTime { get; set; }
DateTime EndTime { get; set; }
}
struct Date {
private readonly DateTime _dt;
public UtcDate(int year, int month, int day)
{
_dt = new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Utc);
}
...
}
Similar Struct Types in third party libraries: corefxlab NodaTime
I would like to use these struct types in my entities:
class Event {
string Name { get;set; }
TimeRange TimeRange { get; set; }
}
class BankHoliday {
string Name { get;set; }
Date Date { get; set; }
}
modelBuilder.Entity<Event>().OwnsOne(e => e.TimeRange);
modelBuilder.Entity<BankHoliday>().OwnsOne(e => e.Date);
so that the generated tables look like these:
Event [Name, TimeRange_StartTime, TimeRange_EndTime]
BankHoliday [Name, Date_dt]
Good point.
Value types (like an address type for instance) are most likely to be mapped as an Owned Entity. Value types are mostly represented by structs, so not having the possibility to map a struct as an 'owned entity' could indeed be a dealbreaker.
It seems like nullable owned entities are also not supported.
@sir-boformer - nullable owned entities are covered by #9005 If you map them to different table then they can work.
Why would you want to map an owned entity to a different table ? In most cases, I want them to be part of the owning table. Nullable owned entities are a valid use-case, especially if the database already exists ...
@fgheysels - That is valid use-case. At present, when they are mapped in same table, they uses same column for PK. In current implementation there is no way to figure out if the owned entity is null. So if you map it to separate table, you can find out owned entity is null when no matching row is found. It is not ideal and users would want to map it to same table hence #9005 is the tracking issue to implement support for it.
Notes from triage: we think the common cases for structs like these could be handled by being able to map a type to multiple columns. The main missing feature for this is type mapping from a single property to multiple columns.
@smitpatel - I think that , when using owned entities as a value type (which will be for me the most obvious usage of owned entities), you should not think in terms of primary keys. A value type is not an entity and has no identifier; it's identity is made up by its value.
@ajcvickers I'm a fond user of NHibernate, and there this is done using 'component-mapping'. Maybe helpfull to take a look at it ? http://nhibernate.info/doc/nhibernate-reference/components.html
Note: consider the case that the value object contains key properties--see #10682
This prevents me from reusing my models, as I'm using struct for related properties set heavily.
It works for classes but not for structs. If you try to use struct
with OwnsOne
you get error: error CS0452: The type 'Vector' must be a reference type in order to use it as parameter 'TRelatedEntity' in the generic type or method 'EntityTypeBuilder<Player>.OwnsOne<TRelatedEntity>(Expression<Func<Player, TRelatedEntity>>)
It says clearly that Vector
must be a reference type (class
) but struct
is a value type.
See also https://github.com/aspnet/EntityFrameworkCore/issues/13947#issuecomment-457353765
So, when I would want to use something like System.Numerics.Quaternion (a struct) in my model classes this is not possible using entity framework correct? I have to create my own class and map them in the setters and getters?
I suggest to add any note on this topic in the official entity framework documentation (eg here), currently this issue here is the top result when searching for documentation on using structs with entity framework..
@cs-util See https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions?tabs=data-annotations#examples
Would also like this for type-safe Id fields, aka newtype from Haskell, with defining structs like this:
public struct UserId
{
public int Id { get; private set; }
public UserId(int id) : this() { Id = id; }
}
So it won't be possible to use id of incorrect entity in expected place (with zero overhead also, coz of struct).
Just "shoot in my leg" today with using Id of wrong entity.
Having support for centralized value object mapping would be great too!
Something like IValueObjectTypeConfiguration<DateTimeRange>
.
public class DateTimeRangeMap : IValueObjectTypeConfiguration<DateTimeRange>
{
public void Configure(ValueObjectTypeBuilder<DateTimeRange> builder)
{
// Regular property to db column mapping.
}
}
Then reuse it in other mappings (entity mapping and value object mappings for nested VOs):
public class FooMap : IEntityTypeConfiguration<Foo> // Could be IValueObjectTypeConfiguration for nested VOs
{
public void Configure(EntityTypeBuilder<Foo> entityTypeBuilder)
{
entityTypeBuilder.ValueObject(t => t.MyDateRange); // No need to configure it,
// since the property MyDateRange type is of type DateTimeRange and mapped through DateTimeRangeMap class.
}
}
Please, take into account F# singe-case discriminated unions
I would say It's a drastically needed feature that prevents EF to be used in my projects. My vote for adding this. Until it's not done, I have to go with NH.
If owned entities support is rewritten for EF Core 7, they should not be loaded by default. At least for the 1-n relations since it results in a join. It does not make sense to fetch order details for each order when I fetch a list of orders. Same for customers and their addresses.
@ah1508 Making an entity owned indicates that it is part of an aggregate. Aggregates are designed to be loaded and manipulated as a unit. If you want the parent to be loaded without its children, then they by-definition should not be owned.
@ajcvickers I agree with you in theory, but in practice there should be an option for developer to choose if he/she wants to load a relation that involves a join (especially a 1-n join). NHibernate and Hibernate have always given this option.
"always loaded" policy is not that terrible if the entity is retrieved by its id, the only problem with the join comes from the amount of data that travels between the DB and the application for 1-n relations (what if the entity has several several owns many). Don't you think that additional selects for each 1-n relations may be better ?
But if you want to display a some information about all of your customers, fetching their addresses is a performance killer.
With EF the workaround would be :
from c in ctx.Customers select new Customer { Id = c.Id, Name = c.Name }
Actually it looks like a best practice (only fetch what you need), until you need a lot of properties.
@ah1508 Why do you think this relationship should be owned?
Because an OrderDetail
belongs to an Order
, an InvoiceLine
belongs to an Invoice
, a Storey
belongs to a Building
. And none of them has a unique identifier (storey#12345678).
And if a Customer
is removed it does not make sense to keep his addresses.
Sometime what seems owned needs to be an Entity since it offers more data access patterns, but it's not the case all the time.
But if you want to display a some information about all of your customers, fetching their addresses is a performance killer.
I think you're misusing Aggregate DDD term. Aggregate structure is chosen according to how you modify the state of your model. In other words, your DDD-style model is a Command model, where you execute some business logic and that can lead to aggregate state update. What you're describing is a Query model, where you're displaying something to the user, and the shape of what you're displaying is conflicting with the shape you need for your business logic. In this case, it's better to have separate Command and Query models, where Query model can have whatever shape to satisfy performance and the output shape.
@andriysavin I agree an I use CQRS design a lot, but EF should be agnostic regarding design choices. Many dev teams use the same model for query and command.
And even on the command side, if you fetch an invoice to change its status you do not want all the invoice lines to be fetched.
Automatic loading of owned entity could be enabled by default in the next version of EF to preserve backward compatibility but a setting on the DbContextOptions
could give the possibility to choose explicit Include
instead (at least for 1-n, for 1-1 the owned entity data live along other owner's property in the same table so it's not a problem).
@ah1508 if you don't want to load all invoice lines, then the lines shouldn't be owned by the invoice. You can still have the lines deleted when the invoice is deleted (cascade) - you don't need ownership for that. So as @ajcvickers wrote above, I'd recommend reviewing why you think you need to have an owned relationship.
And even on the command side, if you fetch an invoice to change its status you do not want all the invoice lines to be fetched.
DDD clearly states that an aggregate must be loaded and persisted fully and atomically. For the scenario you're mentioning this might sound ridiculous, but one of the goals of DDD is to completely abstract the model from the storage mechanism.
E.g. today you have code which changes status of an invoice which looks like simple property assignment (so it's tempting to optimize this by doing column update without even loading the entity), but tomorrow you will want status changing to do more things, like sending domain events, doing some validation and so on. Now you will have to change your repository implementation and even interface. And you will have to change client code, since status update must be done in different way. (I know this example is not exactly what you have, just for illustration).
From my experience, having collections in an aggregate is often a sign of an incorrect aggregate shape choice (which I did myself too). Main mistake is to think of an aggregate as a logical grouping of related entities, while it's meant to be a state modification unit. In other words, you design aggregate shape so that you load and save together the entities which make sense to be changed together (or to be consistent). If Invoice has invoice lines which never change with the Invoice itself, they probably should not be part of the aggregate (and Invoice). Similar scenarios are widely discussed at Stackoverflow, Reddit etc.
I agree ,though, it would be nice to have as much options as possible, since not every application design follows DDD or other practices strictly due to different reasons.
Even if the aggregate is considered a unit conceptually, it doesn't mean that some parts couldn't be loaded lazily. In similar fashion that we consider enabling lazy loading properties (https://github.com/dotnet/efcore/issues/1387) we could allow lazy loading owned types (https://github.com/dotnet/efcore/issues/10787).
Please direct further discussion to the latter issue as the current issue isn't about owned types nor lazy loading.
@AndriySvyryd, sorry to hijack this discussion; if there are plans for lazy-loading owned types, then maybe there are plans to enable split queries to retrieve owned entities from separate tables? For now, EF Core generates LEFT JOINs regardless of AsSplitQuery().
@rmihael If it's an owned collection then AsSplitQuery()
should affect it, file an issue if it doesn't.
We don't have plans to support split queries for non-collection navigations
Is Value Objects supported only with OwnsOne? in our project we use lot of OnwsMany with ToTable as List of Value Objects connected to entity.