Git Product home page Git Product logo

entityframeworkcore.cacheable's Issues

Object reference not set to an instance of an object.

hi,I have a problem with it
var smallItems = await _context.posts.Where(a => a.TypeName == item).Take(10).Select(a => new TypeItemDto() { Title = a.Title, TypeName = a.TypeName, Time = a.CreateTime, Id = a.Id }).Cacheable(TimeSpan.FromHours(2)).ToListAsync();
This use will raise an exception :Object reference not set to an instance of an object.

If I remove select,but Using tolist directly is normal

error message:
Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSectionAsync(CancellationToken cancellationToken) Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider+ExceptionInterceptor<T>+EnumeratorExceptionInterceptor.MoveNext(CancellationToken cancellationToken) System.Linq.AsyncEnumerable.Aggregate_<TSource, TAccumulate, TResult>(IAsyncEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> accumulator, Func<TAccumulate, TResult> resultSelector, CancellationToken cancellationToken) in Aggregate.cs SEOSite.Controllers.ArticlesController.GetTypeList(string type) in ArticlesController.cs + var smallItems = await _context.posts.Where(a => a.TypeName == item).Take(10).Select(a => new TypeItemDto() Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor+TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, object controller, object[] arguments) Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync() Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync() Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context) Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()

.NET Core 3.0 and EF Core 3.0.0 support

The EF Core nuget packages needs to be updated to support .NET Core 3.0. When migrating from .NET Core 2.2 to 3.0 and EF Core to 3.0.0 assembly cannot be loaded:

System.TypeLoadException: Method 'get_Info' in type 'EntityFrameworkCore.Cacheable.CacheableOptionsExtension' from assembly 'EntityFrameworkCore.Cacheable, Version=2.0.1.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.

Entity state "Detached"

When fetching an entity using the cache the entity state is "Detached" and not "Unchanged" as expected.

Steps to reproduce

A first call to

var product = _context.Product.Where(p => p.Id == request.ProductID).Cacheable(TimeSpan.FromMinutes(10)).FirstOrDefault();
var state = _context.Entry(product).State;

Has the result: "Unchanged"

The second call (using the cache) results in : "Detached" !

When using the product reference as relation, the next call to SaveChanges() fails as it tries to insert the entity a second time.

Further technical details

Using the cache in an asp.net core application with dependency injection

Feature request: SlidingExpiration and TokenExpiration options

Hi.
What do you think about adding another expiration logic, such as SlidingExpiration or ChangeToken expiration?
I see that MemroyCacheProvider is based on Microsoft.Extensions.Caching.Memory.MemoryCache (which is already support theese features), so would be great to use token expiration for rarely modified data or sliding expiration to keep frequently requested data always in cache.

Cache Not "Flushing" and Going Back to Database

Describe what is not working as expected.

The cache doesn't seem to be "flushing" and going back to the database when a record is added after the query is cached. An example test method is below to demonstrate.

Steps to reproduce

        /// <summary>
        /// Ensure that the cache is flushing correctly when new records are added.
        /// </summary>
        [TestMethod]
        public void CacheFlushingTest()
        {
            MemoryCacheProvider.ClearCache();

            var loggerProvider = new DebugLoggerProvider();
            var loggerFactory = new LoggerFactory(new[] { loggerProvider });

            var options = new DbContextOptionsBuilder<BloggingContext>()
                .UseLoggerFactory(loggerFactory)
                .UseInMemoryDatabase(databaseName: "EntityExpressionTest")
                .Options;

            // create test entries
            using (var initContext = new BloggingContext(options))
            {
                initContext.Blogs.Add(new Blog { BlogId = 1, Url = "http://sample.com/cats" });
                initContext.Blogs.Add(new Blog { BlogId = 2, Url = "http://sample.com/catfish" });
                initContext.SaveChanges();
            }

            using (var entityContext = new BloggingContext(options))
            {
                // shoud not hit cache, because first execution
                var result = entityContext.Blogs
                    .Where(d => d.BlogId > 1)
                    .Cacheable(TimeSpan.FromMinutes(5))
                    .ToList();

                // shoud hit cache, because second execution
                var cachedResult = entityContext.Blogs
                    .Where(d => d.BlogId > 1)
                    .Cacheable(TimeSpan.FromMinutes(5))
                    .ToList();

                Assert.AreEqual(result.Count, cachedResult.Count);
            }
            
            // create additional test entries
            using (var initContext = new BloggingContext(options))
            {
                initContext.Blogs.Add(new Blog { BlogId = 3, Url = "http://sample.com/dogs" });
                initContext.SaveChanges();
            }

            using (var entityContext = new BloggingContext(options))
            {
                // shoud not hit cache
                var result = entityContext.Blogs
                    .Where(d => d.BlogId > 1)
                    .Cacheable(TimeSpan.FromMinutes(5))
                    .ToList();

                // shoud hit cache, because second execution
                var cachedResult = entityContext.Blogs
                    .Where(d => d.BlogId > 1)
                    .Cacheable(TimeSpan.FromMinutes(5))
                    .ToList();

                Assert.AreEqual(2, cachedResult.Count);
            }

            // find "cache hit" log entries
            var logs = loggerProvider.Entries.Where(e => e.EventId == CacheableEventId.CacheHit);

            // cache should hit one time
            Assert.IsTrue(logs.Count() == 1);
        }

