Viewing Generated Proxy Code in the Entity Framework

This is one post that’s been on my to-do list for a while, and since I’ve seen some questions relating to it in our forums, I thought it appropriate to get it out the door before .NET 4 officially releases.

As you may know, the Entity Framework supports mapping database information to POCO (Plain Old CLR Objects) classes in .NET 4. Normally, giving you control over your classes would mean we lose out on a few benefits on controlling the classes ourselves, such as lazy loading and change tracking capabilities. However, if your classes meet a few requirements, then we can dynamically create an assembly at runtime which contains classes that inherit from your POCO types. Today these classes add additional behavior such as lazy loading and change tracking, but in future releases we could potentially augment them to add more features. Typically we refer to these dynamic classes as POCO proxies.

The Entity Framework uses the features in the Reflection.Emit namespace to generate these classes. If you check out Reflector, you can see that the System.Data.Objects.Internal.EntityProxyFactory.GetDynamicModule starts this process by calling AppDomain.CurrentDomain.DefineDynamicAssembly with the AssemblyBuilderAccess specified in the s_ProxyAssemblyBuilderAccess field. During normal execution, s_ProxyAssemblyBuilderAccess is AssemblyBuilderAccess.Run and will never change, but we added this field as a test hook for other purposes. As a result, you can also save the assembly to disk by setting the s_ProxyAssemblyBuilderAccess field to AssemblyBuilderAccess.RunAndSave with reflection.

You can’t just save the assembly right away, since we lazily emit new types into the dynamic assembly as they’re requested. You’ll need to force the Entity Framework to create proxy types by calling ObjectContext.CreateProxyTypes with a list of all POCO types for which you want proxies generated. Then you can save the assembly to disk by calling AssemblyBuilder.Save on the dynamic assembly. If that sounds complicated, don’t worry. We’ll walk through some code to make these steps more concrete. Please note that you’ll need to run in full trust or at least have ReflectionPermission with ReflectionPermissionFlag.MemberAccess to run the code below.

I’ve attached a solution to this post that shows the code in more detail, but let’s walk through the major parts. To set up, I’ve created an Entity Data Model based on the Northwind database, and I’ve used our POCO templates to create classes that the Entity Framework will create proxy types for. The first step is to set the s_ProxyAssemblyBuilderAccess field via reflection.

    C#

    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);

    VB

    Dim entityProxyFactoryType As Type = Type.GetType(

            "System.Data.Objects.Internal.EntityProxyFactory, " + GetType(ObjectContext).Assembly.FullName)

     

    Const bindingFlags As BindingFlags = BindingFlags.NonPublic Or BindingFlags.Static

    entityProxyFactoryType.GetField("s_ProxyAssemblyBuilderAccess", bindingFlags) _

        .SetValue(Nothing, AssemblyBuilderAccess.RunAndSave)

Next, we need to force the creation of proxy types. Here we’re using a variable context that represents an ObjectContext we’re interested in generating proxies for. (Its instantiation is not shown.) Fortunately, the CreateProxyTypes method ignores any types that are not represented by the model, so we can call the method passing all types in the current assembly.

    C#

    context.CreateProxyTypes(Assembly.GetExecutingAssembly().GetTypes());

    VB

    context.CreateProxyTypes(Assembly.GetExecutingAssembly().GetTypes())

Finally, we need to access the AssemblyBuilder using a bit of reflection and call its Save method.

    C#

    var moduleBuilders = (IDictionary<Assembly, ModuleBuilder>)

        entityProxyFactoryType.GetField("s_ModuleBuilders", bindingFlags).GetValue(null);

     

    var pocoProxyModule = moduleBuilders[typeof(NorthwindEntities).Assembly];

    var pocoProxyAssembly = (AssemblyBuilder)pocoProxyModule.Assembly;

     

    pocoProxyAssembly.Save("EntityProxyModule.dll");

    VB

    Dim moduleBuilders = DirectCast(

            entityProxyFactoryType.GetField("s_ModuleBuilders", bindingFlags).GetValue(Nothing),  _

            IDictionary(Of Assembly, ModuleBuilder))

     

    Dim pocoProxyModule = moduleBuilders(GetType(NorthwindEntities).Assembly)

    Dim pocoProxyAssembly = DirectCast(pocoProxyModule.Assembly, AssemblyBuilder)

     

    pocoProxyAssembly.Save("EntityProxyModule.dll")

After the preceding code runs, you’ll be left with a file named EntityProxyModule.dll in the current directory, which you can easily pop into Reflector to view the code for the proxies. As you can see the names of the generated types are quite long. :) Since proxies today are tied to the metadata of the ObjectContext by which they were created, we use a hash of the metadata in the proxy’s type name to correlate the proxy type with that ObjectContext. If we need to use the same CLR type for an ObjectContext with different metadata, we will create a new proxy type.

Proxy assembly in Reflector

I’m not going to dive deep into the proxy code, since it resembles code from the default code generation in some ways (e.g. change tracking, relationship management). There is one interesting piece that I’ll point out and that is how we override navigation properties.

image

Our overrides are very simple—usually we delegate to the base implementation, but if lazy loading is enabled we will call into a delegate (ef_proxy_interceptor…), which is where we will load the navigation property into memory if it’s not there already. Of course, in the proxy code there is nothing specific to lazy loading; we just call a delegate that could do any number of things in future.

I encourage you to download the solution and play around with the code. Let me know here or on our forums if you have any additional questions!