Can't pass Core Data object between Swift classes - core-data

I have an array of Core Data entities that I can successfully pass from Objective-C VC to a Swift VC. I've verified through the console that they are coming in.
Now I need to loop through the array of core data objects and pass them onto another swift vc. I have the array set up as a property (Customer is the CD entity):
var arrCustomers: Array = Customer
In my loop I am trying to pass them to the other Swift VC:
for thisCustomer in self.arrCustomers {
let gridCell = storyboard.instantiateViewController(withIdentifier: "bulkAddCell") as! BulkAddCell
resizeGridCell(vGridCell: gridCell.view, frameY: frameY, frameH:50.0)
gridCell.thisCustomer = thisCustomer
self.svData.addSubview(gridCell.view)
}
In BulkAddCell I have thisCustomer as a property:
var thisCustomer: Customer!
I know BulkAddCell is initializing correctly as I have some IBOutlets in it and I can see them in the console. But thisCustomer is always nil.
I'm making the switch from Obj-C to Swift so I am sure it is something basic I am missing.

Change:
var arrCustomers: Array = Customer
To:
var arrCustomers = [Customer]() //init to an empty array of customer objects
//sometime later
//append your arrCustomers with instances of Customer
// before leaving first view controller access the arrCustomers object
// contents as needed and assign to a corresponding property on the
// destination view controller. (usually in preparforSegue)

Related

How to change managed object class name before fetching

I have a Swift app that uses CoreData. I created List entity with class MyAppTarget.List. Everything is configured properly in .xcdatamodeld file. In order to fetch my entities from persistent store, I am using NSFetchedResultsController:
let fetchRequest = NSFetchRequest()
fetchRequest.entity = NSEntityDescription.entityForName("List", inManagedObjectContext: managedObjectContext)
fetchRequest.sortDescriptors = [ NSSortDescriptor(key: "name", ascending: true) ]
let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: "ListFetchedResultsControllerCache")
and it works like expected, returning array of MyAppTarget.List objects when fetching.
However, I would like to use it inside another target, for unit testing. I added List class to MyUnitTestTarget, so I can access it inside the unit test target. The problem is that the fetched results controller returns MyAppTarget.List objects, not the MyUnitTestTarget.List objects. In order to make the List entity testable, I have to make it public along with all methods that I need to use and I would like to avoid this.
I tried to change the managedObjectClassName property on NSEntityDescription:
fetchRequest.entity.managedObjectClassName = "MyUnitTestTarget.List"
but it generates exception:
failed: caught "NSInternalInconsistencyException", "Can't modify an immutable model."
The documentation states that
[...] once a description is used (when the managed object model to which it belongs is associated with a persistent store coordinator), it must not (indeed cannot) be changed. [...] If you need to modify a model that is in use, create a copy, modify the copy, and then discard the objects with the old model.
Unfortunately, I don't know how to implement this flow. I wonder if there is a way to change the managed object class name in runtime, before fetching the entities with NSFetchedResultsController?
It occurs that solution to my problem was pretty simple. In order to make it working, I had to create a copy of managedObjectModel, edit its entities and create NSPersistentStoreCoordinator with the new model. Changing the managedObjectClassName property on NSEntityDescription instance is possible only before model to which it belongs is associated with NSPersistentStoreCoordinator.
let testManagedObjectModel = managedObjectModel.copy() as NSManagedObjectModel
for entity in testManagedObjectModel.entities as [NSEntityDescription] {
if entity.name == "List" {
entity.managedObjectClassName = "CheckListsTests.List"
}
}
This also solves my other problem with unit testing CoreData model entities in Swift.
You can dynamically alter the class name of the NSManagedObject subclass with something like:
let managedObjectModel = NSManagedObjectModel.mergedModelFromBundles([NSBundle.mainBundle()])!
// Check if it is within the test environment
let environment = NSProcessInfo.processInfo().environment as! [String : AnyObject]
let isTestEnvironment = (environment["XCInjectBundle"] as? String)?.pathExtension == "xctest"
// Create the module name based on product name
let productName:String = NSBundle.mainBundle().infoDictionary?["CFBundleName"] as! String
let moduleName = (isTestEnvironment) ? productName + "Tests" : productName
let newManagedObjectModel:NSManagedObjectModel = managedObjectModel.copy() as! NSManagedObjectModel
for entity in newManagedObjectModel.entities as! [NSEntityDescription] {
entity.managedObjectClassName = "\(moduleName).\(entity.name!)"
}

Swift CoreData subclass not working

