efcore
efcore copied to clipboard
Navigation on value objects
Create easy navigation between Entity objects that share value objects
In our solutions we use a lot of value objects. There are many definitions for Value objects and suggestions that value objects have no key. But in fact the content of a value object is its key. Two value objects of the same type with the same property value are identical. What if EF core could understand value objects as keys?
We could model this in EF core as an object with key properties where the key properties are the value state. For example if external systems deal with a person but our system only uses the identity of the Person we could limit it to a Value object, only containing a key:
public class Person
{
public string PersonId { get; set; }
}
The value of the key is driven from other systems takes the PersonId for granted. It is a value received from the outside world. Our database does not contain any table for Person since any PersonId (any value) should be acceptable.
Now in the database we like to create relationships with the Person.
Suppose we want to store orders and relate to the person
public class Order
{
public int OrderId { get; set; }
public string PersonId { get; set; }
}
We could create a table of Orders in the database. The PersonId could have any value here since it is taken from an external system.
The problem here is we don't have any navigation objects. It is fairly hard to answer a question like what other Orders did the person create? We would need to go back to the entity context and (re)load all orders for the given PersonId.
Instead I like to create navigation
public class Person
{
public string PersonId { get; set; }
public ICollection<Order> Orders {get; set;}
}
public class Order
{
public int OrderId { get; set; }
public string PersonId { get; set; }
public Person Person {get; set;}
}
It is easy to create this navigation when we add a table of Person. But this feature request is about creating this navigation without creating a database table for Person. Actually the value of PersonId is the full content of the Person value object. So there is no need to load it since any navigation provides the content.
If we load an Order from the database the navigation property should always automatically return a Person object.
So the whole idea here is to create classes for value objects. All the non-navigation properties should be part of their primary key. But they don't exist in the database. Any call to Context.Find() should return the class with the given key or automatically create a new object based on the given key. It should be possible to tell EF core that is should pick objects from the context cache or automatically instantiate a new class with the given key value.
We have many examples in our code base where we use just a value and want to relate two objects on a value. We use just a CustomerId for referring a customer but have many objects with a CustomerId property. Currently we cannot navigate. It is possible to execute queries on the context itself. Navigation over a common object is more efficient in many cases since it will become part of the EF context cache and prevents reloading the same objects.
If I understand the above correctly, it seems like you want to have a PersonId on your Order entity, but without having an actual Person table in your database; you then want to query out all Orders for a given Person. This is already something you can do simply by filtering on the PersonId field directly:
var someOrderId = 8;
var ordersForSamePerson = await context.Orders
.Where(o => o.PersonId == context.Orders.FirstOrDefault(o => o.Id == someOrderId).PersonId)
.ToListAsync();
Have I understood the request more or less? I understand that the above doesn't have actual EF navigations and EF doesn't instantiate a Person instance for you (which would be merely a wrapper for its ID), but how is the above actually insufficient?
Yes you are right. You can query without using navigation. But that would be true for all data relations in EF.
The strong point of the navigation properties are:
- Caching: once you load a list of orders for a person, the second time you access it it does not load from the database again. The change tracker would update the relations
- Easy access in Expressions: the relations would make it much easier to create expressions based on the navigation like give me all orders where the same person has no next order.