How to get PersistentIdentifier from a model created in a transaction?

I have a ModelActor that creates a hierarchy of models and returns a PersistentIdentifier for the root. I'd like to do that in a transaction, but I don't know of a good method of getting that identifier if the models are created in a transaction.

For instance, an overly simple example:

func createItem(timestamp: Date) throws -> PersistentIdentifier {
    try modelContext.transaction {
        let item = Item(timestamp: timestamp)
        modelContext.insert(item)
     }

     // how to return item.persistentModelID?
}

I can't return the item.persistentModelID from the transaction closure and even if I could, it will be a temporary ID until after the transaction is executed.

I can't create the Item outside the transaction and just have the transaction do an insert because swift will raise a data race error if you then try to return item.persistentModelID.

Is there any way to do this besides a modelContext.fetch* with separate unique identifiers?

Answered by DTS Engineer in 854968022

Thank you @Aloisius for your following up. I'd like to confirm that you are right, and I was wrong.

SwiftData does use a temporary identifier for a model that is not persisted yet. The identifier is converted to a permanent one when the model is saved. In the case of using transaction(block:), the transaction takes care the saving, and so the identifier does change when the scope goes outside the transaction block.

This behavior originates in Core Data (SwiftData DefaultStore is based on Core Data). Core Data provides a way to obtain a permanent ID for a managed object (obtainPermanentIDs(for:)), while SwiftData doesn't, and so I was in an impression that persistentModelID had eliminated the difference – That is actually wrong. I've removed my previous reply to avoid confusing people. Sorry for that.

Saving a model makes the model's persistentModelID permanent. Assuming that I need to set up an object graph in a transaction, I'd probably consider the following:

  1. Create the root item (named rootItem, for example) and save it so I get a permanent persistentModelID before doing the transaction.

  2. Do the transaction. In the transaction block, use rootItem.persistentModelID to grab the root item, and set up the whole object graph.

  3. If the transaction throws, roll back by deleting the root item. Otherwise, return rootItem.persistentModelID.

I hope this is a reasonable solution for your case.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

item.persistentModelID should be valid, even before item is persisted to the store.

I must be missing something. It appears to return a temporary Identifier that can't be fetched.

@ModelActor actor DataActor {
    func createItem() throws {
        var newIdentifier: PersistentIdentifier? = nil
        try modelContext.transaction {
            let item = Item(timestamp: Date())
            modelContext.insert(item)
            newIdentifier = item.persistentModelID
            print("Created Item: \(item.timestamp)")
        }

        print("New identifier: \(String(describing: newIdentifier!))")
        if let model: Item = try existingModel(for: newIdentifier!) {
            print("Found model with \(model.timestamp)")
        } else {
            print("Failed to load model")
        }
    }

    func existingModel<T: PersistentModel>(for persistentIdentifier: PersistentIdentifier) throws -> T? {
        if let model: T = modelContext.registeredModel(for: persistentIdentifier) {
            return model
        }

        var fetchDescriptor = FetchDescriptor<T>(predicate: #Predicate { $0.persistentModelID == persistentIdentifier })
        fetchDescriptor.fetchLimit = 1

        return try modelContext.fetch(fetchDescriptor).first
    }
}

Output:

Created Item: 2025-08-20 18:33:55 +0000
New identifier: PersistentIdentifier(id: SwiftData.PersistentIdentifier.ID(backing: SwiftData.PersistentIdentifier.PersistentIdentifierBacking.temporaryIdentifier(SwiftData.TemporaryPersistentIdentifierImplementation)))
Failed to load model
Accepted Answer

Thank you @Aloisius for your following up. I'd like to confirm that you are right, and I was wrong.

SwiftData does use a temporary identifier for a model that is not persisted yet. The identifier is converted to a permanent one when the model is saved. In the case of using transaction(block:), the transaction takes care the saving, and so the identifier does change when the scope goes outside the transaction block.

This behavior originates in Core Data (SwiftData DefaultStore is based on Core Data). Core Data provides a way to obtain a permanent ID for a managed object (obtainPermanentIDs(for:)), while SwiftData doesn't, and so I was in an impression that persistentModelID had eliminated the difference – That is actually wrong. I've removed my previous reply to avoid confusing people. Sorry for that.

Saving a model makes the model's persistentModelID permanent. Assuming that I need to set up an object graph in a transaction, I'd probably consider the following:

  1. Create the root item (named rootItem, for example) and save it so I get a permanent persistentModelID before doing the transaction.

  2. Do the transaction. In the transaction block, use rootItem.persistentModelID to grab the root item, and set up the whole object graph.

  3. If the transaction throws, roll back by deleting the root item. Otherwise, return rootItem.persistentModelID.

I hope this is a reasonable solution for your case.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

How to get PersistentIdentifier from a model created in a transaction?
 
 
Q