Поделиться через


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

Comments

  • Anonymous
    March 10, 2010
    I see no real need for differentiating between template experience and Generate Once. When you only generate once using the Template experience, you effectively have a Generate Once solution with the added benefit of always being able to correct errors if in practice it turns out to be a 'Generate Twice' situation. The fact that you yourself indicate Generate Once would still use templates behind the scenes, indicates it would be (a potentially confusing) overkill to introduce this as an extra paradigm. So for me a template based approach would be all that's needed.

  • Anonymous
    March 10, 2010
    The key differentiator for me between "generate once" and regular T4 templates is that with generate once you can manipulate the source code that is generated. If you modify individual files then you don't have to worry about the T4 template overriding your changes. But I hear you on the overkill of options. It'd be nice if generate once was some additive thing on top of T4 templates. What about an experience where you right click on a T4 template in your VS project and choose "Freeze". This would take out the .tt file and just add the generated code to your project. Jeff

  • Anonymous
    March 10, 2010
    And the performance impact of the different styles is...???

  • Anonymous
    March 13, 2010
    @Bart: When you have the template included in your project, every time you edit and save your EDM model the code of entities and context is re-generated. On the other side, if you do use "generate-once", there is no automatic re-generation: if you want to generate, you have to choose the option in the menu agein. This could make a difference in the perceived design-time performance, especially when you are working with a very large model. That said, it is a tricky comparision because the template included in the project is actually doing more work for you: it keeps generated classes up to date when you change the EDM model, something that would take a more time consuming manual process if you are using generate-once, i.e. if you have done manual edits to the entities that you don't want to loose.

  • Anonymous
    March 15, 2010
    The comment has been removed

  • Anonymous
    March 17, 2010
    As a WPF developer, the current POCO template is basically worthless to me without the option to implement INotifyPropertyChanged. Implementing the WCF attributes would also be a nice bonus.

  • Anonymous
    April 01, 2010
    I think the key issues with the current generation options are really related to the t4 templates and the OOTB generation options. The t4 templates are great. I think this feature has the potential of supporting both the generate once option and the evolving model.  The real problem in my mind is the lack of full Visual Studio support for t4 templates.  I understand there are some external tools which can be used to provide highlighting, and other help.  However, if the core of the POCO functionality is something that is not easy to extend it really makes it difficult.  For example, if editing the t4 templates was easier a developer who might otherwise choose the "generate once" option might now choose to modify the template instead. I also think that all of the various scenarios which were provided for the "default" generation of POCO options, are valid and useful.  if there was an option to enable/disable these or better examples of templates which would enable this I think it woudl be ideal (I know I just asked for eveything :P). Lastly, the idea of providing a OOTB Fake ObjectContext is great.  I think it would help promote better testing. If you made me choose, the comments above are in the order their importantance to me.

  • Anonymous
    April 12, 2010
    Now that EF4 - which, if any of these (in addition to the template experience) have been implemented? What are the plans to introduce them? Thanks, Jason

  • Anonymous
    April 12, 2010
    Hello Jason, The options described in the post are being considered for future versions. It would be great if you could give us feedbacks on which options are more important to you and why. That would help us prioritize the features and eventually build a plan. Thanks, Diego

  • Anonymous
    April 19, 2010
    Hello, I would love to see the "Basic POCO without Fixup" scenario implemented. I am currently developing an application with WCF + EF + POCO T4 template. Because I dont want to return for every object the whole object graph for every request I want to offer the client application to choose a retrieve option for the selected object - whether the whole object graph should be returned or only the child objects or maybe only the ids of the child objects. Unfortunately this isnt possible at the moment - having both a reference to the child object and the id of the child object in a poco entity.

  • Anonymous
    April 30, 2010
    The comment has been removed

  • Anonymous
    May 07, 2010
    Being in a similar scenario as Krassi I'd like to use the "Basic POCO without Fixup" option, too

  • Anonymous
    May 28, 2010
    i'd like to use "Change Tracking Proxies without Fixup"

  • Anonymous
    June 01, 2010
    Another vote here for the option (i.e. the ability to set property on the T4 template) to generate:

  1. "Data annotations based on model facets".
  2. WCF Serilization attributes. A further suggestion is to offer support for repeated generation of a base class, and one time generation of a class that inherits from that, where custom validation, business logic could be placed that would not be lost during regeneration against a changed model. This has worked well in frameworks such as .NetTiers, but it would be interesting to see if this broke the proxy generation in EF.
  • Anonymous
    June 08, 2010
    It would be nice if the model in the EDMX allowed you to enter attributes against the properties of an Entity. When the POCO code is generated, the attributes would then be added to the properties in the class. This would be useful for adding Enterprise Library Validation attributes to the POCO classes, and i'm sure could be useful in other ways, such as serialisation and so on. At the moment you'd either need to add the attributes manually, which results in them being lost after regeneration. Yes, you could create separate partial classes, but if you have to do all this, you've lost the benefit of code generation, as the partial classes would require nearly as much work as the POCO classes to write.

  • Anonymous
    June 17, 2010
    Heres my vote for an option to add data annotations to the poco t4... would simplify use with MVC 2

  • Anonymous
    August 12, 2010
    I have issues with the current template because of the integrated fixup logic. Probably the problem only occurs when lazy loading is enabled. I am creating a new entity e.g. "Order". After creating it I assign an object of type "OrderType" to it. The assignment of one OrderType entity to Order triggers the fixup logic to call OrderType.Order.Contains(this). In my case there are 2 million orders saved in database so the assignment takes several minutes.