I've been mostly watching LINQ take shape and only recently started in looking at it a bit more closely.I've been pretty excited about the prospect of LINQ in general and the database connectivity in particular - after all I come from an XBase background where a built in Data Definition Language and native SQL syntax has always been available (for 20 years!). LINQ of course is much more than just the data access layer and to be honest the language integration features are much more impressive than the data possibilities.
Disclaimer: I'm just starting to look at LINQ to Entities stuff, so it's quite possible I'm missing some of the picture - I'm throwing out some of the impressions and questions I have from what I've read, tried and heard from various speakers.
Last week at the MVP summit, Anders Heljsberg went through a quite extensive demo of various LINQ features and it was just one more kick in teh ass for me to take a closer look. However, after seeing Ander's demo and hearing some of the comments made, and asking a few questions regarding LINQ there's one issue that really gnaws at me and that's the issue of anomyous types travelling well across local method boundaries such as returning a dynamic query result from a method.
I'm not 100% sure I understand all the issues here, but apparently one issue of LINQ is that you cannot run 'dynamic' queries - queries that return anonymous types - and return the result back out of a method in a meaningful way. So if you do something like this:
public static void WebStore101()
{
WebStoreDataBaseDataContext db = new WebStoreDataBaseDataContext();
var q = from c in db.Customers
from i in db.Invoices
where c.company == "West Wind Technologies"
where c.pk == i.custpk
where i.invdate > DateTime.Now.AddMonths(-5)
select new { c.company, c.lastname, c.pk, i.invdate, i.invtotal };
ObjectDumper.Write(q, 0);
}
where the select clause basically creates an anonymous type on the fly, there's no good way to pass out the var q result. You can't define a method to return of type var as far as I can tell. You can return the type as a generic object. So return q can be returned generically only as type object or IEnumerable when the type is anonymous.
So we have duck typing but no way to waddle our way out of the local scope <s>.
This makes perfect sense in a strongly typed environment I suppose - the specifications for the anonymous type are bound up within this local method and outside of that method nothing can really know what the type of the created object is. But nevertheless from a re-usability point of view we have a big problem if you want dynamic results.
What’s a bit infuriating about this is – we’re so close yet not quite there! You can force a projection to be identified with a type name by using the into clause. For example to return both the invoice and customer data in a local method you can use code like this:
var q = from c in db.Customers
from i in db.Invoices
where c.company == "West Wind Technologies"
where c.pk == i.custpk
where i.invdate > DateTime.Now.AddMonths(-5)
select new { c, i, c.lastname, i.invtotal} into x
select x;
foreach (var x in q)
{
Console.WriteLine(x.c.company + " - " + x.c.lastname + " - " +
x.i.invdate.ToString() + " - " + x.i.invtotal.ToString());
}
So within the local method you can get this duck typing to work through projection, but unfortunately ‘x’ in this case is a local only variable. It sure would be nice if there was some way to define a public type…
The above only applies if you’re using anonymous types. You CAN return query results if you run the results into a specific enumarable type which means using the select clause and inserting into an existing type:
public static IEnumerable<Customers> WebStore101()
{
WebStoreDataBaseDataContext db = new WebStoreDataBaseDataContext();
IEnumerable<Customers> q =
from c in db.Customers
from i in db.Invoices
where c.company == "West Wind Technologies"
where c.pk == i.custpk
where i.invdate > DateTime.Now.AddMonths(-5)
select c;
ObjectDumper.Write(q, 0);
return q;
}
the result q in this case is a concrete enumaration and that can easily passed out of the method. But if you do this you're also not getting any of the related data - the invoice data in this case since the type you assign has to be a single type. IOW, this completely changes the nature of the query...
The other problem with concrete class implementations – unless you create specific classes for every possible query result – is that you end up with a lot more data than you might be asking for. If you run say: select c.pk, c.lastname and you project it into a full customers object you’ll get the entire customer data instead of just the pk (although single values can be cast directly to their structural type with LINQ, so maybe that’s a bad example – take two fields instead).
What works and what doesn’t?
If you're going to end up using LINQ queries right in the UI layer and databinding the data directly to a DataSources there's not much of a problem. The DataSource controls understand and can bind to the data no problem because they are already using Reflection to dynamically figure out what fields need to be bound etc.
DataSource controls appear to also understand the object structure if you return a dynamic query result as IEnumerable or object, because they essentially look at the object structure via Reflection.
But I’m seriously wondering how can I use LINQ in my business layer and pass the results I might get back from a dynamic query back up to another application layer like the UI. And say the UI layer needs to iterate over the data manually to perform some calculation. This doesn’t appear to be an option…
Am I missing something really obvious here? This seems like a major limitation of LINQ.
Other ways to query?
So while looking at this I started experimenting around with a number of different ways to query the data and try to return results back. As I mentioned earlier you can return a dynamic query result as object or IEnumerable. Not terribly useful but it works with databinding.
One thing that does work is to create a custom time and then have LINQ project the result into a custom type. So if you run a query and you have a couple of dynamic fields you can create a public matching class and dump the data into that. It’s pretty cool that you can do this, but it begs the question – why isn’t there some way that the compiler can’t do this for me with the into (or some other directive):
public class WebStoreDataTest
{
public static IEnumerable<SqlResult> WebStore101()
//public static void WebStore101()
{
WebStoreDataBaseDataContext db = new WebStoreDataBaseDataContext();
// use Where() to find only elements that match
//IEnumerable<Customers> q = db.Customers.Where(c => c.company == "West Wind Technologies");
//IEnumerable<Customers> q =
IEnumerable<SqlResult> q = from c in db.Customers
from i in db.Invoices
where c.company == "West Wind Technologies"
where c.pk == i.custpk
where i.invdate > DateTime.Now.AddMonths(-5)
select new SqlResult(c.company, c.lastname);
ObjectDumper.Write(q, 0);
return q;
}
}
public class SqlResult
{
public SqlResult(string Company, string LastName)
{
this.company = Company;
this.lastname = LastName;
}
private string _company;
public string company
{
get { return _company; }
set { _company = value; }
}
private string _lastname;
public string lastname
{
get { return _lastname; }
set { _lastname = value; }
}
}
This works great – I get a strongly typed object, but then we're back into the same mess that we've always had with Entity object frameworks where every kind of query requires a custom object to load the data into.
It sure would be nice if the into clause could somehow create a public object (optionally) - after all the compiler is already doing the hard part of creating the types in the first place...
So what?
LINQ looks just awesome (not just for the Data access stuff, but for the base language), but this issue has me pretty worried about just how useful LINQ is going to be inside of a business layer.
Anybody been thinking about this issue? I can’t be the first one who’s hit this and trying to analyze the implications.
Other Posts you might also like