Further technical details

EntityFrameworkCore.Cacheable version: 2.0.0
EF Core version: 2.2.3
IDE: Visual Studio 2019.0

Support .Net Core

Change library framework to .Net standard 2.0 to support .Net Core.

SaveChanges on entity not persisted in Cache

I've tried out Cacheable, and found that it doesn't seem to register new changes to entities. I kinds of expected Cacheable to purge any entity that was modified using the DbContext, so as to make sure it's fetched again.

Am I using this wrong?

I've noticed none of the tests test a SaveChanges() after the initial setup.

Steps to reproduce

Blog aa;
using (MyContext db = services.GetRequiredService<MyContext>())
{
	// Cache the first entity in the DB
	Blog c = db.Blogs.Cacheable(TimeSpan.FromMinutes(60)).First();
	aa = c;
	
	Console.WriteLine(c.Id);
	Console.WriteLine(c.Name);
}

Blog bb;
using (MyContext db = services.GetRequiredService<MyContext>())
{
	// Pick the first entity in the DB, without caching, so we can save changes
	// Else #10 will kick in
	Blog c = db.Blogs.First();
	bb = c;

	c.Name = DateTime.UtcNow.ToString();

	Console.WriteLine(c.Name);

	db.SaveChanges();
}

Blog cc;
using (MyContext db = services.GetRequiredService<MyContext>())
{
	// Fetch the Blog again elsewhere, observe that Name has not changed
	Blog c = db.Blogs.Cacheable(TimeSpan.FromMinutes(60)).First();
	cc = c;

	Console.WriteLine(c.Name);
}

// Here we see that the two Cached entries (aa, cc) are the same object
Console.WriteLine(ReferenceEquals(aa, bb));
Console.WriteLine(ReferenceEquals(aa, cc));
Console.WriteLine(ReferenceEquals(bb, cc));

Further technical details

EntityFrameworkCore.Cacheable version: (found in project.csproj or packages.config)
EF Core version: 2.2.4
IDE: VS 2019 (16.1.2)

Support MySQL

Describe what is not working as expected.

If you are seeing an exception, include the full exceptions details (message and stack trace).

System.AggregateException
  HResult=0x80131500
  Message=One or more errors occurred.
  Source=System.Private.CoreLib
  StackTrace:
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at Wilmar.Wmp.Gateway.Application.WmpGatewayApplicationModule.OnApplicationInitialization(ApplicationInitializationContext context) in C:\Users\zhangshanyouv\source\Workspaces\KDS X1.0\02-代码库\Framework\VNext\wmp\modules\gateways\src\Wilmar.Wmp.Gateway.Application\WmpGatewayApplicationModule.cs:line 41
   at Volo.Abp.Modularity.ModuleManager.InitializeModules(ApplicationInitializationContext context)
   at Volo.Abp.AbpApplicationBase.InitializeModules()
   at Wilmar.Wmp.Gateway.Startup.Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime applicationLifetime) in C:\Users\zhangshanyouv\source\Workspaces\KDS X1.0\02-代码库\Framework\VNext\wmp\modules\gateways\src\Wilmar.Wmp.Gateway\Startup.cs:line 34

内部异常 1:
NotSupportedException: Could not parse expression 'value(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[Wilmar.Wmp.Gateway.Domain.WmpGlobalConfiguration]).Include(x => x.WmpReRoutes).Where(x => (x.IsDefault == __isDefault_0)).Cacheable(value(EntityFrameworkCore.Cacheable.CacheableOptions))': This overload of the method 'EntityFrameworkCore.Cacheable.EntityFrameworkQueryableExtensions.Cacheable' is currently not supported.

Steps to reproduce

Use MySQL

