One of the limitations of Entity Framework V1 released as part of .NET Framework 3.5 SP1 and Visual Studio 2008 SP1 is lack of support for POCO entity objects. POCO stands for Plain Old CLR Object and refers to an object that does not have any persistence concerns and is not aware of persistence framework. Modern development approaches such as DDD (Domain-Driven Design) and TDD (Test-Driven Development) prefer clear separation of concerns which implies persistence ignorance.

In order to be consumable by EF, entity classes have to:

  • Derive from EntityObject class or implement at least one mandatory IPOCO interface: IEntityWithChangeTracker. If the entity participates in relationships it also has to implement IEntityWithRelationships interface. Implementing those interfaces is not hard, but causes entity objects to have a dependency on EF assembly, which is sometimes not desirable
  • Provide assembly, class and property-level attributes to define mapping from CLR space to model space (we call those O-C mapping attributes)
  • Use Entity-Framework-provided relationship classes: EntityReference<T>, EntityCollection<T> and RelationshipManager instead of CLR collections (List<T>, ICollection<T>)

Clearly those requirements make EF’s entity objects persistence-aware.

It is possible to write (generate) an adapter layer that will translate between POCO objects and Entity Framework-aware objects and provide services on top of POCO objects, such as:

  • Change tracking (snapshot-based and proxy-based when possible)
  • Transparent lazy loading
  • Immutable Value Objects
  • Queries (LINQ and Entity SQL)
  • Shadow state (maintaining certain persistence-related fields outside of entity class)

We have recently published a sample called EFPocoAdapter that includes code generator and a supporting library that implements POCO object tracking on top of Entity Framework V1. This serves two purposes:

  • To demonstrate that it is possible to track POCO objects using the current version of EF
  • To gather feedback on POCO-specific features (such as transparent lazy loading, immutable objects, change detection, etc) that customers would like to see implemented natively in future versions of Entity Framework.

The source code is released under Microsoft Public License on Code Gallery. There is no formal support for this sample, but we are interested in improving and evolving it based on user’s feedback. For legal reasons we cannot accept source code contributions, but you are more than welcome to share feature ideas and POCO usage scenarios with us.

Adapter Design

The main idea used when implementing EFPocoAdapter was the use of adapter objects. Adapters are classes that are EF-friendly (in terms of O/C mapping, relationship manager and so on), but internally manage and materialize POCO objects. Materialization is done transparently by delegating property setters and association change notifications to the POCO object. This way, whenever Entity Framework materializes (hydrates) an adapter object, a corresponding POCO object is also created. Note that property setters not only delegate values to POCO objects, but they also store a copy for themselves – this helps later in the change detection process.

Let’s take a look at a simple adapter class on top of a Region POCO that has 2 properties: RegionID and RegionDescription. It has [EdmEntityType] attribute and [EdmScalarProperty] attributes on every property. Every property setter delegates property write to corresponding property on _pocoEntity object along with firing events necessary for change tracking. A copy of object state is stored in _RegionID and _RegionDescription properties.

[EdmEntityType(NamespaceName="NorthwindEFModel", Name="Region")]
public class RegionAdapter : PocoAdapterBase<NorthwindEF.Region>, IPocoAdapter<NorthwindEF.Region>
{
    private String _RegionDescription;
    public RegionAdapter() { }

    public RegionAdapter(NorthwindEF.Region pocoObject) : base(pocoObject) { }
    // some methods removed...

    public override void DetectChanges()
    {
        // change detection – see below
    }

    private Int32 _RegionID;
 
    [EdmScalarProperty(EntityKeyProperty=true, IsNullable=false)]
    public Int32 RegionID
    {
        get
        {
            return _RegionID;
        }
        set
        {
            if (_pocoEntity != null)
            {
               // pass the property value to POCO object
                PocoEntity.RegionID = value;
            }
            ReportPropertyChanging("RegionID");
            _RegionID = value;
            ReportPropertyChanged("RegionID");
        }
    }

    [EdmScalarProperty(EntityKeyProperty=false, IsNullable=false)]
    public String RegionDescription
    {
        get
        {
            return _RegionDescription;
        }
        set
        {
            if (_pocoEntity != null)
            {
                PocoEntity.RegionDescription = value;
            }

            ReportPropertyChanging("RegionDescription");
            _RegionDescription = value;
            ReportPropertyChanged("RegionDescription");
        }
    }
}

