缓存一个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();
}
因此,我可以反复使用相同的查询来请求数据,但可能永远不必重新查询数据库。有办法做到这一点吗?
我认为您希望将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次
- 第四个项目尚未产生
这里是一个实现惰性列表的示例。