I have a problem regarding core data.
I have an app with multiple tabs. Every tab holds a list of different "objects".
In each tab i have an add button (+) that takes me to a screen where i can add an "object".
The problem :
When i click add an entity for insert is being created and added to the context.
So, i go on first tab, click add - an entity is in context.
I go on other tab, i click add - another entity is in context.
I finish adding information for one of them ( to be valid ) and hit save.
Now core data throws an exception saying that could not save the context because the other entity it's not valid.
One idea that came into my mind was :
Copy all objects that are invalid from context, save the context, add the copied ones back (and so on when needed).
But an entity could have other relations with other entities so Person could have address, role, Contact Details.. and Company could have Address, Employes .. etc.
If person is invalid and has relationship Address valid , above idea fails because will not save person but will save address , what it's not correctly.
Something with a temporarily context could work but i don't have an clear idea how to implement this.
Another idea is to take all relationships for an entity (eg Person) when i want to save it, and save only Person.
But i failed to implement an recursive function ( the problem lays in the fact that relationships are inverse and because of many many relationships on my coredata model (person has contact details and also company) ).
Here is the code :
- (void)relationshipInstancesForManagedObject:(NSManagedObject *)managedObject
andSkipValue:(id)skipValue
andSet:(NSMutableSet *)set
{
for (NSRelationshipDescription *relationship in managedObject.entity.properties)
{
if (![relationship isKindOfClass:[NSRelationshipDescription class]]) continue;
id value = [managedObject valueForKey:relationship.name];
if (value == skipValue || value == nil || value == self || [set containsObject:value] ) continue;
NSLog(#"%#",value);
if (relationship.isToMany)
{
if ([value containsObject:skipValue] || [value containsObject:managedObject] || [value count] == 0 ) {
continue;
}
for (NSManagedObject *entity in value)
{
[set addObject:entity];
[self relationshipInstancesForManagedObject:entity
andSkipValue:skipValue
andSet:set];
}
} else {
if (value != nil) {
[set addObject:value];
[self relationshipInstancesForManagedObject:value
andSkipValue:skipValue
andSet:set];
}
}
}
}
If you have any ideas, I would be grateful.
The simplest solution to your problem is to make sure that all objects are valid when you add them to the store. Collect the information you need to create a valid object first, and only then add a new object along with all the required information.
Also, consider carefully whether you need to require all the properties that you currently do. If you have two entities that each have a relationship to the other, do you need to require both relationships? Could you make one of the relationships optional? That can help you avoid a chicken and egg problem where it's impossible to add objects for either entity because none of the other type exist yet.
Related
I don't understand why it is recommended everywhere to use AddOrUpdate in the Seed method?
We develop application for half a year already and the AddOrUpdates overwrites user changes every time we update the server. E.g. if we call in the Seed:
context.Styles.AddOrUpdate(new Style { Id = 1, Color = "red" });
And user changes the Style to "green" then on next server update we overwrite it to "red" again and we get very annoyed user.
It looks that if we change AddOrUpdate to Add we will be guaranteed from overwriting user data. If we still need some special case we can put it to separate migration. Unlike the general Configuration.Seed method particular migrations don't run twice over the same database version.
I assume that Style's primary key is Id. The overload of AddOrUpdate that you use only checks if there is a record having Id == 1. If so, it updates it. That's all.
What's going wrong here is that the primary key is a surrogate key, i.e. it's there for querying convenience, but it's got no business meaning. Usually, with migrations you want to look for the natural keys of entities though. That's how the user identifies data. S/he wants a green style, not a style identified by 1.
So I think you should use this overload of AddOrUpdate:
context.Styles.AddOrUpdate( s => s.Color,
new Style { Id = 1, Color = "red" });
Now when there is no red style anymore, a new one is inserted, overriding the Id value (assuming that it's generated by the database).
From your later comments I understand that you want to Add data when they're new, but not update them when they exist (compared by primary key). For this you could use a slightly adapted version of an AddWhenNew method I described here. For your case I would do it like so:
public T void MarkAsAddedWhenNew<T>(this DbContext context,
Expression<Func<T, object>> identifierExpression, T item)
where T : class
{
context.Set<T>().AddOrUpdate(identifierExpression, item);
if (context.Entry(item).State != System.Data.Entity.EntityState.Added)
{
var identifierFunction = identifierExpression.Compile();
item = context.Set<T>()
.Local
.Single(x => identifierFunction(item)
.Equals(identifierFunction(x)));
context.Entry(item).State = System.Data.Entity.EntityState.Unchanged;
}
return item;
}
Re-fetching the item from the local collection is a nuisance, but necessary because of a bug in AddOrUpdate(). This bug also caused the error you got when setting the state of the original entry to Unchanged: it was a different instance than the attached one.
The way Add method acts is misleading. It Inserts data into database even if there is already a row with the same PrimaryKey as we do Add. It just creates new PrimaryKey ignoring our value silently. I should have tried it before asking the question, but anyway, I think I'm not the only one who confused by this. So, in my situation Add is even worse than AddOrUpdate.
The only solution I've come to is following:
public static void AddWhenNew<T>(this DbContext ctx, T item) where T : Entity
{
var old = ctx.Set<T>().Find(item.Id);
if (old == null)
ctx.Set<T>().AddOrUpdate(item);
/* Unfortunately this approach throws exception when I try to set state to Unchanged.
Something like:"The entity already exists in the context"
ctx.Set<T>().AddOrUpdate(item);
if (ctx.Entry(item).State != System.Data.Entity.EntityState.Added)
ctx.Entry(item).State = System.Data.Entity.EntityState.Unchanged;
*/
}
Framework: I'm using using MVC 3 + EntityFramework 4.1 Code-First.
Concept: One Legislation entity has many Provision entities. The idea is that the user enters a Legislation entity, that gets saved then the function that saves it passes it along to another function to see whether that Legislation has a ShortTitle. If it does, then it formats it into a properly worded string and includes it as the Legislation's first Provision, then saves the changes to db.
Issue: The problem is, I've tried coding it in different ways, I keep getting a NullReferenceException, telling me to create a new object instance with the "new" keyword, and points me to the savedLegislation.Provisions.Add(provision); line in my second function.
Here are the two functions at issue, this first one saves the Legislation proper:
public Legislation Save(NewLegislationView legislation)
{
Legislation newLegislation = new Legislation();
// Simple transfers
newLegislation.ShortTile = legislation.ShortTile;
newLegislation.LongTitle = legislation.LongTitle;
newLegislation.BillType = legislation.BillType;
newLegislation.OriginatingChamber = legislation.OriginatingChamber;
newLegislation.Preamble = legislation.Preamble;
// More complicated properties
newLegislation.Stage = 1;
this.NumberBill(newLegislation); // Provides bill number
newLegislation.Parliament = db.LegislativeSessions.First(p => p.Ending >= DateTime.Today);
newLegislation.Sponsor = db.Members.Single(m => m.Username == HttpContext.Current.User.Identity.Name);
// And save
db.Legislations.Add(newLegislation);
db.SaveChanges();
// Check for Short titles
this.IncludeShortTitle(newLegislation);
// return the saved legislation
return newLegislation;
}
And the second function which is invoked by the first one deals with checking whether ShortTitle is not empty and create a Provision that is related to that Legislation, then save changes.
public void IncludeShortTitle(Legislation legislation)
{
var savedLegislation = db.Legislations.Find(legislation.LegislationID);
if (savedLegislation.ShortTile.Any() && savedLegislation.ShortTile.ToString().Length >= 5)
{
string shortTitle = "This Act may be cited as the <i>" + savedLegislation.ShortTile.ToString() + "</i>.";
var provision = new Provision()
{
Article = Numbers.CountOrNull(savedLegislation.Provisions) + 1,
Proponent = savedLegislation.Sponsor,
Text = shortTitle
};
savedLegislation.Provisions.Add(provision);
db.SaveChanges();
}
}
I've been researching how SaveChanges() works and whether it is properly returning the updated entity, it does (since I get no issue looking it up in the second function). If it works properly, and the legislation is found and the provision is newly created in the second function, I don't see what is the "null" reference it keeps spitting out.
The null reference in this case would be savedLegislation.Provisions. The Provisions collection won't be initialized to a new List<Provision> when EF returns your Legislation instance from the db.Legislations.Find(...) method.
The first thing I'd try is something like this:
var savedLegislation = db.Legislations
.Include("Provisions")
.First(l => l.LegislationID == legislation.LegislationID);
... but I'd also consider just using the legislation instance that was passed into the method rather than fetching it from the database again.
I would like to know how to implement a validation in Core Data. What I'd like to do is ensure that an attribute is unique within the scope of a related parent object. In other words, I'm wondering how to implement the validates_uniqueness_of :field, :scope => :parent paradigm (from rails / activerecord) in Core Data.
For example, suppose I create two models - one called Blog and one called Post. Each Post has an attribute called title. Different Blog objects can have Posts with identical titles, but how do I validate the uniqueness of a title within the scope of a Blog?
Thanks!
Walk the relationship to the parent and grab the set of posts. Then you can run a predicate against it to check for uniqueness like:
NSSet *set = [[self parent] posts];
NSSet *filtered = [set filteredSetWithPredicate:[NSPredicate preicateWithFormat:#"self != %# and title == %#", self, [self title]]];
if ([filtered count] > 0) return NO;
return YES;
I have two classes A and B with a many-to-one relationship from A to B (multiple A objects may reference the same B). The question is, if the delete rule on the A side is Cascade, will B be deleted only when the last referencing A is deleted or will it be deleted the first time an associated A is deleted. The delete rule for the B side of the relationship is Nullify if that matters.
Also, I read in the Core Data docs that the Optional flag matters in some cases. But it wasn't clear how the relationships they were illustrating related to my case. They were talking about a containment case (B is owned by A) whereas my case is one of subscription/association (B is related to A).
I could simply manage deletion programmaticaly in the code but wanted to allow Core Data to do the right thing if possible. But it's not clear that the garbage collection semantics that I'm looking for are supported in Core Data.
Any suggestions?
I had the same goal as you apparently had (delete B as soon as the last referenced A is deleted). It took me longer than expected to get this right. Particularly because
At the time A prepares for deletion, the to-many relationship in B might not be updated yet, so you can't just count the A referenced in B.
isDeleted on A seems to be already set during -prepareForDeletion
Here's what worked for me if anybody's interested (I'll use Department <-->> Employee because it's easier to read):
In Employee:
- (void)prepareForDeletion {
// Delete our department if we we're the last employee associated with it.
Department *department = self.department;
if (department && (department.isDeleted == NO)) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"isDeleted == NO"];
NSSet *employees = [department.employees filteredSetUsingPredicate:predicate];
if ([employees count] == 0) {
[self.managedObjectContext deleteObject:department];
}
}
}
Other people have suggested putting this logic into -willSave in Department. I prefer the solution above since I might actually want to save an empty department in some cases (e.g. during manual store migration or data import).
Here's a Swift 4 version of Lukas' answer:
public override func prepareForDeletion() {
guard let department = department else { return }
if department.employees.filter({ !$0.isDeleted }).isEmpty {
managedObjectContext?.delete(department)
}
}
Adding to #JanApotheker answer you also will need to save the managedObjectContext and cast filter's argument to AnyObject since it is iterating over an NSSet like so:-
public override func prepareForDeletion() {
guard let department = department, let employees = department.employees else { return }
if employees.filter({ !($0 as AnyObject).isDeleted }).isEmpty {
managedObjectContext?.delete(department)
do {
try managedObjectContext?.save()
} catch {
}
}
}
Imagine this case:
var locations = from Locations in this.LocationDataContext.Locations
.Include("ChildLocations")
where
(Locations.LocationType.ID == 3)
select
Locations;
This query will load all locations with type == 3 and all the related child locations, ok. But what i'm trying to figure out is how to filter the child locations that are being loaded. What if location have 3milion child locations?
Maybe something like this? (doesnt work because ChildLocations is a set of entities)
var locations = from Locations in this.LocationDataContext.Locations
.Include("ChildLocations")
where
(Locations.LocationType.ID == 3) &&
(Locations.ChildLocations.LocationType.ID == 2)
select
Locations;
Thank you.
The Entity Framework will never materialize a partially-complete instance. You cannot, in other words, materialize a Location with only some of its ChildLocations. This would be an "incomplete" object, and the Entity Framework does not allow this.
However, there are workarounds. If you only need the information from the ChildLocations and not from the Location itself, just select that:
from Locations in this.LocationDataContext.Locations
where Locations.LocationType.ID == 3
from ChildLocation in Locations
where ChildLocation.LocationType.ID == 2
select ChildLocation;
In this case, since we are only selecting the ChildLocations, it is OK to only select a few of them, since they can be materialized completely. It is only when materializing the Location that we need all of the children.
Another workaround is to materialize partial Location information into an anonymous type. This allows you to get information about both the Location and some of the ChildLocations without violating the rule that instances can only be materialized in their complete form. Since you're not actually materializing a real Location, there is no requirement to materialize the entire thing:
from Locations in this.LocationDataContext.Locations
where Locations.LocationType.ID == 3
select new
{
ID = Locations.ID,
LocationType= Locations.LocationType
ChildLocations = from ChildLocation in Locations
where ChildLocation.LocationType.ID == 2
select ChildLocation
}