zoukankan      html  css  js  c++  java
  • Entity Framework Code First Caching

    Entity Framework Code First Caching

    原文地址:http://dotnetspeak.com/index.php/2011/03/entity-framework-code-first-caching/

      最近团队改为Entity FrameWork 和ASP.NET MVC进行项目开发,为了提高访问速度必须缓存EF的查询结果,在网上查找了两种Cache的缓存方法,一种是基于EF Caching with Jarek Kowalski's Provider,博客园中已经有很多前辈们已经有了详细的介绍。还有一种方法,就是本文作者的实现方法,没有找到园子里有人翻译,就作为开博第一篇吧。

      第一种方法在Code First实现下会有小问题,改造后也可以应用,稍后再整理。

      原文作者需要实现一个方法明确哪些内容需要缓存,定义如下的扩展方法:

            public static IEnumerable<T> AsCacheable<T>(this IQueryable<T> query)
    {
    if (cacheProvider == null)
    {
    throw new InvalidOperationException("Please set cache provider (call SetCacheProvider) before using caching");
    }
    return cacheProvider.GetOrCreateCache<T>(query);
    }

      最终的调用代码如下:

    EFCacheExtensions.SetCacheProvider(MemoryCacheProvider.GetInstance());
    using (ProductContext context = new ProductContext())
    {
    var query = context.Products.OrderBy(one => one.ProductNumber).
    Where(one => one.IsActive).AsCacheable();
    }

    很简单吧。

      在下面的例子里,作者实现了一个基于内存的 Cache Provider。使用全局静态变量来实现缓存。

    作者定义了如下接口:

    public interface IEFCacheProvider

    {

    IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query);

    IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query, TimeSpan cacheDuration);

    bool RemoveFromCache<T>(IQueryable<T> query);

    }

    下面的关键的实现代码,首先看Memory Provider实现

    View Code
    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Text;

    using System.Collections.Concurrent;



    namespace EFCodeFirstCacheExtensions

    {

    public class MemoryCacheProvider : IEFCacheProvider

    {

    private MemoryCacheProvider() { }



    public static MemoryCacheProvider GetInstance()

    {

    lock (locker)

    {

    if (dictionary == null)

    {

    dictionary = new ConcurrentDictionary<string, CacheItem>();

    }



    if (instance == null)

    {

    instance = new MemoryCacheProvider();

    }

    }

    return instance;

    }



    private static ConcurrentDictionary<string, CacheItem> dictionary;

    private static MemoryCacheProvider instance;

    private static object locker = new object();



    public IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query, TimeSpan cacheDuration)

    {

    string key = GetKey<T>(query);



    CacheItem item = dictionary.GetOrAdd(

    key,

    (keyToFind) => { return new CacheItem()

    { Item = query.ToList(), AdditionTime = DateTime.Now }; });



    if (DateTime.Now.Subtract(item.AdditionTime) > cacheDuration)

    {

    item = dictionary.AddOrUpdate(

    key,

    new CacheItem() { Item = item.Item, AdditionTime = DateTime.Now },

    (keyToFind, oldItem) => { return new CacheItem()

    { Item = query.ToList(), AdditionTime = DateTime.Now }; });

    }

    foreach (var oneItem in ((List<T>)item.Item))

    {

    yield return oneItem;

    }

    }



    public IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query)

    {

    string key = GetKey<T>(query);



    CacheItem item = dictionary.GetOrAdd(

    key,

    (keyToFind) => { return new CacheItem()

    { Item = query.ToList(), AdditionTime = DateTime.Now }; });



    foreach (var oneItem in ((List<T>)item.Item))

    {

    yield return oneItem;

    }

    }



    public bool RemoveFromCache<T>(IQueryable<T> query)

    {

    string key = GetKey<T>(query);

    CacheItem item = null;

    return dictionary.TryRemove(key, out item);

    }



    private static string GetKey<T>(IQueryable<T> query)

    {

    string key = string.Concat(query.ToString(), "\n\r",

    typeof(T).AssemblyQualifiedName);

    return key;

    }

    }

    }

    Memory Provider 实现了IEFCacheProvider 接口,并实现了自动过期和不过期缓存。作者使用了EF Code First 的一个实用的功能 IQueryable 的ToString() 方法,得到执行的T-SQL语句。并且将SQL语句结合类名(查询语句返回的泛型结果T)作为缓存的KEY(这里有bug 下面有解决方案)。在执行插入缓存前执行了ToList 方法,因为Entity FrameWork是延迟执行的,直至调用结果前,查询不会执行。

    接下来是AppFabric provider 的实现,要想使用AppFabric,首先要在本机安装AppFabric服务,然后添加以下引用:

    Microsoft.ApplicationServer.Caching.Client

    Microsoft.ApplicationServer.Caching.Core

    下面是AppFabric 的实现

    View Code
        public class AppFabricCacheProvider : IEFCacheProvider
    {
    private AppFabricCacheProvider() { }

    private static object locker = new object();
    private static AppFabricCacheProvider instance;
    private static DataCache cache;

    public static AppFabricCacheProvider GetInstance()
    {
    lock (locker)
    {
    if (instance == null)
    {
    instance = new AppFabricCacheProvider();
    DataCacheFactory factory = new DataCacheFactory();
    cache = factory.GetCache("Default");
    }
    }
    return instance;
    }
    public override IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query, TimeSpan cacheDuration)
    {
    string key = GetKey<T>(query);

    var cacheItem = cache.Get(key);
    if (cacheItem == null)
    {
    cache.Put(key, query.ToList(), cacheDuration);
    foreach (var oneItem in query)
    {
    yield return oneItem;
    }
    }
    else
    {
    foreach (var oneItem in ((List<T>)cacheItem))
    {
    yield return oneItem;
    }
    }
    }

    public override IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query)
    {
    string key = GetKey<T>(query);

    var cacheItem = cache.Get(key);
    if (cacheItem == null)
    {
    cache.Put(key, query.ToList());
    foreach (var oneItem in query)
    {
    yield return oneItem;
    }
    }
    else
    {
    foreach (var oneItem in ((List<T>)cacheItem))
    {
    yield return oneItem;
    }
    }
    }

    public override bool RemoveFromCache<T>(IQueryable<T> query)
    {
    string key = GetKey<T>(query);
    CacheItem item = null;
    return cache.Remove(key);
    }

    }

    AppFabric 中已经内置了缓存依赖,不需要再自己计算缓存时间。上面使用DataCacheFactory方法来创建一个名为“Default”的实例。

    单元测试中的AppFabric 的配置文件:

    View Code
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
    <!--configSections must be the FIRST element -->
    <configSections>
    <section name="dataCacheClient"
    type
    ="Microsoft.ApplicationServer.Caching.DataCacheClientSection, Microsoft.ApplicationServer.Caching.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
    allowLocation
    ="true"
    allowDefinition
    ="Everywhere"/>
    </configSections>

    <dataCacheClient>
    <hosts>
    <host
    name="SERGEYB-PC1"
    cachePort
    ="22233"/>
    </hosts>
    <localCache
    isEnabled="true"
    sync
    ="TimeoutBased"
    objectCount
    ="100000"
    ttlValue
    ="300" />

    </dataCacheClient>

    <connectionStrings>
    <add name="ProductConnection"
    connectionString
    ="Server=(local);Database=Products;Trusted_Connection=True;"
    providerName
    ="System.Data.SqlClient"/>
    </connectionStrings>

    </configuration>

    单元测试代码:

    View Code
      [TestMethod]

    public void MemoryCacheProviderGetOrCreateCacheUsageTest()

    {

    EFCacheExtensions.SetCacheProvider(MemoryCacheProvider.GetInstance());

    using (ProductContext context = new ProductContext())

    {

    var query = context.Products

    .OrderBy(one => one.ProductNumber)

    .Where(one => one.IsActive).AsCacheable();



    Assert.AreEqual(2, query.Count(), "Should have 2 rows");



    SQLCommandHelper.ExecuteNonQuery("Update Products Set IsActive = 0");



    query = context.Products

    .OrderBy(one => one.ProductNumber)

    .Where(one => one.IsActive).AsCacheable();

    Assert.AreEqual(2, query.Count(), "Should have 2 rows");





    IQueryable<Product> cleanupQuery = context.Products

    .OrderBy(one => one.ProductNumber)

    .Where(one => one.IsActive);



    EFCacheExtensions.RemoveFromCache<Product>(cleanupQuery);



    query = context.Products

    .OrderBy(one => one.ProductNumber)

    .Where(one => one.IsActive).AsCacheable();

    Assert.AreEqual(0, query.Count(), "Should have 0 rows");



    EFCacheExtensions.RemoveFromCache<Product>(cleanupQuery);

    }



    }

     源代码下载地址:http://google.proxysec.com/baidu.com.php?u=a5c236e4cca5cc48bd8aOi8vZG90bmV0c3BlYWsuY29tL0Rvd25sb2Fkcy9FRkNvZGVGaXJzdENhY2hlLnppcA%3D%3D&b=1

    上面的缓存Key生成代码有个BUG ,SQL查询的参数没有作为缓存Key的一部分,因此会导致查询结果一致,如下代码

    var isActive = true; 
    var query = context.Products
    .OrderBy(one => one.ProductNumber)
    .Where(one => one.IsActive == isActive).AsCacheable();

    var isActive = false;
    var query = context.Products
    .OrderBy(one => one.ProductNumber)
    .Where(one => one.IsActive == isActive).AsCacheable();

    查询结果相同。

    stackoverflow上的解决方法 http://stackoverflow.com/questions/8275881/generating-cache-keys-from-iqueryable-for-caching-results-of-ef-code-first-queri
     

     原来的缓存KEY生成方法很简单:

    private static string GetKey<T>(IQueryable<T> query) 
    {
    string key = string.Concat(query.ToString(), "\n\r",
    typeof(T).AssemblyQualifiedName);
    return key;
    }

    改进后的:

    public static string GetKey<T>(IQueryable<T> query) 
    {
    var keyBuilder = new StringBuilder(query.ToString());
    var queryParamVisitor = new QueryParameterVisitor(keyBuilder);
    queryParamVisitor.GetQueryParameters(query.Expression);
    keyBuilder.Append("\n\r");
    keyBuilder.Append(typeof (T).AssemblyQualifiedName);

    return keyBuilder.ToString();
    }

    QueryParameterVisitor 的实现方法:

    View Code
    /// <summary> 
    /// <see cref="ExpressionVisitor"/> subclass which encapsulates logic to
    /// traverse an expression tree and resolve all the query parameter values
    /// </summary>
    internal class QueryParameterVisitor : ExpressionVisitor
    {
    public QueryParameterVisitor(StringBuilder sb)
    {
    QueryParamBuilder = sb;
    Visited = new Dictionary<int, bool>();
    }

    protected StringBuilder QueryParamBuilder { get; set; }
    protected Dictionary<int, bool> Visited { get; set; }

    public StringBuilder GetQueryParameters(Expression expression)
    {
    Visit(expression);
    return QueryParamBuilder;
    }

    private static object GetMemberValue(MemberExpression memberExpression, Dictionary<int, bool> visited)
    {
    object value;
    if (!TryGetMemberValue(memberExpression, out value, visited))
    {
    UnaryExpression objectMember = Expression.Convert(memberExpression, typeof (object));
    Expression<Func<object>> getterLambda = Expression.Lambda<Func<object>>(objectMember);
    Func<object> getter = null;
    try
    {
    getter = getterLambda.Compile();
    }
    catch (InvalidOperationException)
    {
    }
    if (getter != null) value = getter();
    }
    return value;
    }

    private static bool TryGetMemberValue(Expression expression, out object value, Dictionary<int, bool> visited)
    {
    if (expression == null)
    {
    // used for static fields, etc
    value = null;
    return true;
    }
    // Mark this node as visited (processed)
    int expressionHash = expression.GetHashCode();
    if (!visited.ContainsKey(expressionHash))
    {
    visited.Add(expressionHash, true);
    }
    // Get Member Value, recurse if necessary
    switch (expression.NodeType)
    {
    case ExpressionType.Constant:
    value = ((ConstantExpression) expression).Value;
    return true;
    case ExpressionType.MemberAccess:
    var me = (MemberExpression) expression;
    object target;
    if (TryGetMemberValue(me.Expression, out target, visited))
    {
    // instance target
    switch (me.Member.MemberType)
    {
    case MemberTypes.Field:
    value = ((FieldInfo) me.Member).GetValue(target);
    return true;
    case MemberTypes.Property:
    value = ((PropertyInfo) me.Member).GetValue(target, null);
    return true;
    }
    }
    break;
    }
    // Could not retrieve value
    value = null;
    return false;
    }

    protected override Expression VisitMember(MemberExpression node)
    {
    // Only process nodes that haven't been processed before, this could happen because our traversal
    // is depth-first and will "visit" the nodes in the subtree before this method (VisitMember) does
    if (!Visited.ContainsKey(node.GetHashCode()))
    {
    object value = GetMemberValue(node, Visited);
    if (value != null)
    {
    QueryParamBuilder.Append("\n\r");
    QueryParamBuilder.Append(value.ToString());
    }
    }

    return base.VisitMember(node);
    }
    }

    没有怎么翻译里面的内容,相信大牛们也不需要看我这稀烂的文笔。


     

  • 相关阅读:
    php安装扩展的几种方法
    navicat连接linux系统中mysql-错误:10038
    linux下报错bash: service: command not found
    linux配置防火墙和重启防火墙
    linux 环境安装
    匿名函数
    workman的学习总结
    xampp/apache启动失败解决方法
    Linux 查看IP
    慢查询日志
  • 原文地址:https://www.cnblogs.com/yanyan45/p/2410362.html
Copyright © 2011-2022 走看看