Linq expressions as params

2019-07-15 08:24发布

I want to call an IQueryable<T> method from my repository, but I want to eagerly load child objects in my ORM. To keep the persistence logic in the persistence layer, I want to pass a list of expressions representing the properties I want to eagerly load.

My IRepository<TClass> method looks like this:

IQueryable<TClass> AsQueryable();

I would like to make it look like :

IQueryable<TClass> AsQueryable(params --something here--[] includeProperties);

...so that I can call it like:

var q = myFooRepository.AsQueryable(x => x.Property1, x => x.Property2, ...);

and disassemble the delegate on the back end to eagerly load the specified property(ies).

What should I be using for this?

3条回答
Fickle 薄情
2楼-- · 2019-07-15 09:00

What I came up with assumes that you need to have all your properties to be of the same type (represented using generic parameter O in the following sample code):

class SampleClass
{
    public string Property { get; set; }
    public SampleClass()
    {
        Property = DateTime.Now.ToString();
    }
}

class Program
{
    public static O Something<I, O>(params System.Linq.Expressions.Expression<Func<I, O>>[] funks)
        where I: new()
    {
        I i = new I();
        var output = funks[0].Compile()(i);
        return output;
    }

    static void Main(string[] args)
    {
        var dx= Something<SampleClass, string>(x => x.Property);
    }
}

hope it'll help. I'm still thinking how I can make the be different. nice question! actually you don't need to use System.Linq.Expression.Expression> but just Func.

查看更多
手持菜刀,她持情操
3楼-- · 2019-07-15 09:05

These options seem like they shouldn't be the concern of this method, since you probably want your entire repo to either load properties lazily or eagerly. I presume you have other methods in this interface, apart from AsQueryable?

A different approach would therefore be to leave this functionality out of the AsQueryable() method, but instead create additional methods which would create a new wrapper configured to load eagerly (I wouldn't mutate the underlying instance).

I.e.

// default repo is lazy
var myRepo = GetRepository<TClass>();

// these cool fluent methods create an eager repo
var myEagerRepo = myRepo
   .LoadEagerly(x => x.Property1)
   .LoadEagerly(x => x.Property2);

This is neat because you don't have to let your caller code decide what to load eagerly, and most likely reduces the amount of plumbing code written all over the place. This way you create the eager repo and pass it forward to a bunch of unsuspecting code.

查看更多
老娘就宠你
4楼-- · 2019-07-15 09:12

Your AsQueryable<TClass> should have property expressions as parameters and it would need to have the following signature:

public static IQueryable<TClass> AsQueryable<TClass>(this TClass obj, params Expression<Func<TClass, object>>[] propertyExpressions)

Note that we are using Func<TClass, object> which is a function acepting TClass as input and returning an object. That allows us to make a call as follows:

IQueryable<TClass> tClassQueryable = tClassObj.AsQueryable(x => x.Property1, x => x.Property2);

Also note that I did not choose an object as the TResult of Func<TClass, object> by chance. Since the TResult generic parameter of the function delegate is covariant, that allows us to pass expressions even with different property types. So your Property1 and Property2 in the example above do not need to be of same type.

That would be it, regarding your question I guess, but here is a little extra:

If you by chance need to evaluate the passed expressions in order to use them with your ORM (e.g. you just need property names but you want to pass them as expressions in order to avoid hardocoding names and preserve compile-time checks), you would need something like this:

  public static IQueryable<TClass> AsQueryable<TClass>(this TClass obj, params Expression<Func<TClass, object>>[] propertyExpressions)
    {
        foreach (var propertyExpression in propertyExpressions)
        {
            MemberExpression memberExpression = propertyExpression.Body as MemberExpression;

            if (memberExpression == null)
            {
                // this is needed for value types properties.
                UnaryExpression unaryExpression = (UnaryExpression)propertyExpression.Body;
                memberExpression = unaryExpression.Operand as MemberExpression;
            }

            if (memberExpression == null)
                throw new ArgumentException(string.Format("Expression '{0}' is not a member expression.", propertyExpression.ToString()));

            PropertyInfo propertyInfo = memberExpression.Member as PropertyInfo;
            if (propertyInfo == null)
                throw new ArgumentException("MemberExpression.Member is not a PropertyInfo.");


            // at this point we have PropertyInfo which you can use with your OR Mapper to further implement logic which will eager load the property
            // e.g. property name can be retrieved with:
            string propertyName = propertyInfo.Name;

            // do your ORM stuff here
        }
    }

The code above ensures that the passed expressions are property expressions and it extracts PropertyInfo from it.

查看更多
登录 后发表回答