-
Notifications
You must be signed in to change notification settings - Fork 25
03 Create a simple application
In this sample, we will create a first application with all the required features to be able to synchronize your device with an on premise database.
The source code is available here : [Frabikam Sample](http://www.dotmim.com/sitefiles/Fabrikam Sample.zip)
First of all, we need to implement the full Synchronization mechanism. You need to follow the steps from the first and second chapter of the documentation :
- Download and install Sync Framework and Sync Toolkit for WinRT
- Generate the code for your database, your server and your client application
The server side is quite simple, if you have correctly implemented what we see in the previous chapters.
In this sample, we will manage some blob object (customer images) so I have configured my server for batch mode :
public class DefaultScopeSyncService : SyncService<DefaultScopeOfflineEntities> {
public static void InitializeService(ISyncServiceConfiguration config) {
config.ServerConnectionString = ConfigurationManager.ConnectionStrings["Fabrikam"].ConnectionString;
config.SetEnableScope("DefaultScope");
config.SetDefaultSyncSerializationFormat(SyncSerializationFormat.ODataJson);
config.SetConflictResolutionPolicy(ConflictResolutionPolicy.ServerWins);
// for debug purpose, I need the whole error details
config.UseVerboseErrors = true;
// Because my json may be huge, i enable the batch mode
config.SetDownloadBatchSize(2 * 1024);
config.UseVerboseErrors = true;
}
}
This application is a simple master details application :
You can create, update or delete any items, and of course, you can synchronize your local database with your on premise database.
The synchronization is straightforward, don’t forget to check the connectivity.
The Synchronization process is included in the DataService class :
public async Task<CacheRefreshStatistics> Sync()
{
try
{
var result = await this.SyncContext.SynchronizeAsync();
// For debug time
if (result.Error != null)
Debug.WriteLine(result.Error.Message);
return result;
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
throw;
}
}
In this sample, you will find a Context class, where i check the connectivity.
I used a MVVM pattern, so my Sync process is included in a RelayCommand. This command is included in my ItemsPageViewModel
Then I used MessageDialog and ProgressBar to make a good user experience :
public RelayCommand SyncCommand
{
get
{
if (syncCommand != null) return syncCommand;
syncCommand = new RelayCommand(async () =>
{
try
{
this.IsSyncing = true;
var attenteTask = Task.Delay(1000);
var stats = await DataService.Current.Sync();
await attenteTask;
if (stats.Error != null)
{
var messageDialog = new MessageDialog("An error occured during sync session : " + Environment.NewLine + stats.Error.Message);
await messageDialog.ShowAsync();
}
else
{
if (stats.TotalDownloads > 0)
await GetServiceTickets();
var messageDialog = new MessageDialog(
"Sync session completed: " + Environment.NewLine +
"Total downloads :" + stats.TotalDownloads + Environment.NewLine +
"Total uploads :" + stats.TotalUploads + Environment.NewLine +
"Total conflicts :" + stats.TotalSyncErrors + Environment.NewLine +
"Ellapsed Time :" + stats.EndTime.Subtract(stats.StartTime).TotalSeconds + " s.");
await messageDialog.ShowAsync();
}
this.IsSyncing = false;
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
throw;
}
}, () =>
{
var connectionIsOk = Context.Current.ConnectivityLevel == NetworkConnectivityLevel.InternetAccess;
var isNotSyncing = !this.IsSyncing;
return (connectionIsOk && isNotSyncing);
});
return syncCommand;
}
}
Checking the connectivity is pretty StraightForward :
public Context()
{
NetworkInformation.NetworkStatusChanged += NetworkInformation_NetworkStatusChanged;
this.UpdateConnectionProfile();
}
private static Context current;
public static Context Current
{
get
{
if (current == null)
current = new Context();
return current;
}
}
private NetworkConnectivityLevel connectivityLevel;
public NetworkConnectivityLevel ConnectivityLevel
{
get
{
return connectivityLevel;
}
set
{
if (value == connectivityLevel)
return;
connectivityLevel = value;
base.RaisePropertyChanged("ConnectivityLevel");
}
}
private void UpdateConnectionProfile()
{
var connectionProfile = NetworkInformation.GetInternetConnectionProfile();
if (connectionProfile != null)
this.ConnectivityLevel = connectionProfile.GetNetworkConnectivityLevel();
else
this.ConnectivityLevel = NetworkConnectivityLevel.None;
}
private void NetworkInformation_NetworkStatusChanged(object sender)
{
Context.Current.UpdateConnectionProfile();
}
To be able to query the local database, I used the SQLitePCL nuget package wrapper. Again you will find those methods in the DataService class.
I used a SettingFlyout object (See this blog article to see how I manage a SettingFlyout item) to show a complete form with all my service ticket fields:
public async Task<List<ServiceTickets>> GetTickets()
{
List<ServiceTickets> serviceTickets = new List<ServiceTickets>();
try
{
using (Database connection = new Database(this.DatabaseStorageFolder, this.DatabaseName))
{
await connection.OpenAsync(SqliteOpenMode.OpenRead);
const string commandText = "SELECT * FROM ServiceTickets";
using (var stmt = await connection.PrepareStatementAsync(commandText))
{
while (await stmt.StepAsync())
{
ServiceTickets ticket = FillTicketFromReader(stmt);
serviceTickets.Add(ticket);
}
}
}
}
catch (Exception ex)
{
var result = Database.GetSqliteErrorCode(ex.HResult);
Debug.WriteLine(result);
throw;
}
return serviceTickets;
}
internal async Task InsertServiceTickets(List<ServiceTickets> serviceTickets)
{
Database connection = null;
try
{
using (connection = new Database(this.DatabaseStorageFolder, this.DatabaseName))
{
await connection.OpenAsync(SqliteOpenMode.OpenReadWrite);
await connection.ExecuteStatementAsync("Begin Transaction");
const string commandText = "Insert Into ServiceTickets " +
"(Title, Description, StatusValue, EscalationLevel, Opened, Closed, CustomerID, ServiceTicketID) " +
"Values (?, ?, ?, ?, ?, ?, ?, ?)";
using (var stmt = await connection.PrepareStatementAsync(commandText))
{
foreach (var serviceTicket in serviceTickets)
{
BindServiceTicketParameter(stmt, serviceTicket);
await stmt.StepAsync();
stmt.Reset();
}
}
await connection.ExecuteStatementAsync("Commit Transaction");
}
}
catch (Exception ex)
{
var result = Database.GetSqliteErrorCode(ex.HResult);
Debug.WriteLine(result);
if (connection != null)
connection.ExecuteStatementAsync("Rollback Transaction").AsTask().Wait();
throw;
}
}
internal async Task UpdateServiceTickets(List<ServiceTickets> serviceTickets)
{
Database connection = null;
try
{
using (connection = new Database(this.DatabaseStorageFolder, this.DatabaseName))
{
await connection.OpenAsync(SqliteOpenMode.OpenReadWrite);
await connection.ExecuteStatementAsync("Begin Transaction");
const string commandText = "Update ServiceTickets Set " +
"Title = ?, Description = ?, StatusValue = ?, EscalationLevel = ?, " +
"Opened = ?, Closed = ?, CustomerID = ? " +
"Where ServiceTicketID = ?";
using (var stmt = await connection.PrepareStatementAsync(commandText))
{
foreach (var serviceTicket in serviceTickets)
{
BindServiceTicketParameter(stmt, serviceTicket);
await stmt.StepAsync();
stmt.Reset();
}
}
await connection.ExecuteStatementAsync("Commit Transaction");
}
}
catch (Exception ex)
{
var result = Database.GetSqliteErrorCode(ex.HResult);
Debug.WriteLine(result);
if (connection != null)
connection.ExecuteStatementAsync("Rollback Transaction").AsTask().Wait();
throw;
}
}
public async Task<bool> DeleteTicket(Guid id)
{
try
{
using (Database connection = new Database(this.DatabaseStorageFolder, this.DatabaseName))
{
await connection.OpenAsync(SqliteOpenMode.OpenReadWrite);
const string commandText = "DELETE FROM ServiceTickets Where ServiceTicketID = ?";
using (var stmt = await connection.PrepareStatementAsync(commandText))
{
stmt.BindTextParameterAt(1, id.ToString());
if (await stmt.StepAsync())
return true;
}
}
return false;
}
catch (Exception ex)
{
var result = Database.GetSqliteErrorCode(ex.HResult);
Debug.WriteLine(result);
throw;
}
}
In this sample, you can see that we manage some images. They come from the on premise database.
There is no specific server code, but just a quick reminder on how I have insert those thumbnails in my server database :
(this code is not included in the sample zip package)
static void Main(string[] args)
{
foreach (var imgFile in Directory.EnumerateFiles(@"C:\Users\spertus\Pictures\Persons"))
{
var imgFileInfo = new FileInfo(imgFile);
var img = System.Drawing.Bitmap.FromFile(imgFile);
using (MemoryStream msImg = new MemoryStream())
{
img.Save(msImg, img.RawFormat);
using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["Fabrikam"].ConnectionString))
{
SqlCommand command = new SqlCommand("Insert into Images (ID, Name, Image, ImageFormat, Length, Width, Height) Values (@ID, @Name, @Image, @ImageFormat, @Length, @Width, @Height) ");
command.Connection = connection;
SqlParameter p = new SqlParameter("@Id", System.Data.SqlDbType.UniqueIdentifier);
p.Value = Guid.NewGuid();
command.Parameters.Add(p);
p = new SqlParameter("@Image", System.Data.SqlDbType.VarBinary);
p.Value = msImg.ToArray();
command.Parameters.Add(p);
p = new SqlParameter("@Name", System.Data.SqlDbType.VarChar, 255);
p.Value = imgFileInfo.Name;
command.Parameters.Add(p);
p = new SqlParameter("@ImageFormat", System.Data.SqlDbType.VarChar, 50);
p.Value = GetImageFormat(img.RawFormat, msImg.GetBuffer());
command.Parameters.Add(p);
p = new SqlParameter("@Length", System.Data.SqlDbType.Int);
p.Value = msImg.Length;
command.Parameters.Add(p);
p = new SqlParameter("@Width", System.Data.SqlDbType.Int);
p.Value = img.Width;
command.Parameters.Add(p);
p = new SqlParameter("@Height", System.Data.SqlDbType.Int);
p.Value = img.Height;
command.Parameters.Add(p);
connection.Open();
command.ExecuteNonQuery();
connection.Close();
}
}
}
}
From the client point of view, querying the local SQLite database and display the image is, again, straightforward :
Getting the Images object from the local SQLite database:
public async Task<List<Images>> GetImages()
{
List<Images> imgs = new List<Images>();
try
{
using (Database connection = new Database(this.DatabaseStorageFolder, this.DatabaseName))
{
await connection.OpenAsync(SqliteOpenMode.OpenRead);
const string commandText = "SELECT * FROM Images";
using (var stmt = await connection.PrepareStatementAsync(commandText))
{
while (await stmt.StepAsync())
{
Images img = FillImageFromReader(stmt);
imgs.Add(img);
}
}
}
}
catch (Exception ex)
{
var result = Database.GetSqliteErrorCode(ex.HResult);
Debug.WriteLine(result);
throw;
}
return imgs;
}
Create a BitmapImage from the Byte[] object :
internal async Task<BitmapImage> GetBitmapImageFromImageEntity(Images img)
{
var bitmapImage = new BitmapImage();
var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(img.Image.AsBuffer());
stream.Seek(0);
bitmapImage.SetSource(stream);
return bitmapImage;
}
In the next chapter, we will see how to manage a filtered synchronization, based on this sample.