Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document: OwnedEntities requires AsNoTracking when queried separately #2205

Open
slipdef opened this issue Nov 13, 2019 · 14 comments
Open

Document: OwnedEntities requires AsNoTracking when queried separately #2205

slipdef opened this issue Nov 13, 2019 · 14 comments

Comments

@slipdef
Copy link

slipdef commented Nov 13, 2019

The code below just crashes with "A tracking query projects owned entity without corresponding owner in result. Owned entities cannot be tracked without their owner":

.Select(x => new A
{
     OwnedEntity = x.OwnedEntity
});

It worked fine on EF Core 2. No single word about that in the breaking changes doc!

I have had a hard time finding this comment:
dotnet/efcore#17617 (comment)

I believe this pattern of querying data is quite often. At least I used to it since EF 6. Adding AsNoTracking() in these cases doesn't make sense for me considering the pattern. I understand it's too late to re-consider but please describe that behavior in the breaking changes at least.

@ajcvickers
Copy link
Member

@slipdef What is it that you're trying to do that needs an owned entity tracked without it's owner? In other words, why is it an owned entity if it can exist without its owner?

@slipdef
Copy link
Author

slipdef commented Nov 14, 2019

I indeed don't need an owned entity being tracked without owner. So I would be totally fine if AsNoTracking() is added by default in such cases.

@ajcvickers
Copy link
Member

@slipdef In that case can you elaborate on, "adding AsNoTracking() in these cases doesn't make sense for me."

@slipdef
Copy link
Author

slipdef commented Nov 14, 2019

I didn't need to write that code before EF Core 3:

DbSet.AsNoTracking().Select(x => new A
{
     OwnedEntity = x.OwnedEntity
});

I just did

DbSet.Select(x => new A
{
     OwnedEntity = x.OwnedEntity
});

and all worked fine.

Now I need because AsNoTracking() is required when querying owned entity without principal. As you pointed out: why would someone need to query owned entity without owner and expect it to be tracked by EF. It doesn't make sense for me too.

@ajcvickers
Copy link
Member

@slipdef Thanks; we will discuss.

@ajcvickers
Copy link
Member

We discussed in triage and decided to keep this on the backlog to consider not requiring AsNoTracking in this case.

@smitpatel Can you write down something that explains the main reason owned types can be more confusing than other non-tracked objects.

@AndriySvyryd
Copy link
Member

Adding AsNoTracking implicitly wouldn't prevent the breaking change if the code relied on them being tracked.

@joelmdev
Copy link

joelmdev commented Dec 4, 2019

Might be worth noting that mapping properties of an Owned Entity Type in a projection will not cause this issue but mapping the Owned Entity Type itself in a projection will, even if properties of the owner are mapped. Examples:

_context.DomainUser.Select(du => new { id = du.Id, Address = du.Address.Line1 }).ToList(); //no exception

_context.DomainUser.Select(du => new { id = du.Id, Address = du.Address }).ToList(); //throws exception

_context.DomainUser.Select(du => new { Address = du.Address.Line1 }).ToList(); //no exception

This really makes Owned Entity Types painful to use when mapping to view models or DTOs.

@smitpatel
Copy link
Member

With model

public class Customer
{
    public int Id { get; set; }
    public Address Address { get; set; }
}

[Owned]
public class Address
{
    public int CityId { get; set; }
    public City City { get; set; }
}

public class City
{
    public int Id { get; set; }
}

Query

var query = db.Customers.Select(c => new
{
    One = c,
    Two = c.Address,
    Three = c.Address.City
});

