1

I have a query like :

from joinedEntity in joinedEntities
join relatedEntity in dbContext.X on ....

Now, I want to write the above query in a way that "X" could be dynamically passed so that I don't need to write a separate linq query for every different "X". Any help? Should I use expression trees and reflections? If yes, how? Thank you.

I have some guess that it should be something like this:

var parameter =  Expression.Parameter(typeof (CompanyContext), "dbContext");
var member = Expression.MakeMemberAccess(parameter, typeof(CompanyContext).GetMember("X")[0]);
.....

Here is a sample of my Join:

       from joinedEntity in joinedEntities
       join relatedEntity in dbContext.Channels on joinedEntity.Id equals relatedEntity.CId
        select new JoinedEntities(joinedEntity)
        {
        Channel  = relatedEntity.Name
        };

And this is the method syntax of it:

joinedEntities.Join(dbContext.Channels, m => m.Id, k => k.CId, (m, k) => new JoinedEntities(m)    { Channel = k.Name });

This is also another one:

from joinedEntity in joinedEntities
join relatedEntity in dbContext.ActivityOutput on joinedEntity.Id equals relatedEntity.CId
        select new JoinedEntities(joinedEntity)
         {
              ActivityOutput = relatedEntity.Name
           };
Moji
  • 361
  • 6
  • 19
  • does it help? http://stackoverflow.com/questions/389094/how-to-create-a-dynamic-linq-join-extension-method – Vladimir Gondarev Apr 19 '14 at 07:25
  • Why not create a generic method whic accept `T` in Enities? –  Apr 19 '14 at 07:29
  • Is your join on different or the same for each "X"? – CharlesNRice Apr 20 '14 at 01:25
  • @VladimirGondarev I didn't know of this class. Now trying to figure out how to use it. Not sure if it can help me with this. – Moji Apr 20 '14 at 03:51
  • @Inanikian. Yeah I was thinking of that, but how? can u elaborate on that more? – Moji Apr 20 '14 at 03:51
  • @CharlesNRice Yeah they're joined on the same field which is ID. I'll update the question with a sample. – Moji Apr 20 '14 at 04:06
  • @CharlesNRice Yes, it's Entity Framework. db and dbContext is same. sorry for that. They are on different entities (properties), Projection is always like the example, project to the field "Name" of that entity(property). Also I call class constructor for my projection in order to save the previous values. – Moji Apr 20 '14 at 05:05
  • 1
    Your projections are different one is setting ActivityOutput and the others Channel. Does the projections need to be different? – CharlesNRice Apr 20 '14 at 05:12
  • @CharlesNRice Yes they are different. I'm gonna try the approach you posted. thanks – Moji Apr 20 '14 at 09:07
  • I've updated the answer to allow you to still do the projection. – CharlesNRice Apr 20 '14 at 11:33

1 Answers1

1

The easiest way would be to create an interface that says there is an CId field.

// Don't know if you need this one or if you want to have the class of joinedEntities always being the same
public interface IJoinedEntities
{
    int Id { get; set; }
}

public interface IRelatedEntity
{
    int CId { get; set; }
}

All the related entities would need to implement IRelatedEntity. Entity Framework has all their class are partial so you would just need to make another partial class to add this interface.

Since you want the projections just create an extension method that we will chain into the standard join.

public static class CustomIQueryableExtensions
{
    public static IQueryable<TResult> CommonJoinQueryable<TOuter, TInner, TResult>(this IQueryable<TOuter> outer,
                                                                          IQueryable<TInner> inner,
                                                                          Expression<Func<TOuter, TInner, TResult>>
                                                                              resultSelector)
        where TOuter : IJoinedEntities
        where TInner : IRelatedEntity
    {
        // have to use expression trees to build the join otherwise cast to interface is in expression tree
        var outerParam = Expression.Parameter(typeof (TOuter), "outer");
        var outerBody = Expression.Lambda<Func<TOuter, int>>(Expression.Property(outerParam, "CId"), outerParam);

        var innerParam = Expression.Parameter(typeof (TInner), "inner");
        var innerBody = Expression.Lambda<Func<TInner, int>>(Expression.Property(innerParam, "Id"), innerParam);

        return outer.Join(inner, outerBody, innerBody, resultSelector);
    }


