Delen via


Initial POCO Design 1-Pager

Here is a raw cut and paste for our POCO 1-Pager. We are currently working through the design and have some prototype work going on and we would like to hear your feedback. Note this is the "feature design 1-Pager" not the "implementation design 1-Pager" we are still looking at the details for the design and at this point would love to hear feedback on the experience plus thought around state mgmt in general. We will post additional design notes for this feature after the upcoming team design meeting.

1. Overview

Persistence Ignorance refers to being able to allow the developer to write and test domain objects in a way that is entirely independent of fundamental requirements and assumptions that may be made by the infrastructure service (in this case, the Entity Framework). Such requirements / assumptions may often include:

  • The need to implement a specific interface (for e.g., IPOCO)
  • Inheritance from a base class
  • Providing specific constructors
  • Object Instantiation/Construction requirements – use a specific factory for instance
  • The need for metadata or mapping class or property Attributes
  • The need to use specific relationship mechanisms

This amounts to being able to use Plain Old CLR Objects (POCO) so that a developer can author their domain objects free of all assumptions and requirements imposed by the framework. Using this approach, once the domain objects are ready to their satisfaction, the developer can use these classes with the Entity Framework in order for relational database access and persistence.

2. Context

While the customization of entity types has always been a goal with Entity Framework, v1 imposed several restrictions/requirements in terms of how the entity types had to be built – for instance, at the minimum, a few interfaces had to be implemented at a minimum:

  • IEntityWithRelationships
  • IEntityWithChangeTracking

Pure POCO should eliminate the need to implement these interfaces.

In addition, POCO shouldn’t require the use of attributes that are required to map object members to the C-space.

3. Design

When the Entity Framework supports Persistence Ignorance in the form of pure POCO, entity classes that are free of any infrastructure related artifacts can be authored as shown. The following example shows a Customer entity and an Order entity. There is a relationship between Customer and Order – i.e. a Customer can have many Orders.

O-Space to C-Space mapping is done by convention – i.e. the CLR types of the entities below map to the corresponding entities already defined in the Conceptual Model.

     public class Customer
    {
        string _CustomerID;
        string _ContactName;
        string _City;
        List<Order> _Orders;

        public string CustomerID
        {
            get { return _CustomerID; }
            set { _CustomerID = value; }
        }

        public string ContactName
        {
            get { return _ContactName; }
            set { _ContactName = value; }
        }

        public string City
        {
            get { return _City; }
            set { _City = value; }
        }

        public List<Order> Orders
        {
            get { return _Orders; }
            set { _Orders = value; }
        }
    }
     public class Order
    {
        int _OrderID;
        Customer _Customer;
        DateTime _OrderDate;

        public int OrderID
        {
            get { return _OrderID; }
            set { _OrderID = value; }
        }

        public Customer CustomerID
        {
            get { return _CustomerID; }
            set { _CustomerID = value; }
        }

        public DateTime OrderDate
        {
            get { return _OrderDate; }
            set { _OrderDate = value; }
        }
    }

Once the POCO classes are in place, one can program against them as first class entity objects.

For instance, query can be used to materialize POCO objects as shown:

         var query = from c in db.Customers.Include("Orders")
                    where c.City == "London"
                    select c;

ObjectContext can be used for CUD operations much like the usage patterns supported today:

         Customer customer = new Customer();
        customer.City = "London";
        customer.ContactName = "Steve";
        customer.CompanyName = "Acme";

        Order order = new Order();
        order.OrderDate = DateTime.Today;

        customer.Orders.Add(order);

        db.AddObject("Customers", customer);
        db.SaveChanges();

Tim Mallalieu
Program Manager,
Entity Framework Team

This post is part of the transparent design exercise in the Entity Framework Team. To understand how it works and how your feedback will be used please look at this post .

