Entity Framework Model Adapter

More details about this project may be found on my blog.

Description

A set of Entity Framework connection and model adapters, allowing the arbitrary adjustment of an ObjectContext model at runtime.

This project allows a developer to adjust an Entity Framework schema at runtime when the deployment and development environments differ.

Goals

  • Run-time adjustment of an Entity Framework EDMX model, including:
    • Connection-based adaptation (e.g. use of a runtime-specified connection string)
  • Run-time adjustment of model schema, including:
    • Adjusting data-level table prefixes or suffixes
    • Adjusting the owner of database objects
  • Continued use of the Visual Studio Model designer
  • No tedious changes in the compiler-generated code
  • Continued use of an assembly-embedded EDMX model (and thereby no additional or external schema deployment files)

Background

The Visual Studio EDMX model editor allows for the visual development of an Entity Framework model, which is then typically embedded directly into the resultant assembly for later consumption. This model is highly static, and there exists no API method by which the model may be (easily) adjusted when deployment requirements differ from that at development. This is unfortunate, as this is far from an edge-case, and many circumstances exist where it would be advantageous to adapt the model to slightly differing environments (without the added overhead of maintaining multiple and highly-similar models). Until now, there was no straightforward remedy to this issue.

This solution provides a generalized framework for the runtime adaptation of an Entity Framework model, allowing the adjustment of connections and metadata (at any of the storage, mapping, or associative levels). The framework is designed to be easily extended to novel requirements, and a number of useful (and demonstrative) adapters are included to demonstrate the pattern. Adaptation is reasonably inexpensive, and since the models are cached by type, performance is not significantly affected.

Usage

  1. Download the latest release
  2. Add a reference to the adapter assembly (BrandonHaynes.ModelAdapter.EntityFramework)
  3. Add or edit your EDMX designer file in Visual Studio (or using any external tool) as normal
  4. Change the base class of the resulting ObjectContext to reflect the desired adaptation behavior using the AdaptingObjectContext (see below)
    1. Note that the Visual Studio EDMX editor UI does not expose the ObjectContext base class as an editable field. As such, it must be edited manually in the underlying generated code.
    2. Additionally, this base class must be updated with each modification to the model (as the code is, perhaps obviously, regenerated).
    3. This is especially unfortunate, and it is hoped that a future version of the editor would expose method by which this base class might be modified via the UI.
  5. Ensure that the adapter assembly is deployed with the project

AdaptingObjectContext Examples

The AdaptingObjectContext contains four constructors, prototyped as:

public AdaptingObjectContext(string connectionString, IConnectionAdapter adapter);
public AdaptingObjectContext(EntityConnection connection, IConnectionAdapter adapter);
public AdaptingObjectContext(string connectionString, string defaultContainerName, IConnectionAdapter adapter);
public AdaptingObjectContext(EntityConnection connection, string defaultContainerName, IConnectionAdapter adapter);

These constructors closely resemble the constructors present in the AdaptingObjectContext's base class, ObjectContext, with the addition of an IConnectionAdapter parameter. An instance of the IConnectionAdapter is used to perform the runtime-adaptation of the loaded model.

The provided ConnectionAdapter has a single prototype, defined as:

public ConnectionAdapter(IModelAdapter modelAdapter, Assembly resourceAssembly);

Here, one or more model adapters may be injected for highly-flexible runtime adaptation. Note that all the supplied model adapters include a constructor that allows for decoration; in this manner multiple model adapters may be chained for collaborative behavior.

This configuration adds a table prefix to all store-level entities in the model:
public partial class MyObjectContext : BrandonHaynes.ModelAdapter.EntityFramework.AdaptingObjectContext
{
        public MyObjectContext() 
		: base(myConnectionString, new ConnectionAdapter(
				new TablePrefixModelAdapter("Prefix"), 
			System.Reflection.Assembly.GetCallingAssembly()))
		{
		...
		}
}

This configuration changes the database schema used at runtime:
public partial class MyObjectContext : BrandonHaynes.ModelAdapter.EntityFramework.AdaptingObjectContext
{
        public MyObjectContext() 
		: base(myConnectionString, 
			new ConnectionAdapter(
				new TableSchemaModelAdapter("myschema"), 
			System.Reflection.Assembly.GetCallingAssembly()))
		{
		...
		}
}

