Reusing a join in LINQ
I have a number of queries (in a C# project) that all use the same join syntax, is there a simple way to move the join out of the query an into a 'function' or 'template' (for want of a better word).
The current query looks like this (rc is my ObjectContext)
var x =
from b in rc.Pressures
join g in rc.GUIDIdentityMaps on b.UserIdentity equals g.UserIdentity
where g.UserId == userId
orderby b.Recorded descending
select b;
I'd like to be able to take the join out and put it in a 'function' called something like 'PressuresJoin'
from b in rc.Pressures
join g in rc.GUIDIdentityMaps on b.UserIdentity equals g.UserIdentity
where g.UserId == userId
and then use something like
var x = PressuresJoin()
orderby b.Recorded descending
select b;
Update : Following @Xanatos' directions I created an IQueryable function like this.
public static IQueryable<Pressure> PressuresJoin(ObjectContext rc, Guid userId)
{
return from b in rc.Pressures
join g in rc.GUIDIdentityMaps on b.UserIdentity equals g.UserIdentity
where g.UserId == userId
select b;
}
which I then used like this
IQueryable<Pressure> q = PressuresJoin(rc, userId);
var y =
from b in q
orderby b.Recorded descending
select b;
so using the IQueryable now means that I don't need to write the same join code over and over and nor do I need to ensure that the Pressures coming from the DB are restricted to the correct user (as the PressuresJoin ensures this always happens).
Notice that I didn't need to create an extra class (as suggested) because I don't need any of the GUIDIdentityMaps information AFTER the join is made.
Very neat and tidy - thanks to all contributors
Update II : Following the answer provided by Kirk I moved away from the 'manual join' syntax and looked at using the navigation property.
This led me to a couple of choices The first one was to use the .Include like this
public static IQueryable<Pressure> PressuresJoinInclude(ObjectContext rc, Guid userId)
{
return from b in rc.Pressures.Include("GuidIdentityMap")
where b.GUIDIdentityMap.UserId == userId
select b;
}
and call it like this
IQueryable<Pressure> q = PressuresJoinInclude(rc, userId);
var y =
from b in q
orderby b.Recorded descending
select b;
That works just dandy (so Kirks answer is also the answer) but I don't really like the .Include(string) syntax and it caught me out as I'd mis-spelt the GUIDIdentityMap.
Then I looked at ways to avoid using .Include(string) and came up with this
public static IQueryable<Pressure> PressuresJoinDirect(ObjectContext rc, Guid userId)
{
return from b in rc.GUIDIdentityMaps.Single(i => i.UserId == userId).Pressures.AsQueryable()
select b;
}
notice the 'AsQueryable() at the end (casting it from IEnumerable back to IQueryable) This is called like
IQueryable<Pressure> q = PressuresJoinDirect(rc, userId);
var y =
from b in q
orderby b.Recorded descending
select b;
So far I think this is my favourite, although it would be nicer if Pressures was the 'first' object in the statement instead of GUIDIdentityMaps, but I've not checked to see what SQL is produced or what performace differences there are (if any)
Now I'm going to look at Jims .(dot) solution.
Update III : Hopefully I've understood the dot notation correctly, and here is my 'join' by using the Navigation already built in to the objects. My IQueryable function looks like this
public static IQueryable<Pressure> PressuresJoinDot(ObjectContext rc, Guid userId)
{
return from b in rc.Pressures
where b.GUIDIdentityMap.UserId == userId
select b;
}
and using it is as simple as the others
IQueryable<Pressure> q = PressuresJoinDot(rc, userId);
var y =
from b in q
orderby b.Recorded descending
select b;
At the end of all of this I do a Skip/Take like this
IQueryable<Pressure> z = y.Skip(startRow).Take(rows);
List<Pressure> l = z.ToList();
All four of these options above work and the end result of any of these queries is the same set of records. To my surprise they don't all produce the same SQL and they don't all go at the same speed (hence why I split the Skip/Take and ToList()).
The plain old Join and the Dot version perform at similar speeds, with the plain Join being a bit quicker probably. The Include version performs well but has an increasing number of DB reads as you Skip/Take up into the list of records. The Direct method is woefully slow, more than 10 fold on the other methods and I'm sure that the Skip/Take is performed on the List() generated rather than on the database.
Certainly opened my eyes to how easy it would be to choose the wrong LINQ, and certainly choosing it on the basis that it looks 't开发者_开发知识库he nicest', the Direct method, would not be the best idea.
What you see as a join
is really Queryable.Join<TOuter, TInner, TKey, TResult>. So any method that returns a IQueryable<TResult>
can be used as PressuresJoin
.
If you are working with linq-to-objects clearly you only need to return a IEnumerable<TResult>
class PressuresGUIDIdentityMap
{
public Pressure Pressure;
public GUIDIdentityMap GUIDIdentityMap;
}
public static IQueryable<PressuresGUIDIdentityMap> PressuresJoin(ObjectContext rc)
{
return from b in rc.Pressures
join g in rc.GUIDIdentityMaps on b.UserIdentity equals g.UserIdentity
select new PressuresGUIDIdentityMap { Pressure = b, GUIDIdentityMap = g };
}
Note that you'll have to create a class to "support" your join, as I've done. This because normally you had the projection part (the select) at the end of your query. Here you have to create a temporary object.
You could take a look at using compiled query to increase performance. Quote:
"When you have an application that executes structurally similar queries many times in the Entity Framework, you can frequently increase performance by compiling the query one time and executing it several times with different parameters. For example, an application might have to retrieve all the customers in a particular city; the city is specified at runtime by the user in a form. LINQ to Entities supports using compiled queries for this purpose."
You should simply be using navigational properties. If you've mapped your entities correctly then the Entity Framework should be taking care of 'joins' for you.
See my question Why use LINQ Join on a simple one-many relationship?
Instead of
from b in rc.Pressures
join g in rc.GUIDIdentityMaps on b.UserIdentity equals g.UserIdentity
where g.UserId == userId
just call
rc.Pressures.Include("GUIDIdentityMaps")
or if you prefer
from b rc.Pressures.Include("GUIDIdentityMaps")
select b
精彩评论