dotnet / dotnet/efcore

How to access the principal entity given an owned entity?

Open
#29,848 1 comment 4 reactions 0 assignees View on GitHub
area-change-tracking area-dbcontext customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

To avoid running into an A-B Problem I will try to summarize where the question is coming from.

In our application we are heaviliy using owned entitiy types for ddd-like value objects. Also, to track the CreatedDate and UpdatedDate of entities we have some basic logic in our override of SaveChanges which utilizes the context's ChangeTracker to get changed entities and update the respective values.

Now, suppose we have some entity `A` which owns the type `B`:

```
// entity classes
class A : Entity {
public B B { get; private set; }
}

class B : ValueObject {
// ...
}

// configuration
modelBuilder.Entity().OwnsOne(a => a.B);
```

If, during a transaction, only A.B is changed (via `A.B = new B()`), the change tracker contains an entry for both entities, A and B, but the A-entry has the status `Unchanged` and B the status `Added`.

Because of that, the following method was introduced:

```
private bool IsValueObjectModified(EntityEntry entity)
{
return entity.Navigations.Any(navigation =>
navigation.CurrentValue is ValueObject
&& ((ReferenceEntry) navigation).TargetEntry.State == EntityState.Added
);
}
```

The idea was to check all owned dependencies of all tracked entities to find out which of those have a modified value object, to then change to UpdatedDate of such an entity.
Now, some cases arose, where a transaction loaded a huge amount of entities into the ChangeTracker (almost all unchanged) and where this method leads to an unacceptable performance hit - profiling the application seems to point out the access `entity.Navigation` as the source of the latter since it calls `DetectChanges` every time.

I think the easiest solution to this problem is to access the parent entity of a modified owned entity. The question is what would be the best way for that.

* An obvious solution would be to configure all backwards references via `OwnedBy()` and a backreference `Parent` on each ValueObject. The reason I would not want to do this is that the general code architecture, from ddd perspective (i think), does not see value objects as actual entities - introducing all possible backreferences would kind of violate this idea of an owned type, requiring a large code and migration change only for a change of an implementation detail (the UpdatedDate tracking). Nevertheless, if this is the "recommended" way, I would consider going with it.
* The alternative I thought about is to utilize the structure of an EntityEntry which contains information about the (shadowed backreferencing) primary key and the principal entity type - although I did not find a direct way to access the owning parent (principal). The best I came up with looks something like this (`this` is a DbContext):
```
// for some entry in ChangeTracker.Entries():
if (entry.Metadata.IsOwned())
{
var ownership = entry.Metadata.FindOwnership();
var foreignKeyValue = Entry(entry.Entity).Property(ownership.Properties.First().Name).CurrentValue;
var owningEntity = Find(ownership.PrincipalEntityType.ClrType, foreignKeyValue) as Entity;
owningEntity.SetUpdatedDateUtc();
}
```
Is there an easier, and especially faster way to do this? This still requires an id lookup (although it should be fast given we are searching by an indexed primary key).

Sorry for the lengthy text, I hope my question is somewhat clear.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.