Above query is marked as tracking by default. If you project Customer, we have enough data to update Address also.

  • If customer is not projected then we are left with Address & City entity. Address cannot be updated without Customer so it create first confusion point, would City be tracked (making only owned entity non-tracked or City won't be tracked (making whole query non-tracking).
  • If only owned entities are skipped then user who is updating owned entity values would have changes saved sometimes (if owner is also projected or values won't be saved). Also creates another odd behavior that if owner is already in change tracker and owned entity is queried, it may be saved.
  • If query becomes non-tracking then City won't be saved. By far, City is neither owned nor part of aggregate, having it's behavior vary is hard to predict.

Overall, making implicit non-tracking forces users to understand what case it would be tracked vs non-tracked with above set of complex rules, alongwith potential of data loss if something is not saved correctly.

By making AsNoTracking required explicitly, users would know when they are querying owned entity which cannot be updated. So they can take corrective action based on what usage they want. Further, by putting explicit AsNoTracking it becomes clearer that what happens to unrelated entities like City in such queries.

@ajcvickers ajcvickers changed the title Not documented breaking change: OwnedEntities requires AsNoTracking when queried separately Document: OwnedEntities requires AsNoTracking when queried separately Mar 20, 2020
@ajcvickers ajcvickers transferred this issue from dotnet/efcore Mar 20, 2020
@ajcvickers ajcvickers self-assigned this Mar 20, 2020
@ajcvickers ajcvickers added this to the 5.0.0 milestone Mar 20, 2020
@ajcvickers ajcvickers modified the milestones: 5.0.0, Backlog Dec 16, 2020
@marcwittke
Copy link

marcwittke commented Jul 1, 2022

I have a domain layer that is provided with an IQueryable<TAggregate> that in reality is a DbSet<TAggregate> created by the persistence layer. The domain layer now wants to do such a projection but out of a sudden gets an Exception, that yesterday wasn't there.

Plus: The domain layer has no idea what Entity Framework is. It is persistence agnostic, so there is no .AsNoTracking(). I am basically forced to do a load of the whole table into memory, just to prevent someone who copies code from the documentation without reading the documentation to shoot themseves into the foot.

Sadly, this is a recurrent pattern with Entity Framework Core: you spent too much energy on the "Demoware" use cases, but leave behind the serious architectual concepts like domain driven design. Don't get me wrong, I really love EF Core, but the time I spent to make thing such as an aggregate working is really high and I start thinking that an implementation at lower level like Dapper would be easier in the end, and using EF Core only as query implementation as the Q in CQRS

@bkoelman
Copy link

bkoelman commented May 10, 2023

One of our customers reported this error, though the scenario is slightly different. JsonApiDotNetCore dynamically produces LINQ queries of the following form when the API request contains sparse fieldsets (basically selecting a subset of the entity properties and navigations).

var customers = db.Customers.Select(c => new Customer()
{
    Id = c.Id,
    Address = c.Address
});

When no owned entities are involved, this works without explicitly setting a tracking behavior. The entities won't get tracked (obviously), which is fine because we only do this on read-only requests. But when owned entities are used, we now need to explicitly mark the query as non-tracking.

I understand that the rules when tracking occurs are complex, but the current experience is pretty inconsistent: We've all been building queries for years that weren't trackable, and we never cared. Now we suddenly need to rationalize and specify when we want to track or not, which is a pain because it may heavily affect performance. It would make more sense to me to downgrade this exception to a warning, which we can suppress in configuration. As a workaround, we're now setting the tracking behavior to NoTrackingWithIdentityResolution for all read-only requests.

Aside from that, in our example query there is actually an owner, so the error message does not seem to apply. Is this a bug?

@ajcvickers ajcvickers removed this from the Backlog milestone May 11, 2023
@ajcvickers
Copy link
Member

@bkoelman We will discuss.

@ajcvickers
Copy link
Member

Note from triage: we discussed this again, but we still believe that the error is useful for preventing people falling into a pit of failure with tracking queries. We are not going to make changes here.

@ajcvickers ajcvickers added this to the Backlog milestone Jul 8, 2023
sbodenschatz added a commit to TUAS-Serious-Games-Lab/SGL-Utilities that referenced this issue Nov 27, 2023
Make property instances non-owned.
@DaveCousineau
Copy link

DaveCousineau commented Jun 20, 2024

I don't get this. We cannot track primitives or anonymous types either but we don't explode if we happen to project to something else untrackable; why should these explode? .AsNoTracking() is not possible when I am outside of EF with an IQueryable (unless I really had to add it to my interfaces; but that is 'leaky').

I would think the Owned Entity should just be seen as a named set of fields and if I project to it then I am effectively projecting to those fields (which I could do manually already right? but I would throw away the functionality in my type for no reason).

I don't get the reason for the error; I already know my type is not a full entity and that I would lose tracking by narrowing to it; but there are lots of queries where that is true and they don't explode...

@ajcvickers ajcvickers removed their assignment Aug 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants