kreta/Framework/Entities/Entity.cs
2024-03-13 00:33:46 +01:00

911 lines
28 KiB
C#

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
}
}