实体框架,获取所选项的周围项

4

我想从数据库中获取一个项目,以及在它之前和之后的项目。

var data= repo .OrderBy(a => a.Date)
               .Select((item, index) => new { item, index })
               .Where(itemAndIndex=>itemAndIndex.item.Id == someId)

这是我目前得到的内容。

为了澄清,

假设这是我的表格:

Id     Name        Date
1      SomeText1   01.01.2017
2      SomeText2   03.01.2017
3      SomeText3   02.01.2017
4      SomeText4   04.01.2017
5      SomeText5   05.01.2017

我想进行一个选择查询,查询条件是Id==3,然后按照日期字段对结果进行排序,并获取项目列表。

Id     Name        Date
1      SomeText1   01.01.2017
3      SomeText3   02.01.2017
2      SomeText2   03.01.2017

like this.

Thank you.


你想通过item.Id还是代表行号的索引来排序数据? - Dawid Wekwejt
仍然不清楚规则是什么... - Giorgi Nakeuri
按日期排序, 获取 Id=3 的项目, 获取其周围的项目(在示例表中为 Id=1 和 Id=2)。 - Rudithus
5个回答

1

这里的想法与Harald Coppoolse的回答相同(我认为这是满足您要求的唯一合理方法),但使用最佳(在我看来)的LINQ to Entities SQL查询翻译(假设repo是代表您的表的IQueryable<T>):

var data = repo
    .Where(elem => elem.Id == someId)
    .SelectMany(elem =>
        repo.Where(e => e.Date < elem.Date).OrderByDescending(e => e.Date).Take(1)
        .Concat(new[] { elem })
        .Concat(repo.Where(e => e.Date > elem.Date).OrderBy(e => e.Date).Take(1)))
    .ToList();

0

最简单的方法:

var data = repo.OrderBy(x => x.Date)
    .Select((item, index) => new { item, index });

var idx = data.First(x => x.item.Id == someId);

var result = data
    .Where(x => x.index >= idx.index)
    .Take(3);

1
请查看修改后的问题,这不是我要找的。 - Rudithus
现在我认为这就是他正在寻找的。 - Dawid Wekwejt

0

这不是针对EF的,但稍作修改后也可以使用:

public class Data
{
    public int id;
    public string name;
    public DateTime date;
}

class Program
{
    static void Main(string[] args)
    {
        List<Data> l = new List<Data>
        {
            new Data { id = 1, name = "Name1", date = DateTime.Parse("2017/01/01")},
            new Data { id = 2, name = "Name2", date = DateTime.Parse("2017/01/03")},
            new Data { id = 3, name = "Name3", date = DateTime.Parse("2017/01/02")},
            new Data { id = 4, name = "Name4", date = DateTime.Parse("2017/01/04")},
            new Data { id = 5, name = "Name5", date = DateTime.Parse("2017/01/05")},
        };

        int id = 2;

        var result = l.Where(c => c.id == id)
            .Union(l.Where(c => c.date < l.Where(t => t.id == id).Select(d => d.date).First()).OrderByDescending(c => c.date).Take(1))
            .Union(l.Where(c => c.date > l.Where(t => t.id == id).Select(d => d.date).First()).OrderBy(c => c.date).Take(1)).ToList();
    }
}

这不是我想要的,我希望按日期而不是ID排序。由于数据库中只有一个ID为3的项目,联合将只是将其与自身合并,我认为。 - Rudithus
我只是给你提供了一个实现这个的想法。如果你想按日期排序,将我的代码改为按照你想要的其他列排序难吗? - Giorgi Nakeuri
这是否可以翻译为单个数据库查询? - Rudithus
只要在末尾使用 ToList 方法,它就会转换为单个。 - Giorgi Nakeuri
@Rudithus,你现在可以检查一下吗? - Giorgi Nakeuri
事实上,对于这个查询(问题中的是简化版本),我有许多嵌套的where和orderby条件,这种方法使我的查询过于冗长。我希望有一个更简单的解决方案。 - Rudithus

0

另一种最简单的方法:

var data= repo.OrderBy(c => c.Date)
           .Where(c => c.Id == id || c.Id == id - 1 || c.Id == id + 1)
           .Select(c => new { c.Id, c.Name,c.Date });

0

