POCO Template Code Generation Options

We recently released an updated version of the POCO template that works with Visual Studio 2010 RC on the Visual Studio Gallery. In this post we will delve into the various possible code generation options for future releases of the POCO template. We hope that you will read the various options presented here and give us feedback about the most compelling features for you.

1. Template Versus Generate Once experience

Template Experience (current design)

This experience is the same as what we currently have, where Text transform (.tt) files are added to the project either from the EDM Designer surface or the Solution Explorer. The generated code files are nested under the .tt files and are added to the project. This is useful in the following scenarios –

· Your model is evolving, so you expect to regenerate your entities often

· You have patterns of changes you want to make to your entities. This includes additional code you want included in each of your entities or modifying parts of the auto-generated code.

An example of a patterned change would be to introduce a data value constraint check in the property setters of certain data types. More information about customizing T4 templates can be found in this blog post.

Generate once experience

This user experience would mean that you could generate entity classes from the ADO.NET Entity Data Model (.edmx file) by right clicking on it from the Solution Explorer and selecting “Generate Entities”. Instead of template files being added to your project, the resulting code files for the entities would be added to your project. In future if the model changes, the entity classes will not be regenerated. These are the scenarios where this experience would be useful –

· You are working with a legacy database and don’t plan to evolve your model frequently. In this case it is only required to generate the entities once.

· After generating once, you expect to customize your entities with business logic or code that is specific to each entity type, and not a pattern of changes.

In these scenarios generate once is a seamless, VS integrated way of creating entity classes. The code generation would still use T4 templates behind the scenes. We can potentially support additional features in this scenario like customizing the generated code by choosing which features you want (details follow in the next sections). We would take your input through a VS dialog and feed it to the templates to generate the code your application requires.

These are the features that can be added to the basic POCO entities -

  1. Proxies – For change tracking and lazy loading
  2. Fixup logic in entities – Ensures that the object graph remains connected even when EF is not present
  3. WCF Serialization support – Adds DataContractAttribute, DataMemberAttribute and KnownTypeAttributes to the entities and its members
  4. Data binding support – Entities implement INotifyPropertyChanged interface
  5. Data annotations based on model facets – Adds data validation by specifying attributes based on model facets like StringLengthAttribute (MaxLength facet), RequiredAttribute (Nullable facet), KeyAttribute(Key element), etc.

Proxies

Proxies add functionality to the basic POCO entities while maintaining the simplicity of POCO classes. They are dynamically generated entity types which derive from the basic POCO entity types and notify the object state manager about changes to entities. Thus they obviate the need to maintain snapshots of the original property values and the snapshot comparisons on SaveChanges. They also enable lazy loading which enables automatic loading of the related entity/entities when a navigation property is dereferenced. More details on POCO proxies can be found here.

Fixup

A fixup method is written for every navigation property on an entity and is called from the setter of the navigation property whenever its value changes. Its purpose is to ensure that each end of a bidirectional relationship stays in sync with the other. For example, in a one-to-many relationship between Cutomer and Order, whenever Order.Customer is set, the fixup method ensures that the Order is in the Customer’s Orders collection. It also keeps the corresponding foreign key property viz. Order.CustomerID in sync with the new Customer’s primary key (ID) value. This logic can be useful if the POCO entities are used independently of the EF stack, like for writing tests against them which don’t hit the database. Fixup ensures that the object graph is connected in the same way as you would expect while using them with EF. Fixup methods are a bit complex to write and hence it is useful to have them auto-generated if you are planning on using the entities in an EF independent scenario.

We will explore five interesting combinations of these features -

1. Basic POCO without Fixup - In this case the POCO classes would be very simple as both fixup logic and proxies are removed. Cons of this approach are - (a) it uses snapshot mechanism to infer the UPDATE commands sent to the database server. It cannot perform notification-based change tracking which provides performance improvement in some scenarios. (b) Writing EF-independent tests can be inconvenient since fixup logic is not included and the object graph may be disconnected (or inconsistent) until DetectChanges is called.

 public class Order
{
    public int OrderID { get; set; }
    public string CustomerID { get; set; }
    // Navigation Properties
    public Customer Customer { get; set; }
}

