user247702
user247702

Reputation: 24212

Pass Func<> to Select

I'm starting with this:

query
    .Take(20)
    .Select(item => new
    {
        id = item.SomeField,
        value = item.AnotherField
    })
    .AsEnumerable()
    .ToDictionary(item => item.id, item => item.value);

Now, I want to reuse everything except SomeField and AnotherField.

public static Dictionary<int, string> ReusableMethod<T>(
    this IQueryable<T> query,
    Func<T, int> key,
    Func<T, string> value)
{
    return query
        .Take(20)
        .Select(item => new
        {
            id = key(item),
            value = value(item)
        })
        .AsEnumerable()
        .ToDictionary(item => item.id, item => item.value);
}

query.ReusableMethod(item => item.SomeField, item => item.AnotherField);

This works, but the DB query selects more data than required, so I guess that means ReusableMethod is using linq-to-objects.

Is it possible to do this while only selecting the required data? I'll add that Func<> is still part magic for me, so I might be missing something obvious.

Clarification to avoid confusion: the Take(20) is fine, the Select() isn't.

Upvotes: 2

Views: 2401

Answers (2)

Ondra
Ondra

Reputation: 1647

Recently I had the same problem and here is what I did:

  1. You have some DbEntity (generated by LINQ to EF,SQL), but you want to query only some fields (I did this to save network bandwidth). You have to create class derived from DbEntity, beacuse you cant create anonyous types in Expression trees and you can not create new instance of DbEntity in select statement. (No need to add any fields, properties etc.)

    public class LocalEntity : DbEntity {}

  2. You need to define a method to generate your select expression tree. It should look like this. This will generate expression tree similar to this: .Select(db => new LocalEntity() { Property1 = db.Property1, Proeprty2 = db.Property2})

    protected Expression<Func<DbEntity, LocalEntity>> getSelectExpression()
    {
        ParameterExpression paramExpr = Expression.Parameter(typeof(DbEntity), "dbRecord");
        var selectLambda = Expression.Lambda<Func<DbEntity, LocalEntity>>(
                            Expression.MemberInit(
                                Expression.New(typeof(LocalEntity)),
                                Expression.Bind(typeof(LocalEntity).GetProperty("DbEntityFieldName"), Expression.Property(paramExpr, "DbEntityFieldName"),
                                ....
                                ))
                            ),
                            paramExpr);
    
        return selectLambda;
    }
    
  3. Use it like this:

    query.Select(getSelectExpression()).ToDictionary();

Consider this more as pseudo-code than C# code, as I had to simplify it a lot and I can´t test it, but if oyu make it work, it will transfer from DB only fields you define in getSelectedExpression, not the whole row.

Upvotes: 1

Daniel A. White
Daniel A. White

Reputation: 190943

Wrap your funcs with Expression and remove the AsEnumerable call.

 public static Dictionary<int, string> ReusableMethod<T>(
    this IQueryable<T> query,
    Expression<Func<T, int>> key, 
    Expression<Func<T, string>> value)

An alternative would be to just return the whole row then. No need for Expression in this case.

return query
    .Take(20)
    .ToDictionary(key, value);

Upvotes: 3

Related Questions