简体   繁体   English

LINQ / EF-如何选择数据范围

[英]LINQ/EF - How to select range of data

I have a set of employees (id, name, age) in the employee table. 我在employee表中有一组雇员(id, name, age) I want to select a range of employees which are just next & previous to the given ID. 我想选择给定ID的下一个和上一个雇员。

So eg if employee ids are {1, 4, 5, 6, 8, 10, 25, 26, 40} then given an id to search for of 10 and a range of 2. Then it should select the 2 items which come before and after 10. 因此,例如,如果员工ID为{1, 4, 5, 6, 8, 10, 25, 26, 40} 1、4、5、6、8、10、25、26、40 {1, 4, 5, 6, 8, 10, 25, 26, 40}则给定ID以搜索10和2的范围。然后应选择之前的2个项目之后10

Output should be {6, 8, 10, 25, 26} . 输出应为{6, 8, 10, 25, 26}

I want to make the fewest possible calls to the database (preferably only one call). 我想尽可能少地调用数据库(最好只调用一次)。 I tried writing the LINQ query as follows 我尝试如下编写LINQ查询

The problem is how would I get the value of start index in the following query. 问题是如何在以下查询中获取起始索引的值。

 var v = (from x in employeeList
                 where x.ID == itemToSearch
                 select x).ToList().GetRange(index-2,4);

You can do it like this. 您可以这样做。

int key=10;
int range=2;

var v= employeeList.GetRange((employeeList.FindIndex(x => x == key) - range), (range + range + 1));

this works for me. 这对我有用。

Here is the alternative solution: 这是替代解决方案:

 var itemIndex = employeeList.IndexOf(itemToSearch);
 var result = employeeList.Select((item, index) => {
                                       var diff = Math.Abs(index - itemIndex);
                                       if(diff <= 2) return item;
                                       else return Int32.MinValue;
                                       })
                           .Where(x => x != Int32.MinValue)
                           .ToList();

在此处输入图片说明

var v = (from x in employeeList select x).ToList().GetRange( 
((int)((from d in employeeList select d.ID).ToList().FindIndex(e => e == itemToSearch))) - 2 , 5);

I don't think you can do this in 1 query, because there's no concept of "previous" or "next" record in SQL. 我认为您无法在1个查询中执行此操作,因为SQL中没有“上一个”或“下一个”记录的概念。 It would all depend on how the results are ordered, anyway. 无论如何,这都取决于结果如何排序。

I'm thinking you can: 我想您可以:

  1. Get up to n records before and including the itemToSearch itemToSearch之前(包括其中)获取多达n条记录
  2. Get up to n records after and including the itemToSearch 包含itemToSearch之后最多包含n条记录
  3. Get the union of (1) and (2). 获取(1)和(2)的并集。

Code example: 代码示例:

var before = (from x in employeeList
              where x.ID <= itemToSearch
              select x).Take(2);

var after = (from x in employeeList
             where x.ID >= itemToSearch
             select x).Take(2);

var result = before.Union(after);

You can use following extension method to return range of items around first item matching some predicate: 您可以使用以下扩展方法来返回匹配某些谓词的第一个项目周围的项目范围:

public static IEnumerable<T> GetRange<T>(
    this IEnumerable<T> source, Func<T, bool> predicate, int range)
{
    int itemsToFetch = range * 2 + 1;
    Queue<T> queue = new Queue<T>(range + 1);
    bool itemFound = false;
    int itemsFetched = 0;

    using (var iterator = source.GetEnumerator())
    {
        while (iterator.MoveNext())
        {
            T current = iterator.Current;

            if (itemFound) // if item found, then just yielding all next items
            {
                yield return current; // we don't need queue anymore
                itemsFetched++;
            }
            else
            {
                if (predicate(current))
                {
                    itemFound = true;

                    while (queue.Any()) // yield all content of queue
                        yield return queue.Dequeue();

                    yield return current; // and item which matched predicate
                    itemsToFetch = range;
                }
                else
                {
                    queue.Enqueue(current);

                    if (queue.Count >= range)
                        queue.Dequeue();
                }
            }

            if (itemsFetched == itemsToFetch)
                break;
        }
    }
}

Usage is simple 用法很简单

var result = employeeList.GetRange(e => e.ID == 10, 2);

It uses queue to keep track of last items which was checked. 它使用队列来跟踪最后检查的项目。 When item matching predicate found, all queue content it yielded. 找到项目匹配谓词后,它产生的所有队列内容。 Then we return next range count of items (if there is enough items in source). 然后,我们返回下一个项目的范围计数(如果源中有足够的项目)。

For given ids {1, 4, 5, 6, 8, 10, 25, 26, 40} following data is returned: 对于给定的ID {1、4、5、6、8、10、25、26、40},返回以下数据:

itemToSearch | range | result
---------------------------------------------
    10           2     { 6, 8, 10, 25, 26 }
    40           2     { 25, 26, 40 }
     1           2     { 1, 4, 5 }
    10           0     { 10 }     

You can use this query: 您可以使用以下查询:

var index = 3;
var range = 2;
var query = employeeList
    .Where(c=>c.ID <= index)
    .OrderByDescending(c=>c.ID)
    .Take(range + 1)
    .Union(
             employeeList
               .Where(c=>c.ID >= index)
               .OrderBy(c=>c.ID)
               .Take(range + 1)
         );

In EF this will produce something like this: 在EF中,这将产生以下内容:

SELECT * 
FROM
    (SELECT TOP 2 *
     FROM employee
     WHERE ID <= 3
     ORDER BY ID DESC
     UNION
     SELECT TOP 2 *
     FROM employee
     WHERE ID >= 3
     ORDER BY ID) A

Try this code 试试这个代码

class Program
        {
            static void Main(string[] args)
            {
                int[] a = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
                var b = GetRange(a.AsEnumerable(), 2, 2);
                foreach (var i in b)
                {
                    Console.WriteLine(i);
                }
                Console.ReadLine();
            }

            private static List GetRange(IEnumerable intList, int current, int range)
            {
                List lst = new List();
                int newCurrent = current;
                for (int i = 0; i  intList, int current)
            {
                return intList.SkipWhile(i => !i.Equals(current)).Skip(1).First();
            }

            private static int GetPrevious(IEnumerable intList, int current)
            {
                return intList.TakeWhile(i => !i.Equals(current)).Last();
            }


        }

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

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