private void ConfigureDatabaseServices(IServiceCollection services, IConfigurationRoot configuration)
        {
            services.Configure<DbConnectionOptions>(options =>
            {
                options.ConnectionStrings["WmpGateway"] = configuration.GetConnectionString("WmpGateway");
                options.ConnectionStrings.Default = configuration.GetConnectionString("Default");
            });

            services.Configure<AbpDbContextOptions>(options =>
            {
                options.UseMySQL();
            });
        }

WmpGatewayDbContext:
       protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.UseSecondLevelCache();
            }
        }

       public async Task<WmpGlobalConfiguration> GetGlobalConfiguration(int isDefault = 1)
        {             
            var cacheableQuery = DbSet.Include(x=> x.WmpReRoutes)
                .Where(x => x.IsDefault == isDefault)
                .Cacheable(TimeSpan.FromSeconds(60));
            return await cacheableQuery.FirstOrDefaultAsync();
        }

Further technical details

EntityFrameworkCore.Cacheable version: 2.0.0
EF Core version: 2.2
IDE: Visual Studio 2017 15.9

Global query filters raise a "An item with the same key has already been added" exception

If an entity has Global Query Filters defined (e.g. in the OnModelCreating method of the DbContext executing modelBuilder.Entity().HasQueryFilter(...)) and the DbContext is using UseSecondLevelCache, then when trying to retrieve data from the DbContext an exception is thrown "An item with the same key has already been added".

Exception message: System.ArgumentException: An item with the same key has already been added.
Stack trace:
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
   at Microsoft.EntityFrameworkCore.Query.QueryContext.AddParameter(String name, Object value)
   at lambda_method(Closure , QueryContext )
   at EntityFrameworkCore.Cacheable.CustomQueryCompiler.<>c__DisplayClass21_2`1.<CompileQueryCore>b__1(QueryContext qc) in C:\Users\Paul\Source\Repos\EntityFrameworkCore.Cacheable\EntityFrameworkCore.Cacheable\CustomQueryCompiler.cs:line 196
   at EntityFrameworkCore.Cacheable.CustomQueryCompiler.Execute[TResult](Expression query) in C:\Users\Paul\Source\Repos\EntityFrameworkCore.Cacheable\EntityFrameworkCore.Cacheable\CustomQueryCompiler.cs:line 120
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
   at Remotion.Linq.QueryableBase`1.GetEnumerator()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at EntityFrameworkCore.CacheableTests.GlobalQueryFilterTest.GlobalQueryFiltersWithCacheableEnabledAndUsingCacheQuery() in C:\Users\Paul\Source\Repos\EntityFrameworkCore.Cacheable\EntityFrameworkCore.CacheableTests\GlobalQueryFilterTest.cs:line 190
Result Message:	
Test method EntityFrameworkCore.CacheableTests.GlobalQueryFilterTest.GlobalQueryFiltersWithCacheableEnabledAndUsingCacheQuery threw exception: 
System.ArgumentException: An item with the same key has already been added.

Steps to reproduce

The below unit test shows the issue - it will error on line 103 "Assert.IsTrue(test1Context.Blogs.Count() == 3);"

This unit test is modelling a multi-tenant scenario, where each Entity has a MultiTenantId int property. The relevant MultiTenantID is passed to the DbContext in it's constructor and the Global Query Filter automatically applies a "MultiTenantID = X" predicate.

This unit test will succeed if the "optionsBuilder.UseSecondLevelCache();" is removed from the OnConfiguring method in the GlobalQueryFilterContext class

