all groups > c# > april 2008 >
You're in the

c#

group:

Cache in Dlinq



Cache in Dlinq Andrus
4/20/2008 10:14:33 AM
c#: I need to repeatedly execute same queries which returns single entity by id,
like:

Customer cust = (from c in db.Customers
where c.CustomerID=="AIRBU"
select c).SingleOrDefault();

DLinq holds tracked object list internally so customer "AIRBU" exists in
memory.

How to force DLinq to look its list and not to make round trip to server ?
I looked into Entity Framework also but havent found any caching solution in
EF.

Andrus.

Re: Cache in Dlinq Marc Gravell
4/21/2008 1:26:05 PM
"LINQ in Action" states that Single() is an exception, in that it
checks the cache first, not the database. I haven't tried it yet (long
day...) - but worth a try ;-p

(I'm assuming that SingleOrDefault doesn't qualify for this exception
since you've tried it... - and it would presumably need the predicate
to be based solely on the primary key column[s])

Re: Cache in Dlinq Marc Gravell
4/21/2008 11:38:24 PM
OK; I couldn't find a single case when Single() did anything other
than hit the database... I wonder what the authors had in mind?

Anyways... here is something truly, truly hacky that does the job; I'm
off to lather...


using System;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using WindowsFormsApplication4; // ns to DataClasses1DataContext =
Northwind

