开发者

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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