Saturday, December 8, 2007

C# Type Inference But Still Strongly Typed

I have spent considerable time today reviewing Linq and more specifically Linq to Sql. I'm currently working on a blog post where I'll go in to the details of what I think the pros and cons of Linq to Sql are as well as my overall opinion. In case you couldn't guess it I'll be using NHibernate for my comparisons, after all it is what I'm familiar with.

While reviewing some things I ran in to the following compile time check. It was very simple for me to resolve, but I wonder if it will cause developers to fall in to traps. Especially those developers who have some experience with weakly typed languages such as Javascript.

Take a look at the following code I wrote:


AdventureWorksDataContext context =
new AdventureWorksDataContext();

var orders = from po in context.PurchaseOrderHeaders
select po;

if (chkUseDate.Checked)
{
orders = from po in orders
where po.OrderDate > dtOrderFrom.Value
select po;
}

orders = from po in orders
orderby po.OrderDate ascending
select new
{
po.PurchaseOrderID,
po.RevisionNumber,
po.OrderDate,
po.ShipDate
};


Does anyone see what is wrong with the code above and why it failed to compile?

The compile-time error was:
Cannot implicitly convert type 'System.Linq.IQueryable<AnonymousType#1>' to 'System.Linq.IQueryable<BLL.PurchaseOrderHeader>'. An explicit conversion exists (are you missing a cast?)

After seeing that I immediately realized that I tried to use an object which type inferred to return PurchaseOrderHeader objects to return anonymous type objects instead. You can't just change a reference to be of another type in C# 3.0, hence the strong typing, I should know better.

But honestly, with the whole var keyword, I wasn't really thinking about it. It was a minor slip up, but I wonder how many developers will fall in to that trap. I think some developers may have seen the var keyword before in Javascript and they may have used it in the fashion I just did.

That being said, I have been enjoying my time with Linq today. I should have a post up within the next few days with more details.

P.S. If you're wondering what is going on with the 3 step linq ueries above, that's how you write dynamic queries in Linq. Simply reference the previously defined query in your new linq query in order to further restrict the query which you are building. Keep in mind that writing a linq query doesn't perform any operations. You have to either enumerate over the values of the query or call a method on the query like ToArray(), ToDictionary(), Select() etc...

If you're curious how to resolve the issue above you just need to declare a new variable for the last query to store the new type. var results = <Linq expression> would work just fine.

--John Chapman

5 comments:

Popo said...

I got the same problem and I would appreciate if you could provide me the solution or example how to create strong typed to fix the problem. Thank you

John Chapman said...

Popo,

Actually C# is always strongly typed. That was sort of the point of this post. My example threw an exception because I had already defined the type of "orders". I later tried to assign a different type to query when I used the anonymous type at the bottom. If I use a new variable for the last line everything will work fine.

The var keyword simply says that I shouldn't have to specify the type since what I'm assigning it to already has a type which is clear based on how I used it. However, that doesn't mean you can just assign that variable to anything. It can only be assigned to items of that same type (or subtypes).

For my example change the last Linq query to var result = from po in orders...

Then you'll see that it will work.

Girija Sankar said...
This comment has been removed by the author.
Girija Sankar said...

AdventureWorksDataContext context =
new AdventureWorksDataContext();

var orders = from po in context.PurchaseOrderHeaders
select po;

if (chkUseDate.Checked)
{
orders = from po in orders
where po.OrderDate > dtOrderFrom.Value
select po;
}

orders = from po in orders
orderby po.OrderDate ascending
select new
{
po.PurchaseOrderID,
po.RevisionNumber,
po.OrderDate,
po.ShipDate
};

There must be a returnd value for this. For ex : return (IQuerable) orders.

But If you call this method is somewhere and try to iterate through a Foreach loop then you will get an error of converting system.string or system.int to an anynomous type..

So I added the orders value to a Generics List . Example is as follows :
//type is string for the List
public List getAdventureWorkdata()
{
AdventureWorksDataContext context =
new AdventureWorksDataContext();

var orders = from po in context.PurchaseOrderHeaders
select po;

if (chkUseDate.Checked)
{
orders = from po in orders
where po.OrderDate > dtOrderFrom.Value
select po;
}

orders = from po in orders
orderby po.OrderDate ascending
select new
{
po.PurchaseOrderID,
po.RevisionNumber,
po.OrderDate,
po.ShipDate
};

// Here we can add this orders object to a Generics List (type of as your choice)

List lstListt=new List
foreach (var query in orders)
{
// Here I am adding only purchaseOrderID. You can add more columns also.
lstListt.Add( po.PurchaseOrderID.ToString ());
}

return lstList;
}

Now call this method in a separate class. ( here I am calling through a console application)
public static void SelectOperator()
{
//here I put string as the type of list.
List lstLing;
AdventureWorksData objAdventureWorksData; // The class name. I put AdventureWorksData here
try
{
objAdventureWorksData= new AdventureWorksData();
lstLink= objAdventureWorksData.getAdventureWorkdata();
foreach (string user in lstLink)
{
Console.WriteLine(user.ToString());
}


}
catch
{
}
}

Anonymous said...
This comment has been removed by a blog administrator.

Blogger Syntax Highliter