static class DataContextExt {
public static T GetFromCache<T>(this Table<T> table, params
object[] keyValues) where T : class
{
if (table == null) throw new ArgumentNullException("table");
return GetFromCache<T>(table.Context, keyValues);
}
public static T GetFromCache<T>(this DataContext context, params
object[] keyValues) where T : class
{
// surfactants on standby - this is dirty, plain and simple
// (oh, and brittle, and not compile-time safe, and requires
// sufficient trust... you get the idea; nasty nasty nasty)
if (context == null) throw new
ArgumentNullException("context");
const BindingFlags FLAGS = BindingFlags.Instance |
BindingFlags.Public | BindingFlags.NonPublic;
object services = context.GetType().GetProperty("Services",
FLAGS).GetValue(context, null);

object[] args = { context.Mapping.GetMetaType(typeof(T)),
keyValues };
Type[] paramTypes = { typeof(MetaType), typeof(object[]) };
return (T)services.GetType().GetMethod("GetCachedObject",
FLAGS, null, paramTypes, null).Invoke(services, args);
}
}
static class Program
{
static void Main()
{
using (DataClasses1DataContext ctx = new
DataClasses1DataContext())
{
// cache the original
Supplier sup = ctx.Suppliers.First();
int id = sup.SupplierID;

// get from the context
Supplier same = ctx.GetFromCache<Supplier>(id);
Trace.WriteLine(ReferenceEquals(sup, same), "Got from
Context");

// get from the table
Supplier again = ctx.Suppliers.GetFromCache(id);
Trace.WriteLine(ReferenceEquals(sup, again), "Got from
Table");

// and check what happens if it isn't there...
Supplier nothing = ctx.GetFromCache<Supplier>(53);
Trace.WriteLine(ReferenceEquals(nothing, null), "Null when
missing");
}

}
Re: Cache in Dlinq Andrus
4/22/2008 7:50:15 PM
Marc,

[quoted text, click to view]

MSDN clearly states that every simple query must return result form identity
cache if it exists, not reaching to database.

Do you have any idea why it is not working ?
How to use identity cache in documented way ?

[quoted text, click to view]

Where did you found information which allows to create such program ?
I havent found any documentation on Services property and GetCachedObject
method.

Andrus.

Re: Cache in Dlinq Marc Gravell
4/23/2008 8:26:00 AM
[quoted text, click to view]

Can you cite a source there?

[quoted text, click to view]

I have asked that very question on a LINQ group; no answer yet...

[quoted text, click to view]

From grubbling around... it is dirty and brittle (i.e. it will break if
somebody [quite reasonably] refactors the internal implementation)...

Re: Cache in Dlinq Marc Gravell
4/23/2008 8:27:35 AM
[quoted text, click to view]

(in particular - yes it should return the same instance from the cache
[and it does] - the question is focused around whether it hits the db...
Re: Cache in Dlinq Marc Gravell
4/23/2008 9:42:49 AM
So I guess it comes down to defining "easily identifiable"... I'll run
some more tests at some point (not "right now"), but I don't know how
easy / hard it will be to get working... which is why I am trying to get
some input from the LINQ people...

Re: Cache in Dlinq Andrus
4/23/2008 11:14:47 AM
[quoted text, click to view]

http://msdn2.microsoft.com/en-us/library/bb399376.aspx

Quote:
If the object requested by the query is easily identifiable as one already
retrieved, no query is executed. The identity table acts as a cache of all
previously retrieved objects.



Andrus.

Re: Cache in Dlinq Marc Gravell
4/23/2008 1:38:03 PM
[quoted text, click to view]
Feel free ["connect", or the managed LINQ forum]; personally I'd like
to do a little more looking first...

[quoted text, click to view]
The cache is internal, not protected. Subclassing won't help.

[quoted text, click to view]
Authors plural; but I guess I can try to find some e-mail addresses
and see what they say...

Re: Cache in Dlinq Andrus
4/23/2008 8:32:51 PM
Marc,

[quoted text, click to view]

id == constant expression if definitely "easily identifiable".
So it seems that we have found serious bug in Linq-SQL.
Can we submit product feedback to Microsoft?
Is it reasonable to subclass DataContext to fix this ?

There is also bug in "Linq in Action" book if it states that cache works
only with Single(). Ca we inform author about this ?

Andrus.

Re: Cache in Dlinq Marc Gravell
4/24/2008 3:35:03 AM
For info, looked at the EF offering... unfortunately it is harder to
profile, and I don't have my SQL profiler handy; following gets the
right objects at least - but I don't know (until I get a few seconds
to switch servers) how many times it hits the database:

using System;
using System.Data;
using System.Data.Objects.DataClasses;
using System.Linq;
using AdventureWorksModel;

static class Program
{
[STAThread]
static void Main()
{
AdventureWorksEntities1 ctx = new AdventureWorksEntities1();

string setName = GetEntitySetName<Customer>(false),
qualifiedSetName = GetEntitySetName<Customer>(true),
keyName = GetKeyName<Customer>();

// expect DB hit here
var cust = ctx.Customer.First();
var id = cust.CustomerID;

// look by known key
EntityKey key = ctx.GetEntityKey(setName, cust);
var byKey = ctx.GetObjectByKey(key);
object obj;
if (ctx.TryGetObjectByKey(key, out obj))
{
var cust1 = (Customer)obj;
Console.WriteLine("Got...");
}

// look by custom key and a new ctx (assume a single primary
key for keyName)
AdventureWorksEntities1 ctx2 = new AdventureWorksEntities1();
EntityKey customKey = new EntityKey(qualifiedSetName, keyName,
id);
// (different instance, but it works...; expect another DB hit
here)
var cust2 = ctx2.GetObjectByKey(customKey);

// look using predicates
var cust3 = ctx.Customer.Where(x => x.CustomerID ==
id).First();
var cust4 = ctx.Customer.First(x => x.CustomerID == id);
}

static string GetEntitySetName<T>(bool qualified) where T :
EntityObject
{
EdmEntityTypeAttribute attrib =
(EdmEntityTypeAttribute)Attribute.GetCustomAttribute(typeof(T),
typeof(EdmEntityTypeAttribute));
if (attrib == null) return null;
return qualified ? (attrib.NamespaceName + "." +
attrib.Name) : attrib.Name;
}
static string GetKeyName<T>() where T : EntityObject
{
return ( from prop in typeof(T).GetProperties()
let attrib = (EdmScalarPropertyAttribute)
Attribute.GetCustomAttribute(prop,
typeof(EdmScalarPropertyAttribute))
where attrib != null && attrib.EntityKeyProperty
select prop.Name).Single();
}
Re: Cache in Dlinq Marc Gravell
4/24/2008 12:40:09 PM
No; I've been invited to log a "connect" bug about it - I just want to
make sure I can't get it to work first...

Re: Cache in Dlinq Andrus
4/24/2008 9:08:29 PM
Marc,

[quoted text, click to view]

Thank you. Have you got any idea why Linq-SQL does not use cache ?

Andrus.

AddThis Social Bookmark Button