Initial sync and duplicate objects with same primary key inside Realm Mobile Platform - realm

I'm trying to turn on realm sync on device which already contains some data, that already persists on server. When new user connects to realm, it should merge local realm data with synced realm data. But this code is launched before initial sync happens. Since there is no data from server is received yet, app creates some records in synchronized realm. When sync finishes I see same data twice. Records I've just created and data fetched from server. With same primary key.
See code below for an example:
RLMRealmConfiguration *config = [[RLMRealmConfiguration alloc] init];
config.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:user realmURL:self.realmURL];
NSError *error = nil;
RLMRealm *newRealm = [RLMRealm realmWithConfiguration:config error:&error];
if(newRealm != nil && error == nil)
{
[newRealm beginWriteTransaction];
for(ModelFolder *folder in [ModelFolder allObjectsInRealm:curRealm])
{
ModelFolder *newFolder = [ModelFolder objectInRealm:newRealm forPrimaryKey:folder.uuid];
if(newFolder == nil)
[ModelFolder createInRealm:newRealm withValue:folder];
}
[newRealm commitWriteTransaction];
}
Is there a way to detect, that realm is completed initial sync?
UPD: Few more details.
ModelFolder contains #property RLMArray<ModelBookmark *><ModelBookmark> *bookmarks; And when I create Folder, that equals some folder that will be fetched in a few seconds they merged correctly. But. Bookmarks inside Folder object is not deduplicated and we get something like this:
RLMResults <0x802082d0> (
[0] ModelFolder {
uuid = 2615AB34-1C08-4E7B-8D49-6E02EDBCDF89;
name = (null);
descr = (null);
shareURL = (null);
date = 1484566331137;
bookmarks = RLMArray <0x806c78d0> (
[0] ModelBookmark {
uuid = C752FCEB-65CB-47C8-8CF4-6CA44C119ECC;
name = (null);
descr = (null);
shareURL = (null);
date = 1484566331137;
folderUuid = 2615AB34-1C08-4E7B-8D49-6E02EDBCDF89;
longitude = 27.54834598813616;
latitude = 53.91333128839566;
mapZoom = 11.73785983313041;
category = 0;
visible = 1;
},
[1] ModelBookmark {
uuid = C752FCEB-65CB-47C8-8CF4-6CA44C119ECC;
name = (null);
descr = (null);
shareURL = (null);
date = 1484566331137;
folderUuid = 2615AB34-1C08-4E7B-8D49-6E02EDBCDF89;
longitude = 27.54834598813616;
latitude = 53.91333128839566;
mapZoom = 11.73785983313041;
category = 0;
visible = 1;
}
);
tracks = RLMArray <0x806fb120> (
);
opened = 1;
}
)