2. Basic POCO with Fixup – Similar to (1) proxies are removed. However fixup logic is added to the entities so that the navigation properties and foreign keys are kept in sync even when EF is not present. Like (1) snapshot mechanism is used to detect property and relationship changes and performance could be improved in some scenarios by using proxies. The main disadvantage of this option as compared to (1) is that the code is more complex due to presence of fixup logic.

 public class Order
{
    public int OrderID { get; set; }

    public string CustomerID
    {
        get { return _customerID; }
        set
        {
            try
            {
                _settingFK = true;
                if (_customerID != value)
                {
                    if (Customer != null 
                        && Customer.CustomerID != value)
                    {
                        Customer = null;
                    }
                    _customerID = value;
                }
            }
            finally
            {
                _settingFK = false;
            }
        }
    }
    private string _customerID;
    
    // Navigation Properties
    public Customer Customer
    {
        get { return _customer; }
        set
        {
            if (!ReferenceEquals(_customer, value))
            {
                var previousValue = _customer;
                _customer = value;
                FixupCustomer(previousValue);
            }
        }
    }
    private Customer _customer;

    // Association Fixup
    private bool _settingFK = false;
    private void FixupCustomer(Customer previousValue)
    {
        if (previousValue != null 
            && previousValue.Orders.Contains(this))
        {
            previousValue.Orders.Remove(this);
        }

        if (Customer != null)
        {
            if (!Customer.Orders.Contains(this))
            {
                Customer.Orders.Add(this);
            }
            if (CustomerID != Customer.CustomerID)
            {
                CustomerID = Customer.CustomerID;
            }
        }
        else if (!_settingFK)
        {
            CustomerID = null;
        }
    }
}

3. Change Tracking Proxies without Fixup – This marks all the properties of the entities as “virtual” which are then internally overridden by the EF to inform the state manager about changes to the entity. The change tracking POCO entities are capable of doing their own fixup, so that code can be removed in this version. However you would have fixup if the entities are used with EF and not have it if they are used independently of EF like for testing them without hitting the database. Thus they would exhibit different behaviors depending on the scenario. Pros are of this approach are the performance improvement on SaveChanges and the simplicity of the code. There are no known issues with this combination, except for serialization.

 public class Order
{
    public virtual int OrderID { get; set; }
    public virtual string CustomerID { get; set; }
    // Navigation Properties
    public virtual Customer Customer { get; set; }
}

4. Change Tracking Proxies with Fixup – This has the advantage of performance improvement on SaveChanges. Bidirectional navigation properties and foreign keys are kept in sync even when EF is not present. Cons of this combination are that (a) it increases the complexity of the POCO entities due to presence of the fixup logic. (b) We used this combination for the Visual Studio 2010 RC POCO templates release and noted a few issues as documented in the post. We are working on fixing those issues.

 public partial class Order
{
    public virtual int OrderID { get; set; }

    public virtual string CustomerID
    {
        get { return _customerID; }
        set
        {
            try
            {
                _settingFK = true;
                if (_customerID != value)
                {
                    if (Customer != null 
                        && Customer.CustomerID != value)
                    {
                        Customer = null;
                    }
                    _customerID = value;
                }
            }
            finally
            {
                _settingFK = false;
            }
        }
    }
    private string _customerID;
    
    // Navigation Properties
    public virtual Customer Customer
    {
        get { return _customer; }
        set
        {
            if (!ReferenceEquals(_customer, value))
            {
                var previousValue = _customer;
                _customer = value;
                FixupCustomer(previousValue);
            }
        }
    }
    private Customer _customer;

    // Association Fixup
    private bool _settingFK = false;
    private void FixupCustomer(Customer previousValue)
    {
        if (previousValue != null 
            && previousValue.Orders.Contains(this))
        {
            previousValue.Orders.Remove(this);
        }

        if (Customer != null)
        {
            if (!Customer.Orders.Contains(this))
            {
                Customer.Orders.Add(this);
            }
            if (CustomerID != Customer.CustomerID)
            {
                CustomerID = Customer.CustomerID;
            }
        }
        else if (!_settingFK)
        {
            CustomerID = null;
        }
    }
}

