@@ -25,7 +25,7 @@ public class MobileServiceSQLiteStore : MobileServiceLocalStore
25
25
/// Note: The default maximum number of parameters allowed by sqlite is 999
26
26
/// See: http://www.sqlite.org/limits.html#max_variable_number
27
27
/// </summary>
28
- private const int MaxParametersPerUpsertQuery = 800 ;
28
+ private const int MaxParametersPerQuery = 800 ;
29
29
30
30
private Dictionary < string , TableDefinition > tableMap = new Dictionary < string , TableDefinition > ( StringComparer . OrdinalIgnoreCase ) ;
31
31
private SQLiteConnection connection ;
@@ -183,64 +183,17 @@ private Task UpsertAsyncInternal(string tableName, IEnumerable<JObject> items, b
183
183
return Task . FromResult ( 0 ) ;
184
184
}
185
185
186
- // Generate the prepared insert statement
187
- string sqlBase = String . Format (
188
- "INSERT OR REPLACE INTO {0} ({1}) VALUES " ,
189
- SqlHelpers . FormatTableName ( tableName ) ,
190
- String . Join ( ", " , columns . Select ( c => c . Name ) . Select ( SqlHelpers . FormatMember ) )
191
- ) ;
192
-
193
- // Use int division to calculate how many times this record will fit into our parameter quota
194
- int batchSize = MaxParametersPerUpsertQuery / columns . Count ;
195
- if ( batchSize == 0 )
196
- {
197
- throw new InvalidOperationException ( string . Format ( Properties . Resources . SQLiteStore_TooManyColumns , MaxParametersPerUpsertQuery ) ) ;
198
- }
199
186
200
- foreach ( var batch in items . Split ( maxLength : batchSize ) )
201
- {
202
- var sql = new StringBuilder ( sqlBase ) ;
203
- var parameters = new Dictionary < string , object > ( ) ;
187
+ this . ExecuteNonQuery ( "BEGIN TRANSACTION" , null ) ;
204
188
205
- foreach ( JObject item in batch )
206
- {
207
- AppendInsertValuesSql ( sql , parameters , columns , item ) ;
208
- sql . Append ( "," ) ;
209
- }
189
+ BatchInsert ( tableName , items , columns . Where ( c => c . Name . Equals ( MobileServiceSystemColumns . Id ) ) . Take ( 1 ) . ToList ( ) ) ;
190
+ BatchUpdate ( tableName , items , columns ) ;
210
191
211
- if ( parameters . Any ( ) )
212
- {
213
- sql . Remove ( sql . Length - 1 , 1 ) ; // remove the trailing comma
214
- this . ExecuteNonQuery ( sql . ToString ( ) , parameters ) ;
215
- }
216
- }
192
+ this . ExecuteNonQuery ( "COMMIT TRANSACTION" , null ) ;
217
193
218
194
return Task . FromResult ( 0 ) ;
219
195
}
220
196
221
- private static void AppendInsertValuesSql ( StringBuilder sql , Dictionary < string , object > parameters , List < ColumnDefinition > columns , JObject item )
222
- {
223
- sql . Append ( "(" ) ;
224
- int colCount = 0 ;
225
- foreach ( var column in columns )
226
- {
227
- if ( colCount > 0 )
228
- sql . Append ( "," ) ;
229
-
230
- JToken rawValue = item . GetValue ( column . Name , StringComparison . OrdinalIgnoreCase ) ;
231
- object value = SqlHelpers . SerializeValue ( rawValue , column . StoreType , column . JsonType ) ;
232
-
233
- //The paramname for this field must be unique within this statement
234
- string paramName = "@p" + parameters . Count ;
235
-
236
- sql . Append ( paramName ) ;
237
- parameters [ paramName ] = value ;
238
-
239
- colCount ++ ;
240
- }
241
- sql . Append ( ")" ) ;
242
- }
243
-
244
197
/// <summary>
245
198
/// Deletes items from local table that match the given query.
246
199
/// </summary>
@@ -373,6 +326,110 @@ private void CreateAllTables()
373
326
}
374
327
}
375
328
329
+ private void BatchUpdate ( string tableName , IEnumerable < JObject > items , List < ColumnDefinition > columns )
330
+ {
331
+ if ( columns . Count <= 1 )
332
+ {
333
+ return ; // For update to work there has to be at least once column besides Id that needs to be updated
334
+ }
335
+
336
+ ValidateParameterCount ( columns . Count ) ;
337
+
338
+ string sqlBase = String . Format ( "UPDATE {0} SET " , SqlHelpers . FormatTableName ( tableName ) ) ;
339
+
340
+ foreach ( JObject item in items )
341
+ {
342
+ var sql = new StringBuilder ( sqlBase ) ;
343
+ var parameters = new Dictionary < string , object > ( ) ;
344
+
345
+ ColumnDefinition idColumn = columns . FirstOrDefault ( c => c . Name . Equals ( MobileServiceSystemColumns . Id ) ) ;
346
+ if ( idColumn == null )
347
+ {
348
+ continue ;
349
+ }
350
+
351
+ foreach ( var column in columns . Where ( c => c != idColumn ) )
352
+ {
353
+ string paramName = AddParameter ( item , parameters , column ) ;
354
+
355
+ sql . AppendFormat ( "{0} = {1}" , SqlHelpers . FormatMember ( column . Name ) , paramName ) ;
356
+ sql . Append ( "," ) ;
357
+ }
358
+
359
+ if ( parameters . Any ( ) )
360
+ {
361
+ sql . Remove ( sql . Length - 1 , 1 ) ; // remove the trailing comma
362
+
363
+ }
364
+
365
+ sql . AppendFormat ( " WHERE {0} = {1}" , SqlHelpers . FormatMember ( MobileServiceSystemColumns . Id ) , AddParameter ( item , parameters , idColumn ) ) ;
366
+
367
+ this . ExecuteNonQuery ( sql . ToString ( ) , parameters ) ;
368
+ }
369
+ }
370
+
371
+ private void BatchInsert ( string tableName , IEnumerable < JObject > items , List < ColumnDefinition > columns )
372
+ {
373
+ if ( columns . Count == 0 ) // we need to have some columns to insert the item
374
+ {
375
+ return ;
376
+ }
377
+
378
+ // Generate the prepared insert statement
379
+ string sqlBase = String . Format (
380
+ "INSERT OR IGNORE INTO {0} ({1}) VALUES " ,
381
+ SqlHelpers . FormatTableName ( tableName ) ,
382
+ String . Join ( ", " , columns . Select ( c => c . Name ) . Select ( SqlHelpers . FormatMember ) )
383
+ ) ;
384
+
385
+ // Use int division to calculate how many times this record will fit into our parameter quota
386
+ int batchSize = ValidateParameterCount ( columns . Count ) ;
387
+
388
+ foreach ( var batch in items . Split ( maxLength : batchSize ) )
389
+ {
390
+ var sql = new StringBuilder ( sqlBase ) ;
391
+ var parameters = new Dictionary < string , object > ( ) ;
392
+
393
+ foreach ( JObject item in batch )
394
+ {
395
+ AppendInsertValuesSql ( sql , parameters , columns , item ) ;
396
+ sql . Append ( "," ) ;
397
+ }
398
+
399
+ if ( parameters . Any ( ) )
400
+ {
401
+ sql . Remove ( sql . Length - 1 , 1 ) ; // remove the trailing comma
402
+ this . ExecuteNonQuery ( sql . ToString ( ) , parameters ) ;
403
+ }
404
+ }
405
+ }
406
+
407
+ private static int ValidateParameterCount ( int parametersCount )
408
+ {
409
+ int batchSize = MaxParametersPerQuery / parametersCount ;
410
+ if ( batchSize == 0 )
411
+ {
412
+ throw new InvalidOperationException ( string . Format ( Properties . Resources . SQLiteStore_TooManyColumns , MaxParametersPerQuery ) ) ;
413
+ }
414
+ return batchSize ;
415
+ }
416
+
417
+ private static void AppendInsertValuesSql ( StringBuilder sql , Dictionary < string , object > parameters , List < ColumnDefinition > columns , JObject item )
418
+ {
419
+ sql . Append ( "(" ) ;
420
+ int colCount = 0 ;
421
+ foreach ( var column in columns )
422
+ {
423
+ if ( colCount > 0 )
424
+ sql . Append ( "," ) ;
425
+
426
+ sql . Append ( AddParameter ( item , parameters , column ) ) ;
427
+
428
+ colCount ++ ;
429
+ }
430
+ sql . Append ( ")" ) ;
431
+ }
432
+
376
433
internal virtual void CreateTableFromObject ( string tableName , IEnumerable < ColumnDefinition > columns )
377
434
{
378
435
ColumnDefinition idColumn = columns . FirstOrDefault ( c => c . Name . Equals ( MobileServiceSystemColumns . Id ) ) ;
@@ -404,6 +461,21 @@ internal virtual void CreateTableFromObject(string tableName, IEnumerable<Column
404
461
// NOTE: In SQLite you cannot drop columns, only add them.
405
462
}
406
463
464
+ private static string AddParameter ( JObject item , Dictionary < string , object > parameters , ColumnDefinition column )
465
+ {
466
+ JToken rawValue = item . GetValue ( column . Name , StringComparison . OrdinalIgnoreCase ) ;
467
+ object value = SqlHelpers . SerializeValue ( rawValue , column . StoreType , column . JsonType ) ;
468
+ string paramName = CreateParameter ( parameters , value ) ;
469
+ return paramName ;
470
+ }
471
+
472
+ private static string CreateParameter ( Dictionary < string , object > parameters , object value )
473
+ {
474
+ string paramName = "@p" + parameters . Count ;
475
+ parameters [ paramName ] = value ;
476
+ return paramName ;
477
+ }
478
+
407
479
/// <summary>
408
480
/// Executes a sql statement on a given table in local SQLite database.
409
481
/// </summary>
0 commit comments