线程消息系统数据库架构设计

Sha*_*eKm 39 linq database asp.net database-design entity-framework

我正在努力实现这里所解释的: 创建一个像facebook和gmail这样的线程私人消息系统,但我并不完全理解Joel Brown的答案.任何人都可以解释一下.

这是我的数据库表与样本数据的样子(我假设我为了演示目的正确地填充了它): 在此输入图像描述

  1. 我需要显示一个基于LoginId的线程列表(最新的)在LINQ中查询的样子是什么?(我要问的是在一组消息线程中,在每个线程中给我一条最新消息) - 就像这样在facebook上完成.

  2. 我需要在消息线程(LINQ)中显示所有消息 - >就像它在Facebook上完成的那样,你点击消息,你会看到整个"对话".

请帮忙!谢谢

编辑 - >继续乔尔,这是正确的吗?

在此输入图像描述

乔尔,我有点困惑,你能解释一下(评论/问题用粗体):

这里的想法是,每次用户启动一个全新的线程/消息时,它都会以THREAD表中的新记录开头.然后将用户添加为THREAD_PARTICIPANT,并将消息内容添加到MESSAGE,该MESSAGE指向包含THREAD.从MESSAGE到USER的FK表示消息的作者.

LoginId 1向LoginId2发送消息=>将新记录插入到MessageThread表中.此外,还会在MessageThreadId = 1,LoginId = 1(发件人)的MessageThreadParticipant记录中插入记录.并在Message表中插入一条新记录,MessageId = 1,MessageThreadid = 1,SenderLoginId = 1(正确??)

这是迭代后我所拥有的: 在此输入图像描述

我想我很困惑,因为Loginid 2无法知道他有消息.?? 或者我可能需要在MessageThreadParticipant中插入2条记录?(发送者和接收者) - >这种方式都可以看到整个"对话"?

EDIT2: 乔,我想我能做到这一点:

SELECT
  Message.MessageId, Message.CreateDate, Message.Body, Login.Username, Message.SenderLoginId
, (SELECT MessageReadState.ReadDate 
   FROM MessageReadState 
   WHERE MessageReadState.MessageId = Message.MessageId 
     ) as ReadDate
FROM Message 
    INNER JOIN Login ON Message.SenderLoginId = Login.LoginId
    INNER JOIN MessageThreadParticipant mtp on mtp.MessageThreadId = Message.MessageThreadId 
AND ( Message.MessageId in 
        ( SELECT Max(Message.MessageId)
          FROM MessageThreadParticipant INNER JOIN Message 
            ON MessageThreadParticipant.MessageThreadId = Message.MessageThreadId
          GROUP BY MessageThreadParticipant.MessageThreadId
        )
      )
Where mtp.LoginId = 2
ORDER BY Message.CreateDate DESC;
Run Code Online (Sandbox Code Playgroud)

如果我错了请纠正我:)

Joe*_*own 75

那么你为什么不问?:)

让我试着确定你对你的要求的理解.在我看来,你正在看一个线程列表(不是树)两个人之间的消息.我认为你可能想要允许更多人而不仅仅是两个人.这就像Facebook就像有人发布消息一样,然后任何数量的人都可以阅读它,然后开始添加评论.当您添加注释时,它会将您置于该线程中,并开始获取状态更新和电子邮件,告诉您线程中的活动等等.假设这就是你所追求的,那么我向Big Mike建议的架构并不完全是你想要的.

请考虑以下内容:

架构

这里的想法是,每次用户启动一个全新的线程/消息时,它都会以THREAD表中的新记录开头.然后将用户添加为THREAD_PARTICIPANT,并将消息内容添加到MESSAGE,该MESSAGE指向包含THREAD.从MESSAGE到USER的FK表示消息的作者.

当用户阅读消息时,他们会在MESSAGE_READ_STATE表中获得一个条目,表明他们已明确或隐式地标记了读取的消息,具体取决于您的要求.

当有人对线程中的初始消息发表评论时,会添加第二个MESSAGE,其中FK返回到原始THREAD,并且回复作者(用户)被添加到THREAD_PARTICIPANT表中.因此,消息被一个,两个甚至更多的参与者添加到线程中.

要在任何线程中获取最新消息,只需将MESSAGE中的前1个按创建日期(或身份密钥)降序排序,其中消息FK是感兴趣的线程.

要获取用户的最近更新的主题,得到消息相关顶端1线程降序排序上,其中消息是在该用户是THREAD_PARTICIPANT线程创建日期.

