Entity Framework 4.0 Improvements
First up, let me just say that I’m quite pleased that my needs with respect to declaring my fetching intentions (eager and / or lazy), no longer will require the assistance of
PostSharp to get the job done. PostSharp is a great tool but I really shouldn’t have had to use it to achieve working the way I wanted to, therefore POCO is a welcome addition even if it does force me into some things such as choosing ICollection for 1..M navigable relationships within my Domain Model (only if you want to use change tracking proxies).
I am not going to regurgitate the changes here, for a complete list of the changes and new features see the ADO.Net Team blog.
There seem to be several points along an axis of how POCO you can choose to be with Entity Framework 4.0. At this stage I have the feeling that I will fall into the camp that probably wont use the proxies for change tracking but I will reserve judgement on that and perhaps that decision will be best left as a case by case assessment for some.
The thing I like the most is the fact I can (as already stated) remove the smoke and mirrors from my past efforts to get (somewhat) transparent lazy loading and streamline my whole approach. I say ‘somewhat’ transparent due to the fact that DeferedLoading is still ultimately ‘opt in’ and hence if you wanted to be pedantic you could make a case that this is still explicit. See below.
context.ContextOptions.DeferredLoadingEnabled = true
I will follow up from this post will several more by examining each constituent part in the new version of NFetchSpec for the Entity Framework version 4.0. NFetchSpec4Ef, provides a reusable approach to implementing Repositories, Fetching Strategies and Specifications. Fetching Strategies provide a unified way of declaring your intent when instructing the Entity Framework to fetch data. Specifications enables a functional approach to supplying dynamic query predicates and stays true to the patterns original intent, that being to provide a means to testing that an entity satisfies a given set of conditions. Repositories speak for themselves.
2 commentsEntity Framework, Repositories, Specifications and Fetching Strategies Part 9.0
Some time ago I started out writing a post that started out with a title of :
“Entity Framework won’t be featuring in my production code for now!”
Clearly you can see I was getting frustrated. These were my initial thoughts in the post:
“As long as the Entity Framework forces me to sprinkle lazy load instructions amongst my business logic, I will not use it. I will continue to follow it’s progress, however as far as production code goes, I will be using NHibernate as it allows me to work in the manner I wish to without making compromises that are not in my estimation reasonable.”
I left that post to ferment a little and eventually went back to the drawing board on getting Fetching Strategies into the EF nonetheless and recently I posted about some success I believed I was having in developing an approach to unifying Lazy and Eager fetching, neither of which have a suitable (for me) out of the box solution baked in.
Let me be clear from the outset, that this approach uses Aspect Oriented techniques (courteousey of PostSharp) and does require developer intervention when it comes to managing your Entity Framework source files, so if you were looking for a POCO solution (much as I wish it were) you won’t find it here.
A well rounded loading story (both eager and lazy), is critical in making the right performance choices when it comes time to writing the business logic that you know will end up causing persistence and my favoured approach to this is a highly configurable an tunable one. Udi demonstrates this using NHibernate and Fetching Strategies.
using (ISession s = sf.OpenSession()) using (ITransaction tx = s.BeginTransaction()) { ICustomerToMakePreferred c = s.Get<ICustomerToMakePreferred>(customerId); c.MakePreferred(); tx.Commit(); }
If your not familiar with the approach then I suggest you check out Udi’s posts. The code above has expressed it’s fetching intent appropriately by specifying the ICustomerMakePrefered interface for which a FetchingStrategy will have also been implemented where the interface (role) is specified as a generic argument and from there the configuration does the rest.
Currently the Entity Framework provides the very specific and in-line options of ObjectQuery<T>.Include() and .IsLoaded and .Load(). I don’t mind per se having as a choice the option to use the IsLoaded property and Load() method however I don’t wish to use them and have them clutter my entities business logic with infrastructure details. As far as the .Include() function is concerned, that’s easily dealt with and we can wrap that up in our a fetching strategy quite nicely, passing our Repository our intent on eager loading with a Fetching Strategy is not too much work and up until now this is how the FetchingIntention has been initialised.
public class MultiLevelMixedStrategy : IFetchingStrategy { private readonly IList<FetchingIntention> _intentions = new List<FetchingIntention>(); public MultiLevelMixedStrategy() { this.Intentions.Add(new FetchingIntention(“Orders”, FetchMode.Eager)); this.Intentions.Add(new FetchingIntention(“Orders.Order_Details”, FetchMode.Eager)); this.Intentions.Add(new FetchingIntention(“Product”, FetchMode.Lazy)); } public IList<FetchingIntention> Intentions { get { return _intentions; } } }
And we use it in the following manner:
public void Multi_Level_Mixed_Hierarchy_With_Persisting_Test() { EntitiesRepository<Customer, NorthwindEntities> repos = new EntitiesRepository<Customer, NorthwindEntities> (new NorthwindEntities()); Specification<Customer> german_cust_spec = new Specification<Customer>(c => c.CustomerID == “ALFKI”); var german_custs = repos.AllToIList(german_cust_spec, new MultiLevelMixedStrategy()); foreach (var cust in german_custs) { cust.Country = “Germany”; Console.WriteLine(“The Customer Name is {0}”, cust.CompanyName); foreach (var order in cust.Orders) { order.ShipName = “Battleship Potemkin”; Console.WriteLine(“\tThe Order ID is : {0}”, order.OrderID); foreach (var orderline in order.Order_Details) { orderline.Discount = 0.15f; Console.WriteLine(“\t\tThe value ordered for Product “ + “ID {0} is {1}”, orderline.Product.ProductID, orderline.UnitPrice * orderline.Quantity); } } } repos.Save(); }
K. Scott Allen suggested an approach that would do away with the magic strings in the FetchingIntention such as “Orders.Order_Details” and have them replaced with a LINQ Expression something like:
var fi = new FetchingIntention(o => o.Order_Details),
FetchMode.Eager);
The only real problem to deal with in taking this approach was how to accommodate the full object graph of the intention. In the example above it would have proved problematic to express the “Orders.Order_Details” relationship and get the property names correct, when the “o” is an Order as far as the Entity Framework ObjectContext is concerned. I decided to approach this by refactoring the FetchingIntention class and adding the following factory method :
public static FetchingIntention CreateInstance<TRootEntity, TFetchEntity>( Expression<Func<TRootEntity, TFetchEntity>> fetch, FetchMode mode) { if (fetch.Parameters.Count > 1) throw new ArgumentException (“FetchingIntentions support only “ + “one parameter in a dynamic expression!”); int dot = fetch.Body.ToString().IndexOf(“.”) + 1; string includes = fetch.Body.ToString().Remove(0, dot); _factory_intent = new FetchingIntention(includes, mode); return _factory_intent; }
However this alone wasn’t enough to satisfy the problem of expressing the full object graph as a string, such as (”Orders.Order_Details”). To deal with this issue I decided on a fluent interface (simple extension method) to compound FetchingIntentions, it’s listed here:
public static FetchingIntention And(this FetchingIntention original, FetchingIntention addTo) { FetchingIntention compound_intention; FetchMode mode; if (original.FetchMode != addTo.FetchMode) throw new ArgumentException (“FetchingIntention objects must “ + “share the same type of fetch mode “ + “to be joined together!”); compound_intention = new FetchingIntention(original.FetchAssociate + “.” + addTo.FetchAssociate, original.FetchMode); return compound_intention; }
And a simple example of the usage of our new approach to specifying Fetching intent is:
public void Lambda_Built_Strategy_With_Hierarchies_Test() { EntitiesRepository<Customer, NorthwindEntities> repos = new EntitiesRepository<Customer, NorthwindEntities>(new NorthwindEntities()); var custs = repos.AllToIList(new RoleStrategy()); foreach (var cust in custs) { Console.WriteLine(“The Customer Name is {0}”, cust.CompanyName); foreach (var order in cust.Orders) { Console.WriteLine(“\tThe Order ID is : {0}”, order.OrderID); foreach (var orderline in order.Order_Details) { Console.WriteLine(“\t\tThe value ordered for Product ID” + “{0} is {1}”, orderline.Product.ProductID, orderline.UnitPrice * orderline.Quantity); } } } }
When executed and profiled at the Database, the following TSQL can be observed to be executed:
SELECT [Project2].[Address] AS [Address], [Project2].[City] AS [City], [Project2].[CompanyName] AS [CompanyName], [Project2].[ContactName] AS [ContactName], [Project2].[ContactTitle] AS [ContactTitle], [Project2].[Country] AS [Country], [Project2].[CustomerID] AS [CustomerID], [Project2].[CustType] AS [CustType], [Project2].[Fax] AS [Fax], [Project2].[Phone] AS [Phone], [Project2].[PostalCode] AS [PostalCode], [Project2].[Region] AS [Region], [Project2].[C1] AS [C1], [Project2].[C4] AS [C2], [Project2].[EmployeeID] AS [EmployeeID], [Project2].[Freight] AS [Freight], [Project2].[OrderDate] AS [OrderDate], [Project2].[OrderID] AS [OrderID], [Project2].[RequiredDate] AS [RequiredDate], [Project2].[ShipAddress] AS [ShipAddress], [Project2].[ShipCity] AS [ShipCity], [Project2].[ShipCountry] AS [ShipCountry], [Project2].[ShipName] AS [ShipName], [Project2].[ShippedDate] AS [ShippedDate], [Project2].[ShipPostalCode] AS [ShipPostalCode], [Project2].[ShipRegion] AS [ShipRegion], [Project2].[ShipVia] AS [ShipVia], [Project2].[CustomerID1] AS [CustomerID1], [Project2].[C3] AS [C3], [Project2].[C2] AS [C4], [Project2].[OrderID1] AS [OrderID1], [Project2].[ProductID] AS [ProductID], [Project2].[UnitPrice] AS [UnitPrice], [Project2].[Quantity] AS [Quantity], [Project2].[Discount] AS [Discount] FROM ( SELECT [Extent1].[Address] AS [Address], [Extent1].[City] AS [City], [Extent1].[CompanyName] AS [CompanyName], [Extent1].[ContactName] AS [ContactName], [Extent1].[ContactTitle] AS [ContactTitle], [Extent1].[Country] AS [Country], [Extent1].[CustomerID] AS [CustomerID], [Extent1].[CustType] AS [CustType], [Extent1].[Fax] AS [Fax], [Extent1].[Phone] AS [Phone], [Extent1].[PostalCode] AS [PostalCode], [Extent1].[Region] AS [Region], 1 AS [C1], [Project1].[CustomerID] AS [CustomerID1], [Project1].[EmployeeID] AS [EmployeeID], [Project1].[Freight] AS [Freight], [Project1].[OrderDate] AS [OrderDate], [Project1].[OrderID] AS [OrderID], [Project1].[RequiredDate] AS [RequiredDate], [Project1].[ShipAddress] AS [ShipAddress], [Project1].[ShipCity] AS [ShipCity], [Project1].[ShipCountry] AS [ShipCountry], [Project1].[ShipName] AS [ShipName], [Project1].[ShippedDate] AS [ShippedDate], [Project1].[ShipPostalCode] AS [ShipPostalCode], [Project1].[ShipRegion] AS [ShipRegion], [Project1].[ShipVia] AS [ShipVia], [Project1].[Discount] AS [Discount], [Project1].[OrderID1] AS [OrderID1], [Project1].[ProductID] AS [ProductID], [Project1].[Quantity] AS [Quantity], [Project1].[UnitPrice] AS [UnitPrice], CASE WHEN ([Project1].[C1] IS NULL) THEN CAST(NULL AS int) ELSE CASE WHEN ([Project1].[Discount] IS NULL) THEN CAST(NULL AS int) ELSE 1 END END AS [C2], CASE WHEN ([Project1].[C1] IS NULL) THEN CAST(NULL AS int) ELSE CASE WHEN ([Project1].[Discount] IS NULL) THEN CAST(NULL AS int) ELSE 1 END END AS [C3], [Project1].[C1] AS [C4] FROM [dbo].[Customers] AS [Extent1] LEFT OUTER JOIN (SELECT [Extent2].[CustomerID] AS [CustomerID], [Extent2].[EmployeeID] AS [EmployeeID], [Extent2].[Freight] AS [Freight], [Extent2].[OrderDate] AS [OrderDate], [Extent2].[OrderID] AS [OrderID], [Extent2].[RequiredDate] AS [RequiredDate], [Extent2].[ShipAddress] AS [ShipAddress], [Extent2].[ShipCity] AS [ShipCity], [Extent2].[ShipCountry] AS [ShipCountry], [Extent2].[ShipName] AS [ShipName], [Extent2].[ShippedDate] AS [ShippedDate], [Extent2].[ShipPostalCode] AS [ShipPostalCode], [Extent2].[ShipRegion] AS [ShipRegion], [Extent2].[ShipVia] AS [ShipVia], [Extent3].[Discount] AS [Discount], [Extent3].[OrderID] AS [OrderID1], [Extent3].[ProductID] AS [ProductID], [Extent3].[Quantity] AS [Quantity], [Extent3].[UnitPrice] AS [UnitPrice], 1 AS [C1] FROM [dbo].[Orders] AS [Extent2] LEFT OUTER JOIN [dbo].[Order Details] AS [Extent3] ON [Extent2].[OrderID] = [Extent3].[OrderID] ) AS [Project1] ON [Extent1].[CustomerID] = [Project1].[CustomerID] ) AS [Project2] ORDER BY [Project2].[CustomerID] ASC, [Project2].[C4] ASC, [Project2].[OrderID] ASC, [Project2].[C3] ASC
This is completely expected as the FetchingStrategy used was the following:
public class RoleStrategy : IFetchingStrategy<ICustomerMakePrefered> { private readonly IList<FetchingIntention> _intentions = new List<FetchingIntention>(); public RoleStrategy() { var order_intention = FetchingIntention.CreateInstance <Customer, EntityCollection<Order>> (c => c.Orders, FetchMode.Eager); var order_details_intention = order_intention.And( FetchingIntention.CreateInstance <Order, EntityCollection<Order_Detail>> (o => o.Order_Details, FetchMode.Eager)); var product_intention = FetchingIntention.CreateInstance <Order_Detail, Product> (od => od.Product, FetchMode.Lazy); this.Intentions.Add(order_intention); this.Intentions.Add(order_details_intention); this.Intentions.Add(product_intention); } public IList<FetchingIntention> Intentions { get { return _intentions; } } }
And also expected therefore (courtesy of the single lazy load intention) was a series of TSQL queries asking for the Product data from the Order_Details lines. Observed from SQL Profiler we can see this behaviour.
exec sp_executesql N‘SELECT 1 AS [C1], CASE WHEN ([Extent2].[Discontinued] = 0) THEN ‘‘1X’‘ ELSE ‘‘1X0X’‘ END AS [C2], [Extent2].[ProductID] AS [ProductID], [Extent2].[ProductName] AS [ProductName], [Extent2].[SupplierID] AS [SupplierID], [Extent2].[QuantityPerUnit] AS [QuantityPerUnit], [Extent2].[UnitPrice] AS [UnitPrice], [Extent2].[UnitsInStock] AS [UnitsInStock], [Extent2].[UnitsOnOrder] AS [UnitsOnOrder], [Extent2].[ReorderLevel] AS [ReorderLevel], [Extent2].[CategoryID] AS [CategoryID] FROM [dbo].[Order Details] AS [Extent1] INNER JOIN [dbo].[Products] AS [Extent2] ON [Extent1].[ProductID] = [Extent2].[ProductID] WHERE ([Extent1].[OrderID] = @EntityKeyValue1) AND ([Extent1].[ProductID] = @EntityKeyValue2)’,N‘@EntityKeyValue1 int,@EntityKeyValue2 int’, @EntityKeyValue1=10979,@EntityKeyValue2=27
Lazy Loading
The lazy fetching is made possible by introducing a post compiled aspect using PostSharp. By decorating partial classes (that are themselves Entities in a domain model) with an OnMethodBoundaryAspect attribute, we are able to intercept calls to the entity properties that will be called when we would expect lazy loading to occur. For example in Northwind we might have something like the following:
[LazyLoad("Orders", AttributeTargetMembers = @"regex:(Orders)")] public partial class Customer : IFetchable, ICustomerMakePrefered { private IFetchingStrategy _strategy; public void Makeprefered() { } public IFetchingStrategy FetchingStrategy { get { return _strategy; } set { _strategy = value; } } }
As stated, the LazyLoad attribute is a OnMethodBoundaryAspect and will weave the lazy loading code into the Getter for the Orders property in the Customer entity. Without delving into the mechanics, I should mention that the intercepting code that gets woven here will not interfere with Entity Framework code that is written without a Fetching Strategy, however this could of course be changed rather easily but it was my preference at the time of spiking this project.
What’s Left?
public void Example_Role_Specified_Strategy_Test() { EntitiesRepository<Customer, NorthwindEntities> repos = new EntitiesRepository<Customer, NorthwindEntities>(new NorthwindEntities()); var custs = repos.AllToIList<ICustomerMakePrefered>(); }
I did intend to leave the contents of this post for one final post on this subject, including the code that injects FetchingStrategy implementations into the Repository using an IoC container (see above usage - not implementation), however I decided to list the source code now and re-list it when I make subsequent updates.
It’s important to point out that this code is free for you to prod, paw over and mangle at your hearts content, however it is spiking code and not intended for production without significant further testing and refactoring. Some areas that I plan to clean up are the PostSharp attributes by placing their declarations in the AssemblyInfo file, as opposed to decorating the entities themselves. Also to clean up the code in the attribute itself, I did plan on borrowing Pablo Blamirez code when it became apparent to me recently however working on it in got placed further down in the priority queue for now.
4 commentsWill ORM mass adoption kill SQL Skills
Will OR/M products succeed in delivering a Generation of developers with no or little understanding of SQL and does it matter? Recently I decided to take a close look at another OR/M (Active Record) and it got me thinking……..
After some initial suspicion, I eventually became a HUGE fan of using OR/M technology. I also appreciate my many years of having to write intricate T/SQL, however I don’t like having to write masses of data access code any more, it’s tedious and I have come to the point where I don’t ever want to have to revisit an anaemic domain model either. I want to continue using POCO’s and couldn’t be happier that REAL object oriented thinking is slowly coming back into vogue with DotNet developers (as a community) and shows up in how we model our domain objects. There is more to say on this, but ultimately it moves me farther from my question, actually its more a nightmare scenario.
The Scenario
Lets assume that in five years from now, most data access code is being managed
by OR/M’s whether it be LINQ To SQL, NHibernate, Entity Framework, IBatis.Net, etc. A fraction of the SQL / TSQL & stored procedures we used to write is now deemed unnecessary, in fact it’s become almost entirely negligible. Universities are now (it’s 2013 remember) spending a bare minimum amount of time teaching database technology in preference to teaching OR/M technologies and almost no developers are seeking to sit 3rd party database development exams or take up an Oracle or Microsoft database certifications. Finding a burgeoning DBA in the making is like the search for new fossil fuel. Microsoft Developers are now whipping out Entity Framework 4.0 to quickly dial up an ad-hoc query cause they are not sure about how to write the SQL.
I know I have presented an extreme view and implicitly retired a lot of us SQL dinosaurs off, but is this scenario possible to some degree? What do you think? Will demand for the DBA increase as developers become less skilled with SQL.
2 comments








