/// <summary> /// Writes the file with isolatedStorageSchema information. /// </summary> /// <param name="stream">Stream to which to write</param> /// <param name="uri">Scope uri to be written</param> /// <param name="scope">Scope Name</param> /// <param name="storageSchema">Schema to be written</param> private void WriteSchemaFile(Stream stream, Uri uri, string scope, OfflineSchema storageSchema) { // Write data as text, so create the stream reader. using (var writer = new StreamWriter(stream)) { // Write the text version of the Uri. writer.WriteLine(uri.AbsoluteUri); writer.WriteLine(scope); // Get the list of types as strings and sort to make comparison // faster when reading. List <string> types = (from type in storageSchema.Collections select type.FullName).ToList(); types.Sort(); // Write the types. foreach (string type in types) { writer.WriteLine(type); } } }
/// <summary> /// Constructor for the WinEightStorageOfflineContext /// </summary> /// <param name="schema">The WinEightStorageSchema that specifies the set of the collections for the c.</param> /// <param name="scopeName">The scope name used to identify the scope on the service.</param> /// <param name="cachePath">Path in isolated storage where the data will be stored.</param> /// <param name="uri">Uri of the scope. Used to intialize the CacheController.</param> /// <remarks> /// If the Uri specified is different from the one that is stored in the cache path, the /// Load method will throw an InvalidOperationException. /// </remarks> public WinEightContext(OfflineSchema schema, string scopeName, string cachePath, Uri uri) { if (schema == null) { throw new ArgumentNullException("OfflineSchema"); } if (string.IsNullOrEmpty(scopeName)) { throw new ArgumentNullException("scopeName"); } if (string.IsNullOrEmpty(cachePath)) { throw new ArgumentNullException("cachePath"); } if (uri == null) { throw new ArgumentNullException("uri"); } this.isDisposed = false; this.schema = schema; this.scopeUri = uri; this.scopeName = scopeName; this.cachePath = cachePath; this.StorageHandler = new StorageHandler(schema, cachePath); this.saveSyncLock = new AutoResetLock(); this.CreateCacheController(); }
/// <summary> /// Get total number of changes of sqlite database /// </summary> /// <param name="schema">All Tables</param> /// <param name="lastModifiedDate">Changes since this date</param> internal long GetChangeCount(OfflineSchema schema, DateTime lastModifiedDate) { long totalCount = 0; using (SQLiteDatabaseConnection connection = SQLitePCL.pretty.SQLite3.Open(localFilePath)) { try { foreach (var ty in schema.Collections) { // Get mapping from my type var map = manager.GetMapping(ty); // Create query to select changes var querySelect = SQLiteConstants.SelectChangeCount; querySelect = String.Format(querySelect, map.TableName); var count = connection.Query(querySelect, P(lastModifiedDate)).SelectScalarInt().First(); Debug.WriteLine($"Table {map.TableName} has {count} changes"); totalCount += count; } } catch (Exception ex) { Debug.WriteLine(ex.Message); throw; } } Debug.WriteLine($"Total change count: {totalCount}"); return(totalCount); }
/// <summary> /// Constructor for the SQLiteContext /// </summary> /// <param name="schema">The OfflineSchema that specifies the set of the collections.</param> /// <param name="scopeName">The scope name used to identify the scope on the service.</param> /// <param name="databasePath">Name of the database used to store entities.</param> /// <param name="uri">Uri of the scope. Used to intialize the CacheController.</param> /// <param name="cookieContainer"></param> /// <param name="uploadBatchSize">If set, determines that the upload should be batched and the maximum number of rows to send in one batch (row count - not KB!)</param> /// <remarks> /// If the Uri specified is different from the one that is stored in the cache path, the /// Load method will throw an InvalidOperationException. /// 1/11/2015 Added an optional parameter to allow setting cookies /// </remarks> public SQLiteContext(OfflineSchema schema, string scopeName, string databasePath, Uri uri, CookieContainer cookieContainer = null, int uploadBatchSize = -1) { if (schema == null) { throw new ArgumentNullException("schema"); } if (string.IsNullOrEmpty(scopeName)) { throw new ArgumentNullException("scopeName"); } if (string.IsNullOrEmpty(databasePath)) { throw new ArgumentNullException("databasePath"); } if (uri == null) { throw new ArgumentNullException("uri"); } this.isDisposed = false; this.schema = schema; this.scopeUri = uri; this.scopeName = scopeName; // set cookiecontainer this.cookieContainer = cookieContainer; this.uploadBatchSize = uploadBatchSize; this.databaseName = databasePath; bool isPath = databasePath.Contains('/') || databasePath.Contains('\\'); string localPath = databaseName; // allow to explicitly specify full path - relevant e.g. for unittests if (isPath) { this.databaseName = Path.GetFileName(databaseName); } else { #if (WINDOWS_PHONE || NETFX_CORE) localPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, databaseName); #elif (__ANDROID__) localPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), databaseName); #elif (NET) localPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), databaseName); #elif (__IOS__) // we need to put in /Library/ on iOS5.1 to meet Apple's iCloud terms // (they don't want non-user-generated data in Documents) string documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); // Documents folder string libraryPath = Path.Combine(documentsPath, "..", "Library"); localPath = Path.Combine(libraryPath, databaseName); #endif } this.Manager = new SQLiteManager(schema, localPath); this.CreateCacheController(); }
/// <summary> /// Ctor /// </summary> public SQLiteManager(OfflineSchema schema, String localfilePath) { this.schema = schema; this.localFilePath = localfilePath; this.sqliteHelper = new SQLiteHelper(this.localFilePath, this); }
/// <summary> /// Constructor which initializes the data. /// </summary> /// <param name="schema">Schema for the data</param> public CacheData(OfflineSchema schema) { Collections = new Dictionary <Type, OfflineCollection>(); SyncConflicts = new List <SyncConflict>(); SyncErrors = new List <SyncError>(); CreateCollections(schema); }
/// <summary> /// Constructor which initializes the data. /// </summary> /// <param name="schema">Schema for the data</param> public CacheData(OfflineSchema schema) { Collections = new Dictionary<Type, OfflineCollection>(); SyncConflicts = new List<SyncConflict>(); SyncErrors = new List<SyncError>(); CreateCollections(schema); }
/// <summary> /// Get total number of changes of sqlite database /// </summary> /// <param name="schema">All Tables</param> /// <param name="lastModifiedDate">Changes since this date</param> internal long GetChangeCount(OfflineSchema schema, DateTime lastModifiedDate) { long totalCount = 0; using (SQLiteConnection connection = new SQLiteConnection(localFilePath)) { try { foreach (var ty in schema.Collections) { // Get mapping from my type var map = manager.GetMapping(ty); // Create query to select changes var querySelect = SQLiteConstants.SelectChangeCount; querySelect = String.Format(querySelect, map.TableName); // Prepare command using (var stmt = connection.Prepare(querySelect)) { try { // Set Values BindParameter(stmt, 1, lastModifiedDate); stmt.Step(); var count = stmt.GetInteger(0); Debug.WriteLine($"Table {map.TableName} has {count} changes"); totalCount += count; } finally { stmt.Reset(); stmt.ClearBindings(); } } } } catch (Exception ex) { Debug.WriteLine(ex.Message); throw; } } Debug.WriteLine($"Total change count: {totalCount}"); return(totalCount); }
/// <summary> /// Creates the collections for the types in the schema /// </summary> /// <param name="schema">Schema for which to create collections</param> private void CreateCollections(OfflineSchema schema) { Type collectionType = typeof(OfflineCollection <,>); foreach (Type t in schema.Collections) { // Create the generic type for the type in the collection. Type objectType = typeof(Object); Type generic = collectionType.MakeGenericType(objectType, t); OfflineCollection collection = (OfflineCollection)Activator.CreateInstance(generic); Collections[t] = collection; } }
/// <summary> /// Constructor for the SQLiteContext /// </summary> /// <param name="schema">The OfflineSchema that specifies the set of the collections.</param> /// <param name="scopeName">The scope name used to identify the scope on the service.</param> /// <param name="datbaseName">Name of the database used to store entities.</param> /// <param name="uri">Uri of the scope. Used to intialize the CacheController.</param> /// <remarks> /// If the Uri specified is different from the one that is stored in the cache path, the /// Load method will throw an InvalidOperationException. /// </remarks> public SQLiteContext(OfflineSchema schema, string scopeName, string datbaseName, Uri uri) { if (schema == null) { throw new ArgumentNullException("OfflineSchema"); } if (string.IsNullOrEmpty(scopeName)) { throw new ArgumentNullException("scopeName"); } if (string.IsNullOrEmpty(datbaseName)) { throw new ArgumentNullException("cachePath"); } if (uri == null) { throw new ArgumentNullException("uri"); } this.isDisposed = false; this.schema = schema; this.scopeUri = uri; this.scopeName = scopeName; this.databaseName = datbaseName; #if (WINDOWS_PHONE || NETFX_CORE) var localPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, databaseName); #elif (__ANDROID__) var localPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), databaseName); #elif (__IOS__) // we need to put in /Library/ on iOS5.1 to meet Apple's iCloud terms // (they don't want non-user-generated data in Documents) string documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); // Documents folder string libraryPath = Path.Combine(documentsPath, "..", "Library"); var localPath = Path.Combine(libraryPath, databaseName); #endif this.Manager = new SQLiteManager(schema, localPath); this.CreateCacheController(); }
/// <summary> /// Method that verifies a previously cached isolatedStorageSchema and uri (if they exist) with the current isolatedStorageSchema and uri. /// </summary> /// <param name="offlineSchema">Schema to verify</param> /// <param name="uri">Uri to verify</param> /// <param name="scope">The scope name that the client will be accessing on the service</param> private async Task CheckSchemaAndUriAsync(OfflineSchema offlineSchema, Uri uri, string scope, CancellationToken cancellationToken, IProgress<SyncProgressEvent> progress) { await Task.Run(() => { DateTime durationStartDate = DateTime.Now; this.Configuration = this.Manager.ReadConfiguration(scope); if (progress != null) progress.Report(new SyncProgressEvent(SyncStage.ReadingConfiguration, DateTime.Now.Subtract(durationStartDate))); if (this.Configuration != null) { // Verify the scope uri. if (this.Configuration.ServiceUri.AbsoluteUri != uri.AbsoluteUri) throw new ArgumentException( "Specified uri does not match uri previously used for the specified database"); // Verify the types. List<Type> userTypes = offlineSchema.Collections.ToList(); // Sort by name (the class Type isn't sortable) userTypes.Sort((x, y) => String.Compare(x.FullName, y.FullName, StringComparison.Ordinal)); if (userTypes.Count != this.Configuration.Types.Count) throw new ArgumentException( "Specified offlineSchema does not match database Offline schema previously used for cache path"); // Fix this.Configuration.Types.Sort((x, y) => String.Compare(x, y, StringComparison.Ordinal)); if (userTypes.Where( (t, i) => t.FullName != this.Configuration.Types[i]).Any()) throw new ArgumentException( "Specified offlineSchema does not match database Offline schema previously used for cache path"); } else { bool existScope = this.Manager.ScopeTableExist(); if (!existScope) { durationStartDate = DateTime.Now; this.Manager.CreateScopeTable(); if (progress != null) progress.Report(new SyncProgressEvent(SyncStage.CreatingScope, DateTime.Now.Subtract(durationStartDate))); } // Get the list of types as strings and sort to make comparison // faster when reading. List<string> types = (from type in offlineSchema.Collections select type.FullName).ToList(); types.Sort(); // Create the initial configuration this.Configuration = new SQLiteConfiguration { AnchorBlob = null, LastSyncDate = new DateTime(1900, 01, 01), ScopeName = scope, ServiceUri = uri, Types = types }; durationStartDate = DateTime.Now; this.Manager.SaveConfiguration(this.Configuration); if (progress != null) progress.Report(new SyncProgressEvent(SyncStage.ApplyingConfiguration, DateTime.Now.Subtract(durationStartDate))); } // Try to save tables if not exists if (schema == null || schema.Collections == null || schema.Collections.Count == 0) return; durationStartDate = DateTime.Now; foreach (var table in schema.Collections.Where(table => table.Name != SQLiteConstants.ScopeInfo)) { this.Manager.CreateTable(table); } if (progress != null) progress.Report(new SyncProgressEvent(SyncStage.CheckingTables, DateTime.Now.Subtract(durationStartDate))); }); }
/// <summary> /// Get all changes fro SQLite Database /// </summary> /// <param name="schema">All Tables</param> /// <param name="lastModifiedDate">Changes since this date</param> internal IEnumerable <SQLiteOfflineEntity> GetChanges(OfflineSchema schema, DateTime lastModifiedDate) { List <SQLiteOfflineEntity> lstChanges = new List <SQLiteOfflineEntity>(); using (SQLiteConnection connection = new SQLiteConnection(localFilePath)) { try { foreach (var ty in schema.Collections) { // Get mapping from my type var map = manager.GetMapping(ty); // Create query to select changes var querySelect = SQLiteConstants.SelectChanges; var columnsDcl = new List <String>(); var columnsPK = new List <String>(); // Foreach columns, create the tsql command to execute foreach (var c in map.Columns) { if (!c.IsPK) { columnsDcl.Add("[s].[" + c.Name + "]"); } // If it's the PK, add it from Tracking (because of deleted items not in real table if (c.IsPK) { columnsDcl.Add("[t].[" + c.Name + "]"); columnsPK.Add("[s].[" + c.Name + "] = [t].[" + c.Name + "]"); } } var decl = string.Join(",\n", columnsDcl.ToArray()); var pk = string.Join(" \nAND ", columnsPK.ToArray()); querySelect = String.Format(querySelect, map.TableName, pk, decl); // Prepare command using (var stmt = connection.Prepare(querySelect)) { try { // Set Values BindParameter(stmt, 1, lastModifiedDate); // Get mapping form the statement var cols = new TableMapping.Column[map.Columns.Length]; // Foreach column, get the property in my object for (int i = 0; i < cols.Length; i++) { var name = stmt.ColumnName(i); var c = map.FindColumn(name); if (c != null) { cols[i] = map.FindColumn(name); } } // While row is available //while (await stmt.StepAsync().AsTask().ConfigureAwait(false)) while (stmt.Step() == SQLiteResult.ROW) { // Create the object SQLiteOfflineEntity obj = (SQLiteOfflineEntity)Activator.CreateInstance(map.MappedType); for (int i = 0; i < cols.Length; i++) { if (cols[i] == null) { continue; } // Read the column var val = ReadCol(stmt, i, cols[i].ColumnType); // Set the value cols[i].SetValue(obj, val); } // Read the Oem Properties var newIndex = map.Columns.Count(); obj.ServiceMetadata = new OfflineEntityMetadata(); obj.ServiceMetadata.IsTombstone = (Boolean)ReadCol(stmt, newIndex, typeof(Boolean)); obj.ServiceMetadata.Id = (String)ReadCol(stmt, newIndex + 1, typeof(String)); obj.ServiceMetadata.ETag = (String)ReadCol(stmt, newIndex + 2, typeof(String)); String absoluteUri = (String)ReadCol(stmt, newIndex + 3, typeof(String)); obj.ServiceMetadata.EditUri = String.IsNullOrEmpty(absoluteUri) ? null : new Uri(absoluteUri); lstChanges.Add(obj); } } catch (Exception ex) { Debug.WriteLine(ex.Message); throw; } finally { stmt.Reset(); stmt.ClearBindings(); } } } } catch (Exception ex) { Debug.WriteLine(ex.Message); throw; } } return(lstChanges); }
/// <summary> /// Constructor for the SQLiteContext /// </summary> /// <param name="schema">The OfflineSchema that specifies the set of the collections.</param> /// <param name="scopeName">The scope name used to identify the scope on the service.</param> /// <param name="datbaseName">Name of the database used to store entities.</param> /// <param name="uri">Uri of the scope. Used to intialize the CacheController.</param> /// <remarks> /// If the Uri specified is different from the one that is stored in the cache path, the /// Load method will throw an InvalidOperationException. /// </remarks> public SQLiteContext(OfflineSchema schema, string scopeName, string datbaseName, Uri uri) { if (schema == null) throw new ArgumentNullException("OfflineSchema"); if (string.IsNullOrEmpty(scopeName)) throw new ArgumentNullException("scopeName"); if (string.IsNullOrEmpty(datbaseName)) throw new ArgumentNullException("cachePath"); if (uri == null) throw new ArgumentNullException("uri"); this.isDisposed = false; this.schema = schema; this.scopeUri = uri; this.scopeName = scopeName; this.databaseName = datbaseName; #if ( WINDOWS_PHONE || NETFX_CORE) var localPath = Path.Combine(ApplicationData.Current.LocalFolder.Path, databaseName); #elif (__ANDROID__) var localPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), databaseName); #elif (__IOS__) // we need to put in /Library/ on iOS5.1 to meet Apple's iCloud terms // (they don't want non-user-generated data in Documents) string documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); // Documents folder string libraryPath = Path.Combine(documentsPath, "..", "Library"); var localPath = Path.Combine(libraryPath, databaseName); #endif this.Manager = new SQLiteManager(schema, localPath); this.CreateCacheController(); }
/// <summary> /// Method that verifies a previously cached isolatedStorageSchema and uri (if they exist) with the current isolatedStorageSchema and uri. /// </summary> /// <param name="path">Cache path for the c</param> /// <param name="isolatedStorageSchema">Schema to verify</param> /// <param name="uri">Uri to verify</param> /// <param name="scope">The scope name that the client will be accessing on the service</param> private async Task CheckSchemaAndUri(string path, OfflineSchema isolatedStorageSchema, Uri uri, string scope) { // Get the isolated storage file for the application. StorageFolder isoFolder = ApplicationData.Current.LocalFolder; bool cachePathExist = await isoFolder.FolderExistsAsync(path); if (!cachePathExist) { await isoFolder.CreateFolderAsync(path); } StorageFolder cacheFolder = await isoFolder.GetFolderAsync(path); // Generate the path to the scope info file. string infoPath = Constants.SCOPE_INFO; bool fileExist = await cacheFolder.FileExistsAsync(infoPath); // If the file exists, read it, otherwise, everything is fine. if (fileExist) { // Open the scope file. using (Stream stream = await cacheFolder.OpenStreamForReadAsync(infoPath)) { Stream readStream = stream; try { List <string> fileTypes; string fileUri; string fileScopeName; // Read the file types and uri from the file. ReadSchemaAndUri(readStream, out fileUri, out fileScopeName, out fileTypes); // Verify the scope uri. if (fileUri != uri.AbsoluteUri) { throw new ArgumentException( "Specified uri does not match uri previously used for the specified cache path"); } if (fileScopeName != scope) { throw new ArgumentException( "Specified scope name does not match scope name previously used for the specified cache path"); } // Verify the types. List <Type> userTypes = isolatedStorageSchema.Collections.ToList(); // Sort by name (the class Type isn't sortable) userTypes.Sort((x, y) => String.Compare(x.FullName, y.FullName, StringComparison.Ordinal)); if (userTypes.Count != fileTypes.Count) { throw new ArgumentException( "Specified isolatedStorageSchema does not match isolatedStorageSchema previously used for cache path"); } if (userTypes.Where((t, i) => t.FullName != fileTypes[i]).Any()) { throw new ArgumentException( "Specified isolatedStorageSchema does not match isolatedStorageSchema previously used for cache path"); } } finally { readStream.Dispose(); } } } else { // If the file doesn't exist, write the new info. using ( Stream stream = await cacheFolder.OpenStreamForWriteAsync(infoPath, CreationCollisionOption.ReplaceExisting)) { Stream writeStream = stream; try { WriteSchemaFile(writeStream, uri, scope, isolatedStorageSchema); } finally { writeStream.Dispose(); } } } }
/// <summary> /// Get all changes fro SQLite Database /// </summary> /// <param name="schema">All Tables</param> /// <param name="lastModifiedDate">Changes since this date</param> /// <param name="uploadBatchSize">Maximum number of rows to upload</param> internal IEnumerable <SQLiteOfflineEntity> GetChanges(OfflineSchema schema, DateTime lastModifiedDate, int uploadBatchSize) { List <SQLiteOfflineEntity> lstChanges = new List <SQLiteOfflineEntity>(); using (SQLiteDatabaseConnection connection = SQLitePCL.pretty.SQLite3.Open(localFilePath)) { try { foreach (var ty in schema.Collections) { // Get mapping from my type var map = manager.GetMapping(ty); // Create query to select changes var querySelect = SQLiteConstants.SelectChanges; var columnsDcl = new List <String>(); var columnsPK = new List <String>(); // Foreach columns, create the tsql command to execute foreach (var c in map.Columns) { if (!c.IsPK) { columnsDcl.Add("[s].[" + c.Name + "]"); } // If it's the PK, add it from Tracking (because of deleted items not in real table if (c.IsPK) { columnsDcl.Add("[t].[" + c.Name + "]"); columnsPK.Add("[s].[" + c.Name + "] = [t].[" + c.Name + "]"); } } var decl = string.Join(",\n", columnsDcl.ToArray()); var pk = string.Join(" \nAND ", columnsPK.ToArray()); querySelect = String.Format(querySelect, map.TableName, pk, decl); // add limit if specified if (uploadBatchSize > 0) { querySelect += $" LIMIT {uploadBatchSize}"; } try { // Get mapping form the statement var cols = new TableMapping.Column[map.Columns.Length]; bool firstRow = true; // While row is available foreach (var row in connection.Query(querySelect, P(lastModifiedDate))) { if (firstRow) { // Foreach column, get the property in my object for (int i = 0; i < cols.Length; i++) { var name = row[i].ColumnInfo.Name; var c = map.FindColumn(name); if (c != null) { cols[i] = map.FindColumn(name); } } firstRow = false; } // Create the object SQLiteOfflineEntity obj = (SQLiteOfflineEntity)Activator.CreateInstance(map.MappedType); for (int i = 0; i < cols.Length; i++) { if (cols[i] == null) { continue; } // Read the column var val = ReadCol(row, i, cols[i].ColumnType); // Set the value cols[i].SetValue(obj, val); } // Read the Oem Properties var newIndex = map.Columns.Count(); obj.ServiceMetadata = new OfflineEntityMetadata(); obj.ServiceMetadata.IsTombstone = (bool)ReadCol(row, newIndex, typeof(Boolean)); obj.ServiceMetadata.Id = (String)ReadCol(row, newIndex + 1, typeof(String)); obj.ServiceMetadata.ETag = (String)ReadCol(row, newIndex + 2, typeof(String)); String absoluteUri = (String)ReadCol(row, newIndex + 3, typeof(String)); obj.ServiceMetadata.EditUri = String.IsNullOrEmpty(absoluteUri) ? null : new Uri(absoluteUri); lstChanges.Add(obj); } } catch (Exception ex) { Debug.WriteLine(ex.Message); throw; } // if we are batching uploads and the upload rowcount has been reached, skip if (uploadBatchSize > 0 && lstChanges.Count >= uploadBatchSize) { break; } } } catch (Exception ex) { Debug.WriteLine(ex.Message); throw; } } // if we are batching uploads, limit the in-memory result set as well if (uploadBatchSize > 0) { return(lstChanges.Take(uploadBatchSize)); } return(lstChanges); }
/// <summary> /// Get all changes fro SQLite Database /// </summary> /// <param name="schema">All Tables</param> /// <param name="lastModifiedDate">Changes since this date</param> internal IEnumerable<SQLiteOfflineEntity> GetChanges(OfflineSchema schema, DateTime lastModifiedDate) { List<SQLiteOfflineEntity> lstChanges = new List<SQLiteOfflineEntity>(); using (SQLiteConnection connection = new SQLiteConnection(localFilePath)) { try { foreach (var ty in schema.Collections) { // Get mapping from my type var map = manager.GetMapping(ty); // Create query to select changes var querySelect = SQLiteConstants.SelectChanges; var columnsDcl = new List<String>(); string pkColumnName = string.Empty; // Foreach columns, create the tsql command to execute foreach (var c in map.Columns) { if (!c.IsPK) columnsDcl.Add("[s].[" + c.Name + "]"); // If it's the PK, add it from Tracking (because of deleted items not in real table if (c.IsPK) { columnsDcl.Add("[t].[" + c.Name + "]"); pkColumnName = c.Name; } } var decl = string.Join(",\n", columnsDcl.ToArray()); querySelect = String.Format(querySelect, map.TableName, pkColumnName, decl); // Prepare command using (var stmt = connection.Prepare(querySelect)) { try { // Set Values BindParameter(stmt, 1, lastModifiedDate); // Get mapping form the statement var cols = new TableMapping.Column[map.Columns.Length]; // Foreach column, get the property in my object for (int i = 0; i < cols.Length; i++) { var name = stmt.ColumnName(i); var c = map.FindColumn(name); if (c != null) cols[i] = map.FindColumn(name); } // While row is available //while (await stmt.StepAsync().AsTask().ConfigureAwait(false)) while (stmt.Step() == SQLiteResult.ROW) { // Create the object SQLiteOfflineEntity obj = (SQLiteOfflineEntity)Activator.CreateInstance(map.MappedType); for (int i = 0; i < cols.Length; i++) { if (cols[i] == null) continue; // Read the column var val = ReadCol(stmt, i, cols[i].ColumnType); // Set the value cols[i].SetValue(obj, val); } // Read the Oem Properties var newIndex = map.Columns.Count(); obj.ServiceMetadata = new OfflineEntityMetadata(); obj.ServiceMetadata.IsTombstone = (Boolean)ReadCol(stmt, newIndex, typeof(Boolean)); obj.ServiceMetadata.Id = (String)ReadCol(stmt, newIndex + 1, typeof(String)); obj.ServiceMetadata.ETag = (String)ReadCol(stmt, newIndex + 2, typeof(String)); String absoluteUri = (String)ReadCol(stmt, newIndex + 3, typeof(String)); obj.ServiceMetadata.EditUri = String.IsNullOrEmpty(absoluteUri) ? null : new Uri(absoluteUri); lstChanges.Add(obj); } } catch (Exception ex) { Debug.WriteLine(ex.Message); throw; } finally { stmt.Reset(); stmt.ClearBindings(); } } } } catch (Exception ex) { Debug.WriteLine(ex.Message); throw; } } return lstChanges; }
/// <summary> /// Creates the collections for the types in the schema /// </summary> /// <param name="schema">Schema for which to create collections</param> private void CreateCollections(OfflineSchema schema) { Type collectionType = typeof(OfflineCollection<,>); foreach (Type t in schema.Collections) { // Create the generic type for the type in the collection. Type objectType = typeof(Object); Type generic = collectionType.MakeGenericType(objectType, t); OfflineCollection collection = (OfflineCollection)Activator.CreateInstance(generic); Collections[t] = collection; } }
/// <summary> /// Method that verifies a previously cached isolatedStorageSchema and uri (if they exist) with the current isolatedStorageSchema and uri. /// </summary> /// <param name="offlineSchema">Schema to verify</param> /// <param name="uri">Uri to verify</param> /// <param name="scope">The scope name that the client will be accessing on the service</param> private async Task CheckSchemaAndUriAsync(OfflineSchema offlineSchema, Uri uri, string scope, CancellationToken cancellationToken, IProgress <SyncProgressEvent> progress) { await Task.Run(() => { DateTime durationStartDate = DateTime.Now; this.Configuration = this.Manager.ReadConfiguration(scope); if (progress != null) { progress.Report(new SyncProgressEvent(SyncStage.ReadingConfiguration, DateTime.Now.Subtract(durationStartDate))); } if (this.Configuration != null) { // Verify the scope uri. if (this.Configuration.ServiceUri.AbsoluteUri != uri.AbsoluteUri) { throw new ArgumentException( "Specified uri does not match uri previously used for the specified database"); } // Verify the types. List <Type> userTypes = offlineSchema.Collections.ToList(); // Sort by name (the class Type isn't sortable) userTypes.Sort((x, y) => String.Compare(x.FullName, y.FullName, StringComparison.Ordinal)); if (userTypes.Count != this.Configuration.Types.Count) { throw new ArgumentException( "Specified offlineSchema does not match database Offline schema previously used for cache path"); } // Fix this.Configuration.Types.Sort((x, y) => String.Compare(x, y, StringComparison.Ordinal)); if (userTypes.Where( (t, i) => t.FullName != this.Configuration.Types[i]).Any()) { throw new ArgumentException( "Specified offlineSchema does not match database Offline schema previously used for cache path"); } } else { bool existScope = this.Manager.ScopeTableExist(); if (!existScope) { durationStartDate = DateTime.Now; this.Manager.CreateScopeTable(); if (progress != null) { progress.Report(new SyncProgressEvent(SyncStage.CreatingScope, DateTime.Now.Subtract(durationStartDate))); } } // Get the list of types as strings and sort to make comparison // faster when reading. List <string> types = (from type in offlineSchema.Collections select type.FullName).ToList(); types.Sort(); // Create the initial configuration this.Configuration = new SQLiteConfiguration { AnchorBlob = null, LastSyncDate = new DateTime(1900, 01, 01), ScopeName = scope, ServiceUri = uri, Types = types }; durationStartDate = DateTime.Now; this.Manager.SaveConfiguration(this.Configuration); if (progress != null) { progress.Report(new SyncProgressEvent(SyncStage.ApplyingConfiguration, DateTime.Now.Subtract(durationStartDate))); } } // Try to save tables if not exists if (schema == null || schema.Collections == null || schema.Collections.Count == 0) { return; } durationStartDate = DateTime.Now; foreach (var table in schema.Collections.Where(table => table.Name != SQLiteConstants.ScopeInfo)) { this.Manager.CreateTable(table); } if (progress != null) { progress.Report(new SyncProgressEvent(SyncStage.CheckingTables, DateTime.Now.Subtract(durationStartDate))); } }); }