你忘了提到如果你的某个ID的项目是第一个或最后一个,应该填什么,这种情况下你没有前一个或下一个项目。

如果没有带有someId的项目,你想要什么?一个空序列还是一个带有三个NULL值的序列?

此外,你的日期值唯一吗?如果你有几个日期相同的项目,你想要哪一个作为前一个和下一个项目?

暂且不考虑这些问题,看着你的问题,从你的RepoElements源序列中,你想要选择一个包含3个RepoElements的序列,其中

  • result1 = 从源序列中具有Id = someId的元素。这是您的序列的中间元素。
  • result[0] = 源序列中日期小于 result1.Date 的元素。
  • result[2] = 从源序列中具有 Data > result1.Date 的最小元素
从您的仓库序列中,使用 Where 选择结果1。然后使用 Select 和 Min、Max 找到 result[0] 和 result[2] 并将所有三个结果放入数组中。最后使用 SingleOrDefault 选择具有正确结果1的唯一元素:
var result = repo
    .Where(repoElement => repoElement.Id == someId)
    .Select( result1 => new RepoElement[]
    {
        // [0]: the largest element with date < date of item with Id == someId
        repo.Where(repoElement => repoElement.Date < result1.Date)
            .Max(repoElement => repoElement.Date),
        // [1] the item with Id == someId
        result1,
        // [2] the smallest element date > date of item with Id == someId
        repo.Where(repoElement => repoElement.Date > result1.Date)
            .Min(repoElement => repoElement.Date),
    })
    .SingleOrDefault();

第一个Where使用索引快速完成。Select需要两次遍历:一次查找小于中间元素的最大元素,一次查找大于中间元素的最小元素。但我认为在大多数情况下,这将比排序更快。

请注意,如果没有具有较小/较大日期的元素,则Max的结果将是默认值,在您的情况下为NULL。

如果您的日期不唯一,则会找到没有日期的最大/最小值。

补充

我刚意识到我使用的Max函数不返回具有最大值的元素,而是返回最大值本身。幸运的是,MSDN Enumerable.Max描述了Max函数使用IComparable。

考虑让您的类实现IComparable,按日期排名。

private class RepoElement: IComparable<MyData>
{
    public int Id {get; set;}
    public string Name {get; set;}
    public DateTime Date {get; set;}

    public int CompareTo(RepoElement other)
    {   // this object smaller than other object
        // if this date smaller than other date
        return this.Date.CompareTo(other.Date);
    }
}

var result = repo
    .Where(repoElement => repoElement.Id == someId)
    .Select( result1 => new RepoElement[]
    {
        repo.Where(repoElement => repoElement.Date < result1.Date).Max(),
        repoElement,
        repo.Where(repoElement => repoElement.Date > result1.Date).Min(),
    }

这将使用扩展函数Enumerable.Max(),它返回一个TSource。

虽然这个方法可以工作,但它的缺点是它只适用于IEnumerable,而不适用于IQueryable。它无法在数据库端执行。

如果您的集合非常大,并且您确实需要将其作为IQueryable(在数据库端)执行,请考虑使用Enumerable.Aggregate查找元素[0]和[2]。

Aggregate比较序列的前两个元素,决定哪一个是“最佳”的,并使用此“最佳”元素与第三个项目进行比较,以再次决定哪一个是“最佳”,并使用此元素与第四个项目进行比较,依此类推。最后返回“最佳”。

 var result = repo
    .Where(repoElement => repoElement.Id == someId)
    .Select(repoElement => new RepoElement[]
    {
        // [0]: take only the elements that are smaller,
        // aggregate to find the largest one:
        repo.Where(element => element.Date < repoElement.Date)
            .Aggregate(
            // compare the largest item already found, with the current one
            // and take the largest one as largest
            (largest, next) => next.Date > largest.Date ? next : largest),
        // [1]
        repoElement,
        // [2]: take only the elements that are bigger
        // aggregate to find the smallest one
        repo.Where(element => element.Date > repoElement.Date)
            .Aggregate(
            // compare the smallest item already found, with the current one
            // and take the smallest one as smallest
            (smallest, next) => next.Date < smallest.Date ? next : smallest),
    })
    .SingleOrDefault();

类似这样,但 MaxMin 不返回项,因此应进行重新设计。 - Ivan Stoev

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