Change detection is a step that occurs just before changes are saved to the database. Adapter objects compare their stored values and relationship information against information in POCO objects (that users may have changed). If the values differ, adapter sets its own property to the value from POCO object which essentially triggers EF change tracker and ensures that SaveChanges() will persist the object correctly. Change detection looks similar to (in reality more corner cases are covered):

public override void DetectChanges()
{
  // change detection - simplified for explanation purposes
  base.DetectChanges();
  if (PocoEntity.RegionID != _RegionID)
    this.RegionID = PocoEntity.RegionID;
  if (PocoEntity.RegionDescription != _RegionDescription)
    this.RegionDescription = PocoEntity.RegionDescription;
}

DetectChanges() method seems heavy and it really is – it has to store and compare all property values of all objects. Fortunately we can optimize the cost of change detection by introducing Proxies. Proxies are objects that implement POCO interface (by deriving from a POCO class), but they have a hidden dependency on an adapter object. They override all property getters and setters that can be overridden and provide just-in-time change tracking. See the following example of a proxy for Territory class that has a single virtual property called TerritoryDescription:

public partial class TerritoryProxy : NorthwindEF.Territories.Territory
{
    NorthwindEF.Territories.PocoAdapters.TerritoryAdapter _adapter;

    object IEntityProxy.Adapter
    {
        get { return _adapter; }
    }

    public TerritoryProxy(TerritoryAdapter adapter) { _adapter = adapter; }

    public override String TerritoryDescription
    {
        get { return this._adapter.TerritoryDescription; }
        set
        {
           // pass the value to the POCO property
            base.TerritoryDescription = value;
            if (this._adapter.Context != null 
               && value != this._adapter.TerritoryDescription)
            {
               // raise change detection event
              _adapter.Context.RaiseChangeDetected(this, 
                     "TerritoryDescription",
                     this._adapter.TerritoryDescription, value);
            }
           // pass the value to the Adapter which triggers change tracker
            this._adapter.TerritoryDescription = value;
        }
    }
}

Adapters are not meant to be seen in any scenarios and users should not be aware of their existence. To do this, we need to expose a programmable layer that hides adapters completely. We need to be able to specify LINQ to Entities queries in terms of POCO objects (similar to ObjectQuery<T>) and we want inserts/attaches/deletes to work too. We are introducing a new object called EntitySet<T>, that combines function of ObjectQuery<T> and object-management methods (AddToX(), AttachTo(), DeleteObject()). We are also generating a replacement for ObjectContext, which makes the end-to-end programming experience with POCOs more natural. See the "Working with POCO classes” section below for more usage information.

Components

The sample includes two main components:

·         EFPocoAdapter - library which you reference in your applications

·         EFPocoClassGen - code generator which generates the adapter layer

To use the sample you will need five additional pieces of the puzzle:

·         POCO classes (you typically write them by hand, the sample includes NorthwindEF project)

·         POCO container – root object used for querying POCO (equivalent of ObjectContext for persistence-aware objects).

·         POCO adapter – generated

·         Entity Data Model (CSDL,SSDL & MSL files)

·         Connection string (in your App.config or in code)

In a typical case you will generate POCO classes or write them by hand, but EFPocoClassGen has a mode to generate them from an existing model.

POCO container can be written by hand or generated from a CSDL file.

POCO Adapter for your model is generated at compile time or at runtime. In the former case, you will be able to debug the adapter code as the source code will be stored on your hard drive. In the latter case, your application will include just classes written by hand and no artificial artifacts.

Compile-Time Adapter Generation

EFPocoClassGen has three modes and can generate:

·         POCO Adapter (default)

·         POCO container

·         POCO classes

Assuming you want to generate a POCO adapter for classes that are stored in NorthwindEF.dll and your model is in NorthwindEFModel.csdl, you should run the following command:

EFPocoClassGen.exe /incsdl:NorthwindEFModel.csdl /outputfile:PocoAdapter.cs 
                   /ref:NorthwindEF.dll /map:NorthwindEFModel=NorthwindEF

This will generate PocoAdapter.cs file based on NorthwindEFModel.csdl (specified in /incsdl option). By specifying /ref argument, you allow class generator to scan the assembly and generate certain optimizations such as proxy-based change tracking for virtual properties. This is optional but if you don’t provide the assembly references you will have to put optimization hints in the CSDL file manually. /map allows you to use a different namespace in CSDL than in your classes. Code generator has some more options; to see them all, run it without any parameters.

