如何使用实体框架和 MySQL 获取每个组的最新记录,包括相关实体

Posted

技术标签:

【中文标题】如何使用实体框架和 MySQL 获取每个组的最新记录,包括相关实体【英文标题】:How to get latest record for each group using Entity Framework and MySQL including related entity 【发布时间】:2013-10-10 05:08:28 【问题描述】:

我正在使用带有 mysql 数据库和 DbContext 的实体框架。我有一个实体“消息”,它有一个相关的实体“发件人”。 (“消息”也有一个相关的实体“接收者”)。我正在尝试编写一个查询,该查询将只返回每个接收者的“最新”消息。但是当我这样做时,我还想加载关联的“发件人”,以便我可以访问我需要包含在我要返回的数据传输对象中的发件人属性之一(电子邮件字段)。 “MessageDTO”是我返回的数据传输对象,其中包括消息的 ID、消息的内容和发件人的电子邮件。

如果我从 DTO 中排除发件人的电子邮件,则以下查询将准确返回我需要的内容(即每个收件人的最新消息):

var refGroupQuery = (from m in dbContext.Messages.SqlQuery("select * from messages order by created_at desc")
     group m by m.receiver_id into refGroup
     select new MessageDTO  id = refGroup.FirstOrDefault().id, content = refGroup.FirstOrDefault().content);

但是,上述语句不会加载与消息关联的发件人,因此当我在 DTO 中重新包含发件人的电子邮件时,我会收到如下所示的 NullReferenceException:

var refGroupQuery = (from m in dbContext.Messages.SqlQuery("select * from messages order by created_at desc")
     group m by m.receiver_id into refGroup
     select new MessageDTO  id = refGroup.FirstOrDefault().id, content = refGroup.FirstOrDefault().content, sender_email = refGroup.FirstOrDefault().sender.email);

refGroup.FirstOrDefault().sender.email 抛出 NullReferenceException,因为 sender 为 null。

如何在查询中加载发件人,以便在我的 DTO 中包含发件人的电子邮件?

编辑:

根据要求,我包含了由 Gert Arnold 建议的方法生成的 SQL:

SELECT
1 AS `C1`, 
`Apply1`.`id`, 
`Apply1`.`sender_id`, 
`Apply1`.`RECEIVER_ID1` AS `receiver_id`, 
`Apply1`.`created_at`, 
`Apply1`.`read_status`, 
`Extent3`.`email`
FROM (SELECT
`Distinct1`.`receiver_id`, 
(SELECT
`Project2`.`id`
FROM (SELECT
`Extent2`.`id`, 
`Extent2`.`sender_id`, 
`Extent2`.`receiver_id`, 
`Extent2`.`created_at`, 
`Extent2`.`read_status`
FROM `messages` AS `Extent2`
 WHERE (`Extent1`.`receiver_id` = `Extent2`.`receiver_id`) OR ((`Extent1`.`receiver_id` IS  NULL) AND (`Extent2`.`receiver_id` IS  NULL))) AS `Project2` LIMIT 1) AS `id`, 
(SELECT
`Project2`.`sender_id`
FROM (SELECT
`Extent2`.`id`, 
`Extent2`.`sender_id`, 
`Extent2`.`receiver_id`, 
`Extent2`.`content`, 
`Extent2`.`created_at`, 
`Extent2`.`read_status`
FROM `messages` AS `Extent2`
 WHERE (`Extent1`.`receiver_id` = `Extent2`.`receiver_id`) OR ((`Extent1`.`receiver_id` IS  NULL) AND (`Extent2`.`receiver_id` IS  NULL))) AS `Project2` LIMIT 1) AS `sender_id`, 
(SELECT
`Project2`.`receiver_id`
FROM (SELECT
`Extent2`.`id`, 
`Extent2`.`sender_id`, 
`Extent2`.`receiver_id`, 
`Extent2`.`content`, 
`Extent2`.`created_at`,
`Extent2`.`read_status`
FROM `messages` AS `Extent2`
 WHERE (`Extent1`.`receiver_id` = `Extent2`.`receiver_id`) OR ((`Extent1`.`receiver_id` IS  NULL) AND (`Extent2`.`receiver_id` IS  NULL))) AS `Project2` LIMIT 1) AS `RECEIVER_ID1`, 
(SELECT
`Project2`.`receivable_type`
FROM (SELECT
`Extent2`.`id`, 
`Extent2`.`sender_id`, 
`Extent2`.`receiver_id`, 
`Extent2`.`content`, 
`Extent2`.`created_at`,  
`Extent2`.`read_status`
 WHERE (`Extent1`.`receiver_id` = `Extent2`.`receiver_id`) OR ((`Extent1`.`receiver_id` IS  NULL) AND (`Extent2`.`receiver_id` IS  NULL))) AS `Project2` LIMIT 1) AS `content`, 
(SELECT
`Project2`.`created_at`
FROM (SELECT
`Extent2`.`id`, 
`Extent2`.`sender_id`, 
`Extent2`.`receiver_id`,  
`Extent2`.`content`, 
`Extent2`.`created_at`, 
`Extent2`.`read_status`
FROM `messages` AS `Extent2`
 WHERE (`Extent1`.`receiver_id` = `Extent2`.`receiver_id`) OR ((`Extent1`.`receiver_id` IS  NULL) AND (`Extent2`.`receiver_id` IS  NULL))) AS `Project2` LIMIT 1) AS `created_at`, 
(SELECT
`Project2`.`updated_at`
FROM (SELECT
`Extent2`.`id`, 
`Extent2`.`sender_id`, 
`Extent2`.`receiver_id`, 
`Extent2`.`content`, 
`Extent2`.`created_at`, 
`Extent2`.`read_status`
FROM `messages` AS `Extent2`
 WHERE (`Extent1`.`receiver_id` = `Extent2`.`receiver_id`) OR ((`Extent1`.`receiver_id` IS  NULL) AND (`Extent2`.`receiver_id` IS  NULL))) AS `Project2` LIMIT 1) AS `read_status`
FROM (SELECT DISTINCT 
`Extent1`.`receiver_id`
FROM `messages` AS `Extent1`) AS `Distinct1`) AS `Apply1` LEFT OUTER JOIN `users` AS `Extent3` ON `Apply1`.`sender_id` = `Extent3`.`id`