i know that this has been asked a couple of times but the usual solution does not seem to work for me. I created a CoreData entity and a subclass for it using <ProjectName>.<SubclassName> syntax as class name. Creating a new object like this:
let object = NSEntityDescription.insertNewObjectForEntityForName("User", inManagedObjectContext: CoreDataManager.sharedInstance.managedObjectContext) as User
println("-->\(object)<--")
object.setValue(12, forKey: "userID")
object.setValue("username", forKey: "username")
the console output:
although the object does not get printed in the console it seems to have been in some way created and setting a value on that object refers to a Core Data Object
when i use it without a subclass it works as expected (setting Class name back to default):
let object = NSEntityDescription.insertNewObjectForEntityForName("User", inManagedObjectContext: CoreDataManager.sharedInstance.managedObjectContext) as NSManagedObject
println("-->\(object)<--")
output:
Here's my subclass declaration:
import Foundation
import CoreData
class User: NSManagedObject {
#NSManaged var userID: NSNumber
#NSManaged var username: String
}
and here's the core data model form:
What's wrong with the code? Or do i miss anything?
For printing out the value of an NSManagedObject, do not use:
println(...)
But rather use:
NSLog(...)
Please find an example of using NSLog and an NSManagedObject below:
/**
* Called when the user clicks on the save button.
*/
#IBAction func saveTapped(sender: AnyObject) {
// Reference to our app delegate
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
// Reference MOC
let context: NSManagedObjectContext = appDel.managedObjectContext!
let en = NSEntityDescription.entityForName("List", inManagedObjectContext: context)
// Create instance of put data model and initialize
var newItem: List = List(entity: en!, insertIntoManagedObjectContext: context)
// Map our properties
newItem.item = textFieldItemName.text
newItem.quantity = textFieldQt.text
newItem.info = textFieldMoreInfo.text
// Save our context
var error: NSError? = nil;
if (context.hasChanges) {
if (!context.save(&error)) { // save failed
println("Save Failed: \(error!.localizedDescription)")
} else {
println("Save Succeeded")
}
}
NSLog("newItem: %#", newItem)
// Navigate back to root ViewController
self.navigationController?.popToRootViewControllerAnimated(true)
}
Note: I do not know the exact reason (bug, or implementation maybe ...) but it turns out that it does not print out a value when we use println(...) function, instead of that it returns an empty String.
Consequently I recommend to all of you guys to use NSLog(...) function instead of println(...) when you want to print out the value of an NSManagedObject.
If you print the expression CoreDataManager.sharedInstance.managedObjectContext twice, do you get a different pointer each time?
It sounds like the managed object context is getting deallocated right after you use it, which indicates that your CoreDataManager.sharedInstance.managedObjectContext property is returning a new managed object context every time, not the same one, or your sharedInstance property is returning a new instance every time.
A managed object's in-memory state is stored as a weak reference to a managed object context. When the context drops out from underneath you (e.g. it's no longer referenced and so is deallocated), your managed object's storage disappears.
A few good indicators that this is happening:
You haven't saved your managed object context yet but a newly created managed object prints as fault
You get weird errors when you try to set a property
You get weird errors when you try to retrieve a property you just set
Include the following at the top of your .swift file
import CoreData
The reason for not working is it doesn't know which library to reference.
Hope it helps
try creating it with this method :
let entityDescripition = NSEntityDescription.entityForName(“User”, inManagedObjectContext: managedObjectContext)
let user = User(entity: entityDescripition, insertIntoManagedObjectContext: managedObjectContext)

How to get id of saved entity

Dynamics CRM 2011 on premise
I can barely believe I can't find this out by Googling. MSDN is useless.
Here is some C# from a plugin:
integ_creditpayment creditpayment = new integ_creditpayment();
creditpayment.integ_Amount = totalPay;
//set more properties
context.AddObject(creditpayment);
context.SaveChanges();
Now I want to get the value of the id field in integ_creditpayment.
Can I get this immediately from creditpayment.id? (As in, does context.SaveChanges() cause the creditpayment variable to be updated with the new id?)
I'm assuming your real code is more complicated, but there is no need to use the context in your example code:
integ_creditpayment creditpayment = new integ_creditpayment();
creditpayment.integ_Amount = totalPay;
//set more properties
creditpayment.Id = service.Create(creditpayment);
You can also use a type initializer and get rid of your object all together if you'd like:
Guid id = service.Create(new integ_creditpayment
{
integ_Amount = totalPay;
});
service in this case is of type IOrganizationService
After the SaveChanges() you can get the record id with:
Guid justCreatedId = creditpayment.Id;

Entity Framework 5 deep copy/clone of an entity