It is usually best to automate code generation for a project by putting the appropriate commands in Pre-Build Step of VS project or in BeforeCompile target in MSBuild script. The generator will not regenerate the output file if the input hasn’t changed, so you will not get unnecessary recompiles because of that.

POCO Container

The POCO Container is a simple class that derives from EFPocoContext. It has a bunch of properties representing top-level entity sets. All methods that developers interact with are defined in the base class or in entity set methods.

public partial class NorthwindEntities : EFPocoContext<NorthwindEF.PocoAdapters.NorthwindEntitiesAdapter>
{
    public NorthwindEntities()
     : base(new NorthwindEntitiesAdapter()) { }
    public NorthwindEntities(string connectionString)
     : base(new NorthwindEntitiesAdapter(connectionString)) { }
    public NorthwindEntities(EntityConnection connection)
     : base(new NorthwindEntitiesAdapter(connection)) { }

    public IEntitySet<NorthwindEF.Employee> Employees
    {
        get { return GetEntitySet<NorthwindEF.Employee>("Employees"); }
    }
 
    public IEntitySet<NorthwindEF.Order> Orders
    {
        get { return GetEntitySet<NorthwindEF.Order>("Orders"); }
    }
 
    // additional entity sets removed for brevity
}

To generate POCO container, run the same command as above with /mode:PocoContainer – just be sure to specify a different output file:

EFPocoClassGen.exe /mode:PocoContainer /incsdl: NorthwindEFModel.csdl /outputfile:NorthwindEntities.cs 
/ref:NorthwindEF.dll /map:NorthwindEFModel=NorthwindEF

If you prefer, you can also write the class by hand – the class needs to have as many entity set properties as you require (not all EntitySets from CSDL have to exposed) and has to have one or more of the constructors as seen above.

Setting Up the Solution

The recommended way of setting up your solution when using compile-time code generation is:

1.       Put all POCO classes in a separate assembly, say NorthwindEF.dll. The assembly doesn’t need to have any references (other than mscorlib.dll and maybe System.Core.dll)

2.       Create a separate library project for your adapter, name it for example NorthwindEF.PocoAdapter.dll Add references to System.Data.Entity.dll, EFPocoAdapter.dll and NorthwindEF.dll

3.       Set up pre-build scripts that will generate PocoAdapter.cs and NorthwindEntities.cs. Build the project once and add generated files to the project (you don’t have to add them to source control as they will be regenerated at build time)

4.       In your code that uses the POCO adapter, add references to: EFPocoAdapter.dll, NorthwindEF.dll, NorthwindEF.PocoAdapter.dll and System.Data.Entity.dll

POCO Adapter can be also generated at runtime using EFPocoAdapterGenerator class:

// set up generator
var generator = new EFPocoAdapterGenerator<MyHandWrittenNorthwindEntities>();
generator.EntityConnectionString = "name=NorthwindEntities";
generator.EdmToClrNamespaceMapping["NorthwindEFModel"] = "NorthwindEF";
generator.GenerateProxies = true;
generator.ObjectAssemblies.Add(typeof(Customer).Assembly);
generator.ObjectAssemblies.Add(typeof(Territory).Assembly);

// generate adapter and create context factory
var factory = generator.CreateContextFactory();

// use generated context
using (var context = factory.CreateContext())
{
    Assert.IsInstanceOfType(context, typeof(MyHandWrittenNorthwindEntities));
    var cust = context.Customers.First(c => c.CustomerID == "ALFKI");
}

See EFPocoAdapterGeneratorTests class for a full working example.

Working with POCO classes

Usage patterns of the generated POCO adapter layer are very similar to regular EF, but because objects are now persistence-ignorant, some APIs have been changed. This section describes important differences:

Managing POCO Container

POCO Container should be managed in the same way as a regular ObjectContext. Same rules and recommendations apply. It is usually best to have short-living contexts and ensure proper disposal of all resources, preferably with the use of the using statement:

using (NorthwindEntities context = new NorthwindEntities())
{
    // use context
}

Queries

EFPocoContext exposes CreateQuery<T> which internally wraps ObjectQuery<T>. POCO context also exposes entity sets that can be used to query objects using LINQ to Entities, ESQL builder methods and Entity SQL. Of course, you can still use EntityClient and EntityCommand to do queries in the value layer.

In short: queries look exactly the same as in EF, just entity types and query object types are different.

