Restkit + Core Data: Inserts duplicate values in a UITableView on every app launch - core-data

Following AlexEdge Tutorial I have encountered the following behavior. I can provide code as requested since there are many lines and I'm not really sure where to look. Basically, it "works" in the sense that the data is properly loaded into the UITableView, but after stopping and starting up the simulator, it inserts new duplicate rows in each section.
I figured this would have something to do with caching but I am following the above tutorial pretty closely and I've set the identificationAttributes to identify unique records (wouldn't, for example, adding ALL attributes effectively eliminate the possibility that I've not specified a sufficiently unique key, just for debugging purposes?). I've tried changing the Cache name, setting it nil, but it keeps inserting duplicates. Every so often I reset the simulator just to start on a clean slate.
If it matters, I am calling getObjectsAtPath in viewDidLoad as per the tutorial. My understanding of how RestKit worked was that it was okay to do so because it would be smart enough to infer that no updates were necessary as the records are all the same.
Edit
I have set identificationAttributes to an array of two integer attributes that really do determine a unique record.
I also have a managedObjectCache:
// Seal the deal
[managedStore createPersistentStoreCoordinator];
NSString *storePath = [RKApplicationDataDirectory() stringByAppendingPathComponent:#"CCTDB.sqlite"];
NSError *error;
NSPersistentStore *persistentStore =
[managedStore
addSQLitePersistentStoreAtPath:storePath
fromSeedDatabaseAtPath:nil
withConfiguration:nil
options:#{
NSMigratePersistentStoresAutomaticallyOption:#YES,
NSInferMappingModelAutomaticallyOption:#YES
}
error:&error];
NSAssert(persistentStore, #"Failed to add persistent store with error: %#", error);
[managedStore createManagedObjectContexts];
managedStore.managedObjectCache = [[RKInMemoryManagedObjectCache alloc] initWithManagedObjectContext:managedStore.persistentStoreManagedObjectContext];
EDIT 1
Here is the log output of the second time around
(2013-07-11 03:18:29.961 cocoaclinical[18773:3d07] D restkit.object_mapping:RKPropertyInspector.m:130 Cached property inspection for Class 'EMDisease': {
diseaseId = {
isPrimitive = 0;
keyValueCodingClass = NSNumber;
name = diseaseId;
};
diseaseIdValue = {
isPrimitive = 1;
keyValueCodingClass = NSNumber;
name = diseaseIdValue;
};
name = {
isPrimitive = 0;
keyValueCodingClass = NSString;
name = name;
};
subDiseaseId = {
isPrimitive = 0;
keyValueCodingClass = NSNumber;
name = subDiseaseId;
};
subDiseaseIdValue = {
isPrimitive = 1;
keyValueCodingClass = NSNumber;
name = subDiseaseIdValue;
};
}
2013-07-11 03:18:29.975 cocoaclinical[18773:3f03] I restkit.core_data:RKInMemoryManagedObjectCache.m:94 Caching instances of Entity 'EMDisease' by attributes 'diseaseId, subDiseaseId'
2013-07-11 03:18:29.983 cocoaclinical[18773:3f03] T restkit.core_data:RKInMemoryManagedObjectCache.m:127 Cached 31 objects
2013-07-11 03:18:29.984 cocoaclinical[18773:3f03] D restkit.object_mapping:RKMapperOperation.m:231 Asked to map source object {
DiseaseSystemIdMember = 161;
DisplayNameMember = "Acute Lymphocytic Leukemia";
SubDiseaseSystemIdMember = 1886;
} with mapping <RKEntityMapping:0x96c2850 objectClass=EMDisease propertyMappings=(
"<RKAttributeMapping: 0x96ba060 SubDiseaseSystemIdMember => subDiseaseId>",
"<RKAttributeMapping: 0xb58df40 DisplayNameMember => name>",
"<RKAttributeMapping: 0xb58df70 DiseaseSystemIdMember => diseaseId>"
)>
2013-07-11 03:18:29.984 cocoaclinical[18773:3f03] D restkit.object_mapping:RKMappingOperation.m:952 Starting mapping operation...
2013-07-11 03:18:29.985 cocoaclinical[18773:3f03] T restkit.object_mapping:RKMappingOperation.m:953 Performing mapping operation: <RKMappingOperation 0xb5b7820> for 'EMDisease' object. Mapping values from object {
DiseaseSystemIdMember = 161;
DisplayNameMember = "Acute Lymphocytic Leukemia";
SubDiseaseSystemIdMember = 1886;
} to object <EMDisease: 0xb5b7ee0> (entity: EMDisease; id: 0xb5b7f80 <x-coredata:///EMDisease/t8DD8EE18-C798-468F-9E03-C6A3C724AA772> ; data: {
diseaseId = 161;
name = nil;
subDiseaseId = 1886;
}) with object mapping (null)
2013-07-11 03:18:30.027 cocoaclinical[18773:3f03] T restkit.object_mapping:RKMappingOperation.m:550 Mapping attribute value keyPath 'SubDiseaseSystemIdMember' to 'subDiseaseId'
2013-07-11 03:18:30.028 cocoaclinical[18773:3f03] T restkit.object_mapping:RKMappingOperation.m:583 Skipped mapping of attribute value from keyPath 'SubDiseaseSystemIdMember to keyPath 'subDiseaseId' -- value is unchanged (1886)
2013-07-11 03:18:30.029 cocoaclinical[18773:3f03] T restkit.object_mapping:RKMappingOperation.m:550 Mapping attribute value keyPath 'DisplayNameMember' to 'name'
2013-07-11 03:18:30.029 cocoaclinical[18773:3f03] T restkit.object_mapping:RKMappingOperation.m:572 Mapped attribute value from keyPath 'DisplayNameMember' to 'name'. Value: Acute Lymphocytic Leukemia
2013-07-11 03:18:30.030 cocoaclinical[18773:3f03] T restkit.object_mapping:RKMappingOperation.m:550 Mapping attribute value keyPath 'DiseaseSystemIdMember' to 'diseaseId'
2013-07-11 03:18:30.030 cocoaclinical[18773:3f03] T restkit.object_mapping:RKMappingOperation.m:583 Skipped mapping of attribute value from keyPath 'DiseaseSystemIdMember to keyPath 'diseaseId' -- value is unchanged (161)
2013-07-11 03:18:30.031 cocoaclinical[18773:3f03] D restkit.object_mapping:RKMappingOperation.m:1021 Finished mapping operation successfully...

You do need to set identificationAttributes, generally only one or two attributes though, not everything. Your entities should have some unique identifier.
You also want to add a managedObjectCache to your managed object store. This is the part that allows RestKit to match objects using your identificationAttributes and update the existing items instead of creating new ones.

Related

Field not automatically included in poline

I want to create a purchase order, the fields are not calculated : line.SiteID ; line.LineType ; line.ExpenseAcctID ; line.POAccrualAcctID
while in manual entry everything works correctly
order.OrderType = "RO";
order.Status="H";
order.BranchID=une_commandevente.BranchID;
order = poOrder.CurrentDocument.Insert(order);
order.VendorID = cmdfrs.Usrfournisseur;
order.OrderDate = DateTime.Today;
order.OrderDesc = "XXX";
order.VendorRefNbr="XXX";
poOrder.CurrentDocument.Update(order);
foreach (SOLine une_lignevente in PXSelectReadonly<SOLine,Where<SOLine.orderNbr, Equal<Required<SOLine.orderNbr>>,And<SOLine.orderType,Equal<Required<SOLine.orderType>>>>>.Select(this.Base,une_commandevente.OrderNbr,une_commandevente.OrderType))
{
var une_ligneventeext = une_lignevente.GetExtension<SOLineExt>();
if (une_ligneventeext.Usrfournisseur==cmdfrs.Usrfournisseur)
{
var line = poOrder.Transactions.Insert();
line.OrderType = "RO";
line.InventoryID = une_lignevente.InventoryID;
line.SiteID=3;
line.LineType = "NS";
line.ExpenseAcctID=39367;
line.POAccrualAcctID=39367;
line.OrderQty= une_lignevente.Qty;
line.UOM=une_lignevente.UOM;
poOrder.CurrentDocument.Update(order);
poOrder.Transactions.Update(line);
}
}
poOrder.CurrentDocument.Update(order);
poOrder.Actions.PressSave();
Thanks
Xav
There are a few adjustments needed in your logic:
For the header's cache, use the main view (Document) instead of CurrentDocument
For the header DAC, assign the Key values, then insert the row in the cache and then assign the rest of the values
For the iteration, there is no need to update the header's cache
For the grid DAC, there is no need to explicitly indicate the key values as those are defaulted from the header based on [PXDBDefault] attribute
I'd also recommend to update the cache after there is a known field that triggers events. For instance, entering the Vendor, defaults the vendor location.
Try this modified (and simplified) version:
order.OrderType = "RO";
order = poOrder.Document.Insert(order);
order.OrderDate = DateTime.Today;
order.VendorID = cmdfrs.Usrfournisseur;
poOrder.Document.Update(order);
order.BranchID=une_commandevente.BranchID;
order.OrderDesc = "XXX";
order.VendorRefNbr="XXX";
poOrder.Document.Update(order);
foreach (SOLine une_lignevente in PXSelectReadonly<SOLine,Where<SOLine.orderNbr, Equal<Required<SOLine.orderNbr>>,And<SOLine.orderType,Equal<Required<SOLine.orderType>>>>>.Select(this.Base,une_commandevente.OrderNbr,une_commandevente.OrderType))
{
var une_ligneventeext = une_lignevente.GetExtension<SOLineExt>();
if (une_ligneventeext.Usrfournisseur==cmdfrs.Usrfournisseur)
{
POLine line = new POLine();
line = poOrder.Transactions.Insert(line);
line.InventoryID = une_lignevente.InventoryID;
poOrder.Transactions.Update(line);
line.SiteID=3;
poOrder.Transactions.Update(line);
line.ExpenseAcctID=39367;
line.POAccrualAcctID=39367;
poOrder.Transactions.Update(line);
line.OrderQty= une_lignevente.Qty;
line.UOM=une_lignevente.UOM;
poOrder.Transactions.Update(line);
}
}
poOrder.Actions.PressSave();
Also, I'd recommend testing this in a fresh environment w/o customizations. Create a test button that instantiates the graph and creates a PO with a couple of lines.

Reconstructing an ODataQueryOptions object and GetInlineCount returning null

In an odata webapi call which returns a PageResult I extract the requestUri from the method parameter, manipulate the filter terms and then construct a new ODataQueryOptions object using the new uri.
(The PageResult methodology is based on this post:
http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options )
Here is the raw inbound uri which includes %24inlinecount=allpages
http://localhost:59459/api/apiOrders/?%24filter=OrderStatusName+eq+'Started'&filterLogic=AND&%24skip=0&%24top=10&%24inlinecount=allpages&_=1376341370337
Everything works fine in terms of the data returned except Request.GetInLineCount returns null.
This 'kills' paging on the client side as the client ui elements don't know the total number of records.
There must be something wrong with how I'm constructing the new ODataQueryOptions object.
Please see my code below. Any help would be appreciated.
I suspect this post may contain some clues https://stackoverflow.com/a/16361875/1433194 but I'm stumped.
public PageResult<OrderVm> Get(ODataQueryOptions<OrderVm> options)
{
var incomingUri = options.Request.RequestUri.AbsoluteUri;
//manipulate the uri here to suit the entity model
//(related to a transformation needed for enumerable type OrderStatusId )
//e.g. the query string may include %24filter=OrderStatusName+eq+'Started'
//I manipulate this to %24filter=OrderStatusId+eq+'Started'
ODataQueryOptions<OrderVm> options2;
var newUri = incomingUri; //pretend it was manipulated as above
//Reconstruct the ODataQueryOptions with the modified Uri
var request = new HttpRequestMessage(HttpMethod.Get, newUri);
//construct a new options object using the new request object
options2 = new ODataQueryOptions<OrderVm>(options.Context, request);
//Extract a queryable from the repository. contents is an IQueryable<Order>
var contents = _unitOfWork.OrderRepository.Get(null, o => o.OrderByDescending(c => c.OrderId), "");
//project it onto the view model to be used in a grid for display purposes
//the following projections etc work fine and do not interfere with GetInlineCount if
//I avoid the step of constructing and using a new options object
var ds = contents.Select(o => new OrderVm
{
OrderId = o.OrderId,
OrderCode = o.OrderCode,
CustomerId = o.CustomerId,
AmountCharged = o.AmountCharged,
CustomerName = o.Customer.FirstName + " " + o.Customer.LastName,
Donation = o.Donation,
OrderDate = o.OrderDate,
OrderStatusId = o.StatusId,
OrderStatusName = ""
});
//note the use of 'options2' here replacing the original 'options'
var settings = new ODataQuerySettings()
{
PageSize = options2.Top != null ? options2.Top.Value : 5
};
//apply the odata transformation
//note the use of 'options2' here replacing the original 'options'
IQueryable results = options2.ApplyTo(ds, settings);
//Update the field containing the string representation of the enum
foreach (OrderVm row in results)
{
row.OrderStatusName = row.OrderStatusId.ToString();
}
//get the total number of records in the result set
//THIS RETURNS NULL WHEN USING the 'options2' object - THIS IS MY PROBLEM
var count = Request.GetInlineCount();
//create the PageResult object
var pr = new PageResult<OrderVm>(
results as IEnumerable<OrderVm>,
Request.GetNextPageLink(),
count
);
return pr;
}
EDIT
So the corrected code should read
//create the PageResult object
var pr = new PageResult<OrderVm>(
results as IEnumerable<OrderVm>,
request.GetNextPageLink(),
request.GetInlineCount();
);
return pr;
EDIT
Avoided the need for a string transformation of the enum in the controller method by applying a Json transformation to the OrderStatusId property (an enum) of the OrderVm class
[JsonConverter(typeof(StringEnumConverter))]
public OrderStatus OrderStatusId { get; set; }
This does away with the foreach loop.
InlineCount would be present only when the client asks for it through the $inlinecount query option.
In your modify uri logic add the query option $inlinecount=allpages if it is not already present.
Also, there is a minor bug in your code. The new ODataQueryOptions you are creating uses a new request where as in the GetInlineCount call, you are using the old Request. They are not the same.
It should be,
var count = request.GetInlineCount(); // use the new request that your created, as that is what you applied the query to.

Restkit mapping empty json list with relationship

I've got a problem with empty json list mapping, I've got a JSON response that look this way :
{
id = 1;
image = "http://image.com/image.png";
name = "Test Category #1";
"restricted_position" = 0;
"restricted_time" = 0;
restrictions = {
"restricted_position_detail" = (
);
"restricted_time_detail" = (
);
}
The mapping for the class look like this :
+(RKManagedObjectMapping *)getObjectMapping
{
RKObjectRelationshipMapping *restrictionMapping = [RKObjectRelationshipMapping mappingFromKeyPath:#"restrictions" toKeyPath:#"restrictions" withMapping:[Restriction getObjectMapping]];
RKManagedObjectMapping *mapping = [RKManagedObjectMapping mappingForClass:[PLCategory class] inManagedObjectStore:[RKObjectManager sharedManager].objectStore];
[mapping mapKeyPath:#"id" toAttribute:#"identifiant"];
[mapping mapKeyPath:#"name" toAttribute:#"name"];
[mapping mapKeyPath:#"image" toAttribute:#"imageUrl"];
[mapping mapKeyPath:#"restricted_position" toAttribute:#"restricted_position"];
[mapping mapKeyPath:#"restricted_time" toAttribute:#"restricted_time"];
[mapping addRelationshipMapping:restrictionMapping];
[mapping setPrimaryKeyAttribute:#"identifiant"];
return mapping;
}
The mapping for the Restriction class :
+(RKManagedObjectMapping *)getObjectMapping
{
RKObjectRelationshipMapping *zg_mapping = [RKObjectRelationshipMapping mappingFromKeyPath:#"restricted_time_detail" toKeyPath:#"restricted_time_detail" withMapping:[GeographicalArea getObjectMapping]];
RKObjectRelationshipMapping *ph_mapping = [RKObjectRelationshipMapping mappingFromKeyPath:#"restricted_position_detail" toKeyPath:#"restricted_position_detail" withMapping:[TimeSlot getObjectMapping]];
RKManagedObjectMapping *mapping = [RKManagedObjectMapping mappingForClass:[Restriction class] inManagedObjectStore:[RKObjectManager sharedManager].objectStore];
[mapping addRelationshipMapping:zg_mapping];
[mapping addRelationshipMapping:ph_mapping];
return mapping;
}
No matter what I try I keep getting these annoying errors :
Core Data Save Error
NSLocalizedDescription: The operation couldn’t be completed. (Cocoa error 1580.)
NSValidationErrorKey: restricted_position_detail
NSValidationErrorPredicate: (null)
NSValidationErrorObject:
<Restriction: 0xb9c2f20> (entity: Restriction; id: 0xb9b5d90 <x-coredata:///Restriction/tE6CDEA6C-5A2A-48AE-9454-A8E6CC35F5078> ; data: {
category = "0xb9b67b0 <x-coredata:///PLCategory/tE6CDEA6C-5A2A-48AE-9454-A8E6CC35F5077>";
"restricted_position_detail" = (
);
"restricted_time_detail" = (
);
})
2013-07-08 12:39:00.716 [7442:28513] E restkit.core_data:RKManagedObjectStore.m:263 Core Data Save Error
NSLocalizedDescription: The operation couldn’t be completed. (Cocoa error 1580.)
NSValidationErrorKey: restricted_time_detail
NSValidationErrorPredicate: (null)
NSValidationErrorObject:
<Restriction: 0xb9c2f20> (entity: Restriction; id: 0xb9b5d90 <x-coredata:///Restriction/tE6CDEA6C-5A2A-48AE-9454-A8E6CC35F5078> ; data: {
category = "0xb9b67b0 <x-coredata:///PLCategory/tE6CDEA6C-5A2A-48AE-9454-A8E6CC35F5077>";
"restricted_position_detail" = (
);
"restricted_time_detail" = (
);
})
What I'd like to do is map these empty list with an empty NSArray. Does anyone have a clue how to do that ? is it even possible ?
The problem is that you have made the relationships non-optional. By default they will be empty sets (not arrays), but if you try to save when the sets are empty you will get validation errors because you configured that the relationships have to have at least one connection (that's what non-optional means).
If you want to allow zero connections, make the relationships optional.
Note that you might wan't to make the other end of the relationships non-optional, so you know if you have a GeographicalArea or TimeSlot then it must be connected to a Restriction. This would usually tie in with a Cascade deletion rule.

Dynamics CRM 2011 - Getting too many rows back from a retrievemultiple on salesordersdetail

I have a plugin that is registered Update, Order, Post Operation. In the plugin I perform a retrievemultiple on the salesorderdetail. The problem I'm having is that there are 3 products that make up the order but I am returning 5 rows from the retrieve operation. I have added and deleted the same product multiple times during my testing and I'm not sure if that's what's causing the problem. I was thinking that after deleting a product from the order it may set a flag and get deleted after, but I don't see a status code or state code as an attribute. Why would it return too many rows?
Here is my code...
// Set the properties of the QueryExpression object.
orderDetailQuery.EntityName = "salesorderdetail";
orderDetailQuery.ColumnSet = orderDetailColumnSet;
EntityCollection salesOrderDetail = service.RetrieveMultiple(orderDetailQuery);
orderProductQuery.EntityName = "product";
orderProductQuery.ColumnSet = orderProductColumnSet;
foreach (var orderDetail in salesOrderDetail.Entities)
{
if(orderDetail.Attributes.Contains("productid"))
{
productGuid = ((EntityReference)orderDetail["productid"]).Id;
Entity product = service.Retrieve("product", productGuid, orderProductColumnSet);
}
}
Thank you for the help!!
The code you posted does not show you filtering for the specific Order.
I would expect that to retrieve all entities of that type in the system.
To filter, assuming you are using a QueryByAttribute, is to add an filter along the lines of:
var query = new QueryByAttribute();
query.EntityName = "salesorderdetail";
query.AddAttributeValue("orderid", orderId);//orderId is the Id of the parent Order
orderDetailQuery.EntityName = "salesorderdetail";
orderDetailQuery.ColumnSet = orderDetailColumnSet;
var results = service.RetrieveMultiple(query);
That way you are restricting your query to just products for the given order.
I'm not sure that your filtering is implemented. Here's a shot from the hip on how you could query for instances of SalesOrderDetail entity, fetching the values of fieldName1 and fieldName2 fields provided that the it's linked to the order with guid orderId.
QueryExpression query = new QueryExpression
{
EntityName = "salesorderdetail",
ColumnSet = new ColumnSet("fieldName1", "fieldName2"),
Criteria = new FilterExpression
{
Conditions =
{
new ConditionExpression
{
AttributeName = "orderid",
Operator = ConditionOperator.Equal,
Values = { orderId }
}
}
}
};

Array value replacement from array within array in iPhone

I have array output like this.
{
id = 1;
user = {
name="ABC"
}
},
{
id = 2;
user = {
name="XYZ"
}
},
I have to change id with number 5 and 6 and name with "asd" and "fgh". My array definition is here.
myArray=[[NSMutableArray alloc]initWithArray:statuses];
And here is my approach for chaning of "id".
[[myArray objectAtIndex:0]replaceObjectAtIndex:0 withObject:#"5"];
[[myArray objectAtIndex:0]replaceObjectAtIndex:0 withObject:#"6"];
But I am getting following acception.
[__NSCFDictionary replaceObjectAtIndex:withObject:]: unrecognized selector sent to instance 0x6511810
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFDictionary replaceObjectAtIndex:withObject:]: unrecognized selector sent to instance 0x6511810'
myArray seems to be NSArray which is Immutable.
How did you declare myArray?
EDIT:
First:
myArray = [statuses mutableCopy];
Second:
[[myArray objectAtIndex:0] setObject:#"5" forKey:#"id"];
[[myArray objectAtIndex:1] setObject:#"6" forKey:#"id"];
[[[myArray objectAtIndex:0] objectForKey:#"user"] setObject:#"asd" forKey:#"name"];
[[[myArray objectAtIndex:1] objectForKey:#"user"] setObject:#"fgh" forKey:#"name"];

Resources