8

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

“订单”、“身份证”、“74”

“订单”、“身份证”、“77”

“订单”、“身份证”、“115”

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

var orders = from order in Context.Orders

我需要把它变成类似的东西:

var orders = from order in Context.Orders

// 订单 id 应该在 74,77,115 中

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

从字符串强类型化的最佳方法是什么?我有没有更好的方法来做到这一点,性能更好?

4

2 回答 2

2

行。有了评论,我们可能会去做类似的事情(假设您在EntityPropertiestable 中有一个导航属性,它是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
于 2013-07-04T12:45:36.687 回答
0

我的回答取决于你是否愿意稍微改变你的访问模式。我在我编写的应用程序中遇到了类似的情况,我个人不喜欢必须依靠我的调用代码来根据用户身份验证正确过滤掉记录的想法。

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

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

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

于 2013-07-04T08:22:57.463 回答