【问题讨论】:

你为什么使用SqlQuery?没有它,整个查询将被转换为 SQL,并且发送者将被加入。 使用 SqlQuery 是我可以在“group by”之前执行“order by”的唯一方法。如果我用“order by”指令替换 SqlQuery,则查询不再返回每个组的最新记录,而只返回每个组的一些记录。我在***.com/questions/5140785/mysql-order-before-group-by 中读到“从每个组中选择值不会受到添加 ORDER BY 子句的影响。结果集的排序发生在选择值之后,并且 ORDER BY 不会影响服务器选择的值” 【参考方案1】:

您不需要SqlQuery 构造在分组之前进行排序:

var refGroupQuery = from m in dbContext.Messages
     group m by m.receiver_id into refGroup
     let firstItem = refGroup.OrderByDescending(x => x.created_at)
                             .FirstOrDefault()
     select new MessageDTO  
                              id = firstItem.id, 
                              content = firstItem.content,
                              sender_email = firstItem.sender.email
                           ;

这个也是一样的,不过是把整个语句翻译成SQL,有两个好处

sender 不会为每条消息延迟加载 当sender 为空时,sender.email 不会崩溃,因为在 SQL 中没有空对象引用。整个表达式 (sender.email) 只返回 null。

【讨论】:

这给出了一个错误“'where 子句'中的未知列'Extent1.receiver_id'” 我实际上完全使用了您提供的方法。我在我的方法中额外做的就是将 refGroupQuery 返回给调用函数。我想知道这是 MySQL 还是 MySQL 适配器中的错误。我找到了bugs.mysql.com/bug.php?id=68513,而且我确实使用的是 6.6.5 版本的适配器。所以看来我需要先升级到新版本... 我的意思是整个 SQL 查询 :) 恐怕很可能有错误,但也可能是映射问题。 MessageReceiver 之间的映射是什么样的?您是明确命名 receiver_id 列还是依赖 EF 的默认命名约定? (我假设你首先在这里工作代码)。 我已将生成的 SQL 查询添加到问题中。实际上,我采用了数据库优先的方法,因为我们有一个现有的数据库,该数据库由 Ruby-on-Rails 提供服务,并且 Web API 功能正在转移到 .NET。我不依赖 EF 的默认命名约定,因为它们在我们的案例中不起作用。 “messages”表有一个“receiver_id”字段,它是“users”表中“id”的外键。所以消息将有一个接收者。 绝对是一个错误。 Extent1 作用于底部的子查询,但在任何地方都被引用。

以上是关于如何使用实体框架和 MySQL 获取每个组的最新记录,包括相关实体的主要内容,如果未能解决你的问题,请参考以下文章

获取每个组的最新 n 条记录

VS2013与MySql建立连接;您的项目引用了最新实体框架;但是,找不到数据链接所需的与版本兼容的实体框架数据库 EF6使用Mysql的技巧

从分组的 MySQL 数据中获取最新日期

Django 获取组的最新外国计数

MySQL获取每个产品组的列值最小的行[重复]

mysql使用sum的列来获取每个组的总金额