Efcore: Unexpected behaviour of AsNoTracking()

Created on 11 Dec 2019  路  11Comments  路  Source: dotnet/efcore

AsNoTracking method does not allow to include data for all elements in a collection. Reference properties are filled only for first matching by id objects. Full info can be found here, in my SO question

Steps to reproduce

There is a sample project branch states-countries-relation

Further technical details

EF Core version: 2.2
Database provider: Pomelo.EntityFrameworkCore.MySql
Target framework: .NET Core 2.2
Operating system: macOS Mojave
IDE: Rider 2019.2

closed-question customer-reported

All 11 comments

@VitaliiIsaenko I suspect the model configuration doesn't match what you have in the database. This is the model EF is building:

Model: 
  EntityType: AddressDto
    Properties: 
      Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd 0 0 0 -1 0
      CountryId (int) Required 1 1 -1 -1 -1
    Navigations: 
      CountryLocale (<CountryLocale>k__BackingField, List<CountryLocaleDto>) Collection ToDependent CountryLocaleDto 0 -1 1 -1 -1
    Keys: 
      Id PK
  EntityType: CountryLocaleDto
    Properties: 
      Id (int) Required PK AfterSave:Throw 0 0 0 -1 -1
      Locale (string) Required PK AfterSave:Throw 1 1 1 -1 -1
      AddressDtoId (no field, Nullable<int>) Shadow FK Index 2 2 2 0 0
    Keys: 
      Id, Locale PK
    Foreign keys: 
      CountryLocaleDto {'AddressDtoId'} -> AddressDto {'Id'} ToDependent: CountryLocale

I suspect that maybe you want to use address.countryId as a foreign key, but that will only work if the address is on the dependent end of the relationship, which is in conflict with using a collection navigation property here.

You might want to take a look at the documentation on modeling relationships: https://docs.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api

@ajcvickers thank you for helping out! Unfortunately, you had a look at the master branch that I didn't want to modify due to another question on the topic on SO. Branch states-countries-relation has an example of the described case. However, it is possible, that the same appears in the address-country relation as well. I understand that the relation setup might be wrong, however, it was the only way to achieve what I need and it works (thanks for help for my SO question). This relation is indeed non-traditional (due to locales support) but not that difficult to understand.
However, the question I'm posting here is relevant to another SO question I posted and has sample code on states-countries relation branch of the same repository. It also requires a similar setup in Context but I could not find any other option. I would be thankful for helping me with that as well but usually, it ends up referencing me to documentation without understanding this specific case's requirements. So, since it is working, I'm fine with this option that at least works. The only problem is - EF Core fills my reference collection for every object without AsNoTracking() and fills only the first element among ones with the same ids with AsNoTracking(). Following the documentation, this option should influence only the modification of data and should not influence its fetching. Therefore I opened this issue. I would be happy to either know the explanation of why it happens with further documentation adjustments or adding the bug into the queue if it is a bug.
Thank you again, hope now it is clear!

@VitaliiIsaenko This line:
C# .HasPrincipalKey(x => x.CountryId);
sets up an alternate key using CountryId. However, the code then inserts duplicate values with the same key value. This is not valid; each primary or alternate key much uniquely identify each entity.

Interesting, thank you! However, it works somehow.. maybe you have a better idea for such relation then?
But still, the question about AsNoTracking() is open and it indeed influences how reference fields are filled with .Include(). Isn't it a separate problem?

@VitaliiIsaenko The reason you are only seeing inconsistent results is that there are multiple entities with the same key. One of the entities is being discarded because it has the same key as an entity that was already retrieved, indicating that these are duplicates.

So does it mean that AsNoTracking() method influences data retrieval in this case? It's not documented anywhere, it is meant to affect data modification due to documentation. That's why I call it unexpected behaviour (maybe undocumented fits better).

https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#no-tracking-queries-no-longer-perform-identity-resolution

@VitaliiIsaenko Note that all EF behavior is undefined when the data does not comply with the constraints inherent in the configured model, which is the case here.

Thank you, I understand. Is there any example of such a relation? I haven't found it anywhere and on SO people suggested only this option. Basically, it's country having the same id for several rows due to locales referencing states having the same id for several rows for the same reason. The state has one country logically but many with the same id because of locales and vice versa. I would appreciate an example or maybe an it is not possible response :) Could not get this relation from documentation only.
But anyway, thank you for help!

@VitaliiIsaenko Consider this data from your post:

Example data would be:
address (id, countryId)
(1, 1)
(2, 1)
address_country (id, locale, name)
(1, 'en', 'Germany')
(1, 'de', 'Deutschland')

This means that address.countryId matches two rows in the address_country table. This could be modeled as so (using both navigation properties for clarity):
```C#
public class AddressDto
{
public int Id { get; set; }

public int CountryId { get; set; }
public CountryLocaleDto CountryLocale { get; set; }

}

public class CountryLocaleDto
{
public int Id { get; set; }
public string Locale { get; set; }

public List<AddressDto> Address { get; set; }

}

```C#
modelBuilder.Entity<AddressDto>()
    .HasOne(e => e.CountryLocale)
    .WithMany(e => e.Address)
    .HasForeignKey(e => e.CountryId);

But this limits each Address to a single CountryLocale. If you _also_ want each address to be associated with many CountryLocale's, then this is a many-to-many relationship that requires a join table.

@ajcvickers, thank you a lot for your help! Also, thank you, @smitpatel, the link is very useful and explains much.
I understood that there is no way to configure the relationship I explained in EF Core. Just so it stays here for anyone else interested.

Was this page helpful?
0 / 5 - 0 ratings