我担心,如果不打破LinqPad,我就永远不能在LINQ中陈述这些东西.如果你无法从上面看到我的偏差,我可以用表定义和一些SQL来充实答案.请在评论中提问.

编辑:澄清要求和实施

澄清要求:最初我正在考虑公开发布的消息,并有机会进行评论,而Shane则更多地使用直接消息功能.在这种情况下,初始接收者需要在开始时包含在THREAD_PARTICIPANT表中.

为了清楚起见,我们在表格中添加几行.这是一个场景,(为纪念加拿大日):用户1 DM用户2询问有关啤酒会议的问题.用户2回答有关在哪里见面和用户1回答的问题.表格看起来像这样:(可能过于简化了)

样本数据第1部分 样本数据第2部分

编辑#2:访问SQL以获取线程中所有消息的列表,具有读取状态...

使用@ OP的模式,此SQL将获取给定线程中的消息列表,并指示给定用户是否已读取每条消息.消息是最近的第一个订单.

SELECT 
  Message.MessageId
, Message.CreateDate
, Message.Body
, Login.Username
, (SELECT MessageReadState.ReadDate 
   FROM MessageReadState 
   WHERE MessageReadState.MessageId = Message.MessageId 
     and MessageReadState.LoginId = 2) as ReadState
FROM (Message INNER JOIN Login ON Message.SenderLoginId = Login.LoginId) 
WHERE (((Message.MessageThreadId)=10))
ORDER BY Message.CreateDate DESC;
Run Code Online (Sandbox Code Playgroud)

请注意,如果将其称之为公平,那就是通过子选择来获取读取状态.这是必要的,因为获取读取状态的部分标准要求使用外部连接无法满足的where子句.因此,您可以使用子选择来确定MessageReadState子表中所需的值(可能缺少).

编辑3:SQL用于获取给定用户的每个最新消息的所有线程...

要获取给定用户参与的所有线程的列表,首先按最新消息排序,仅显示最新消息(每个线程1条消息),那么您将使用与上述消息类似的查询,除了通过FK将消息过滤到感兴趣的线程之外,您可以通过子查询过滤消息,该子查询在感兴趣的用户参与的每个线程中查找最新消息.它将如下所示:

SELECT
  Message.MessageId
, Message.CreateDate
, Message.Body
, Login.Username
, (SELECT MessageReadState.ReadDate 
   FROM MessageReadState 
   WHERE MessageReadState.MessageId = Message.MessageId 
     and MessageReadState.LoginId = 2) AS ReadState
FROM Message INNER JOIN Login ON Message.SenderLoginId = Login.LoginId
WHERE ( Message.MessageId in 
        ( SELECT Max(Message.MessageId)
          FROM MessageThreadParticipant INNER JOIN Message 
            ON MessageThreadParticipant.MessageThreadId = Message.MessageThreadId
          WHERE MessageThreadParticipant.LoginId=2
          GROUP BY MessageThreadParticipant.MessageThreadId
        )
      )
ORDER BY Message.CreateDate DESC;
Run Code Online (Sandbox Code Playgroud)

  • @Spock - OP 的要求没有说明用户删除消息的任何内容,但可以轻松增强模型以适应个人用户选择“删除”消息,同时将消息留在那里供其他用户查看。这可以在线程级别通过删除该用户的“THREAD_PARTICIPANT”记录来完成,或者在消息级别通过向“MESSAGE_READ_STATE”添加代码或标志以指示用户已软删除消息来完成。 (2认同)
  • @user1066133 - 我认为代码首先有其地位,特别是作为原型工具或用于构建小型或利基系统。我个人认为代码优先是对一些不以数据为中心的程序员的让步。根据我的经验,经过深思熟虑(即精心设计)的数据库是任何规模或重要性的应用程序的坚实基础。如果你的项目允许,我会先从模型开始。如果您必须先编写代码,那么您只需确保不会跳过数据设计步骤而将自己陷入非常糟糕的境地。 (2认同)
  • @compguy24 基于缺失信息排除某些内容的常用方法是使用 **sub-select** 和 `WHERE NOT IN ...`。因此,例如,您可以采用基本查询并将其添加到它的 where 子句中`AND WHERE Message.MessageID NOT IN({select 子句提取您正在等待的事物的消息 ID})` (2认同)
  • @DaveHarding - 是的,如果你想区分消息_read_和消息_deleted_,你可以扩展`MESSAGE_READ_STATE`表以包括删除的标志,或者最好包括删除的日期.这将提供软删除功能(以便您可以_undelete_,如果您愿意)并且它允许线程中的一个参与者隐藏某些内容而不会中断其他线程参与者的数据. (2认同)