Linq表达为params

时间:2014-05-24 21:00:10

标签: c# linq delegates expression lambda

我想从我的存储库中调用IQueryable<T>方法,但我想在ORM中急切地加载子对象。为了将持久性逻辑保留在持久层中,我想传递一个表达式列表,表示我想要急切加载的属性。

我的IRepository<TClass>方法如下所示:

IQueryable<TClass> AsQueryable();

我想让它看起来像:

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

...所以我可以这样称呼它:

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

并反汇编后端的委托以急切加载指定的属性。

我应该为此使用什么?

3 个答案:

答案 0 :(得分:2)

您的AsQueryable<TClass>应该将属性表达式作为参数,并且需要具有以下签名:

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

请注意,我们使用的是Func<TClass, object>,它是一个将TClass作为输入并返回一个对象的函数。这使我们可以按如下方式拨打电话:

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

另请注意,我没有偶然选择object作为Func<TClass, object>的TResult。由于函数委托的TResult泛型参数是协变的,因此即使使用不同的属性类型,我们也可以传递表达式。因此,上例中的Property1和Property2不需要是同一类型。

就我的问题而言,这就是它,但这里有一点额外的内容:

如果您偶然需要评估传递的表达式以便将它们与ORM一起使用(例如,您只需要属性名称,但是您希望将它们作为表达式传递,以避免硬编码名称并保留编译时检查),你需要这样的东西:

  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
        }
    }

上面的代码确保传递的表达式是属性表达式,并从中提取PropertyInfo。

答案 1 :(得分:1)

这些选项似乎不应该成为这种方法的关注点,因为您可能希望整个仓库能够懒散地或急切地加载属性。除了AsQueryable

之外,我认为你在这个界面中有其他方法

因此,另一种方法是将此功能保留在AsQueryable()方法之外,而是创建其他方法,这些方法将创建一个配置为热切加载的新包装器(我不会改变底层实例)。 / p>

// 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);

这很简洁,因为您不必让您的调用者代码急切地决定要加载什么,并且最有可能减少在整个地方写入的管道代码的数量。通过这种方式,您可以创建热切的repo并将其传递给一堆毫无防备的代码。

答案 2 :(得分:0)

我想到的是假设您需要将所有属性设置为相同类型(在以下示例代码中使用泛型参数O表示):

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);
    }
}

希望它能帮到你。我仍在思考如何才能与众不同。好问题!实际上你不需要使用System.Linq.Expression.Expression&gt;但只是Func。