Implementing second level caching in EF code first
نویسنده: وحید نصیری
تاریخ: ۱۳۹۱/۰۴/۰۵ ۱۱:۱۱
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Objects;
using System.Diagnostics;
using System.Linq;
using System.Web;
using System.Web.Caching;
namespace EfSecondLevelCaching.Core
{
public static class EfHttpRuntimeCacheProvider
{
#region Methods (6)
// Public Methods (2)
public static IList<TEntity> ToCacheableList<TEntity>(
this IQueryable<TEntity> query,
int durationMinutes = 15,
CacheItemPriority priority = CacheItemPriority.Normal)
{
return query.Cacheable(x => x.ToList(), durationMinutes, priority);
}
/// <summary>
/// Returns the result of the query; if possible from the cache, otherwise
/// the query is materialized and the result cached before being returned.
/// The cache entry has a one minute sliding expiration with normal priority.
/// </summary>
public static TResult Cacheable<TEntity, TResult>(
this IQueryable<TEntity> query,
Func<IQueryable<TEntity>, TResult> materializer,
int durationMinutes = 15,
CacheItemPriority priority = CacheItemPriority.Normal)
{
// Gets a cache key for a query.
var queryCacheKey = query.GetCacheKey();
// The name of the cache key used to clear the cache. All cached items depend on this key.
var rootCacheKey = typeof(TEntity).FullName;
// Try to get the query result from the cache.
printAllCachedKeys();
var result = HttpRuntime.Cache.Get(queryCacheKey);
if (result != null)
{
debugWriteLine("Fetching object '{0}__{1}' from the cache.", rootCacheKey, queryCacheKey);
return (TResult)result;
}
// Materialize the query.
result = materializer(query);
// Adding new data.
debugWriteLine("Adding new data: queryKey={0}, dependencyKey={1}", queryCacheKey, rootCacheKey);
storeRootCacheKey(rootCacheKey);
HttpRuntime.Cache.Insert(
key: queryCacheKey,
value: result,
dependencies: new CacheDependency(null, new[] { rootCacheKey }),
absoluteExpiration: DateTime.Now.AddMinutes(durationMinutes),
slidingExpiration: Cache.NoSlidingExpiration,
priority: priority,
onRemoveCallback: null);
return (TResult)result;
}
/// <summary>
/// Call this method in `public override int SaveChanges()` of your DbContext class
/// to Invalidate Second Level Cache automatically.
/// </summary>
public static void InvalidateSecondLevelCache(this DbContext ctx)
{
var changedEntityNames = ctx.ChangeTracker
.Entries()
.Where(x => x.State == EntityState.Added ||
x.State == EntityState.Modified ||
x.State == EntityState.Deleted)
.Select(x => ObjectContext.GetObjectType(x.Entity.GetType()).FullName)
.Distinct()
.ToList();
if (!changedEntityNames.Any()) return;
printAllCachedKeys();
foreach (var item in changedEntityNames)
{
item.removeEntityCache();
}
printAllCachedKeys();
}
// Private Methods (4)
private static void debugWriteLine(string format, params object[] args)
{
if (!Debugger.IsAttached) return;
Debug.WriteLine(format, args);
}
private static void printAllCachedKeys()
{
if (!Debugger.IsAttached) return;
debugWriteLine("Available cached keys list:");
int count = 0;
var enumerator = HttpRuntime.Cache.GetEnumerator();
while (enumerator.MoveNext())
{
if (enumerator.Key.ToString().StartsWith("__")) continue; // such as __System.Web.WebPages.Deployment
debugWriteLine("queryKey: {0}", enumerator.Key.ToString());
count++;
}
debugWriteLine("count: {0}", count);
}
private static void removeEntityCache(this string rootCacheKey)
{
if (string.IsNullOrWhiteSpace(rootCacheKey)) return;
debugWriteLine("Removing items with dependencyKey={0}", rootCacheKey);
// Removes all cached items depend on this key.
HttpRuntime.Cache.Remove(rootCacheKey);
}
private static void storeRootCacheKey(string rootCacheKey)
{
// The cacheKeys of a cacheDependency that are not already in cache ARE NOT inserted into the cache
// on the Insert of the item in which the dependency is used.
if (HttpRuntime.Cache.Get(rootCacheKey) != null)
return;
HttpRuntime.Cache.Add(
rootCacheKey,
rootCacheKey,
null,
Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
null);
}
#endregion Methods
}
}
var data = context.Products.AsQueryable().Cacheable(x => x.FirstOrDefault());
using System.Data.Entity;
using EfSecondLevelCaching.Core;
using EfSecondLevelCaching.Test.Models;
namespace EfSecondLevelCaching.Test.DataLayer
{
public class ProductContext : DbContext
{
public DbSet<Product> Products { get; set; }
public override int SaveChanges()
{
this.InvalidateSecondLevelCache();
return base.SaveChanges();
}
}
}
using System.Data.Entity;
using System.Linq;
using EfSecondLevelCaching.Core;
using EfSecondLevelCaching.Test.DataLayer;
using EfSecondLevelCaching.Test.Models;
using System;
namespace EfSecondLevelCaching
{
public static class TestUsages
{
public static void RunQueries()
{
using (ProductContext context = new ProductContext())
{
var isActive = true;
var name = "Product1";
// reading from db
var list1 = context.Products
.OrderBy(one => one.ProductNumber)
.Where(x => x.IsActive == isActive && x.ProductName == name)
.ToCacheableList();
// reading from cache
var list2 = context.Products
.OrderBy(one => one.ProductNumber)
.Where(x => x.IsActive == isActive && x.ProductName == name)
.ToCacheableList();
// reading from cache
var list3 = context.Products
.OrderBy(one => one.ProductNumber)
.Where(x => x.IsActive == isActive && x.ProductName == name)
.ToCacheableList();
// reading from db
var list4 = context.Products
.OrderBy(one => one.ProductNumber)
.Where(x => x.IsActive == isActive && x.ProductName == "Product2")
.ToCacheableList();
}
// removes products cache
using (ProductContext context = new ProductContext())
{
var p = new Product()
{
IsActive = false,
ProductName = "P4",
ProductNumber = "004"
};
context.Products.Add(p);
context.SaveChanges();
}
using (ProductContext context = new ProductContext())
{
var data = context.Products.AsQueryable().Cacheable(x => x.FirstOrDefault());
var data2 = context.Products.AsQueryable().Cacheable(x => x.FirstOrDefault());
context.SaveChanges();
}
}
}
}
Adding new data: queryKey=72AF5DA1BA9B91E24DCCF83E88AD1C5F, dependencyKey=EfSecondLevelCaching.Test.Models.Product Available cached keys list: queryKey: EfSecondLevelCaching.Test.Models.Product queryKey: 72AF5DA1BA9B91E24DCCF83E88AD1C5F count: 2 Fetching object 'EfSecondLevelCaching.Test.Models.Product__72AF5DA1BA9B91E24DCCF83E88AD1C5F' from the cache. Available cached keys list: queryKey: EfSecondLevelCaching.Test.Models.Product queryKey: 72AF5DA1BA9B91E24DCCF83E88AD1C5F count: 2 Fetching object 'EfSecondLevelCaching.Test.Models.Product__72AF5DA1BA9B91E24DCCF83E88AD1C5F' from the cache. Available cached keys list: queryKey: EfSecondLevelCaching.Test.Models.Product queryKey: 72AF5DA1BA9B91E24DCCF83E88AD1C5F count: 2 Adding new data: queryKey=11A2C33F9AD7821A0A31003BFF1DF886, dependencyKey=EfSecondLevelCaching.Test.Models.Product Available cached keys list: queryKey: 72AF5DA1BA9B91E24DCCF83E88AD1C5F queryKey: 11A2C33F9AD7821A0A31003BFF1DF886 queryKey: EfSecondLevelCaching.Test.Models.Product count: 3 Removing items with dependencyKey=EfSecondLevelCaching.Test.Models.Product Available cached keys list: count: 0 Available cached keys list: count: 0 Adding new data: queryKey=02E6FE403B461E45C5508684156C1D10, dependencyKey=EfSecondLevelCaching.Test.Models.Product Available cached keys list: queryKey: 02E6FE403B461E45C5508684156C1D10 queryKey: EfSecondLevelCaching.Test.Models.Product count: 2 Fetching object 'EfSecondLevelCaching.Test.Models.Product__02E6FE403B461E45C5508684156C1D10' from the cache.
var list2 = context.Products
.OrderBy(one => one.ProductNumber)
.Where(x => x.IsActive == isActive && x.ProductName == name);
ctx.Entity.SingleOrDefault(a=>a.ID==1); ctx.Entity.SingleOrDefault(a=>a.ID==2);
System.InvalidOperationExceptionWhen called from 'VisitMemberInit', rewriting a node of type 'System.Linq.Expressions.NewExpression' must return a non-null value of the same type. Alternatively, override 'VisitMemberInit' and change it to not visit children of this type.
protected override Expression VisitMemberInit(MemberInitExpression node)
{
if (node.NewExpression.NodeType == ExpressionType.New)
return node;
return base.VisitMemberInit(node);
}سعی کردم کدهام و با SecondLevelCash به صورت Reactor اصلاح کنم اما یکی از موارد پر کاربرد گرفتند Count از Iqueryble است .
موقع Count گرفتن Linq به دستورات Sql مواردی اضافه میکند و نمیتوان Count را کش کرد.
برای این دست موارد باید دستی Query کانت جنریت بشه و یا راه حل دیگه ای دارد؟
context.Products.Cacheable(x => x.First()) context.Products.Cacheable(x => x.Count()) ...
سلام؛ نظرتون در رابطه با ترکیب سطح دوم کش و از کارانداختن سطح اول کش در زمان گزارش گیری چیه؟
به نظرتون کد زیر مناسبتر هست و یا چون دستور اسکیوالی اجرا نمیشه لزومی به اجرای آن ندارد؟
context.Products.AsNoTracking().ToCacheableList()
public Article GetById(int id)
{
return articles.Where(e => e.Id == id).Include(x => x.Category).Include(x => x.Tags).Cacheable(x => x.FirstOrDefault());
} public Article GetById(int id)
{
return articles
.Where(e => e.Id == id)
.Include(x => x.Category)
.Include(x => x.Tags) //پارامتر در نظر گرفته شده برای تولید کلید کش
.Cacheable(x => x.FirstOrDefault());
} public Article GetById(int id)
{
return articles
.Include(x => x.Category)
.Include(x => x.Tags)
.Where(e => e.Id == id) //پارامتر در نظر گرفته شده برای تولید کلید کش
.Cacheable(x => x.FirstOrDefault());
}