The results of this query;
using (ObjectContext ctx = new ObjectContext("Name=NorthwindEntities"))
{
ObjectQuery<Customers> query = ctx.CreateQuery<Customers>(
"select value c from NorthwindContext.Customers as c");
foreach (Customers c in query)
{
Console.WriteLine(c.Orders.Count);
}
}
are very different to their moral equivalent in the LINQ to SQL world in that what you get is a lot of rows containing the number 0 - that is, the loading of the Orders relationship is not done automatically for you by default.
The same would be true where you to have something like this reverse view of the relationship;
using (ObjectContext ctx = new ObjectContext("Name=NorthwindEntities"))
{
ObjectQuery<Orders> query = ctx.CreateQuery<Orders>(
"select value o from NorthwindContext.Orders as o");
foreach (Orders o in query)
{
Console.WriteLine(o.Customers.City);
}
}
(Note - don't get confused by the plural Customers in the code above where we are doing o.Customers.City - it's just that the entity type is called Customers in my model and so that's why we end up with Customers there as that property rather than Customer but there is only one of those things).
Having said that, you can go and load a property like Orders yourself using code such as;
using (ObjectContext ctx = new ObjectContext("Name=NorthwindEntities"))
{
ObjectQuery<Customers> query = ctx.CreateQuery<Customers>(
"select value c from NorthwindContext.Customers as c");
foreach (Customers c in query)
{
c.Orders.Load();
Console.WriteLine(c.Orders.Count);
}
}
Note: When I run that code above I get an exception;
"There is already an open DataReader associated with this Command which must be closed first."
Now, as far as I know this is because the bits here against SQL 2005 are trying to use MARS support without having switched it in the connection string. This might not be present in the recently released bits so you might be ok but I had to change my SQL connection string to add;
MultipleActiveResultSets=true
and then things worked ok and my Orders were populated for me. I can also use the Include method of ObjectQuery<T> to give me another query which will pre-populate the members of the relationship that I'm interested in;
using (ObjectContext ctx = new ObjectContext("Name=NorthwindEntities"))
{
ObjectQuery<Customers> query = ctx.CreateQuery<Customers>(
"select value c from NorthwindContext.Customers as c");
query = query.Include("Orders");
foreach (Customers c in query)
{
Console.WriteLine(c.Orders.Count);
}
}
and, once again, this gives me a list of numbers rather than a list of zeroes. Going a little further I can include 2 relationships for the price of one;
using (ObjectContext ctx = new ObjectContext("Name=NorthwindEntities"))
{
ObjectQuery<Customers> query = ctx.CreateQuery<Customers>(
"select value c from NorthwindContext.Customers as c");
query = query.Include("Orders.Order_Details");
foreach (Customers c in query)
{
Console.WriteLine(c.Orders.Count);
foreach (Orders o in c.Orders)
{
Console.WriteLine(o.Order_Details.Count);
}
}
}
and include 2 relationships all in one go which is pretty neat :-) Note that this aspect of loading relationships also comes into play when dealing with deleting data because you might go and delete a Customer whose Orders you have not loaded into your application and it's important that something sensible happens there. I'll return to that in a future post where I start to actually modify data :-)
Continuing with this theme of deferred loading, there are likely to be places where you're not sure whether or not a relationship has been loaded. There's help in the framework for that. For example;
using (ObjectContext ctx = new ObjectContext("Name=NorthwindEntities"))
{
ObjectQuery<Customers> query = ctx.CreateQuery<Customers>(
"select value C from NorthwindContext.Customers as c");
query.Include("orders");
foreach (Customers c in query)
{
Console.WriteLine("Customer [{0}]", c.CustomerID);
if (!c.Orders.IsLoaded)
{
c.Orders.Load();
}
foreach (Orders o in c.Orders)
{
Console.WriteLine("\tOrder [{0}]", o.OrderID);
if (!o.Order_Details.IsLoaded)
{
o.Order_Details.Load();
foreach (Order_Details d in o.Order_Details)
{
Console.WriteLine("\t\tDetails [{0}]", d.ProductID);
}
}
}
}
}
so that IsLoaded property provides a bit of a "safety harness" in terms of being able to know whether someone has been kind enough to pre-populate these lazily-loaded properties for you or not :-)
Posted
Mon, Aug 27 2007 4:32 PM
by
mtaulty