Unfortunately merging of the ordered lists is not supported currently (until https://github.com/realm/realm-core/issues/1206 is implemented). For now you have to manually deduplicate list items, you can use the same workaround we use in RealmTasks app, see https://github.com/realm/RealmTasks/pull/180 for implementation details.

Related

Why do I not get all Channel items visible in Android TV provider database?

First of all, this is the /data/data/com.android.providers.tv/databases/tv.db
Then in my code I applied this code snippet to retrieve the channels:
TvInputManager tv = (TvInputManager)getApplicationContext().getSystemService(Context.TV_INPUT_SERVICE);
List<TvInputInfo> list = tv.getTvInputList();
ContentResolver cr = getContentResolver();
Iterator<TvInputInfo> it = list.iterator();
while(it.hasNext()) {
TvInputInfo aux = it.next();
Uri uri = TvContract.buildChannelsUriForInput(aux.getId());
Cursor cur = cr.query(uri, projection, null, null ,null);
cur.moveToFirst();
do {
val channel = Channel.fromCursor(channelCursor)
Log.d("Log", "New channel ${channel.id} : ${channel.displayName} : ${channel.packageName}"}
} while (channelCursor.moveToNext() && channelCursor.isLast.not())
}
Unfortunately, I get as input_id for TvInputInfo aux only
com.google.android.videos/.tv.usecase.tvinput.playback.TvInputService
thus my cursor returns only 1 Channel with _id = 4.
Despite, I did neither got the input_id's _id=22 for amazon nor _id=25 for netflix, as listed in the screenshot above, I would like to get all above shown 18 Channels.
How can I query all Channels, also those with an empty input_id?
You get restricted by TvContract.buildChannelsUriForInput(aux.getId()); what depends on input_id. When you take TvContractCompat.Channels.CONTENT_URI for Uri, you will get:
Uri uri = TvContractCompat.Channels.CONTENT_URI;
Cursor cur = getContentResolver().query(uri, projection, null, null ,null);
cur.moveToFirst();
do {
val channel = Channel.fromCursor(channelCursor)
Log.d("Log", "New channel ${channel.id} : ${channel.displayName} : ${channel.packageName}"}
} while (channelCursor.moveToNext())

How to create multi items/records or save item/record array at one time in client script file

I want to create multiple records at the same time using client script. This is what I'm doing:
var ceateDatasource = app.datasources.Reservation.modes.create;
var newItem = ceateDatasource.item;
newItem.User = user; //'eric'
newItem.Description = description; //'000'
newItem.Location_Lab_fk = lab.value.Id; //'T'
newItem.Area_fk = area.value.Id; //'L'
newItem.Equipment_fk = equipment.value.Id; //'S'
for(var i = 0 ; i < 3; i ++) {
newItem.Start_Date = startDate;
newItem.Start_Hours = '03';
newItem.Start_Minutes = '00';
newItem.End_Date = startDate;
newItem.End_Hours = '23';
newItem.End_Minutes = '30';
// Create the new item
ceateDatasource.createItem();
}
But the result I'm getting is this one:
The three records are created but the only the first one has data. The other two records have empty values on their fields. How can I achieve this?
Thanks.
Update(2019-3-27):
I was able to make it work by putting everything inside the for loop block. However, I have another question.
Is there any method like the below sample code?
var recordData = [Data1, Data2, Data3]
var ceateDatasource;
var newItem = new Array(recordData.length) ;
for(var i = 0 ; i < recordData.length; i ++) {
ceateDatasource = app.datasources.Reservation.modes.create;
newItem[i] = ceateDatasource.item;
newItem[i].User = recordData[i].user;
newItem[i].Description = recordData[i].Description;
newItem[i].Location_Lab_fk = recordData[i].Location_Lab_fk;
newItem[i].Area_fk = recordData[i].Area_fk;
newItem[i].Equipment_fk = recordData[i].Equipment_fk;
newItem[i].Start_Date = recordData[i].Start_Date;
newItem[i].Start_Hours = recordData[i].Start_Hours;
newItem[i].Start_Minutes = recordData[i].Start_Minutes;
newItem[i].End_Date = recordData[i].End_Date;
newItem[i].End_Hours = recordData[i].End_Hours;
newItem[i].End_Minutes = recordData[i].End_Minutes;
}
// Create the new item
ceateDatasource.createItem();
First, it prepares an array 'newItem' and only calls 'ceateDatasource.createItem()' one time to save all new records(or items).
I try to use this method, but it only saves the last record 'newItem[3]'.
I need to write a callback function in 'ceateDatasource.createItem()' but Google App Maker always show a warning "Don't make functions within a loop". So, are there any methods to call 'createItem()' one time to save several records? Or are there some functions like 'array.push' which can be used?
Thanks.
As per AppMaker's official documentation:
A create datasource is a datasource used to create items in a particular data source. Its item property is always populated by a draft item which can be bound to or set programmatically.
What you are trying to do is create three items off the same draft item. That why you see the result you get. If you want to create multiple items, you need to create a draft item for each one, hence all you need to do is put all your code inside the for loop.
for(var i = 0 ; i < 3; i ++) {
var ceateDatasource = app.datasources.Reservation.modes.create;
var newItem = ceateDatasource.item;
newItem.User = user; //'eric'
newItem.Description = description; //'000'
newItem.Location_Lab_fk = lab.value.Id; //'T'
newItem.Area_fk = area.value.Id; //'L'
newItem.Equipment_fk = equipment.value.Id; //'S'
newItem.Start_Date = startDate;
newItem.Start_Hours = '03';
newItem.Start_Minutes = '00';
newItem.End_Date = startDate;
newItem.End_Hours = '23';
newItem.End_Minutes = '30';
// Create the new item
ceateDatasource.createItem();
}
If you want to save several records at the same time using client script, then what you are looking for is the Manual Save Mode. So all you have to do is go to your model's datasource and click on the checkbox "Manual Save Mode".
Then use the same code as above. The only difference is that in order to persist the changes to the server, you need to explicitly save changes. So all you have to do is add the following after the for loop block:
app.datasources.Reservation.saveChanges(function(){
//TODO: Callback handler
});

Copy table data from one object to another object AX 2012

I'm trying to do a Job which used to Copy and insert Journal Names from one entity to another entity. Below code able to handle only fields only I hardcoded. But I'm trying to do a code which will be useful in future i.e if we add new custom field to the table. My code should able to copy data of that newly custom field added.
static void sa_CopyData(Args _args)
{
LedgerJournalName tblLedgerJournalNameSource, tblLedgerJournalNameDesination;
NumberSequenceTable tblNST, tblNSTCopy;
NumberSequenceScope tblNSS;
Counter countIN, countOUT;
DataAreaId sourceEntity, destinationEntity;
sourceEntity = 'CNUS';
destinationEntity = 'CNEN';
//Journal Names creation
changeCompany(sourceEntity)
{
tblLedgerJournalNameSource = null;
while select * from tblLedgerJournalNameSource
where tblLedgerJournalNameSource.dataAreaId == sourceEntity
{
++countIN;
tblNSTcopy = null; tblNST = null; tblNSS = null;
select * from tblNSTcopy
where tblNSTCopy.RecId == tblLedgerJournalNameSource.NumberSequenceTable;
changeCompany(destinationEntity)
{
tblNST = null; tblNSS = null; tblLedgerJournalNameDesination = null;
select * from tblNST
join tblNSS
where tblNST.NumberSequenceScope == tblNSS.RecId
&& tblNST.NumberSequence == tblNSTCopy.NumberSequence
&& tblNSS.DataArea == destinationEntity;
//Insert only if Journal name is not exists
if(!(LedgerJournalName::find(tblLedgerJournalNameSource.JournalName)))
{
ttsBegin;
tblLedgerJournalNameDesination.initValue();
tblLedgerJournalNameDesination.JournalName = tblLedgerJournalNameSource.JournalName;
tblLedgerJournalNameDesination.Name = tblLedgerJournalNameSource.Name;
tblLedgerJournalNameDesination.JournalType = tblLedgerJournalNameSource.JournalType;
tblLedgerJournalNameDesination.ApproveActive = tblLedgerJournalNameSource.ApproveActive;
tblLedgerJournalNameDesination.ApproveGroupId = tblLedgerJournalNameSource.ApproveGroupId;
tblLedgerJournalNameDesination.NoAutoPost = tblLedgerJournalNameSource.NoAutoPost;
tblLedgerJournalNameDesination.WorkflowApproval = tblLedgerJournalNameSource.WorkflowApproval;
tblLedgerJournalNameDesination.Configuration = tblLedgerJournalNameSource.Configuration;
if (!tblNST.RecId)
{
info(strFmt('Number Sequence is not updated for %1 > Entity %2', tblLedgerJournalNameDesination.JournalName, destinationEntity));
}
tblLedgerJournalNameDesination.NumberSequenceTable = tblNST.recid;
tblLedgerJournalNameDesination.NewVoucher = tblLedgerJournalNameSource.NewVoucher;
tblLedgerJournalNameDesination.BlockUserGroupId = tblLedgerJournalNameSource.BlockUserGroupId;
tblLedgerJournalNameDesination.FixedOffsetAccount = tblLedgerJournalNameSource.FixedOffsetAccount;
tblLedgerJournalNameDesination.OffsetAccountType = tblLedgerJournalNameSource.OffsetAccountType;
tblLedgerJournalNameDesination.OffsetLedgerDimension = tblLedgerJournalNameSource.OffsetLedgerDimension;
tblLedgerJournalNameDesination.LedgerJournalInclTax = tblLedgerJournalNameSource.LedgerJournalInclTax;
tblLedgerJournalNameDesination.insert();
ttsCommit;
++countOUT;
}
}
}
}
info(strFmt('Journal Names Creation > Read: %1, Inserted: %2', countIN, countOUT));
}
Please let me know if you have any suggestions.
Use buf2buf(from, to). https://msdn.microsoft.com/en-us/library/global.buf2buf.aspx
It does not perform an insert, so after you do buf2buf() you can modify any fields you want in the target before doing an insert.
You could do tblLedgerJournalNameDesination.data(tblLedgerJournalNameSource); but that also copies system fields, which you most likely do not want.
You can also look at the source code behind Global::buf2Buf(from, to) and modify that logic if you want.

DynamoDB Xcode6 Swift using three columns as key

I am trying to use a DynamoDB table to store this data:
DartsPlayerInsultTable
CustomerId String
PlayerId String
PlayerInsult String
Using the method (concept, not code) described here:
https://java.awsblog.com/post/Tx3GYZEVGO924K4/The-DynamoDBMapper-Local-Secondary-Indexes-and-You
here:
http://mobile.awsblog.com/post/TxTCW7KW8BGZAF/Amazon-DynamoDB-on-Mobile-Part-4-Local-Secondary-Indexes
and here:
http://labs.journwe.com/2013/12/15/dynamodb-secondary-indexes/comment-page-1/#comment-116
I want to have multiple insult records per customer-player.
CustomerId is my Hash Key
PlayerId is my Range Key
and I a trying to use PlayerInsult in a key so that
a second PlayerInsult value inserts a second record
rather than replacing the existing one.
Have tried both Global and Secondary indexes for this,
but if I try to add a row with a new insult, it still
replaces the insult with the same customer-player key
rather than adding a new one.
Any suggestions on the best approach to use for this is
DynanoDB? Do I need to create a hybrid column for a range-key?
Trying to keep this simple...
class func createDartsPlayerInsultTable() -> BFTask {
let dynamoDB = AWSDynamoDB.defaultDynamoDB()
let hashKeyAttributeDefinition = AWSDynamoDBAttributeDefinition()
hashKeyAttributeDefinition.attributeName = "CustomerId"
hashKeyAttributeDefinition.attributeType = AWSDynamoDBScalarAttributeType.S
let hashKeySchemaElement = AWSDynamoDBKeySchemaElement()
hashKeySchemaElement.attributeName = "CustomerId"
hashKeySchemaElement.keyType = AWSDynamoDBKeyType.Hash
let rangeKeyAttributeDefinition = AWSDynamoDBAttributeDefinition()
rangeKeyAttributeDefinition.attributeName = "PlayerId"
rangeKeyAttributeDefinition.attributeType = AWSDynamoDBScalarAttributeType.S
let rangeKeySchemaElement = AWSDynamoDBKeySchemaElement()
rangeKeySchemaElement.attributeName = "PlayerId"
rangeKeySchemaElement.keyType = AWSDynamoDBKeyType.Range
/*
let indexRangeKeyAttributeDefinition = AWSDynamoDBAttributeDefinition()
indexRangeKeyAttributeDefinition.attributeName = "PlayerInsult"
indexRangeKeyAttributeDefinition.attributeType = AWSDynamoDBScalarAttributeType.S
let rangeKeySchemaElement = AWSDynamoDBKeySchemaElement()
rangeKeySchemaElement.attributeName = "PlayerId"
rangeKeySchemaElement.keyType = AWSDynamoDBKeyType.Range
let indexRangeKeyElement = AWSDynamoDBKeySchemaElement()
indexRangeKeyElement.attributeName = "PlayerInsult"
indexRangeKeyElement.keyType = AWSDynamoDBIndexRangeKeyType.
*/
//Add non-key attributes
let playerInsultAttrDef = AWSDynamoDBAttributeDefinition()
playerInsultAttrDef.attributeName = "PlayerInsult"
playerInsultAttrDef.attributeType = AWSDynamoDBScalarAttributeType.S
let provisionedThroughput = AWSDynamoDBProvisionedThroughput()
provisionedThroughput.readCapacityUnits = 5
provisionedThroughput.writeCapacityUnits = 5
// CREATE GLOBAL SECONDARY INDEX
/*
let gsi = AWSDynamoDBGlobalSecondaryIndex()
let gsiArray = NSMutableArray()
let gsiHashKeySchema = AWSDynamoDBKeySchemaElement()
gsiHashKeySchema.attributeName = "PlayerId"
gsiHashKeySchema.keyType = AWSDynamoDBKeyType.Hash
let gsiRangeKeySchema = AWSDynamoDBKeySchemaElement()
gsiRangeKeySchema.attributeName = "PlayerInsult"
gsiRangeKeySchema.keyType = AWSDynamoDBKeyType.Range
let gsiProjection = AWSDynamoDBProjection()
gsiProjection.projectionType = AWSDynamoDBProjectionType.All;
gsi.keySchema = [gsiHashKeySchema,gsiRangeKeySchema];
gsi.indexName = "PlayerInsult";
gsi.projection = gsiProjection;
gsi.provisionedThroughput = provisionedThroughput;
gsiArray .addObject(gsi)
*/
// CREATE LOCAL SECONDARY INDEX
let lsi = AWSDynamoDBLocalSecondaryIndex()
let lsiArray = NSMutableArray()
let lsiHashKeySchema = AWSDynamoDBKeySchemaElement()
lsiHashKeySchema.attributeName = "CustomerId"
lsiHashKeySchema.keyType = AWSDynamoDBKeyType.Hash
let lsiRangeKeySchema = AWSDynamoDBKeySchemaElement()
lsiRangeKeySchema.attributeName = "PlayerInsult"
lsiRangeKeySchema.keyType = AWSDynamoDBKeyType.Range
let lsiProjection = AWSDynamoDBProjection()
lsiProjection.projectionType = AWSDynamoDBProjectionType.All;
lsi.keySchema = [lsiHashKeySchema,lsiRangeKeySchema];
lsi.indexName = "PlayerInsult";
lsi.projection = lsiProjection;
//lsi.provisionedThroughput = provisionedThroughput;
lsiArray .addObject(lsi)
//Create TableInput
let createTableInput = AWSDynamoDBCreateTableInput()
createTableInput.tableName = DartsPlayerInsultTableName;
createTableInput.attributeDefinitions = [hashKeyAttributeDefinition, rangeKeyAttributeDefinition, playerInsultAttrDef]
//createTableInput.attributeDefinitions = [hashKeyAttributeDefinition, rangeKeyAttributeDefinition]
createTableInput.keySchema = [hashKeySchemaElement, rangeKeySchemaElement]
createTableInput.provisionedThroughput = provisionedThroughput
//createTableInput.globalSecondaryIndexes = gsiArray as [AnyObject]
createTableInput.localSecondaryIndexes = lsiArray as [AnyObject]
return dynamoDB.createTable(createTableInput).continueWithSuccessBlock({ (var task:BFTask!) -> AnyObject! in
if ((task.result) != nil) {
// Wait for up to 4 minutes until the table becomes ACTIVE.
let describeTableInput = AWSDynamoDBDescribeTableInput()
describeTableInput.tableName = DartsPlayerInsultTableName;
task = dynamoDB.describeTable(describeTableInput)
for var i = 0; i < 16; i++ {
task = task.continueWithSuccessBlock({ (task:BFTask!) -> AnyObject! in
let describeTableOutput:AWSDynamoDBDescribeTableOutput = task.result as! AWSDynamoDBDescribeTableOutput
let tableStatus = describeTableOutput.table.tableStatus
if tableStatus == AWSDynamoDBTableStatus.Active {
return task
}
sleep(15)
return dynamoDB .describeTable(describeTableInput)
})
}
}
return task
})
}
Putting this as an answer and not another comment in case it gets long...
It sounds like the average user's insults might fit into a single record. With the disclaimer that I know absolutely nothing about swift, this might at least be something relatively simple. Keep your customer and player keys. Before you persist the insults, turn the whole list into one big string using whatever version of join("|") swift has. When you fetch the record, do a split("|") to get your list back. (Just be a little judicious with your choice of separators, I'm only using "|" as an example, you don't want to choose something that might appear in an insult...)
There's going to be that one user with enough insults to take you over the 400kb object limit. Set a max list size constant in your code -- when you turn your lists into strings to persist them to dynamo, check the player's list length against that limit. If you exceed it, break your list into chunks of that size and use hash and range keys like ("foo", "bar"), ("foo", "bar1"), ("foo", "bar2"), etc. Yes, the first one does not have a bucket number at the end...
When you query for the data, just do a straight query first and assume you'll be in the good case (just "foo" and "bar", no other buckets). When you unpack that first list, check its length. If it's equal to your max list size constant, you know that you got a "bad" user and need to do a range query. That second one can use the hash key "foo" and the range "bar" to "bar9999". You will fetch back all those buckets with that range query. Unpack and concatenate all the lists.
This is a little gory, but it should also ultimately be straight ahead to code up. Hopefully it's still simple enough to hook into the patterns you mentioned.
What I decided to do was make a conventional dynamodb table with just one hash key, but the new hash key is a combined string of:
CustomerId + "|" + PlayerId
It is not too hard to maintain synchrony between players and insults tables because once a player is inserted into the player table, modifying the player name results in a new row being inserted. Thus, insults do not need to be modified if the player name changes. You only need to cleanup insults if a player is deleted.
This update behavior is just the way dynamodb works if you make Player name a hash key, which I did to insure they were unique.

. The An object with the same key already exists in the ObjectStateManagerObjectStateManager cannot track multiple objects with the same key

I am trying to simply update the entity object and I get this error.. All the googling on the error I did takes me to complex explanations... can anyone put it simply?
I am working of of this simple tutorial
http://aspalliance.com/1919_ASPNET_40_and_the_Entity_Framework_4__Part_2_Perform_CRUD_Operations_Using_the_Entity_Framework_4.5
else
{
//UPDATE
int iFid = Int32.Parse(fid.First().fid.ToString());
oFinancial.fid = iFid;
oFinancial.mainqtr = currentQuarter;
oFinancial.mainyear = currentYear;
oFinancial.qtr = Int32.Parse(currentQuarter);
oFinancial.year = Int32.Parse(currentYear);
oFinancial.updatedate = DateTime.Now;
// ObjectStateEntry ose = null;
// if (!dc.ObjectStateManager.TryGetObjectStateEntry(oFinancial.EntityKey, out ose))
// {
dc.financials.Attach(oFinancial);
// }
dc.ObjectStateManager.ChangeObjectState(oFinancial, System.Data.EntityState.Modified);
}
dc.SaveChanges();
here is what is higher up in the code that I use simple to get me the primary key value.. probably a better way but it works.
var fid = from x in dc.financials
where iPhaseID == x.phaseid &&
strTaskID == x.ftaskid &&
strFundType == x.fundtype &&
iCurrentQuarter == x.qtr &&
iCurrentYear == x.year
select x;
If the oFinancial object came from your dc and you never manually detached it, then there is no reason to call the Attach method or to mess with the ObjectStateManager. As long as the dc knows about the object (which it does unless you detach it), then the ObjectStateManager will keep track of any changes you make and update them accordingly when you call dc.SaveChanges().
EDIT: Here's a refactored version of what you posted, hope it helps:
else {
//UPDATE
// as long as oFinancial was never detatched after you retrieved
// it from the "dc", then you don't have to re-attach it. And
// you should never need to manipulate the primary key, unless it's
// not generated by the database, and you don't already have another
// object in the "dc" with the same primary key value.
int iFid = Int32.Parse(fid.First().fid.ToString());
oFinancial.fid = iFid;
oFinancial.mainqtr = currentQuarter;
oFinancial.mainyear = currentYear;
oFinancial.qtr = Int32.Parse(currentQuarter
oFinancial.year = Int32.Parse(currentYear);
oFinancial.updatedate = DateTime.Now;
}
dc.SaveChanges();
One other thing: if iFid is the primary key, then you shouldn't mess with it as long as this object came from the dc. I believe the problem is that you're resetting the primary key (iFid) to the same value of another object within the dc, and EF4 is barking because you can't have two rows with the same primary key value in a table.

Resources