    public static IEnumerable<TResult> CommonJoinEnumerable<TOuter, TInner, TResult>(this IEnumerable<TOuter> outer,
                                                                           IEnumerable<TInner> inner,
                                                                           Func<TOuter, TInner, TResult>
                                                                               resultSelector)
        where TOuter : IJoinedEntities
        where TInner : IRelatedEntity
    {
        // have to use expression trees to build the join otherwise cast to interface is in expression tree
        Func<TOuter, int> outerJoin = outerParam => outerParam.Id;
        Func<TInner, int> relatedJoin = innerParam => innerParam.CId;

        return outer.Join(inner, outerJoin, relatedJoin, resultSelector);
    }
}

Now you can just use it and still handle the projection.

joinedEntities.CommonJoinQueryable(dbContext.Channels, (m, k) => new JoinedEntities()    { Channel = k.Name, tracking = m }); 

joinedEntities.CommonJoinEnumerable(dbContext.Channels, (m, k) => new JoinedEntities(m)    { Channel = k.Name }); 

You should still use SQL Profiler to watch what gets generated when you use the Enumerable because it will pull the results down and then join them in memory, not from SQL. I know that's what you requested but it's not common. Instead of passing a parameter into the constructor you should consider making a property that you can just set with the projection then stick with IQueryable.

I believe this is what lnanikian was trying to get at.

CharlesNRice
  • 3,219
  • 1
  • 16
  • 25
  • Thanks. Tha's what I was looking for. But now when I try this approach, I get this error: "Unable to cast the type 'Mokhaberat.Models.ActivityArea' to type 'Mokhaberat.Models.Helper.IRelatedEntity'. LINQ to Entities only supports casting EDM primitive or enumeration types." ActivityArea here is something like the Channels we have in your sample. I've also Implemented IRelatedEntity for ActivityArea. Any thoughts? – Moji Apr 23 '14 at 07:32
  • Updated it to use expression tree for joins to remove the cast. – CharlesNRice Apr 23 '14 at 17:11
  • I tired this way, everything sound like working except I get error for the constructor "Only parameterless constructors and initializers are supported in LINQ to Entities." I was trying to solve it by using extension method for IEnumerable but couldn't make any progress...I saw some people said to use Copy-Constructor but don't know how to actually use it. – Moji Apr 26 '14 at 07:34
  • To be honest I was surprised EF would allow you to pass an object into the constructor. We use EF 5 and it doesn't allow it. I thought maybe EF 6 allowed it. But it's nothing to do with the join statement that's an EF limitation. The only other way would be to change from IQueryable to IEnumberable but that would query the database and pull the objects down into memory before the join happened. I can give an example of that but you could be pulling down a lot of data. – CharlesNRice Apr 26 '14 at 15:29
  • I actually read somewhere for IEnumerable, before you use Where clause, it's still differed execution. So maybe I still could go with IEnumerable? Can you please help with the same extension method to get IEnumerable? I get this "the type argument for .... cannot be inferred from the usage. Try specifying the type arguments explicitly", when I change the method to use IEnumerable. – Moji Apr 27 '14 at 06:26
  • I updated the answer but you should watch SQL Profiler you will see it will join them in memory and not from the select statement sent to SQL. – CharlesNRice Apr 27 '14 at 18:53
  • Thanks man. Yeah sure I will check it out with SQL Profiler. You know what, the problem is I have like hell number of properties to set to. So having class constructor doing this is much easier. Otherwise my .cs file would be like scroll able to infinity! – Moji Apr 28 '14 at 04:06