如何通过Entity Framework选择下一个和上一个实体?

时间:2011-03-21 18:34:17

标签: .net entity-framework entity-framework-4 linq-to-entities

我有一个显示某个实体详细信息的网络应用,我们称之为Log。该实体是通过Entity Framework 4从SQL Server加载的。

我想提供“下一个”和“上一个”链接,以双向浏览日志。

日志按两个属性/列排序:

  • Date
  • Time

这两个列都可能包含null,并且不保证唯一性。如果这两个值都为null,那么为了保证稳定的排序,我按数据库Id排序,这保证是非空且唯一的。

此外,在给定的Log之前或之后实际上可能没有实体。

some其他questions可以直接使用SQL来解决这个问题。我想知道如何使用Entity Framework执行此操作,理想情况下只需要一次访问数据库就可以为这对Logs(id,title等)带回几个字段。

4 个答案:

答案 0 :(得分:1)

EF不支持TakeSkip吗?

LINQ的美妙之处在于,您可以通过说q.Skip(50).Take(50)来描述这个复杂的排序标准和结果页面。如果每个页面显示50个结果,那么您将获得第二页。它当然被转换为有效的T-SQL,它使用ROW_NUMBER窗口函数指示数据库使用您指定的顺序查找结果。

您甚至可以使用大量过滤器进行非常复杂的查询。最终结果仍然可以管理,因为你要么有行,要么你不会。你需要考虑的是结果可能是空的。

关于身份的说明,正如Ladislav指出的那样,在完全相同的排序键的条目之间不能保证顺序(即日期和时间都为空)。所以你要做的是添加一个标识列,这是你最不重要的排序列。没有标识的日志表/实体可能在某种程度上被认为设计不当,因为当日期和时间可以为空时,数据的增长是不可预测的。这将导致错误的页面拆分。经验法则是表应该具有狭窄且唯一的聚类主键。标识列很适合这个。它还将确保插入操作是您的日志表将会欣赏的快速操作。

在视图的帮助下,您可以将order by和row_number的东西放在普通的T-SQL中,然后使用EF查询,如下所示:

var q = from x in source
        join y in source on x.RowNumber equals y.RowNumber - 1 into prev
        join z in source on x.RowNumber equals z.RowNumber + 1 into next
        from p in prev.DefaultIfEmpty()
        from n in next.DefaultIfEmpty()
        select new { Current = x, Previous = p, Next = n }
        ;

......或可能:

var q = from x in source
        join y in source on x.RowNumber equals y.RowNumber - 1 into prev
        join z in source on x.RowNumber equals z.RowNumber + 1 into next
        select new { 
            Current = x, 
            Previous = prev.DefaultIfEmpty(), 
            Next = next.DefaultIfEmpty() 
        }
        ;

答案 1 :(得分:1)

我不确定这是否有效,但试一试:

var query =(
        from l in context.Logs
        where l.UserId == log.UserId &&
             (    l.Date < log.Date
              || (l.Date == log.Date && l.Time < log.Time)
              || (l.Date == log.Date && l.Time == log.Time && l.Id < log.Id)
             )
        orderby l.Date descending, l.Time descending
        select l
    ).Take(1)
    .Concat((
        from l in context.Logs
        where l.UserId == log.UserId &&
             (    l.Date > log.Date
              || (l.Date == log.Date && l.Time > log.Time)
              || (l.Date == log.Date && l.Time == log.Time && l.Id > log.Id)
             )
        orderby l.Date, l.Time
        select l
    ).Take(1));

答案 2 :(得分:0)

这是我一直在使用的解决方案。理想情况下,我想通过一次数据库调用来做到这一点,所以如果有人能告诉我如何做到这一点,我会接受他们的回答。

// Prev
var previousLog = (
        from l in context.Logs
        where l.UserId == log.UserId &&
             (    l.Date < log.Date
              || (l.Date == log.Date && l.Time < log.Time)
              || (l.Date == log.Date && l.Time == log.Time && l.Id < log.Id)
             )
        orderby l.Date descending, l.Time descending
        select l
    ).FirstOrDefault();

// Next
var nextLog = (
    from l in context.Logs
    where l.UserId == log.UserId &&
             (    l.Date > log.Date
              || (l.Date == log.Date && l.Time > log.Time)
              || (l.Date == log.Date && l.Time == log.Time && l.Id > log.Id)
             )
    orderby l.Date, l.Time
    select l
).FirstOrDefault();

答案 3 :(得分:0)

将记录选择逻辑分别放入prev和next按钮。这样,您只需要为每次按钮单击调用数据库一次。