简体   繁体   English

realm 中的计算属性。 如何从 realm 收集侦听器更新不同的 realm?

[英]Computed property in realm. How to update a different realm from a realm collection listener?

I'm building a chat app.我正在构建一个聊天应用程序。 Each channel has many messages.每个频道都有许多消息。 I am building the channel list view where I want to display all the channels sorted by the most recent message sent at per channel.我正在构建频道列表视图,我想在其中显示按每个频道发送的最新消息排序的所有频道。

Each time a message is sent or received I would like to keep the channel.latestMessageUpdatedAt up to date so that I can sort channels later.每次发送或接收消息时,我都希望使channel.latestMessageUpdatedAt保持最新,以便以后对频道进行排序。

I would like to separate concerns and not have to remember to update channels each time messages are updated.我想将关注点分开,而不必记住每次更新消息时都更新频道。

My strategy is to update the channel inside the listener to the message realm, but I get the following error我的策略是将侦听器内部的通道更新为消息 realm,但出现以下错误

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

I've checked the docs there seems to be no reason why this wouldn't be possible.我已经检查了文档,似乎没有理由说明这是不可能的。

Perhaps there is a better way of having this computed field.也许有一个更好的方法来获得这个计算域。

Note I thought about having embedded objects / a list of messages on the channel but the number of messages could be up to 10k, I don't want that all returned at once into memory.注意我考虑过在通道上嵌入对象/消息列表,但消息的数量可能高达 10k,我不希望所有这些都立即返回到 memory。

I've also tried doing我也试过做

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

but I get the error that a transaction is already in progress.但我收到交易已经在进行中的错误。

The OP requested a Swift solution so I have two: which one is used depends on the data set and coding preference for relationships. OP 请求了 Swift 解决方案,所以我有两个:使用哪个取决于数据集和关系的编码偏好。 There is no automatic inverse relationship in the question but wanted to include that just in case.问题中没有自动反比关系,但想包括在内以防万一。

1 - Without LinkingObjects: a manual inverse relationship 1 - 没有 LinkingObjects:手动反向关系

Let's set up the models with a 1-Many relationship from a channel to messages and then a single inverse relationship from a message back to it's parent channel让我们将模型设置为从通道到消息的一对多关系,然后从消息返回到其父通道的单个反向关系

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

Then after we populate Realm we have some objects that look like this - keeping in mind that different channels will have had messages added at different times然后在我们填充 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

It would look like this because: suppose a user posts a message to channel 0, which would be message 1. Then a day later another user posts a message to to channel 2, which would be message 2. Then, on another day, a user posts a message to channel 0 which would be message 3. Etc etc看起来是这样的,因为:假设用户向通道 0 发布消息,即消息 1。一天后,另一个用户向通道 2 发布消息,即消息 2。然后,在另一天,一个用户向通道 0 发布消息,这将是消息 3。等等

Keeping in mind that while Realm objects are unsorted, List objects always maintain their order.请记住,虽然 Realm 对象未排序,但 List 对象始终保持其顺序。 So the last element in each channels list is the most current message in that channel.所以每个频道列表中的最后一个元素是该频道中的最新消息。

From there getting the channels sorted by their most recent message is a one liner从那里获取按他们最近的消息排序的频道是一个班轮

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

If you now iterate over messages to print the channels, here's the output如果您现在遍历消息以打印通道,这里是 output

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

Then you say to yourself "self, wait a sec - the third message was in channel 0? So why output channel 1 as the last item?"然后你对自己说“自己,等一下——第三条消息在通道 0 中?那么为什么 output 通道 1 是最后一条?”

The reasoning is that the OP has a requirement that channels should only be listed once - therefore since channel 0 was already listed, the remaining channel is channel 1.原因是 OP 要求频道只能列出一次 - 因此,由于频道 0 已经列出,剩余的频道是频道 1。

2 - With LinkingObjects: Automatic inverse relationship 2 - 使用 LinkingObjects:自动反向关系

Take a scenario where LinkingObjects are used to automatically create the backlink from the messages object back to the channel eg reverse transversing the object graph假设使用 LinkingObjects 自动创建从消息 object 到通道的反向链接,例如反向遍历 object 图

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

the thought process is similar but we have to lean on Swift a little to provide a sort.思考过程是相似的,但我们必须稍微依靠 Swift 来提供排序。 Here's the one liner这是一个班轮

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

What were doing here is querying the channels and using the msgDate property from the last message object in each channels list to sort the channels.这里所做的是查询频道并使用每个频道列表中最后一条消息 object 中的 msgDate 属性对频道进行排序。 and the output is the same和 output 是一样的

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

The only downside here is this solution will have larger memory impact but adds the convenience of automatic reverse relationships through LinkingObjects唯一的缺点是此解决方案将产生更大的 memory 影响,但通过 LinkingObjects 增加了自动反向关系的便利性

3 Another option 3 另一种选择

Another options to add a small function and a property to the Channel class that both adds a message to the channels messagesList and also populates the 'lastMsgDate' property of the channel.添加一个小的 function 和一个属性到通道 class 的另一个选项,它们都将消息添加到通道messagesList列表并填充通道的“lastMsgDate”属性。 Then sorting the channels is a snap.然后对频道进行排序是一件轻而易举的事。 So it would look like this所以它看起来像这样

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

When ever a message is added to the channel, the last message date is updated.每当向频道添加消息时,都会更新最后一条消息的日期。 Then sort channels by lastMsgDate然后按lastMsgDate对频道进行排序

someChannel.addMessage(msg: someMessage)

Note: I used Strings for message dates for simplicity.注意:为简单起见,我使用字符串作为消息日期。 If you want to do that ensure it's a yyyymmddhhmmss format, or just use an actual Date property.如果您想这样做,请确保它是 yyyymmddhhmmss 格式,或者只使用实际的 Date 属性。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM