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.
No comments:
Post a Comment