Monday, May 13, 2013

Viewing Generated Proxy Code for Entity Framework 5 Code First

There is an old post by David DeWinter which shows how to see the proxy code generated by EF. The information is a little bit outdated or at least, there is no information on how to do the same for Code First approach.

The problem stems from the fact that David uses the CreateProxyTypes method which is not available on the DbContext class.

There are two possible approaches to get the same result (an assembly containing all your proxy types) for Code First’s DbContext.

An easiest approach is to switch the AssemblyBuilderAccess strategy just before you access any of your data. Then execute a linq statement on one of your DbSets and the proxy will automatically be created and thus – it can be persisted.

// this assumes that ctx is of type DbContext
ctx = new YourDbContext();
 
// switch the strategy to allow the assembly to be persisted
Type entityProxyFactoryType = Type.GetType(
     "System.Data.Objects.Internal.EntityProxyFactory, " + 
     typeof( ObjectContext ).Assembly.FullName );
 
const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Static;
entityProxyFactoryType.GetField( "s_ProxyAssemblyBuilderAccess", bindingFlags )
     .SetValue( null, AssemblyBuilderAccess.RunAndSave );
 
// this forces the proxy to be created
foreach ( var entity in ctx.OneOfYourDbSets )
{
}
 
var moduleBuilders = (IDictionary<Assembly, ModuleBuilder>)
    entityProxyFactoryType.GetField( "s_ModuleBuilders", bindingFlags ).GetValue( null );
 
var pocoProxyModule = moduleBuilders[typeof( YourDbContext ).Assembly];
var pocoProxyAssembly = (AssemblyBuilder)pocoProxyModule.Assembly;
 
pocoProxyAssembly.Save( "EntityProxyModule.dll" );

This method has one disadvantage – the persisted assembly doesn’t contain all possible proxies but rather – only these proxies that have been created so far (OneOfYourDbSets in the above snippet).

A difficult approach is to get deeply under the hood of the DbContext and fix David’s code to still work for this scenario:

var ctx = new YourDbContext(); 
 
Type entityProxyFactoryType = Type.GetType(
    "System.Data.Objects.Internal.EntityProxyFactory, " + 
    typeof( ObjectContext ).Assembly.FullName );
 
const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Static;
entityProxyFactoryType.GetField( "s_ProxyAssemblyBuilderAccess", bindingFlags )
    .SetValue( null, AssemblyBuilderAccess.RunAndSave );
 
// dig deeply to the internal ObjectContext in the DbContext
var changeTracker = ctx.ChangeTracker;
object _internalContext = changeTracker.GetType().InvokeMember( "_internalContext", 
    BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance, null, changeTracker , null );
object _objectContext = _internalContext.GetType().InvokeMember( "ObjectContext", 
    BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, _internalContext, null );
 
ObjectContext objectContext = _objectContext as ObjectContext;
 
objectContext.CreateProxyTypes( Assembly.GetExecutingAssembly().GetTypes() );
 
var moduleBuilders = (IDictionary<Assembly, ModuleBuilder>)
    entityProxyFactoryType.GetField( "s_ModuleBuilders", bindingFlags ).GetValue( null );
 
var pocoProxyModule = moduleBuilders[typeof( YourDbContext ).Assembly];
var pocoProxyAssembly = (AssemblyBuilder)pocoProxyModule.Assembly;
 
pocoProxyAssembly.Save( "EntityProxyModule.dll" );

In both cases, the generated assembly can be inspected with any IlSpy-like tool.

No comments: