将IQueryables的“列表”应用于目标?

时间:2018-04-25 08:42:09

标签: c# entity-framework iqueryable

我有这个想法来创建一个执行不同操作的IQueryables的“列表”。

基本上是这样的:

var query1 = Enumerable.Empty<Person>().AsQueryable().Where(e => e.Name == "Ronald");
var query2 = Enumerable.Empty<Person>().AsQueryable().Where(e => e.Age == 43);
var query3 = Enumerable.Empty<Person>().AsQueryable().Select(e => e.EyeColor);

var listOfQueries = new List<IQueryable<Person>
{
    query1,
    query2,
    query3
};

现在,我也有这个DbSet充满了“人”,我想“应用”我对DbSet的所有查询。我该怎么办?它甚至可能吗?

更新的例子:

        var personQueryFactory = new PersonQueryFactory();
        var personQueryByFirstname = personQueryFactory.CreateQueryByFirstname("Ronald");           //Query by Firstname.
        var personQueryByAge = personQueryFactory.CreateQueryByAge(42);                             //Query by age.
        var personQueryByHasChildWithAgeOver = personQueryFactory.CreateQueryByChildAgeOver(25);    //Query using a "join" to the child-relationship.
        var personQuerySkip = personQueryFactory.Take(5);                                           //Only get the 5 first matching the queries.

        var personQuery = personQueryFactory.AggregateQueries                                       //Aggragate all the queries into one single query.
        (
            personQueryByFirstname,
            personQueryByAge,
            personQueryByHasChildWithAgeOver,
            personQuerySkip
        );

        var personSurnames = personsService.Query(personQuery, e => new { Surname = e.Surname });          //Get only the surname of the first 5 persons with a firstname of "Ronald" with the age 42 and that has a child thats over 25 years old.
        var personDomainObjects = personsService.Query<DomainPerson>(personQuery);          //Get the first 5 persons as a domain-object (mapping behind the "scenes") with a firstname of "Ronald" with the age 42 and that has a child thats over 25 years old.
        var personDaos = personsService.Query(personQuery);          //Get the first 5 persons as a DAO-objects/entityframework-entities with a firstname of "Ronald" with the age 42 and that has a child thats over 25 years old.

这样做的原因是创建一种更“统一”的方式来创建和重用预定义查询,然后能够针对DbSet执行它们并将结果作为域对象而不是“实体框架模型/对象”

2 个答案:

答案 0 :(得分:4)

这是可能的,但您需要稍微重新构建您的方法。

存储过滤器

IQueryable

阅读您的意图,您实际上并不想处理Where个对象,而是您提供给Select()方法的参数。

编辑:我错过了第三个是Where(),而不是Where()。我调整了答案的其余部分,好像这也是Select()。请参阅以下评论,了解我为何可以轻松地将Where()Func<Person,bool> filter1 = (e => e.Name == "Ronald"); Func<Person,bool> filter2 = (e => e.Age == 43); Func<Person,bool> filter3 = (e => e.EyeColor == "Blue"); 轻轻混合。

IQueryable

这会存储相同的信息(过滤条件),但它不会将每个过滤器包装在自己的Func<A,B>中。

  

简短说明

     

注意A符号。在这种情况下,Person是输入类型(B),bool是输出类型(Func<string,Person,bool>)。

     

这可以进一步扩展。 string有两个输入参数(Personbool)和一个输出参数(Func<string, Person, bool> filter = (inputString, inputPerson) => inputString == "TEST" && inputPerson.Age > 35;)。用法示例:

     

Func<Person,bool>

     

始终有一个输出参数(最后一种类型)。每个其他提到的类型都是输入参数

将过滤器放在列表中

直观地说,List<Func<Person,bool>>表示单个过滤器;您可以使用List<T>来表示过滤器列表。

嵌套泛型类型有点难以阅读,但它确实像其他任何List<Func<Person,bool>> listOfFilters = new List<Func<Person,bool>>() { (e => e.Name == "Ronald"), (e => e.Age == 43), (e => e.EyeColor == "Blue") }; 一样工作。

var myFilteredData = myContext.Set<Person>()
                                 .Where(filter1)
                                 .Where(filter2)
                                 .Where(filter3)
                                 .ToList();

执行过滤器

你很幸运。因为您想要应用所有过滤器(逻辑AND),所以可以通过堆叠它们来轻松完成:

List<Func<Person,bool>>

