通过反射从某些给定字符串创建强类型LINQ查询的最佳方法是什么

本文关键字:查询 LINQ 强类型 最佳 是什么 方法 创建 字符串 反射 | 更新日期: 2023-09-27 18:21:09

我使用的是EF5、工作单元和存储库模式。我想为指定用户定义一些访问数据的限制。在数据库中,我设计了一个表来保存我的实体名称及其属性,称为EntityProperties,另一个表用于保存这些属性的值,称为PropertyValue,每个EntityProperty都有一个或多个PropertyValue。在业务层中,当用户请求数据时,如果为他定义了任何限制,则应该在linq语句中添加一些条件。我所做的是通过"userId"获得实体名称及其属性和值的列表,然后在linq查询中添加一些"Where"子句。然而,实体名称及其属性的类型是"字符串",因此我应该使用反射来管理它们。但我不知道这一部分,也不知道如何从给定的一组条件字符串中创建LINQ where子句。例如,假设一个用户请求列表订单,并且用户id为5。我首先查询那些访问限制表,结果是:

"订单"、"Id"、"74"

"订单"、"Id"、"77"

"订单"、"Id"、"115"

这意味着这个用户应该只看到这三个订单,而在订单表中,我们有更多的订单。所以,如果我想使用LINQ查询来获取订单,比如:

var orders = from order in Context.Orders

我需要把它变成这样的东西:

var orders = from order in Context.Orders

//订单id应在74,77115 中

但是,从"Order"answers"Id"字符串中获取Order实体和Id属性需要反射。因此有两个问题:

从字符串中获得强类型的最佳方法是什么?有没有更好的方法让我做到这一点,有更好的表现?

通过反射从某些给定字符串创建强类型LINQ查询的最佳方法是什么

好的。有了注释,我们可能会选择类似的内容(假设您在EntityProperties表中有一个导航属性,它是PropertyValues的集合,并命名为PropertyValueList(如果没有,只需执行联接而不是使用Include)。

以下是一些非常乡村的示例代码,仅适用于Int32属性,但这可能是解决方案的开始。

您还可以查看PredicateBuilder。。。

无论如何

我使用"中间类"过滤器。

public class Filter
    {
        public string PropertyName { get; set; }
        public List<string> Values { get; set; }
    }

然后是一个助手类,它将返回一个IQueryable<T>,但是。。。过滤

public static class FilterHelper {
    public static IQueryable<T> Filter(this IQueryable<T> queryable, Context context, int userId) {
        var entityName = typeof(T).Name;
        //get all filters for the current entity by userId, Select the needed values as a `List<Filter>()`
        //this could be done out of this method and used as a parameter
        var filters = context.EntityProperties
                      .Where(m => m.entityName == entityName && m.userId = userId)
                      .Include(m => m.PropertyValueList)
                      .Select(m => new Filter {
                          PropertyName = m.property,
                          Values = m.PropertyValueList.Select(x => x.value).ToList()
                      })
                      .ToList();
        //build the expression
        var parameter = Expression.Parameter(typeof(T), "m");
        var member = GetContains( filters.First(), parameter);
        member = filters.Skip(1).Aggregate(member, (current, filter) => Expression.And(current, GetContains(filter, parameter)));
        //the final predicate
        var lambda = Expression.Lambda<Func<T, bool>>(member, new[] { parameter });
        //use Where with the final predicate on your Queryable
        return queryable.Where(lambda);
    }
//this will build the "Contains" part
private static Expression GetContains(Filter filter, Expression expression)
    {
        Expression member = expression;
        member = Expression.Property(member, filter.PropertyName);
        var values = filter.Values.Select(m => Convert.ToInt32(m));
        var containsMethod = typeof(Enumerable).GetMethods().Single(
            method => method.Name == "Contains"
                      && method.IsGenericMethodDefinition
                      && method.GetParameters().Length == 2)
                      .MakeGenericMethod(new[] { typeof(int) });
        member = Expression.Call(containsMethod, Expression.Constant(values), member);
        return member;
    }
}

使用

var orders = from order in Context.Orders
             select order;
var filteredOrders = orders.Filter(Context, 1);//where 1 is a userId

我的答案取决于您是否愿意稍微改变您的访问模型。我在编写的应用程序中也遇到过类似的情况,就我个人而言,我不喜欢必须依靠我的调用代码来根据用户身份验证正确筛选出记录的想法。

我的方法是使用OData服务模式来调用我的实体框架,每个存储库都通过OData独立地公开。

OData(WCFDataService)具有QueryInterceptors,它在进行查询时对数据进行实时过滤。因此,如果您向OData存储库询问上下文。订单(o=>o.Id)您只会看到该用户被允许看到的订单,而没有附加子句。

这里有一个很好的基础链接,但它需要一些工作来管理调用用户并提供您可能需要的过滤。您可以在每个记录级别提供查询拦截器。