using (NorthwindEntities context = new NorthwindEntities())
{
    Customer k = (from c in context.Customers 
                  where c.CustomerID == "ALFKI"
                  select c).First();
    Console.WriteLine(k.Address.City);
}

See QueryTests.cs and BuilderMethodsTests.cs for more examples of usage.

Inserting, Removing and Attaching Objects

POCO context code exposes IEntitySet<T> properties that can be used to query (because they derive from IQueryable<T>) but also insert, remove and attach objects to the context. This is a change from EF where you used to use AddObject(), AttachTo() and DeleteObject() on ObjectContext class. The idea of exposing EntitySet<T> object to aggregate all operations to entity sets was originally introduced in the EFExtensions project:

using (NorthwindEntities context = new NorthwindEntities(conn))
{
  // add new object
  context.Customers.InsertOnSaveChanges(new Customer { 
      CustomerID = "YYYY", 
      CompanyName = "Una Firma" 
  });

  // attach existing object for tracking
  var product = new Product(new Supplier()) { ProductID = 1 };
  context.Products.Attach(product);

  // locate and delete object
  var cust = context.Customers.First(c => c.CustomerID == "XXXX");
  context.Customers.DeleteOnSaveChanges(cust);

  // persist changes
  context.SaveChanges();
}

See EntitySetTests.cs and CRUDTests.cs for more examples.

Transparent Lazy Loading

Lazy loading happens automatically when you reference a virtual property on an entity object that was retrieved from a query. Because the property is marked as virtual, EFPocoClassGen generates a derived class called a proxy, which overrides the property and provides lazy-loading behavior on it.

Similar thing happens for collections – they get loaded on first access, but in this case the collection accessor doesn’t have to be declared virtual but its type must be an interface (ICollection<T>, IList<T>) instead of a concrete class (such as List<T>). In this case POCO adapter initializes the collection with a tracked collection that provides transparent lazy loading on enumeration and other types of access.

You can disable this lazy loading behavior at runtime, by setting EFPocoContext.EnableLazyLoading property to false or at compile time, by passing /enableLazyLoading:false to EFPocoClassGen. Note that if compile your adapter classes without lazy loading support, you won’t be able to enable lazy loading at runtime.

If the lazy loading has been disabled, you can still load a reference or collection you want to navigate to by using new methods on EFPocoContext:

·         IsPropertyLoaded(o,p) – determines whether object or collection represented by property p on object o has been loaded. Property can be specified as a string or as a strongly typed lambda.

·         LoadProperty(o,p) – loads the related end of the navigation property p on object o. Property can be specified as a string or as a strongly typed lambda.

See LazyLoadingTests.cs for example usage.

Object-to-Conceptual Mapping

You can specify the namespace mapping (from conceptual space to CLR space) when generating POCO adapter using /map parameter to EFPocoClassGen. If you need to fine-tune this (for example to materialize a class from another assembly or namespace), you can attach an attribute called ClrType to <EntityType> or <ComplexType> in your CSDL schema.

<EntityType Name="CurrentEmployee" BaseType ="NorthwindEFModel.Employee" 
objectmapping:ClrType="Some.Clr.Namespace.CurrentEmployee" />

The sample comes with NorthwindEF schema split into two assemblies: NorthwindEF and NorthwindEF.Territories.dll and classes live in three namespaces to demonstrate this concept. See NorthwindEFModel.csdl for details.

Read-Only Value Objects

You can map your entities to classes that are read-only and all they do is a public constructor that initializes all properties. You can also have some properties read-only and others settable. All you have to do to get this to work is to use a constructor parameter naming convention. If the property is read-only, the adapter will pass its initial value as the corresponding constructor argument:

public struct CommonAddress
{
    private string _address;
    private string _city;
    private string _region;
    private string _postalCode;
    private string _country;

    public CommonAddress(string address, string city, string region, 
                         string postalCode, string country)
    {
        _address = address;
        _city = city;
        _region = region;
        _postalCode = postalCode;
        _country = country;
    }
 
    public String Address
    {
        get { return _address; }
    }

    public String City
    {
        get { return _city; }
    }
 
    public String Region
    {
        get { return _region; }
    }
 
    public String PostalCode
    {
        get { return _postalCode; }
    }

    public String Country
    {
        get { return _country; }
    }
}

See CommonAddress.cs, Product.cs and Category.cs for more examples of usage.

Change Tracking

