How to preserve connection between One-to-One records after deletion? - ios

The question is a little bit tricky, so I'll try to explain it well.
At first, I have 2 entities, Books and Favorites where they are connected as One-to-One.
Books: bookID, title
Favorites: bookID
When I'm tapping on favoriteButton I execute this code:
let favorite = Favorites(context: context)
favorite.bookID = book.id
favorite.book = book
And when I want to check if the book is favorite, I do:
if book.favorite != nil
Now about the problem:
In some moments I have to delete ALL books and add them again, like resetting Books table and when I delete and add a book, it adds it with new ID(inner CoreData ID, not from the server) and thus I loose my connection between Books and Favorites, even the favorite books are still there and still keep all values.
How can I fix that problem? Can I somehow preserve that relationship between records even after deletion?
In web I would set foreign key as bookID and connect with it. In this case I would not loose that connection, but as CoreData connects via inner ID of records, not objects, I'm loosing my connection and cannot check book.favorite anymore, it returns nil.
I hope I could describe my problem. If not, please ask me

Related

Fetching the latest entity from a relationship in Swift Core Data

Im trying to find the best way to get the latest entity from a relationship with swift and core data.
For example I have a Conversation with many Messages:
Conversation Entity:
id = 1, name = "Test"
Message Entity:
id = 1, body = "Test", conversation = 1
id = 2, body = "Test", conversation = 1
In my SwiftUI View I have this FetchRequest:
#FetchRequest(sortDescriptors: []) var conversations: FetchedResults<Conversation>
What would be the best way to access the relation and get the latest result while looping over the conversations in View like this:
List(conversations) { conversation in
Text(conversation.name ?? "")
Text(conversation.messages.latest.body ?? "") // <--- Trying to do something like this
}
The best way is to store the data you need in the entity you are fetching. So in your case make a latestMessageBody property on the conversation entity. Apple provide derived attributes to automate this.
It's a common mistake to think of Core Data as a relational database, instead just think of it as a way to persist what you see on screen and design your entities based on how you want to fetch and display them. If you are making too many fetches on one screen then its time to redesign the entities.
I.e. you wouldn't want to fetch the latest message for all the converstations you want to show that is too many fetches. And you don't want to load in all the messages for every conversation just to find the latest, that has too high memory overhead. This is why Core Data offers denormalisation via the derived attributes feature.
Another point is #FetchRequest (which is powered by NSFetchedResultsController by the way) only tracks changes to the entity that is currently fetched. So if you reach into relations to show data, that view won't update when the related object changes. You can work around this by passing the object into another View marked with #ObservedObject however that doesn't work for a 1-to-many relation.
There is a neat trick using entity inheritance to allow a NSFetchedResultsController to work on multiple entities however I don't think that is suitable for this use case.

Can't insert same Entity (with different related properties-entity) to another Entity

I have an Entity Product who has a collection of Suppliers (which is also an Entity) and i have a Contract Entity who has a collection of Products. A contract can have multiple products, even 2 same products but with a different Supplier.
When i try to add this second product to my contract (same product but different Supplier this time) EF seems to ignore it and doesn't add it to the Contract.Products collection. No errors but it doens't add it. How can i bypass this behavior or setup my model/logic in a way that i can perform this action?
Code:
// THIS CONTEXT RESIDES IN A USING BLOCK
// THE updateContracts.Products ARE COMING FROM ANOTHER CONTEXT and we are receiving this entity as a parameter
// fetching the contract we are updating from database
Contract contractFromDB = ctx.Contracts.Include(s)(...)Where(p => p.ID == updateContract.ID).FirstOrDefault();
// list to populate the products we have added
List<Product> productsToAddToDBContract = new List<Product>();
foreach (Entity.Product product in updateContract.Products)
{
if (!contractFromDB.Products.Any(prod => prod.ProductName == product.ProductName))
{
// tried detaching it but didn't work
//ctx.Entry(product).State = EntityState.Detached;
productsToAddToDBContract.Add(product);
}
}
foreach (Product product in productsToAddToDBContract)
{
// get these from DB and add those to DB contract products
Product productToAdd = ctx.Products.Include(p=> p.Suppliers).Where(prod=> prod.ProductName == product.ProductName).FirstOrDefault();
if (productToAdd != null)
{
// HERE IS WHERE EF DOESN'T ADD THE SECOND PRODUCT (WHICH IS THE SAME BUT FROM ANOTHER SUPPLIER)
contractFromDB.Products.Add(productToAdd);
}
}
Thank you very much.
EDIT:
I tried to remove the line where i fetched my product from Database
Product productToAdd = ctx.Products.Include(...).Where(...).FirstOrDefault();
and just use the product i already fetched before and passing with the updateContract.Products, i got "The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects."
so i went and attach the product from this iteration:
foreach (Product product in productsToAddToDBContract)
{
// removed this line:
// Entity.Product productToAdd = ctx.Products.Include(p=> p.Suppliers).Where(prod=> prod.ProductName == product.ProductName).FirstOrDefault();
// and added this one
ctx.Products.Attach(product);
contractFromDB.Products.Add(product);
}
then i received '"Attaching an entity of type 'Supplier' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate." Note that the product already has a supplier attached/included to it but from another context (same as the product's context).
This has to regarding the relation of Product and Supplier i think, or maybe because i am using different contexts, i don't know but i will investigate further and post back.
Feel free to contribute in any way of course :)
The issue was my model structure, i can't elaborate on how it was but the related entities relation was not setup correctly, i have created a new entity that holds a ProductId-SupplierId and others, tweaking it till i got what i wanted. The reason why i got these errors (stated above) was because i didn't attach all the related entities when performing actions, when i refered the proper related entities to my product, the errors went away... :)
Hope this helps someone and forgive me for the somewhat unclear solution, i am in the middle of this project and time is an issue, for know the company wants a working solution not a clean one so i didn't not everything down ...
Kind regards!