I am using Entity Framework 5 (DBContext) and I am trying to find the best way to deep copy an entity (i.e. copy the entity and all related objects) and then save the new entities in the database. How can I do this? I have looked into using extension methods such as CloneHelper but I am not sure if it applies to DBContext.
One cheap easy way of cloning an entity is to do something like this:
var originalEntity = Context.MySet.AsNoTracking()
.FirstOrDefault(e => e.Id == 1);
Context.MySet.Add(originalEntity);
Context.SaveChanges();
the trick here is AsNoTracking() - when you load an entity like this, your context do not know about it and when you call SaveChanges, it will treat it like a new entity.
If MySet has a reference to MyProperty and you want a copy of it too, just use an Include:
var originalEntity = Context.MySet.Include("MyProperty")
.AsNoTracking()
.FirstOrDefault(e => e.Id == 1);
Here's another option.
I prefer it in some cases because it does not require you to run a query specifically to get data to be cloned. You can use this method to create clones of entities you've already obtained from the database.
//Get entity to be cloned
var source = Context.ExampleRows.FirstOrDefault();
//Create and add clone object to context before setting its values
var clone = new ExampleRow();
Context.ExampleRows.Add(clone);
//Copy values from source to clone
var sourceValues = Context.Entry(source).CurrentValues;
Context.Entry(clone).CurrentValues.SetValues(sourceValues);
//Change values of the copied entity
clone.ExampleProperty = "New Value";
//Insert clone with changes into database
Context.SaveChanges();
This method copies the current values from the source to a new row that has been added.
This is a generic extension method which allows generic cloning.
You have to fetch System.Linq.Dynamic from nuget.
public TEntity Clone<TEntity>(this DbContext context, TEntity entity) where TEntity : class
{
var keyName = GetKeyName<TEntity>();
var keyValue = context.Entry(entity).Property(keyName).CurrentValue;
var keyType = typeof(TEntity).GetProperty(keyName, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).PropertyType;
var dbSet = context.Set<TEntity>();
var newEntity = dbSet
.Where(keyName + " = #0", keyValue)
.AsNoTracking()
.Single();
context.Entry(newEntity).Property(keyName).CurrentValue = keyType.GetDefault();
context.Add(newEntity);
return newEntity;
}
The only thing you have to implement yourself is the GetKeyName method. This could be anything from return typeof(TEntity).Name + "Id" to return the first guid property or return the first property marked with DatabaseGenerated(DatabaseGeneratedOption.Identity)].
In my case I already marked my classes with [DataServiceKeyAttribute("EntityId")]
private string GetKeyName<TEntity>() where TEntity : class
{
return ((DataServiceKeyAttribute)typeof(TEntity)
.GetCustomAttributes(typeof(DataServiceKeyAttribute), true).First())
.KeyNames.Single();
}
I had the same issue in Entity Framework Core where deep clone involves multiple steps when children entities are lazy loaded. One way to clone the whole structure is the following:
var clonedItem = Context.Parent.AsNoTracking()
.Include(u => u.Child1)
.Include(u => u.Child2)
// deep includes might go here (see ThenInclude)
.FirstOrDefault(u => u.ParentId == parentId);
// remove old id from parent
clonedItem.ParentId = 0;
// remove old ids from children
clonedItem.Parent1.ForEach(x =>
{
x.Child1Id = 0;
x.ParentId= 0;
});
clonedItem.Parent2.ForEach(x =>
{
x.Child2Id = 0;
x.ParentId= 0;
});
// customize entities before inserting it
// mark everything for insert
Context.Parent.Add(clonedItem);
// save everything in one single transaction
Context.SaveChanges();
Of course, there are ways to make generic functions to eager load everything and/or reset values for all keys, but this should make all the steps much clear and customizable (e.g. all for some children to not be cloned at all, by skipping their Include).

Fill CoreData ManagedObject property based on another object property

I have app that stores tree structure in CoreData.
There is an ManagedObject, "Item", and it has attributes:
itemId (string)
List item
title (string)
parentId (string)
parent (relationship to Item)
parentTitle (string)
parentId points to another Item object.
How do I make property parentTitle to be filled automatically with title of parent Item ?
While Martin's suggestion is a good solution for derived values, my question on yours is, why would you want this? You are not manipulating the value from the parent at all, ever. Since you are just accessing it, access the parent directly via KVC such as:
Item *item = ...;
NSString *title = [item valueForKeyPath:#"parent.title"];
//Do something with title
The only time you would want to use the keyPathsForValues... functionality is if you are changing something based on that value. If you are just accessing it, use KVC directly.
This is a possibility to achieve the desired functionality:
// implement in Item.m
// manages KVO notifications
+ (NSSet *)keyPathsForValuesAffectingParentTitle
{
return [NSSet setWithObjects:#"parent.title", nil];
}
// getter for parentTitle
- (NSString*) parentTitle
{
return [self valueForKeyPath:#"parent.title"];
}
additionally declare the property for parentTitle as readonly in Item.h
There is no need to declare a Core Data attribute "parentTitle".
The only problem I see with this solution is the following:
Item A is parent of item B
A gets turned into fault
B is still active and some View is bound to B.parentTitle
The view gets a notification because of the dependency declared with keyPathsForValuesAffecting, still object A is already faulted (and on shutdown unable to be unfaulted again) does Core Data manage such faulting&observation problems automatically?

Resources