如何通过反射从给定的字符串创建强类型LINQ查询的最佳方法?

8
我正在使用EF5、工作单元和仓储模式。我希望为指定的用户定义一些访问数据的限制。在数据库中,我设计了一个名为EntityProperties的表来保存实体名称及其属性,另一个名为PropertyValues的表来保存这些属性的值,每个EntityProperty都有一个或多个PropertyValues。
在业务层中,当用户请求数据时,如果为他定义了任何限制,则应向linq语句添加一些条件。我所做的是通过“userId”获取实体名称及其属性和值的列表,然后将一些“Where”子句添加到linq查询中。
但是,实体名称和它们的属性都是“String”类型,因此我应该使用反射来管理它们。但我不知道这部分,也不知道如何从给定的一组条件字符串创建LINQ where子句。
例如,假设一个用户请求订单列表,用户ID为5。我首先查询那些访问限制表,结果是:
"Order","Id","74"
"Order","Id","77"
"Order","Id","115"
这意味着这个用户只能看到这三个订单,而在Orders表中,我们有更多的订单。因此,如果我想使用LINQ查询来获取订单,就像:
var orders = from order in Context.Orders

我需要将它转换成类似于以下内容:

var orders = from order in Context.Orders

// 订单ID应该在74、77、115之间

然而,从“Order”和“Id”字符串获取订单实体和ID属性需要使用反射。因此有两个问题:

最佳方法是如何从字符串中获得强类型? 有没有更好的方法可以提高性能?


在基础中,你会有什么限制?你展示了一个Contains在一个int字段上。但是你会有其他类型的限制吗? - Raphaël Althaus
任何属性都有限制,例如:ID为5的用户只允许访问价格等于“1200”的订单。 - Mehrdad Bahrainy
好的,那么这个在你的限制表中要如何表示? - Raphaël Althaus
只访问其价格等于“1500”和“1200”的订单,位于“EntityProperties”表中的以下记录: 记录1 { Id: 1 , userId: 5 , entityName: "Order" , property: "Price" } 位于“PropertyValues”表中的以下记录: 记录1 { Id: 1 , EntityPropertiesId: 1 , value: "1500" } 记录2 { Id: 2 , EntityPropertiesId: 1 , value: "1200" } - Mehrdad Bahrainy
好的,但是你只有“相等性”比较吗?没有像“用户5可以访问名称以'a'开头的产品”或“用户5可以访问价格小于1500的订单”这样的情况吗? - Raphaël Althaus
是的,你说得对,在我的项目中,“相等性”就足够了。作为第一步,这是可以的,然后我会扩展它。 - Mehrdad Bahrainy
2个回答

2
好的。根据评论,我们可以做出类似以下的东西(假设您在 EntityProperties 表中有一个导航属性,它是 PropertyValues 的集合,并命名为 PropertyValueList (如果没有,请改用连接而不是使用 Include )。
这里是一些示例代码,非常简陋,仅适用于Int32属性,但这可能是解决方案的开端。
您也可以查看PredicateBuilder...
无论如何
我使用了一个“中间类”Filter。
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

0

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

我的方法是使用OData服务模式调用我的Entity Framework,每个存储库都通过OData独立公开。

OData(WCFDataService)具有QueryInterceptors,当进行查询时,可以实时过滤数据。因此,如果您要求OData存储库提供context.Orders(o => o.Id),则只会看到该用户被允许查看的订单,没有其他子句。

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


我刚刚重新阅读了你的问题,关于字符串的问题还没有得到解决。虽然我会保留这个答案作为基于用户执行过滤的建议。 - The Senator

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接