2

我有一个web应用程序,显示一些实体的细节,我们称之为Log。该实体通过实体框架4从SQL Server加载。如何通过Entity Framework选择下一个和前一个实体?

我想提供'下一个'和'上一个'链接来双向浏览日志。

日志由两个属性/列下令:

  • Date
  • Time

这两列可能包含null,并没有唯一性保证。如果这两个值都为空,那么为了保证稳定的排序,我通过数据库Id订购,该数据库保证为非空且唯一。

此外,在给定Log之前或之后可能实际上不存在实体。

some其他questions这个地址直接与SQL做这个。我想知道如何使用实体框架来做到这一点,理想情况下只做一次数据库访问,以便为这对Logs(id,标题等)带回几个字段。

+0

如果数据和时间都为空,则不能保证上一个和下一个值实际上是当前记录的上一个和下一个日志记录。 – 2011-03-21 18:44:37

+0

为了保证一个稳定的订单,如果两行中的日期和时间相等(包括它们都为空),我将回退到行ID。我已经更新了这个问题,谢谢。 – 2011-03-21 19:15:01

+0

所以不会像这样的工作? '.OrderBy(x => x.Id).FirstOrDefault(x => x.Id> currentSequenceId)'这就是说给我当前记录的'ID',我会在数据库中搜索更大的第一条记录比你给我的身份证。 – 2012-09-28 04:00:38

回答

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)); 
+0

由于'Concat'可用于'IQueryable',因此这似乎工作正常。有趣。我必须引入一个额外的属性来指出哪一行是哪一行,因为'Take(1)'可能会返回一个空序列,在这种情况下,单行是前一个日志还是下一个日志是不清楚的。 'Concat'似乎是我错过的东西(现在看起来很明显!)谢谢。 – 2011-03-22 06:23:57

1

不支持TakeSkip

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

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

关于身份的说明,因为拉迪斯拉夫指出,在完全相同的排序键(即日期和时间都为空)的条目之间不保证顺序。所以你要做的是添加一个身份列,这是你最不重要的排序列。没有身份的日志表/实体有时会被认为设计不当,因为当Date和Time可能为空时,数据的增长是不可预测的。这会导致页面拆分不良。经验法则是表应该有一个狭窄而唯一的集群主键。身份专栏非常适合这一点。它还将确保插入是快速操作,这是您的日志表将会欣赏的事情。

随着视图的帮助下,你可以把订单和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() 
     } 
     ; 
+0

谢谢约翰。是EF支持Take和Skip。当我不知道我的实体在排序后的查询中是什么行号时,如何使用它们?如果我这样做了,我可以使用'Skip(N-1).Take(3)',但在这种情况下我不能。我有正在查看的项目的“Id”,需要查找其他两个。至于拉迪斯拉夫的观点,我更新了这个问题,指出我使用'Id'列作为第三层排序鉴别器,保证了稳定的排序。 – 2011-03-21 19:20:17

+0

我在这里得到的真正问题是:我怎样才能做到这一点与SQL一次?我有一个使用'FirstOrDefault'的实现,它使用两组过滤和排序的'Log's,但这需要两次到DB的往返。在普通的SQL中,我可以使用'union',但是我不知道如何在使用'FirstOrDefault'时如何在Linq中进行联合,因为该方法是评估IQueryable并终止一个对象的终结器,而不是一个可以结合的集合。 – 2011-03-21 19:22:29

+0

您需要跟踪当前行号,如果您没有它并需要根据id计算当前行号,ROW_NUMBER窗口函数可以执行此操作,但它需要编写一些T-SQL ,如果你真的想用EF做它,我想这取决于产生索引的Select重载是否可以从类似'ROW_NUMBER'函数的项目中得到结果,但我对此表示怀疑。 – 2011-03-21 22:05:02

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(); 
0

把记录选择逻辑到上一个和下一个按钮seperatly。这样你只需要每次点击一次按钮就可以调用数据库。

+0

感谢您的回答。这可能会帮助其他人,但在我的情况下,我想在按钮(实际上是链接)中包含next/prev记录的详细信息。 – 2013-03-14 01:49:54

相关问题