All posts

LINQ Tip of the week: LINQ and Anonymous types

Today we have a closer look at using LINQ and anonymous types and it's pros and cons.

Consider the case where we want to obtain a subset of data instead of an entire persistent instance. For example, if we want to obtain the contact details of all Customers based in London we would use the following query

var query = from c in scope.Extent<Customer>()
                  
where c.City.Equals("London")
                  
select new { c.City, c.Address, c.ContactName };
 

Notice the last line in the above query - select new { c.City, c.Address, c.ContactName }
This line creates an anonymous type. Behind the scenes, at compile time, a very simple class is generated automatically. In this case, three properties, City, Address and ContactName, are added to the class.

The result of the query can be processed further using the var keyword as follows -

foreach(var contact in query)
   
Console.WriteLine(contact);
 
There are a couple of problems with the above approach -
  1. Since var cannot be used in the parameter list or the return type of a method, anonymous types can be used only within the scope of the current method.There is usually no way to pass it to another method or broaden it's scope.
  2. You could get over this by declaring a type that contains members to hold the result and use that instead of the anonymous type. We can do this as follows -
public class CustomerContact
{
   
public string City { get; set; }
   
public string Address { get; set; }
   
public string ContactName { get; set; }
}
 
and the query would now be
var query = from c in scope.Extent<Customer>() 
                  
where c.City.Equals("London")                  
                   select new CustomerContact { City = c.City, Address = c.Address, ContactName = c.ContactName };
 
But what if you need additional data? You would then need to modify the query and also the temporary type declaration accordingly. Definitely tedious!
You might want to use this approach thinking that it is more efficient to query for only the required columns rather than the entire instance, but this is not necessarily the case.
 
To explain this further, here is the actual SQL query that is executed for the LINQ query mentioned above.
SELECT [City] AS COL1, [Address] AS COL2, [ContactName] AS COL3 FROM [Customers] WHERE [City] = 'London'
 

To avoid the overhead of using anonymous types and to achieve the same minimal query, you could use the Telerik OpenAccess ORM FetchPlan API. All you need to do is specify the FetchFieldAttribute, with a fetch group name, for the fields you want to retrieve and use that FetchGroup instead of the ‘Default Fetch Group’. So your code would now be something like this

  • Add the fields to the FetchGroup
 public partial class Customer 
   
{
       
private string customerID; // pk 

       
[Telerik.OpenAccess.FetchField("ContactDetails")]
       
private string address;

       
[Telerik.OpenAccess.FetchField("ContactDetails")]
       
private string city;

       
private string companyName;

       
[Telerik.OpenAccess.FetchField("ContactDetails")]
       
private string contactName;

       
private string contactTitle; 
        ……..
 
        ……..
}
 
  • Instruct OpenAccess to use this FetchGroup
scope.FetchPlan.Clear();
scope.FetchPlan.Add("ContactDetails"); //where 'ContactDetails' is the fetch group name that you specify at the FetchFieldAttribute 

var query = from c in scope.Extent<Customer>()
                  
where c.City.Equals("London")
                  
select c;

This would generate the following SQL query

SELECT [CustomerID] AS COL1, [Address] AS COL2,[City] AS COL3, [ContactName] AS COL4 FROM [Customers] WHERE [City] = 'London'

 

The only difference to the anonymous type query is the additional CustomerID column that is required for the change tracking features. So using FetchPlans over anonymous types requires you to write lesser code which can be very easily extended and you also retain the Telerik OpenAccess ORM flexibility like change tracking, for the retrieved instances.

Hope this tip was encouraging and stay tuned for more.

Facebook Twitter DZone It! Digg It! StumbleUpon Technorati Del.icio.us NewsVine Reddit Blinklist Add diigo bookmark

Comments  2

  • 21 Sep

    I agree and disagree with the retention of the change tracking, why do we need change tracking since the returned results in this scenario is not the whole object, and we are not refering to the entity instance here but a portion of it.

    i also do not see a reason for using the scope.FetchPlan.Add("ContactDetails"); to project returned query results, your linq query clearly states that you are returning an entire "Customer" instance. If the case is about returning part of Customer, nothing stops us from returning the entire object and use the linq provider to visit the select method again which will return the expected projection. If you guys are using a second level cache approach, the returned object will be cached and if the same query is run again, results will be picked from the cached object and not directly to the database.

    This following statements makes the whole query to look more uglier :

    scope.FetchPlan.Clear();
    scope.FetchPlan.Add("ContactDetails");



    I understand that creating a provider for linq API is a serious pain because linq cannot be easily transformed into query statements especially if you already have your own query API that you want to build linq upon. So a developer knows that an anonymous type cannot be used out of its method scope, so we could live that to the developer.

    Ahmed Salako

  • 21 Sep

    scope.FetchPlan.Add( c => c.ContactDetails);

    is another more intuitive way of doing this will be, using a fluent prgramming construct, rather than having the columns as a string. You can introduce a FetchPlan method into your queryable class (The class that implements IQueryable<T> ), This you can use to add your fetch plan properties.  You do not need to clear fetch plan here, as the fetch plan will be visible to only the current query.

    var query = ( from c in scope.Extent<Customer>()
                       where c.City.Equals("London")
                       select c
                )
                .FetchPlan( c => c.ContactDetails );

    Ahmed Salako

Post a comment
  1. Formatting options
       
     
     
     
     
       
  2. Security image