Unacceptable type of value for to-one relationship: property = "user"; desired type = User; given type = User;
Asked Answered
V

3

13

I'm having a wired problem with core data. With Swift3 in iOS 10 I get the managed object context each time I am fetching or storing data with

 func getContext () -> NSManagedObjectContext {
    let appDelegate = UIApplication.shared.delegate as! AppDelegate
    return appDelegate.persistentContainer.viewContext
}

In my app I have two entities 'User' and 'Ledger'. I want to assign a ledger to a user, but a user can have multiple ledgers. Therefore, I have a UserTableView where I can display the users and a UserViewController class, where I create a user. The same I have for ledger. When creating a ledger I get also a list of all users from which I select one and which should assigned to the ledger and vice versa.

When saving like aforementioned, I get the error

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for to-one relationship: property = "user"; desired type = User; given type = User;

My data model looks as follows: Data Model

Any help is highly appreciated:)

Vladivostok answered 14/11, 2016 at 21:18 Comment(1)
did you resolve this?Louislouisa
P
13

I had this same problem. In my case it was happening while I was running unit tests. In that case, there were two core data stacks in memory at once, one for the application harness and the other for my running unit tests.

The first clue to solving this was to put an assertion right before setting the relationship property to make sure the entity type of the object I was setting was the same as the expected entity type of the relationship. They should be identical, and in my case they were not.

In my case I had a MatchRequest that had a one-to-one relationship with Player called "initiator". So my assertion looked like the following:

    let player = try Player.findLocal(for: matchRequest.initiator, in: moc, createIfMissing: true)
    let expectedEntity = self.entity.relationshipsByName["initiator"]!.destinationEntity!
    assert(player!.entity === expectedEntity, "Player returned doesn't have the same entity type")
    self.initiator = player

The above assertion failed, which I suspect is similar to the assertion used by Core Data that causes the argument exception.

When inspecting Player.entity(), it would return the same entity instance that was causing the failure.

I think the root of the problem is that Core Data is setting some static property for entities that will get incorrectly shared across core data stacks. Calling MyManagedObject.entity() will work correctly when called from one stack but not the other.

So, to work around the problem, when I create my Player object to put into the relationship, I get the entity using the older NSEntityDescription.insertNewObject(...) API, rather than the newer MyManagedObject(context:) constructor. This ensures that the correct entity is used for the given managed object context.

So, to recap:

// SOMETIMES FAILS if you have more than one core data stack:
result = Player(context: managedObjectContext)

// ALWAYS WORKS:
result = NSEntityDescription.insertNewObject(forEntityName: "Player", into: managedObjectContext) as? Player
Photogravure answered 27/7, 2017 at 14:6 Comment(1)
This solved my issue when using multiple object contexts with a document based app. Each document had its own context as created by NSPersistantDocument but when creating objects I would get this exact crash.Morose
S
2

I had the same issue, but i was very sure I didn't have 2 Core Data Stacks like the previous answer did.

In the end i realized i had initialized a reference to NSStoreCoordinator and a view context and background context using the lazy keyword. I also had a bunch of code doing some heavy lifting in the background thread and saving using said stack.

Apple's docs say: "If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property has not yet been initialized, there is no guarantee that the property will be initialized only once."

So, this unfortunate scenario is another way to get to this error. Solution: make sure don't lazy anything in your Core Data Stack specially when doing lots of background/foreground thread work.

I hope it helps someone in the future. To you future developer, if you are here: Good luck.

Slighting answered 29/1, 2019 at 13:17 Comment(0)
F
0

I came across the same issue. I think @Chris has given the reason clearly. There are more than one instance in the memory while doing the testing.

The solution is moving the NSPersistentContainer init part into setUpWithError and tearDownWithError.

In my example below, CoreDataManager take care of the init of NSPersistentContainer.

Here is the code:

Before

class FooTests: XCTestCase {
    var manager: CoreDataManager = CoreDataManager()
}

after

 class FooTests: XCTestCase {

        var manager: CoreDataManager?

        override func setUpWithError() throws {
             manager = CoreDataManager(inMemory: true)
        }

        override func tearDownWithError() throws {
             manager = nil
        }

    }
Fend answered 4/12, 2020 at 1:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.