Archive

Archive for the ‘LINQ’ Category

LINQ To XML and the Specification Pattern - [In a static world]

April 4th, 2009 Simon Segal No comments

I have documented quite extensively on how to use the Specification Pattern and in particular how that can work with a variety of LINQ flavours. The pattern has proven useful in matching objects based on a computation that seeks to test equality and when turned slightly on it’s head a little, it can provide dynamic querying capability to the current Microsoft group of ORM products, namely LINQ To SQL and the Entity Framework. You can check out most of the series on this topic from here. In more recent times I looked at how to use the same pattern with IronRuby which whilst very satisfying for the author, it currently lacks the ability to produce expressions that either ORM products are capable of utilising.

So, for the moment I want to go back again to the Specification pattern and explore it’s use specifically in the world of C# and it’s statically typed sibling languages. The Specification that I currently use is based primarily around our ability to push around Expression<Func<T>> and Func<T> as delegates that can be used as arguments to the extension methods in a given LINQ implementation. Therefore, LINQ to XML should be no exception. Given that the class at the centre of all this ( Specification<T> ) matches objects that satisfy a boolean condition that is asserted using the generic T argument, we are able to specify a function to pass to any extension method implemented by the LINQ To XML provider, that takes a generic as it’s input parameter and returns a boolean. A quick reminder of the Specification classes interface:

public interface ISpecification<T>
{
    Expression<Func<T, bool>> EvalPredicate { get; }
    Func<T, bool> EvalFunc { get; }
    bool Matches(T entity);
}

Here is a simple example of filtering a query over XML data by supplying a predicate in the form of a specification.

public void employees_from_uk()
{
    var all_employees_on_file =
        XDocument.Load(@"..\..\XML\Employees.xml").Descendants();

    Specification<XElement> aus_emp_spec =
        new Specification<XElement>
            (x => x.Attribute("Country").Value == "Australia");

    var aus_employees = from e in all_employees_on_file.Descendants()
                        .Where(aus_emp_spec.EvalFunc)
                        select e;

    aus_employees.ToList().ForEach(x =>
        { Console.WriteLine(x.ToString()); });
}

And now a more complex specification that uses the AND operator to create a combined set of conditions to be satisfied in a .Where extension method.

public void employees_from_uk_that_are_ceo()
{
    var all_employees_on_file =
        XDocument.Load(@"..\..\XML\Employees.xml").Descendants();

    var aus_emp_spec =
        new Specification<XElement>
            (x => x.Attribute("Country").Value == "UK");

    var ceo_emp_spec =
        new Specification<XElement>
            (x => x.Attribute("Title").Value == "CEO");

    var compound_spec = aus_emp_spec & ceo_emp_spec;

    var aus_employees = from e in all_employees_on_file.Descendants()
                        .Where(compound_spec.EvalFunc)
                        select e;

    aus_employees.ToList().ForEach(x =>
        { Console.WriteLine(x.ToString()); });
}

More than just a ‘Where’ predicate?

Your probably noticing that we are always applying our specifications to the “where” method. As I mentioned, a Specification in it’s current incarnation can only be applied to extension methods that take a function that accepts a generic argument as an input parameter and returns a boolean, therefore there are some other methods that can be specified. Here is example of using the .SkipWhile() extension method.

public void skip_while_employees_from_usa_then_return_the_rest_regardless()
{
    var all_employees_on_file =
        XDocument.Load(@"..\..\XML\Employees.xml").Descendants();

    var usa_empoyee_spec =
        new Specification<XElement>
            (x => x.Attribute("Country").Value == "USA");

    var usa_emps = from e in all_employees_on_file.Descendants()
                       .SkipWhile(usa_empoyee_spec.EvalFunc)
                       select e;

    usa_emps.ToList().ForEach(x =>
        { Console.WriteLine("Skipping from the USA : " +
            x.ToString()); });
}

The final example combines data from two XML sources, each one requiring it’s own specification, where the LINQ query will bring the disparate data together.