或者,如果您正在使用var myFilteredData = myContext.Set<Person>().AsQueryable(); foreach(var filter in listOfFilters) { myFilteredData = myFilteredData.Where(filter); }

Func<Person, bool> filterCombined = 
              e => filter1(e) || filter2(e) || filter3(e);

var myFilteredData = myContext.Set<Person>()
                                 .Where(filterCombined)
                                 .ToList();

边缘案例

但是,如果您试图查找适合一个或多个过滤器的所有项目(逻辑OR),则会变得稍微困难​​。

The full answer is quite complicated.。你可以在这里查看。

但是,假设您已在已知变量中设置了过滤器there is a simpler method

def get_lap(cmap):
    return scipy.sparse.csc_matrix(csgraph.laplacian(cmap, normed=False))

def fromcmaptocoords(C): 
    L = get_lap(C)
    eigw, eigv = LA.eigh(L.todense())
    vecs = eigv[:,1:3]
    guess_coords = pd.DataFrame(data=vecs, columns=["x", "y"])
    return guess_coords

答案 1 :(得分:1)

其中一个问题是IQueryable的集合只有在您的DbSet有效时才有效。一旦您的DbContext被处置,您精心填充的集合就毫无价值。

因此,您必须考虑重建查询的另一种方法,而不是使用DbSet<Person>的方法

虽然乍一看它们看起来是一样的,但IEnumerableIQueryable之间存在差异。 Enumerable中包含所有内容以枚举结果序列。

另一方面,Queryable包含Expression和Provider。提供者知道可以获取数据的位置。这通常是一个数据库,但它也可以是CSV文件或其他可以获取序列的项目。提供程序的任务是解释Expression并将其转换为数据库可以理解的格式,通常是SQL。

在将linq语句连接成一个大的linq语句时,不访问数据库。只有表达式被更改。

一旦调用GetEnumerator()和MoveNext()(通常通过执行ForEach,ToList()或类似),Expression将被发送给提供者,提供者将其转换为数据库理解并执行的查询格式。查询。查询的结果是一个Enumerable序列,因此调用提供者的查询结果的Getenumerator()和MoveNext()。

因为您的IQueryable持有此提供者,所以在提供者被处置后您不能再枚举。

使用实体框架时,DbSet包含Provider。在Provider中是DbContext持有的数据库信息。一旦你处理了DbContext,就不能再使用IQueryable了:

IQueryable<Person> query = null;
using (var dbContext = new MyDbcontext())
{
     query = dbContext.Persons.Where(person => person.Age > 20);
}
foreach (var person in query)
{
      // expect exception: the DbContext is already Disposed
}

因此,您无法将提供者放入您的收藏或可能的查询中。但是,你可以记住表达式。您对Expression的唯一要求是它返回一个Person。你还需要一个带有这个Expression的函数和一个Personons的QueryaProvider来将它转换为IQueryable。

让我们为此创建一个通用函数,因此它可以用于任何类型,而不仅仅用于人:

static IQueryable<TSource> ToQueryable<TSource>(this IQueryProvider provider, 
    Expression expression)
{
     return provider.CreateQuery(expression);
}

//好吧,我们还添加以下内容:

static IQueryable<Tsource> ToQueryable<TSource>(this DbContext dbContext, 
     Expression expression)
{
     return dbContext.Set<TSource>.Provider.ToQueryable<TSource>(expression);
}

有关扩展功能的帮助,请参阅Extension Functions Demystified

现在只有在创建表达式集合时才会这样。对于快速查找,请将其设为字典:

enum PersonQuery
{
    ByFirstname,
    ByAge,
    ByHasChildWithAgeOver,
    Skip,
}

public IReadOnlyDictionary<PersonQuery, Expression> CreateExpressions()
{
    Dictionary<PersonQuery, Expression> dict = new Dictionary<PersonQuery, Expression>();
    using (var dbContext = new MyDbContext())
    {
         IQueryable<Person> queryByFirstName = dbContext.Persons
             .Where(...);
         dict.Add(PersonQuery.ByfirstName, queryByFirstName.Expression);
         ... // etc for the other queries
    }
    return dict.
}

用法:

IReadOnlyCollection<Person> PerformQuery(PersonQuery queryId)
{
     using (var dbContext = new MyDbContext())
     {
          // get the Expression from the dictionary:
          var expression = this.QueryDictionary[queryId];
          // translate to IQueryable:
          var query = dbContext.ToQueryable<Person>(expression);
          // perform the query:
          return query.ToList();

          // because all items are fetched by now, the DbContext may be Disposed
     }
}