缓存一个Linq查询-这可能吗

本文关键字:查询 Linq 一个 缓存 | 更新日期: 2023-09-27 17:59:40

所以,这可能看起来像是一个边缘情况,但我只是想知道这是否可能。我所拥有的是封装在IEnumerable中的静态集和延迟集的组合,例如:

public IEnumerable<T> MakeMyQuery<T>()
{
    // returns a List<T>
    var someStaticData = LoadMyStaticDataFromDatabase();
    // Returns IEnumerable<T>, but from another resource
    var deferredQuery = CreateADeferredQueryUsingYieldReturn(); 
    return someStaticData.Concat(deferredQuery);
}

因此,当我对可枚举对象调用.Take(someNumber)时,它会在尝试评估延迟组件之前先从静态数据中返回元素——实际上,我在可枚举对象后面"隐藏"了一些潜在的耗时生产任务,这样,如果我永远不需要获取这些元素,那么由于LINQ的延迟特性,它们实际上永远不会得到评估。

但是,我认为不可能缓存这个查询以备将来使用(我不相信迭代器的状态会保存在缓存中,对吧?)或者有没有一种简单的方法可以做到这一点,而不需要枚举要保存的结果?

理想情况下,我的流程应该是这样的:

public List<T> SomeMethod<T>(int numberOfGuys)
{
     IEnumerable<T> query = null;
     if(// Is in Cache)
       query = Cache["MyQuery"];
     else
     {
         query = MakeMyQuery();
         Cache["MyQuery"] = query;
     }
     return query.Take(numberOfGuys).ToList();
}

因此,我可以反复使用相同的查询来请求数据,但可能永远不必重新查询数据库。有办法做到这一点吗?

缓存一个Linq查询-这可能吗

我认为您希望将query.Take(numberOfGuys).ToList()的结果缓存在一个新的列表中。在调用MakeMyQuery()之前,您可以查看缓存列表中的元素数量(如果存在),如果缓存列表中元素的数量大于或等于numberOfGuys,那么您将从缓存列表中返回numberOfGuys。否则,您将用query.Take(numberOfGuys).ToList()的新结果替换缓存的列表。

正如default.krammer所指出的,你可能真正想要缓存的是LoadMyStaticDataFromDatabase()的结果,因为如果numberOfGuys总是小于LoadMyStaticDataFromDatabase(),你最终会重复地访问DB,直到numberOfGuys大于LoadMyStaticDataFromDatabase()返回的数字。因此,您可以在MakeMyQuery<T>()方法中缓存LoadMyStaticDataFromDatabase(),同时在SomeMethod<T>(int numberOfGuys)中缓存query.Take(numberOfGuys).ToList(),这将允许您只访问DB一次,但仍然可以利用IEnumerable<T>的延迟执行。

我知道这可能有点过时,但您可能会将数据库中的ADO.NET数据集填充为数据集,而ADO.NET层的DataTable是断开连接的层。然后数据集可以由应用程序在内存中保存一段确定的时间。然后你可以把它发布回数据库。创建您的数据集,从实体、ADO.NET连接层或Linq到SQL层填充它,它已存在并已填充,您可以根据需要进一步使用新数据填充它,然后在最终查询中将它与数据库进行比较以合并更改。

我知道我以前做过一个项目,我混合了Linq、ADO.NET和xml序列化,基本上用ADO.NET的内置xml序列化将数据从ADO.NET序列化到xml文件。然后用Linq将其读取到xml。这与您所说的类似,因为XML文件本质上是文件格式的缓存,我只是通过计算它在数据库中代表键值的不同元素来更新它。如果它的计数被取消,它就会更新,否则它就会保持不变。这不适用于数百万行的大集合,但对于我想始终访问的小东西,它很好,速度也很快。

我知道在70-516 MS Press关于.NET 4.0数据访问的书中,如果你能在网上找到的话,在书的末尾有一个关于缓存的实验室。它基本上以数据库为目标,收集自上次以来的更改,处理这些更改,然后在最后合并。这样,你就可以不断地处理内存较小的差异,但可以跟踪你的工作变化。

也许我没有完全理解你的问题。如果是,请告诉我,我会重新制定我的答案。

我相信你所写的东西已经会如你所愿。考虑以下玩具示例(类似于您显示的代码)。我还没有测试过,但你应该看到的是,如果你的Take少于4个项目,你就永远不会进入SuperExpensiveQuery

static IEnumerable<int> SuperExpensiveQuery()
{
    Console.WriteLine("super expensive query (#1)");
    yield return 100;
    Console.WriteLine("super expensive query (#2)");
    yield return 200;
    Console.WriteLine("super expensive query (#3)");
    yield return 300;
    Console.WriteLine("super expensive query (#4)");
}
static IEnumerable<int> MakeMyQuery()
{
    var someStaticData = new int[] { 1, 2, 3 };
    var deferredQuery = SuperExpensiveQuery();
    return someStaticData.Concat(deferredQuery);
}
static void Test()
{
    var query = MakeMyQuery();
    for (int i = 0; i <= 7; i++)
    {
        Console.WriteLine("BEGIN Take({0})", i);
        foreach (var n in query.Take(i))
            Console.WriteLine("    {0}", n);
        Console.WriteLine("END Take({0})", i);
    }
    Console.ReadLine();
}

我在一个项目中也有类似的需求。我最终所做的是为我的每个组件创建一个数据访问层(DAL)缓存库,我在DAL中继承它。我有一个单独的缓存类来保存缓存。请注意,我所有的对象都有ID和Name。您可以根据需要定制基类。

DAL基本类别:

public abstract class DALBaseCache<T>
    {
        public List<T> ItemList
        {
            get
            {
                List<T> itemList = DALCache.GetItem<List<T>>(typeof(T).Name + "Cache");
                if (itemList != null)
                    return itemList;
                else
                {
                    itemList = GetItemList();
                    DALCache.SetItem(typeof(T).Name + "Cache", itemList);
                    return itemList;
                }
            }
        }
        /// <summary>
        /// Get a list of all the Items
        /// </summary>
        /// <returns></returns>
        protected abstract List<T> GetItemList();
        /// <summary>
        /// Get the Item based on the ID
        /// </summary>
        /// <param name="name">ID of the Item to retrieve</param>
        /// <returns>The Item with the given ID</returns>
        public T GetItem(int id)
        {
            return (from item in ItemList
                    where (int)item.GetType().GetProperty("ID").GetValue(item, null) == id
                    select item).SingleOrDefault();
        }
        /// <summary>
        /// Get the Item based on the Name
        /// </summary>
        /// <param name="name">Name of the Item to retrieve</param>
        /// <returns>The Item with the given Name</returns>
        public T GetItem(string name)
        {
            return (from item in ItemList
                    where (string)item.GetType().GetProperty("Name").GetValue(item, null) == name
                    select item).SingleOrDefault();
        }
    }

然后是我的缓存类,它基本上保存了我的查询的字典

public static class DALCache
{
    static Dictionary<string, object> _AppCache = new Dictionary<string, object>();
    public static T GetItem<T>(string key)
    {
        if(_AppCache.ContainsKey(key))
        {
            return (T) _AppCache[key];
        }
        else
        {
            return default(T);
        }
    }
    public static void SetItem(string key, object obj)
    {
        _AppCache.Add(key, obj);
    }
}

最后是一个带有缓存列表的实现。我使用EF来获取我的CustomerType列表,并在应用程序的剩余寿命中缓存它。您可以根据需要进行更改。

public class CustomerTypeDAL: DALBaseCache<CustomerType>
{
    protected override List<CustomerType> GetItemList()
    {
        DBEntities entities = new DBEntities();
        return Mapper.Map <List<CustomerType>>(entities.GetAllCustomerTypes().ToList());
    }
}

在代码中的任何位置,您都可以将其用作:

CustomerTypeDAL customerTypeDAL = new CustomerTypeDAL();
List<CustomerType> custTypes = customerTypeDAL.ItemList;

第一次调用它时,它会从DB中获取它。之后,它将进入缓存。

是的,如果在迭代时缓存值,则是可能的。

它看起来是这样的:

var lazyList = MakeMyQuery<int>().ToLazyList();
var list1 = lazyList.Take(2).Sum();
var list2 = lazyList.Take(3).Sum();
var list3 = lazyList.Take(1).Sum();

在这种情况下:f

  • 前3个项目总共只从MakeMyQuery中产生了1次
  • 第四个项目尚未产生

这里是一个实现惰性列表的示例。