CoreData save record without contextsave - core-data

Project is my NSManagedObject
let proj = Project(context: context!)
//at this point when i try to fetch for my proj nothing is there
proj.title = "title"
//at this point I can fetch my project record
now i try
let proj = Project(context: context!)
let proj1 = Project(context: context!)
//at this point when i try to fetch for my proj nothing is there
proj.title = "title"
//I find 1 record
I change my code a bit
let proj = Project(context: context!)
let proj1 = Project(context: context!)
//when i try to fetch for my proj nothing is there
proj.title = "title"
proj1.title = "title"
//I find 2 record
it seems like proj.title = "title" is saving the record
I do not have .save() inside my subclass
why is this ? I am confused
i thought object are only saved when you call
managedObjectContext.save

The object exists immediately after insert within the ManagedObjectContext.
The .save() pushes the changes from the MOC to its parent context, or to the persistent store. The idea is that you can manipulate the objects within a scratch MOC, but throw them away in response to a "Cancel".
If you do a fetch using a different MOC that's pointing to the same persistent store, you won't see the changes without a .save() on the MOC you're using for the insertions.

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!)"
}

Get entity record values from Audit Details Response

Auditing of an entity is enabled,I want the entity record after deletion.So
I was trying to get that from audit entity records,like this:
RetrieveAuditDetailsRequest request = new RetrieveAuditDetailsRequest();
request.AuditId = _selectedId;
RetrieveAuditDetailsResponse response = (RetrieveAuditDetailsponse)_orgService.Execute(request);
EntityReference ObjectId = (EntityReference)response.AuditDetail.AuditRecord.Attributes["objectid"];
string ObjectName = ObjectId.LogicalName;
Guid Id = ObjectId.Id;
ColumnSet col = new ColumnSet(true);
Entity ent = _orgService.Retrieve(ObjectName,Id,col);
Its throwing an error "Expected non empty Guid".
FYI, I want this record values because I want to restore/recover record by creating it again.
Please help whats wrong with it??
You are attempting to retrieve the deleted record with this code:
string ObjectName = ObjectId.LogicalName;
Guid Id = ObjectId.Id;
ColumnSet col = new ColumnSet(true);
Entity ent = _orgService.Retrieve(ObjectName,Id,col);
This will fail with the error you are getting because no such record exists (it is deleted.) Unlike CRM 4 and earlier there are no soft deletes in 2011, once deleted it is gone from the database.
Replace it with the following code:
RetrieveRecordChangeHistoryRequest retrieveRequest = new RetrieveRecordChangeHistoryRequest();
changeRequest.Target = new EntityReference(ObjectName, Id);
RetrieveRecordChangeHistoryResponse response =
(RetrieveRecordChangeHistoryResponse)_orgService.Execute(retrieveRequest);
if (response.AuditDetailCollection != null)
{
var auditDetails = response.AuditDetailCollection;
// Do work
}
You then enumerate through the auditDetails to get the correct attributes.
You can find more at http://blogs.msdn.com/b/crm/archive/2011/05/23/recover-your-deleted-crm-data-and-recreate-them-using-crm-api.aspx.
The "Expected non empty Guid" error is thrown whenever you try to retrieve something with an empty GUID (Guid.Empty, 00000000-0000-0000-0000-000000000000). I'm guessing your _selectedId is not set to an actual GUID. Maybe you're setting it from a Nullable GUID and you are calling ValueOrDefault(), which is resulting in it getting set to the empty Guid, and failing in your Request?

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).

Change the state of a relation in Entity Framework

My question is directly in relation to this one.
The accepted answer says "You also have to change state of the relation".
I use Model-First approach, and I don't have the foreign key in my entity. I only have the navigation property.
I Can change the state of the entities via DbEntityEntry, but I can't figure how to change the state of the relation itself. How can I access it?
Exemple of my code :
Building building = new Building() { Id = 1, Name = "modified" }; //Buiding 1 exists in DB
building.Adress = new Adress() { Id = 1, Road = "Sesame street" }; //Address 1 exists in DB
building.Adress.State = new State() { Id = 1 }; //State with Id 1 exists in DB
dbContext.Entry<Building>(building).State = EntityState.Modified;
dbContext.Entry<Adress>(building.Adress).State = EntityState.Modified;
//The state itself is not modified, but the relation between adress and state may do.
dbContext.Entry<State>(building.Adress.State).State = EntityState.Unchanged;
dbContext.SaveChanges();
This code pass without error, but the relation between Adress and State is never updated.
The Name of the building and the Street properties does.
Take a look at the ObjectStatementManager (and use ctrl + . to get the references for IObjectContextAdapter).
var unchangedItems = ((IObjectContextAdapter)dbContext).ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Unchanged);
var addedItems = ((IObjectContextAdapter)dbContext).ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added);
It also specifically has ChangeRelationshipState which sounds appropriate in your scenario.

Is it correct to let entities call repository store methods?

For example if I have 2 aggregate roots AR1 and AR2, and AR1 updates AR2 in some way - what is the proper way of storing that change in the repository? Should AR1 call the repository store method?
I dislike the idea of having repositories commiting changes to the database.
In my case, the xRepository.Add(x) only attaches the entity/AR to the current unit of work.
To perform the actual commit to db, I call commit on my uow.
eg.
C#
using(var uow = UoW.Begin()
{
var ar1Repo = new Ar1Repository(uow);
var ar1 = ar1Repo.FindById(123); //fetch already persistent entity
var ar1.MakeChangesToAr2();
//both ar1 and ar2 are persistent and known by the UoW
//so no need to add them to the repositories since they
//are already _in_ the repositories
uow.Commit();
}
or:
using(var uow = UoW.Begin()
{
var ar1Repo = new Ar1Repository(uow);
var ar2Repo = new Ar2Repository(uow);
var ar2 = new AR2();
var ar1 = new AR1(AR2);
ar1.MakeChangesToAr2();
//attach the new entities to the uow
ar1Repo.Add(ar1);
ar2Repo.Add(ar2);
uow.Commit();
}
This way, you can commit entire batches of changes at once, instead of trying to do atomic commits per entity where you might get problems committing object graphs.
This idea is based on Jimmy Nilssons ideas in his book Applying Domain Driven Design and patterns.
I would have a method in AR2 named Save(). This method would instantiate the repository to save itself. AR1 could then call the Save() method on AR2 once it has made the change.

Resources