using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Data; using WmData; using System.Reflection; using System.Diagnostics; using MySql.Data.MySqlClient; namespace WmData { /// /// WmData: The ADO.NET based object-to-SQL and SQL-to-object system /// Original data was part of the Manager system before it /// was used in systems like WMCM and WMDS. That data was based /// upon the original data as created on Curacao october 2004. /// The first version of this library was based upon the /// Secure Checkout version. /// Original WmData created at 18th of October 2007 /// /// Updated to conform WmGenericData at 3rd of August 2010 /// public class WmData : WmGenericData { protected WmDataAdapterManager dataAdapterManager ; protected object adapterLinkLock = new object(); protected DataSet wmDataSet; protected WmDataFileSupport fileSupport; public WmData(string databaseUsername, string databasePassword, string databaseHost, int databasePort, string databaseCatalog) : base(databaseUsername,databasePassword,databaseHost,databasePort,databaseCatalog) { Trace.WriteLine("WmData initiated for " + databaseCatalog+ " on host "+databaseHost); } public WmData(string databaseUsername, string databasePassword, string databaseHost, int databasePort, string databaseCatalog, string thisServerAddress, List dataLinkServers, int syncIntervalInSeconds, ListsyncObjectTypes) : base(databaseUsername, databasePassword, databaseHost, databasePort, databaseCatalog,thisServerAddress, dataLinkServers,syncIntervalInSeconds,syncObjectTypes) { Trace.WriteLine("WmData initiated for " + databaseCatalog + " on host " + databaseHost); } /// /// Reset the data set, live link, id manager and file support /// public override void reset() { if (wmDataSet != null) { wmDataSet.Dispose(); } if (dataAdapterManager != null) { dataAdapterManager = null; } if (idManager != null) { idManager = null; } if (fileSupport != null) { fileSupport = null; } wmDataSet = new DataSet("wmdata"); dataAdapterManager = new WmDataAdapterManager(this, dataBaseCredentials); idManager = new WmDataIdManager(dataBaseCredentials); fileSupport = new WmDataFileSupport(this, idManager, dataBaseCredentials); } /// /// Checks if object has a database adapter and SQL table. /// This is required for new objects or the retrieval of objects, you should not call this method when updating. /// /// public void checkDataAdapter(Type objectType) { string tableName = WmDataTools.getTableName(objectType); lock (adapterLinkLock) { if (!dataAdapterManager.isAdapted(tableName)) { dataAdapterManager.createAdapterForObject(objectType); } } } #region ADD and IMPORT: addDataRow, importDataRow, addObject /// /// Add a data row and automatically assigns a new id /// public void addDataRow(DataRow newRow) { addDataRow(newRow, false); } /// /// Add a data row and specify to override the auto id /// public void addDataRow(DataRow newRow, bool overrideAutoId) { addDataRow(newRow, overrideAutoId, -1); } /// /// Add a data row using and specify override auto id and reference transaction moment /// public override void addDataRow(Type objectType, DataRow addRow, bool overrideAutoId, long referenceTransactionMomentUtcTics) { addDataRow(addRow, overrideAutoId, referenceTransactionMomentUtcTics); } /// /// Add a data row, specify override auto id and reference transaction moment /// public void addDataRow(DataRow newRow, bool overrideAutoId, long referenceTransactionMomentUtcTicks) { string tableName = WmDataTools.getTableName(newRow); DataTable addTable = WmDataSet.Tables[tableName]; lock (addTable) { int givenId = -1; if (!overrideAutoId) { givenId = idManager.getNewId(tableName); newRow["id"] = givenId; } addTable.Rows.Add(newRow); dataAdapterManager.update(tableName); } raiseDataChangeEvent(tableName, WmDataRowAction.ADD, (int)newRow["id"]); if (transactionManager != null) { transactionManager.registerInsertOrUpdateTransaction(tableName,newRow,WmDataRowAction.ADD,referenceTransactionMomentUtcTicks); } } /// /// Import a given data row, automatically assigns an Id /// public void importDataRow(DataRow importRow) { importDataRow(importRow, false); } /// /// Import a given data row and specify override auto id /// public void importDataRow(DataRow importRow, bool overrideAutoId) { importDataRow(importRow, overrideAutoId, -1); } /// /// Import a given data row and specify override auto id and reference transaction moment /// public override void importDataRow(Type objectType, DataRow importRow, bool overrideAutoId, long referenceTransactionMomentUtcTicks) { importDataRow(importRow, overrideAutoId, referenceTransactionMomentUtcTicks); } /// /// Import a given data row and specify override auto id and reference transaction moment /// public void importDataRow(DataRow importRow, bool overrideAutoId, long referenceTransactionMomentUtcTicks) { string tableName = WmDataTools.getTableName(importRow); DataTable importTable = WmDataSet.Tables[tableName]; lock (importTable) { int givenId = -1; if (!overrideAutoId) { givenId = idManager.getNewId(tableName); importRow["id"] = givenId; } importTable.ImportRow(importRow); // Reset the row state importRow.AcceptChanges(); importRow.SetAdded(); // will cause the backend to add this row to the sql server dataAdapterManager.update(tableName); } raiseDataChangeEvent(tableName, WmDataRowAction.ADD, (int)importRow["id"]); if (transactionManager != null) { transactionManager.registerInsertOrUpdateTransaction(tableName,importRow,WmDataRowAction.ADD,referenceTransactionMomentUtcTicks); } } /// /// Add an object and specify override auto id and reference transaction moment /// public override void addObject(object objectInstance, bool overrideAutoId, long referenceTransactionMomentUtcTicks) { if (objectInstance is WmDataFile) { throw new Exception("WmDataFiles should only be handled by the WmDataFileSupport object"); } Type objectType = objectInstance.GetType(); WmIntermediateObject intermediateObject = getIntermediateObject(objectType); string tableName = intermediateObject.TableName; // Check if object exists in database backend and check if it is adapted correctly checkDataAdapter(objectType); int givenId = -1; DataTable addTable = WmDataSet.Tables[tableName]; lock (addTable) { // Create a new row DataRow newRow = addTable.NewRow(); // Set values of object to corresponding columns in newly created row: updateRowValuesFromObjectProperties(newRow, objectInstance,intermediateObject); // Now workout the possible auto id of the new row: if (!overrideAutoId) { givenId = idManager.getNewId(tableName); intermediateObject.IdProperty.Setter(objectInstance, givenId); newRow["id"] = givenId; } else { givenId = intermediateObject.getObjectInstanceId(objectInstance); newRow["id"] = givenId; // Override of auto id was request, check if row isn't already present: DataRow conflictingRow = getDataRow(addTable, givenId); if (conflictingRow != null) { // OOPS! Conflicting id was found, check if rows arent a duplicate... if (isDuplicateRow(newRow, conflictingRow)) { // TODO: Report this by throwing internal event Trace.WriteLine("WmData: Ignored duplicate entry of object in table=" + tableName + " id=" + newRow["id"]); return; // ignore the duplicate, none gets hurt.. } else { // Conflicting row was found and was not a duplicate, data mismatch so it seems: throw new Exception("WmData: Duplicate entry found, table=" + tableName + " id=" + newRow["id"]); } } } // Add the object instance to the row based object cache: newRow["objectinstance"] = objectInstance; addTable.Rows.Add(newRow); // add the row to the table dataAdapterManager.update(tableName); // update the database backend } raiseDataChangeEvent(tableName, WmDataRowAction.ADD, givenId); if (transactionManager != null) { transactionManager.registerInsertOrUpdateTransaction(intermediateObject,givenId,WmDataRowAction.ADD,objectInstance,referenceTransactionMomentUtcTicks); } } #endregion #region UPDATE: updateDataRow, updateObject /// /// Update the data row /// public void updateDataRow(DataRow updateRow) { updateDataRow(updateRow, -1); } /// /// Update a data row and specify the reference transaction moment /// public override void updateDataRow(Type objectType, DataRow updateRow, long referenceTransactionMomentUtcTicks) { updateDataRow(updateRow, referenceTransactionMomentUtcTicks); } /// /// Update a data row and specify the reference transaction moment /// public void updateDataRow(DataRow updateRow, long referenceTransactionMomentUtcTicks) { string tableName = WmDataTools.getTableName(updateRow); DataTable updateTable = WmDataSet.Tables[tableName]; lock (updateTable) { updateRow.EndEdit(); dataAdapterManager.update(tableName); } raiseDataChangeEvent(tableName, WmDataRowAction.UPDATE, (int)updateRow["id"]); if (transactionManager != null) { transactionManager.registerInsertOrUpdateTransaction(tableName,updateRow,WmDataRowAction.UPDATE,referenceTransactionMomentUtcTicks); } } /// /// Update an object and specify the reference transaction moment /// public override void updateObject(object objectInstance, long referenceTransactionMomentUtcTicks) { if (objectInstance is WmDataFile) { throw new Exception("WmDataFiles should only be handled by the WmDataFileSupport object"); } Type objectType = objectInstance.GetType(); bool objectWasChanged = false; WmIntermediateObject intermediateObject = getIntermediateObject(objectType); int objectId = intermediateObject.getObjectInstanceId(objectInstance); string tableName = WmDataTools.getTableName(objectType); DataTable updateTable = WmDataSet.Tables[tableName]; lock (updateTable) { DataRow updateRow = getDataRow(updateTable, objectId); if (updateRow == null) { Trace.WriteLine("WmData: Request to update a non existing object. table=" + tableName + " objectid=" + objectId); return; // no one gets hurt, perhaps we should do this to prevent these kinds of bogus calls... } else { updateRow.BeginEdit(); if (updateRowValuesFromObjectProperties(updateRow, objectInstance,intermediateObject)) { objectWasChanged = true; // indicating something was in fact changed updateRow.EndEdit(); // Stop editing row, causing row to be marked as edited dataAdapterManager.update(tableName); // write the changes back to SQL backend, only this row will be updated } else { // the original row and the object had the same values, ignore this by not performing backend SQL update updateRow.CancelEdit(); objectWasChanged = false; Trace.WriteLine("WmData: Ignored obsolete update, DataRow was already the same as object properties. Table " + tableName + " objectId=" + objectId); } } } if (objectWasChanged) { raiseDataChangeEvent(tableName, WmDataRowAction.UPDATE, objectId); } if ((objectWasChanged || referenceTransactionMomentUtcTicks > 0) && transactionManager!=null) { transactionManager.registerInsertOrUpdateTransaction(intermediateObject, objectId, WmDataRowAction.UPDATE, objectInstance, referenceTransactionMomentUtcTicks); } } #endregion #region DELETE: deleteDataRow, deleteObject, deleteTableContents /// /// Delete a data row /// public void deleteDataRow(DataRow row) { deleteDataRow(row,-1); // event fires there, not here } /// /// Delete a data row and specify the reference transaction moment /// public void deleteDataRow(DataRow row, long referenceTransactionMomentUtcTicks) { string tableName = row.Table.TableName; int id = (int)row["id"]; deleteObject(tableName, id, referenceTransactionMomentUtcTicks); // event fires there, not here. } /// /// Delete a data row and specify the reference transaction moment /// public override void deleteDataRow(Type objectType, DataRow deleteRow, long referenceTransactionMomentUtcTicks) { deleteDataRow(deleteRow, referenceTransactionMomentUtcTicks); } /// /// Delete an object and specify the reference tranasaction moment /// public override void deleteObject(Type objectType, int id, long referenceTransactionMomentUtcTicks) { deleteObject(WmDataTools.getTableName(objectType), id, -1); // event fires there, not here. } /// /// Delete an object and specify the reference transaction moment /// public void deleteObject(string tableName, int objectId, long referenceTransactionMomentUtcTicks) { if (tableName.Equals(WmDataFileSupport.TableName)) { throw new Exception("WmDataFiles should only be handled by the WmDataFileSupport object"); } DataTable delTable = WmDataSet.Tables[tableName]; lock (delTable) { DataRow deleteRow = getDataRow(delTable, objectId); if (deleteRow != null) { // Check if row still exists deleteRow.Delete(); // will mark the row as to be deleted dataAdapterManager.update(tableName); // will perform the row removal on DataBase backend } else { Trace.WriteLine("WmData: Ignored attempt to remove non existant object. Table " + tableName + " objectId=" + objectId); } } raiseDataChangeEvent(tableName, WmDataRowAction.DELETE, objectId); if (transactionManager != null) { transactionManager.registerDeleteTransaction(tableName,objectId,referenceTransactionMomentUtcTicks); } } /// /// Delete table contents (each and every row) /// public override void deleteTableContents(Type objectType) { string tableName = WmDataTools.getTableName(objectType); checkDataAdapter(objectType); DataTable delTable = WmDataSet.Tables[tableName]; lock (delTable) { DataRow[] allRows = delTable.Select(); for(int r=0; r /// Get data row matching the select /// public override DataRow getDataRow(Type objectType, string select) { return getDataRow(WmDataTools.getTableName(objectType), select); } /// /// Get data row matching the select /// public DataRow getDataRow(string tableName, string select) { DataTable table = WmDataSet.Tables[tableName]; return getDataRow(table, select); } /// /// Get data row matching the select /// public DataRow getDataRow(DataTable table, string select) { lock (table) { DataRow[] tmpResult = table.Select(select); if (tmpResult.Length > 0) { return tmpResult[0]; } else { return null; } } } /// /// Get data row matching the specified id /// public override DataRow getDataRow(Type objectType, int id) { DataTable table = WmDataSet.Tables[WmDataTools.getTableName(objectType)]; return getDataRow(table, id); } /// /// Get data row matching the specified id /// public DataRow getDataRow(DataTable table, int rowId) { return table.Rows.Find(rowId); // find the row matching the rowId as primary key } /// /// Get data rows matching the select /// public DataRow[] getDataRows(string selectTableName, string select) { return getDataRows(selectTableName, select, null); } /// /// Get data rows matching the select /// public DataRow[] getDataRows(DataTable selectTable, string select) { return getDataRows(selectTable, select, null); } /// /// Get data rows matching the select, sorted as specified /// public override DataRow[] getDataRows(Type objectType, string select,string sort) { checkDataAdapter(objectType); string tableName = WmDataTools.getTableName(objectType); return getDataRows(tableName, select,sort); } /// /// Get data rows matching the select, sorted as specified /// public DataRow[] getDataRows(string selectTableName, string select, string sort) { DataTable selectTable = WmDataSet.Tables[selectTableName]; return getDataRows(selectTable, select, sort); } /// /// Get data rows matchign the select, sorted as specified /// public DataRow[] getDataRows(DataTable selectTable, string select, string sort) { lock (selectTable) { return selectTable.Select(select, sort); } } /// /// Get object matching the specified id /// public override object getObject(Type objectType, int id) { object returnObject = null; string tableName = WmDataTools.getTableName(objectType); checkDataAdapter(objectType); DataTable selectTable = WmDataSet.Tables[tableName]; lock (selectTable) { DataRow result = getDataRow(selectTable, id); if (result != null) { checkObjectCacheInRow(result, objectType); returnObject = result["objectinstance"]; } } return returnObject; } /// /// Get object matching the select /// public override object getObject(Type objectType, string select) { object returnObject = null; string tableName = WmDataTools.getTableName(objectType); checkDataAdapter(objectType); DataTable selectTable = WmDataSet.Tables[tableName]; lock (selectTable) { DataRow[] results = getDataRows(selectTable, select); if (results.Length > 0) { checkObjectCacheInRow(results[0], objectType); returnObject = results[0]["objectinstance"]; } } return returnObject; } /// /// Get objects matching the select /// public override ArrayList getObjects(Type objectType, string select) { string tableName = WmDataTools.getTableName(objectType); if (tableName.Equals("runtimetype")) { throw new Exception("WmData: Select Object was a System.Type"); } if (tableName.Equals(WmDataFileSupport.TableName)) { throw new Exception("WmDataFiles should only be handled by the WmDataFileSupport object"); } checkDataAdapter(objectType); DataTable selectTable = WmDataSet.Tables[tableName]; ArrayList objects = new ArrayList(); lock (selectTable) { DataRow[] result; lock (selectTable) { result = getDataRows(selectTable, select); for (int i = 0; i < result.Length; i++) { checkObjectCacheInRow(result[i], objectType); objects.Add(result[i]["objectinstance"]); } } } return objects; } /// /// Get the amount of rows for this object type /// public override int getRowCount(Type objectType) { string tableName = WmDataTools.getTableName(objectType); int rowCount = 0; checkDataAdapter(objectType); DataTable selectTable = WmDataSet.Tables[tableName]; lock (selectTable) { rowCount = selectTable.Rows.Count; } return rowCount; } #endregion /// /// Create an object instance if necessary /// protected void checkObjectCacheInRow(DataRow row, Type objectType) { if (row["objectinstance"] is DBNull) { row["objectinstance"] = getObjectInstanceFromRow(objectType, row); row.AcceptChanges(); // Otherwise this row is marked as updated and SQL backend will be forced to update this, bad for large tables } } /// /// Data File Support /// public WmDataFileSupport FileSupport{ get { return fileSupport; } } /// /// The ADO.NET data set used by this data instance /// public DataSet WmDataSet { get { return wmDataSet; } set { wmDataSet = value; } } } }