如何通过实体框架选择下一个和上一个实体?

发布于 2024-10-24 02:27:51 字数 676 浏览 1 评论 0原文

我有一个 Web 应用程序,可以显示某个实体的详细信息,我们将其称为 Log。该实体是通过 Entity Framework 4 从 SQL Server 加载的。

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

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

  • Date
  • Time

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

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

还有一些其他问题直接使用 SQL 执行此操作。我想知道如何使用实体框架执行此操作,理想情况下只需访问数据库一次即可带回这对日志的一些字段(id、标题等)。

I have a web app that displays the details of some entity, let's call it Log. The entity is loaded from SQL Server via Entity Framework 4.

I would like to provide 'next' and 'previous' links to walk through logs bidirectionally.

Logs are ordered by two properties/columns:

  • Date
  • Time

Both of these columns may contain null, and there is no guarantee of uniqueness. If both of these values are null, then in order to guarantee a stable sort I order by the database Id, which is guaranteed to be non-null and unique.

Furthermore there may not actually be an entity before or after a given Log.

There are some other questions that address doing this with SQL directly. I'd like to know how to do this with the Entity Framework, ideally making only a single trip to the DB to bring back a few fields for this pair of Logs (id, title, etc).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

记忆里有你的影子 2024-10-31 02:27:52

EF 不支持 TakeSkip 吗?

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

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

关于身份的注释,正如拉迪斯拉夫指出的那样,不能保证完全相同的排序键的条目之间的顺序(即日期和时间均为空)。因此,您要做的就是添加一个标识列,这是最不重要的排序列。没有标识的日志表/实体可能会被认为设计不当,因为当日期和时间可以为空时,数据的增长是不可预测的。这将导致糟糕的页面分割。经验法则是表应该有一个狭窄且唯一的集群主键。身份列非常适合这一点。它还将确保插入是快速操作,这是您的日志表所欣赏的。

借助视图,您可以将 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() 
        }
        ;

Doesn't EF support Take and Skip?

The beauty of LINQ was that you could describe this complex sort criteria and the just page the result by saying q.Skip(50).Take(50). That would take you the second page if each page displayed 50 results. And it's of course translated to effective T-SQL that uses the ROW_NUMBER window function to instruct the database to seek out the result using the order you specified.

You can even have a very complex query with lots of filters. The end result is still manageable as you'll either have rows or you won't. All you need to think about is that the result could be empty.

A note about identity, as Ladislav pointed out the order is not guaranteed between entries of the exact same sort key (i.e. Date and Time both being null). So what you do is that you add an identity column which is your least important sort column. A log table/entity that doesn't have an identity can be somewhat argued to be improperly designed because the growth of the data is unpredictable when Date and Time can be null. It will result in bad page splits. The rule of thumb is that a table should have a narrow and unique clustering primary key. The identity column fits this quite nicely. It will also ensure that inserts are fast operations something your log table will appreciate.

With the help of a view you can put the order by and row_number stuff in plain T-SQL then query that using EF like this:

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 }
        ;

...or possibly:

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() 
        }
        ;
吹泡泡o 2024-10-31 02:27:52

这是我一直在使用的解决方案。理想情况下,我想通过一次数据库调用来完成此操作,因此如果有人可以向我展示如何做到这一点,我会接受他们的答案。

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

Here is the solution I have been running with. Ideally I'd like to do this with a single DB call, so if anyone can show me how to do that I'll accept their answer.

// 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();
我很坚强 2024-10-31 02:27:52

将记录选择逻辑分别放入上一个和下一个按钮中。这样,每次单击按钮时,您只需调用数据库一次。

Put the record selection logic into the prev and next buttons seperatly. This way you only need to call the database one time for each button click.

夜深人未静 2024-10-31 02:27:51

我不确定这是否有效,但让我们尝试一下:

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

I'm not sure if this works but let get it a try:

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));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文