0

我正在构建一个聊天应用程序。每个频道都有许多消息。我正在构建频道列表视图,我想在其中显示按每个频道发送的最新消息排序的所有频道。

每次发送或接收消息时,我都希望保持channel.latestMessageUpdatedAt最新,以便以后对频道进行排序。

我想将关注点分开,而不必记住每次更新消息时都更新频道。

我的策略是将侦听器内的通道更新到消息领域,但出现以下错误

Error: Wrong transactional state (no active transaction, wrong type of transaction, or transaction already in progress)
const ChannelSchema = {
  name: "channel",
  primaryKey: "id",
  properties: {
    id: "string",
    latestMessageUpdatedAt: "date",
  },
};

const MessageSchema = {
  name: "message",
  primaryKey: "id",
  properties: {
    id: "string",
    channelId: "string",
    text: "string",
    updatedAt: "date",
  },
};

const realm = await Realm.open({
  path: "default.realm",
  schema: [ChannelSchema, MessageSchema],
  schemaVersion: 0,
});

const messagesRealm = realm.objects("message");
messagesRealm.addListener((messages, changes) => {
  for (const index of changes.insertions) {
    const channel = realm.objectForPrimaryKey(
      "channel",
      messages[index].channelId
    );
    if (!channel) continue;

    const message = messages[index];
    channel.latestMessageUpdatedAt = new Date();
  }
});

我已经检查了文档,似乎没有理由说明这是不可能的。

也许有一个更好的方法来获得这个计算域。

请注意,我考虑过在通道上嵌入对象/消息列表,但消息的数量可能高达 10k,我不希望所有这些都立即返回到内存中。

我也试过做

realm.write(() => {
    channel.latestMessageUpdatedAt = new Date();
});

但我收到交易已经在进行中的错误。

4

1 回答 1

1

OP 要求使用 Swift 解决方案,所以我有两个:使用哪一个取决于数据集和关系的编码偏好。问题中没有自动反比关系,但想包括在内以防万一。

1 - 没有 LinkingObjects:手动反向关系

让我们将模型设置为从通道到消息的一对多关系,然后从消息返回到它的父通道的单个反向关系

class ChannelClass: Object {
    @Persisted(primaryKey: true) var _id: ObjectId
    @Persisted var channelName = ""
    @Persisted var messageList: List<MessageClass>
}

class MessageClass: Object {
    @Persisted(primaryKey: true) var _id: ObjectId
    @Persisted var msg = ""
    @Persisted var msgDate = ""
    @Persisted var myChannel: ChannelClass!
}

然后在我们填充 Realm 之后,我们有一些看起来像这样的对象 - 请记住,不同的通道会在不同的时间添加消息

channel 0
   message 1
   message 3
   message 4

channel 1
   message 5
   message 9
   message 10

channel 2
   message 2
   message 6
   message 7
   message 8

看起来是这样的,因为:假设用户向通道 0 发布消息,即消息 1。一天后,另一个用户向通道 2 发布消息,即消息 2。然后,在另一天,一个用户向通道 0 发布消息,这将是消息 3。等等

请记住,虽然 Realm 对象未排序,但 List 对象始终保持其顺序。所以每个频道列表中的最后一个元素是该频道中的最新消息。

从那里获取按他们最近的消息排序的频道是一个班轮

let messages = realm.objects(MessageClass.self).sorted(byKeyPath: "msgDate").distinct(by: ["myChannel._id"])

如果您现在遍历消息以打印通道,这里是输出。注意:这仅用于显示已经从 Realm 检索到的数据,并且在应用程序中不需要。

Channel 0 //the first message
Channel 2 //the second message
Channel 1 //the third message? Not quite

然后你对自己说“自己,等一下——第三条消息在通道 0 中!那么为什么将通道 1 输出为最后一项?”

原因是 OP 要求频道只能列出一次 - 因此,由于频道 0 已经列出,剩余的频道是频道 1。

2 - 使用 LinkingObjects:自动反向关系

以 LinkingObjects 用于自动创建从消息对象到通道的反向链接的场景为例,例如反向遍历对象图

class MessageClass: Object {
    @Persisted(primaryKey: true) var _id: ObjectId
    
    @Persisted var msg = ""
    @Persisted var msgDate = ""
    @Persisted(originProperty: "messageList") var linkedChannels: LinkingObjects<ChannelClass>
}

思考过程是相似的,但我们必须稍微依靠 Swift 来提供一种排序。这是一个班轮

let channels = realm.objects(ChannelClass.self).sorted { $0.messageList.last!.msgDate < $1.messageList.last!.msgDate }

这里所做的是查询频道并使用每个频道列表中最后一个消息对象的 msgDate 属性对频道进行排序。并且输出是相同的

Channel 0 //the first message
Channel 2 //the second message
Channel 1 //see above

这里唯一的缺点是这种解决方案会产生更大的内存影响,但通过 LinkingObjects 增加了自动反向关系的便利

3 另一种选择

向 Channel 类添加一个小函数和一个属性的另一个选项,它既可以向通道添加消息messagesList,也可以填充通道的“lastMsgDate”属性。然后对频道进行排序是一件轻而易举的事。所以它看起来像这样

class ChannelClass: Object {
    @Persisted(primaryKey: true) var _id: ObjectId
    
    @Persisted var channelName = ""
    @Persisted var messageList: List<MessageClass>
    @Persisted var lastMsgDate: String
    
    func addMessage(msg: MessageClass) {
        self.messageList.append(msg)
        self.lastMsgDate = msg.msgDate
    }
}

每当向频道添加消息时,都会更新最后一条消息的日期。然后按频道排序lastMsgDate

someChannel.addMessage(msg: someMessage)

注意:为简单起见,我使用字符串作为消息日期。如果您想这样做,请确保它是 yyyymmddhhmmss 格式,或者只使用实际的 Date 属性。

于 2022-02-03T20:32:45.083 回答