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.
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>));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With