diff options
| author | Roy <roy.mail.net@gmail.com> | 2017-11-26 00:31:52 +0200 |
|---|---|---|
| committer | Roy <roy.mail.net@gmail.com> | 2017-11-26 00:31:52 +0200 |
| commit | 4eff62d399ff3f3093f67185ac58b5d0a134e25e (patch) | |
| tree | 8337f34b4b8d7f92f4eebb00fd7a65edfa5b65a5 /Software/Visual_Studio/Tango.Synchronization | |
| parent | fd60c446a29842227028ad20385e9a99950ea231 (diff) | |
| download | Tango-4eff62d399ff3f3093f67185ac58b5d0a134e25e.tar.gz Tango-4eff62d399ff3f3093f67185ac58b5d0a134e25e.zip | |
Implemented SQLite Synchronization Library!
Diffstat (limited to 'Software/Visual_Studio/Tango.Synchronization')
11 files changed, 1020 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Tango.Synchronization/App.config b/Software/Visual_Studio/Tango.Synchronization/App.config new file mode 100644 index 000000000..a5105cd61 --- /dev/null +++ b/Software/Visual_Studio/Tango.Synchronization/App.config @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<configuration> + <configSections> + <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --> + <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> + </configSections> + <entityFramework> + <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> + <parameters> + <parameter value="v13.0" /> + </parameters> + </defaultConnectionFactory> + <providers> + <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> + <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" /> + </providers> + </entityFramework> + <system.data> + <DbProviderFactories> + <remove invariant="System.Data.SQLite.EF6" /> + <add name="SQLite Data Provider (Entity Framework 6)" invariant="System.Data.SQLite.EF6" description=".NET Framework Data Provider for SQLite (Entity Framework 6)" type="System.Data.SQLite.EF6.SQLiteProviderFactory, System.Data.SQLite.EF6" /> + <remove invariant="System.Data.SQLite" /><add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".NET Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" /></DbProviderFactories> + </system.data> +</configuration>
\ No newline at end of file diff --git a/Software/Visual_Studio/Tango.Synchronization/Constants.cs b/Software/Visual_Studio/Tango.Synchronization/Constants.cs new file mode 100644 index 000000000..3558fd506 --- /dev/null +++ b/Software/Visual_Studio/Tango.Synchronization/Constants.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Synchronization +{ + internal static class Constants + { + //SQLite Internal Constants + public const String SEQUENCE_TABLE_NAME = "sqlite_sequence"; + public const String TABLE_INFO = "table_info"; + public const String COLUMN_TYPE = "type"; + public const String IS_NOT_NULL = "notnull"; + public const String DEFAULT_VALUE = "dflt_value"; + + //Must Column Names. + + /// <summary> + /// The table auto incremented id column name. + /// </summary> + public const String ID = "ID"; + + /// <summary> + /// The table unique identifier column name used for synchronization. + /// </summary> + public const String GUID = "GUID"; + + /// <summary> + /// The table last updated column name used for synchronization. + /// </summary> + public const String LAST_UPDATED = "LAST_UPDATED"; + } +} diff --git a/Software/Visual_Studio/Tango.Synchronization/ExtensionMethods.cs b/Software/Visual_Studio/Tango.Synchronization/ExtensionMethods.cs new file mode 100644 index 000000000..ba11e0719 --- /dev/null +++ b/Software/Visual_Studio/Tango.Synchronization/ExtensionMethods.cs @@ -0,0 +1,88 @@ +using Tango.Synchronization; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Synchronization +{ + internal static class SQLExtensions + { + /// <summary> + /// Gets the type of the SQL column. + /// </summary> + /// <param name="column">The column.</param> + /// <returns></returns> + public static String GetSQLType(this DataColumn column) + { + return column.ExtendedProperties[Constants.COLUMN_TYPE].ToString(); + } + + /// <summary> + /// Gets the information table. + /// </summary> + /// <param name="table">The table.</param> + /// <returns></returns> + public static DataTable GetInfoTable(this DataTable table) + { + return table.ExtendedProperties[Constants.TABLE_INFO] as DataTable; + } + + /// <summary> + /// Determines whether column is marked NOT NULL. + /// </summary> + /// <param name="column">The column.</param> + public static bool IsNotNull(this DataColumn column) + { + int value = int.Parse(column.ExtendedProperties[Constants.IS_NOT_NULL].ToString()); + return value == 0 ? false : true; + } + + /// <summary> + /// Gets the column default value. + /// </summary> + /// <param name="column">The column.</param> + /// <returns></returns> + public static String GetDefaultValue(this DataColumn column) + { + String def = column.ExtendedProperties[Constants.DEFAULT_VALUE].ToString(); + + double num = -1; + + if (double.TryParse(def, out num)) + { + return num.ToString(); + } + else + { + if (def.Contains("HEX")) + { + def = def.Insert(0, "("); + def = def.Insert(def.Length - 1, ")"); + } + return def; + } + } + + /// <summary> + /// Determines whether column definition has default value. + /// </summary> + /// <param name="column">The column.</param> + public static bool HasDefaultValue(this DataColumn column) + { + return !String.IsNullOrWhiteSpace(column.ExtendedProperties[Constants.DEFAULT_VALUE].ToString()); + } + + /// <summary> + /// Converts the DateTime instance to SQLite DateTime string. + /// </summary> + /// <param name="date">The date.</param> + /// <returns></returns> + public static String ToSQLiteDateString(this DateTime date) + { + return date.ToString("yyyy-MM-dd HH:mm:ss.fff"); + } + } +}
\ No newline at end of file diff --git a/Software/Visual_Studio/Tango.Synchronization/ISqlDataBase.cs b/Software/Visual_Studio/Tango.Synchronization/ISqlDataBase.cs new file mode 100644 index 000000000..f087dfd15 --- /dev/null +++ b/Software/Visual_Studio/Tango.Synchronization/ISqlDataBase.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Synchronization +{ + /// <summary> + /// Represents an SQL data base adapter used by <see cref="SqlDataBaseComparer"/>. + /// </summary> + /// <seealso cref="System.IDisposable" /> + public interface ISqlDataBase : IDisposable + { + /// <summary> + /// Gets the database source URL/File. + /// </summary> + String Source { get; } + + /// <summary> + /// Gets the database tables collection. + /// </summary> + List<DataTable> Tables { get; } + + /// <summary> + /// Clones a table from another database into this database. + /// </summary> + /// <param name="otherDB">The other database.</param> + /// <param name="otherTable">The other table.</param> + /// <returns></returns> + DataTable CloneTableFrom(ISqlDataBase otherDB, DataTable otherTable); + + /// <summary> + /// Gets the SQL command for <see cref="CloneTableFrom(ISqlDataBase, DataTable)"/>. + /// </summary> + /// <param name="otherDB">The other database.</param> + /// <param name="otherTable">The other table.</param> + /// <returns></returns> + String GetCloneTableFromCommand(ISqlDataBase otherDB, DataTable otherTable); + + /// <summary> + /// Adds the specified column to the specified table. + /// </summary> + /// <param name="table">The table.</param> + /// <param name="column">The column.</param> + /// <returns></returns> + DataColumn AddColumn(DataTable table, DataColumn column); + + /// <summary> + /// Gets the SQL command for <see cref="AddColumn(DataTable, DataColumn)"/>. + /// </summary> + /// <param name="table">The table.</param> + /// <param name="column">The column.</param> + /// <returns></returns> + String GetAddColumnCommand(DataTable table, DataColumn column); + + /// <summary> + /// Adds the specified row to the specified table. + /// </summary> + /// <param name="table">The table.</param> + /// <param name="row">The row.</param> + void AddRow(DataTable table, DataRow row); + + /// <summary> + /// Gets the SQL command for <see cref="AddRow(DataTable, DataRow)"/>. + /// </summary> + /// <param name="table">The table.</param> + /// <param name="row">The row.</param> + /// <returns></returns> + String GetAddRowCommand(DataTable table, DataRow row); + + /// <summary> + /// Updates the matching row by the row GUID. + /// </summary> + /// <param name="table">The table.</param> + /// <param name="row">The row.</param> + void UpdateRow(DataTable table, DataRow row); + + /// <summary> + /// Gets the SQL command for <see cref="UpdateRow(DataTable, DataRow)"/>. + /// </summary> + /// <param name="table">The table.</param> + /// <param name="row">The row.</param> + /// <returns></returns> + String GetUpdateRowCommand(DataTable table, DataRow row); + } +} diff --git a/Software/Visual_Studio/Tango.Synchronization/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Tango.Synchronization/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..1221e84be --- /dev/null +++ b/Software/Visual_Studio/Tango.Synchronization/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Tango - DataBase Synchronization Components")] +[assembly: ComVisible(false)]
\ No newline at end of file diff --git a/Software/Visual_Studio/Tango.Synchronization/SqlDataBaseComparer.cs b/Software/Visual_Studio/Tango.Synchronization/SqlDataBaseComparer.cs new file mode 100644 index 000000000..07c94045b --- /dev/null +++ b/Software/Visual_Studio/Tango.Synchronization/SqlDataBaseComparer.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Synchronization +{ + /// <summary> + /// Represents an <see cref="ISqlDataBase"/> synchronization engine. + /// </summary> + /// <seealso cref="System.IDisposable" /> + public class SqlDataBaseComparer : IDisposable + { + /// <summary> + /// Gets the master SQL. + /// </summary> + public ISqlDataBase MasterSQL { get; private set; } + + /// <summary> + /// Gets the slave SQL. + /// </summary> + public ISqlDataBase SlaveSQL { get; private set; } + + /// <summary> + /// Initializes a new instance of the <see cref="SqlDataBaseComparer"/> class. + /// </summary> + /// <param name="masterSQL">The master SQL.</param> + /// <param name="slaveSQL">The slave SQL.</param> + public SqlDataBaseComparer(ISqlDataBase masterSQL, ISqlDataBase slaveSQL) + { + MasterSQL = masterSQL; + SlaveSQL = slaveSQL; + } + + /// <summary> + /// Compares the master and slave SQL data base and returns a collection of <see cref="SqlDiff"/>. + /// </summary> + /// <returns></returns> + public List<SqlDiff> Compare() + { + List<SqlDiff> diffs = new List<SqlDiff>(); + + foreach (var masterTable in MasterSQL.Tables) + { + var slaveTable = SlaveSQL.Tables.SingleOrDefault(x => x.TableName == masterTable.TableName); //Get matching slave table on slave db. + + if (slaveTable == null) //Table not found on slave. + { + //Clone table from slave db to master db including records! + diffs.Add(new SqlDiff(SqlDiffAction.AddTableToSlave, String.Format("Add table {0} to slave", masterTable.TableName), () => SlaveSQL.CloneTableFrom(MasterSQL, masterTable), SlaveSQL.GetCloneTableFromCommand(MasterSQL, masterTable))); + continue; + } + + foreach (DataColumn masterColumn in masterTable.Columns) + { + var slaveColumn = slaveTable.Columns[masterColumn.ColumnName]; //Get matching slave column on slave table. + + if (slaveColumn == null) //Slave column not found. + { + //Add column to slave table. + diffs.Add(new SqlDiff(SqlDiffAction.AddColumnToSlave, String.Format("Add column {0} to slave table", masterColumn.ColumnName), () => SlaveSQL.AddColumn(masterTable, masterColumn), SlaveSQL.GetAddColumnCommand(masterTable, masterColumn))); + } + } + + List<DataRow> addToSlave = new List<DataRow>(); + List<DataRow> updateSlave = new List<DataRow>(); + List<DataRow> addToMaster = new List<DataRow>(); + List<DataRow> updateMaster = new List<DataRow>(); + + foreach (DataRow masterRow in masterTable.Rows) + { + String guid = masterRow.Field<String>(Constants.GUID); + + //Get Matching slave row. + DataRow slaveRow = slaveTable.AsEnumerable().SingleOrDefault(x => x.Field<String>(Constants.GUID) == guid); + + if (slaveRow != null) + { + DateTime masterDate = masterRow.Field<DateTime>(Constants.LAST_UPDATED); + DateTime slaveDate = slaveRow.Field<DateTime>(Constants.LAST_UPDATED); + + if (masterDate > slaveDate) + { + updateSlave.Add(masterRow); + } + else if (slaveDate > masterDate) + { + updateMaster.Add(slaveRow); + } + } + else + { + addToSlave.Add(masterRow); + } + } + + foreach (DataRow slaveRow in slaveTable.Rows) + { + String guid = slaveRow.Field<String>(Constants.GUID); + + //Get Matching slave row. + DataRow masterRow = masterTable.AsEnumerable().SingleOrDefault(x => x.Field<String>(Constants.GUID) == guid); + + if (masterRow == null) + { + addToMaster.Add(slaveRow); + } + } + + foreach (var row in addToSlave) + { + diffs.Add(new SqlDiff(SqlDiffAction.AddRowToSlave, String.Format("Add row to slave table {0}", slaveTable.TableName), () => SlaveSQL.AddRow(slaveTable, row), SlaveSQL.GetAddRowCommand(slaveTable, row))); + } + + foreach (var row in addToMaster) + { + diffs.Add(new SqlDiff(SqlDiffAction.AddRowToMaster, String.Format("Add row to master table {0}", masterTable.TableName), () => MasterSQL.AddRow(masterTable, row), MasterSQL.GetAddRowCommand(masterTable, row))); + } + + foreach (var row in updateSlave) + { + diffs.Add(new SqlDiff(SqlDiffAction.UpdateRowInSlave, String.Format("Update row in slave table {0}", slaveTable.TableName), () => SlaveSQL.UpdateRow(slaveTable, row), SlaveSQL.GetUpdateRowCommand(slaveTable, row))); + } + + foreach (var row in updateMaster) + { + diffs.Add(new SqlDiff(SqlDiffAction.UpdateRowInMaster, String.Format("Update row in master table {0}", masterTable.TableName), () => MasterSQL.UpdateRow(masterTable, row), MasterSQL.GetUpdateRowCommand(masterTable, row))); + } + } + + return diffs; + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() + { + MasterSQL.Dispose(); + SlaveSQL.Dispose(); + } + } +} diff --git a/Software/Visual_Studio/Tango.Synchronization/SqlDiff.cs b/Software/Visual_Studio/Tango.Synchronization/SqlDiff.cs new file mode 100644 index 000000000..b5a274cdc --- /dev/null +++ b/Software/Visual_Studio/Tango.Synchronization/SqlDiff.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Synchronization +{ + /// <summary> + /// Represents an <see cref="SqlDataBaseComparer.Compare"/> result. + /// </summary> + public class SqlDiff + { + private Action _action; + + /// <summary> + /// Gets the difference action. + /// </summary> + public SqlDiffAction Action { get; private set; } + + /// <summary> + /// Gets the difference description. + /// </summary> + public String Description { get; private set; } + + /// <summary> + /// Gets the <see cref="Commit"/> command. + /// </summary> + public String Command { get; private set; } + + /// <summary> + /// Initializes a new instance of the <see cref="SqlDiff"/> class. + /// </summary> + /// <param name="dataBaseAction">The data base action.</param> + /// <param name="description">The description.</param> + /// <param name="action">The action.</param> + /// <param name="command">The command.</param> + public SqlDiff(SqlDiffAction dataBaseAction, String description, Action action, String command) + { + Action = dataBaseAction; + Description = description; + _action = action; + Command = command; + } + + /// <summary> + /// Commits the difference to the target DB. + /// </summary> + public void Commit() + { + _action(); + } + + /// <summary> + /// Returns a <see cref="System.String" /> that represents this instance. + /// </summary> + /// <returns> + /// A <see cref="System.String" /> that represents this instance. + /// </returns> + public override string ToString() + { + return Command; + } + } +} diff --git a/Software/Visual_Studio/Tango.Synchronization/SqlDiffAction.cs b/Software/Visual_Studio/Tango.Synchronization/SqlDiffAction.cs new file mode 100644 index 000000000..798be78d8 --- /dev/null +++ b/Software/Visual_Studio/Tango.Synchronization/SqlDiffAction.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Synchronization +{ + /// <summary> + /// Represents the type of <see cref="SqlDiff"/>. + /// </summary> + public enum SqlDiffAction + { + /// <summary> + /// Add table to slave + /// </summary> + AddTableToSlave, + /// <summary> + /// Add column to slave + /// </summary> + AddColumnToSlave, + /// <summary> + /// Add row to slave + /// </summary> + AddRowToSlave, + /// <summary> + /// Add row to master + /// </summary> + AddRowToMaster, + /// <summary> + /// Update row in slave + /// </summary> + UpdateRowInSlave, + /// <summary> + /// Update row in master + /// </summary> + UpdateRowInMaster, + } +} diff --git a/Software/Visual_Studio/Tango.Synchronization/SqliteDataBase.cs b/Software/Visual_Studio/Tango.Synchronization/SqliteDataBase.cs new file mode 100644 index 000000000..3802232e0 --- /dev/null +++ b/Software/Visual_Studio/Tango.Synchronization/SqliteDataBase.cs @@ -0,0 +1,436 @@ +using Tango.Synchronization; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SQLite; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.Synchronization +{ + /// <summary> + /// Represents an SQLite database adapter used for synchronization by <see cref="SqlDataBaseComparer"/>. + /// </summary> + /// <seealso cref="Tango.Synchronization.ISqlDataBase" /> + public class SqliteDataBase : ISqlDataBase + { + private SQLiteConnection _connection; + + /// <summary> + /// Gets the database source URL/File. + /// </summary> + public String Source { get; private set; } + + /// <summary> + /// Gets the database tables collection. + /// </summary> + public List<DataTable> Tables { get; private set; } + + /// <summary> + /// Initializes a new instance of the <see cref="SqliteDataBase"/> class. + /// </summary> + /// <param name="filePath">The file path.</param> + public SqliteDataBase(String filePath) + { + Source = filePath; + + _connection = new SQLiteConnection(String.Format("Data Source={0};Version=3;New={1};Compress=FALSE;", + filePath, + (File.Exists(filePath) ? "False" : "True") + )); + + _connection.Open(); + + InitializeTables(); + } + + /// <summary> + /// Initializes the tables. + /// </summary> + private void InitializeTables() + { + Tables = new List<DataTable>(); + + var command = _connection.CreateCommand(); + command.CommandText = "SELECT name FROM sqlite_master WHERE type = 'table'"; + var reader = command.ExecuteReader(); + + while (reader.Read()) + { + String name = reader.GetString(0); + + if (name != Constants.SEQUENCE_TABLE_NAME) + { + Debug.WriteLine(name); + Tables.Add(LoadTable(name)); + } + } + } + + /// <summary> + /// Loads the table. + /// </summary> + /// <param name="name">The name.</param> + /// <returns></returns> + private DataTable LoadTable(String name) + { + var tableCommand = _connection.CreateCommand(); + tableCommand.CommandText = "SELECT * FROM " + name; + + DataTable table = new DataTable(name); + table.Load(tableCommand.ExecuteReader()); + + var infoCommand = _connection.CreateCommand(); + infoCommand.CommandText = String.Format("PRAGMA table_info({0});", name); + DataTable infoTable = new DataTable(name); + infoTable.Load(infoCommand.ExecuteReader()); + + table.ExtendedProperties.Add(Constants.TABLE_INFO, infoTable); + + for (int i = 0; i < table.Columns.Count; i++) + { + DataColumn column = table.Columns[i]; + column.ExtendedProperties.Add(Constants.COLUMN_TYPE, infoTable.Rows[i][Constants.COLUMN_TYPE]); + column.ExtendedProperties.Add(Constants.IS_NOT_NULL, infoTable.Rows[i][Constants.IS_NOT_NULL]); + column.ExtendedProperties.Add(Constants.DEFAULT_VALUE, infoTable.Rows[i][Constants.DEFAULT_VALUE]); + } + + return table; + } + + /// <summary> + /// Clones a table from another database into this database. + /// </summary> + /// <param name="otherDB">The other database.</param> + /// <param name="otherTable">The other table.</param> + /// <returns></returns> + public DataTable CloneTableFrom(ISqlDataBase otherDB, DataTable otherTable) + { + String cmd = String.Format("CREATE TABLE {0} (", otherTable.TableName); + + foreach (DataColumn column in otherTable.Columns) + { + cmd += String.Format("{0} {1} {2} {3} {4} {5}", + column.ColumnName, + column.GetSQLType(), + otherTable.PrimaryKey.Contains(column) ? "PRIMARY KEY" : null, + column.Unique ? "UNIQUE" : null, + column.IsNotNull() ? "NOT NULL" : null, + column.HasDefaultValue() ? ("DEFAULT " + column.GetDefaultValue()) : null) + + + ", " + Environment.NewLine; + } + + cmd += ");"; + + cmd = cmd.Remove(cmd.LastIndexOf(","), 1); + + var createCommand = _connection.CreateCommand(); + createCommand.CommandText = cmd; + createCommand.ExecuteNonQuery(); + + var attacheCommand = _connection.CreateCommand(); + attacheCommand.CommandText = String.Format("ATTACH DATABASE '{0}' AS other;", otherDB.Source); + attacheCommand.ExecuteNonQuery(); + + var copyCommand = _connection.CreateCommand(); + copyCommand.CommandText = String.Format("INSERT INTO main.{0} SELECT * FROM {1}.{0};", otherTable.TableName, "other"); + copyCommand.ExecuteNonQuery(); + + var detacheCommand = _connection.CreateCommand(); + detacheCommand.CommandText = "DETACH other;"; + detacheCommand.ExecuteNonQuery(); + + var table = LoadTable(otherTable.TableName); + Tables.Add(table); + return table; + } + + /// <summary> + /// Gets the SQL command for <see cref="CloneTableFrom(ISqlDataBase, DataTable)" />. + /// </summary> + /// <param name="otherDB">The other database.</param> + /// <param name="otherTable">The other table.</param> + /// <returns></returns> + public String GetCloneTableFromCommand(ISqlDataBase otherDB, DataTable otherTable) + { + String cmd = String.Format("CREATE TABLE {0} (", otherTable.TableName); + + foreach (DataColumn column in otherTable.Columns) + { + cmd += String.Format("{0} {1} {2} {3} {4} {5}", + column.ColumnName, + column.GetSQLType(), + otherTable.PrimaryKey.Contains(column) ? "PRIMARY KEY" : null, + column.Unique ? "UNIQUE" : null, + column.IsNotNull() ? "NOT NULL" : null, + column.HasDefaultValue() ? ("DEFAULT " + column.GetDefaultValue()) : null) + + + ", " + Environment.NewLine; + } + + cmd += ");"; + + cmd = cmd.Remove(cmd.LastIndexOf(","), 1); + + cmd += Environment.NewLine; + + cmd += String.Format("ATTACH DATABASE '{0}' AS other;", otherDB.Source); + + cmd += Environment.NewLine; + + cmd += String.Format("INSERT INTO main.{0} SELECT * FROM {1}.{0};", otherTable.TableName, "other"); + + cmd += Environment.NewLine; + + cmd += "DETACH other;"; + + return cmd; + } + + /// <summary> + /// Adds the specified column to the specified table. + /// </summary> + /// <param name="table">The table.</param> + /// <param name="column">The column.</param> + /// <returns></returns> + public DataColumn AddColumn(DataTable table, DataColumn column) + { + var insertCommand = _connection.CreateCommand(); + insertCommand.CommandText = String.Format("ALTER TABLE {0} ADD COLUMN {1} {2} {3} {4} {5} {6}", + table.TableName, + column.ColumnName, + column.GetSQLType(), + table.PrimaryKey.Contains(column) ? "PRIMARY KEY" : null, + column.Unique ? "UNIQUE" : null, + column.IsNotNull() ? "NOT NULL" : null, + column.HasDefaultValue() ? ("DEFAULT " + column.GetDefaultValue()) : null); + + + insertCommand.ExecuteNonQuery(); + var t = LoadTable(table.TableName); + var index = Tables.IndexOf(Tables.Single(x => x.TableName == table.TableName)); + Tables[index] = t; + return t.Columns[column.ColumnName]; + } + + /// <summary> + /// Gets the SQL command for <see cref="AddColumn(DataTable, DataColumn)" />. + /// </summary> + /// <param name="table">The table.</param> + /// <param name="column">The column.</param> + /// <returns></returns> + public String GetAddColumnCommand(DataTable table, DataColumn column) + { + String cmd = String.Format("ALTER TABLE {0} ADD COLUMN {1} {2} {3} {4} {5} {6}", + table.TableName, + column.ColumnName, + column.GetSQLType(), + table.PrimaryKey.Contains(column) ? "PRIMARY KEY" : null, + column.Unique ? "UNIQUE" : null, + column.IsNotNull() ? "NOT NULL" : null, + column.HasDefaultValue() ? ("DEFAULT " + column.GetDefaultValue()) : null); + + return cmd; + } + + /// <summary> + /// Adds the specified row to the specified table. + /// </summary> + /// <param name="table">The table.</param> + /// <param name="row">The row.</param> + public void AddRow(DataTable table, DataRow row) + { + var insertCommand = _connection.CreateCommand(); + + List<String> values = new List<string>(); + + for (int i = 0; i < row.ItemArray.Length; i++) + { + String value = row.ItemArray[i].ToString(); + + double num = 0; + + if (row.ItemArray[i].GetType() == typeof(DateTime)) + { + value = ((DateTime)row.ItemArray[i]).ToSQLiteDateString(); + } + + if (!double.TryParse(value, out num)) + { + value = "'" + value + "'"; + } + + values.Add(value); + } + + insertCommand.CommandText = String.Format("INSERT INTO {0} VALUES({1});", table.TableName, String.Join(",", values)); + insertCommand.ExecuteNonQuery(); + + var desRow = table.NewRow(); + desRow.ItemArray = row.ItemArray.Clone() as object[]; + } + + /// <summary> + /// Gets the SQL command for <see cref="AddRow(DataTable, DataRow)" />. + /// </summary> + /// <param name="table">The table.</param> + /// <param name="row">The row.</param> + /// <returns></returns> + public String GetAddRowCommand(DataTable table, DataRow row) + { + List<String> values = new List<string>(); + + for (int i = 0; i < row.ItemArray.Length; i++) + { + String value = row.ItemArray[i].ToString(); + + double num = 0; + + if (row.ItemArray[i].GetType() == typeof(DateTime)) + { + value = ((DateTime)row.ItemArray[i]).ToSQLiteDateString(); + } + + if (!double.TryParse(value, out num)) + { + value = "'" + value + "'"; + } + + values.Add(value); + } + + String cmd = String.Format("INSERT INTO {0} VALUES({1});", table.TableName, String.Join("," + Environment.NewLine, values)); + return cmd; + } + + /// <summary> + /// Updates the matching row by the row GUID. + /// </summary> + /// <param name="table">The table.</param> + /// <param name="row">The row.</param> + public void UpdateRow(DataTable table, DataRow row) + { + var updateCommand = _connection.CreateCommand(); + + List<String> values = new List<string>(); + + for (int i = 0; i < row.ItemArray.Length; i++) + { + String value = row.ItemArray[i].ToString(); + + double num = 0; + + if (row.ItemArray[i].GetType() == typeof(DateTime)) + { + value = ((DateTime)row.ItemArray[i]).ToSQLiteDateString(); + } + + if (!double.TryParse(value, out num)) + { + value = "'" + value + "'"; + } + + values.Add(value); + } + + String cmd = String.Format("UPDATE {0} SET ", table.TableName); + + String guid = String.Empty; + + for (int columnIndex = 0; columnIndex < table.Columns.Count; columnIndex++) + { + DataColumn column = table.Columns[columnIndex]; + if (column.ColumnName == Constants.ID) + { + continue; + } + else if (column.ColumnName == Constants.GUID) + { + guid = values[columnIndex]; + continue; + } + + cmd += String.Format("{0} = {1},{2}", column.ColumnName, values[columnIndex], Environment.NewLine); + } + + cmd = cmd.Remove(cmd.LastIndexOf(","), 1); + + cmd += "WHERE" + Environment.NewLine; + cmd += String.Format("{0} = {1};", Constants.GUID, guid); + + updateCommand.CommandText = cmd; + updateCommand.ExecuteNonQuery(); + var desRow = table.NewRow(); + desRow.ItemArray = row.ItemArray.Clone() as object[]; + } + + /// <summary> + /// Gets the SQL command for <see cref="UpdateRow(DataTable, DataRow)" />. + /// </summary> + /// <param name="table">The table.</param> + /// <param name="row">The row.</param> + /// <returns></returns> + public String GetUpdateRowCommand(DataTable table, DataRow row) + { + List<String> values = new List<string>(); + + for (int i = 0; i < row.ItemArray.Length; i++) + { + String value = row.ItemArray[i].ToString(); + + double num = 0; + + if (row.ItemArray[i].GetType() == typeof(DateTime)) + { + value = ((DateTime)row.ItemArray[i]).ToSQLiteDateString(); + } + + if (!double.TryParse(value, out num)) + { + value = "'" + value + "'"; + } + + values.Add(value); + } + + String cmd = String.Format("UPDATE {0} SET ", table.TableName); + + String guid = String.Empty; + + for (int columnIndex = 0; columnIndex < table.Columns.Count; columnIndex++) + { + DataColumn column = table.Columns[columnIndex]; + if (column.ColumnName == Constants.ID) + { + continue; + } + else if (column.ColumnName == Constants.GUID) + { + guid = values[columnIndex]; + continue; + } + + cmd += String.Format("{0} = {1},{2}", column.ColumnName, values[columnIndex], Environment.NewLine); + } + + cmd = cmd.Remove(cmd.LastIndexOf(","), 1); + + cmd += "WHERE" + Environment.NewLine; + cmd += String.Format("{0} = {1};", Constants.GUID, guid); + + return cmd; + } + + /// <summary> + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// </summary> + public void Dispose() + { + _connection.Close(); + } + } +} diff --git a/Software/Visual_Studio/Tango.Synchronization/Tango.Synchronization.csproj b/Software/Visual_Studio/Tango.Synchronization/Tango.Synchronization.csproj new file mode 100644 index 000000000..da8054956 --- /dev/null +++ b/Software/Visual_Studio/Tango.Synchronization/Tango.Synchronization.csproj @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{7ADA4E86-CAD7-4968-A210-3A8A9E5153AB}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Tango.Synchronization</RootNamespace> + <AssemblyName>Tango.Synchronization</AssemblyName> + <TargetFrameworkVersion>v4.5</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + <NuGetPackageImportStamp> + </NuGetPackageImportStamp> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> + <DebugSymbols>true</DebugSymbols> + <DebugType>full</DebugType> + <Optimize>false</Optimize> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> + <DebugType>pdbonly</DebugType> + <Optimize>true</Optimize> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <ErrorReport>prompt</ErrorReport> + <WarningLevel>4</WarningLevel> + </PropertyGroup> + <ItemGroup> + <Reference Include="EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL"> + <HintPath>..\packages\EntityFramework.6.0.0\lib\net45\EntityFramework.dll</HintPath> + </Reference> + <Reference Include="EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL"> + <HintPath>..\packages\EntityFramework.6.0.0\lib\net45\EntityFramework.SqlServer.dll</HintPath> + </Reference> + <Reference Include="System" /> + <Reference Include="System.ComponentModel.DataAnnotations" /> + <Reference Include="System.Core" /> + <Reference Include="System.Data.SQLite, Version=1.0.106.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Data.SQLite.Core.1.0.106.0\lib\net45\System.Data.SQLite.dll</HintPath> + </Reference> + <Reference Include="System.Data.SQLite.EF6, Version=1.0.106.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Data.SQLite.EF6.1.0.106.0\lib\net45\System.Data.SQLite.EF6.dll</HintPath> + </Reference> + <Reference Include="System.Data.SQLite.Linq, Version=1.0.106.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL"> + <HintPath>..\packages\System.Data.SQLite.Linq.1.0.106.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath> + </Reference> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Net.Http" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="..\Versioning\GlobalVersionInfo.cs"> + <Link>GlobalVersionInfo.cs</Link> + </Compile> + <Compile Include="Constants.cs" /> + <Compile Include="ExtensionMethods.cs" /> + <Compile Include="ISqlDataBase.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="SqlDataBaseComparer.cs" /> + <Compile Include="SqlDiff.cs" /> + <Compile Include="SqlDiffAction.cs" /> + <Compile Include="SqliteDataBase.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="App.config" /> + <None Include="packages.config" /> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <Import Project="..\packages\System.Data.SQLite.Core.1.0.106.0\build\net45\System.Data.SQLite.Core.targets" Condition="Exists('..\packages\System.Data.SQLite.Core.1.0.106.0\build\net45\System.Data.SQLite.Core.targets')" /> + <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> + <PropertyGroup> + <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> + </PropertyGroup> + <Error Condition="!Exists('..\packages\System.Data.SQLite.Core.1.0.106.0\build\net45\System.Data.SQLite.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.Data.SQLite.Core.1.0.106.0\build\net45\System.Data.SQLite.Core.targets'))" /> + </Target> +</Project>
\ No newline at end of file diff --git a/Software/Visual_Studio/Tango.Synchronization/packages.config b/Software/Visual_Studio/Tango.Synchronization/packages.config new file mode 100644 index 000000000..1c70a6138 --- /dev/null +++ b/Software/Visual_Studio/Tango.Synchronization/packages.config @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="EntityFramework" version="6.0.0" targetFramework="net45" /> + <package id="System.Data.SQLite" version="1.0.106.0" targetFramework="net45" /> + <package id="System.Data.SQLite.Core" version="1.0.106.0" targetFramework="net45" /> + <package id="System.Data.SQLite.EF6" version="1.0.106.0" targetFramework="net45" /> + <package id="System.Data.SQLite.Linq" version="1.0.106.0" targetFramework="net45" /> +</packages>
\ No newline at end of file |
