|
@@ -0,0 +1,596 @@
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Text;
|
|
|
+using System.Linq;
|
|
|
+using System.IO;
|
|
|
+using System.Collections;
|
|
|
+
|
|
|
+using Newtonsoft.Json;
|
|
|
+using ICSharpCode.SharpZipLib.GZip;
|
|
|
+using System.ComponentModel;
|
|
|
+
|
|
|
+namespace FsDb
|
|
|
+{
|
|
|
+ public class FsDbEngine
|
|
|
+ {
|
|
|
+ //---- consts
|
|
|
+
|
|
|
+ private const string ENTITY_FILE_EX = ".Json";
|
|
|
+ private const string IDENTITY_FILE_EX = ".FsDbIdentity";
|
|
|
+ private const string COMPRESS_FILE_EX = ".FsDb.gz";
|
|
|
+
|
|
|
+ //---- props
|
|
|
+
|
|
|
+ private bool m_MaxinumCompress = true;
|
|
|
+ private bool m_EnableAutoCompress = true;
|
|
|
+ private int m_FileCountToAutoCompress = 50;
|
|
|
+ private bool m_OpenAsReadonly;
|
|
|
+
|
|
|
+ protected bool MaxinumCompress
|
|
|
+ {
|
|
|
+ get { return m_MaxinumCompress; }
|
|
|
+ set { m_MaxinumCompress = value; }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected bool EnableAutoCompress
|
|
|
+ {
|
|
|
+ get { return m_EnableAutoCompress; }
|
|
|
+ set { m_EnableAutoCompress = value; }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected int FileCountToAutoCompress
|
|
|
+ {
|
|
|
+ get { return m_FileCountToAutoCompress; }
|
|
|
+ set { m_FileCountToAutoCompress = value; }
|
|
|
+ }
|
|
|
+
|
|
|
+ public event CancelEventHandler OnBeforeAutoCompress;
|
|
|
+ public event EventHandler OnAfterAutoCompress;
|
|
|
+
|
|
|
+ //---- ctors
|
|
|
+
|
|
|
+ public FsDbEngine(string dbFolderPath, bool openAsReadonly = false)
|
|
|
+ {
|
|
|
+ m_OpenAsReadonly = openAsReadonly;
|
|
|
+
|
|
|
+ if (Directory.Exists(dbFolderPath) == false && m_OpenAsReadonly == false)
|
|
|
+ {
|
|
|
+ Directory.CreateDirectory(dbFolderPath);
|
|
|
+ OnCreate();
|
|
|
+ }
|
|
|
+
|
|
|
+ m_strDbFolderPath = dbFolderPath;
|
|
|
+
|
|
|
+ //lazy load...
|
|
|
+ m_dicCollections = new Dictionary<Type, object>();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ //---- internals
|
|
|
+
|
|
|
+ private string m_strDbFolderPath;
|
|
|
+
|
|
|
+ private Dictionary<Type, object> m_dicCollections;
|
|
|
+
|
|
|
+ //---- internal commons
|
|
|
+
|
|
|
+ private T LoadEntityFromJson<T>(string p) where T : FsDbEntityBase, new()
|
|
|
+ {
|
|
|
+ //load user data
|
|
|
+ var ud = JsonConvert.DeserializeObject<T>(p);
|
|
|
+
|
|
|
+ //load system data
|
|
|
+ var sd = new { IsDeleted = false, ID = 0 };
|
|
|
+ sd = JsonConvert.DeserializeAnonymousType(p, sd);
|
|
|
+
|
|
|
+ ud.ID = sd.ID;
|
|
|
+ ud.IsDeleted = sd.IsDeleted;
|
|
|
+ ud.EngineInstance = this;
|
|
|
+
|
|
|
+ return ud;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void OverrodeColl<T>(FsDbCollection<T> coll, T p) where T : FsDbEntityBase, new()
|
|
|
+ {
|
|
|
+ var oe = coll.InternalContainer.FirstOrDefault(q => q.ID == p.ID);
|
|
|
+ if (oe == null)
|
|
|
+ coll.InternalContainer.Add(p);
|
|
|
+ else
|
|
|
+ coll.InternalContainer[coll.InternalContainer.IndexOf(oe)] = p;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static byte[] PackColl<T>(FsDbCollection<T> coll) where T : FsDbEntityBase, new()
|
|
|
+ {
|
|
|
+ var msPack = new MemoryStream();
|
|
|
+
|
|
|
+ var bw = new BinaryWriter(msPack);
|
|
|
+
|
|
|
+ //identity
|
|
|
+ bw.Write(coll.CurrentIdentity);
|
|
|
+ //count
|
|
|
+ bw.Write(coll.Count);
|
|
|
+ foreach (var item in coll)
|
|
|
+ {
|
|
|
+ var buf = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(item));
|
|
|
+
|
|
|
+ //length-of-bytes
|
|
|
+ bw.Write(buf.Length);
|
|
|
+ //json-utf8-bytes
|
|
|
+ bw.Write(buf);
|
|
|
+ }
|
|
|
+
|
|
|
+ bw.Flush();
|
|
|
+
|
|
|
+ return msPack.ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void UnPackColl<T>(FsDbCollection<T> coll, byte[] bufPack) where T : FsDbEntityBase, new()
|
|
|
+ {
|
|
|
+ var msPack = new MemoryStream(bufPack);
|
|
|
+
|
|
|
+ var br = new BinaryReader(msPack);
|
|
|
+
|
|
|
+ //identity
|
|
|
+ coll.CurrentIdentity = br.ReadInt32();
|
|
|
+ //count
|
|
|
+ coll.InternalContainer = new List<T>(br.ReadInt32());
|
|
|
+ for (int i = 0; i < coll.InternalContainer.Capacity; i++)
|
|
|
+ {
|
|
|
+ var buf = br.ReadBytes(br.ReadInt32());
|
|
|
+ var str = Encoding.UTF8.GetString(buf);
|
|
|
+
|
|
|
+ var item = LoadEntityFromJson<T>(str);
|
|
|
+
|
|
|
+ coll.InternalContainer.Add(item);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ //-- load/save whole
|
|
|
+
|
|
|
+ private FsDbCollection<T> LoadCollection<T>() where T : FsDbEntityBase, new()
|
|
|
+ {
|
|
|
+ var coll = new FsDbCollection<T>();
|
|
|
+
|
|
|
+ coll.EngineInstance = this;
|
|
|
+ coll.InternalContainer = new List<T>();
|
|
|
+
|
|
|
+ // Load data from compress
|
|
|
+ var compress_file_path = m_strDbFolderPath.CombineLocalPath(typeof(T).Name + COMPRESS_FILE_EX);
|
|
|
+ if (File.Exists(compress_file_path))
|
|
|
+ {
|
|
|
+ var bufFile = File.ReadAllBytes(compress_file_path);
|
|
|
+
|
|
|
+ var misCompress = new MemoryStream(bufFile);
|
|
|
+ using (var sDecompress = new GZipInputStream(misCompress))
|
|
|
+ {
|
|
|
+ var bufArc = sDecompress.ReadToEnd();
|
|
|
+
|
|
|
+ UnPackColl(coll, bufArc);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // load data from File System
|
|
|
+ var folderEntity = m_strDbFolderPath.CombineLocalPath(typeof(T).Name);
|
|
|
+
|
|
|
+ coll.UnCompressedFileCount = Directory
|
|
|
+ .GetFiles(folderEntity, "*" + ENTITY_FILE_EX)
|
|
|
+ .Select(p => File.ReadAllText(p))
|
|
|
+ .Select(p => LoadEntityFromJson<T>(p))
|
|
|
+ // override to from compress
|
|
|
+ .DoEach(p => OverrodeColl<T>(coll, p))
|
|
|
+ // tell filecount ~
|
|
|
+ .Count();
|
|
|
+
|
|
|
+ // remove deleted
|
|
|
+ coll.InternalContainer.RemoveAll(p => p.IsDeleted);
|
|
|
+
|
|
|
+ //if exist identity file, load it
|
|
|
+ var strFileNameIdentity = m_strDbFolderPath.CombineLocalPath(typeof(T).Name + IDENTITY_FILE_EX);
|
|
|
+ if (File.Exists(strFileNameIdentity))
|
|
|
+ coll.CurrentIdentity = TypeParser<int>.Convert((File.ReadAllText(strFileNameIdentity)));
|
|
|
+
|
|
|
+
|
|
|
+ return coll;
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void Compress<T>(FsDbCollection<T> coll) where T : FsDbEntityBase, new()
|
|
|
+ {
|
|
|
+ if (coll.EngineInstance != this)
|
|
|
+ throw new ArgumentException("集合不属于当前引擎实例(引擎BUG?)");
|
|
|
+
|
|
|
+ if (m_OpenAsReadonly)
|
|
|
+ throw new InvalidOperationException("在只读打开模式尝试写入");
|
|
|
+
|
|
|
+ // compress
|
|
|
+
|
|
|
+ lock (this)
|
|
|
+ {
|
|
|
+ lock (coll)
|
|
|
+ {
|
|
|
+ var bufArc = PackColl(coll); //coll -> arc
|
|
|
+
|
|
|
+ //arc -> compress
|
|
|
+ var msCompress = new MemoryStream();
|
|
|
+ using (var sCompress = new GZipOutputStream(msCompress))
|
|
|
+ {
|
|
|
+ if (MaxinumCompress)
|
|
|
+ sCompress.SetLevel(9);
|
|
|
+
|
|
|
+ sCompress.Write(bufArc, 0, bufArc.Length);
|
|
|
+
|
|
|
+ sCompress.Flush();
|
|
|
+ }
|
|
|
+ var bufCompress = msCompress.ToArray();
|
|
|
+
|
|
|
+ //write to compressed archive file
|
|
|
+ var compress_file_path = m_strDbFolderPath.CombineLocalPath(typeof(T).Name + COMPRESS_FILE_EX);
|
|
|
+ File.WriteAllBytes(compress_file_path, bufCompress);
|
|
|
+
|
|
|
+ // delete all single file from File System
|
|
|
+ var folder_entity = m_strDbFolderPath.CombineLocalPath(typeof(T).Name);
|
|
|
+ Directory.Delete(folder_entity, true);
|
|
|
+
|
|
|
+ //for continue add...
|
|
|
+ Directory.CreateDirectory(folder_entity);
|
|
|
+
|
|
|
+ File.Delete(m_strDbFolderPath.CombineLocalPath(typeof(T).Name + IDENTITY_FILE_EX));
|
|
|
+
|
|
|
+ coll.UnCompressedFileCount = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //-- oper
|
|
|
+
|
|
|
+ internal void UpdateIndentity<T>(int id) where T : FsDbEntityBase
|
|
|
+ {
|
|
|
+ if (m_OpenAsReadonly)
|
|
|
+ throw new InvalidOperationException("在只读打开模式尝试写入");
|
|
|
+
|
|
|
+ var strFileNameIdentity = m_strDbFolderPath.CombineLocalPath(typeof(T).Name + IDENTITY_FILE_EX);
|
|
|
+ lock (this)
|
|
|
+ {
|
|
|
+ File.WriteAllText(strFileNameIdentity, id.ToString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void SaveEntity<T>(T entity) where T : FsDbEntityBase, new()
|
|
|
+ {
|
|
|
+ if (entity.ID == 0)
|
|
|
+ throw new ArgumentException("实体未初始化(引擎BUG?)");
|
|
|
+
|
|
|
+ if (m_OpenAsReadonly)
|
|
|
+ throw new InvalidOperationException("在只读打开模式尝试写入");
|
|
|
+
|
|
|
+ var coll = GetCollection<T>();
|
|
|
+
|
|
|
+ lock (this)
|
|
|
+ {
|
|
|
+ lock (coll)
|
|
|
+ {
|
|
|
+ var json = JsonConvert.SerializeObject(entity);
|
|
|
+ var folder = m_strDbFolderPath.CombineLocalPath(typeof(T).Name);
|
|
|
+ var fn = folder.CombineLocalPath(entity.ID + ENTITY_FILE_EX);
|
|
|
+
|
|
|
+ if (Directory.Exists(folder) == false)
|
|
|
+ Directory.CreateDirectory(folder);
|
|
|
+
|
|
|
+ File.WriteAllText(fn, json);
|
|
|
+
|
|
|
+ entity.EngineInstance = this;
|
|
|
+
|
|
|
+ coll.UnCompressedFileCount++;
|
|
|
+
|
|
|
+ if (EnableAutoCompress && GetCollection<T>().UnCompressedFileCount > FileCountToAutoCompress)
|
|
|
+ {
|
|
|
+ //TODO:runtime optz, props, events
|
|
|
+ var flgCompress = true;
|
|
|
+
|
|
|
+ if (OnBeforeAutoCompress != null)
|
|
|
+ {
|
|
|
+ var cea = new CancelEventArgs();
|
|
|
+ OnBeforeAutoCompress(GetCollection<T>(), cea);
|
|
|
+
|
|
|
+ if (cea.Cancel)
|
|
|
+ flgCompress = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (flgCompress)
|
|
|
+ GetCollection<T>().Compress();
|
|
|
+
|
|
|
+ if (OnAfterAutoCompress != null)
|
|
|
+ OnAfterAutoCompress(GetCollection<T>(), EventArgs.Empty);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ internal void Delete<T>(T entity) where T : FsDbEntityBase, new()
|
|
|
+ {
|
|
|
+ lock (this)
|
|
|
+ {
|
|
|
+ entity.IsDeleted = true;
|
|
|
+ SaveEntity(entity);
|
|
|
+ entity.EngineInstance = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //---- protects
|
|
|
+
|
|
|
+ protected virtual void OnCreate()
|
|
|
+ {
|
|
|
+ //do nothing
|
|
|
+ }
|
|
|
+
|
|
|
+ //---- publics
|
|
|
+
|
|
|
+ public FsDbCollection<T> GetCollection<T>() where T : FsDbEntityBase, new()
|
|
|
+ {
|
|
|
+ lock (this)
|
|
|
+ {
|
|
|
+ if (m_dicCollections.ContainsKey(typeof(T)) == false)
|
|
|
+ {
|
|
|
+ Directory.CreateDirectory(m_strDbFolderPath.CombineLocalPath(typeof(T).Name));
|
|
|
+ m_dicCollections[typeof(T)] = LoadCollection<T>();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return (FsDbCollection<T>)m_dicCollections[typeof(T)];
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public class FsDbCollection<T> : ICollection<T> where T : FsDbEntityBase, new()
|
|
|
+ {
|
|
|
+ //---- internals
|
|
|
+
|
|
|
+ internal int CurrentIdentity;
|
|
|
+
|
|
|
+ internal FsDbEngine EngineInstance;
|
|
|
+
|
|
|
+ internal List<T> InternalContainer;
|
|
|
+
|
|
|
+ internal FsDbCollection()
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public int UnCompressedFileCount { get; internal set; }
|
|
|
+
|
|
|
+ public void Compress()
|
|
|
+ {
|
|
|
+ lock (this)
|
|
|
+ {
|
|
|
+ EngineInstance.Compress(this);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //---- publics
|
|
|
+
|
|
|
+ #region ICollection<T> 成员
|
|
|
+
|
|
|
+ public void Add(T item)
|
|
|
+ {
|
|
|
+ if (item.IsDeleted)
|
|
|
+ throw new InvalidOperationException("实体对象已是被删除的");
|
|
|
+
|
|
|
+ lock (this)
|
|
|
+ {
|
|
|
+ lock (EngineInstance)
|
|
|
+ {
|
|
|
+ item.ID = ++this.CurrentIdentity;
|
|
|
+
|
|
|
+ EngineInstance.UpdateIndentity<T>(CurrentIdentity);
|
|
|
+ EngineInstance.SaveEntity(item);
|
|
|
+
|
|
|
+
|
|
|
+ this.InternalContainer.Add(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool Remove(T item)
|
|
|
+ {
|
|
|
+ if (item.IsDeleted)
|
|
|
+ throw new InvalidOperationException("实体对象已是被删除的");
|
|
|
+
|
|
|
+ if (item.EngineInstance != this.EngineInstance)
|
|
|
+ throw new ArgumentException("实体对象所属集合不匹配");
|
|
|
+
|
|
|
+ lock (this)
|
|
|
+ {
|
|
|
+ EngineInstance.Delete(item);
|
|
|
+ return InternalContainer.Remove(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Clear()
|
|
|
+ {
|
|
|
+ lock (this)
|
|
|
+ {
|
|
|
+ foreach (var item in InternalContainer)
|
|
|
+ Remove(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool Contains(T item)
|
|
|
+ {
|
|
|
+ if (item.IsDeleted)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if (item.EngineInstance != this.EngineInstance)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ if (item.ID == 0)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ return InternalContainer.Contains(item);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void CopyTo(T[] array, int arrayIndex)
|
|
|
+ {
|
|
|
+ InternalContainer.CopyTo(array, arrayIndex);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int Count
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return InternalContainer.Count;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool IsReadOnly
|
|
|
+ {
|
|
|
+ get { return false; }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region IEnumerable<T> 成员
|
|
|
+
|
|
|
+ public IEnumerator<T> GetEnumerator()
|
|
|
+ {
|
|
|
+ return InternalContainer.GetEnumerator();
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region IEnumerable 成员
|
|
|
+
|
|
|
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
|
|
+ {
|
|
|
+ return InternalContainer.GetEnumerator();
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ public void SaveChange(T item)
|
|
|
+ {
|
|
|
+ if (item.IsDeleted)
|
|
|
+ throw new InvalidOperationException("实体对象已是被删除的");
|
|
|
+
|
|
|
+ if (item.EngineInstance == null)
|
|
|
+ throw new InvalidOperationException("要保存更改,必须添加到集合");
|
|
|
+
|
|
|
+ lock (this)
|
|
|
+ {
|
|
|
+ EngineInstance.SaveEntity(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void SaveOrAdd(T item)
|
|
|
+ {
|
|
|
+ if (item.EngineInstance == null)
|
|
|
+ Add(item);
|
|
|
+ else
|
|
|
+ SaveChange(item);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public abstract class FsDbEntityBase
|
|
|
+ {
|
|
|
+ [JsonIgnore]
|
|
|
+ internal FsDbEngine EngineInstance { get; set; }
|
|
|
+
|
|
|
+ public bool IsDeleted { get; internal set; }
|
|
|
+ public int ID { get; internal set; }
|
|
|
+
|
|
|
+ public FsDbEntityBase()
|
|
|
+ {
|
|
|
+ IsDeleted = false;
|
|
|
+ EngineInstance = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public class FsDbEntitySet<T> : IEnumerable<T> where T : FsDbEntityBase, new()
|
|
|
+ {
|
|
|
+ private FsDbEntityBase m_entity;
|
|
|
+ private FFunc<T, bool> m_onQuering;
|
|
|
+ private Action<T> m_onAdding;
|
|
|
+
|
|
|
+ public FsDbEntitySet(FsDbEntityBase entity, FFunc<T, bool> onQuering, Action<T> onAdding)
|
|
|
+ {
|
|
|
+ m_entity = entity;
|
|
|
+ m_onQuering = onQuering;
|
|
|
+ m_onAdding = onAdding;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Add(T entity)
|
|
|
+ {
|
|
|
+ if (m_entity.EngineInstance == null)
|
|
|
+ throw new InvalidOperationException("实体必须加入集合才能操作关系");
|
|
|
+
|
|
|
+ m_onAdding(entity);
|
|
|
+ m_entity.EngineInstance.GetCollection<T>().Add(entity);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void AddRange(IEnumerable<T> items)
|
|
|
+ {
|
|
|
+ foreach (var item in items)
|
|
|
+ {
|
|
|
+ Add(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public IEnumerator<T> GetEnumerator()
|
|
|
+ {
|
|
|
+ if (m_entity.EngineInstance == null)
|
|
|
+ throw new InvalidOperationException("实体必须加入集合才能操作关系");
|
|
|
+
|
|
|
+ return m_entity.EngineInstance.GetCollection<T>()
|
|
|
+ .Where(m_onQuering).GetEnumerator();
|
|
|
+ }
|
|
|
+
|
|
|
+ #region IEnumerable 成员
|
|
|
+
|
|
|
+ IEnumerator IEnumerable.GetEnumerator()
|
|
|
+ {
|
|
|
+ return GetEnumerator();
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public class FsDbEntityRef<T> where T : FsDbEntityBase, new()
|
|
|
+ {
|
|
|
+ private FsDbEntityBase m_OwnerEntity;
|
|
|
+ private FFunc<T, bool> m_onGetting;
|
|
|
+ private Action<T> m_onSetting;
|
|
|
+ private T m_Entity;
|
|
|
+
|
|
|
+ public FsDbEntityRef(FsDbEntityBase ownerEntity, FFunc<T, bool> onGetting, Action<T> onSetting)
|
|
|
+ {
|
|
|
+ m_OwnerEntity = ownerEntity;
|
|
|
+ m_onGetting = onGetting;
|
|
|
+ m_onSetting = onSetting;
|
|
|
+ }
|
|
|
+
|
|
|
+ public T Entity
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ if (m_OwnerEntity.EngineInstance == null)
|
|
|
+ return m_Entity;
|
|
|
+
|
|
|
+ return m_OwnerEntity.EngineInstance.GetCollection<T>()
|
|
|
+ .Where(m_onGetting).FirstOrDefault();
|
|
|
+ }
|
|
|
+ set
|
|
|
+ {
|
|
|
+ m_onSetting(value);
|
|
|
+ m_Entity = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|