public void customer_joined_with_customer_representative()
{
    var cust_rep_spec =
        new Specification<XElement>(x => x.Name == "CustomerRep");
    var emp_id_spec =
        new Specification<XAttribute>(x => x.Name == "EmployeeID");

    var all_employees_on_file =
        XDocument.Load(@"..\..\XML\Employees.xml").Descendants().Attributes();

    var all_customers_on_file =
        XDocument.Load(@"..\..\XML\Customers.xml").Root.Descendants().Elements();

    var customer_with_rep =
        from c in all_customers_on_file.Where(cust_rep_spec.EvalFunc)
        from e in all_employees_on_file.Where(emp_id_spec.EvalFunc)
        where c.Value == e.Value
        select new
        {
            c = c.Parent.Element("FirstName").Value + " " +
                c.Parent.Element("LastName").Value,
            e = e.Value
        };

    customer_with_rep.ToList().ForEach(x =>
    {
        Console.WriteLine(
            string.Format("Customer : {0} is represented by Rep Number {1}",
            x.c, x.e));
    });
}
 
For ease of reading this code without the necessity of downloading the code, I have included the XML directly in the post here.
 

Employee.xml File

 
<?xml version="1.0" encoding="utf-8" ?>
<Employees>
  <Employee EmployeeID="1" FirstName="Nancy"
            LastName="Davolio" Title="Sales Representative"
            HireDate="1992-05-01T00:00:00" Country="USA"
            Extension="5467" />
  <Employee EmployeeID="2" FirstName="Andrew"
            LastName="Fuller" Title="Vice President, Sales"
            HireDate="1992-08-14T00:00:00" Country="AUSTRALIA"
            Extension="3457" />
  <Employee EmployeeID="3" FirstName="Janet"
            LastName="Leverling" Title="Sales Representative"
            HireDate="1992-04-01T00:00:00" Country="USA"
            Extension="3355" />
  <Employee EmployeeID="4" FirstName="Margaret"
            LastName="Peacock" Title="Sales Representative"
            HireDate="1993-05-03T00:00:00" Country="AUSTRALIA"
            Extension="5176" />
  <Employee EmployeeID="5" FirstName="Steven" LastName="Buchanan"
            Title="Sales Manager" HireDate="1993-10-17T00:00:00"
            Country="UK" Extension="3453" />
  <Employee EmployeeID="6" FirstName="Michael"
            LastName="Suyama" Title="Sales Representative"
            HireDate="1993-10-17T00:00:00" Country="UK"
            Extension="428" />
  <Employee EmployeeID="7" FirstName="Robert" LastName="King"
            Title="CEO" HireDate="1994-01-02T00:00:00"
            Country="UK" Extension="465" />
  <Employee EmployeeID="8" FirstName="Laura" LastName="Callahan"
            Title="Inside Sales Coordinator"
            HireDate="1994-03-05T00:00:00" Country="USA"
            Extension="2344" />
  <Employee EmployeeID="9" FirstName="Anne" LastName="Dodsworth"
            Title="Sales Representative" HireDate="1994-11-15T00:00:00"
            Country="UK" Extension="452" />
</Employees>

 

Customers.xml File

<?xml version="1.0" encoding="utf-8" ?>
<Customers>
  <Customer>
    <FirstName>Simon</FirstName>
    <LastName>Segal</LastName>
    <JoinDate>19-10-2008</JoinDate>
    <Country>Australia</Country>
    <Email>simon.segal@techavalanche.com</Email>
    <MobilePhone>0405987367</MobilePhone>
    <CustomerRep>2</CustomerRep>
  </Customer>
  <Customer>
    <FirstName>Bob</FirstName>
    <LastName>Jones</LastName>
    <JoinDate>23-10-2008</JoinDate>
    <Country>Germany</Country>
    <Email>bob.j@bundes.com</Email>
    <MobilePhone>8983478347</MobilePhone>
    <CustomerRep>1</CustomerRep>
  </Customer>
  <Customer>
    <FirstName>Clair</FirstName>
    <LastName>Humgola</LastName>
    <JoinDate>10-10-2008</JoinDate>
    <Country>Australia</Country>
    <Email>chumg@telstra.com.au</Email>
    <MobilePhone>0458193487</MobilePhone>
    <CustomerRep>4</CustomerRep>
  </Customer>
  <Customer>
    <FirstName>Milton</FirstName>
    <LastName>Nomik</LastName>
    <JoinDate>20-12-2008</JoinDate>
    <Country>Australia</Country>
    <Email>milt.n@techavalanche.com</Email>
    <MobilePhone>09072625717</MobilePhone>
    <CustomerRep>6</CustomerRep>
  </Customer>
