Linq To Sql POCO Support

A colleague and I were recently discussing POCO support in Linq To Sql (LTS), and he pointed me to a great blog post by Ian Cooper on Domain v.s. Data Centric design methodologies and how Linq To Sql can be used for domain centric development.  As Ian points out, LTS places very little requirements on domain classes, allowing them to remain "persistence ignorant".  For example, we don't require any particular base class or interface, no particular constructors are required (except the default), no special types are required for association members, etc.  I don't believe the last point is widely known which is why I decided to make this post.  The EntitySet<T>/EntityRef<T> classes that we codegen by default for associations are not required.  In POCO scenarios you can map your association members to either List<T> or T[] like so (see the attached project for complete sources):

     public class Customer
    {
        private List<Order> _Orders;
         public List<Order> Orders {
            get {
                return this._Orders;
            }
            set {
                this._Orders = value;
            }
        }
    }

Assuming this member is mapped as an association in your external mapping file, LTS will treat it as such.  Likewise, instead of EntityRef<T> for singleton associations, you will just type your member as T.  Of course now that you're not using our association types, one of the things you lose is support for deferred loading.  Normally during materialization Linq To Sql assigns deferred sources to your EntitySets/EntityRefs which upon enumeration result in the deferred source query being compiled and executed.  In the case of POCO associations you'll have to explicitly specify the span to eager load - by default no associations will be loaded:

      Northwind nw = new Northwind("Northwind.sdf");

     // Load Customer, specifying span
     DataLoadOptions ds = new DataLoadOptions();
     ds.LoadWith<Customer>(p => p.Orders);
     ds.LoadWith<Order>(p => p.OrderDetails);
     ds.AssociateWith<Customer>(p => p.Orders.Where(o => o.Freight > 20));
     nw.LoadOptions = ds;
     Customer cust = nw.Customers.Where(p => p.City == "London").First();

With the DataLoadOptions set for preloading, the materializer compiles the appropriate member query, executing it with the correct FK arguments for each row materialized.  In this example, the returned customer has all of it's Orders with a Freight > 20 preloaded, and all OrderDetails for each Order are also preloaded.

There are some other Linq To Sql features that are useful from a domain driven design perspective.  For example, DataContext exposes CreateDatabase functionality allowing you to generate a database from your domain model (expressed as a DataContext of POCO entities), as opposed to the normal method of using SqlMetal or the LTS Designer to generate full featured entities from your database (Data Centric).  When coupled with our support for SqlCE, this is a very convenient feature:

      Northwind nw = new Northwind("NewNorthwind.sdf");
     if (!nw.DatabaseExists()) {
          nw.CreateDatabase();
     }
     Customer cust = new Customer { CustomerID = "MATHC", CompanyName = "Microsoft" };
     nw.Customers.Add(cust);
     nw.SubmitChanges();

One other final note on POCO -  as you'll notice, entities code genned by SqlMetal implement INotifyPropertyChanging.  As stated above this is not required, but it does allow our change tracker to be more efficient in it's copying of original values.  For classes implementing this interface the change tracker subscribes to the PropertyChanging event, allowing us to put off copying original values until the object has actually changed.  Otherwise we have no choice but to copy the values immediately on materialization/attach.

In summary, Linq To Sql offers very good support for POCO classes, providing framework services to your objects in a non-invasive, loosely coupled manner.

LTSPoco.zip