using System.ComponentModel.DataAnnotations;
using System.Linq;
using EntityFrameworkCore.Cacheable;
using EntityFrameworkCore.CacheableTests.Logging;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace EntityFrameworkCore.CacheableTests.QuickTest
{
    [TestClass]
    public class ProofOfIssue
    {
        public class GlobalQueryFilterContext : DbContext
        {
            private readonly int _multiTenantId;

            public GlobalQueryFilterContext(int multiTenantId, DbContextOptions<GlobalQueryFilterContext> options)
                : base(options)
            {
                _multiTenantId = multiTenantId;
            }

            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSecondLevelCache();
            }

            #region Overrides of DbContext

            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);

                // Add global query filters to all entities
                modelBuilder.Entity<GlobalQueryFilterBlog>().HasQueryFilter(e => e.MultiTenantId == _multiTenantId);
            }

            #endregion

            public DbSet<GlobalQueryFilterBlog> Blogs { get; set; }
        }

        public class GlobalQueryFilterBlog
        {
            [Key]
            public int BlogId { get; set; }
            public int MultiTenantId { get; set; }
            public string Url { get; set; }
        }


        [TestMethod]
        public void QueryingWithGlobalQueryFilters()
        {

            MemoryCacheProvider.ClearCache();

            var loggerProvider = new DebugLoggerProvider();
            var loggerFactory = new LoggerFactory(new[] { loggerProvider });

            var options = new DbContextOptionsBuilder<GlobalQueryFilterContext>()
                .UseLoggerFactory(loggerFactory)
                .UseInMemoryDatabase(databaseName: "GlobalQueryFilterTest")
                .Options;

            // Multi Tenant
            const int firstMultiTenantId = 1;
            const int secondMultiTenantId = 2;

            // create test entries - this time don't disable the cacheable extension
            using (var init1Context = new GlobalQueryFilterContext(firstMultiTenantId, options))
            {
                init1Context.Blogs.Add(new GlobalQueryFilterBlog
                { BlogId = 1, MultiTenantId = firstMultiTenantId, Url = "http://sample.com/cats" });

                init1Context.Blogs.Add(new GlobalQueryFilterBlog
                { BlogId = 2, MultiTenantId = firstMultiTenantId, Url = "http://sample.com/catfish" });

                init1Context.Blogs.Add(new GlobalQueryFilterBlog
                { BlogId = 3, MultiTenantId = firstMultiTenantId, Url = "http://sample.com/dogs" });
                init1Context.SaveChanges();
            }

            using (var init2Context = new GlobalQueryFilterContext(secondMultiTenantId, options))
            {
                init2Context.Blogs.Add(new GlobalQueryFilterBlog
                { BlogId = 4, MultiTenantId = secondMultiTenantId, Url = "http://sample.com/clowns" });

                init2Context.Blogs.Add(new GlobalQueryFilterBlog
                { BlogId = 5, MultiTenantId = secondMultiTenantId, Url = "http://sample.com/clownfish" });

                init2Context.Blogs.Add(new GlobalQueryFilterBlog
                { BlogId = 6, MultiTenantId = secondMultiTenantId, Url = "http://sample.com/circus" });

                init2Context.Blogs.Add(new GlobalQueryFilterBlog
                { BlogId = 7, MultiTenantId = secondMultiTenantId, Url = "http://sample.com/circustent" });
                init2Context.SaveChanges();
            }

            using (var test1Context = new GlobalQueryFilterContext(firstMultiTenantId, options))
            {
                Assert.IsTrue(test1Context.Blogs.Count() == 3);

                Assert.IsTrue(test1Context.Blogs.ToList().All(b => b.MultiTenantId == firstMultiTenantId));
            }

            using (var test2Context = new GlobalQueryFilterContext(secondMultiTenantId, options))
            {
                Assert.IsTrue(test2Context.Blogs.Count() == 4);

                Assert.IsTrue(test2Context.Blogs.ToList().All(b => b.MultiTenantId == secondMultiTenantId));
            }

        }
    }
}

Further technical details

EntityFrameworkCore.Cacheable version: 2.0.0 (and also latest Master branch code from GitHub)
EF Core version: 2.2.0
IDE: Visual Studio 2017 15.9.6

NPE When Param Value is Null

Describe what is not working as expected.
When one of the params in a query is NULL a NPE is thrown.

// This generates a NPE
.Where(q => q.CodeTypeId == codeType /*int*/ && q.CodeKey == key/*NULL*/).Cacheable(TimeSpan.FromMinutes(10)).FirstOrDefault()
Exception message: Object reference not set to an instance of an object
Stack trace:
System.NullReferenceException: Object reference not set to an instance of an object. 
   at EntityFrameworkCore.Cacheable.MemoryCacheProvider.<>c.b__7_0(KeyValuePair`2 d) 
   at System.Linq.Enumerable.SelectEnumerableIterator`2.MoveNext() 
   at System.String.Join(String separator, IEnumerable`1 values) 
   at EntityFrameworkCore.Cacheable.MemoryCacheProvider.CreateQueryKey(Expression expression, IReadOnlyDictionary`2 parameterValues) 
   at EntityFrameworkCore.Cacheable.CustomQueryCompiler.Execute[TResult](Expression query) 
   at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source) 
   at Mx.BusinessLogic.CodeFacade.GetSingleCached(Int32 codeType, String key) 

Steps to reproduce

Include a complete code listing (or project/solution) that we can run to reproduce the issue.

Partial code listings, or multiple fragments of code, will slow down our response or cause us to push the issue back to you to provide code to reproduce the issue.

// To Reproduce use any cached query where one of the params is null.  In this case null is a valid value 
// The exception happens at MemoryCacheProvider line 60

Further technical details

EntityFrameworkCore.Cacheable version: 2.0.0
EF Core version: 2.2.1
IDE: Visual Studio 2017

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.