Change tracking for scalar properties and complex types is implemented in one of two modes: snapshot-based or proxy-based. Snapshot-based change tracking means that when EF materializes an object, the adapter stores a copy of its field values for further comparison. This way it knows which fields have changes and can notify object state manager accordingly.

Proxy-based change tracking is used when you declare your POCO object property to be virtual. EFPocoClassGen generates a proxy class that overrides all virtual properties in your POCO classes and injects notifications that update object state manager just-in-time.

Change tracking for collections can also use snapshots or proxies – in the first case EntityCollection<T> managed by ObjectStateManager is used as original values, if proxies are used (only possible when collection formal type is ICollection<T> or IList<T>) the Object State Manager gets updated just-in-time.

Change detection happens just before changes are saved to the database. You can force it by calling DetectChanges() on ObjectStateManager. Forcing change detection can be useful to do relationship fixup before changes are saved. It also raises change tracking events.

There are 2 change tracking events on EFPocoContext:

·         ChangeDetected that gets raised whenever a change is detected on a scalar, complex type property or a relationship where related end is of single cardinality

·         CollectionChangeDetected that gets raised whenever a change in collection contents is detected

You can hook up those events for debugging purposes, but there may be other possible uses, such as auditing or tracing.

Compiled Queries

Compiled queries work in the same way as in EF, except that you use EFPocoCompiledQuery.Compile() instead of CompiledQuery.Compile()

var getCustomerByID = EFPocoCompiledQuery.Compile(
   (NorthwindEntities context, string customerID) =>
       context.Customers.Where(c => c.CustomerID == customerID).First());

using (NorthwindEntities context = new NorthwindEntities())
{
    var alfki = getCustomerByID(context, "ALFKI");
}

See CompiledQueryTests.cs for more information.

Working With Proxies

Proxies are injected automatically when EFPocoAdapter materializes new POCO objects. This applies to all objects being results of queries and does NOT apply to objects passed by users.

Users can force a proxy to be created by calling EFPocoContext.CreateObject<T> instead of new T().

using (NorthwindEntities context = new NorthwindEntities())
{
    var cust = context.CreateObject<Customer>();
    Assert.IsTrue(context.IsProxy(cust));
}

Users can also enable and disable proxy use (which effectively disables transparend lazy loading) by setting EFPocoContext.EnableChangeTrackingUsingProxies property to false. Note that this applies to newly materialized objects, not to objects that have already been created and managed by the context. You can convert your existing POCO objects to Proxies with and back with ConvertPocoToProxies() and ConvertProxiesToPoco() methods accordingly.

Shadow State

Sometimes we want to hide certain properties from POCO objects and let the framework managed them. For example, we typically don’t need to see GUIDs, timestamp or version values used for optimistic concurrency checks as long as the framework can manage them automatically. The state for those properties can still be maintained by the adapter, but the property doesn’t have to be present on the POCO object.

In the similar manner we may want to hide primary keys if they don’t have business value, provided that there you have defined your column as IDENTITY.

For example, the following POCO class:

public class Territory
{
    public virtual string TerritoryDescription { get; set; }
}

Is declared in CSDL as:

<EntityType Name="Territory" objectmapping:ClrType="NorthwindEF.Territories.Territory">
  <Key>
      <PropertyRef Name="TerritoryID" />
  </Key>

  <Property Name="TerritoryID" Type="Int32" Nullable="false"
            objectmapping:ChangeTracking="Hidden" />
  <Property Name="TerritoryDescription" Type="String" Nullable="false" />
</EntityType>

 

Note the use of objectmapping:ChangeTracking="Hidden" to notify the code generator that the property is not present on the POCO class. This is optional if you use /ref: as in this case the code generator can detect that the property is missing and automatically assume “shadow” property.

Other Properties of EFPocoContext

EFPocoContext exposes several helper properties, similar to ObjectContext:

·         MetadataWorkspace – gives you access to metadata workspace for the model

·         QueryProvider – exposes IQueryProvider used to translate LINQ queries to the form that Linq To Entities understands

·         Connection – gives you access to underlying EntityConnection

Limitations

The first version of the sample has certain limitations. Not all APIs of EF are wrapped and exposed in a POCO-friendly manner. You can still access the following APIs through adapter objects:

·         ObjectStateManager/ObjectStateEntry

·         Query modes other than AppendOnly

·         ObjectResult and other databinding-specific classes

It should be relatively straightforward to add the wrappers as needed using techniques described in the article.