Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any way to look up an item in the L2S identity map?

LINQ-2-SQL maintains an identity map so subsequent calls to entity.First(e => e.Id == id) do not cause additional queries beyond the first one for a context.

Is there anyway to ask L2S if a particular item exists in the identity map?

I ask this cause there is support for .Attach which allows you to attach entities to a context, however the method will exception out if the item already exists in the identity map.

In an interoperability scenario I may want to load up entities in a different, faster orm and attach, however it makes no sense to lookup an entity if it is already in the identity map.

like image 900
Sam Saffron Avatar asked May 30 '11 01:05

Sam Saffron


1 Answers

No nice way... you can hack your way in, though. For example imagine you have a User object that you know is keyed by Id:

var user = // get some user
var dataContext = // your DB context

const BindingFlags AllInstance = BindingFlags.Instance | BindingFlags.NonPublic
        | BindingFlags.Public;
object commonDataServices = typeof(DataContext)
    .GetField("services", AllInstance)
    .GetValue(dataContext);
object identifier = commonDataServices.GetType()
    .GetProperty("IdentityManager", AllInstance)
    .GetValue(commonDataServices, null);
MethodInfo find = identifier.GetType().GetMethod("Find", AllInstance);
var metaType = dataContext.Mapping.GetMetaType(typeof(User));
object[] keys = new object[] { user.Id };
var user2 = (User)find.Invoke(identifier, new object[] { metaType, keys });
bool pass = ReferenceEquals(user, user2);

Pretty-much every access is non-public; I would expect this to suck performance-wise unless you use DynamicMethod to spoof access from another type.

And as a DynamicMethod version:

(yes, I'm hard-coding to an int key... you could make it any single-value by replacing the int with object, and just drop the OpCodes.Box, typeof(int) - or you could make it a params object[] argument and pass it in directly)

static readonly Func<DataContext, Type, int, object> identityLookup = BuildIdentityLookup();

static Func<DataContext, Type, int, object> BuildIdentityLookup()
{
    var quickFind = new DynamicMethod("QuickFind", typeof(object), new Type[] { typeof(DataContext), typeof(Type), typeof(int) }, typeof(DataContext), true);
    var il = quickFind.GetILGenerator();

    const BindingFlags AllInstance = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
    il.Emit(OpCodes.Ldarg_0); // DB
    var services = typeof(DataContext).GetField("services", AllInstance);
    il.Emit(OpCodes.Ldfld, services); // services

    var identifier = services.FieldType.GetProperty("IdentityManager", AllInstance);
    il.EmitCall(OpCodes.Callvirt, identifier.GetGetMethod(true), null); // identifier

    il.Emit(OpCodes.Ldarg_0); // identifier DB
    var mapping = typeof(DataContext).GetProperty("Mapping");
    il.EmitCall(OpCodes.Callvirt, mapping.GetGetMethod(), null); // identifier mapping

    il.Emit(OpCodes.Ldarg_1); // identifier mapping type
    il.EmitCall(OpCodes.Callvirt, mapping.PropertyType.GetMethod("GetMetaType"), null); // identifier metatype

    il.Emit(OpCodes.Ldc_I4_1); // identifier metatype 1
    il.Emit(OpCodes.Newarr, typeof(object)); // identifier metatype object[]
    il.Emit(OpCodes.Dup); // identifier metatype object[] object[]
    il.Emit(OpCodes.Ldc_I4_0); // identifier metatype object[] object[] 0
    il.Emit(OpCodes.Ldarg_2); // identifier metatype object[] object[] 0 id
    il.Emit(OpCodes.Box, typeof(int)); // identifier metatype object[] object[] 0 boxed-id
    il.Emit(OpCodes.Stelem_Ref); // identifier metatype object[]

    il.EmitCall(OpCodes.Callvirt, identifier.PropertyType.GetMethod("Find", AllInstance), null); // object
    il.Emit(OpCodes.Ret);

    return (Func<DataContext, Type, int, object>)quickFind.CreateDelegate(typeof(Func<DataContext, Type, int, object>));
}
like image 109
Marc Gravell Avatar answered Oct 14 '22 04:10

Marc Gravell