5. Lazy Loading Proxies with Fixup – Here you get lazy loading functionality for your entities by marking only the navigation properties as virtual. Pros are that it provides automatic loading of related entities when the navigation properties are dereferenced. Bidirectional navigation properties and foreign keys are kept in sync even when EF is not present. There are no known issues for this combination. Cons are that (a) performance could be improved in some scenarios by using notification-based mechanism for SaveChanges. (b) Also code is complex due to presence of fixup logic.

 public partial class Order
{
    public int OrderID { get; set; }

    public string CustomerID
    {
        get { return _customerID; }
        set
        {
            try
            {
                _settingFK = true;
                if (_customerID != value)
                {
                    if (Customer != null 
                        && Customer.CustomerID != value)
                    {
                        Customer = null;
                    }
                    _customerID = value;
                }
            }
            finally
            {
                _settingFK = false;
            }
        }
    }
    private string _customerID;
    
    // Navigation Properties
    public virtual Customer Customer
    {
        get { return _customer; }
        set
        {
            if (!ReferenceEquals(_customer, value))
            {
                var previousValue = _customer;
                _customer = value;
                FixupCustomer(previousValue);
            }
        }
    }
    private Customer _customer;

    // Association Fixup
    private bool _settingFK = false;
    private void FixupCustomer(Customer previousValue)
    {
        if (previousValue != null 
            && previousValue.Orders.Contains(this))
        {
            previousValue.Orders.Remove(this);
        }

        if (Customer != null)
        {
            if (!Customer.Orders.Contains(this))
            {
                Customer.Orders.Add(this);
            }
            if (CustomerID != Customer.CustomerID)
            {
                CustomerID = Customer.CustomerID;
            }
        }
        else if (!_settingFK)
        {
            CustomerID = null;
        }
    }
}

Data Binding, WCF Serialization and Data Annotations

We could potentially add data binding support, WCF serialization support and data annotations based on model facets to the generated POCO classes.

3. Fake ObjectContext

POCO entities offer independence from the EF stack so that the logic in the entities can be tested without hitting the database. This offers advantages like the tests run faster and the database doesn’t have to be reset to its initial state after each test run. However to help write a completely EF independent test, you would also need a fake repository or a fake ObjectContext. Currently the typed ObjectContext generated from the POCO template derives from ObjectContext which is an EF class and thus is dependent on the Entity Framework. More information on using a fake ObjectContext for testability can be found in Jonathan’s post here. Let us see what generating fakes would mean for an application containing a typed ObjectContext named BloggingEntities,

1. It would create the interface IBloggingEntities. The interface basically contains property getters for all the IObjectSets contained in this ObjectContext and the SaveChanges method. The interface for ObjectSet<T>, IObjectSet<T> is a public interface in EF.

2. It would generate the classes FakeBloggingEntities and FakeObjectSet<T> which implement the interfaces IBloggingEntities and IObjectSet<T>. FakeObjectSet<T> uses an in-memory data structure like HashSet<T> to store the entities. The properties on FakeBloggingEntities return FakeObjectSets instead of real ObjectSets.

3. The typed ObjectContext, BloggingEntities would derive from ObjectContext as well as IBloggingEntities and would contain the additional property members defined by IBloggingEntities.

4. It could generate a repository class, BlogRepository, which serves as a thin mapper between the entities and the store. The store would be represented by a member of type IBloggingEntities. So you could substitute the store with an in-memory data structure that implements IBloggingEntities, like FakeBloggingEntities. The repository class would serve as a starting point that you could customize as needed.

The tests would run against the repository, BlogRepository which would work on fake ObjectContext and fake ObjectSets and thus not hit the database. The full implementation of these classes can be found in the same post

Summary

In this post we have explored pros and cons of the various code generation options for POCO template like generate-once and template experience, full featured versus basic POCO classes and creation of fake ObjectContext for testability of your application. We would love to hear your feedback about which options are the most compelling for you and if there is anything else you believe should be added to the POCO template.

Thanks,
Sheetal Gupta
Developer
Entity Framework Team