using System; using System.Collections; using System.Collections.Generic; using Kreta.Framework.Util; namespace Kreta.Framework.Entities { /// <summary> /// Az entitások absztrakt ősosztálya. /// </summary> public abstract class Entity { // <<Layer Supertype>> //private static Hashtable m_EntityAttributeCache = Hashtable.Synchronized(new Hashtable()); protected int m_ID; protected EntityState m_State; protected int m_Serial; protected DateTime? m_EntityLastChanged = null; protected DateTime? m_EntityCreated = null; protected bool m_Torolt = false; protected int? m_EntityModifier = null; protected int m_EntityCreator = -1; private Hashtable m_OriginalValues = new Hashtable(); private Hashtable m_CurrentValues = new Hashtable(); /// <summary> /// Az osztály alapértelmezett konstruktora. /// </summary> protected Entity() { } #region Belső dolgok /// <summary> /// Ellenőrzi, hogy az entitás módosítható állapotban van. /// </summary> /// <exception cref="EntityStateException">Ha nem módosítható az entitás</exception> protected void CheckModifyable() { if (!CanModifyFields) { throw new EntityStateException(m_State); } } /// <summary> /// Alapállapotba állítja az objektumot /// </summary> protected virtual void Reset() { m_State = EntityState.Uninitialized; m_ID = -1; m_Serial = -1; m_EntityCreated = null; m_EntityCreator = -1; m_EntityLastChanged = null; m_Torolt = false; m_EntityModifier = null; m_CurrentValues.Clear(); m_OriginalValues.Clear(); } /// <summary> /// Visszaadja az entitás adatbázisműveleteit végző <see cref="IEntityDataAccessor"/> objektumát. /// </summary> /// <returns>Az entitás adatbázisműveleteit végző <see cref="IEntityDataAccessor"/> objektum</returns> protected abstract IEntityDataAccessor GetDataAccessor(); #endregion #region Nyilvános felület /// <summary> /// Visszaadja az entitás nevét. /// </summary> /// <remarks> /// Az entitás nevét a rajta lévő <see cref="EntityAttribute"/> attribútum definiálja. /// A névnek rendszer szinten egyedinek kell lennie. /// </remarks> public string GetEntityName() { return EntityAttributeCache.GetEntityNameFromCache(GetType()); } /// <summary> /// Az entitás azonosítója /// </summary> /// <value>Az entitást azonosító nemnegatív egész szám</value> public int ID { get { return m_ID; } set { m_ID = value; } } /// <summary> /// Az entitás utolsó módosításának ideje /// </summary> public virtual DateTime? EntityLastChanged { get { return m_EntityLastChanged; } internal protected set { m_EntityLastChanged = value; } } /// <summary> /// Az entitás létrehozásának ideje /// </summary> public virtual DateTime? EntityCreated { get { return m_EntityCreated; } internal protected set { m_EntityCreated = value; } } /// <summary> /// Az entitás létrehozójának adatai /// </summary> public virtual int EntityCreator { get { return m_EntityCreator; } internal protected set { m_EntityCreator = value; } } /// <summary> /// Az entitás logikailag törölve van. /// </summary> public virtual bool Torolt { get { return m_Torolt; } set { m_Torolt = value; } } /// <summary> /// Az entitás módosítójának adatai /// </summary> public virtual int? EntityModifier { get { return m_EntityModifier; } internal protected set { m_EntityModifier = value; } } /// <summary> /// Az entitás verziója /// </summary> /// <value>Az entitás verziója</value> /// <remarks>Az entitás verziója kezdetben nulla, és minden mentésnél eggyel no.</remarks> public int Serial { get { return m_Serial; } set { m_Serial = value; } } /// <summary> /// Az entitás jelenlegi állapota. /// </summary> public EntityState State { get { return m_State; } set { SetState(value); } } /// <summary> /// Új állapotba hozza az entitást. /// </summary> /// <param name="state">Új állapot</param> public void SetState(EntityState state) { m_State = state; } /// <summary> /// Betöltött állapotba helyezi az entitást. /// </summary> public void SetLoaded() { m_State = EntityState.Initialized; StoreOriginalValues(); } /// <summary> /// Ellenőrzi, hogy az entitás attribútumai érvényes értékeket tartalmaznak-e. /// </summary> /// <exception cref="InvalidEntityAttributeException"> /// Ha valamelyik attribútum értéke érvénytelen. /// </exception> protected virtual void Validate(bool skipValidateAttributes = false) { // Az ősosztályban nem kell csinálnunk semmit. } #endregion #region Állapottal kapcsolatos dolgok /// <summary> /// Visszaadja, hogy az entitás be van-e már töltve. /// </summary> public bool IsInitialized { get { return (m_State == EntityState.Initialized || m_State == EntityState.Modified); } } /// <summary> /// Visszaadja, hogy az entitás módosítva lett-e. /// </summary> public bool IsModified { get { return (m_State == EntityState.Modified); } } /// <summary> /// Visszaadja, hogy az entitás törölve lett-e. /// </summary> public bool IsDeleted { get { return (m_State == EntityState.Removed); } } /// <summary> /// Ellenőrzi, hogy az entitás állapota megfelelő-e a betöltés művelethez. /// </summary> /// <value>True, ha az entitás betölthető; egyébként false</value> protected bool CanLoad { get { return (m_State == EntityState.Uninitialized); } } /// <summary> /// Ellenőrzi, hogy az entitás állapota megfelelő-e a beszúrás művelethez. /// </summary> /// <value>True, ha az entitás beszúrható az adatbázisba; egyébként false</value> protected bool CanInsert { get { return (m_State == EntityState.New || m_State == EntityState.Uninitialized); } } /// <summary> /// Ellenőrzi, hogy az entitás állapota megfelelő-e a módosítás művelethez. /// </summary> /// <value>True, ha az entitás módosítható az adatbázisban; egyébként false</value> protected bool CanUpdate { get { return (m_State == EntityState.Modified || m_State == EntityState.Initialized); } } /// <summary> /// Ellenőrzi, hogy az entitás állapota megfelelő-e a törléshez. /// </summary> /// <value>True, ha az entitás törölhető az adatbázisban; egyébként false</value> protected bool CanRemove { get { return (m_State != EntityState.Uninitialized); } } /// <summary> /// Ellenőrzi, hogy az entitás állapota megfelelő-e az attribútumainak módosításához. /// </summary> /// <value>True, ha az entitás attribútumai módosíthatóak; egyébként false</value> protected bool CanModifyFields { get { return (m_State != EntityState.Removed); } } #endregion #region CRUD logika /// <summary> /// Azonosító alapján feltölti az entitást az adatbázisból /// </summary> /// <param name="id">Az entitás azonosítója</param> /// <returns>True, ha sikeres; egyébként False</returns> public bool LoadEntityByID(int id) { if (m_State != EntityState.Uninitialized) { throw new EntityStateException(m_State); } m_ID = id; if (!GetDataAccessor().LoadEntity(this, id)) { m_ID = -1; return false; } return true; } /// <summary> /// Azonosító alapján feltölti az entitást az adatbázisból, minden oszloppal /// </summary> /// <param name="id">Az entitás azonosítója</param> /// <exception cref="EntityNotFoundException">Ha a megadott azonosítójú entitás nem található.</exception> public void LoadByID(int id) { if (m_State != EntityState.Uninitialized) { throw new EntityStateException(m_State); } m_ID = id; if (!GetDataAccessor().LoadEntity(this, id)) { m_ID = -1; throw new EntityNotFoundException(GetEntityName(), id); } } /// <summary> /// Azonosító alapján feltölti az entitást az adatbázisból, tiltó oszlopszűréssel /// </summary> /// <param name="id">Az entitás azonosítója</param> /// <param name="filteredColumns">A betöltésre nem kerülő oszlopok</param> public void LoadByID(int id, IEnumerable<string> filteredColumns) { this.LoadByID(id, ColumnFilterMode.DEFAULT_ALLOWED, filteredColumns); } /// <summary> /// Azonosító alapján feltölti az entitást az adatbázisból, oszlopszűréssel /// </summary> /// <param name="id">Az entitás azonosítója</param> /// <param name="columnFilterMode">A szűrés módja, megengedő vagy tiltó</param> /// <param name="columns">A szűrendő oszlopok felsorolása</param> public void LoadByID(int id, ColumnFilterMode columnFilterMode, IEnumerable<string> columns) { if (m_State != EntityState.Uninitialized) { throw new EntityStateException(m_State); } m_ID = id; if (!GetDataAccessor().FilteredLoadEntity(this, id, columnFilterMode, columns)) { m_ID = -1; throw new EntityNotFoundException(GetEntityName(), id); } } /// <summary> /// Azonosító alapján feltölti az entitást adatbázisból. /// </summary> /// <param name="id">Az entitás azonosítója</param> /// <param name="serial">Az entitás verziószáma</param> /// <exception cref="EntityExpiredException">Ha az entitás verziószáma nem egyezik a megadottal.</exception> /// <exception cref="EntityNotFoundException">Ha a megadott azonosítójú entitás nem található.</exception> public void LoadByID(int id, int serial) { LoadByID(id); if (m_Serial != serial) { throw new EntityExpiredException(GetEntityName(), ID, m_Serial, serial); } } /// <summary> /// Azonosító alapján feltölti az entitást adatbázisból, oszlopszűréssel /// </summary> /// <param name="id"></param> /// <param name="serial"></param> /// <param name="columnFilterMode"></param> /// <param name="columns"></param> public void LoadByID(int id, int serial, ColumnFilterMode columnFilterMode, IEnumerable<string> columns) { LoadByID(id, columnFilterMode, columns); if (m_Serial != serial) { throw new EntityExpiredException(GetEntityName(), ID, m_Serial, serial); } } /// <summary> /// Adatbázisba menti a még nem létező entitást. /// </summary> public void Insert() { Insert(true); } /// <summary> /// Adatbázisba menti a még nem létező entitást. /// </summary> /// <param name="runHandler">Futtassa-e a hozzá tartozó <see cref="EntityHandler"/> objektumot, vagy sem</param> public void Insert(bool runHandler) { try { // <<Template Method>> IEntityHandler handler = null; if (runHandler) { handler = EntityHandler.Create(GetType()); handler.BeforeInsert(this); } DoInsert(); if (runHandler) { handler.AfterInsert(this); } } catch { EntityState es = this.State; this.State = EntityState.New; // biztos ami biztos this.StoreOriginalValues(); // hogy legyen mit logolni //EntityHistoryLogger.LogErrorHistory(this); this.State = es; throw; } } /// <summary> /// Adatbázisba menti a már létező entitás tulajdonságait. /// </summary> public void Update() { Update(true); } /// <summary> /// Adatbázisba menti a már létező entitás tulajdonságait. /// </summary> /// <param name="runHandler">Futtassa-e a hozzá tartozó <see cref="EntityHandler"/> objektumot, vagy sem</param> public void Update(bool runHandler) { try { // <<Template Method>> IEntityHandler handler = null; if (runHandler) { handler = EntityHandler.Create(GetType()); handler.BeforeUpdate(this); } DoUpdate(true, false); if (runHandler) { handler.AfterUpdate(this); } } catch { EntityState es = this.State; this.State = EntityState.Modified; // különben nem logol //EntityHistoryLogger.LogErrorHistory(this); this.State = es; throw; } } /// <summary> /// Adatbázisba menti az entitás asszociációit. /// </summary> /// <remarks>Valójában csak azokat, amelyeknél a kapcsolómező ennek az entitásnak a táblájában van!</remarks> public void UpdateAssociations() { UpdateAssociations(true); } /// <summary> /// Adatbázisba menti az entitás asszociációit. /// </summary> /// <param name="runHandler">Futtassa-e a hozzá tartozó <see cref="EntityHandler"/> objektumot, vagy sem</param> /// <remarks>Valójában csak azokat, amelyeknél a kapcsolómező ennek az entitásnak a táblájában van!</remarks> public void UpdateAssociations(bool runHandler) { try { // <<Template Method>> IEntityHandler handler = null; if (runHandler) { handler = EntityHandler.Create(GetType()); handler.BeforeUpdate(this); } DoUpdate(false, true); if (runHandler) { handler.AfterUpdate(this); } } catch { EntityState es = this.State; this.State = EntityState.Modified; // különben nem logol //EntityHistoryLogger.LogErrorHistory(this); this.State = es; throw; } } /// <summary> /// Adatbázisba menti a már létező entitás tulajdonságait és asszociációit. /// </summary> public void FullUpdate() { FullUpdate(true); } /// <summary> /// Adatbázisba menti a már létező entitás tulajdonságait és asszociációit. /// </summary> /// <param name="runHandler">Futtassa-e a hozzá tartozó <see cref="EntityHandler"/> objektumot, vagy sem</param> public void FullUpdate(bool runHandler) { try { // <<Template Method>> IEntityHandler handler = null; if (runHandler) { handler = EntityHandler.Create(GetType()); handler.BeforeUpdate(this); } DoUpdate(true, true); if (runHandler) { handler.AfterUpdate(this); } } catch { EntityState es = this.State; this.State = EntityState.Modified; // különben nem logol //EntityHistoryLogger.LogErrorHistory(this); this.State = es; throw; } } /// <summary> /// Törli az entitást az adatbázisból. /// </summary> /// <remarks>Sikeres törlés esetén az entitás 'EntityState.REMOVED' állapotba kerül.</remarks> public void Delete(bool logikai = true) { Delete(true, logikai); } /// <summary> /// Leellenőrzi az aktív kapcsolatait törléshez. /// </summary> void CheckActiveConnections() { var entityNames = new List<string>() { GetEntityName() }; var entityType = this.GetType(); if (entityType.BaseType != typeof(Entity)) { entityNames.Add(EntityAttributeCache.GetEntityNameFromCache(entityType.BaseType)); } var EntitasKapcsolatok = EntityUtils.GetEntitiesConnections(new List<int>() { this.ID }, entityNames); // ha van az entitásnak kapcsolata, akkor elszáll if (EntitasKapcsolatok.Count > 0 && EntitasKapcsolatok[ID].Count > 0) { throw new EntityDeleteFailedException(StringResourcesUtil.GetString(3478), EntitasKapcsolatok[ID]); } } /// <summary> /// Törli az entitást az adatbázisból. /// </summary> /// <param name="runHandler">Futtassa-e a hozzá tartozó <see cref="EntityHandler"/> objektumot, vagy sem</param> public void Delete(bool runHandler, bool logikai = true) { try { // <<Template Method>> IEntityHandler handler = null; if (runHandler) { handler = EntityHandler.Create(GetType()); handler.BeforeDelete(this); } // ellenőrzi az aktív kapcsolatait CheckActiveConnections(); DoDelete(false, runHandler, logikai); if (runHandler) { handler.AfterDelete(this); } } catch /*(Exception ex)*/ { EntityState es = this.State; this.State = EntityState.Removed; // biztos ami biztos //EntityHistoryLogger.LogErrorHistory(this); this.State = es; throw; } } /// <summary> /// Törli az entitást az adatbázisból a kapcsolódásaival együtt. /// </summary> /// <remarks>Sikeres törlés esetén az entitás 'EntityState.REMOVED' állapotba kerül.</remarks> public void CascadeDelete(bool logikai = true) { CascadeDelete(false, logikai); } /// <summary> /// Törli az entitást az adatbázisból a kapcsolódásaival együtt. /// </summary> /// <param name="runHandler">Futtassa-e a hozzá tartozó <see cref="EntityHandler"/> objektumot, vagy sem</param> public void CascadeDelete(bool runHandler, bool logikai = true) { try { // <<Template Method>> IEntityHandler handler = null; if (runHandler) { handler = EntityHandler.Create(GetType()); handler.BeforeDelete(this); } DoDelete(true, runHandler); if (runHandler) { handler.AfterDelete(this); } } catch { EntityState es = this.State; this.State = EntityState.Removed; // biztos ami biztos //EntityHistoryLogger.LogErrorHistory(this); this.State = es; throw; } } /// <summary> /// Elvégzi az entitás törlése előtti szétkapcsolásokat és kaszkád törléseket. /// </summary> protected virtual void DeAssociateBeforeDelete(bool runHandler = false) { // Az ősosztályban nem kell csinálnunk semmit. } /// <summary> /// Elvégzi az entitás törlését. /// </summary> /// <param name="performCascadeDelete">Kaszkádosított törlés legyen-e, vagy sem</param> protected virtual void DoDelete(bool performCascadeDelete, bool runHandler = false, bool logikai = true) { if (!CanRemove) { throw new EntityStateException(m_State); } if (performCascadeDelete) { DeAssociateBeforeDelete(runHandler); } GetDataAccessor().DeleteEntity(this, logikai); SetState(EntityState.Removed); //EntityHistoryLogger.LogHistory(this); } /// <summary> /// elvegzi az entitás update-jet /// </summary> protected virtual void DoUpdate(bool withattributes, bool withassociations) { if (!CanUpdate) { throw new EntityStateException(m_State); } Validate(!withattributes); if (withattributes) { if (!GetDataAccessor().UpdateEntity(this)) { throw new EntityExpiredException(GetEntityName(), ID, m_Serial); } } if (withassociations) { if (!GetDataAccessor().UpdateAssociations(this)) { throw new EntityExpiredException(GetEntityName(), ID, m_Serial); } } //EntityHistoryLogger.LogHistory(this); SetState(EntityState.Initialized); } /// <summary> /// elvegzi az entitas insertjet /// </summary> protected virtual void DoInsert() { if (!CanInsert) { throw new EntityStateException(m_State); } Validate(); GetDataAccessor().InsertEntity(this); StoreOriginalValues(); //EntityHistoryLogger.LogHistory(this); SetState(EntityState.Initialized); } #endregion #region Oszlopszintű naplózás támogatása /// <summary> /// Eltárolja az entitás összes attribútumának adatbázisban tárolt értékét. /// </summary> protected virtual void StoreOriginalValues() { m_OriginalValues.Clear(); } /// <summary> /// A függvény eltárolja egy attribútum értékének megváltozását. /// </summary> /// <param name="attributeName">A módosult attribútum neve</param> /// <param name="value">A módosult attribútum új értéke</param> /// <remarks>Ezt a metódust meg kell hívni minden olyan esetben, amikor olyan attribútum módosult, /// amelyet oszlop szinten naplózni kell.</remarks> protected void FieldModified(string attributeName, object value) { if (string.IsNullOrWhiteSpace(attributeName)) { throw new ArgumentNullException(nameof(attributeName)); } if (m_State == EntityState.Initialized) { SetState(EntityState.Modified); } else if (m_State == EntityState.Uninitialized) { SetState(EntityState.New); } m_CurrentValues[attributeName] = (value ?? DBNull.Value); } /// <summary> /// Az entitás eredeti naplózandó attribútumai. /// </summary> protected internal Hashtable OriginalValues { get { return m_OriginalValues; } } /// <summary> /// Az entitás módosult naplózandó attribútumai a jelenlegi értékükkel. /// </summary> protected internal Hashtable CurrentValues { get { return m_CurrentValues; } } /// <summary> /// Visszaadja egy attribútum eredeti értékét. /// </summary> /// <param name="attributeName">Az attribútum neve</param> /// <returns>Az attribútum eredeti értéke</returns> /// <exception cref="ArgumentException">Ha az entitás nem rendelkezik a magadott attribútummal, vagy az nem naplózandó.</exception> public object GetOriginalValue(string attributeName) { if (!m_OriginalValues.ContainsKey(attributeName)) { throw new ArgumentException("The entity does not contain the original value for the specified attribute.", nameof(attributeName)); } object result = m_OriginalValues[attributeName]; if (result == DBNull.Value) { return null; } return result; } /// <summary> /// Megvizsgálja, hogy egy attribútum eredeti értéke null-e, vagy sem. /// </summary> /// <param name="attributeName">Az attribútum neve</param> /// <returns>True, ha az attribútum eredeti értéke null; egyébként false</returns> /// <exception cref="ArgumentException">Ha az entitás nem rendelkezik a magadott attribútummal, vagy az nem naplózandó.</exception> public bool IsOriginalValueNull(string attributeName) { return (GetOriginalValue(attributeName) == null); } /// <summary> /// Megvizsgálja, hogy egy attribútum értéke változott-e, vagy sem. /// </summary> /// <param name="attributeName">Az attribútum neve</param> /// <returns>True, ha változott; egyébként false</returns> public bool HasChanged(string attributeName) { return m_CurrentValues.ContainsKey(attributeName); } public IEnumerable<string> ChangedAttributes() { string[] result = new string[m_CurrentValues.Count]; m_CurrentValues.Keys.CopyTo(result, 0); return result; } #endregion } }