</Customers>

On a final note: I am going to have a go shortly at putting together some variations of the Specification<T> class to allow for a more complete use with LINQ in General, remembering that the initial requirement was based around testing objects for equality and then creating a more manageable and malleable system of creating dynamic queries in LINQ To SQL and the Entity Framework. The “new” Specification class will need to support more than the just Func<T, bool> and should prove to be in interesting exercise.

Full code download for this post can be found here.

Share/Save/Bookmark

Lazy Loading and the Entity Framework. How long I can go on like this?

March 5th, 2009 Simon Segal 4 comments

In more recent times I have spent a fair slice of time looking at how to get lazy loading working in the Entity Framework (without using the codeplex solution). Oddly, the driver for this was to get Fetching Strategies implemented for the Entity Framework and not the other way around and I have devoted a quite a few posts recently to that.

If you have followed the story until now, then you will know that I developed a method of transparently lazy loading by using some AOP style trickery and weaving code into the Entities themselves. I did say that I wasn’t going to post on this but it will become apparent why I chose to liberally change my mind about that as we move through this post. Currently the way to have your entity ‘opt’ into lazy loading (with my code), is to decorate it with an attribute in the partial class representation of the Entity itself.

[LazyLoad("Customer", AttributeTargetMembers = @"regex:(Customer$)")]
[LazyLoad("Order_Details", AttributeTargetMembers = @"regex:(Order_Details$)")]
public partial class Order : IFetchable
{
    private IFetchingStrategy _strategy;
    public IFetchingStrategy FetchingStrategy
    {
        get { return _strategy; }
        set { _strategy = value; }
    }
}
Note that this attribute is already written and part of the code download, you don’t have to write any PostSharp attributes yourself to get the code to work. Once having applied the attribute and compiled, PostSharp kicks in and weaves code where it’s directed, in this case the Order entities Customer and Order_Details properties. A look at the code in Reflector confirms this:
Post Sharp Code Weave into Entity
Now cool as PostSharp is, this is not a job I was hoping to have to have ever used it for. Entity Framework shouldn’t need that much work for transparent lazy loading and we are promised that will change in a later (next?) version. The lazy loading however is only part of the story. As much as I want transparent Lazy Loading OOTB, I equally need the Eager Loading story to be dealt with. As I have said throughout this series of posts, part of the fetching strategy problem lies in the disconnect between Lazy and Eager fetching and something tells that I will still be rewriting this solution for a later version of Entity Framework.

While we are waiting…..

This solution isn’t proven in battle and needs a lot more work to verify it’s bona fides, but I will push it forward on very small, low risk projects to see how it pans out. I for one really want the Entity Framework to ‘make good’ and reach the hearts and minds of the POCO and DDD audience into the bargain. The question is how long am I prepared to wait and ‘go via the cape’?
 

Step by Step

  • Download the code
  • Copy the LazyLoadAttribute.cs file into your project with an entity model
  • Reference the PostSharp.Laos, PostSharp.Public libraries.
  • Reference the Org.TechAvalanche.Orm.Repository library
  • Decorate the Partial class representation of your Entity with the LazyLoad attribute (copied in the second step) and your done. Write your fetching strategies, Repositories and Specifications and away you go.

I will soon follow with an Item Template for VS.Net that creates the attribute file and makes the references for you automatically.

Share/Save/Bookmark