This configuration utilizes decoration to add a table prefix AND suffix to all store-level entities:
public partial class MyObjectContext : BrandonHaynes.ModelAdapter.EntityFramework.AdaptingObjectContext
{
        public MyObjectContext() 
		: base(myConnectionString, 
			new ConnectionAdapter(
				new TablePrefixModelAdapter("Prefix", 
					new TableSuffixModelAdapter("Suffix")), 
			System.Reflection.Assembly.GetCallingAssembly()))
		{
		...
		}
}

Included Adapters

To demonstrate the adaptation approach used in this project, the following adapters are included out-of-the-box.

Connection Adapters

A connection adapter is used to modify the connection used at runtime by the Entity Framework.
  • IConnectionAdapter (Interface)
    • This is the interface through which connection adaption takes place. It requires that the following two methods be implemented:
		EntityConnection AdaptConnection(EntityConnection connection);
		EntityConnection AdaptConnection(string connectionString);
  • ConnectionAdapter (Concrete Class)
    • Used to specify a custom connection string at runtime, bypassing the specification made at design-time. Note that if the ConnectionAdapter is instantiated with a specific EntityConnection, it is assumed that this underlying connection should be used without modification.
    • The ConnectionAdapter requires one (or more, via decoration) model adapters (see below). A NonTransformingModelAdapter may be used when no model transformation is required.
    • A reference to the assembly within which the EDMX resource may be loaded must be specified when instantiating a ConnectionAdapter. If the passed-in connection string does not specify a resource-embedded source, this value is not otherwise used.

Model Adapters

A model adapter is used to modify the underlying model schema used at runtime by the Entity Framework.
  • IModelAdapter (Interface)
    • This is the interface through which model adaption takes place. It requires that the following three methods be implemented:
		void AdaptStoreEntitySet(XElement storeEntitySet);
		void AdaptStoreAssociationEnd(XElement associationEnd);
		void AdaptMappingStoreEntitySetAttribute(XAttribute attribute);
  • TablePrefixModelAdapter
    • This adapter may be used to include a specified prefix on all store-level elements in an Entity Framework model (this also adapts, by implication, the mappings and associations that point to this adapted store element).
    • For example, if a table is named "Table" at design-time, and the adapter is instantiated with a "My" prefix, the resultant store element would read "MyTable," and this table would be queried at runtime.
  • TableSuffixModelAdapter
    • In a manner highly similar to the TablePrefixModelAdapter, this adapter appends a suffix on all store-level elements of an Entity Framework model. By way of example, a store entity named "Table" with a suffix specified as "WithASuffix" would be transformed to use "TableWithASuffix" at runtime.
  • TableSchemaModelAdapter
    • This adapter modifies the database owner schema used at runtime by the Entity Framework.
    • As an example, if the "dbo" schema was used at design-time, but the deployment environment utilized a database with tables owned by "myschema," this adapter may be used to transform this owner at runtime.
  • NonTransformingModelAdapter (Concrete Class)
    • This adapter performs a null transformation; it may be used when a model adapter is required, but no transformation is needed.

Additional adapters may be easily developed by direct implementation of the IConnectionAdapter or IModelAdapter interfaces. Note that all the supplied adapters include a constructor that allows for decoration; in this manner multiple adapters may be chained for collaborative behavior. For example, to include the prefix "Prefix" and the suffix "Suffix" on a table element named "Table," a model adapter could be instantiated as:

var suffixAdapter = new TableSuffixModelAdapter("Suffix");
var prefixAndSuffixAdapter = new TablePrefixModelAdapter("Prefix", suffixAdapter);

With an actual ObjectContext, the base class would be changed to read:

public partial class MyObjectContext : BrandonHaynes.ModelAdapter.EntityFramework.AdaptingObjectContext
{
        public MyObjectContext() 
		: base(myConnectionString, 
			new ConnectionAdapter(
				new TablePrefixModelAdapter("Prefix", 
					new TableSuffixModelAdapter("Suffix")), 
			System.Reflection.Assembly.GetCallingAssembly()))
		{
		...
		}
}

Feedback Appreciated!

Feedback about your experiences is needed, and greatly appreciated!

Last edited Sep 27, 2010 at 1:38 PM by BrandonHaynes, version 5