VahidN / EFSecondLevelCache.Core

Entity Framework Core Second Level Caching Library
Apache License 2.0
326 stars 51 forks source link

Cached value sometimes invalid #52

Closed punppis closed 4 years ago

punppis commented 4 years ago

Sometimes when using cache with parameters returns incorrect data.

List<int> currencyIds = ...; // only a few ids
Listt<Currency> currencies = await database.Currencies.Where(c => currencyIds.Contains(c.Id)).Cacheable().ToListAsync();

Sometimes I get empty list and sometimes partial results (missing items in list). During the lifetime of the app this data is never changed and definitely is available in the database. If error occurs, it fixes itself when updating the data from database next time even though the data has not changed!

If I remove Cacheable() the code works. If I remove the Where-clause (just cache all items) the code works.

This code is run with different currencyIds so I suspect some kind of cache collision; for conditions X you get data for conditions Y.

Dependencies:

<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.2.6"/>
<PackageReference Include="EFSecondLevelCache.Core" Version="2.6.2"/>
<PackageReference Include="CacheManager.Core" Version="1.2.0"/>
<PackageReference Include="CacheManager.Microsoft.Extensions.Caching.Memory" Version="1.2.0"/>
<PackageReference Include="CacheManager.Serialization.Json" Version="1.2.0"/>

Edit: tested also with 2.6.3 and problem persists

It seems to be an issue with having List<T>.Contains(...) inside Where-clause. This is only case where I have had problems. Seems like Contains does not leverage parameterization. Maybe its related to this?

punppis commented 4 years ago

After further investigation it seems like first cached result is always returned in this case.

Here is a test code with 100% reproduction:

async Task TestFunc(List<int> ids)
{
    List<Currency> currencies = await database.Currencies.Where(c => ids.Contains(c.Id)).OrderBy(c => c.Id).Cacheable().ToListAsync();
    string correct = ids.Join(); // joins values with , delimiter
    string result = currencies.Select(c => c.Id).Join();
    Debug.Log("Should be: " + correct);
    if(correct != result)
    {
        Debug.LogError("Invalid: " + result);
    }
    else
    {
        Debug.Log("ok");
    }
}

Tested like

await TestFunc(new List<int>() { 1, 2 });
await TestFunc(new List<int>() { 3 });
await TestFunc(new List<int>() { 1, 6, 8 });
await TestFunc(new List<int>() { 1, 2, 3, 6, 8 });
await TestFunc(new List<int>() { 1, 2 });

Results

[13.9.2019 8:09:47 INFO T:10] Should be: 1,2
[13.9.2019 8:09:47 INFO T:10] ok
[13.9.2019 8:09:47 INFO T:10] Should be: 3
[13.9.2019 8:09:47 ERROR T:10] Invalid: 1,2
[13.9.2019 8:09:47 INFO T:10] Should be: 1,6,8
[13.9.2019 8:09:47 ERROR T:10] Invalid: 1,2
[13.9.2019 8:09:47 INFO T:10] Should be: 1,2,3,6,8
[13.9.2019 8:09:47 ERROR T:10] Invalid: 1,2
[13.9.2019 8:09:47 INFO T:10] Should be: 1,2
[13.9.2019 8:09:47 INFO T:10] ok

As stated before, everything works as expected when removing Cacheable() from the query.

VahidN commented 4 years ago

Fixed it via #https://github.com/VahidN/EFSecondLevelCache.Core/commit/e088162760ced9901a5c8ba98da646cc11a081d5

punppis commented 4 years ago

Seems to be working, nice!

lock[bot] commented 4 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related problems.