【问题标题】:Linq expressions as paramsLinq 表达式作为参数
【发布时间】:2014-05-24 21:00:09
【问题描述】:

我想从我的存储库中调用一个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, ...);

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

我应该用什么来做这个?

【问题讨论】:

  • 希望你明白我的意思?您需要遍历不同的表达式/函数,我目前在我的示例中没有这样做。让我知道它是否对您有帮助:)

标签: c# linq delegates expression lambda


【解决方案1】:

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

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

请注意,我们使用的是Func&lt;TClass, object&gt;,它是一个接受 TClass 作为输入并返回一个对象的函数。这使我们可以进行如下调用:

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

另外请注意,我没有偶然选择object 作为Func&lt;TClass, object&gt; 的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。

【讨论】:

    【解决方案2】:

    这些选项似乎不应该成为此方法的关注点,因为您可能希望整个 repo 要么懒惰地加载属性,要么急切地加载属性。除了AsQueryable,我想你在这个接口里还有其他方法?

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

    // 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 并将其传递给一堆毫无戒心的代码。

    【讨论】:

    • 这是一个很好的设计适配建议,但我认为它并没有回答这个问题,其实就是:如何将linq属性表达式作为参数?
    • @Edin:是的,你回答了这个问题,我什至没有提到如何解决这个问题(我什至故意为每个属性使用一个方法)。恕我直言,我仍然相信这个答案可能有用,但它并不适合发表评论。
    • 这实际上与我最终得到的结果很接近,但我认为 Edin 确实回答了这个问题。
    【解决方案3】:

    我想出的假设是您需要将所有属性都设为同一类型(在以下示例代码中使用泛型参数 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> 而只是 Func。

    【讨论】:

    • 嗯,不,属性类型会有所不同。这是一个 ORM 的服务层;我想急切加载的每个属性都是不同的类型。
    • 然后您可以将 Func 替换为 Func (其中 I 是您的实体类型)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多