├── .nuget ├── NuGet.exe ├── _run.bat ├── NuGet.Config └── NuGet.targets ├── EFSecondLevelCache ├── EnumerableExtentions.cs ├── DynamicEqualityComparer.cs ├── packages.config ├── Contracts │ ├── IEFCacheKeyHashProvider.cs │ ├── EFCacheDebugInfo.cs │ ├── IEFCacheKeyProvider.cs │ ├── EFCacheKey.cs │ ├── EFCachePolicy.cs │ └── IEFCacheServiceProvider.cs ├── Properties │ └── AssemblyInfo.cs ├── EFCacheKeyHashProvider.cs ├── App.config ├── EFSecondLevelCache.nuspec ├── EFAsyncEnumerator.cs ├── EFCommandTreeCollector.cs ├── readme.txt ├── LinqToObjectsCacheKeyProvider.cs ├── FastReflectionUtils.cs ├── EFCachedQueryable.cs ├── EFCacheKeyProvider.cs ├── ObjectQueryExtensions.cs ├── XxHashUnsafe.cs ├── EFCommandTreeVisitor.cs ├── EFCacheServiceProvider.cs ├── EFCachedQueryExtension.cs ├── EFCachedQueryProvider.cs └── EFSecondLevelCache.csproj ├── EFSecondLevelCache.Tests ├── EFSecondLevelCache.TestDataLayer │ ├── packages.config │ ├── Models │ │ ├── Tag.cs │ │ ├── User.cs │ │ ├── TPT.cs │ │ └── Product.cs │ ├── App.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── DataLayer │ │ ├── SampleContext.cs │ │ └── Configuration.cs │ └── EFSecondLevelCache.TestDataLayer.csproj ├── EFSecondLevelCache.PerformanceTests │ ├── packages.config │ ├── App.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Program.cs │ └── EFSecondLevelCache.PerformanceTests.csproj ├── EFSecondLevelCache.FunctionalDbFirstTests │ ├── packages.config │ ├── DBModel.cs │ ├── DBModel.Designer.cs │ ├── Tag.cs │ ├── User.cs │ ├── DBModel.edmx.diagram │ ├── Product.cs │ ├── DBModel.Context.cs │ ├── App.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── UnitTests.cs │ └── EFSecondLevelCache.FunctionalDbFirstTests.csproj ├── EFSecondLevelCache.MockingTests │ ├── packages.config │ ├── App.config │ ├── TestDbAsyncEnumerable.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── TestDbAsyncQueryProvider.cs │ ├── MockingTests.cs │ └── EFSecondLevelCache.MockingTests.csproj ├── EFSecondLevelCache.FunctionalTests │ ├── packages.config │ ├── Bootstrapper.cs │ ├── App.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── PerformanceTests.cs │ ├── EFCacheServiceProviderTests.cs │ ├── EFSecondLevelCache.FunctionalTests.csproj │ ├── CachedUserEntityTests.cs │ └── DynamicLINQTests.cs └── EFSecondLevelCache.UnitTests │ ├── XxHashTests.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── EFSecondLevelCache.UnitTests.csproj ├── .github └── issue_template.md ├── .gitattributes ├── README.md ├── .gitignore └── EFSecondLevelCache.sln /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VahidN/EFSecondLevelCache/HEAD/.nuget/NuGet.exe -------------------------------------------------------------------------------- /EFSecondLevelCache/EnumerableExtentions.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VahidN/EFSecondLevelCache/HEAD/EFSecondLevelCache/EnumerableExtentions.cs -------------------------------------------------------------------------------- /EFSecondLevelCache/DynamicEqualityComparer.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VahidN/EFSecondLevelCache/HEAD/EFSecondLevelCache/DynamicEqualityComparer.cs -------------------------------------------------------------------------------- /.nuget/_run.bat: -------------------------------------------------------------------------------- 1 | "%~dp0NuGet.exe" pack "..\EFSecondLevelCache\EFSecondLevelCache.csproj" -Prop Configuration=Release 2 | copy "%~dp0*.nupkg" "%localappdata%\NuGet\Cache" 3 | pause -------------------------------------------------------------------------------- /EFSecondLevelCache/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.TestDataLayer/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.PerformanceTests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalDbFirstTests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.MockingTests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.TestDataLayer/Models/Tag.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace EFSecondLevelCache.TestDataLayer.Models 4 | { 5 | public class Tag 6 | { 7 | public int Id { set; get; } 8 | public string Name { set; get; } 9 | 10 | public virtual ICollection Products { set; get; } 11 | } 12 | } -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | # Summary of the issue 2 | 3 | 4 | 5 | ## Environment 6 | 7 | ``` 8 | The in-use version: 9 | Operating system: 10 | IDE: (e.g. Visual Studio 2015) 11 | ``` 12 | 13 | ## Example code/Steps to reproduce: 14 | 15 | ``` 16 | paste your core code 17 | ``` 18 | 19 | ## Output: 20 | 21 | ``` 22 | Exception message: 23 | Full Stack trace: 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalTests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalDbFirstTests/DBModel.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated from a template. 4 | // 5 | // Manual changes to this file may cause unexpected behavior in your application. 6 | // Manual changes to this file will be overwritten if the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.TestDataLayer/Models/User.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace EFSecondLevelCache.TestDataLayer.Models 5 | { 6 | public class User 7 | { 8 | public int Id { set; get; } 9 | 10 | [Required] 11 | public string Name { set; get; } 12 | 13 | public virtual ICollection Products { set; get; } 14 | 15 | public virtual ICollection Posts { set; get; } 16 | } 17 | } -------------------------------------------------------------------------------- /EFSecondLevelCache/Contracts/IEFCacheKeyHashProvider.cs: -------------------------------------------------------------------------------- 1 | namespace EFSecondLevelCache.Contracts 2 | { 3 | /// 4 | /// The CacheKey Hash Provider Contract. 5 | /// 6 | public interface IEFCacheKeyHashProvider 7 | { 8 | /// 9 | /// Computes the unique hash of the input. 10 | /// 11 | /// the input data to hash 12 | /// Hashed data 13 | string ComputeHash(string data); 14 | } 15 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.TestDataLayer/Models/TPT.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations.Schema; 2 | 3 | namespace EFSecondLevelCache.TestDataLayer.Models 4 | { 5 | public class Post 6 | { 7 | public int Id { get; set; } 8 | public string Title { get; set; } 9 | 10 | [ForeignKey("UserId")] 11 | public virtual User User { set; get; } 12 | public int UserId { set; get; } 13 | } 14 | 15 | [Table("Pages")] 16 | public class Page : Post 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /EFSecondLevelCache/Contracts/EFCacheDebugInfo.cs: -------------------------------------------------------------------------------- 1 | namespace EFSecondLevelCache.Contracts 2 | { 3 | /// 4 | /// Stores the debug information of the caching process. 5 | /// 6 | public class EFCacheDebugInfo 7 | { 8 | /// 9 | /// Stores information of the computed key of the input LINQ query. 10 | /// 11 | public EFCacheKey EFCacheKey { set; get; } 12 | 13 | /// 14 | /// Determines this query is using the 2nd level cache or not. 15 | /// 16 | public bool IsCacheHit { set; get; } 17 | } 18 | } -------------------------------------------------------------------------------- /EFSecondLevelCache/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("EFSecondLevelCache")] 5 | [assembly: AssemblyDescription("Entity Framework Second Level Caching Library.")] 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("Vahid N.")] 8 | [assembly: AssemblyProduct("EFSecondLevelCache")] 9 | [assembly: AssemblyCopyright("Copyright Vahid N. 2015")] 10 | [assembly: AssemblyTrademark("")] 11 | [assembly: AssemblyCulture("")] 12 | 13 | [assembly: ComVisible(false)] 14 | [assembly: Guid("77a71787-ef47-4d2d-8b6e-420ef339edd0")] 15 | 16 | [assembly: AssemblyVersion("1.2.0.0")] 17 | [assembly: AssemblyFileVersion("1.2.0.0")] -------------------------------------------------------------------------------- /EFSecondLevelCache/EFCacheKeyHashProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EFSecondLevelCache.Contracts; 3 | 4 | namespace EFSecondLevelCache 5 | { 6 | /// 7 | /// Computes the unique hash of the input, using the xxHash algorithm. 8 | /// 9 | public class EFCacheKeyHashProvider : IEFCacheKeyHashProvider 10 | { 11 | /// 12 | /// Computes the unique hash of the input. 13 | /// 14 | /// the input data to hash 15 | /// Hashed data using the xxHash algorithm 16 | public string ComputeHash(string data) 17 | { 18 | if(string.IsNullOrWhiteSpace(data)) 19 | throw new ArgumentNullException("data"); 20 | 21 | return string.Format("{0:X}", XxHashUnsafe.ComputeHash(data)); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalDbFirstTests/DBModel.Designer.cs: -------------------------------------------------------------------------------- 1 | // T4 code generation is enabled for model 'D:\Prog\1393\EFSecondLevelCache\EFSecondLevelCache.Tests\EFSecondLevelCache.FunctionalDbFirstTests\DBModel.edmx'. 2 | // To enable legacy code generation, change the value of the 'Code Generation Strategy' designer 3 | // property to 'Legacy ObjectContext'. This property is available in the Properties Window when the model 4 | // is open in the designer. 5 | 6 | // If no context and entity classes have been generated, it may be because you created an empty model but 7 | // have not yet chosen which version of Entity Framework to use. To generate a context class and entity 8 | // classes for your model, open the model in the designer, right-click on the designer surface, and 9 | // select 'Update Model from Database...', 'Generate Database from Model...', or 'Add Code Generation 10 | // Item...'. -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalDbFirstTests/Tag.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated from a template. 4 | // 5 | // Manual changes to this file may cause unexpected behavior in your application. 6 | // Manual changes to this file will be overwritten if the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | namespace EFSecondLevelCache.FunctionalDbFirstTests 11 | { 12 | using System; 13 | using System.Collections.Generic; 14 | 15 | public partial class Tag 16 | { 17 | public Tag() 18 | { 19 | this.Products = new HashSet(); 20 | } 21 | 22 | public int Id { get; set; } 23 | public string Name { get; set; } 24 | 25 | public virtual ICollection Products { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalDbFirstTests/User.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated from a template. 4 | // 5 | // Manual changes to this file may cause unexpected behavior in your application. 6 | // Manual changes to this file will be overwritten if the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | namespace EFSecondLevelCache.FunctionalDbFirstTests 11 | { 12 | using System; 13 | using System.Collections.Generic; 14 | 15 | public partial class User 16 | { 17 | public User() 18 | { 19 | this.Products = new HashSet(); 20 | } 21 | 22 | public int Id { get; set; } 23 | public string Name { get; set; } 24 | 25 | public virtual ICollection Products { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.MockingTests/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.TestDataLayer/Models/Product.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.ComponentModel.DataAnnotations.Schema; 4 | 5 | namespace EFSecondLevelCache.TestDataLayer.Models 6 | { 7 | public class Product 8 | { 9 | [Key] 10 | public int ProductId { get; set; } 11 | 12 | [StringLength(30)] 13 | [Required] 14 | public string ProductNumber { get; set; } 15 | 16 | [StringLength(50)] 17 | [Required] 18 | [Index(IsUnique = true)] 19 | public string ProductName { get; set; } 20 | 21 | [StringLength(int.MaxValue)] 22 | public string Notes { get; set; } 23 | 24 | public bool IsActive { get; set; } 25 | 26 | public virtual ICollection Tags { set; get; } 27 | 28 | [ForeignKey("UserId")] 29 | public virtual User User { set; get; } 30 | public int UserId { set; get; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /EFSecondLevelCache/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalTests/Bootstrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity; 3 | using EFSecondLevelCache.TestDataLayer.DataLayer; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace EFSecondLevelCache.FunctionalTests 7 | { 8 | [TestClass] 9 | public class Bootstrapper 10 | { 11 | [AssemblyInitialize] 12 | public static void Initialize(TestContext context) 13 | { 14 | startDb(); 15 | } 16 | 17 | [AssemblyCleanup] 18 | public static void AssemblyCleanup() 19 | { 20 | } 21 | 22 | private static void startDb() 23 | { 24 | AppDomain.CurrentDomain.SetData("DataDirectory", AppDomain.CurrentDomain.BaseDirectory); 25 | Database.SetInitializer(new MigrateDatabaseToLatestVersion()); 26 | using (var ctx = new SampleContext()) 27 | { 28 | ctx.Database.Initialize(force: true); 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.TestDataLayer/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /EFSecondLevelCache/Contracts/IEFCacheKeyProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Linq.Expressions; 3 | 4 | namespace EFSecondLevelCache.Contracts 5 | { 6 | /// 7 | /// CacheKeyProvider Contract. 8 | /// 9 | public interface IEFCacheKeyProvider 10 | { 11 | /// 12 | /// Gets an EF query and returns its hash to store in the cache. 13 | /// 14 | /// Type of the entity 15 | /// The EF query. 16 | /// An expression tree that represents a LINQ query. 17 | /// Its default value is EF_. 18 | /// If you think the computed hash of the query is not enough, set this value. 19 | /// Information of the computed key of the input LINQ query. 20 | EFCacheKey GetEFCacheKey(IQueryable query, Expression expression, string keyHashPrefix = EFCacheKey.KeyHashPrefix, string saltKey = ""); 21 | } 22 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.MockingTests/TestDbAsyncEnumerable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data.Entity.Infrastructure; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace EFSecondLevelCache.MockingTests 7 | { 8 | public class TestDbAsyncEnumerable : EnumerableQuery, IDbAsyncEnumerable, IQueryable 9 | { 10 | public TestDbAsyncEnumerable(IEnumerable enumerable) 11 | : base(enumerable) 12 | { } 13 | 14 | public TestDbAsyncEnumerable(Expression expression) 15 | : base(expression) 16 | { } 17 | 18 | public IDbAsyncEnumerator GetAsyncEnumerator() 19 | { 20 | return new EFAsyncEnumerator(this.AsEnumerable().GetEnumerator()); 21 | } 22 | 23 | IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() 24 | { 25 | return GetAsyncEnumerator(); 26 | } 27 | 28 | IQueryProvider IQueryable.Provider 29 | { 30 | get { return new TestDbAsyncQueryProvider(this); } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalDbFirstTests/DBModel.edmx.diagram: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /EFSecondLevelCache/EFSecondLevelCache.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | EFSecondLevelCache 5 | $version$ 6 | Vahid Nasiri 7 | Vahid Nasiri 8 | https://github.com/VahidN/EFSecondLevelCache/blob/master/LICENSE.md 9 | https://github.com/VahidN/EFSecondLevelCache 10 | false 11 | $description$ 12 | $description$ 13 | en-US 14 | EntityFramework Cache Caching SecondLevelCache EF6 ORM Interceptor 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalDbFirstTests/Product.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated from a template. 4 | // 5 | // Manual changes to this file may cause unexpected behavior in your application. 6 | // Manual changes to this file will be overwritten if the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | namespace EFSecondLevelCache.FunctionalDbFirstTests 11 | { 12 | using System; 13 | using System.Collections.Generic; 14 | 15 | public partial class Product 16 | { 17 | public Product() 18 | { 19 | this.Tags = new HashSet(); 20 | } 21 | 22 | public int ProductId { get; set; } 23 | public string ProductNumber { get; set; } 24 | public string ProductName { get; set; } 25 | public string Notes { get; set; } 26 | public bool IsActive { get; set; } 27 | public int UserId { get; set; } 28 | 29 | public virtual User User { get; set; } 30 | public virtual ICollection Tags { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalDbFirstTests/DBModel.Context.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated from a template. 4 | // 5 | // Manual changes to this file may cause unexpected behavior in your application. 6 | // Manual changes to this file will be overwritten if the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | namespace EFSecondLevelCache.FunctionalDbFirstTests 11 | { 12 | using System; 13 | using System.Data.Entity; 14 | using System.Data.Entity.Infrastructure; 15 | 16 | public partial class TestDB2015Entities : DbContext 17 | { 18 | public TestDB2015Entities() 19 | : base("name=TestDB2015Entities") 20 | { 21 | } 22 | 23 | protected override void OnModelCreating(DbModelBuilder modelBuilder) 24 | { 25 | throw new UnintentionalCodeFirstException(); 26 | } 27 | 28 | public virtual DbSet Products { get; set; } 29 | public virtual DbSet Tags { get; set; } 30 | public virtual DbSet Users { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /EFSecondLevelCache/Contracts/EFCacheKey.cs: -------------------------------------------------------------------------------- 1 | namespace EFSecondLevelCache.Contracts 2 | { 3 | /// 4 | /// Stores information of the computed key of the input LINQ query. 5 | /// 6 | public class EFCacheKey 7 | { 8 | /// 9 | /// Its default value is EF_. 10 | /// 11 | public const string KeyHashPrefix = "EF_"; 12 | 13 | /// 14 | /// The computed key of the input LINQ query. 15 | /// 16 | public string Key { set; get; } 17 | 18 | /// 19 | /// Hash of the input LINQ query's computed key. 20 | /// 21 | public string KeyHash { set; get; } 22 | 23 | /// 24 | /// Determines which entities are used in this LINQ query. 25 | /// This array will be used to invalidate the related cache of all related queries automatically. 26 | /// 27 | public string[] CacheDependencies { set; get; } 28 | 29 | /// 30 | /// Stores information of the computed key of the input LINQ query. 31 | /// 32 | public EFCacheKey() 33 | { 34 | CacheDependencies = new[] { string.Empty }; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.PerformanceTests/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.UnitTests/XxHashTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace EFSecondLevelCache.UnitTests 4 | { 5 | internal sealed class TestConstants 6 | { 7 | public static readonly string Empty = ""; 8 | public static readonly string FooBar = "foobar"; 9 | public static readonly string LoremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut ornare aliquam mauris, at volutpat massa. Phasellus pulvinar purus eu venenatis commodo."; 10 | } 11 | 12 | [TestClass] 13 | public class XxHashTests 14 | { 15 | [TestMethod] 16 | public void TestEmptyXxHashReturnsCorrectValue() 17 | { 18 | var hash = XxHashUnsafe.ComputeHash(TestConstants.Empty); 19 | Assert.AreEqual((uint)0x02cc5d05, hash); 20 | } 21 | 22 | [TestMethod] 23 | public void TestFooBarXxHashReturnsCorrectValue() 24 | { 25 | var hash = XxHashUnsafe.ComputeHash(TestConstants.FooBar); 26 | Assert.AreEqual((uint)2348340516, hash); 27 | } 28 | 29 | [TestMethod] 30 | public void TestLoremIpsumXxHashReturnsCorrectValue() 31 | { 32 | var hash = XxHashUnsafe.ComputeHash(TestConstants.LoremIpsum); 33 | Assert.AreEqual((uint)4046722717, hash); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalTests/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalDbFirstTests/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.UnitTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("EFSecondLevelCache.UnitTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("EFSecondLevelCache.UnitTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("19693e05-0400-4f17-9f99-dbd75a9f3c48")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.MockingTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("EFSecondLevelCache.MockingTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("EFSecondLevelCache.MockingTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("86e6d7cf-1fb7-4e35-8c00-54efbc08ca2b")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.TestDataLayer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("EFSecondLevelCache.TestDataLayer")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("EFSecondLevelCache.TestDataLayer")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("aa31ad86-f876-453e-bed0-8753da988bbf")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("EFSecondLevelCache.FunctionalTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("EFSecondLevelCache.FunctionalTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f6fed369-7d8d-4ad6-b83c-be1f8d95f1a1")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.PerformanceTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("EFSecondLevelCache.PerformanceTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("EFSecondLevelCache.PerformanceTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("fa46725a-66d3-48cc-8a1f-a2903eed346b")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.MockingTests/TestDbAsyncQueryProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Entity.Infrastructure; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace EFSecondLevelCache.MockingTests 8 | { 9 | public class TestDbAsyncQueryProvider : IDbAsyncQueryProvider 10 | { 11 | private readonly IQueryProvider _inner; 12 | 13 | public TestDbAsyncQueryProvider(IQueryProvider inner) 14 | { 15 | _inner = inner; 16 | } 17 | 18 | public IQueryable CreateQuery(Expression expression) 19 | { 20 | return new TestDbAsyncEnumerable(expression); 21 | } 22 | 23 | public IQueryable CreateQuery(Expression expression) 24 | { 25 | return new TestDbAsyncEnumerable(expression); 26 | } 27 | 28 | public object Execute(Expression expression) 29 | { 30 | return _inner.Execute(expression); 31 | } 32 | 33 | public TResult Execute(Expression expression) 34 | { 35 | return _inner.Execute(expression); 36 | } 37 | 38 | public Task ExecuteAsync(Expression expression, CancellationToken cancellationToken) 39 | { 40 | return Task.FromResult(Execute(expression)); 41 | } 42 | 43 | public Task ExecuteAsync(Expression expression, CancellationToken cancellationToken) 44 | { 45 | return Task.FromResult(Execute(expression)); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalDbFirstTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("EFSecondLevelCache.FunctionalDbFirstTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("EFSecondLevelCache.FunctionalDbFirstTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("701a2095-7f00-4460-a853-b32e90256d9a")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /EFSecondLevelCache/Contracts/EFCachePolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web.Caching; 3 | 4 | namespace EFSecondLevelCache.Contracts 5 | { 6 | /// 7 | /// EFCachePolicy determines the AbsoluteExpiration time and Priority of the cache. 8 | /// 9 | public class EFCachePolicy 10 | { 11 | private DateTime? _absoluteExpiration; 12 | 13 | /// 14 | /// Its deafult value is 20 minutes later. 15 | /// 16 | public DateTime? AbsoluteExpiration 17 | { 18 | set { _absoluteExpiration = value; } 19 | get { return _absoluteExpiration ?? (_absoluteExpiration = DateTime.Now.AddMinutes(20)); } 20 | } 21 | 22 | /// 23 | /// Its deafult value is EF_. 24 | /// 25 | public string KeyHashPrefix { set; get; } 26 | 27 | /// 28 | /// If you think the computed hash of the query is not enough, set this value. 29 | /// Its deafult value is string.Empty. 30 | /// 31 | public string SaltKey { set; get; } 32 | 33 | /// 34 | /// Its deafult value is CacheItemPriority.Normal. 35 | /// 36 | public CacheItemPriority Priority { set; get; } 37 | 38 | /// 39 | /// EFCachePolicy determines the AbsoluteExpiration time and Priority of the cache. 40 | /// 41 | public EFCachePolicy() 42 | { 43 | KeyHashPrefix = EFCacheKey.KeyHashPrefix; 44 | SaltKey = string.Empty; 45 | Priority = CacheItemPriority.Normal; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalTests/PerformanceTests.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Entity; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using EFSecondLevelCache.TestDataLayer.DataLayer; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace EFSecondLevelCache.FunctionalTests 8 | { 9 | [TestClass] 10 | public class PerformanceTests 11 | { 12 | [TestInitialize] 13 | public void ClearEFGlobalCacheBeforeEachTest() 14 | { 15 | new EFCacheServiceProvider().ClearAllCachedEntries(); 16 | } 17 | 18 | [TestMethod] 19 | public void UncachedQueries() 20 | { 21 | using (var context = new SampleContext()) 22 | { 23 | var watch = Stopwatch.StartNew(); 24 | for (var i = 0; i <= 10000; i++) 25 | { 26 | var products = context.Products.Include(x => x.Tags).ToList(); 27 | } 28 | Trace.WriteLine(string.Format("10000 iterations in {0} ms. Average speed: {1} iterations/second.", watch.ElapsedMilliseconds, (int)(10000 / watch.Elapsed.TotalSeconds))); 29 | } 30 | } 31 | 32 | [TestMethod] 33 | public void CachedQueries() 34 | { 35 | using (var context = new SampleContext()) 36 | { 37 | var watch = Stopwatch.StartNew(); 38 | for (var i = 0; i <= 10000; i++) 39 | { 40 | var products = context.Products.Include(x => x.Tags).Cacheable().ToList(); 41 | } 42 | Trace.WriteLine(string.Format("10000 iterations in {0} ms. Average speed: {1} iterations/second.", watch.ElapsedMilliseconds, (int)(10000 / watch.Elapsed.TotalSeconds))); 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.PerformanceTests/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using EFSecondLevelCache.TestDataLayer.DataLayer; 6 | 7 | namespace EFSecondLevelCache.PerformanceTests 8 | { 9 | class Program 10 | { 11 | private static void cachedQueries() 12 | { 13 | Console.WriteLine("CachedQueries"); 14 | using (var context = new SampleContext()) 15 | { 16 | var watch = Stopwatch.StartNew(); 17 | for (var i = 0; i <= 10000; i++) 18 | { 19 | var products = context.Products.Include(x => x.Tags).Cacheable().ToList(); 20 | } 21 | Console.WriteLine("10000 iterations in {0} ms. Average speed: {1} iterations/second.", watch.ElapsedMilliseconds, (int)(10000 / watch.Elapsed.TotalSeconds)); 22 | } 23 | } 24 | 25 | static void Main(string[] args) 26 | { 27 | startDb(); 28 | cachedQueries(); 29 | uncachedQueries(); 30 | } 31 | 32 | private static void startDb() 33 | { 34 | Database.SetInitializer(new MigrateDatabaseToLatestVersion()); 35 | using (var ctx = new SampleContext()) 36 | { 37 | ctx.Database.Initialize(force: true); 38 | } 39 | } 40 | 41 | private static void uncachedQueries() 42 | { 43 | Console.WriteLine("UncachedQueries"); 44 | using (var context = new SampleContext()) 45 | { 46 | var watch = Stopwatch.StartNew(); 47 | for (var i = 0; i <= 10000; i++) 48 | { 49 | var products = context.Products.Include(x => x.Tags).ToList(); 50 | } 51 | Console.WriteLine("10000 iterations in {0} ms. Average speed: {1} iterations/second.", watch.ElapsedMilliseconds, (int)(10000 / watch.Elapsed.TotalSeconds)); 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /EFSecondLevelCache/EFAsyncEnumerator.cs: -------------------------------------------------------------------------------- 1 | #if !NET40 2 | using System.Collections.Generic; 3 | using System.Data.Entity.Infrastructure; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace EFSecondLevelCache 8 | { 9 | /// 10 | /// Asynchronous version of the IEnumerator of T interface that allows elements to be retrieved asynchronously. 11 | /// 12 | /// 13 | public class EFAsyncEnumerator : IDbAsyncEnumerator 14 | { 15 | private readonly IEnumerator _inner; 16 | 17 | /// 18 | /// Asynchronous version of the IEnumerator of T interface that allows elements to be retrieved asynchronously. 19 | /// 20 | /// The inner IEnumerator 21 | public EFAsyncEnumerator(IEnumerator inner) 22 | { 23 | _inner = inner; 24 | } 25 | 26 | /// 27 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 28 | /// 29 | public void Dispose() 30 | { 31 | _inner.Dispose(); 32 | } 33 | 34 | /// 35 | /// Advances the enumerator to the next element in the sequence, returning the result asynchronously. 36 | /// 37 | /// A System.Threading.CancellationToken to observe while waiting for the task to complete. 38 | /// A task that represents the asynchronous operation. The task result contains true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the sequence. 39 | public Task MoveNextAsync(CancellationToken cancellationToken) 40 | { 41 | return Task.FromResult(_inner.MoveNext()); 42 | } 43 | 44 | /// 45 | /// Gets the current element in the iteration. 46 | /// 47 | public T Current 48 | { 49 | get { return _inner.Current; } 50 | } 51 | 52 | /// 53 | /// Gets the current element in the iteration. 54 | /// 55 | object IDbAsyncEnumerator.Current 56 | { 57 | get { return Current; } 58 | } 59 | } 60 | } 61 | #endif -------------------------------------------------------------------------------- /EFSecondLevelCache/EFCommandTreeCollector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity.Core.Common.CommandTrees; 3 | using System.Data.Entity.Core.Metadata.Edm; 4 | using System.Data.Entity.Core.Objects; 5 | using System.Data.Entity.Infrastructure.Interception; 6 | 7 | namespace EFSecondLevelCache 8 | { 9 | /// 10 | /// Receives notifications when Entity Framework creates a DbCommandTree. 11 | /// It collects only DbQueryCommandTrees. 12 | /// 13 | public class EFCommandTreeCollector : IDbCommandTreeInterceptor, IDisposable 14 | { 15 | private readonly ObjectContext _objectContext; 16 | 17 | /// 18 | /// Receives notifications when Entity Framework creates a DbCommandTree. 19 | /// 20 | /// The associated objectContext of the collected DbQueryCommandTree. 21 | public EFCommandTreeCollector(ObjectContext objectContext) 22 | { 23 | _objectContext = objectContext; 24 | DbInterception.Add(this); 25 | } 26 | 27 | /// 28 | /// The associated DbQueryCommandTree of the given ObjectContext. 29 | /// 30 | public DbQueryCommandTree DbQueryCommandTree { get; private set; } 31 | 32 | /// 33 | /// This method is called after a new DbCommandTree has been created. 34 | /// It collects only DbQueryCommandTrees. 35 | /// 36 | /// Represents contextual information associated with calls into IDbCommandTreeInterceptor implementations. 37 | public void TreeCreated(DbCommandTreeInterceptionContext interceptionContext) 38 | { 39 | if (interceptionContext.Result.CommandTreeKind == DbCommandTreeKind.Query 40 | && interceptionContext.Result.DataSpace == DataSpace.SSpace) 41 | { 42 | if (interceptionContext.ObjectContexts.Contains(_objectContext, ReferenceEquals)) 43 | { 44 | DbQueryCommandTree = (DbQueryCommandTree)interceptionContext.Result; 45 | } 46 | } 47 | } 48 | 49 | /// 50 | /// Removes the registered IDbInterceptor so that it will no longer receive notifications. 51 | /// 52 | public void Dispose() 53 | { 54 | DbInterception.Remove(this); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /EFSecondLevelCache/readme.txt: -------------------------------------------------------------------------------- 1 | EFSecondLevelCache 2 | ======= 3 | Entity Framework 6.x Second Level Caching Library. 4 | 5 | Second level caching is a query cache. The results of EF commands will be stored in the cache, 6 | so that the same EF commands will retrieve their data from the cache rather than executing them 7 | against the database again. 8 | 9 | 10 | Usage: 11 | 1- Setting up the cache invalidation by overriding the SaveChanges method to prevent stale reads: 12 | 13 | namespace EFSecondLevelCache.TestDataLayer.DataLayer 14 | { 15 | public class SampleContext : DbContext 16 | { 17 | // public DbSet Products { get; set; } 18 | 19 | public SampleContext() 20 | : base("connectionString1") 21 | { 22 | } 23 | 24 | public override int SaveChanges() 25 | { 26 | return SaveAllChanges(invalidateCacheDependencies: true); 27 | } 28 | 29 | public int SaveAllChanges(bool invalidateCacheDependencies = true) 30 | { 31 | var changedEntityNames = getChangedEntityNames(); 32 | var result = base.SaveChanges(); 33 | if (invalidateCacheDependencies) 34 | { 35 | new EFCacheServiceProvider().InvalidateCacheDependencies(changedEntityNames); 36 | } 37 | return result; 38 | } 39 | 40 | private string[] getChangedEntityNames() 41 | { 42 | // Updated version of this method: \EFSecondLevelCache\EFSecondLevelCache.Tests\EFSecondLevelCache.TestDataLayer\DataLayer\SampleContext.cs 43 | return this.ChangeTracker.Entries() 44 | .Where(x => x.State == EntityState.Added || 45 | x.State == EntityState.Modified || 46 | x.State == EntityState.Deleted) 47 | .Select(x => System.Data.Entity.Core.Objects.ObjectContext.GetObjectType(x.Entity.GetType()).FullName) 48 | .Distinct() 49 | .ToArray(); 50 | } 51 | } 52 | } 53 | 54 | Sometimes you don't want to invalidate the cache when non important properties such as NumberOfViews are updated. 55 | In these cases, try SaveAllChanges(invalidateCacheDependencies: false), before updating the data. 56 | 57 | 2- Then to cache the results of the normal queries like: 58 | var products = context.Products.Include(x => x.Tags).FirstOrDefault(); 59 | 60 | We can use the new `Cacheable()` extension method: 61 | var products = context.Products.Include(x => x.Tags).Cacheable().FirstOrDefault(); // Async methods are supported too. 62 | 63 | 64 | Notes: 65 | Good candidates for query caching are global site's settings, list of public articles or comments 66 | and not frequently changed, private or specific data to each user. 67 | If a page requires authentication, its data shouldn't be cached. 68 | 69 | Project's Url: 70 | https://github.com/VahidN/EFSecondLevelCache -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /EFSecondLevelCache/Contracts/IEFCacheServiceProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Web.Caching; 4 | 5 | namespace EFSecondLevelCache.Contracts 6 | { 7 | /// 8 | /// Cache Service Provider Contract. 9 | /// 10 | public interface IEFCacheServiceProvider 11 | { 12 | /// 13 | /// Returns list of the cached keys. 14 | /// 15 | IList AllCachedKeys { get; } 16 | 17 | /// 18 | /// Removes the cached entries added by this library. 19 | /// 20 | /// Its default value is EF_. 21 | void ClearAllCachedEntries(string keyHashPrefix = EFCacheKey.KeyHashPrefix); 22 | 23 | /// 24 | /// Gets all of the cached keys, added by this library. 25 | /// 26 | /// Its default value is EF_. 27 | /// list of the keys 28 | IList GetAllEFCachedKeys(string keyHashPrefix = EFCacheKey.KeyHashPrefix); 29 | 30 | /// 31 | /// Gets a cached entry by key. 32 | /// 33 | /// key to find 34 | /// cached value 35 | object GetValue(string cacheKey); 36 | 37 | /// 38 | /// Adds a new item to the cache. 39 | /// 40 | /// key 41 | /// value 42 | /// cache dependencies 43 | /// absolute expiration time 44 | /// its default value is CacheItemPriority.Normal 45 | void InsertValue(string cacheKey, object value, 46 | string[] rootCacheKeys, 47 | DateTime absoluteExpiration, 48 | CacheItemPriority priority = CacheItemPriority.Normal); 49 | 50 | /// 51 | /// Invalidates all of the cache entries which are dependent on any of the specified root keys. 52 | /// 53 | /// cache dependencies 54 | void InvalidateCacheDependencies(string[] rootCacheKeys); 55 | 56 | /// 57 | /// The name of the cache keys used to clear the cache. All cached items depend on these keys. 58 | /// 59 | /// cache dependencies 60 | void StoreRootCacheKeys(string[] rootCacheKeys); 61 | 62 | /// 63 | /// `HttpRuntime.Cache.Insert` won't accept null values. 64 | /// So we need a custom Null object here. It should be defined `static readonly` in your code. 65 | /// 66 | object NullObject { get; } 67 | } 68 | } -------------------------------------------------------------------------------- /EFSecondLevelCache/LinqToObjectsCacheKeyProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using EFSecondLevelCache.Contracts; 6 | 7 | namespace EFSecondLevelCache 8 | { 9 | /// 10 | /// A custom cache key provider for normal LINQ to objects queries, results of a Mocking process. 11 | /// 12 | public class LinqToObjectsCacheKeyProvider : IEFCacheKeyProvider 13 | { 14 | private readonly IEFCacheKeyHashProvider _cacheKeyHashProvider; 15 | 16 | /// 17 | /// A custom cache key provider for normal LINQ to objects queries. 18 | /// 19 | /// Provides the custom hashing algorithm. 20 | public LinqToObjectsCacheKeyProvider(IEFCacheKeyHashProvider cacheKeyHashProvider) 21 | { 22 | _cacheKeyHashProvider = cacheKeyHashProvider; 23 | } 24 | 25 | /// 26 | /// Gets a LINQ to objects query and returns its hashed key to store in the cache. 27 | /// 28 | /// Type of the entity 29 | /// The input query. 30 | /// An expression tree that represents a LINQ query. 31 | /// Its default value is EF_. 32 | /// If you think the computed hash of the query is not enough, set this value. 33 | /// Information of the computed key of the input LINQ query. 34 | public EFCacheKey GetEFCacheKey( 35 | IQueryable query, 36 | Expression expression, 37 | string keyHashPrefix = EFCacheKey.KeyHashPrefix, 38 | string saltKey = "") 39 | { 40 | var traceString = GetDebugView(expression); 41 | var key = string.Format("{0}{1}{2}", traceString, Environment.NewLine, saltKey); 42 | var keyHash = string.Format("{0}{1}", keyHashPrefix, _cacheKeyHashProvider.ComputeHash(key)); 43 | return new EFCacheKey { Key = key, KeyHash = keyHash, CacheDependencies = new[] { typeof(T).FullName } }; 44 | } 45 | 46 | ///  47 | /// Gets the string representation of an Expression. 48 | ///  49 | /// the input expression 50 | /// The string representation of the Expression 51 | public string GetDebugView(Expression expression) 52 | { 53 | var debugViewDelegate = typeof(Expression).GetPropertyGetterDelegateFromCache( 54 | "DebugView", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); 55 | var debugView = debugViewDelegate(expression); 56 | if (debugView == null) 57 | { 58 | throw new NotSupportedException("Failed to get DebugView."); 59 | } 60 | 61 | return (string)debugView; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.TestDataLayer/DataLayer/SampleContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Entity; 4 | using System.Linq; 5 | using EFSecondLevelCache.TestDataLayer.Models; 6 | 7 | namespace EFSecondLevelCache.TestDataLayer.DataLayer 8 | { 9 | public static class ChangeTrackerExtenstions 10 | { 11 | public static string[] GetChangedEntityNames(this DbContext dbContext) 12 | { 13 | if (!dbContext.Configuration.AutoDetectChangesEnabled) 14 | { 15 | dbContext.ChangeTracker.DetectChanges(); 16 | } 17 | 18 | var typesList = new List(); 19 | foreach (var type in dbContext.getChangedEntityTypes()) 20 | { 21 | typesList.Add(type); 22 | typesList.AddRange(type.getBaseTypes().ToList()); 23 | } 24 | 25 | var changedEntityNames = typesList 26 | .Select(type => System.Data.Entity.Core.Objects.ObjectContext.GetObjectType(type).FullName) 27 | .Distinct() 28 | .ToArray(); 29 | 30 | return changedEntityNames; 31 | } 32 | 33 | private static IEnumerable getBaseTypes(this Type type) 34 | { 35 | if (type.BaseType == null) return type.GetInterfaces(); 36 | 37 | return Enumerable.Repeat(type.BaseType, 1) 38 | .Concat(type.GetInterfaces()) 39 | .Concat(type.GetInterfaces().SelectMany(getBaseTypes)) 40 | .Concat(type.BaseType.getBaseTypes()); 41 | } 42 | 43 | private static IEnumerable getChangedEntityTypes(this DbContext dbContext) 44 | { 45 | return dbContext.ChangeTracker.Entries().Where( 46 | dbEntityEntry => dbEntityEntry.State == EntityState.Added || 47 | dbEntityEntry.State == EntityState.Modified || 48 | dbEntityEntry.State == EntityState.Deleted) 49 | .Select(dbEntityEntry => System.Data.Entity.Core.Objects.ObjectContext.GetObjectType(dbEntityEntry.Entity.GetType())); 50 | } 51 | } 52 | 53 | public class SampleContext : DbContext 54 | { 55 | public SampleContext() 56 | : base("connectionString1") 57 | { 58 | } 59 | 60 | // This is virtual because Moq needs to override the behavior. 61 | public virtual DbSet Posts { get; set; } 62 | public virtual DbSet Products { get; set; } 63 | public virtual DbSet Tags { get; set; } 64 | public virtual DbSet Users { get; set; } 65 | 66 | public int SaveAllChanges(bool invalidateCacheDependencies = true) 67 | { 68 | var changedEntityNames = this.GetChangedEntityNames(); 69 | var result = base.SaveChanges(); 70 | if (invalidateCacheDependencies) 71 | { 72 | new EFCacheServiceProvider().InvalidateCacheDependencies(changedEntityNames); 73 | } 74 | return result; 75 | } 76 | 77 | public override int SaveChanges() 78 | { 79 | return SaveAllChanges(invalidateCacheDependencies: true); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EFSecondLevelCache 2 | ======= 3 | Entity Framework 6.x Second Level Caching Library. 4 | 5 | Second level caching is a query cache. The results of EF commands will be stored in the cache, so that the same EF commands will retrieve their data from the cache rather than executing them against the database again. 6 | 7 | Install via NuGet 8 | ----------------- 9 | To install EFSecondLevelCache, run the following command in the Package Manager Console: 10 | 11 | ``` 12 | PM> Install-Package EFSecondLevelCache 13 | ``` 14 | 15 | You can also view the [package page](http://www.nuget.org/packages/EFSecondLevelCache/) on NuGet. 16 | 17 | 18 | 19 | Usage: 20 | ----------------- 21 | 1- [Setting up the cache invalidation](/EFSecondLevelCache.Tests/EFSecondLevelCache.TestDataLayer/DataLayer/SampleContext.cs) by overriding the SaveChanges method to prevent stale reads: 22 | 23 | ```csharp 24 | namespace EFSecondLevelCache.TestDataLayer.DataLayer 25 | { 26 | public class SampleContext : DbContext 27 | { 28 | // public DbSet Products { get; set; } 29 | 30 | public SampleContext() 31 | : base("connectionString1") 32 | { 33 | } 34 | 35 | public override int SaveChanges() 36 | { 37 | return SaveAllChanges(invalidateCacheDependencies: true); 38 | } 39 | 40 | public int SaveAllChanges(bool invalidateCacheDependencies = true) 41 | { 42 | var changedEntityNames = getChangedEntityNames(); 43 | var result = base.SaveChanges(); 44 | if (invalidateCacheDependencies) 45 | { 46 | new EFCacheServiceProvider().InvalidateCacheDependencies(changedEntityNames); 47 | } 48 | return result; 49 | } 50 | 51 | private string[] getChangedEntityNames() 52 | { 53 | // Updated version of this method: \EFSecondLevelCache\EFSecondLevelCache.Tests\EFSecondLevelCache.TestDataLayer\DataLayer\SampleContext.cs 54 | return this.ChangeTracker.Entries() 55 | .Where(x => x.State == EntityState.Added || 56 | x.State == EntityState.Modified || 57 | x.State == EntityState.Deleted) 58 | .Select(x => System.Data.Entity.Core.Objects.ObjectContext.GetObjectType(x.Entity.GetType()).FullName) 59 | .Distinct() 60 | .ToArray(); 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | Sometimes you don't want to invalidate the cache when non important properties such as NumberOfViews are updated. 67 | In these cases, try `SaveAllChanges(invalidateCacheDependencies: false)`, before updating the data. 68 | 69 | 70 | 2- Then to cache the results of the normal queries like: 71 | ```csharp 72 | var products = context.Products.Include(x => x.Tags).FirstOrDefault(); 73 | ``` 74 | We can use the new `Cacheable()` extension method: 75 | ```csharp 76 | var products = context.Products.Include(x => x.Tags).Cacheable().FirstOrDefault(); // Async methods are supported too. 77 | ``` 78 | 79 | 80 | Notes: 81 | ----------------- 82 | Good candidates for query caching are global site's settings, 83 | list of `public` articles or comments and not frequently changed, 84 | private or specific data to each user. 85 | If a page requires authentication, its data shouldn't be cached. 86 | -------------------------------------------------------------------------------- /EFSecondLevelCache/FastReflectionUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | 7 | namespace EFSecondLevelCache 8 | { 9 | /// 10 | /// Fast reflection, using compiled property getters. 11 | /// 12 | public static class FastReflectionUtils 13 | { 14 | private static readonly 15 | ConcurrentDictionary>> _gettersCache 16 | = new ConcurrentDictionary>>(); 17 | 18 | ///  19 | /// Gets a compiled property getter delegate for the underlying type. 20 | ///  21 | /// 22 | /// 23 | /// 24 | /// 25 | public static Func GetPropertyGetterDelegate( 26 | this Type type, string propertyName, BindingFlags bindingFlags) 27 | { 28 | var property = type.GetProperty(propertyName, bindingFlags); 29 | if (property == null) 30 | throw new InvalidOperationException(string.Format("Couldn't find the {0} property.", propertyName)); 31 | 32 | var getMethod = property.GetGetMethod(nonPublic: true); 33 | if (getMethod == null) 34 | throw new InvalidOperationException(string.Format("Couldn't get the GetMethod of {0}", type)); 35 | 36 | var instanceParam = Expression.Parameter(typeof(object), "instance"); 37 | var getterExpression = Expression.Convert( 38 | Expression.Call(Expression.Convert(instanceParam, type), getMethod), typeof(object)); 39 | return Expression.Lambda>(getterExpression, instanceParam).Compile(); 40 | } 41 | 42 | ///  43 | /// Gets a compiled property getter delegate for the underlying type. 44 | ///  45 | /// 46 | /// 47 | /// 48 | /// 49 | public static Func GetPropertyGetterDelegateFromCache( 50 | this Type type, string propertyName, BindingFlags bindingFlags) 51 | { 52 | ConcurrentDictionary> getterDictionary; 53 | Func getter; 54 | if (_gettersCache.TryGetValue(propertyName, out getterDictionary)) 55 | { 56 | if (getterDictionary.TryGetValue(type, out getter)) 57 | { 58 | return getter; 59 | } 60 | } 61 | 62 | getter = type.GetPropertyGetterDelegate(propertyName, bindingFlags); 63 | if (getter == null) 64 | { 65 | throw new NotSupportedException(string.Format("Failed to get {0}Getter.", propertyName)); 66 | } 67 | 68 | if (getterDictionary != null) 69 | { 70 | getterDictionary.TryAdd(type, getter); 71 | } 72 | else 73 | { 74 | _gettersCache.TryAdd(propertyName, 75 | new ConcurrentDictionary>( 76 | new Dictionary> 77 | { 78 | { type, getter } 79 | })); 80 | } 81 | 82 | return getter; 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # Roslyn cache directories 20 | *.ide/ 21 | 22 | # MSTest test Results 23 | [Tt]est[Rr]esult*/ 24 | [Bb]uild[Ll]og.* 25 | 26 | #NUNIT 27 | *.VisualState.xml 28 | TestResult.xml 29 | 30 | # Build Results of an ATL Project 31 | [Dd]ebugPS/ 32 | [Rr]eleasePS/ 33 | dlldata.c 34 | 35 | *_i.c 36 | *_p.c 37 | *_i.h 38 | *.ilk 39 | *.meta 40 | *.obj 41 | *.pch 42 | *.pdb 43 | *.pgc 44 | *.pgd 45 | *.rsp 46 | *.sbr 47 | *.tlb 48 | *.tli 49 | *.tlh 50 | *.tmp 51 | *.tmp_proj 52 | *.log 53 | *.vspscc 54 | *.vssscc 55 | .builds 56 | *.pidb 57 | *.svclog 58 | *.scc 59 | 60 | # Chutzpah Test files 61 | _Chutzpah* 62 | 63 | # Visual C++ cache files 64 | ipch/ 65 | *.aps 66 | *.ncb 67 | *.opensdf 68 | *.sdf 69 | *.cachefile 70 | 71 | # Visual Studio profiler 72 | *.psess 73 | *.vsp 74 | *.vspx 75 | 76 | # TFS 2012 Local Workspace 77 | $tf/ 78 | 79 | # Guidance Automation Toolkit 80 | *.gpState 81 | 82 | # ReSharper is a .NET coding add-in 83 | _ReSharper*/ 84 | *.[Rr]e[Ss]harper 85 | *.DotSettings.user 86 | 87 | # JustCode is a .NET coding addin-in 88 | .JustCode 89 | 90 | # TeamCity is a build add-in 91 | _TeamCity* 92 | 93 | # DotCover is a Code Coverage Tool 94 | *.dotCover 95 | 96 | # NCrunch 97 | _NCrunch_* 98 | .*crunch*.local.xml 99 | 100 | # MightyMoose 101 | *.mm.* 102 | AutoTest.Net/ 103 | 104 | # Web workbench (sass) 105 | .sass-cache/ 106 | 107 | # Installshield output folder 108 | [Ee]xpress/ 109 | 110 | # DocProject is a documentation generator add-in 111 | DocProject/buildhelp/ 112 | DocProject/Help/*.HxT 113 | DocProject/Help/*.HxC 114 | DocProject/Help/*.hhc 115 | DocProject/Help/*.hhk 116 | DocProject/Help/*.hhp 117 | DocProject/Help/Html2 118 | DocProject/Help/html 119 | 120 | # Click-Once directory 121 | publish/ 122 | 123 | # Publish Web Output 124 | *.[Pp]ublish.xml 125 | *.azurePubxml 126 | ## TODO: Comment the next line if you want to checkin your 127 | ## web deploy settings but do note that will include unencrypted 128 | ## passwords 129 | #*.pubxml 130 | 131 | # NuGet Packages Directory 132 | packages/* 133 | ## TODO: If the tool you use requires repositories.config 134 | ## uncomment the next line 135 | #!packages/repositories.config 136 | 137 | # Enable "build/" folder in the NuGet Packages folder since 138 | # NuGet packages use it for MSBuild targets. 139 | # This line needs to be after the ignore of the build folder 140 | # (and the packages folder if the line above has been uncommented) 141 | !packages/build/ 142 | 143 | # Windows Azure Build Output 144 | csx/ 145 | *.build.csdef 146 | 147 | # Windows Store app package directory 148 | AppPackages/ 149 | 150 | # Others 151 | sql/ 152 | *.Cache 153 | ClientBin/ 154 | [Ss]tyle[Cc]op.* 155 | ~$* 156 | *~ 157 | *.dbmdl 158 | *.dbproj.schemaview 159 | *.pfx 160 | *.publishsettings 161 | node_modules/ 162 | 163 | # RIA/Silverlight projects 164 | Generated_Code/ 165 | 166 | # Backup & report files from converting an old project file 167 | # to a newer Visual Studio version. Backup files are not needed, 168 | # because we have git ;-) 169 | _UpgradeReport_Files/ 170 | Backup*/ 171 | UpgradeLog*.XML 172 | UpgradeLog*.htm 173 | 174 | # SQL Server files 175 | *.mdf 176 | *.ldf 177 | 178 | # Business Intelligence projects 179 | *.rdl.data 180 | *.bim.layout 181 | *.bim_*.settings 182 | 183 | # Microsoft Fakes 184 | FakesAssemblies/ 185 | 186 | # LightSwitch generated files 187 | GeneratedArtifacts/ 188 | _Pvt_Extensions/ 189 | ModelManifest.xml 190 | /.nuget/*.nupkg 191 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.TestDataLayer/DataLayer/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data.Entity.Migrations; 3 | using System.Linq; 4 | using EFSecondLevelCache.TestDataLayer.Models; 5 | 6 | namespace EFSecondLevelCache.TestDataLayer.DataLayer 7 | { 8 | public class Configuration : DbMigrationsConfiguration 9 | { 10 | public Configuration() 11 | { 12 | AutomaticMigrationsEnabled = true; 13 | AutomaticMigrationDataLossAllowed = true; 14 | } 15 | 16 | protected override void Seed(SampleContext context) 17 | { 18 | User user1; 19 | 20 | const string user1Name = "User1"; 21 | if (!context.Users.Any(user => user.Name == user1Name)) 22 | { 23 | user1 = new User { Name = user1Name }; 24 | user1 = context.Users.Add(user1); 25 | } 26 | else 27 | { 28 | user1 = context.Users.First(user => user.Name == user1Name); 29 | } 30 | 31 | const string product1Name = "Product1"; 32 | if (!context.Products.Any(product => product.ProductName == product1Name)) 33 | { 34 | var product1 = new Product 35 | { 36 | ProductName = product1Name, 37 | IsActive = true, 38 | Notes = "Notes ...", 39 | ProductNumber = "001", 40 | User = user1 41 | }; 42 | product1 = context.Products.Add(product1); 43 | var tag1 = new Tag 44 | { 45 | Name = "Tag1" 46 | }; 47 | context.Tags.Add(tag1); 48 | product1.Tags = new List {tag1}; 49 | } 50 | 51 | 52 | const string product2Name = "Product2"; 53 | if (!context.Products.Any(product => product.ProductName == product2Name)) 54 | { 55 | var product2 = new Product 56 | { 57 | ProductName = product2Name, 58 | IsActive = true, 59 | Notes = "Notes ...", 60 | ProductNumber = "002", 61 | User = user1 62 | }; 63 | context.Products.Add(product2); 64 | } 65 | 66 | const string product3Name = "Product3"; 67 | if (!context.Products.Any(product => product.ProductName == product3Name)) 68 | { 69 | var product3 = new Product 70 | { 71 | ProductName = product3Name, 72 | IsActive = true, 73 | Notes = "Notes ...", 74 | ProductNumber = "003", 75 | User = user1 76 | }; 77 | context.Products.Add(product3); 78 | } 79 | 80 | const string post1Title = "Post1"; 81 | if (!context.Posts.Any(post => post.Title == post1Title)) 82 | { 83 | var page1 = new Page 84 | { 85 | Title = post1Title, 86 | User = user1 87 | }; 88 | context.Posts.Add(page1); 89 | } 90 | 91 | const string post2Title = "Post2"; 92 | if (!context.Posts.Any(post => post.Title == post2Title)) 93 | { 94 | var page2 = new Page 95 | { 96 | Title = post2Title, 97 | User = user1 98 | }; 99 | context.Posts.Add(page2); 100 | } 101 | 102 | base.Seed(context); 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.MockingTests/MockingTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data.Entity; 3 | using System.Linq; 4 | using EFSecondLevelCache.TestDataLayer.DataLayer; 5 | using EFSecondLevelCache.TestDataLayer.Models; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using Moq; 8 | using System.Threading.Tasks; 9 | using System.Data.Entity.Infrastructure; 10 | 11 | namespace EFSecondLevelCache.MockingTests 12 | { 13 | [TestClass] 14 | public class MockingTests 15 | { 16 | // more info: http://msdn.microsoft.com/en-us/data/dn314429 17 | 18 | [TestMethod] 19 | public void TestCacheableGetAllProductsSync() 20 | { 21 | var data = new List 22 |             { 23 |                 new Product { ProductName = "BBB"}, 24 |                 new Product { ProductName = "ZZZ" }, 25 |                 new Product { ProductName = "AAA" } 26 |             }.AsQueryable(); 27 | 28 | var mockSet = new Mock>(); 29 | mockSet.As>().Setup(m => m.Provider).Returns(data.Provider); 30 | mockSet.As>().Setup(m => m.Expression).Returns(data.Expression); 31 | mockSet.As>().Setup(m => m.ElementType).Returns(data.ElementType); 32 | mockSet.As>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator()); 33 | 34 | var mockContext = new Mock(); 35 | mockContext.Setup(c => c.Products).Returns(mockSet.Object); 36 | 37 | // public List GetAllProductsOrderedByName() 38 | var products = mockContext.Object.Products 39 | .OrderBy(product => product.ProductName) 40 | .Cacheable() 41 | .ToList(); 42 | 43 | Assert.AreEqual(3, products.Count); 44 | Assert.AreEqual("AAA", products[0].ProductName); 45 | Assert.AreEqual("BBB", products[1].ProductName); 46 | Assert.AreEqual("ZZZ", products[2].ProductName); 47 | } 48 | 49 | 50 | [TestMethod] 51 | public async Task TestCacheableGetAllProductsAsync() 52 | { 53 | var data = new List 54 |             { 55 |                 new Product { ProductName = "BBB"}, 56 |                 new Product { ProductName = "ZZZ" }, 57 |                 new Product { ProductName = "AAA" } 58 |             }.AsQueryable(); 59 | 60 | var mockSet = new Mock>(); 61 | mockSet.As>() 62 | .Setup(m => m.GetAsyncEnumerator()) 63 | .Returns(new EFAsyncEnumerator(data.GetEnumerator())); 64 | 65 | mockSet.As>() 66 | .Setup(m => m.Provider) 67 | .Returns(new TestDbAsyncQueryProvider(data.Provider)); 68 | 69 | mockSet.As>().Setup(m => m.Expression).Returns(data.Expression); 70 | mockSet.As>().Setup(m => m.ElementType).Returns(data.ElementType); 71 | mockSet.As>().Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator()); 72 | 73 | var mockContext = new Mock(); 74 | mockContext.Setup(c => c.Products).Returns(mockSet.Object); 75 | 76 | // public Task> GetAllProductsOrderedByNameAsync() 77 | var products = await mockContext.Object.Products 78 | .OrderBy(product => product.ProductName) 79 | .Cacheable() 80 | .ToListAsync(); 81 | 82 | Assert.AreEqual(3, products.Count); 83 | Assert.AreEqual("AAA", products[0].ProductName); 84 | Assert.AreEqual("BBB", products[1].ProductName); 85 | Assert.AreEqual("ZZZ", products[2].ProductName); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.TestDataLayer/EFSecondLevelCache.TestDataLayer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {A206C065-3CA1-4C61-9191-E57CCD589B89} 8 | Library 9 | Properties 10 | EFSecondLevelCache.TestDataLayer 11 | EFSecondLevelCache.TestDataLayer 12 | v4.5 13 | 512 14 | ..\..\ 15 | true 16 | 17 | 18 | 19 | pdbonly 20 | true 21 | bin\Release\ 22 | TRACE 23 | prompt 24 | 4 25 | false 26 | 27 | 28 | 29 | False 30 | ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll 31 | 32 | 33 | ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll 34 | True 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {c348a09f-eed0-4e99-a3b9-5a698c6883d2} 61 | EFSecondLevelCache 62 | 63 | 64 | 65 | 66 | 67 | 68 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 69 | 70 | 71 | 72 | 79 | -------------------------------------------------------------------------------- /EFSecondLevelCache/EFCachedQueryable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using EFSecondLevelCache.Contracts; 7 | #if !NET40 8 | using System.Data.Entity.Infrastructure; 9 | #endif 10 | 11 | namespace EFSecondLevelCache 12 | { 13 | /// 14 | /// Provides functionality to evaluate queries against a specific data source. 15 | /// 16 | /// Type of the entity. 17 | public class EFCachedQueryable : IQueryable 18 | #if !NET40 19 | , IDbAsyncEnumerable 20 | #endif 21 | { 22 | private readonly IQueryable _query; 23 | private readonly EFCachedQueryProvider _provider; 24 | 25 | /// 26 | /// Provides functionality to evaluate queries against a specific data source. 27 | /// 28 | /// The input EF query. 29 | /// EFCachePolicy determines the AbsoluteExpiration time and Priority of the cache. 30 | /// Stores the debug information of the caching process. 31 | /// Gets an EF query and returns its hash to store in the cache. 32 | /// Cache Service Provider. 33 | public EFCachedQueryable( 34 | IQueryable query, 35 | EFCachePolicy efCache, 36 | EFCacheDebugInfo debugInfo, 37 | IEFCacheKeyProvider cacheKeyProvider, 38 | IEFCacheServiceProvider cacheServiceProvider) 39 | { 40 | _query = query; 41 | _provider = new EFCachedQueryProvider(query, efCache, debugInfo, cacheKeyProvider, cacheServiceProvider); 42 | } 43 | 44 | /// 45 | /// Returns an enumerator that iterates through a collection. 46 | /// 47 | /// A collections that can be used to iterate through the collection. 48 | public IEnumerator GetEnumerator() 49 | { 50 | return ((IEnumerable)_provider.Materialize(_query.Expression, () => _query.ToArray())).GetEnumerator(); 51 | } 52 | 53 | /// 54 | /// Returns an enumerator that iterates through a collection. 55 | /// 56 | /// A collections that can be used to iterate through the collection. 57 | IEnumerator IEnumerable.GetEnumerator() 58 | { 59 | return ((IEnumerable)_provider.Materialize(_query.Expression, () => _query.ToArray())).GetEnumerator(); 60 | } 61 | 62 | /// 63 | /// Gets the type of the element(s) that are returned when the expression tree associated with this instance of System.Linq.IQueryable is executed. 64 | /// 65 | public Type ElementType 66 | { 67 | get { return _query.ElementType; } 68 | } 69 | 70 | /// 71 | /// Gets the expression tree that is associated with the instance of System.Linq.IQueryable. 72 | /// 73 | public Expression Expression 74 | { 75 | get { return _query.Expression; } 76 | } 77 | 78 | /// 79 | /// Gets the query provider that is associated with this data source. 80 | /// 81 | public IQueryProvider Provider 82 | { 83 | get { return _provider; } 84 | } 85 | 86 | 87 | #region IDbAsyncEnumerable implementation 88 | #if !NET40 89 | /// 90 | /// Returns an IDbAsyncEnumerator of TType which when enumerated will execute the query against the database. 91 | /// 92 | /// An enumerator for the query 93 | public IDbAsyncEnumerator GetAsyncEnumerator() 94 | { 95 | return new EFAsyncEnumerator(this.AsEnumerable().GetEnumerator()); 96 | } 97 | 98 | /// 99 | /// Returns an IDbAsyncEnumerator which when enumerated will execute the query against the database. 100 | /// 101 | IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() 102 | { 103 | return GetAsyncEnumerator(); 104 | } 105 | #endif 106 | #endregion 107 | 108 | } 109 | } -------------------------------------------------------------------------------- /EFSecondLevelCache/EFCacheKeyProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.Entity.Core.Common.CommandTrees; 4 | using System.Data.Entity.Core.Objects; 5 | using System.Linq; 6 | using EFSecondLevelCache.Contracts; 7 | using System.Linq.Expressions; 8 | 9 | namespace EFSecondLevelCache 10 | { 11 | /// 12 | /// A custom cache key provider for EF queries. 13 | /// 14 | public class EFCacheKeyProvider : IEFCacheKeyProvider 15 | { 16 | private static readonly object _lock = new object(); 17 | private readonly IEFCacheKeyHashProvider _cacheKeyHashProvider; 18 | private static readonly Dictionary _entityTypesCache = new Dictionary(); 19 | 20 | /// 21 | /// A custom cache key provider for EF queries. 22 | /// 23 | /// Provides the custom hashing algorithm. 24 | public EFCacheKeyProvider(IEFCacheKeyHashProvider cacheKeyHashProvider) 25 | { 26 | _cacheKeyHashProvider = cacheKeyHashProvider; 27 | } 28 | 29 | /// 30 | /// Gets an EF query and returns its hashed key to store in the cache. 31 | /// 32 | /// Type of the entity 33 | /// The EF query. 34 | /// An expression tree that represents a LINQ query. 35 | /// Its default value is EF_. 36 | /// If you think the computed hash of the query is not enough, set this value. 37 | /// Information of the computed key of the input LINQ query. 38 | public EFCacheKey GetEFCacheKey( 39 | IQueryable query, 40 | Expression expression, 41 | string keyHashPrefix = EFCacheKey.KeyHashPrefix, 42 | string saltKey = "") 43 | { 44 | lock (_lock) 45 | { 46 | var objectQuery = query.GetObjectQuery(expression); 47 | objectQuery.MergeOption = MergeOption.NoTracking; // equals to call AsNoTracking() method 48 | 49 | string traceString; 50 | DbQueryCommandTree dbQueryCommandTree; 51 | using (var commandTreeCollector = new EFCommandTreeCollector(objectQuery.Context)) 52 | { 53 | traceString = objectQuery.ToTraceString(); 54 | dbQueryCommandTree = commandTreeCollector.DbQueryCommandTree; 55 | } 56 | 57 | var traceStringHash = _cacheKeyHashProvider.ComputeHash(traceString); 58 | var key = string.Format("{0}{3}{1}{3}{2}{3}{4}", 59 | objectQuery.Context.Connection.ConnectionString, 60 | traceString, 61 | string.Join(Environment.NewLine, getParameterValues(objectQuery)), 62 | Environment.NewLine, 63 | saltKey); 64 | var keyHash = string.Format("{0}{1}", keyHashPrefix, _cacheKeyHashProvider.ComputeHash(key)); 65 | 66 | string[] cacheDependencies; 67 | if (_entityTypesCache.TryGetValue(traceStringHash, out cacheDependencies)) 68 | { 69 | return new EFCacheKey {Key = key, KeyHash = keyHash, CacheDependencies = cacheDependencies}; 70 | } 71 | 72 | cacheDependencies = getCacheDependencies(objectQuery, dbQueryCommandTree); 73 | _entityTypesCache.Add(traceStringHash, cacheDependencies); 74 | 75 | return new EFCacheKey {Key = key, KeyHash = keyHash, CacheDependencies = cacheDependencies}; 76 | } 77 | } 78 | 79 | private static string[] getCacheDependencies(ObjectQuery objectQuery, DbQueryCommandTree queryTree) 80 | { 81 | if (queryTree == null) 82 | throw new KeyNotFoundException("Couldn't find the related DbCommandTree."); 83 | 84 | var visitor = new EFCommandTreeVisitor(objectQuery.Context.MetadataWorkspace); 85 | queryTree.Query.Accept(visitor); 86 | return visitor.EntityClrTypes.Select(x => x.FullName).ToArray(); 87 | } 88 | 89 | private static IEnumerable getParameterValues(ObjectQuery query) 90 | { 91 | return query.Parameters.Select(p => string.Format("{0}={1}", p.Name, p.Value)); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.PerformanceTests/EFSecondLevelCache.PerformanceTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FA334191-408D-4B9B-AAAC-EB1F3189366B} 8 | Exe 9 | Properties 10 | EFSecondLevelCache.PerformanceTests 11 | EFSecondLevelCache.PerformanceTests 12 | v4.5 13 | 512 14 | ..\..\ 15 | true 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | False 39 | ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll 40 | 41 | 42 | ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll 43 | True 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | {c348a09f-eed0-4e99-a3b9-5a698c6883d2} 65 | EFSecondLevelCache 66 | 67 | 68 | {a206c065-3ca1-4c61-9191-e57ccd589b89} 69 | EFSecondLevelCache.TestDataLayer 70 | 71 | 72 | 73 | 74 | 75 | 76 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 77 | 78 | 79 | 80 | 87 | -------------------------------------------------------------------------------- /EFSecondLevelCache/ObjectQueryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data.Entity.Core.Common.CommandTrees; 3 | using System.Data.Entity.Core.Objects; 4 | using System.Data.Entity.Infrastructure; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | 9 | namespace EFSecondLevelCache 10 | { 11 | /// 12 | /// Extension methods for ObjectQuery. 13 | /// 14 | public static class ObjectQueryExtensions 15 | { 16 | private const BindingFlags PrivateMembersFlags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public; 17 | 18 | private static MethodInfo _createQueryMethod; 19 | static ObjectQueryExtensions() 20 | { 21 | cacheCreateQueryMethod(); 22 | } 23 | 24 | private static void cacheCreateQueryMethod() 25 | { 26 | var objectQueryProviderType = typeof(DefaultExpressionVisitor).Assembly 27 | .GetType("System.Data.Entity.Core.Objects.ELinq.ObjectQueryProvider"); 28 | if (objectQueryProviderType == null) 29 | { 30 | throw new NotSupportedException("Failed to get ObjectQueryProvider."); 31 | } 32 | 33 | _createQueryMethod = objectQueryProviderType.GetMethod("CreateQuery", 34 | PrivateMembersFlags, 35 | Type.DefaultBinder, 36 | new[] { typeof(Expression), typeof(Type) }, 37 | null); 38 | if (_createQueryMethod == null) 39 | { 40 | throw new NotSupportedException("Failed to get CreateQuery Method."); 41 | } 42 | } 43 | 44 | /// 45 | /// Converts the query into an ObjectQuery. 46 | /// 47 | /// The type of the entity. 48 | /// The query to convert. 49 | /// The converted ObjectQuery 50 | public static ObjectQuery GetObjectQuery(this IQueryable query) 51 | { 52 | var originalObjectQuery = query as ObjectQuery; 53 | if (originalObjectQuery != null) 54 | return originalObjectQuery; 55 | 56 | var dbQuery = query as DbQuery; 57 | if (dbQuery == null) 58 | { 59 | throw new NotSupportedException( 60 | "Failed to get DbQuery. Please use EFSecondLevelCache library with EntityFramework queries."); 61 | } 62 | 63 | var queryType = query.GetType(); 64 | var internalQueryDelegate = queryType.GetPropertyGetterDelegateFromCache("InternalQuery", PrivateMembersFlags); 65 | var internalQuery = internalQueryDelegate(query); 66 | if (internalQuery == null) 67 | { 68 | throw new NotSupportedException("Failed to get InternalQuery."); 69 | } 70 | 71 | var internalQueryType = internalQuery.GetType(); 72 | var objectQueryDelegate = internalQueryType.GetPropertyGetterDelegateFromCache("ObjectQuery", PrivateMembersFlags); 73 | var objectQuery = objectQueryDelegate(internalQuery) as ObjectQuery; 74 | if (objectQuery == null) 75 | { 76 | throw new NotSupportedException("Failed to get ObjectQuery."); 77 | } 78 | 79 | return objectQuery; 80 | } 81 | 82 | 83 | /// 84 | /// Creates an ObjectQuery from an expression. 85 | /// 86 | /// The type of the entity. 87 | /// The input query. 88 | /// The input expression. 89 | /// An ObjectQuery created from the expression. 90 | public static ObjectQuery GetObjectQuery(this IQueryable query, Expression expression) 91 | { 92 | var sourceQuery = query.GetObjectQuery(); 93 | if (sourceQuery == null) 94 | { 95 | throw new NotSupportedException("Failed to get ObjectQuery."); 96 | } 97 | 98 | var provider = ((IQueryable)sourceQuery).Provider; 99 | var expressionQuery = _createQueryMethod.Invoke( 100 | provider, new object[] { expression, typeof(TEntity) }) as IQueryable; 101 | if (expressionQuery == null) 102 | { 103 | throw new NotSupportedException("Failed to get expressionQuery."); 104 | } 105 | 106 | return expressionQuery.GetObjectQuery(); 107 | } 108 | } 109 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.UnitTests/EFSecondLevelCache.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {7EC4DC92-9EA1-4990-B6B1-05A1C960E6E2} 7 | Library 8 | Properties 9 | EFSecondLevelCache.UnitTests 10 | EFSecondLevelCache.UnitTests 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {c348a09f-eed0-4e99-a3b9-5a698c6883d2} 59 | EFSecondLevelCache 60 | 61 | 62 | 63 | 64 | 65 | 66 | False 67 | 68 | 69 | False 70 | 71 | 72 | False 73 | 74 | 75 | False 76 | 77 | 78 | 79 | 80 | 81 | 82 | 89 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalTests/EFCacheServiceProviderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using EFSecondLevelCache.Contracts; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace EFSecondLevelCache.FunctionalTests 7 | { 8 | [TestClass] 9 | public class EFCacheServiceProviderTests 10 | { 11 | private readonly IEFCacheServiceProvider _cacheService; 12 | public EFCacheServiceProviderTests() 13 | { 14 | _cacheService = new EFCacheServiceProvider(); 15 | } 16 | 17 | [TestInitialize] 18 | public void ClearEFGlobalCacheBeforeEachTest() 19 | { 20 | _cacheService.ClearAllCachedEntries(); 21 | } 22 | 23 | [TestMethod] 24 | public void TestCacheInvalidationWithTwoRoots() 25 | { 26 | _cacheService.StoreRootCacheKeys(new[] { "entity1.model", "entity2.model" }); 27 | _cacheService.InsertValue("EF_key1", "value1", new[] { "entity1.model", "entity2.model" }, DateTime.Now.AddMinutes(10)); 28 | 29 | _cacheService.StoreRootCacheKeys(new[] { "entity1.model", "entity2.model" }); 30 | _cacheService.InsertValue("EF_key2", "value2", new[] { "entity1.model", "entity2.model" }, DateTime.Now.AddMinutes(10)); 31 | 32 | 33 | var value1 = _cacheService.GetValue("EF_key1"); 34 | Assert.IsNotNull(value1); 35 | 36 | var value2 = _cacheService.GetValue("EF_key2"); 37 | Assert.IsNotNull(value2); 38 | 39 | _cacheService.InvalidateCacheDependencies(new[] { "entity2.model" }); 40 | 41 | value1 = _cacheService.GetValue("EF_key1"); 42 | Assert.IsNull(value1); 43 | 44 | value2 = _cacheService.GetValue("EF_key2"); 45 | Assert.IsNull(value2); 46 | 47 | var keys = _cacheService.GetAllEFCachedKeys(); 48 | var key1 = keys.FirstOrDefault(key => key == "EF_key1"); 49 | Assert.IsNull(key1); 50 | Assert.AreEqual(0, keys.Count); 51 | } 52 | 53 | [TestMethod] 54 | public void TestCacheInvalidationWithOneRoot() 55 | { 56 | _cacheService.StoreRootCacheKeys(new[] { "entity1" }); 57 | _cacheService.InsertValue("EF_key1", "value1", new[] { "entity1" }, DateTime.Now.AddMinutes(10)); 58 | 59 | _cacheService.StoreRootCacheKeys(new[] { "entity1" }); 60 | _cacheService.InsertValue("EF_key2", "value2", new[] { "entity1" }, DateTime.Now.AddMinutes(10)); 61 | 62 | var value1 = _cacheService.GetValue("EF_key1"); 63 | Assert.IsNotNull(value1); 64 | 65 | var value2 = _cacheService.GetValue("EF_key2"); 66 | Assert.IsNotNull(value2); 67 | 68 | _cacheService.InvalidateCacheDependencies(new[] { "entity1" }); 69 | 70 | value1 = _cacheService.GetValue("EF_key1"); 71 | Assert.IsNull(value1); 72 | 73 | value2 = _cacheService.GetValue("EF_key2"); 74 | Assert.IsNull(value2); 75 | 76 | var keys = _cacheService.GetAllEFCachedKeys(); 77 | var key1 = keys.FirstOrDefault(key => key == "EF_key1"); 78 | Assert.IsNull(key1); 79 | Assert.AreEqual(0, keys.Count); 80 | } 81 | 82 | [TestMethod] 83 | public void TestCacheInvalidationWithSimilarRoots() 84 | { 85 | _cacheService.StoreRootCacheKeys(new[] { "entity1", "entity2" }); 86 | _cacheService.InsertValue("EF_key1", "value1", new[] { "entity1", "entity2" }, DateTime.Now.AddMinutes(10)); 87 | 88 | _cacheService.StoreRootCacheKeys(new[] { "entity2" }); 89 | _cacheService.InsertValue("EF_key2", "value2", new[] { "entity2" }, DateTime.Now.AddMinutes(10)); 90 | 91 | var value1 = _cacheService.GetValue("EF_key1"); 92 | Assert.IsNotNull(value1); 93 | 94 | var value2 = _cacheService.GetValue("EF_key2"); 95 | Assert.IsNotNull(value2); 96 | 97 | _cacheService.InvalidateCacheDependencies(new[] { "entity2" }); 98 | 99 | value1 = _cacheService.GetValue("EF_key1"); 100 | Assert.IsNull(value1); 101 | 102 | value2 = _cacheService.GetValue("EF_key2"); 103 | Assert.IsNull(value2); 104 | 105 | var keys = _cacheService.GetAllEFCachedKeys(); 106 | var key1 = keys.FirstOrDefault(key => key == "EF_key1"); 107 | Assert.IsNull(key1); 108 | Assert.AreEqual(0, keys.Count); 109 | } 110 | 111 | [TestMethod] 112 | public void TestInsertingNullValues() 113 | { 114 | _cacheService.StoreRootCacheKeys(new[] { "entity1", "entity2" }); 115 | _cacheService.InsertValue("EF_key1", null, new[] { "entity1", "entity2" }, DateTime.Now.AddMinutes(10)); 116 | 117 | var value1 = _cacheService.GetValue("EF_key1"); 118 | Assert.IsTrue(Equals(value1, _cacheService.NullObject)); 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /EFSecondLevelCache/XxHashUnsafe.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | 4 | namespace EFSecondLevelCache 5 | { 6 | /// 7 | /// xxHash is an extremely fast non-cryptographic Hash algorithm, working at speeds close to RAM limits. 8 | /// http://code.google.com/p/xxhash/ 9 | /// 10 | public static class XxHashUnsafe 11 | { 12 | private const uint Prime1 = 2654435761U; 13 | private const uint Prime2 = 2246822519U; 14 | private const uint Prime3 = 3266489917U; 15 | private const uint Prime4 = 668265263U; 16 | private const int Prime5 = 0x165667b1; 17 | private const uint Seed = 0xc58f1a7b; 18 | 19 | /// 20 | /// Computes the xxHash of the input string. xxHash is an extremely fast non-cryptographic Hash algorithm. 21 | /// 22 | /// the input string 23 | /// xxHash 24 | public static unsafe UInt32 ComputeHash(string data) 25 | { 26 | fixed (char* input = data) 27 | { 28 | return hash((byte*)input, (uint)data.Length * sizeof(char), Seed); 29 | } 30 | } 31 | 32 | /// 33 | /// Computes the xxHash of the input byte array. xxHash is an extremely fast non-cryptographic Hash algorithm. 34 | /// 35 | /// the input byte array 36 | /// xxHash 37 | public static unsafe uint ComputeHash(byte[] data) 38 | { 39 | fixed (byte* input = &data[0]) 40 | { 41 | return hash(input, (uint)data.Length, Seed); 42 | } 43 | } 44 | 45 | /// 46 | /// Computes the xxHash of the input byte array. xxHash is an extremely fast non-cryptographic Hash algorithm. 47 | /// 48 | /// the input byte array 49 | /// start offset 50 | /// length 51 | /// initial seed 52 | /// xxHash 53 | public static unsafe uint ComputeHash(byte[] data, int offset, uint len, uint seed) 54 | { 55 | fixed (byte* input = &data[offset]) 56 | { 57 | return hash(input, len, seed); 58 | } 59 | } 60 | 61 | private unsafe static uint hash(byte* data, uint len, uint seed) 62 | { 63 | if (len < 16) 64 | return hashSmall(data, len, seed); 65 | var v1 = seed + Prime1; 66 | var v2 = v1 * Prime2 + len; 67 | var v3 = v2 * Prime3; 68 | var v4 = v3 * Prime4; 69 | var p = (uint*)data; 70 | var limit = (uint*)(data + len - 16); 71 | while (p < limit) 72 | { 73 | v1 += rotl32(v1, 13); v1 *= Prime1; v1 += *p; p++; 74 | v2 += rotl32(v2, 11); v2 *= Prime1; v2 += *p; p++; 75 | v3 += rotl32(v3, 17); v3 *= Prime1; v3 += *p; p++; 76 | v4 += rotl32(v4, 19); v4 *= Prime1; v4 += *p; p++; 77 | } 78 | p = limit; 79 | v1 += rotl32(v1, 17); v2 += rotl32(v2, 19); v3 += rotl32(v3, 13); v4 += rotl32(v4, 11); 80 | v1 *= Prime1; v2 *= Prime1; v3 *= Prime1; v4 *= Prime1; 81 | v1 += *p; p++; v2 += *p; p++; v3 += *p; p++; v4 += *p; 82 | v1 *= Prime2; v2 *= Prime2; v3 *= Prime2; v4 *= Prime2; 83 | v1 += rotl32(v1, 11); v2 += rotl32(v2, 17); v3 += rotl32(v3, 19); v4 += rotl32(v4, 13); 84 | v1 *= Prime3; v2 *= Prime3; v3 *= Prime3; v4 *= Prime3; 85 | var crc = v1 + rotl32(v2, 3) + rotl32(v3, 6) + rotl32(v4, 9); 86 | crc ^= crc >> 11; 87 | crc += (Prime4 + len) * Prime1; 88 | crc ^= crc >> 15; 89 | crc *= Prime2; 90 | crc ^= crc >> 13; 91 | return crc; 92 | } 93 | 94 | private unsafe static uint hashSmall(byte* data, uint len, uint seed) 95 | { 96 | var p = data; 97 | var bEnd = data + len; 98 | var limit = bEnd - 4; 99 | var idx = seed + Prime1; 100 | uint crc = Prime5; 101 | while (p < limit) 102 | { 103 | crc += (*(uint*)p) + idx; 104 | idx++; 105 | crc += rotl32(crc, 17) * Prime4; 106 | crc *= Prime1; 107 | p += 4; 108 | } 109 | while (p < bEnd) 110 | { 111 | crc += (*p) + idx; 112 | idx++; 113 | crc *= Prime1; 114 | p++; 115 | } 116 | crc += len; 117 | crc ^= crc >> 15; 118 | crc *= Prime2; 119 | crc ^= crc >> 13; 120 | crc *= Prime3; 121 | crc ^= crc >> 16; 122 | return crc; 123 | } 124 | 125 | private static UInt32 rotl32(UInt32 x, int r) 126 | { 127 | return (x << r) | (x >> (32 - r)); 128 | } 129 | } 130 | } -------------------------------------------------------------------------------- /EFSecondLevelCache/EFCommandTreeVisitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Data.Entity.Core.Common.CommandTrees; 5 | using System.Data.Entity.Core.Mapping; 6 | using System.Data.Entity.Core.Metadata.Edm; 7 | using System.Linq; 8 | 9 | namespace EFSecondLevelCache 10 | { 11 | /// 12 | /// Determines the cache dependencies of a given query. 13 | /// 14 | public class EFCommandTreeVisitor : BasicCommandTreeVisitor 15 | { 16 | private static readonly ConcurrentDictionary _entityTypesCache = new ConcurrentDictionary(); 17 | private readonly List _entityClrTypes = new List(); 18 | private readonly MetadataWorkspace _metadata; 19 | 20 | /// 21 | /// Determines the cache dependencies of a given query. 22 | /// 23 | /// Runtime Metadata Workspace 24 | public EFCommandTreeVisitor(MetadataWorkspace metadata) 25 | { 26 | _metadata = metadata; 27 | } 28 | 29 | /// 30 | /// Returns the cache dependencies of a given query. 31 | /// 32 | public IEnumerable EntityClrTypes 33 | { 34 | get { return _entityClrTypes; } 35 | } 36 | 37 | /// 38 | /// Implements the visitor pattern for the command tree. 39 | /// 40 | /// Represents a scan of all elements of a given entity set. 41 | public override void Visit(DbScanExpression expression) 42 | { 43 | var type = getEntityType(expression.Target); 44 | if (type != null) _entityClrTypes.Add(type); 45 | base.Visit(expression); 46 | } 47 | 48 | /// 49 | /// Finds if the table is an entity framework specific table. 50 | /// 51 | /// The set base 52 | /// True if it's an EF internal table 53 | private static bool isEntityFrameworkInternalTable(EntitySetBase setBase) 54 | { 55 | return setBase.Table != null && 56 | (setBase.Table.StartsWith("__") || setBase.Table.StartsWith("Edm")); 57 | } 58 | 59 | private Type getEntityType(EntitySetBase setBase) 60 | { 61 | Type setBaseType; 62 | if (_entityTypesCache.TryGetValue(setBase, out setBaseType)) 63 | { 64 | return setBaseType; 65 | } 66 | 67 | // if it's an entity framework internal table then return null 68 | if (isEntityFrameworkInternalTable(setBase)) 69 | { 70 | return null; 71 | } 72 | 73 | // Get the part of the model that contains info about the actual CLR types 74 | var objectItemCollection = ((ObjectItemCollection)_metadata.GetItemCollection(DataSpace.OSpace)); 75 | 76 | // Get conceptual model 77 | var primitiveTypeCollection = _metadata.GetItems(DataSpace.CSpace).Single(); 78 | 79 | // Get the mapping model 80 | var entityPrimitiveMappingCollection = 81 | _metadata.GetItems(DataSpace.CSSpace).Single(); 82 | 83 | // Get the entity type from the model and find which entities this set base refers to 84 | var oSpace = _metadata.GetItems(DataSpace.OSpace); 85 | foreach (var entityType in oSpace) 86 | { 87 | // Get the entity set that uses this entity type 88 | var entitySet = primitiveTypeCollection.EntitySets 89 | .SingleOrDefault(s => s.ElementType.Name == entityType.Name); 90 | 91 | if (entitySet == null) 92 | { 93 | continue; 94 | } 95 | 96 | // Find the mapping between conceptual and storage model for this entity set 97 | var mapping = entityPrimitiveMappingCollection.EntitySetMappings 98 | .Single(s => s.EntitySet == entitySet); 99 | 100 | // Find the storage entity set (table) that the entity is mapped to. 101 | // This could be mapped to multiple entities 102 | var isRelatedTable = mapping.EntityTypeMappings 103 | .SelectMany(typeMapping => typeMapping.Fragments) 104 | .Select(fragment => fragment.StoreEntitySet) 105 | .Select(set => set.MetadataProperties) 106 | .Any(metadataCollection => (string)metadataCollection["Table"].Value == setBase.Table); 107 | 108 | // is this the table we are looking for? 109 | if (isRelatedTable) 110 | { 111 | var clrType = objectItemCollection.GetClrType(entityType); 112 | _entityTypesCache.TryAdd(setBase, clrType); 113 | return clrType; 114 | } 115 | } 116 | 117 | // not found! 118 | return null; 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFSecondLevelCache", "EFSecondLevelCache\EFSecondLevelCache.csproj", "{C348A09F-EED0-4E99-A3B9-5A698C6883D2}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFSecondLevelCache.FunctionalTests", "EFSecondLevelCache.Tests\EFSecondLevelCache.FunctionalTests\EFSecondLevelCache.FunctionalTests.csproj", "{59E13D5F-0634-4088-8863-D30978DA4ECC}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C8E062FD-C027-40A6-908F-59158D3EEF67}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFSecondLevelCache.TestDataLayer", "EFSecondLevelCache.Tests\EFSecondLevelCache.TestDataLayer\EFSecondLevelCache.TestDataLayer.csproj", "{A206C065-3CA1-4C61-9191-E57CCD589B89}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{415C39FD-6FCB-45EA-84C6-B59EE50B168A}" 15 | ProjectSection(SolutionItems) = preProject 16 | .nuget\_run.bat = .nuget\_run.bat 17 | .nuget\NuGet.Config = .nuget\NuGet.Config 18 | .nuget\NuGet.exe = .nuget\NuGet.exe 19 | .nuget\NuGet.targets = .nuget\NuGet.targets 20 | EndProjectSection 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6F554E64-6AD7-4C97-AB7C-6F8E9204BA52}" 23 | ProjectSection(SolutionItems) = preProject 24 | LICENSE.md = LICENSE.md 25 | README.md = README.md 26 | EndProjectSection 27 | EndProject 28 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFSecondLevelCache.PerformanceTests", "EFSecondLevelCache.Tests\EFSecondLevelCache.PerformanceTests\EFSecondLevelCache.PerformanceTests.csproj", "{FA334191-408D-4B9B-AAAC-EB1F3189366B}" 29 | EndProject 30 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFSecondLevelCache.MockingTests", "EFSecondLevelCache.Tests\EFSecondLevelCache.MockingTests\EFSecondLevelCache.MockingTests.csproj", "{70BC2DB8-D5E6-4B46-9368-2F57B127B24F}" 31 | EndProject 32 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFSecondLevelCache.FunctionalDbFirstTests", "EFSecondLevelCache.Tests\EFSecondLevelCache.FunctionalDbFirstTests\EFSecondLevelCache.FunctionalDbFirstTests.csproj", "{1651B9BE-7E09-4ABA-A26B-1FF1BF7B9AC3}" 33 | EndProject 34 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EFSecondLevelCache.UnitTests", "EFSecondLevelCache.Tests\EFSecondLevelCache.UnitTests\EFSecondLevelCache.UnitTests.csproj", "{7EC4DC92-9EA1-4990-B6B1-05A1C960E6E2}" 35 | EndProject 36 | Global 37 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 38 | Debug|Any CPU = Debug|Any CPU 39 | Release|Any CPU = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 42 | {C348A09F-EED0-4E99-A3B9-5A698C6883D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {C348A09F-EED0-4E99-A3B9-5A698C6883D2}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {C348A09F-EED0-4E99-A3B9-5A698C6883D2}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {C348A09F-EED0-4E99-A3B9-5A698C6883D2}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {59E13D5F-0634-4088-8863-D30978DA4ECC}.Debug|Any CPU.ActiveCfg = Release|Any CPU 47 | {59E13D5F-0634-4088-8863-D30978DA4ECC}.Debug|Any CPU.Build.0 = Release|Any CPU 48 | {59E13D5F-0634-4088-8863-D30978DA4ECC}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {59E13D5F-0634-4088-8863-D30978DA4ECC}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {A206C065-3CA1-4C61-9191-E57CCD589B89}.Debug|Any CPU.ActiveCfg = Release|Any CPU 51 | {A206C065-3CA1-4C61-9191-E57CCD589B89}.Debug|Any CPU.Build.0 = Release|Any CPU 52 | {A206C065-3CA1-4C61-9191-E57CCD589B89}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {A206C065-3CA1-4C61-9191-E57CCD589B89}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {FA334191-408D-4B9B-AAAC-EB1F3189366B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {FA334191-408D-4B9B-AAAC-EB1F3189366B}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {FA334191-408D-4B9B-AAAC-EB1F3189366B}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {FA334191-408D-4B9B-AAAC-EB1F3189366B}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {70BC2DB8-D5E6-4B46-9368-2F57B127B24F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {70BC2DB8-D5E6-4B46-9368-2F57B127B24F}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {70BC2DB8-D5E6-4B46-9368-2F57B127B24F}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {70BC2DB8-D5E6-4B46-9368-2F57B127B24F}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {1651B9BE-7E09-4ABA-A26B-1FF1BF7B9AC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {1651B9BE-7E09-4ABA-A26B-1FF1BF7B9AC3}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {1651B9BE-7E09-4ABA-A26B-1FF1BF7B9AC3}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {1651B9BE-7E09-4ABA-A26B-1FF1BF7B9AC3}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {7EC4DC92-9EA1-4990-B6B1-05A1C960E6E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {7EC4DC92-9EA1-4990-B6B1-05A1C960E6E2}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {7EC4DC92-9EA1-4990-B6B1-05A1C960E6E2}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {7EC4DC92-9EA1-4990-B6B1-05A1C960E6E2}.Release|Any CPU.Build.0 = Release|Any CPU 70 | EndGlobalSection 71 | GlobalSection(SolutionProperties) = preSolution 72 | HideSolutionNode = FALSE 73 | EndGlobalSection 74 | GlobalSection(NestedProjects) = preSolution 75 | {59E13D5F-0634-4088-8863-D30978DA4ECC} = {C8E062FD-C027-40A6-908F-59158D3EEF67} 76 | {A206C065-3CA1-4C61-9191-E57CCD589B89} = {C8E062FD-C027-40A6-908F-59158D3EEF67} 77 | {FA334191-408D-4B9B-AAAC-EB1F3189366B} = {C8E062FD-C027-40A6-908F-59158D3EEF67} 78 | {70BC2DB8-D5E6-4B46-9368-2F57B127B24F} = {C8E062FD-C027-40A6-908F-59158D3EEF67} 79 | {1651B9BE-7E09-4ABA-A26B-1FF1BF7B9AC3} = {C8E062FD-C027-40A6-908F-59158D3EEF67} 80 | {7EC4DC92-9EA1-4990-B6B1-05A1C960E6E2} = {C8E062FD-C027-40A6-908F-59158D3EEF67} 81 | EndGlobalSection 82 | EndGlobal 83 | -------------------------------------------------------------------------------- /EFSecondLevelCache/EFCacheServiceProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Caching; 6 | using EFSecondLevelCache.Contracts; 7 | 8 | namespace EFSecondLevelCache 9 | { 10 | /// 11 | /// Using HttpRuntime.Cache as a cache service. It works with both desktop and web applications. 12 | /// 13 | public class EFCacheServiceProvider : IEFCacheServiceProvider 14 | { 15 | private static readonly EFCacheKey _nullObject = new EFCacheKey(); 16 | private static readonly SortedSet _rootKeys = new SortedSet(); 17 | 18 | /// 19 | /// `HttpRuntime.Cache.Insert` won't accept null values. 20 | /// So we need a custom Null object here. It should be defined `static readonly` in your code. 21 | /// 22 | public object NullObject => _nullObject; 23 | 24 | /// 25 | /// Returns list of the cached keys. 26 | /// 27 | public IList AllCachedKeys 28 | { 29 | get 30 | { 31 | var results = new List(); 32 | var enumerator = HttpRuntime.Cache.GetEnumerator(); 33 | while (enumerator.MoveNext()) 34 | { 35 | results.Add(enumerator.Key.ToString()); 36 | } 37 | return results; 38 | } 39 | } 40 | 41 | /// 42 | /// Removes the cached entries added by this library. 43 | /// 44 | /// Its default value is EF_. 45 | public void ClearAllCachedEntries(string keyHashPrefix = EFCacheKey.KeyHashPrefix) 46 | { 47 | InvalidateCacheDependencies(_rootKeys.ToArray()); 48 | 49 | var keys = GetAllEFCachedKeys(keyHashPrefix); 50 | foreach (var key in keys) 51 | { 52 | HttpRuntime.Cache.Remove(key); 53 | } 54 | } 55 | 56 | /// 57 | /// Gets all of the cached keys, added by this library. 58 | /// 59 | /// Its default value is EF_. 60 | /// list of the keys 61 | public IList GetAllEFCachedKeys(string keyHashPrefix = EFCacheKey.KeyHashPrefix) 62 | { 63 | var results = new List(); 64 | var enumerator = HttpRuntime.Cache.GetEnumerator(); 65 | 66 | while (enumerator.MoveNext()) 67 | { 68 | if (!enumerator.Key.ToString().StartsWith(keyHashPrefix, StringComparison.Ordinal)) 69 | continue; 70 | 71 | results.Add(enumerator.Key.ToString()); 72 | } 73 | 74 | return results; 75 | } 76 | 77 | /// 78 | /// Gets a cached entry by key. 79 | /// 80 | /// key to find 81 | /// cached value 82 | public object GetValue(string cacheKey) 83 | { 84 | return HttpRuntime.Cache.Get(cacheKey); 85 | } 86 | 87 | /// 88 | /// Adds a new item to the cache. 89 | /// 90 | /// key 91 | /// value 92 | /// cache dependencies 93 | /// absolute expiration time 94 | /// its default value is CacheItemPriority.Normal 95 | public void InsertValue(string cacheKey, object value, 96 | string[] rootCacheKeys, 97 | DateTime absoluteExpiration, 98 | CacheItemPriority priority = CacheItemPriority.Normal) 99 | { 100 | if (value == null) 101 | { 102 | value = NullObject; // `HttpRuntime.Cache.Insert` won't accept null values. 103 | } 104 | 105 | HttpRuntime.Cache.Insert( 106 | key: cacheKey, 107 | value: value, 108 | dependencies: new CacheDependency(null, rootCacheKeys), 109 | absoluteExpiration: absoluteExpiration, 110 | slidingExpiration: Cache.NoSlidingExpiration, 111 | priority: priority, 112 | onRemoveCallback: null); 113 | } 114 | 115 | /// 116 | /// Invalidates all of the cache entries which are dependent on any of the specified root keys. 117 | /// 118 | /// cache dependencies 119 | public void InvalidateCacheDependencies(string[] rootCacheKeys) 120 | { 121 | foreach (var rootCacheKey in rootCacheKeys) 122 | { 123 | 124 | if (string.IsNullOrWhiteSpace(rootCacheKey)) continue; 125 | // Removes all cached items depend on this key. 126 | // If any of those cached items change, the whole dependency will be changed and the dependent item will be invalidated as well. 127 | HttpRuntime.Cache.Remove(rootCacheKey); 128 | } 129 | } 130 | 131 | /// 132 | /// The name of the cache keys used to clear the cache. All cached items depend on these keys. 133 | /// 134 | public void StoreRootCacheKeys(string[] rootCacheKeys) 135 | { 136 | foreach (var rootCacheKey in rootCacheKeys) 137 | { 138 | if (HttpRuntime.Cache.Get(rootCacheKey) != null) 139 | continue; 140 | 141 | HttpRuntime.Cache.Add( 142 | rootCacheKey, 143 | rootCacheKey, 144 | null, 145 | Cache.NoAbsoluteExpiration, 146 | Cache.NoSlidingExpiration, 147 | CacheItemPriority.Default, 148 | null); 149 | 150 | _rootKeys.Add(rootCacheKey); 151 | } 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalDbFirstTests/UnitTests.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Entity; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Text; 5 | using EFSecondLevelCache.Contracts; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace EFSecondLevelCache.FunctionalDbFirstTests 9 | { 10 | [TestClass] 11 | public class UnitTests 12 | { 13 | [TestInitialize] 14 | public void ClearEFGlobalCacheBeforeEachTest() 15 | { 16 | new EFCacheServiceProvider().ClearAllCachedEntries(); 17 | } 18 | 19 | [TestMethod] 20 | public void TestIncludeMethodAffectsKeyCache() 21 | { 22 | using (var context = new TestDB2015Entities()) 23 | { 24 | var databaseLog = new StringBuilder(); 25 | context.Database.Log = commandLine => 26 | { 27 | databaseLog.AppendLine(commandLine); 28 | Trace.Write(commandLine); 29 | }; 30 | 31 | Trace.WriteLine("a normal query"); 32 | var product1IncludeTags = context.Products.Include(x => x.Tags).FirstOrDefault(); 33 | Assert.IsNotNull(product1IncludeTags); 34 | 35 | 36 | Trace.WriteLine("1st query using Include method."); 37 | databaseLog.Clear(); 38 | var debugInfo1 = new EFCacheDebugInfo(); 39 | var firstPoductIncludeTags = context.Products.Include(x => x.Tags) 40 | .Cacheable(debugInfo1) 41 | .FirstOrDefault(); 42 | Assert.IsNotNull(firstPoductIncludeTags); 43 | Assert.AreEqual(false, debugInfo1.IsCacheHit); 44 | var hash1 = debugInfo1.EFCacheKey.KeyHash; 45 | var cacheDependencies1 = debugInfo1.EFCacheKey.CacheDependencies; 46 | 47 | Trace.WriteLine( 48 | @"2nd query looks the same, but it doesn't have the Include method, so it shouldn't produce the same queryKeyHash. 49 | This was the problem with just parsing the LINQ expression, without considering the produced SQL."); 50 | databaseLog.Clear(); 51 | var debugInfo2 = new EFCacheDebugInfo(); 52 | var firstPoduct = context.Products.Cacheable(debugInfo2) 53 | .FirstOrDefault(); 54 | Assert.IsNotNull(firstPoduct); 55 | Assert.AreEqual(false, debugInfo2.IsCacheHit); 56 | var hash2 = debugInfo2.EFCacheKey.KeyHash; 57 | var cacheDependencies2 = debugInfo2.EFCacheKey.CacheDependencies; 58 | 59 | Assert.AreNotEqual(hash1, hash2); 60 | Assert.AreNotEqual(cacheDependencies1, cacheDependencies2); 61 | } 62 | } 63 | 64 | [TestMethod] 65 | public void TestEagerlyLoadingMultipleLevels() 66 | { 67 | using (var context = new TestDB2015Entities()) 68 | { 69 | var databaseLog = new StringBuilder(); 70 | context.Database.Log = commandLine => 71 | { 72 | databaseLog.AppendLine(commandLine); 73 | Trace.Write(commandLine); 74 | }; 75 | 76 | Trace.WriteLine("a normal query"); 77 | var product1IncludeTags = context.Users 78 | .Include(x => x.Products) 79 | .Include(x => x.Products.Select(y => y.Tags)) 80 | .FirstOrDefault(); 81 | Assert.IsNotNull(product1IncludeTags); 82 | 83 | 84 | Trace.WriteLine("1st query using Include method."); 85 | databaseLog.Clear(); 86 | var debugInfo1 = new EFCacheDebugInfo(); 87 | var firstPoductIncludeTags = context.Users 88 | .Include(x => x.Products) 89 | .Include(x => x.Products.Select(y => y.Tags)) 90 | .Cacheable(debugInfo1) 91 | .FirstOrDefault(); 92 | Assert.IsNotNull(firstPoductIncludeTags); 93 | Assert.AreEqual(false, debugInfo1.IsCacheHit); 94 | var hash1 = debugInfo1.EFCacheKey.KeyHash; 95 | var cacheDependencies1 = debugInfo1.EFCacheKey.CacheDependencies; 96 | 97 | 98 | Trace.WriteLine("same cached query using Include method."); 99 | databaseLog.Clear(); 100 | var debugInfo11 = new EFCacheDebugInfo(); 101 | var firstPoductIncludeTags11 = context.Users 102 | .Include(x => x.Products) 103 | .Include(x => x.Products.Select(y => y.Tags)) 104 | .Cacheable(debugInfo11) 105 | .FirstOrDefault(); 106 | Assert.IsNotNull(firstPoductIncludeTags11); 107 | Assert.AreEqual(true, debugInfo11.IsCacheHit); 108 | 109 | 110 | Trace.WriteLine( 111 | @"2nd query looks the same, but it doesn't have the Include method, so it shouldn't produce the same queryKeyHash. 112 | This was the problem with just parsing the LINQ expression, without considering the produced SQL."); 113 | databaseLog.Clear(); 114 | var debugInfo2 = new EFCacheDebugInfo(); 115 | var firstPoduct = context.Users.Cacheable(debugInfo2) 116 | .FirstOrDefault(); 117 | Assert.IsNotNull(firstPoduct); 118 | Assert.AreEqual(false, debugInfo2.IsCacheHit); 119 | var hash2 = debugInfo2.EFCacheKey.KeyHash; 120 | var cacheDependencies2 = debugInfo2.EFCacheKey.CacheDependencies; 121 | 122 | Assert.AreNotEqual(hash1, hash2); 123 | Assert.AreNotEqual(cacheDependencies1, cacheDependencies2); 124 | } 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /EFSecondLevelCache/EFCachedQueryExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Entity.Core.Objects; 2 | using System.Data.Entity.Infrastructure; 3 | using System.Linq; 4 | using EFSecondLevelCache.Contracts; 5 | 6 | namespace EFSecondLevelCache 7 | { 8 | /// 9 | /// Returns a new cached query. 10 | /// 11 | public static class EFCachedQueryExtension 12 | { 13 | private static readonly IEFCacheKeyProvider _defaultCacheKeyProvider; 14 | private static readonly IEFCacheServiceProvider _defaultCacheServiceProvider; 15 | private static readonly IEFCacheKeyProvider _defaultLinqToObjectsCacheKeyProvider; 16 | 17 | static EFCachedQueryExtension() 18 | { 19 | _defaultCacheServiceProvider = new EFCacheServiceProvider(); 20 | _defaultCacheKeyProvider = new EFCacheKeyProvider(new EFCacheKeyHashProvider()); 21 | _defaultLinqToObjectsCacheKeyProvider = new LinqToObjectsCacheKeyProvider(new EFCacheKeyHashProvider()); 22 | } 23 | 24 | /// 25 | /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider. 26 | /// 27 | /// Entity type. 28 | /// The input EF query. 29 | /// Determines the AbsoluteExpiration time and Priority of the cache. 30 | /// Stores the debug information of the caching process. 31 | /// Gets an EF query and returns its hash to store in the cache. 32 | /// Cache Service Provider. 33 | /// 34 | public static EFCachedQueryable Cacheable( 35 | this IQueryable query, EFCachePolicy efCachePolicy, EFCacheDebugInfo debugInfo, 36 | IEFCacheKeyProvider cacheKeyProvider, IEFCacheServiceProvider cacheServiceProvider) 37 | { 38 | var noTrackingQuery = query.toAsNoTrackingQuery(); 39 | if (isLinqToObjectsQuery(noTrackingQuery)) 40 | { 41 | return new EFCachedQueryable( 42 | query, efCachePolicy, debugInfo, _defaultLinqToObjectsCacheKeyProvider, cacheServiceProvider); 43 | } 44 | return new EFCachedQueryable( 45 | noTrackingQuery, efCachePolicy, debugInfo, cacheKeyProvider, cacheServiceProvider); 46 | } 47 | 48 | /// 49 | /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider. 50 | /// 51 | /// Entity type. 52 | /// The input EF query. 53 | /// Determines the AbsoluteExpiration time and Priority of the cache. 54 | /// Stores the debug information of the caching process. 55 | /// Provides functionality to evaluate queries against a specific data source. 56 | public static EFCachedQueryable Cacheable( 57 | this IQueryable query, EFCachePolicy efCachePolicy, EFCacheDebugInfo debugInfo) 58 | { 59 | return Cacheable(query, efCachePolicy, debugInfo, _defaultCacheKeyProvider, _defaultCacheServiceProvider); 60 | } 61 | 62 | /// 63 | /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider. 64 | /// 65 | /// Entity type. 66 | /// The input EF query. 67 | /// Provides functionality to evaluate queries against a specific data source. 68 | public static EFCachedQueryable Cacheable(this IQueryable query) 69 | { 70 | return Cacheable(query, new EFCachePolicy(), new EFCacheDebugInfo()); 71 | } 72 | 73 | /// 74 | /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider. 75 | /// 76 | /// Entity type. 77 | /// The input EF query. 78 | /// Stores the debug information of the caching process. 79 | /// Provides functionality to evaluate queries against a specific data source. 80 | public static EFCachedQueryable Cacheable(this IQueryable query, EFCacheDebugInfo debugInfo) 81 | { 82 | return Cacheable(query, new EFCachePolicy(), debugInfo); 83 | } 84 | 85 | /// 86 | /// Returns a new query where the entities returned will be cached in the IEFCacheServiceProvider. 87 | /// 88 | /// Entity type. 89 | /// The input EF query. 90 | /// Determines the AbsoluteExpiration time and Priority of the cache. 91 | /// Provides functionality to evaluate queries against a specific data source. 92 | public static EFCachedQueryable Cacheable( 93 | this IQueryable query, EFCachePolicy efCachePolicy) 94 | { 95 | return Cacheable(query, efCachePolicy, new EFCacheDebugInfo()); 96 | } 97 | 98 | private static bool isLinqToObjectsQuery(IQueryable noTrackingQuery) 99 | { 100 | return noTrackingQuery == null; 101 | } 102 | 103 | /// 104 | /// Returns a new query where the entities returned will not be cached in the DbContext. 105 | /// 106 | private static IQueryable toAsNoTrackingQuery(this IQueryable query) 107 | { 108 | var originalObjectQuery = query as ObjectQuery; 109 | if (originalObjectQuery != null) 110 | { 111 | originalObjectQuery.MergeOption = MergeOption.NoTracking; 112 | return query; 113 | } 114 | 115 | var dbQuery = query as DbQuery; 116 | return dbQuery == null ? null : dbQuery.AsNoTracking(); 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.MockingTests/EFSecondLevelCache.MockingTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {70BC2DB8-D5E6-4B46-9368-2F57B127B24F} 7 | Library 8 | Properties 9 | EFSecondLevelCache.MockingTests 10 | EFSecondLevelCache.MockingTests 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | ..\..\ 20 | true 21 | 22 | 23 | true 24 | full 25 | false 26 | bin\Debug\ 27 | DEBUG;TRACE 28 | prompt 29 | 4 30 | 31 | 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | 39 | 40 | 41 | False 42 | ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll 43 | 44 | 45 | False 46 | ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | {c348a09f-eed0-4e99-a3b9-5a698c6883d2} 76 | EFSecondLevelCache 77 | 78 | 79 | {a206c065-3ca1-4c61-9191-e57ccd589b89} 80 | EFSecondLevelCache.TestDataLayer 81 | 82 | 83 | 84 | 85 | 86 | 87 | False 88 | 89 | 90 | False 91 | 92 | 93 | False 94 | 95 | 96 | False 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 107 | 108 | 109 | 110 | 117 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalTests/EFSecondLevelCache.FunctionalTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {59E13D5F-0634-4088-8863-D30978DA4ECC} 7 | Library 8 | Properties 9 | EFSecondLevelCache.FunctionalTests 10 | EFSecondLevelCache.FunctionalTests 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | ..\..\ 20 | true 21 | 22 | 23 | 24 | pdbonly 25 | true 26 | bin\Release\ 27 | TRACE 28 | prompt 29 | 4 30 | false 31 | 32 | 33 | 34 | False 35 | ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll 36 | 37 | 38 | ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll 39 | True 40 | 41 | 42 | ..\..\packages\FluentValidationNA.1.2.16\lib\net40\FluentValidationNA.dll 43 | True 44 | 45 | 46 | 47 | 48 | 3.5 49 | 50 | 51 | ..\..\packages\System.Linq.Dynamic.Library.1.1.14\lib\net40\System.Linq.Dynamic.dll 52 | True 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | {c348a09f-eed0-4e99-a3b9-5a698c6883d2} 84 | EFSecondLevelCache 85 | 86 | 87 | {a206c065-3ca1-4c61-9191-e57ccd589b89} 88 | EFSecondLevelCache.TestDataLayer 89 | 90 | 91 | 92 | 93 | 94 | 95 | False 96 | 97 | 98 | False 99 | 100 | 101 | False 102 | 103 | 104 | False 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 115 | 116 | 117 | 118 | 125 | -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | true 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | 32 | 33 | 34 | 35 | $(SolutionDir).nuget 36 | 37 | 38 | 39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config 40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config 41 | 42 | 43 | 44 | $(MSBuildProjectDirectory)\packages.config 45 | $(PackagesProjectConfig) 46 | 47 | 48 | 49 | 50 | $(NuGetToolsPath)\NuGet.exe 51 | @(PackageSource) 52 | 53 | "$(NuGetExePath)" 54 | mono --runtime=v4.0.30319 "$(NuGetExePath)" 55 | 56 | $(TargetDir.Trim('\\')) 57 | 58 | -RequireConsent 59 | -NonInteractive 60 | 61 | "$(SolutionDir) " 62 | "$(SolutionDir)" 63 | 64 | 65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 67 | 68 | 69 | 70 | RestorePackages; 71 | $(BuildDependsOn); 72 | 73 | 74 | 75 | 76 | $(BuildDependsOn); 77 | BuildPackage; 78 | 79 | 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /EFSecondLevelCache/EFCachedQueryProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using EFSecondLevelCache.Contracts; 7 | #if !NET40 8 | using System.Data.Entity.Infrastructure; 9 | #endif 10 | 11 | namespace EFSecondLevelCache 12 | { 13 | /// 14 | /// Defines methods to create and execute queries that are described by an System.Linq.IQueryable object. 15 | /// 16 | /// Type of the entity. 17 | public class EFCachedQueryProvider : IQueryProvider 18 | #if !NET40 19 | , IDbAsyncQueryProvider 20 | #endif 21 | { 22 | private readonly IQueryable _query; 23 | private readonly EFCachePolicy _efCachePolicy; 24 | private readonly EFCacheDebugInfo _debugInfo; 25 | private readonly IEFCacheKeyProvider _cacheKeyProvider; 26 | private readonly IEFCacheServiceProvider _cacheServiceProvider; 27 | 28 | /// 29 | /// Defines methods to create and execute queries that are described by an System.Linq.IQueryable object. 30 | /// 31 | /// The input EF query. 32 | /// Determines the AbsoluteExpiration time and Priority of the cache. 33 | /// Stores the debug information of the caching process. 34 | /// Gets an EF query and returns its hash to store in the cache. 35 | /// The Cache Service Provider. 36 | public EFCachedQueryProvider( 37 | IQueryable query, 38 | EFCachePolicy efCachePolicy, 39 | EFCacheDebugInfo debugInfo, 40 | IEFCacheKeyProvider cacheKeyProvider, 41 | IEFCacheServiceProvider cacheServiceProvider) 42 | { 43 | _query = query; 44 | _efCachePolicy = efCachePolicy; 45 | _debugInfo = debugInfo; 46 | _cacheKeyProvider = cacheKeyProvider; 47 | _cacheServiceProvider = cacheServiceProvider; 48 | } 49 | 50 | /// 51 | /// Constructs an System.Linq.IQueryable of T object that can evaluate the query represented by a specified expression tree. 52 | /// 53 | /// The type of the elements that is returned. 54 | /// An expression tree that represents a LINQ query. 55 | /// An System.Linq.IQueryable of T that can evaluate the query represented by the specified expression tree. 56 | public IQueryable CreateQuery(Expression expression) 57 | { 58 | return _query.Provider.CreateQuery(expression); 59 | } 60 | 61 | /// 62 | /// Constructs an System.Linq.IQueryable object that can evaluate the query represented by a specified expression tree. 63 | /// 64 | /// An expression tree that represents a LINQ query. 65 | /// An System.Linq.IQueryable that can evaluate the query represented by the specified expression tree. 66 | public IQueryable CreateQuery(Expression expression) 67 | { 68 | return _query.Provider.CreateQuery(expression); 69 | } 70 | 71 | /// 72 | /// Executes the strongly-typed query represented by a specified expression tree. 73 | /// 74 | /// The type of the value that results from executing the query. 75 | /// An expression tree that represents a LINQ query. 76 | /// The value that results from executing the specified query. 77 | public TResult Execute(Expression expression) 78 | { 79 | return (TResult)Materialize(expression, () => _query.Provider.Execute(expression)); 80 | } 81 | 82 | /// 83 | /// Executes the query represented by a specified expression tree. 84 | /// 85 | /// An expression tree that represents a LINQ query. 86 | /// The value that results from executing the specified query. 87 | public object Execute(Expression expression) 88 | { 89 | return Materialize(expression, () => _query.Provider.Execute(expression)); 90 | } 91 | 92 | /// 93 | /// Executes the query represented by a specified expression tree to cache its results. 94 | /// 95 | /// An expression tree that represents a LINQ query. 96 | /// How to run the query. 97 | /// The value that results from executing the specified query. 98 | public object Materialize(Expression expression, Func materializer) 99 | { 100 | var cacheKey = _cacheKeyProvider.GetEFCacheKey( 101 | _query, 102 | expression, 103 | _efCachePolicy.KeyHashPrefix, 104 | _efCachePolicy.SaltKey); 105 | _debugInfo.EFCacheKey = cacheKey; 106 | var queryCacheKey = cacheKey.KeyHash; 107 | var result = _cacheServiceProvider.GetValue(queryCacheKey); 108 | if(Equals(result, _cacheServiceProvider.NullObject)) 109 | { 110 | _debugInfo.IsCacheHit = true; 111 | return null; 112 | } 113 | 114 | if (result != null) 115 | { 116 | _debugInfo.IsCacheHit = true; 117 | return result; 118 | } 119 | 120 | result = materializer(); 121 | 122 | _cacheServiceProvider.StoreRootCacheKeys(cacheKey.CacheDependencies); 123 | if (_efCachePolicy.AbsoluteExpiration == null) 124 | { 125 | _efCachePolicy.AbsoluteExpiration = DateTime.Now.AddMinutes(20); 126 | } 127 | _cacheServiceProvider.InsertValue( 128 | queryCacheKey, 129 | result, 130 | cacheKey.CacheDependencies, 131 | _efCachePolicy.AbsoluteExpiration.Value, 132 | _efCachePolicy.Priority); 133 | 134 | return result; 135 | } 136 | 137 | #region IDbAsyncQueryProvider implementation 138 | #if !NET40 139 | /// 140 | /// Asynchronously executes the strongly-typed query represented by a specified expression tree. 141 | /// 142 | /// The type of the value that results from executing the query. 143 | /// An expression tree that represents a LINQ query. 144 | /// A CancellationToken to observe while waiting for the task to complete. 145 | /// A task that represents the asynchronous operation. The task result contains the value that results from executing the specified query. 146 | public Task ExecuteAsync(Expression expression, CancellationToken cancellationToken) 147 | { 148 | return Task.FromResult(Execute(expression)); 149 | } 150 | 151 | /// 152 | /// Asynchronously executes the query represented by a specified expression tree. 153 | /// 154 | /// An expression tree that represents a LINQ query. 155 | /// A CancellationToken to observe while waiting for the task to complete. 156 | /// A task that represents the asynchronous operation. The task result contains the value that results from executing the specified query. 157 | public Task ExecuteAsync(Expression expression, CancellationToken cancellationToken) 158 | { 159 | return Task.FromResult(Execute(expression)); 160 | } 161 | #endif 162 | #endregion 163 | } 164 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalTests/CachedUserEntityTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Text; 5 | using EFSecondLevelCache.Contracts; 6 | using EFSecondLevelCache.TestDataLayer.DataLayer; 7 | using EFSecondLevelCache.TestDataLayer.Models; 8 | using Microsoft.VisualStudio.TestTools.UnitTesting; 9 | 10 | namespace EFSecondLevelCache.FunctionalTests 11 | { 12 | [TestClass] 13 | public class CachedUserEntityTests 14 | { 15 | readonly Random _rnd = new Random(); 16 | 17 | [TestInitialize] 18 | public void ClearEFGlobalCacheBeforeEachTest() 19 | { 20 | new EFCacheServiceProvider().ClearAllCachedEntries(); 21 | 22 | 23 | using (var context = new SampleContext()) 24 | { 25 | var user = new User 26 | { 27 | Name = string.Format("User {0}", _rnd.Next()) 28 | }; 29 | context.Users.Add(user); 30 | context.SaveChanges(); 31 | } 32 | } 33 | 34 | [TestMethod] 35 | public void TestSecondLevelCacheDoesNotHitTheDatabase() 36 | { 37 | int id = 20000; 38 | 39 | var databaseLog = new StringBuilder(); 40 | 41 | var uow = new SampleContext(); 42 | uow.Database.Log = commandLine => 43 | { 44 | databaseLog.AppendLine(commandLine); 45 | Trace.Write(commandLine); 46 | }; 47 | 48 | 49 | Trace.WriteLine("1st query, reading from db."); 50 | databaseLog.Clear(); 51 | var debugInfo1 = new EFCacheDebugInfo(); 52 | var list1 = uow.Set() 53 | .OrderBy(x => x.Name) 54 | .Where(x => x.Id > id) 55 | .Cacheable(debugInfo1) 56 | .ToList(); 57 | var sqlCommands = databaseLog.ToString().Trim(); 58 | Assert.AreEqual(false, string.IsNullOrWhiteSpace(sqlCommands)); 59 | Assert.AreEqual(false, debugInfo1.IsCacheHit); 60 | Assert.IsNotNull(list1); 61 | var hash1 = debugInfo1.EFCacheKey.KeyHash; 62 | 63 | Trace.WriteLine("same query, reading from 2nd level cache."); 64 | databaseLog.Clear(); 65 | var debugInfo2 = new EFCacheDebugInfo(); 66 | var list2 = uow.Set() 67 | .OrderBy(x => x.Name) 68 | .Where(x => x.Id > id) 69 | .Cacheable(debugInfo2) 70 | .ToList(); 71 | sqlCommands = databaseLog.ToString().Trim(); 72 | Assert.AreEqual(true, string.IsNullOrWhiteSpace(sqlCommands)); 73 | Assert.AreEqual(true, debugInfo2.IsCacheHit); 74 | Assert.IsNotNull(list2); 75 | var hash2 = debugInfo2.EFCacheKey.KeyHash; 76 | 77 | 78 | Trace.WriteLine("same query, reading from 2nd level cache."); 79 | databaseLog.Clear(); 80 | var debugInfo3 = new EFCacheDebugInfo(); 81 | var list3 = uow.Set() 82 | .OrderBy(x => x.Name) 83 | .Where(x => x.Id > id) 84 | .Cacheable(debugInfo3) 85 | .ToList(); 86 | sqlCommands = databaseLog.ToString().Trim(); 87 | Assert.AreEqual(true, string.IsNullOrWhiteSpace(sqlCommands)); 88 | Assert.AreEqual(true, debugInfo3.IsCacheHit); 89 | Assert.IsNotNull(list3); 90 | var hash3 = debugInfo3.EFCacheKey.KeyHash; 91 | 92 | Assert.AreEqual(hash1, hash2); 93 | Assert.AreEqual(hash2, hash3); 94 | 95 | Trace.WriteLine("different query, reading from db."); 96 | databaseLog.Clear(); 97 | var debugInfo4 = new EFCacheDebugInfo(); 98 | var list4 = uow.Set() 99 | .OrderBy(x => x.Name) 100 | .Where(x => x.Id > 20001) 101 | .Cacheable(debugInfo4) 102 | .ToList(); 103 | sqlCommands = databaseLog.ToString().Trim(); 104 | Assert.AreEqual(false, string.IsNullOrWhiteSpace(sqlCommands)); 105 | Assert.AreEqual(false, debugInfo4.IsCacheHit); 106 | Assert.IsNotNull(list4); 107 | 108 | var hash4 = debugInfo4.EFCacheKey.KeyHash; 109 | Assert.AreNotSame(hash3, hash4); 110 | 111 | uow.Dispose(); 112 | } 113 | 114 | [TestMethod] 115 | public void TestSecondLevelCacheDoesNotHitTheDatabase2() 116 | { 117 | int id = 20000; 118 | var databaseLog = new StringBuilder(); 119 | 120 | using (var uow = new SampleContext()) 121 | { 122 | uow.Database.Log = commandLine => 123 | { 124 | databaseLog.AppendLine(commandLine); 125 | Trace.Write(commandLine); 126 | }; 127 | 128 | using (var ctx = new SampleContext()) 129 | { 130 | ctx.Database.Log = commandLine => 131 | { 132 | databaseLog.AppendLine(commandLine); 133 | Trace.Write(commandLine); 134 | }; 135 | 136 | 137 | Trace.WriteLine("1st query, reading from db."); 138 | databaseLog.Clear(); 139 | var debugInfo1 = new EFCacheDebugInfo(); 140 | var list1 = ctx.Users 141 | .OrderBy(x => x.Name) 142 | .Where(x => x.Id > id) 143 | .Cacheable(debugInfo1) 144 | .ToList(); 145 | var sqlCommands = databaseLog.ToString().Trim(); 146 | Assert.AreEqual(false, string.IsNullOrWhiteSpace(sqlCommands)); 147 | Assert.AreEqual(false, debugInfo1.IsCacheHit); 148 | Assert.IsNotNull(list1); 149 | var hash1 = debugInfo1.EFCacheKey.KeyHash; 150 | 151 | Trace.WriteLine("same query, reading from 2nd level cache."); 152 | databaseLog.Clear(); 153 | var debugInfo2 = new EFCacheDebugInfo(); 154 | var list2 = ctx.Users 155 | .OrderBy(x => x.Name) 156 | .Where(x => x.Id > id) 157 | .Cacheable(debugInfo2) 158 | .ToList(); 159 | sqlCommands = databaseLog.ToString().Trim(); 160 | Assert.AreEqual(true, string.IsNullOrWhiteSpace(sqlCommands)); 161 | Assert.AreEqual(true, debugInfo2.IsCacheHit); 162 | Assert.IsNotNull(list2); 163 | var hash2 = debugInfo2.EFCacheKey.KeyHash; 164 | 165 | 166 | Trace.WriteLine("same query, reading from 2nd level cache."); 167 | databaseLog.Clear(); 168 | var debugInfo3 = new EFCacheDebugInfo(); 169 | var list3 = uow.Set() 170 | .OrderBy(x => x.Name) 171 | .Where(x => x.Id > id) 172 | .Cacheable(debugInfo3) 173 | .ToList(); 174 | sqlCommands = databaseLog.ToString().Trim(); 175 | Assert.AreEqual(true, string.IsNullOrWhiteSpace(sqlCommands)); 176 | Assert.AreEqual(true, debugInfo3.IsCacheHit); 177 | Assert.IsNotNull(list3); 178 | var hash3 = debugInfo3.EFCacheKey.KeyHash; 179 | 180 | Assert.AreEqual(hash1, hash2); 181 | Assert.AreEqual(hash2, hash3); 182 | 183 | Trace.WriteLine("different query, reading from db."); 184 | databaseLog.Clear(); 185 | var debugInfo4 = new EFCacheDebugInfo(); 186 | var list4 = uow.Set() 187 | .OrderBy(x => x.Name) 188 | .Where(x => x.Id > 20001) 189 | .Cacheable(debugInfo4) 190 | .ToList(); 191 | sqlCommands = databaseLog.ToString().Trim(); 192 | Assert.AreEqual(false, string.IsNullOrWhiteSpace(sqlCommands)); 193 | Assert.AreEqual(false, debugInfo4.IsCacheHit); 194 | Assert.IsNotNull(list4); 195 | 196 | var hash4 = debugInfo4.EFCacheKey.KeyHash; 197 | Assert.AreNotSame(hash3, hash4); 198 | } 199 | } 200 | } 201 | } 202 | } -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalDbFirstTests/EFSecondLevelCache.FunctionalDbFirstTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {1651B9BE-7E09-4ABA-A26B-1FF1BF7B9AC3} 7 | Library 8 | Properties 9 | EFSecondLevelCache.FunctionalDbFirstTests 10 | EFSecondLevelCache.FunctionalDbFirstTests 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | ..\..\ 21 | true 22 | 23 | 24 | true 25 | full 26 | false 27 | bin\Debug\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | false 32 | 33 | 34 | pdbonly 35 | true 36 | bin\Release\ 37 | TRACE 38 | prompt 39 | 4 40 | false 41 | 42 | 43 | 44 | ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll 45 | True 46 | 47 | 48 | ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll 49 | True 50 | 51 | 52 | 53 | 54 | 3.5 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | True 76 | True 77 | DBModel.Context.tt 78 | 79 | 80 | True 81 | True 82 | DBModel.tt 83 | 84 | 85 | True 86 | True 87 | DBModel.edmx 88 | 89 | 90 | DBModel.tt 91 | 92 | 93 | DBModel.tt 94 | 95 | 96 | 97 | 98 | DBModel.tt 99 | 100 | 101 | 102 | 103 | 104 | EntityModelCodeGenerator 105 | DBModel.Designer.cs 106 | 107 | 108 | TextTemplatingFileGenerator 109 | DBModel.edmx 110 | DBModel.Context.cs 111 | 112 | 113 | DBModel.edmx 114 | 115 | 116 | TextTemplatingFileGenerator 117 | DBModel.edmx 118 | DBModel.cs 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | {c348a09f-eed0-4e99-a3b9-5a698c6883d2} 128 | EFSecondLevelCache 129 | 130 | 131 | 132 | 133 | 134 | 135 | False 136 | 137 | 138 | False 139 | 140 | 141 | False 142 | 143 | 144 | False 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 155 | 156 | 157 | 158 | 165 | -------------------------------------------------------------------------------- /EFSecondLevelCache/EFSecondLevelCache.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {C348A09F-EED0-4E99-A3B9-5A698C6883D2} 8 | Library 9 | Properties 10 | EFSecondLevelCache 11 | EFSecondLevelCache 12 | v4.5 13 | 512 14 | NET45 15 | ..\ 16 | true 17 | 18 | 19 | 20 | pdbonly 21 | true 22 | bin\Release\NET45\ 23 | TRACE;NET45 24 | prompt 25 | 4 26 | bin\Release\EFSecondLevelCache.XML 27 | true 28 | true 29 | false 30 | 31 | 32 | true 33 | full 34 | false 35 | bin\$(Configuration)\$(Framework)\ 36 | DEBUG;TRACE;NET45 37 | prompt 38 | 4 39 | false 40 | 41 | 42 | v4.5 43 | pdbonly 44 | true 45 | bin\$(Configuration)\$(Framework)\ 46 | TRACE;NET45 47 | prompt 48 | 4 49 | 50 | 51 | v4.5 52 | AnyCPU 53 | true 54 | full 55 | false 56 | bin\$(Configuration)\$(Framework)\ 57 | DEBUG;TRACE;NET45 58 | prompt 59 | 4 60 | 61 | 62 | v4.0 63 | pdbonly 64 | true 65 | bin\$(Configuration)\$(Framework)\ 66 | TRACE;NET40 67 | prompt 68 | 4 69 | 70 | 71 | v4.0 72 | AnyCPU 73 | true 74 | full 75 | false 76 | bin\$(Configuration)\$(Framework)\ 77 | DEBUG;TRACE;NET40 78 | prompt 79 | 4 80 | 81 | 82 | 83 | False 84 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll 85 | 86 | 87 | False 88 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll 89 | 90 | 91 | False 92 | ..\packages\EntityFramework.6.1.3\lib\net40\EntityFramework.dll 93 | 94 | 95 | False 96 | ..\packages\EntityFramework.6.1.3\lib\net40\EntityFramework.SqlServer.dll 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | Designer 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 156 | 157 | 158 | 159 | 166 | -------------------------------------------------------------------------------- /EFSecondLevelCache.Tests/EFSecondLevelCache.FunctionalTests/DynamicLINQTests.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Entity; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Linq.Dynamic; // https://github.com/NArnott/System.Linq.Dynamic 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using EFSecondLevelCache.Contracts; 8 | using EFSecondLevelCache.TestDataLayer.DataLayer; 9 | using EFSecondLevelCache.TestDataLayer.Models; 10 | using Microsoft.VisualStudio.TestTools.UnitTesting; 11 | 12 | namespace EFSecondLevelCache.FunctionalTests 13 | { 14 | [DynamicLinqType] 15 | public class ProductInfo 16 | { 17 | public int ProductId { get; set; } 18 | public string ProductName { get; set; } 19 | } 20 | 21 | [TestClass] 22 | public class DynamicLINQTests 23 | { 24 | [TestInitialize] 25 | public void ClearEFGlobalCacheBeforeEachTest() 26 | { 27 | new EFCacheServiceProvider().ClearAllCachedEntries(); 28 | } 29 | 30 | [TestMethod] 31 | public void TestDynamicLINQWorksUsingProjections() 32 | { 33 | using (var context = new SampleContext()) 34 | { 35 | var isActive = true; 36 | var name = "Product1"; 37 | 38 | var databaseLog = new StringBuilder(); 39 | context.Database.Log = commandLine => 40 | { 41 | databaseLog.AppendLine(commandLine); 42 | Trace.Write(commandLine); 43 | }; 44 | 45 | 46 | Trace.WriteLine("Projection 1"); 47 | databaseLog.Clear(); 48 | var debugInfo2 = new EFCacheDebugInfo(); 49 | var list2 = context.Products 50 | .OrderBy("ProductNumber") 51 | .Where("IsActive = @0 and ProductName = @1", isActive, name) 52 | .Select("ProductId") 53 | .Cast() 54 | .Cacheable(debugInfo2) 55 | .ToList(); 56 | var sqlCommands = databaseLog.ToString().Trim(); 57 | Assert.AreEqual(false, string.IsNullOrWhiteSpace(sqlCommands)); 58 | Assert.AreEqual(false, debugInfo2.IsCacheHit); 59 | Assert.IsTrue(list2.Any()); 60 | 61 | Trace.WriteLine("Projection 2"); 62 | databaseLog.Clear(); 63 | var debugInfo3 = new EFCacheDebugInfo(); 64 | list2 = context.Products 65 | .OrderBy("ProductNumber") 66 | .Where("IsActive = @0 and ProductName = @1", isActive, name) 67 | .Select("ProductId") 68 | .Cast() 69 | .Cacheable(debugInfo3) 70 | .ToList(); 71 | sqlCommands = databaseLog.ToString().Trim(); 72 | Assert.AreEqual(true, string.IsNullOrWhiteSpace(sqlCommands)); 73 | Assert.AreEqual(true, debugInfo3.IsCacheHit); 74 | Assert.IsTrue(list2.Any()); 75 | } 76 | } 77 | 78 | [TestMethod] 79 | public void TestDynamicLINQWorksUsingFirstOrDefault() 80 | { 81 | using (var context = new SampleContext()) 82 | { 83 | var isActive = true; 84 | var name = "Product1"; 85 | 86 | var databaseLog = new StringBuilder(); 87 | context.Database.Log = commandLine => 88 | { 89 | databaseLog.AppendLine(commandLine); 90 | Trace.Write(commandLine); 91 | }; 92 | 93 | 94 | Trace.WriteLine("Projection 1"); 95 | databaseLog.Clear(); 96 | var debugInfo2 = new EFCacheDebugInfo(); 97 | var id1 = context.Products 98 | .OrderBy("ProductNumber") 99 | .Where("IsActive = @0 and ProductName = @1", isActive, name) 100 | .Cast() 101 | .Cacheable(debugInfo2) 102 | .FirstOrDefault(); 103 | var sqlCommands = databaseLog.ToString().Trim(); 104 | Assert.AreEqual(false, string.IsNullOrWhiteSpace(sqlCommands)); 105 | Assert.AreEqual(false, debugInfo2.IsCacheHit); 106 | Assert.IsNotNull(id1); 107 | 108 | Trace.WriteLine("Projection 2"); 109 | databaseLog.Clear(); 110 | var debugInfo3 = new EFCacheDebugInfo(); 111 | id1 = context.Products 112 | .OrderBy("ProductNumber") 113 | .Where("IsActive = @0 and ProductName = @1", isActive, name) 114 | .Cast() 115 | .Cacheable(debugInfo3) 116 | .FirstOrDefault(); 117 | sqlCommands = databaseLog.ToString().Trim(); 118 | Assert.AreEqual(true, string.IsNullOrWhiteSpace(sqlCommands)); 119 | Assert.AreEqual(true, debugInfo3.IsCacheHit); 120 | Assert.IsNotNull(id1); 121 | } 122 | } 123 | 124 | [TestMethod] 125 | public async Task TestDynamicLINQWorksUsingAsyncProjections() 126 | { 127 | using (var context = new SampleContext()) 128 | { 129 | var isActive = true; 130 | var name = "Product1"; 131 | 132 | var databaseLog = new StringBuilder(); 133 | context.Database.Log = commandLine => 134 | { 135 | databaseLog.AppendLine(commandLine); 136 | Trace.Write(commandLine); 137 | }; 138 | 139 | 140 | Trace.WriteLine("Projection 1"); 141 | databaseLog.Clear(); 142 | var debugInfo2 = new EFCacheDebugInfo(); 143 | var list2 = await context.Products 144 | .OrderBy("ProductNumber") 145 | .Where("IsActive = @0 and ProductName = @1", isActive, name) 146 | .Select("ProductId") 147 | .Cast() 148 | .Cacheable(debugInfo2) 149 | .ToListAsync(); 150 | var sqlCommands = databaseLog.ToString().Trim(); 151 | Assert.AreEqual(false, string.IsNullOrWhiteSpace(sqlCommands)); 152 | Assert.AreEqual(false, debugInfo2.IsCacheHit); 153 | Assert.IsTrue(list2.Any()); 154 | 155 | Trace.WriteLine("Projection 2"); 156 | databaseLog.Clear(); 157 | var debugInfo3 = new EFCacheDebugInfo(); 158 | list2 = await context.Products 159 | .OrderBy("ProductNumber") 160 | .Where("IsActive = @0 and ProductName = @1", isActive, name) 161 | .Select("ProductId") 162 | .Cast() 163 | .Cacheable(debugInfo3) 164 | .ToListAsync(); 165 | sqlCommands = databaseLog.ToString().Trim(); 166 | Assert.AreEqual(true, string.IsNullOrWhiteSpace(sqlCommands)); 167 | Assert.AreEqual(true, debugInfo3.IsCacheHit); 168 | Assert.IsTrue(list2.Any()); 169 | } 170 | } 171 | 172 | 173 | [TestMethod] 174 | public async Task TestDynamicLINQWorksUsingAsyncAnonymousProjections() 175 | { 176 | using (var context = new SampleContext()) 177 | { 178 | var isActive = true; 179 | var name = "Product1"; 180 | 181 | var databaseLog = new StringBuilder(); 182 | context.Database.Log = commandLine => 183 | { 184 | databaseLog.AppendLine(commandLine); 185 | Trace.Write(commandLine); 186 | }; 187 | 188 | 189 | Trace.WriteLine("Projection 1"); 190 | databaseLog.Clear(); 191 | var debugInfo2 = new EFCacheDebugInfo(); 192 | var list2 = await context.Products 193 | .OrderBy("ProductNumber") 194 | .Where("IsActive = @0 and ProductName = @1", isActive, name) 195 | //.Select("new (ProductId, ProductName) as ProductInfo") 196 | .Select("new (ProductInfo.ProductId, ProductInfo.ProductName)") 197 | .Cast()//todo: use dynamic type here .....!! 198 | .Cacheable(debugInfo2) 199 | .ToListAsync(); 200 | var sqlCommands = databaseLog.ToString().Trim(); 201 | Assert.AreEqual(false, string.IsNullOrWhiteSpace(sqlCommands)); 202 | Assert.AreEqual(false, debugInfo2.IsCacheHit); 203 | Assert.IsTrue(list2.Any()); 204 | 205 | Trace.WriteLine("Projection 2"); 206 | databaseLog.Clear(); 207 | var debugInfo3 = new EFCacheDebugInfo(); 208 | list2 = await context.Products 209 | .OrderBy("ProductNumber") 210 | .Where("IsActive = @0 and ProductName = @1", isActive, name) 211 | //.Select("new (ProductId, ProductName) as ProductInfo") 212 | .Select("new (ProductInfo.ProductId, ProductInfo.ProductName)") 213 | .Cast() 214 | .Cacheable(debugInfo3) 215 | .ToListAsync(); 216 | sqlCommands = databaseLog.ToString().Trim(); 217 | Assert.AreEqual(true, string.IsNullOrWhiteSpace(sqlCommands)); 218 | Assert.AreEqual(true, debugInfo3.IsCacheHit); 219 | Assert.IsTrue(list2.Any()); 220 | } 221 | } 222 | 223 | } 224 | } --------------------------------------------------------------------------------