LINQ To SQL, Dynamic Querying & Fetching Strategies (cont)

July 15th, 2008 Simon Segal 4 comments

I posted recently on using the Specification Pattern and utilizing the generic Expression<> and Func<> to dynamically change the criteria of SQL queries generated by the LINQ To SQL implementation. I promised to come back with an approach on enabling Eager Loading by implementing a Fetching strategy, that can be used with my Repository. Here’s the Fetching Strategy code (most of it).

   1:  public interface IFetchingStrategy
   2:  {
   3:      DataLoadOptions LoadOptions { get; }
   4:  }
   5:   
   6:  public interface IFetchingStrategy<TRole> : IFetchingStrategy
   7:  {
   8:   
   9:  }
  10:   
  11:  public interface ICustomerLoyaltyDiscount
  12:  {
  13:   
  14:  }
  15:   
  16:  public class CustomerLoyaltyDiscountFetchingStrategy : 
  17:                   IFetchingStrategy<ICustomerLoyaltyDiscount>
  18:  {
  19:      private readonly DataLoadOptions _loadOptions;
  20:   
  21:      public DataLoadOptions LoadOptions
  22:      {
  23:          get { return _loadOptions; }
  24:      }
  25:   
  26:      public CustomerLoyaltyDiscountFetchingStrategy()
  27:      {
  28:          _loadOptions = new DataLoadOptions();
  29:          _loadOptions.LoadWith<Customer>(c => c.Orders);
  30:          _loadOptions.LoadWith<Order>(o => o.OrderLines);
  31:      }
  32:  }

Most of the code above is self explanatory except perhaps for the interface ICustomerLoyaltyDiscount on line 11. This interface is used to describe a role in my repository so I can dynamically lookup Fetching Strategies that take that role as a generic parameter and use them to fetch data.

   1:  internal static void SimpleRepositoryDynamicFetching()
   2:  {
   3:      Specification<poco.Customer> simonsCoSpec =
   4:          new Specification<poco.Customer>
   5:              (c => c.Country == "Germany");
   6:   
   7:      DataContext ctx = GetContext();
   8:   
   9:      Repository<poco.Customer> customerRepository =
  10:          new Repository<poco.Customer>(ctx);
  11:   
  12:      var custs = customerRepository.
  13:               All<ext.ICustomerLoyaltyDiscount>(simonsCoSpec);
  14:   
  15:      foreach (var cust in custs)
  16:      {
  17:          Console.WriteLine("The Name of the Customer " +
  18:                   "from Germany is {0}",
  19:                   cust.CompanyName);
  20:          foreach (var order in cust.Orders)
  21:          {
  22:              Console.WriteLine("\tOrder Number {0}", 
  23:                               order.OrderID);
  24:              foreach (var orderLine in order.OrderLines)
  25:              {
  26:                  Console.WriteLine("\t\tProduct ID {0} " +
  27:                      "Amount {1}",
  28:                      orderLine.ProductID, 
  29:                      (orderLine.Quantity * 
  30:                        orderLine.UnitPrice));
  31:              }
  32:          }
  33:      }
  34:  }

As you can see from the code above, when I call the Repository ALL method (as shown below), I use the overload that takes a generic parameter, indicating to the repository to look for a Fetching Strategy that also has this generic argument defined as its role.

   1:  public IList<T> All<TRole>(ISpecification<T> spec)
   2:  {
   3:      using (_context)
   4:      {
   5:          ext.IFetchingStrategy<TRole> strategy = 
   6:              DynamicFetchingStrategyLoad<TRole>();
   7:          _context.LoadOptions = strategy.LoadOptions;
   8:          return GetTable().Where(spec.EvalPredicate).
   9:                            ToList<T>();
  10:      }
  11:  }

The next time I post on this subject I will include the entire code, repository, specifications and fetching strategies.

Share/Save/Bookmark

Creative Commons Attribution-ShareAlike 2.5 Australia
Creative Commons Attribution-ShareAlike 2.5 Australia