I've been experimenting with Peter Montgomery's "Caching the results of LINQ queries" source listed here
which creates an extension method for IQueryable<T>
returning an IEnumerable<T>
from cached data if possible. The main extension method looks like this.
/// <summary>
/// Returns the result of the query; if possible from the cache, otherwise
/// the query is materialized and the result cached before being returned.
/// </summary>
/// <param name="query">The IQueryable for which to return the query.</param>
/// <param name="priority">The relative cache priority of the object.</param>
/// <param 开发者_高级运维name="slidingExpiration">The timespan indicating the duration of the sliding expiration</param>
/// <returns>The result of the query; if possible from the cache</returns>
/// <typeparam name="T">The type of entity for which to provide the method.</typeparam>
public static IEnumerable<T> FromCache<T>(this IQueryable<T> query, CacheItemPriority priority, TimeSpan slidingExpiration)
{
string key = query.GetCacheKey();
// try to get the query result from the cache
var result = HttpRuntime.Cache.Get(key) as List<T>;
if (result == null)
{
// TODO: ... ensure that the query results do not
// hold on to resources for your particular data source
//
//////// for entity framework queries, set to NoTracking
//////var entityQuery = query as ObjectQuery<T>;
//////if (entityQuery != null)
//////{
////// entityQuery.MergeOption = MergeOption.NoTracking;
//////}
// materialize the query
result = query.ToList();
HttpRuntime.Cache.Insert(
key,
result,
null, // no cache dependency
Cache.NoAbsoluteExpiration,
slidingExpiration,
priority,
null); // no removal notification
}
return result;
}
I use the method like this:
/// <summary>
/// Retrieves all instances of the specified type, if possible from the cache.
/// Objects are maintained in a <see cref="T:System.Data.EntityState.Detached">Detached</see> state and
/// are not tracked in the <see cref="T:System.Data.Objects.ObjectStateManager">ObjectStateManager</see>.
/// </summary>
/// <returns>A list of all instances of the specified type.</returns>
/// <typeparam name="T">The type of entity for which to provide the method.</typeparam>
public IQueryable<T> All<T>() where T : class, new()
{
//return new ObjectQuery<T>(GetSetName<T>(), this.context, MergeOption.NoTracking);
return new ObjectQuery<T>(GetSetName<T>(), this.context, MergeOption.NoTracking).FromCache<T>().AsQueryable<T>();
}
I would then filter my requests like this (Querying the AdventureWorks database sample):
List<Product> products = new List<Product>(readonlySession.All<Product>()
.Where(x => x.Color.Equals("Black", StringComparison.InvariantCultureIgnoreCase)));
My problem is that when I attempt to query the data like this I get a NullReferenceException
as some products return null for the property Color
. If I query the entity without the cache there is not a problem.
Can anyone explain why this happens and how I could fix the problem?
Many thanks.
UPDATE : I've since discovered using == solves my problem though I don't know why. I'd like to be able to use Equals() though.
when you do x.Color.Equals("Black", StringComparison.InvariantCultureIgnoreCase)
and the Color is null you will get the null exception, better way is to use the static string method which will work even if the parameters are null, like that:
String.Equals(x.Color, "Black", StringComparison.InvariantCultureIgnoreCase)
It's up to the LINQ provider for the particular ORM technology you are using to translate your queries into the appropriate SQL. The EF (or L2S) provider knows how to translate x.Color.Equals("Black", ...)
into the appropriate SQL. However, when you cache the resutls (by calling ToList()
) you switch over to LINQ to Objects and then all the rules of C# start being applied: you can't call an instance method on a null instance of an object.
You could try switching it around: x=>"Black".Equals(x.Color)
(hopefully the LINQ to EF provider will understand that as well, though I can't test it right now, so you'll have to try it yourself)
精彩评论