From 7c339519b22a53bd02b4ef955b92a8c17574ca35 Mon Sep 17 00:00:00 2001 From: "grzegorz.russek" Date: Fri, 4 Oct 2019 13:23:28 +0000 Subject: [PATCH] --- AmalgamationTool/DynamORM.Amalgamation.cs | 425 +++++++++++++++++- DynamORM/DynamORM.csproj | 4 + DynamORM/DynamicDatabase.cs | 2 +- DynamORM/Objects/DynamicEntityBase.cs | 260 +++++++++++ DynamORM/Objects/DynamicEntityState.cs | 23 + .../DynamicPropertyChangingEventArgs.cs | 32 ++ DynamORM/Objects/DynamicRepositoryBase.cs | 128 ++++++ 7 files changed, 872 insertions(+), 2 deletions(-) create mode 100644 DynamORM/Objects/DynamicEntityBase.cs create mode 100644 DynamORM/Objects/DynamicEntityState.cs create mode 100644 DynamORM/Objects/DynamicPropertyChangingEventArgs.cs create mode 100644 DynamORM/Objects/DynamicRepositoryBase.cs diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs index af91981..1504c0b 100644 --- a/AmalgamationTool/DynamORM.Amalgamation.cs +++ b/AmalgamationTool/DynamORM.Amalgamation.cs @@ -1952,7 +1952,7 @@ namespace DynamORM } /// Bulk update objects in database. - /// Type of objects to update. + /// Type of objects to update. /// Enumerable containing instances of objects to update. /// Number of updated rows. public virtual int Update(Type t, IEnumerable e) @@ -13814,6 +13814,429 @@ namespace DynamORM } } + namespace Objects + { + /// Base class for strong typed objects. + public class DynamicEntityBase + { + private Dictionary _changedFields = new Dictionary(); + private DynamicEntityState _dynamicEntityState = DynamicEntityState.Unknown; + + /// Occurs when object property is changing. + public event EventHandler PropertyChanging; + + /// Gets the state of the dynamic entity. + /// Current state of entity. + public virtual DynamicEntityState GetDynamicEntityState() { return _dynamicEntityState; } + + /// Sets the state of the dynamic entity. + /// The state. + public virtual void SetDynamicEntityState(DynamicEntityState state) { _dynamicEntityState = state; } + + /// Called when object property is changing. + /// Name of the property. + /// The old property value. + /// The new property value. + protected virtual void OnPropertyChanging(string propertyName, object oldValue, object newValue) + { + OnPropertyChanging(new DynamicPropertyChangingEventArgs(propertyName, oldValue, newValue)); + } + + /// Raises the event. + /// The instance containing the event data. + protected virtual void OnPropertyChanging(DynamicPropertyChangingEventArgs e) + { + _changedFields[e.PropertyName] = e.NewValue; + if (PropertyChanging != null) + PropertyChanging(this, e); + } + + /// Validates this object instance. + /// Returns list of containing results of validation. + public virtual IList Validate() + { + return DynamicMapperCache.GetMapper(this.GetType()).ValidateObject(this); + } + + /// Saves this object to database. + /// The database. + /// Returns true if operation was successful. + /// Can be thrown when object is in invalid state. + public virtual bool Save(DynamicDatabase database) + { + switch (GetDynamicEntityState()) + { + default: + case DynamicEntityState.Unknown: + throw new InvalidOperationException("Unknown object state. Unable to decide whish action should be performed."); + + case DynamicEntityState.New: + return Insert(database); + + case DynamicEntityState.Existing: + return Update(database); + + case DynamicEntityState.ToBeDeleted: + return Delete(database); + + case DynamicEntityState.Deleted: + throw new InvalidOperationException("Unable to do any database action on deleted object."); + } + } + + #region Insert/Update/Delete + + /// Inserts this object to database. + /// The database. + /// Returns true if operation was successful. + public virtual bool Insert(DynamicDatabase db) + { + if (db.Insert(this.GetType()) + .Values(x => this) + .Execute() > 0) + { + _changedFields.Clear(); + SetDynamicEntityState(DynamicEntityState.Existing); + return true; + } + + return false; + } + + /// Updates this object in database. + /// The database. + /// Returns true if operation was successful. + public virtual bool Update(DynamicDatabase db) + { + var t = GetType(); + var mapper = DynamicMapperCache.GetMapper(t); + var query = db.Update(t); + + MakeQueryWhere(mapper, query); + + if (_changedFields.Any()) + { + bool any = false; + + foreach (var cf in _changedFields) + { + var cn = mapper.PropertyMap[cf.Key]; + var pm = mapper.ColumnsMap[cn.ToLower()]; + if (pm.Ignore) + continue; + + if (pm.Column != null) + { + if (pm.Column.IsKey) + continue; + + if (!pm.Column.AllowNull && cf.Value == null) + continue; + } + + query.Values(cn, cf.Value); + any = true; + } + + if (!any) + query.Set(x => this); + } + else + query.Set(x => this); + + if (query.Execute() == 0) + return false; + + SetDynamicEntityState(DynamicEntityState.Existing); + _changedFields.Clear(); + + return true; + } + + /// Deletes this object from database. + /// The database. + /// Returns true if operation was successful. + public virtual bool Delete(DynamicDatabase db) + { + var t = this.GetType(); + var mapper = DynamicMapperCache.GetMapper(t); + + var query = db.Delete(t); + + MakeQueryWhere(mapper, query); + + if (query.Execute() == 0) + return false; + + SetDynamicEntityState(DynamicEntityState.Deleted); + + return true; + } + + #endregion Insert/Update/Delete + + #region Select + + /// Refresh non key data from database. + /// The database. + /// All properties that are primary key values must be filled. + /// Returns true if operation was successful. + public virtual bool Refresh(DynamicDatabase db) + { + var t = this.GetType(); + var mapper = DynamicMapperCache.GetMapper(t); + var query = db.From(t); + MakeQueryWhere(mapper, query); + var o = (query.Execute() as IEnumerable).FirstOrDefault(); + + if (o == null) + return false; + + mapper.Map(o, this); + + SetDynamicEntityState(DynamicEntityState.Existing); + _changedFields.Clear(); + + return true; + } + + #endregion Select + + #region Query Helpers + + private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicUpdateQueryBuilder query) + { + bool keyNotDefined = true; + + foreach (var cm in mapper.ColumnsMap) + { + if (cm.Value.Column != null && cm.Value.Column.IsKey) + { + query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); + keyNotDefined = false; + } + } + + if (keyNotDefined) + throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", + this.GetType().FullName)); + } + + private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicDeleteQueryBuilder query) + { + bool keyNotDefined = true; + + foreach (var cm in mapper.ColumnsMap) + { + if (cm.Value.Column != null && cm.Value.Column.IsKey) + { + query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); + keyNotDefined = false; + } + } + + if (keyNotDefined) + throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", + this.GetType().FullName)); + } + + private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicSelectQueryBuilder query) + { + bool keyNotDefined = true; + + foreach (var cm in mapper.ColumnsMap) + { + if (cm.Value.Column != null && cm.Value.Column.IsKey) + { + var v = cm.Value.Get(this); + + if (v == null) + throw new InvalidOperationException(String.Format("Class '{0}' have key columns {1} not filled with data.", + this.GetType().FullName, cm.Value.Name)); + + query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); + keyNotDefined = false; + } + } + + if (keyNotDefined) + throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", + this.GetType().FullName)); + } + + #endregion Query Helpers + } + + /// Possible states of dynamic database objects. + public enum DynamicEntityState + { + /// Default state. This state will only permit to refresh data from database. + /// In this state repository will be unable to tell if object with this state should be added + /// or updated in database, but you can still manually perform update or insert on such object. + Unknown, + + /// This state should be set to new objects in database. + New, + + /// This state is ser when data is refreshed from database or object was loaded from repository. + Existing, + + /// You can set this state to an object if you want repository to perform delete from database. + ToBeDeleted, + + /// This state is set for objects that were deleted from database. + Deleted, + } + + /// Class containing changed property data. + /// + public class DynamicPropertyChangingEventArgs : EventArgs + { + /// Gets the name of the property. + /// The name of the property. + public string PropertyName { get; private set; } + + /// Gets the old property value. + /// The old value. + public object OldValue { get; private set; } + + /// Gets the new property value. + /// The new value. + public object NewValue { get; private set; } + + /// Initializes a new instance of the class. + /// Name of the property. + /// The old property value. + /// The new property value. + public DynamicPropertyChangingEventArgs(string propertyName, object oldValue, object newValue) + { + PropertyName = propertyName; + OldValue = oldValue; + NewValue = newValue; + } + } + + /// Base repository class for specified object type. + /// Type of stored object. + public class DynamicRepositoryBase : IDisposable where T : DynamicEntityBase + { + private DynamicDatabase _database; + + /// Initializes a new instance of the class. + /// The database. + public DynamicRepositoryBase(DynamicDatabase database) + { + _database = database; + } + + /// Get all rows from database. + /// Objects enumerator. + public virtual IEnumerable GetAll() + { + return EnumerateQuery(_database.From()); + } + + /// Get rows from database by custom query. + /// The query. + /// Query must be based on object type. + /// Objects enumerator. + public virtual IEnumerable GetByQuery(IDynamicSelectQueryBuilder query) + { + return EnumerateQuery(query); + } + + private IEnumerable EnumerateQuery(IDynamicSelectQueryBuilder query, bool forceType = true) + { + if (forceType) + { + var mapper = DynamicMapperCache.GetMapper(typeof(T)); + + var tn = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? + mapper.Type.Name : mapper.Table.Name; + + if (!query.Tables.Any(t => t.Name == tn)) + throw new InvalidOperationException(string.Format("Query is not related to '{0}' class.", typeof(T).FullName)); + } + + foreach (var o in query.Execute()) + { + o.SetDynamicEntityState(DynamicEntityState.Existing); + yield return o; + } + } + + /// Saves single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Save(T element) + { + return element.Save(_database); + } + + /// Saves collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Save(IEnumerable element) + { + return element.All(x => x.Save(_database)); + } + + /// Insert single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Insert(T element) + { + return element.Insert(_database); + } + + /// Insert collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Insert(IEnumerable element) + { + return element.All(x => x.Insert(_database)); + } + + /// Update single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Update(T element) + { + return element.Update(_database); + } + + /// Update collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Update(IEnumerable element) + { + return element.All(x => x.Update(_database)); + } + + /// Delete single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Delete(T element) + { + return element.Delete(_database); + } + + /// Delete collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Delete(IEnumerable element) + { + return element.All(x => x.Delete(_database)); + } + + /// Releases unmanaged and - optionally - managed resources. + public virtual void Dispose() + { + _database = null; + } + } + } + namespace Validation { /// Required attribute can be used to validate fields in objects using mapper class. diff --git a/DynamORM/DynamORM.csproj b/DynamORM/DynamORM.csproj index 5757d10..ac6b209 100644 --- a/DynamORM/DynamORM.csproj +++ b/DynamORM/DynamORM.csproj @@ -105,6 +105,10 @@ + + + + diff --git a/DynamORM/DynamicDatabase.cs b/DynamORM/DynamicDatabase.cs index 7d5bade..f568dec 100644 --- a/DynamORM/DynamicDatabase.cs +++ b/DynamORM/DynamicDatabase.cs @@ -492,7 +492,7 @@ namespace DynamORM } /// Bulk update objects in database. - /// Type of objects to update. + /// Type of objects to update. /// Enumerable containing instances of objects to update. /// Number of updated rows. public virtual int Update(Type t, IEnumerable e) diff --git a/DynamORM/Objects/DynamicEntityBase.cs b/DynamORM/Objects/DynamicEntityBase.cs new file mode 100644 index 0000000..b0029b6 --- /dev/null +++ b/DynamORM/Objects/DynamicEntityBase.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DynamORM.Builders; +using DynamORM.Mapper; +using DynamORM.Validation; + +namespace DynamORM.Objects +{ + /// Base class for strong typed objects. + public class DynamicEntityBase + { + private Dictionary _changedFields = new Dictionary(); + private DynamicEntityState _dynamicEntityState = DynamicEntityState.Unknown; + + /// Occurs when object property is changing. + public event EventHandler PropertyChanging; + + /// Gets the state of the dynamic entity. + /// Current state of entity. + public virtual DynamicEntityState GetDynamicEntityState() { return _dynamicEntityState; } + + /// Sets the state of the dynamic entity. + /// The state. + public virtual void SetDynamicEntityState(DynamicEntityState state) { _dynamicEntityState = state; } + + /// Called when object property is changing. + /// Name of the property. + /// The old property value. + /// The new property value. + protected virtual void OnPropertyChanging(string propertyName, object oldValue, object newValue) + { + OnPropertyChanging(new DynamicPropertyChangingEventArgs(propertyName, oldValue, newValue)); + } + + /// Raises the event. + /// The instance containing the event data. + protected virtual void OnPropertyChanging(DynamicPropertyChangingEventArgs e) + { + _changedFields[e.PropertyName] = e.NewValue; + if (PropertyChanging != null) + PropertyChanging(this, e); + } + + /// Validates this object instance. + /// Returns list of containing results of validation. + public virtual IList Validate() + { + return DynamicMapperCache.GetMapper(this.GetType()).ValidateObject(this); + } + + /// Saves this object to database. + /// The database. + /// Returns true if operation was successful. + /// Can be thrown when object is in invalid state. + public virtual bool Save(DynamicDatabase database) + { + switch (GetDynamicEntityState()) + { + default: + case DynamicEntityState.Unknown: + throw new InvalidOperationException("Unknown object state. Unable to decide whish action should be performed."); + + case DynamicEntityState.New: + return Insert(database); + + case DynamicEntityState.Existing: + return Update(database); + + case DynamicEntityState.ToBeDeleted: + return Delete(database); + + case DynamicEntityState.Deleted: + throw new InvalidOperationException("Unable to do any database action on deleted object."); + } + } + + #region Insert/Update/Delete + + /// Inserts this object to database. + /// The database. + /// Returns true if operation was successful. + public virtual bool Insert(DynamicDatabase db) + { + if (db.Insert(this.GetType()) + .Values(x => this) + .Execute() > 0) + { + _changedFields.Clear(); + SetDynamicEntityState(DynamicEntityState.Existing); + return true; + } + + return false; + } + + /// Updates this object in database. + /// The database. + /// Returns true if operation was successful. + public virtual bool Update(DynamicDatabase db) + { + var t = GetType(); + var mapper = DynamicMapperCache.GetMapper(t); + var query = db.Update(t); + + MakeQueryWhere(mapper, query); + + if (_changedFields.Any()) + { + bool any = false; + + foreach (var cf in _changedFields) + { + var cn = mapper.PropertyMap[cf.Key]; + var pm = mapper.ColumnsMap[cn.ToLower()]; + if (pm.Ignore) + continue; + + if (pm.Column != null) + { + if (pm.Column.IsKey) + continue; + + if (!pm.Column.AllowNull && cf.Value == null) + continue; + } + + query.Values(cn, cf.Value); + any = true; + } + + if (!any) + query.Set(x => this); + } + else + query.Set(x => this); + + if (query.Execute() == 0) + return false; + + SetDynamicEntityState(DynamicEntityState.Existing); + _changedFields.Clear(); + + return true; + } + + /// Deletes this object from database. + /// The database. + /// Returns true if operation was successful. + public virtual bool Delete(DynamicDatabase db) + { + var t = this.GetType(); + var mapper = DynamicMapperCache.GetMapper(t); + + var query = db.Delete(t); + + MakeQueryWhere(mapper, query); + + if (query.Execute() == 0) + return false; + + SetDynamicEntityState(DynamicEntityState.Deleted); + + return true; + } + + #endregion Insert/Update/Delete + + #region Select + + /// Refresh non key data from database. + /// The database. + /// All properties that are primary key values must be filled. + /// Returns true if operation was successful. + public virtual bool Refresh(DynamicDatabase db) + { + var t = this.GetType(); + var mapper = DynamicMapperCache.GetMapper(t); + var query = db.From(t); + MakeQueryWhere(mapper, query); + var o = (query.Execute() as IEnumerable).FirstOrDefault(); + + if (o == null) + return false; + + mapper.Map(o, this); + + SetDynamicEntityState(DynamicEntityState.Existing); + _changedFields.Clear(); + + return true; + } + + #endregion Select + + #region Query Helpers + + private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicUpdateQueryBuilder query) + { + bool keyNotDefined = true; + + foreach (var cm in mapper.ColumnsMap) + { + if (cm.Value.Column != null && cm.Value.Column.IsKey) + { + query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); + keyNotDefined = false; + } + } + + if (keyNotDefined) + throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", + this.GetType().FullName)); + } + + private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicDeleteQueryBuilder query) + { + bool keyNotDefined = true; + + foreach (var cm in mapper.ColumnsMap) + { + if (cm.Value.Column != null && cm.Value.Column.IsKey) + { + query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); + keyNotDefined = false; + } + } + + if (keyNotDefined) + throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", + this.GetType().FullName)); + } + + private void MakeQueryWhere(DynamicTypeMap mapper, IDynamicSelectQueryBuilder query) + { + bool keyNotDefined = true; + + foreach (var cm in mapper.ColumnsMap) + { + if (cm.Value.Column != null && cm.Value.Column.IsKey) + { + var v = cm.Value.Get(this); + + if (v == null) + throw new InvalidOperationException(String.Format("Class '{0}' have key columns {1} not filled with data.", + this.GetType().FullName, cm.Value.Name)); + + query.Where(cm.Key, DynamicColumn.CompareOperator.Eq, cm.Value.Get(this)); + keyNotDefined = false; + } + } + + if (keyNotDefined) + throw new InvalidOperationException(String.Format("Class '{0}' have no key columns defined", + this.GetType().FullName)); + } + + #endregion Query Helpers + } +} \ No newline at end of file diff --git a/DynamORM/Objects/DynamicEntityState.cs b/DynamORM/Objects/DynamicEntityState.cs new file mode 100644 index 0000000..5744546 --- /dev/null +++ b/DynamORM/Objects/DynamicEntityState.cs @@ -0,0 +1,23 @@ +namespace DynamORM.Objects +{ + /// Possible states of dynamic database objects. + public enum DynamicEntityState + { + /// Default state. This state will only permit to refresh data from database. + /// In this state repository will be unable to tell if object with this state should be added + /// or updated in database, but you can still manually perform update or insert on such object. + Unknown, + + /// This state should be set to new objects in database. + New, + + /// This state is ser when data is refreshed from database or object was loaded from repository. + Existing, + + /// You can set this state to an object if you want repository to perform delete from database. + ToBeDeleted, + + /// This state is set for objects that were deleted from database. + Deleted, + } +} \ No newline at end of file diff --git a/DynamORM/Objects/DynamicPropertyChangingEventArgs.cs b/DynamORM/Objects/DynamicPropertyChangingEventArgs.cs new file mode 100644 index 0000000..f06ea35 --- /dev/null +++ b/DynamORM/Objects/DynamicPropertyChangingEventArgs.cs @@ -0,0 +1,32 @@ +using System; + +namespace DynamORM.Objects +{ + /// Class containing changed property data. + /// + public class DynamicPropertyChangingEventArgs : EventArgs + { + /// Gets the name of the property. + /// The name of the property. + public string PropertyName { get; private set; } + + /// Gets the old property value. + /// The old value. + public object OldValue { get; private set; } + + /// Gets the new property value. + /// The new value. + public object NewValue { get; private set; } + + /// Initializes a new instance of the class. + /// Name of the property. + /// The old property value. + /// The new property value. + public DynamicPropertyChangingEventArgs(string propertyName, object oldValue, object newValue) + { + PropertyName = propertyName; + OldValue = oldValue; + NewValue = newValue; + } + } +} \ No newline at end of file diff --git a/DynamORM/Objects/DynamicRepositoryBase.cs b/DynamORM/Objects/DynamicRepositoryBase.cs new file mode 100644 index 0000000..e954572 --- /dev/null +++ b/DynamORM/Objects/DynamicRepositoryBase.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DynamORM.Builders; +using DynamORM.Mapper; + +namespace DynamORM.Objects +{ + /// Base repository class for specified object type. + /// Type of stored object. + public class DynamicRepositoryBase : IDisposable where T : DynamicEntityBase + { + private DynamicDatabase _database; + + /// Initializes a new instance of the class. + /// The database. + public DynamicRepositoryBase(DynamicDatabase database) + { + _database = database; + } + + /// Get all rows from database. + /// Objects enumerator. + public virtual IEnumerable GetAll() + { + return EnumerateQuery(_database.From()); + } + + /// Get rows from database by custom query. + /// The query. + /// Query must be based on object type. + /// Objects enumerator. + public virtual IEnumerable GetByQuery(IDynamicSelectQueryBuilder query) + { + return EnumerateQuery(query); + } + + private IEnumerable EnumerateQuery(IDynamicSelectQueryBuilder query, bool forceType = true) + { + if (forceType) + { + var mapper = DynamicMapperCache.GetMapper(typeof(T)); + + var tn = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ? + mapper.Type.Name : mapper.Table.Name; + + if (!query.Tables.Any(t => t.Name == tn)) + throw new InvalidOperationException(string.Format("Query is not related to '{0}' class.", typeof(T).FullName)); + } + + foreach (var o in query.Execute()) + { + o.SetDynamicEntityState(DynamicEntityState.Existing); + yield return o; + } + } + + /// Saves single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Save(T element) + { + return element.Save(_database); + } + + /// Saves collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Save(IEnumerable element) + { + return element.All(x => x.Save(_database)); + } + + /// Insert single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Insert(T element) + { + return element.Insert(_database); + } + + /// Insert collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Insert(IEnumerable element) + { + return element.All(x => x.Insert(_database)); + } + + /// Update single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Update(T element) + { + return element.Update(_database); + } + + /// Update collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Update(IEnumerable element) + { + return element.All(x => x.Update(_database)); + } + + /// Delete single object to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Delete(T element) + { + return element.Delete(_database); + } + + /// Delete collection of objects to database. + /// The element. + /// Returns true if operation was successful. + public virtual bool Delete(IEnumerable element) + { + return element.All(x => x.Delete(_database)); + } + + /// Releases unmanaged and - optionally - managed resources. + public virtual void Dispose() + { + _database = null; + } + } +} \ No newline at end of file