using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; using DOL.Database.Attributes; using DOL.Database.Connection; using DOL.Database.Handlers; using DOL.Logging; namespace DOL.Database { /// /// Default Object Database Base Implementation /// public abstract class ObjectDatabase : IObjectDatabase { protected static readonly Logger log = LoggerManager.Create(MethodBase.GetCurrentMethod().DeclaringType); protected const long LONG_EXEC_THRESHOLD = 100; /// /// Number Format Info to Use for Database /// protected static readonly NumberFormatInfo Nfi = new CultureInfo("en-US", false).NumberFormat; private static readonly ConcurrentDictionary, Array>> _castToArrayCache = new(); /// /// Data Table Handlers for this Database Handler /// protected readonly Dictionary TableDatasets = new Dictionary(); /// /// Connection String for this Database /// protected string ConnectionString { get; set; } /// /// Creates a new Instance of /// /// Database Connection String protected ObjectDatabase(string ConnectionString) { this.ConnectionString = ConnectionString; } /// /// Helper to Retrieve Table Handler from Object Type /// Return Real Table Handler for Modifications Queries /// /// Object Type /// DataTableHandler for this Object Type or null. protected DataTableHandler GetTableHandler(Type objectType) { var tableName = AttributeUtil.GetTableName(objectType); DataTableHandler handler; return TableDatasets.TryGetValue(tableName, out handler) ? handler : null; } /// /// Helper to Retrieve Table or View Handler from Object Type /// Return View or Table for Select Queries /// /// Object Type /// DataTableHandler for this Object Type or null. protected DataTableHandler GetTableOrViewHandler(Type objectType) { var tableName = AttributeUtil.GetTableOrViewName(objectType); DataTableHandler handler; return TableDatasets.TryGetValue(tableName, out handler) ? handler : null; } #region Public Add Objects Implementation /// /// Insert a new DataObject into the database and save it /// /// DataObject to Add into database /// True if the DataObject was added. public bool AddObject(DataObject dataObject) { return AddObject(new [] { dataObject }); } /// /// Insert new DataObjects into the database and save them /// /// DataObjects to Add into database /// True if All DataObjects were added. public bool AddObject(IEnumerable dataObjects) { var success = true; foreach (var grp in dataObjects.GroupBy(obj => obj.GetType())) { var tableHandler = GetTableHandler(grp.Key); if (tableHandler == null) { if (log.IsErrorEnabled) log.ErrorFormat("AddObject: DataObject Type ({0}) not registered !", grp.Key.FullName); success = false; continue; } foreach (var allowed in grp.GroupBy(item => item.AllowAdd)) { if (allowed.Key) { var objs = allowed.ToArray(); var results = AddObjectImpl(tableHandler, objs); var resultsByObjs = results.Select((result, index) => new { Success = result, DataObject = objs[index] }) .GroupBy(obj => obj.Success); foreach (var resultGrp in resultsByObjs) { if (resultGrp.Key) { // Save in Precache if tablehandler use it if (tableHandler.UsesPreCaching) { var primary = tableHandler.PrimaryKey; if (primary != null) { foreach (var successObj in resultGrp.Select(obj => obj.DataObject)) tableHandler.SetPreCachedObject(primary.GetValue(successObj), successObj); } } // Success Objects Need Relations Save if (tableHandler.HasRelations) success &= SaveObjectRelations(tableHandler, resultGrp.Select(obj => obj.DataObject)); } else { if (log.IsErrorEnabled) { foreach(var obj in resultGrp) log.ErrorFormat("AddObjects: DataObject ({0}) could not be inserted into database...", obj.DataObject); } success = false; } } } else { if (log.IsWarnEnabled) { foreach (var obj in allowed) log.WarnFormat("AddObject: DataObject ({0}) not allowed to be added to Database", obj); } success = false; } } } return success; } #endregion #region Public Save Objects Implementation /// /// Saves a DataObject to database if saving is allowed and object is dirty /// /// DataObject to Save in database /// True is the DataObject was saved. public bool SaveObject(DataObject dataObject) { return SaveObject(new [] { dataObject }); } /// /// Save DataObjects to database if saving is allowed and object is dirty /// /// DataObjects to Save in database /// True if All DataObjects were saved. public bool SaveObject(IEnumerable dataObjects) { var success = true; foreach (var grp in dataObjects.GroupBy(obj => obj.GetType())) { var tableHandler = GetTableHandler(grp.Key); if (tableHandler == null) { if (log.IsErrorEnabled) log.ErrorFormat("SaveObject: DataObject Type ({0}) not registered !", grp.Key.FullName); success = false; continue; } var objs = grp.Where(obj => obj.Dirty).ToArray(); var results = SaveObjectImpl(tableHandler, objs); var resultsByObjs = results.Select((result, index) => new { Success = result, DataObject = objs[index] }) .GroupBy(obj => obj.Success); foreach (var resultGrp in resultsByObjs) { if (resultGrp.Key) { // Save in Precache if tablehandler use it if (tableHandler.UsesPreCaching) { var primary = tableHandler.PrimaryKey; if (primary != null) { foreach (var successObj in resultGrp.Select(obj => obj.DataObject)) tableHandler.SetPreCachedObject(primary.GetValue(successObj), successObj); } } } else { if (log.IsErrorEnabled) { foreach(var obj in resultGrp) log.ErrorFormat("SaveObject: DataObject ({0}) could not be saved into database...", obj.DataObject); } success = false; } } if (tableHandler.HasRelations) success &= SaveObjectRelations(tableHandler, grp); } return success; } #endregion #region Public Delete Objects Implementation /// /// Delete a DataObject from database if deletion is allowed /// /// DataObject to Delete from database /// True if the DataObject was deleted. public bool DeleteObject(DataObject dataObject) { return DeleteObject(new [] { dataObject }); } /// /// Delete DataObjects from database if deletion is allowed /// /// DataObjects to Delete from database /// True if All DataObjects were deleted. public bool DeleteObject(IEnumerable dataObjects) { var success = true; foreach (var grp in dataObjects.GroupBy(obj => obj.GetType())) { var tableHandler = GetTableHandler(grp.Key); if (tableHandler == null) { if (log.IsErrorEnabled) log.ErrorFormat("DeleteObject: DataObject Type ({0}) not registered !", grp.Key.FullName); success = false; continue; } foreach (var allowed in grp.GroupBy(item => item.AllowDelete)) { if (allowed.Key) { var objs = allowed.ToArray(); var results = DeleteObjectImpl(tableHandler, objs); var resultsByObjs = results.Select((result, index) => new { Success = result, DataObject = objs[index] }) .GroupBy(obj => obj.Success); foreach (var resultGrp in resultsByObjs) { if (resultGrp.Key) { // Delete in Precache if tablehandler use it if (tableHandler.UsesPreCaching) { var primary = tableHandler.PrimaryKey; if (primary != null) { foreach (var successObj in resultGrp.Select(obj => obj.DataObject)) tableHandler.DeletePreCachedObject(primary.GetValue(successObj)); } } // Success Objects Need to check Relations that should be deleted if (tableHandler.HasRelations) success &= DeleteObjectRelations(tableHandler, resultGrp.Select(obj => obj.DataObject)); } else { if (log.IsErrorEnabled) { foreach(var obj in resultGrp) log.ErrorFormat("DeleteObject: DataObject ({0}) could not be deleted from database...", obj.DataObject); } success = false; } } } else { if (log.IsWarnEnabled) { foreach (var obj in allowed) log.WarnFormat("DeleteObject: DataObject ({0}) not allowed to be deleted from Database", obj); } success = false; } } } return success; } #endregion #region Relation Update Handling /// /// Save Relations Objects attached to DataObjects /// /// TableHandler for Source DataObjects Relation /// DataObjects to parse /// True if all Relations were saved protected bool SaveObjectRelations(DataTableHandler tableHandler, IEnumerable dataObjects) { var success = true; foreach (var relation in tableHandler.ElementBindings.Where(bind => bind.Relation != null)) { // Relation Check var remoteHandler = GetTableHandler(relation.ValueType); if (remoteHandler == null) { if (log.IsErrorEnabled) log.ErrorFormat("SaveObjectRelations: Remote Table for Type ({0}) is not registered !", relation.ValueType.FullName); success = false; continue; } // Check For Array Type var groups = relation.ValueType.HasElementType ? dataObjects.Select(obj => new { Source = obj, Enumerable = (IEnumerable)relation.GetValue(obj) }) .Where(obj => obj.Enumerable != null).Select(obj => obj.Enumerable.Select(rel => new { Local = obj.Source, Remote = rel })) .SelectMany(obj => obj).Where(obj => obj.Remote != null).GroupBy(obj => obj.Remote.IsPersisted) : dataObjects.Select(obj => new { Local = obj, Remote = (DataObject)relation.GetValue(obj) }).Where(obj => obj.Remote != null).GroupBy(obj => obj.Remote.IsPersisted); foreach (var grp in groups) { // Group by object that can be added or saved foreach (var allowed in grp.GroupBy(obj => grp.Key ? obj.Remote.Dirty : obj.Remote.AllowAdd)) { if (allowed.Key) { var objs = allowed.ToArray(); var results = grp.Key ? SaveObjectImpl(remoteHandler, objs.Select(obj => obj.Remote)) : AddObjectImpl(remoteHandler, objs.Select(obj => obj.Remote)); var resultsByObjs = results.Select((result, index) => new { Success = result, RelObject = objs[index] }); foreach (var resultGrp in resultsByObjs.GroupBy(obj => obj.Success)) { if (resultGrp.Key) { // Update in Precache if tablehandler use it if (remoteHandler.UsesPreCaching) { var primary = remoteHandler.PrimaryKey; if (primary != null) { foreach (var successObj in resultGrp.Select(obj => obj.RelObject.Remote)) remoteHandler.SetPreCachedObject(primary.GetValue(successObj), successObj); } } } else { if (log.IsErrorEnabled) { foreach (var result in resultGrp) log.ErrorFormat("SaveObjectRelations: {0} Relation ({1}) of DataObject ({2}) failed for Object ({3})", grp.Key ? "Saving" : "Adding", relation.ValueType, result.RelObject.Local, result.RelObject.Remote); } success = false; } } } else { // Objects that could not be added can lead to failure if (!grp.Key) { if (log.IsWarnEnabled) { foreach (var obj in allowed) log.WarnFormat("SaveObjectRelations: DataObject ({0}) not allowed to be added to Database", obj); } success = false; } } } } } return success; } /// /// Delete Relations Objects attached to DataObjects /// /// TableHandler for Source DataObjects Relation /// DataObjects to parse /// True if all Relations were deleted public bool DeleteObjectRelations(DataTableHandler tableHandler, IEnumerable dataObjects) { var success = true; foreach (var relation in tableHandler.ElementBindings.Where(bind => bind.Relation != null && bind.Relation.AutoDelete)) { // Relation Check var remoteHandler = GetTableHandler(relation.ValueType); if (remoteHandler == null) { if (log.IsErrorEnabled) log.ErrorFormat("DeleteObjectRelations: Remote Table for Type ({0}) is not registered !", relation.ValueType.FullName); success = false; continue; } // Check For Array Type var groups = relation.ValueType.HasElementType ? dataObjects.Select(obj => new { Source = obj, Enumerable = (IEnumerable)relation.GetValue(obj) }) .Where(obj => obj.Enumerable != null).Select(obj => obj.Enumerable.Select(rel => new { Local = obj.Source, Remote = rel })) .SelectMany(obj => obj).Where(obj => obj.Remote != null && obj.Remote.IsPersisted) : dataObjects.Select(obj => new { Local = obj, Remote = (DataObject)relation.GetValue(obj) }).Where(obj => obj.Remote != null && obj.Remote.IsPersisted); foreach (var grp in groups.GroupBy(obj => obj.Remote.AllowDelete)) { if (grp.Key) { var objs = grp.ToArray(); var results = DeleteObjectImpl(remoteHandler, objs.Select(obj => obj.Remote)); var resultsByObjs = results.Select((result, index) => new { Success = result, RelObject = objs[index] }); foreach (var resultGrp in resultsByObjs.GroupBy(obj => obj.Success)) { if (resultGrp.Key) { // Delete in Precache if tablehandler use it if (remoteHandler.UsesPreCaching) { var primary = remoteHandler.PrimaryKey; if (primary != null) { foreach (var successObj in resultGrp.Select(obj => obj.RelObject.Remote)) remoteHandler.DeletePreCachedObject(primary.GetValue(successObj)); } } } else { foreach (var result in resultGrp) { if (log.IsErrorEnabled) log.ErrorFormat("DeleteObjectRelations: Deleting Relation ({0}) of DataObject ({1}) failed for Object ({2})", relation.ValueType, result.RelObject.Local, result.RelObject.Remote); } success = false; } } } else { // Objects that could not be deleted can lead to failure if (log.IsWarnEnabled) { foreach (var obj in grp) log.WarnFormat("DeleteObjectRelations: DataObject ({0}) not allowed to be deleted from Database", obj); } success = false; } } } return success; } #endregion #region Relation Select/Fill Handling /// /// Populate or Refresh Objects Relations /// /// Objects to Populate public void FillObjectRelations(IEnumerable dataObjects) { // Interface Call, force Refresh FillObjectRelations(dataObjects, true); } /// /// Populate or Refresh Object Relations /// /// Object to Populate public void FillObjectRelations(DataObject dataObject) { // Interface Call, force Refresh FillObjectRelations(new [] { dataObject }, true); } /// /// Populate or Refresh Objects Relations /// /// Objects to Populate /// Force Refresh even if Autoload is False protected virtual void FillObjectRelations(IEnumerable dataObjects, bool force) { var groups = dataObjects.GroupBy(obj => obj.GetType()); foreach (var grp in groups) { var dataType = grp.Key; var tableName = AttributeUtil.GetTableOrViewName(dataType); try { if (!TableDatasets.TryGetValue(tableName, out DataTableHandler tableHandler)) throw new DatabaseException(string.Format("Table {0} is not registered for Database Connection...", tableName)); if (!tableHandler.HasRelations) { TakeSnapshots(grp); continue; } var relations = tableHandler.ElementBindings.Where(bind => bind.Relation != null); foreach (var relation in relations) { // Check if Loading is needed if (!(relation.Relation.AutoLoad || force)) continue; var remoteName = AttributeUtil.GetTableOrViewName(relation.ValueType); try { DataTableHandler remoteHandler; if (!TableDatasets.TryGetValue(remoteName, out remoteHandler)) throw new DatabaseException(string.Format("Table {0} is not registered for Database Connection...", remoteName)); // Select Object On Relation Constraint var localBind = tableHandler.FieldElementBindings.Single(bind => bind.ColumnName.Equals(relation.Relation.LocalField, StringComparison.OrdinalIgnoreCase)); var remoteBind = remoteHandler.FieldElementBindings.Single(bind => bind.ColumnName.Equals(relation.Relation.RemoteField, StringComparison.OrdinalIgnoreCase)); FillObjectRelationsImpl(relation, localBind, remoteBind, remoteHandler, grp); } catch (Exception re) { if (log.IsErrorEnabled) log.ErrorFormat("Could not Retrieve Objects from Relation (Table {0}, Local {1}, Remote Table {2}, Remote {3})\n{4}", tableName, relation.Relation.LocalField, AttributeUtil.GetTableOrViewName(relation.ValueType), relation.Relation.RemoteField, re); } } TakeSnapshots(grp); } catch (Exception e) { if (log.IsErrorEnabled) log.ErrorFormat("Could not Resolve Relations for Table {0}\n{1}", tableName, e); } } static void TakeSnapshots(IGrouping group) { foreach (DataObject dataObject in group) dataObject.TakeSnapshot(); } } /// /// Populate or Refresh Object Relation Implementation /// /// Element Binding for Relation Field /// Local Binding for Value Match /// Remote Binding for Column Match /// Remote Table Handler for Cache Retrieving /// DataObjects to Populate protected virtual void FillObjectRelationsImpl(ElementBinding relationBind, ElementBinding localBind, ElementBinding remoteBind, DataTableHandler remoteHandler, IEnumerable dataObjects) { Type type = relationBind.ValueType; bool isElementType = false; if (type.HasElementType) { type = type.GetElementType(); isElementType = true; } if (remoteHandler.UsesPreCaching) { // This Select directly corresponds to each item in dataObjects, maintaining order. var objsResults = dataObjects.Select(obj => { if (remoteHandler.PrimaryKeys.All(pk => pk.ColumnName.Equals(remoteBind.ColumnName, StringComparison.OrdinalIgnoreCase))) { object local = localBind.GetValue(obj); if (local == null) return Enumerable.Empty(); DataObject retrieve = remoteHandler.GetPreCachedObject(local); if (retrieve == null) return Enumerable.Empty(); return [retrieve]; } else { return remoteHandler.SearchPreCachedObjects(rem => { object local = localBind.GetValue(obj); object remote = remoteBind.GetValue(rem); if (local == null || remote == null) return false; if (localBind.ValueType == typeof(string) || remoteBind.ValueType == typeof(string)) return remote.ToString().Equals(local.ToString(), StringComparison.OrdinalIgnoreCase); return remote.Equals(local); }); } }); var resultByObjsFromCache = dataObjects.Zip(objsResults, (dataObj, results) => new { DataObject = dataObj, Results = results }); AssignRelations(resultByObjsFromCache, isElementType, type, relationBind); FillObjectRelations(resultByObjsFromCache.SelectMany(result => result.Results), false); return; } List localKeys = dataObjects .Select(o => localBind.GetValue(o)) .Where(v => v != null) .Distinct() .ToList(); if (localKeys.Count == 0) { foreach (DataObject obj in dataObjects) relationBind.SetValue(obj, null); return; } WhereClause batchWhereClause = DB.Column(remoteBind.ColumnName).IsIn(localKeys); IEnumerable allRelatedObjects = MultipleSelectObjectsImpl(remoteHandler, [batchWhereClause]).FirstOrDefault() ?? Enumerable.Empty(); ILookup relatedObjectsMap = allRelatedObjects.ToLookup(r => remoteBind.GetValue(r)); var resultByObjs = dataObjects.Select(obj => { object localKeyValue = localBind.GetValue(obj); IEnumerable related; if (localKeyValue != null && relatedObjectsMap.Contains(localKeyValue)) related = relatedObjectsMap[localKeyValue]; else related = []; return new { DataObject = obj, Results = related }; }); AssignRelations(resultByObjs, isElementType, type, relationBind); FillObjectRelations(resultByObjs.SelectMany(result => result.Results), false); } private static void AssignRelations(IEnumerable resultByObjs, bool isElementType, Type type, ElementBinding relationBind) { foreach (dynamic result in resultByObjs) { // Cast the dynamic property to the non-generic IEnumerable. // This ensures that the LINQ extension methods can be found at runtime, // regardless of the underlying collection type (List, T[], Array, etc.). IEnumerable enumerableResults = result.Results; if (isElementType) { DataObject[] resultsArray = enumerableResults.Cast().ToArray(); if (resultsArray.Length != 0) { Array array = CastAndToArray(resultsArray.Cast(), type); relationBind.SetValue(result.DataObject, array); } else relationBind.SetValue(result.DataObject, null); } else relationBind.SetValue(result.DataObject, enumerableResults.Cast().SingleOrDefault()); } } private static Array CastAndToArray(IEnumerable source, Type targetType) { var func = _castToArrayCache.GetOrAdd(targetType, static t => { ParameterExpression param = Expression.Parameter(typeof(IEnumerable), "source"); MethodCallExpression castCall = Expression.Call(typeof(Enumerable), "OfType", [t], param); MethodCallExpression toArrayCall = Expression.Call(typeof(Enumerable), "ToArray", [t], castCall); Expression, Array>> lambda = Expression.Lambda, Array>>(toArrayCall, param); return lambda.Compile(); }); return func(source); } #endregion #region Public Object Select with Key API /// /// Retrieve a DataObject from database based on its primary key value. /// /// Primary Key Value /// Object found or null if not found public TObject FindObjectByKey(object key) where TObject : DataObject { return FindObjectsByKey(new [] { key }).FirstOrDefault(); } /// /// Retrieve a Collection of DataObjects from database based on their primary key values /// /// Collection of Primary Key Values /// Collection of DataObject with primary key matching values public virtual List FindObjectsByKey(IEnumerable keys) where TObject : DataObject { var tableHandler = GetTableOrViewHandler(typeof(TObject)); if (tableHandler == null) { if (log.IsErrorEnabled) log.ErrorFormat("FindObjectByKey: DataObject Type ({0}) not registered !", typeof(TObject).FullName); throw new DatabaseException(string.Format("Table {0} is not registered for Database Connection...", typeof(TObject).FullName)); } if (tableHandler.UsesPreCaching) return keys.Select(tableHandler.GetPreCachedObject).Cast().ToList(); var objs = FindObjectByKeyImpl(tableHandler, keys).Cast().ToList(); FillObjectRelations(objs.Where(obj => obj != null), false); return objs; } /// /// Retrieve a Collection of DataObjects from database based on their primary key values /// /// Table Handler for the DataObjects to Retrieve /// Collection of Primary Key Values /// Collection of DataObject with primary key matching values protected abstract IEnumerable FindObjectByKeyImpl(DataTableHandler tableHandler, IEnumerable keys); #endregion #region Public Parameterized Query Abstraction public TObject SelectObject(WhereClause whereClause) where TObject : DataObject { return SelectObjects(whereClause).FirstOrDefault(); } public List SelectObjects(WhereClause whereClause) where TObject : DataObject { return MultipleSelectObjects(new[] { whereClause }).First(); } public List> MultipleSelectObjects(IEnumerable whereClauseBatch) where TObject : DataObject { if (whereClauseBatch == null) throw new ArgumentNullException("Parameter whereClauseBatch may not be null."); var tableHandler = GetTableOrViewHandler(typeof(TObject)); if (tableHandler == null) { if (log.IsErrorEnabled) log.ErrorFormat("SelectObjects: DataObject Type ({0}) not registered !", typeof(TObject).FullName); throw new DatabaseException(string.Format("Table {0} is not registered for Database Connection...", typeof(TObject).FullName)); } var objs = MultipleSelectObjectsImpl(tableHandler, whereClauseBatch).Select(res => res.OfType().ToList()).ToList(); FillObjectRelations(objs.SelectMany(obj => obj), false); return objs; } #endregion #region Public Object Select All API public List SelectAllObjects() where TObject : DataObject { var tableHandler = GetTableOrViewHandler(typeof(TObject)); if (tableHandler == null) { if (log.IsErrorEnabled) log.ErrorFormat("SelectAllObjects: DataObject Type ({0}) not registered !", typeof(TObject).FullName); throw new DatabaseException(string.Format("Table {0} is not registered for Database Connection...", typeof(TObject).FullName)); } if (tableHandler.UsesPreCaching) return tableHandler.SearchPreCachedObjects(obj => obj != null).OfType().ToList(); var dataObjects = MultipleSelectObjectsImpl(tableHandler, new[] { WhereClause.Empty }).Single().OfType().ToList(); FillObjectRelations(dataObjects, false); return dataObjects; } #endregion #region Public API /// /// Gets the number of objects in a given table in the database. /// /// the type of objects to retrieve /// a positive integer representing the number of objects; zero if no object exists public int GetObjectCount() where TObject : DataObject { return GetObjectCount(string.Empty); } /// /// Gets the number of objects in a given table in the database based on a given set of criteria. (where clause) /// /// the type of objects to retrieve /// the where clause to filter object count on /// a positive integer representing the number of objects that matched the given criteria; zero if no such objects existed public int GetObjectCount(string whereExpression) where TObject : DataObject { return GetObjectCountImpl(whereExpression); } /// /// Register Data Object Type if not already Registered /// /// DataObject Type public virtual void RegisterDataObject(Type dataObjectType) { var tableName = AttributeUtil.GetTableOrViewName(dataObjectType); if (TableDatasets.ContainsKey(tableName)) return; var dataTableHandler = new DataTableHandler(dataObjectType); TableDatasets.Add(tableName, dataTableHandler); } /// /// escape the strange character from string /// /// the string /// the string with escaped character public abstract string Escape(string rawInput); /// /// Execute a Raw Non-Query on the Database /// /// Raw Command /// True if the Command succeeded public virtual bool ExecuteNonQuery(string rawQuery) { throw new NotImplementedException(); } #endregion #region Implementation /// /// Adds new DataObjects to the database. /// /// DataObjects to add to the database /// Table Handler for the DataObjects Collection /// True if objects were added successfully; false otherwise protected abstract IEnumerable AddObjectImpl(DataTableHandler tableHandler, IEnumerable dataObjects); /// /// Saves Persisted DataObjects into Database /// /// DataObjects to Save /// Table Handler for the DataObjects Collection /// True if objects were saved successfully; false otherwise protected abstract IEnumerable SaveObjectImpl(DataTableHandler tableHandler, IEnumerable dataObjects); /// /// Deletes DataObjects from the database. /// /// DataObjects to delete from the database /// Table Handler for the DataObjects Collection /// True if objects were deleted successfully; false otherwise protected abstract IEnumerable DeleteObjectImpl(DataTableHandler tableHandler, IEnumerable dataObjects); /// /// Retrieve a Collection of DataObjects Sets from database filtered by Parametrized Where Expression /// /// Table Handler for these DataObjects /// Parametrized Where Expression /// Parameters for filtering /// Isolation Level /// Collection of DataObjects Sets matching Parametrized Where Expression protected abstract List> SelectObjectsImpl(DataTableHandler tableHandler, string whereExpression, IEnumerable> parameters, Transaction.EIsolationLevel isolation); protected abstract List> MultipleSelectObjectsImpl(DataTableHandler tableHandler, IEnumerable whereClauseBatch); /// /// Gets the number of objects in a given table in the database based on a given set of criteria. (where clause) /// /// the type of objects to retrieve /// the where clause to filter object count on /// a positive integer representing the number of objects that matched the given criteria; zero if no such objects existed protected abstract int GetObjectCountImpl(string whereExpression) where TObject : DataObject; #endregion #region Cache /// /// Selects object from the database and updates or adds entry in the pre-cache. /// /// DataObject Type to Query /// Key to Update /// True if Object was found with given key public bool UpdateInCache(object key) where TObject : DataObject { return UpdateObjsInCache(new [] { key }); } /// /// Selects objects from the database and updates or adds entries in the pre-cache. /// /// DataObject Type to Query /// Key Collection to Update /// True if All Objects were found with given keys public bool UpdateObjsInCache(IEnumerable keys) where TObject : DataObject { var tableHandler = GetTableOrViewHandler(typeof(TObject)); if (tableHandler == null) { if (log.IsErrorEnabled) log.ErrorFormat("UpdateInCache: DataObject Type ({0}) not registered !", typeof(TObject).FullName); throw new DatabaseException(string.Format("Table {0} is not registered for Database Connection...", typeof(TObject).FullName)); } var keysArray = keys.ToArray(); var objs = FindObjectByKeyImpl(tableHandler, keysArray); var objsByKey = objs.Select((obj, i) => new { Key = keysArray[i], DataObject = obj }); var success = true; foreach (var obj in objsByKey) { if (obj.DataObject != null) tableHandler.SetPreCachedObject(obj.Key, obj.DataObject); else success = false; } return success; } #endregion #region Factory public static IObjectDatabase GetObjectDatabase(EConnectionType connectionType, string connectionString) { if (connectionType == EConnectionType.DATABASE_MYSQL) return new MySqlObjectDatabase(connectionString); if (connectionType == EConnectionType.DATABASE_SQLITE) return new SqliteObjectDatabase(connectionString); return null; } #endregion } }