简体   繁体   English

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

[英]What is the best way to create strongly typed LINQ queries from some given strings, via reflection

I'm using EF5, unit of work, and repository pattern. 我正在使用EF5,工作单元和存储库模式。 I want to define some limitations on accessing data for specified users. 我想为指定用户访问数据定义一些限制。 In database I've designed a table to keep my entity names and their properties which is called EntityProperties, and another table to keep the values of those properties which is called PropertyValues, and each EntityProperty has one or more PropertyValues. 在数据库中,我设计了一个表来保存我的实体名称和它们的属性,称为EntityProperties,并设计了另一个表来保存这些属性的值,称为PropertyValues,每个EntityProperty具有一个或多个PropertyValues。 In business layer when user requests data, if any limitation is defined for him, some conditions should be added to the linq statement. 在业务层中,当用户请求数据时,如果为其定义了任何限制,则应在linq语句中添加一些条件。 What I do is to get the list of entity names and their propeties and values by 'userId', then I add some 'Where' clause to the linq query. 我要做的是通过“ userId”获取实体名称及其属性和值的列表,然后在linq查询中添加一些“ Where”子句。 However, the entity names and their properties are of type "String", thus I should use Reflection to manage them. 但是,实体名称及其属性的类型为“字符串”,因此我应该使用反射来管理它们。 But I don't know this part, and I don't know how to create LINQ where clause from a given set of condition strings. 但是我不知道这部分,也不知道如何从给定的条件字符串集中创建LINQ where子句。 For example, let's suppose that a user requests the list orders, and user id is 5. I first query those access limitation tables, and the result is: 例如,假设用户请求列表订单,用户ID为5。我首先查询那些访问限制表,结果是:

"Order", "Id", "74" “订单”,“编号”,“ 74”

"Order", "Id", "77" “订单”,“编号”,“ 77”

"Order", "Id", "115" “订单”,“编号”,“ 115”

It means that this user should only see these three orders, while in Orders table, we have more orders. 这意味着该用户应该只看到这三个订单,而在“订单”表中,我们有更多的订单。 So, if I want to use a LINQ query to get orders, like: 因此,如果我想使用LINQ查询来获取订单,例如:

var orders = from order in Context.Orders

I need to turn it into something like: 我需要将其变成类似:

var orders = from order in Context.Orders

// where order id should be in 74,77,115 //订单编号应位于74,77,115

However, getting to Order entity and Id property from "Order" and "Id" strings requires reflection. 但是,从“ Order”和“ Id”字符串获取Order实体和Id属性需要反思。 Thus two questions: 因此有两个问题:

What is the best way to get strongly typed from strings? 从字符串获得强类型的最佳方法是什么? Is there a better way for me to do this, with better performance? 我有更好的方法来执行此操作吗?

Ok. 好。 With the comments, we might go for something like that (assuming you have a navigation property in EntityProperties table, which is a collection of PropertyValues , and named PropertyValueList (if you don't have, just do a join instead of using Include ). 有了这些注释,我们可能会采用类似的方法(假设您在EntityProperties表中有一个导航属性,该PropertyValuesPropertyValues的集合,并命名为PropertyValueList (如果没有,则只需进行联接而不是使用Include )。

Here is some sample code, really rustic, working only with Int32 properties , but this might be the start of a solution. 这是一些示例代码,确实很粗糙, 仅与Int32属性一起使用 ,但这可能是解决方案的开始。

You may also look at PredicateBuilder ... 您还可以查看PredicateBuilder ...

Anyway 无论如何

I use an "intermediate class" Filter. 我使用“中间类”过滤器。

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

Then an helper class, which will return an IQueryable<T> , but... filtered 然后是一个帮助程序类,它将返回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;
    }
}

usage 用法

var orders = from order in Context.Orders
             select order;

var filteredOrders = orders.Filter(Context, 1);//where 1 is a userId

My answer depends on whether you are happy to alter your access model slightly. 我的答案取决于您是否愿意稍微更改访问模型。 I've got a similar situation in an application that I have written and personally I don't like the idea of having to rely on my calling code to correctly filter out the records based on the users authentication. 我在编写的应用程序中也遇到过类似情况,而且我个人不喜欢必须依靠我的调用代码来基于用户身份验证正确筛选出记录的想法。

My approach was to use an OData service pattern to call into my Entity Framework, each of the repositories are exposed via OData independently. 我的方法是使用OData服务模式调用我的实体框架,每个存储库都通过OData独立公开。

An OData (WCFDataService) has QueryInterceptors which perform on the fly filtering of your data when a query is made. OData(WCFDataService)具有QueryInterceptor,它们在进行查询时对数据进行动态过滤。 Thus if you asked the OData repository for context.Orders(o => o.Id) you'd only see the orders that that user was permitted to see with no additional clauses. 因此,如果您向OData存储库询问context.Orders(o => o.Id),则只会看到该用户被允许查看的订单,而没有其他子句。

A good link to the basics is found here but it requires some work to manage the calling user and provide the filtering that you may require. 这里可以找到基本的链接,但是需要一些工作来管理主叫用户并提供您可能需要的过滤。 You can provide the query interceptor at every record level. 您可以在每个记录级别提供查询拦截器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM