I am finishing up an app utilizing a local Realm database with three models. When I delete an object from the main master model, the connected objects in the child models are supposed to all delete, but only one of the objects in each model deletes. See the attache diagram.
Not sure where to go next. Any help will be greatly appreciated!
Thanks!
Blessings,
—Mark
This is the normal behavior of realm js.
When you delete one element of the master model, the child element related is still present on the database.
The only solution is to get all child element and delete them first.
For example if you have a Master model named "Order" and a child model named "customer" :
/** if you use realm-js **/
const order = realm.objects('Order').filtered(`id = 1`)[0]
realm.write(() => {
realm.delete(order.customer)
realm.delete(order)
})
also you can find related related post with the same question
here or
here
Anyone else with this question, knowing the child databases must be deleted first, the solution became self-evident. Thanks again for pointing this out!.
//get the object at indexPath.row
let a = self.master![indexPath.row]
let id = a.Id //master's record ID
let n = realm.objects(Child1.self).filter("Id = %#", id)
let r = realm.objects(Child2.self).filter("Id = %#", id)
//Delete object at indexPath.row
try! realm.write {
realm.delete(n)
realm.delete(r)
//delete last
realm.delete(a)
}
Blessings,
—Mark
Related
I have two entities in my CoreDataModel that should be linked together with a too-many relationship on one wary and to-one on the other way.
These are the CityEntity and the WeatherEntity.
The CityEntity can have multiple WeatherEntity, but not the opposite.
I have two main errors that I would like to solve.
When saving the data, the NSManagedObjectContext save as many CityEntity as I have WeatherEntity, which is wrong as I am expecting only one CityEntity.
When saving WeatherEntity, the relationship with the CityEntity is not created.
This is the code I am using to save the data.
func saveForecast() {
let cityModel = CityModel(
name: "Paris",
weather: [WeatherModel(temp: 12.65), WeatherModel(temp: 12.43)]
)
let cityEntity = CityEntity(context: persistentContainer.viewContext)
cityEntity.name = await cityModel.name
for weather in await cityModel.weathers {
let weatherEntity = WeatherEntity(context: persistentContainer.viewContext)
weatherEntity.temp = weather.temp
weatherEntity.city = cityEntity
}
do { try persistentContainer.viewContext.save() }
catch { print(error.localizedDescription) }
}
These are the result that I get saved in my CoreDataModel, which is wrong as I have 3 CityEntity instead of one, and no relationships between my WeatherEntity and the expected CityEntity.
You can see that there is not CityEntity saved in place of the name when looking at the `WeatherEntity.
This is the way I set the relationship in the CoreDataModel:
It seems that you (accidentally?) set the parent entity of WeatherEntity to CityEntity. That makes WeatherEntity a “sub-entity” of CityEntity, and the corresponding WeatherEntity class a subclass of CityEntity.
Then every instance of WeatherEntity is also an instance of CityEntity, and that explains why there are three instances of CityEntity in total after running your code.
Resetting the parent entity to “No Parent Entity” in the Core Data model inspector, re-generating the managed object subclasses, and cleaning all application data should solve the problem.
I want to use EF DbContext/POCO entities in a detached manner, i.e. retrieve a hierarchy of entities from my business tier, make some changes, then send the entire hierarchy back to the business tier to persist back to the database. Each BLL call uses a different instance of the DbContext. To test this I wrote some code to simulate such an environment.
First I retrieve a Customer plus related Orders and OrderLines:-
Customer customer;
using (var context = new TestContext())
{
customer = context.Customers.Include("Orders.OrderLines").SingleOrDefault(o => o.Id == 1);
}
Next I add a new Order with two OrderLines:-
var newOrder = new Order { OrderDate = DateTime.Now, OrderDescription = "Test" };
newOrder.OrderLines.Add(new OrderLine { ProductName = "foo", Order = newOrder, OrderId = newOrder.Id });
newOrder.OrderLines.Add(new OrderLine { ProductName = "bar", Order = newOrder, OrderId = newOrder.Id });
customer.Orders.Add(newOrder);
newOrder.Customer = customer;
newOrder.CustomerId = customer.Id;
Finally I persist the changes (using a new context):-
using (var context = new TestContext())
{
context.Customers.Attach(customer);
context.SaveChanges();
}
I realise this last part is incomplete, as no doubt I'll need to change the state of the new entities before calling SaveChanges(). Do I Add or Attach the customer? Which entities states will I have to change?
Before I can get to this stage, running the above code throws an Exception:
An object with the same key already exists in the ObjectStateManager.
It seems to stem from not explicitly setting the ID of the two OrderLine entities, so both default to 0. I thought it was fine to do this as EF would handle things automatically. Am I doing something wrong?
Also, working in this "detached" manner, there seems to be an lot of work required to set up the relationships - I have to add the new order entity to the customer.Orders collection, set the new order's Customer property, and its CustomerId property. Is this the correct approach or is there a simpler way?
Would I be better off looking at self-tracking entities? I'd read somewhere that they are being deprecated, or at least being discouraged in favour of POCOs.
You basically have 2 options:
A) Optimistic.
You can proceed pretty close to the way you're proceeding now, and just attach everything as Modified and hope. The code you're looking for instead of .Attach() is:
context.Entry(customer).State = EntityState.Modified;
Definitely not intuitive. This weird looking call attaches the detached (or newly constructed by you) object, as Modified. Source: http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx
If you're unsure whether an object has been added or modified you can use the last segment's example:
context.Entry(customer).State = customer.Id == 0 ?
EntityState.Added :
EntityState.Modified;
You need to take these actions on all of the objects being added/modified, so if this object is complex and has other objects that need to be updated in the DB via FK relationships, you need to set their EntityState as well.
Depending on your scenario you can make these kinds of don't-care writes cheaper by using a different Context variation:
public class MyDb : DbContext
{
. . .
public static MyDb CheapWrites()
{
var db = new MyDb();
db.Configuration.AutoDetectChangesEnabled = false;
db.Configuration.ValidateOnSaveEnabled = false;
return db;
}
}
using(var db = MyDb.CheapWrites())
{
db.Entry(customer).State = customer.Id == 0 ?
EntityState.Added :
EntityState.Modified;
db.SaveChanges();
}
You're basically just disabling some extra calls EF makes on your behalf that you're ignoring the results of anyway.
B) Pessimistic. You can actually query the DB to verify the data hasn't changed/been added since you last picked it up, then update it if it's safe.
var existing = db.Customers.Find(customer.Id);
// Some logic here to decide whether updating is a good idea, like
// verifying selected values haven't changed, then
db.Entry(existing).CurrentValues.SetValues(customer);
My Entity Model is as follows:
Person , Store and PersonStores Many-to-many child table to store PeronId,StoreId
When I get a person as in the code below, and try to delete all the StoreLocations, it deletes them from PersonStores as mentioned but also deletes it from the Store Table which is undesirable.
Also if I have another person who has the same store Id, then it fails saying
"The DELETE statement conflicted with the REFERENCE constraint \"FK_PersonStores_StoreLocations\". The conflict occurred in database \"EFMapping2\", table \"dbo.PersonStores\", column 'StoreId'.\r\nThe statement has been terminated" as it was trying to delete the StoreId but that StoreId was used for another PeronId and hence exception thrown.
Person p = null;
using (ClassLibrary1.Entities context = new ClassLibrary1.Entities())
{
p = context.People.Where(x=> x.PersonId == 11).FirstOrDefault();
List<StoreLocation> locations = p.StoreLocations.ToList();
foreach (var item in locations)
{
context.Attach(item);
context.DeleteObject(item);
context.SaveChanges();
}
}
The problem is that you don't actually want to delete the store itself, just the relation between the store and the person. Try something like this instead:
Person p = null;
using (ClassLibrary1.Entities context = new ClassLibrary1.Entities())
{
p = context.People.Where(x=> x.PersonId == 11).FirstOrDefault();
p.StoreLocations.Clear();
context.SaveChanges();
}
That will get your person, remove all the stores from his list of stores, and save the changes. Note that you might need an include statement on the first row in the using block, depending on how your ObjectContext is configured.
I have an entity with self reference (generated by Entity Designer):
public MyEntity: EntityObject
{
// only relevant stuff here
public int Id { get...; set...; }
public MyEntity Parent { get...; set...; }
public EntityCollection<MyEntity> Children { get...; set...; }
...
}
I've written a stored procedure that returns a subtree of nodes (not just immediate children) from the table and returns a list of MyEntity objects. I'm using a stored proc to avoid lazy loading of an arbitrary deep tree. This way I get relevant subtree nodes back from the DB in a single call.
List<MyEntity> nodes = context.GetSubtree(rootId).ToList();
All fine. But when I check nodes[0].Children, its Count equals to 0. But if I debug and check context.MyEntities.Results view, Children enumerations get populated. Checking my result reveals children under my node[0].
How can I programaticaly force my entity context to do in-memory magic and put correct references on Parent and Children properties?
UPDATE 1
I've tried calling
context.Refresh(ClientWins, nodes);
after my GetSubtree() call which does set relations properly, but fetches same nodes again from the DB. It's still just a workaround. But better than getting the whole set with context.MyEntities().ToList().
UPDATE 2
I've reliably solved this by using EF Extensions project. Check my answer below.
You need to assign one end of the relationship. First, divide the collection:
var root = nodes.Where(n => n.Id == rootId).First();
var children = nodes.Where(n => n.Id != rootId);
Now, fix up the relationship.
In your case, you'd do either:
foreach (var c in children)
{
c.Parent = root;
}
...or:
foreach (var c in children)
{
root.Children.Add(c);
}
It doesn't matter which.
Note that this marks the entities as modfied. You'll need to change that if you intend to call SaveChanges on the context and don't want this saved.
The REAL solution
Based on this article (read text under The problem), navigation properties are obviously not populated/updated when one uses stored procedures to return data.
But there's a nice manual solution to this. Use EF Extensions project and write your own entity Materilizer<EntityType> where you can correctly set navigation properties like this:
...
ParentReference = {
EntityKey = new EntityKey(
"EntityContextName.ParentEntitySetname",
new[] {
new EntityKeyMember(
"ParentEntityIdPropertyName",
reader.Field<int>("FKNameFromSP")
)
})
}
...
And that's it. Calling stored procedure will return correct data, and entity object instances will be correctly related to eachother. I advise you check EF Extensions' samples, where you will find lots of nice things.
I am using Apple's CoreDataBooks sample application as a basis for pulling data into a secondary managed object context in the background, and then merging that data into the primary managed object context.
The data I am pulling in is a Book entity with a to-one relationship with an Owner entity (called "owner"). The Owner entity has a to-many relationship with the Book (called "books").
My data is an XML document of the form:
<Owner>
<Name>alexpreynolds</Name>
<ID>123456</ID>
</Owner>
<Books>
<Book>Book One</Book>
<Book>Book Two</Book>
...
<Book>Book N</Book>
</Books>
Book One through Book N are associated with one Owner ("alexpreynolds, 123456").
I am parsing this into an Owner instance and an NSMutableSet made up of Book instances.
When I attempt to save the first time, it saves fine and the merged data shows up in the table view.
On the second save, however, when the XML content contains a new book, it doesn't work.
Here's what happens:
I then attempt to load in an XML document that contains a new Book not already in the primary managed object context. The new Book is using the same Owner as that which is associated with the other Books.
I have routines that pick out this unique Owner managed object (which I already have in my primary managed object context) and the unique Book that is not found in the primary MOC.
From this, I create a new Book object in the secondary MOC, and I set its "owner" relationship to point to the unique Owner I found in the primary MOC.
When I save, I get the following error:
*** Terminating app due to uncaught
exception 'NSInvalidArgumentException',
reason: 'Illegal attempt to establish a
relationship 'owner' between objects in
different contexts
(source = <Book: 0x7803590>
(entity: Book; id: 0x7802ae0 <x-coredata:///
Book/t527F06B2-3EB5-47CF-9A29-985B0D3758862>
; data: {
creationDate = 2009-10-12 06:01:53 -0700;
name = nil;
nameInitial = nil;
operations = (
);
owner = nil;
type = 0;
}) ,
destination = <Owner: 0x78020a0> (entity:
Owner; id: 0x3a56f80 <x-coredata://043AF2F0-1AD0-
4078-A5E8-E9D7071D67D1/Owner/p1> ; data: {
books = "<relationship fault: 0x7801bf0 'books'>";
displayName = alexpreynolds;
ownerID = 123456;
}))'
How do I create a new Book entity in the secondary MOC, so that I can still associate it with a pre-existing Owner in the primary MOC?
You can't have relationships between objects in different managed object contexts. So one way of getting around that is to bring the object into the managed object context.
For example:
NSManagedObject *book = // get a book in one MOC
NSManagedObject *owner = // get an owner in a different MOC
[[owner mutableSetValueForKey:#"books"] addObject:[owner.managedObjectContext objectWithID:[book objectID]]];
So what you're doing is actually fetching the Book into the same managed object context with owner. Keep in mind, though, that this is only possible if book has already been saved. The managed object context is going to look for the object in the persistent store, so it has to be saved first.
I had the same problem and this sentence helped me to solve the error.
You can't have relationships between objects in different managed
object contexts. So one way of getting around that is to bring the
object into the managed object context.
Here's my code (I replaced the variables to make it work for you):
// Have the owner object and get the managedObjectContext
Owner *owner = [[DataFunctions alloc] getCurrentOwner];
NSManagedObjectContext *context = [owner managedObjectContext];
// Create a book and use the manageObjectContext of owner
Book *book = [NSEntityDescription insertNewObjectForEntityForName:#"Book" inManagedObjectContext:context];
[newQuote setValue: "Book Title" forKey:#"title"];
[newQuote setOwner:owner];
I hope this helps :)
Here, you are trying to establish a relation between two objects which are fetched/created with a different context. Core data won't allow you to establish such relations. To achieve this, you should fetch the second object (you are trying to make a relationship with) with the context of the first object with objectID. Now you should be able to establish a relation between these two objects. For example:
MagicalRecord.saveWithBlock({[unowned self] (localContext: NSManagedObjectContext!) in
let user = User.MR_createEntityInContext(localContext)!
.....
.....
}) //here local context stores data on end of block itself
MagicalRecord.saveWithBlock({[unowned self] (localContext: NSManagedObjectContext!) in
let address = Address.MR_createEntityInContext(localContext)!
.....
.....
let user = localContext.objectWithID(self.user!.objectID) as! User
user.address = address
})
Hope this will help you!
As the error says, you're not allowed to have a relationship in one Core Data object whose value is set to another object held in a different context. One way you can get around this is to wait until after you save the new object and merge it back into the primary context, then set the owner relationship as appropriate (since both objects are now in the same context, there's no problem with that).
book *book = [mainContext ........] //Get book from default context
NSManagedObjectID *objectId = [book objectID];
Book *tmpBook = [tmpContext objectWithID:objectId]; //Now book has the legal relationship
Swift version...
context.insert(objectFromOtherContext)