Don't delete object of destination if it is in another Source in Coredata

I have a many to many relationship where entity are Employee and Department. Everything is going good but when i am trying to learn the relationship delete rules,i couldnot find out the right way.
I want to remove all the employee of the department if Entity Department gets deleted.But not those employee who are in another deparment.
Cascade Delete the objects at the destination of the relationship. For
example, if you delete a department, fire all the employees in that
department at the same time.
But i dont want to remove the employee if they are already in another department.One teacher teaching Swiftmay be in many departments "Computer","Electrical","Civil".How can i acheive that..Tried to use cascade but that removes all the Employees which i have set destination as below:
EDIT: Tried using nullify but deleting Source causes the deletion of all related Destination. However, deleting any single one Source simply causes Destination to forget about that particular Source.I gues,I need something intermediate nullify and cascade?
The following relationships will do what you want I think (I don't have the ability to test the answer here, but don't have rights to just leave a comment so you get the suggestion as an answer)
Employee -> Department Deny (can't fire an employee that is still assigned to a department).
Department -> Employee Cascade (fire all employees you can fire when the department is deleted, ie no longer has a department).
But it seems more reasonable to me set Department -> Employee to Nullify, and then make a separate scan for unassigned employees to fire outside the delete department code. This would also support general maintenance checks for employees that have had all their assignments removed.
In the case of mine i should not set the delete relationship to Cascade.But instead make both delete rule to nullify.And Check as in
class Departments: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
override func prepareForDeletion() {
for teacher in self.teachers!{
if let tempTeach = teacher as? Teachers{
if tempTeach.departments?.count == 1{
self.managedObjectContext?.deleteObject(tempTeach)
}else{
print("this teacher is assigned to another department also so dont delete it")
}
}
}
}
}

updating many users at once in parse

I'd like to update user's column which presents related posts that the user might like,
my code is like that:
let users = query.findObjects() as [PFUser]
for user in users{
let rel = user.relationForKey("posts")
rel.addObject(post, forKey: "relatedPosts")
rel.saveInBackground()
}
I really don't know why, but I tried to do that in many versions (not only by relation, also by arrays and other methods) and it always updates just one user (the one who logged in)..
how can I get this done?
You can't update user that is not currently authenticated in your app.
The way I see it you have 2 options:
1) You can set some Cloud Code to run using the master key, so it can modify users.
2) You can add a new custom column to your Parse User class that will link to another class DB that has the related posts for the user.
take a look here: Parse.com Relations Guide
I would choose #2.

Cannot insert new Employee entity using InsertOnSubmit()

I'm facing this exception An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported. when I try to insert a new entity into my Employees table (the master one).
There is a relationship between the master Employees table and the details Orders table, and I'm sure that the relationship between these two tables (and specifically Employee.Orders EntitySet) is the cause of the problem since when I removed the relationship, it returns back to insert into Employees table with no problems.
When I searched for the problem, there was this blog post which I tried to implement but my case is a different than the one in the blog post in these items:
He faces the exception when tries to update (while I try to insert).
The tables architecture is different.
how can I solve this problem?
Here's the insertion code:
Employee emp = new Employee();
emp.Name = empName; // empName is a local variable
// What should I default emp.Orders to?
dc.Employees.InsertOnSubmit(emp);
dc.SubmitChanges();
P.S: My DataContext is defined on class-level in my repository and the exception is being thrown when I call dc.SubmitChanges();. and I didn't Attach any object why does it say that?
Here is an article explaining what you need to do using the Attach and Detach methods:
http://www.codeproject.com/KB/linq/linq-to-sql-detach.aspx
I am guessing it is trying to save something else besides just the employee object or you aren't showing us the full code in your repository. When you instantiate your DataContext object (dc) try setting DeferredLoadingEnabled = false, and ObjectTrackingEnabled = false and see if it works. If it does, try watching the SQL code in SQL Server Profiler and see if it is modifying other objects that may have came from a different context like the message says.
var dc = new MyDataContext()
{
DeferredLoadingEnabled = false,
ObjectTrackingEnabled = false
};
My bet is on the primary key.
Are you sure the primary key is also set on auto increment?
Did you
try changing the name, does it work then?
What happens if you remove
all rows from your DB? can you insert one then?

Resources