Comments

  • Anonymous
    June 24, 2008
    We just pushed the first piece of content to the EF Design Blog . This one is a &quot;Feature Design&quot;

  • Anonymous
    June 24, 2008
    Hi, Tim, "Note this is the "feature1-Pager" not the "design 1-Pager" but it's title is "Initial POCO Design 1-Pager"

  • Anonymous
    June 24, 2008
    POCO is easy to understand with base CLR objects, like string FirstName, int Age and DateTime DateOfBirth... but what's your thought behind complex types (more specifically, List<Order> Orders). Do you plan using the constructor of the generic list to load the enumeration of "Order" objects? The same question goes for the ObjectContext with tracking "dirty" objects... it's easy with value types, but again, what are your thoughts / plans for custom types? Thanks, -Timothy Khouri

  • Anonymous
    June 24, 2008
    I will bring up a few points where I see difficulty...

  1. Lazy Loading needing a Repository reference (PI) but I reckon you already know this
  2. As Nullable brought up Value Objects in the DDD sense especially considering they are generally assumed to be immutable
  3. The use of strings ... What happens when an object gets renamed? I would much prefer to see strongly typed prefetch paths
  • Anonymous
    June 24, 2008
    All the issues Greg has raised are important and I wanted to add one. As far as possible we should avoid having to add associations in the domain simply to allow EF to behave correctly. So if I wanted Order to know about Customer but not the reverse then this should be supported.

  • Anonymous
    June 25, 2008
    >>> On Associations Yep - agreed, you should not need to add associations. We are working on this and will share our thoughts on the blog >>> On Greg's points Yep - we are chatting with Greg in email. We expect to be able to support members that are not just scalars so Value Objects (our Complex Types) are expected. It is doubtful that in V2 we will be able to fit support for collections of complex types though. So a reference to a value object would exist a collection of value objects likely wont. Tim M

  • Anonymous
    June 25, 2008
    @timmall My view is whether you support, in V2, something like a collection of value objects is not important. Longer term supporting all the complex mappings that NHibernate and the other mature ORMSs have will be important, but for now I think it should be all about creating a product that encourages good design/testing.

  • Anonymous
    June 25, 2008
    Maybe others have touched on this... You should think about changing the name of the CustomerID property on Order, it's throwing me off, as it's of type Customer. Typically, I wouldn't expose Customer.Orders as List<T>.  I would Encapsulate Collection, and only expose operations I support, like Customer.AddOrder.  I shouldn't have to have public getter/setter pairs for it to work. At the least, I'll only expose a getter.

  • Anonymous
    June 25, 2008
    In regards to materializatoin of relationships... POCO collections are a particularly interesting issue. Ideally we'd like to support many types of collections such as IList<T>, ICollection<T>, List<T>, etc. In these cases, we wouldn't know what the actual concrete collection type is so we'd need to materialize a List<T> and populate that. We could also materialize a type of wrapped collection (like an EntityCollection<T>) that would provide better hooks into relationship change tracking. Another option is to be able to map methods that help access collections. It would be the entity's responsiblity to create the underlying collection, and the materializer would call the mapped methods (such as "AddToOrders()") rather than requiring the entity to expose the collection as a read-write property. Jeff Derstadt Developer Entity Framework Team

  • Anonymous
    June 25, 2008
    >> with tracking "dirty" objects... it's easy with value types, but again, what are your thoughts / plans for custom types? We are working on different strategies to help tracking changes in custom types, complex types, and relationships. You are right that tracking scalar (value) propeties is easier because you can just call Equals, but it gets harder, especially with relationships. One idea we had was to keep a snapshot of relationships in the context prior to a bunch of changes, and then when SaveChanges is called on the ObjectContext we'd do a graph diff to try to sort out what relationships have been added or removed. This has the downside of requiring a larger resource footprint to store the entire original state of relationships, but it would completely remove any change tracking requirement from the entities. An alternative is to call a specific API to tell the context when things have changed. This could be done outside of the entity. We are also experimenting with events, but we havent' quite found a common event that works in enough cases. Jeff Derstadt Developer Entity Framework Team

  • Anonymous
    June 25, 2008
    The comment has been removed

  • Anonymous
    June 25, 2008
    "This has the downside of requiring a larger resource footprint to store the entire original state of relationships" My opinion is that RAM is far more cheaper than our time. And I dont think there will be any ObjectContext ever which is holding thousands and thousands of objects at a time. ;)

  • Anonymous
    June 25, 2008
    Why do you need to specify the C-space name "Customers" in this code: db.AddObject("Customers", customer); Is it possible to map one O-entity to many C-entities in the scope of a single context? What for? Usually it is the other way around, in which case the C-type is overhead.

  • Anonymous
    June 25, 2008
    > POCO collections are a particularly interesting issue. > Ideally we'd like to support many types of collections > such as IList<T>, ICollection<T>, List<T> This would be useful, especially supporting List<T>. Even better if we could use our own collections (for example to add methods/interfaces that we find useful for a particular project). > rather than requiring the entity to expose the > collection as a read-write property. I take it you wouldn't really need to expose the collection, you could write to the field I'd assume? > dirty checking Have you discussed this with people on the NHibernate (and other ORM) team because they already have  good solutions to these issues.

  • Anonymous
    June 26, 2008
    Timothy, Returning proxy entities is not out of the question, but it isn't something we have completely explored. There are defintely advantages, as we could provide several services that would be hidden from the basic entity. How has the experience been with returning proxies for entities and serialization? Jeff

  • Anonymous
    June 26, 2008
    Colinjack, > woudn't really need to expose the collection, you could just write to the field We could write to the field for scalar properties or for these collections if we knew a concrete type (eg the field wasn't IList<T>). If we couldn't directly see which concrete type to use, we would have to use a default (such as List<T>). In the case of relationships, we could materialize directly to the "AddXYZ()" method(s) that are exposed on your entity. For example, if we are materializing an Order that is part of the "Customers.Orders " conceptual collection, and we are mapped to the Customers.AddOrder() method, we could just call this to add the Order to the Customer colleciton rather than creating a collection for Orders and setting it as well. Jeff

  • Anonymous
    June 26, 2008
    Serega, The reason that you need to specify the "Customers" string in AddObject is because EF supports multiple EntitySets per type. This means that the Customer CLR type (and conceptual type), can be mapped to multiple backend sets such as "ActiveCustomers" and "ArchivedCustomers". These sets might represent some horizontal partitioning you have done in your store. You can read more about it here: http://msdn.microsoft.com/en-us/library/bb738537.aspx Jeff

  • Anonymous
    June 27, 2008
    To the association annotation on the class topic: I think that is very hard to avoid it. If there is a single relation between to classes, then at list we need a way to annotate its cardinality. But what I find more problematic is in the case of the scenario of people and meetings. The meeting relates to different people for different reasons (participants vs. coordinator) and different meetings relate to the same people in different manners (participant in one meeting but coordinator of another). Since the idea is to push the abstraction of the model, rather than having a class representation of the database (as a side note, once I was asked “Why use classes and an ORM if you already have typed recordsets?”), I need a way to say in the model: “These two entities relate, and they can be related for different purposes”. I know that we can create another class that represents the meeting participation between a person and a meeting, and put there the role… then again; we would be bringing the database to the class domain. I would expect that the framework would be able to annotate and resolve into a scheme representation on its own… Persistent Ignorance Now, the change tracking… It would be much simpler if the C# team would enhance the language with some concepts from AOP…

  • Anonymous
    June 30, 2008
    .Net -i arendajate kommuuni poolt on postitatud väga asjalik "Vote of no confidence" artikkel ADO.NET

  • Anonymous
    July 08, 2008
    The ADO.NET Entity Framework is very strategic to Microsoft - but a) it is a V1.0 technology (although

  • Anonymous
    July 18, 2008
    We just wrapped up our first iteration of V2. We are shooting to get another iteration in before PDC

  • Anonymous
    July 31, 2008
    Jeff, You stated: >> We are working on different strategies to help tracking changes in custom types, complex types, and relationships. You are right that tracking scalar (value) propeties is easier because you can just call Equals, but it gets harder, especially with relationships. One idea we had was to keep a snapshot of relationships in the context prior to a bunch of changes, and then when SaveChanges is called on the ObjectContext we'd do a graph diff to try to sort out what relationships have been added or removed. This has the downside of requiring a larger resource footprint to store the entire original state of relationships, but it would completely remove any change tracking requirement from the entities. An alternative is to call a specific API to tell the context when things have changed. This could be done outside of the entity. We are also experimenting with events, but we havent' quite found a common event that works in enough cases. << Wouldn't changes be easier to track if System.ValueType supported an OnChange event?  The event would also facilitate rippling changes across a computed value graph allowing flexibility in the conceptual model. Implementing the event in the base Framework shouldn't be too intrusive to merit consideration.

  • Anonymous
    August 09, 2008
    Entity Classes &amp; Architecture Patterns Part of the Entity Framework FAQ . 2. Architecture and Patterns

  • Anonymous
    August 09, 2008
    Entity Classes &amp; Architecture Patterns Part of the Entity Framework FAQ . 2. Architecture and Patterns

  • Anonymous
    January 07, 2009
    Artykuł opisuje propozycję implementacji zasady Inversion of Control (IoC) w systemach OLTP zbudowanych

  • Anonymous
    August 28, 2009
    Just one question. "ObjectContext can be used for CUD operations much like the usage patterns supported today:" Why CUD and why not CRUD ?