Store.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
  1. using System.Collections.Concurrent;
  2. using System.Reflection;
  3. using System.Text.RegularExpressions;
  4. using InABox.Core;
  5. namespace InABox.Database
  6. {
  7. public static class UserTrackingCache
  8. {
  9. public static ConcurrentBag<UserTracking> Cache = new();
  10. public static DateTime Date = DateTime.MinValue;
  11. }
  12. // public interface ICacheStore<T>
  13. // {
  14. // void LoadCache();
  15. // T GetCacheItem();
  16. // void UpdateCache();
  17. // }
  18. public class Store<T> : IStore, IStore<T> where T : Entity, new()
  19. {
  20. public bool IsSubStore { get; set; }
  21. public Guid UserGuid { get; set; }
  22. public string UserID { get; set; }
  23. public string Platform { get; set; }
  24. public string Version { get; set; }
  25. public IProvider Provider { get; set; }
  26. public virtual void Init()
  27. {
  28. }
  29. private Type GetTrackingType(Type type)
  30. {
  31. var attr = type.GetCustomAttribute<UserTrackingAttribute>();
  32. if (attr == null)
  33. return type;
  34. if (!attr.Enabled)
  35. return null;
  36. if (attr.Parent != null)
  37. return GetTrackingType(attr.Parent);
  38. return type;
  39. }
  40. private void UpdateUserTracking(UserTrackingAction action)
  41. {
  42. if (IsSubStore)
  43. return;
  44. if (!DbFactory.IsSupported<UserTracking>())
  45. return;
  46. if (string.IsNullOrWhiteSpace(UserID))
  47. return;
  48. var type = GetTrackingType(typeof(T));
  49. if (type == null)
  50. return;
  51. if (UserTrackingCache.Date != DateTime.Today)
  52. {
  53. var tdata = Provider.Query(
  54. new Filter<UserTracking>(x => x.Date).IsEqualTo(DateTime.Today),
  55. new Columns<UserTracking>().Default(ColumnType.IncludeForeignKeys)
  56. );
  57. UserTrackingCache.Cache = new ConcurrentBag<UserTracking>(tdata.Rows.Select(x => x.ToObject<UserTracking>()));
  58. UserTrackingCache.Date = DateTime.Today;
  59. }
  60. var tracking = UserTrackingCache.Cache.FirstOrDefault(x =>
  61. Equals(x.User.ID, UserGuid) && DateTime.Equals(x.Date, DateTime.Today) && string.Equals(x.Type, type.Name));
  62. if (tracking == null)
  63. {
  64. tracking = new UserTracking();
  65. tracking.Date = DateTime.Today;
  66. tracking.Type = type.Name;
  67. tracking.User.ID = UserGuid;
  68. UserTrackingCache.Cache.Add(tracking);
  69. }
  70. tracking.Increment(DateTime.Now, action);
  71. Provider.Save(tracking);
  72. }
  73. public IStore FindSubStore(Type t)
  74. {
  75. var defType = typeof(Store<>).MakeGenericType(t);
  76. var subType = DbFactory.Stores.Where(myType => myType.IsSubclassOf(defType)).FirstOrDefault();
  77. var result = (IStore)Activator.CreateInstance(subType == null ? defType : subType);
  78. result.UserGuid = UserGuid;
  79. result.UserID = UserID;
  80. result.Platform = Platform;
  81. result.Version = Version;
  82. result.IsSubStore = true;
  83. result.Provider = Provider;
  84. return result;
  85. }
  86. public IStore<TEntity> FindSubStore<TEntity>() where TEntity : Entity, new()
  87. {
  88. var defType = typeof(Store<>).MakeGenericType(typeof(TEntity));
  89. var subType = DbFactory.Stores.Where(myType => myType.IsSubclassOf(defType)).FirstOrDefault();
  90. var store = (Store<TEntity>)Activator.CreateInstance(subType == null ? defType : subType);
  91. store.UserGuid = UserGuid;
  92. store.UserID = UserID;
  93. store.Platform = Platform;
  94. store.Version = Version;
  95. store.IsSubStore = true;
  96. store.Provider = Provider;
  97. return store;
  98. }
  99. private Filter<T>? RunScript(ScriptType type, Filter<T>? filter)
  100. {
  101. var scriptname = type.ToString();
  102. var key = string.Format("{0} {1}", typeof(T).EntityName(), scriptname);
  103. if (DbFactory.LoadedScripts.ContainsKey(key))
  104. {
  105. var script = DbFactory.LoadedScripts[key];
  106. Logger.Send(LogType.Information, UserID, string.Format("{0}.{1} Executing..", typeof(T).EntityName(), scriptname));
  107. try
  108. {
  109. script.SetValue("Store", this);
  110. script.SetValue("Filter", filter);
  111. var result = script.Execute();
  112. Logger.Send(LogType.Information, UserID, string.Format("{0}.{1} returns {2}", typeof(T).EntityName(), scriptname, result));
  113. return result ? script.GetValue("Filter") as Filter<T> : filter;
  114. }
  115. catch (Exception eExec)
  116. {
  117. Logger.Send(LogType.Information, UserID,
  118. string.Format("{0}.{1} Invoke Exception: {2}", typeof(T).EntityName(), scriptname, eExec.Message));
  119. }
  120. }
  121. return filter;
  122. }
  123. private IEnumerable<T> RunScript(ScriptType type, IEnumerable<T> entities)
  124. {
  125. var scriptname = type.ToString();
  126. var variable = typeof(T).EntityName().Split('.').Last() + "s";
  127. var key = string.Format("{0} {1}", typeof(T).EntityName(), scriptname);
  128. if (DbFactory.LoadedScripts.ContainsKey(key))
  129. {
  130. var script = DbFactory.LoadedScripts[key];
  131. script.SetValue("Store", this);
  132. script.SetValue(variable, entities);
  133. Logger.Send(LogType.Information, UserID, string.Format("{0}.{1} Executing..", typeof(T).EntityName(), scriptname));
  134. foreach (var entity in entities)
  135. try
  136. {
  137. var result = script.Execute();
  138. Logger.Send(LogType.Information, UserID,
  139. string.Format("{0}.{1} returns {2}: {3}", typeof(T).EntityName(), scriptname, result, entity));
  140. return result ? script.GetValue(variable) as IEnumerable<T> : entities;
  141. }
  142. catch (Exception eExec)
  143. {
  144. var stack = new List<string>();
  145. var eStack = eExec;
  146. while (eStack != null)
  147. {
  148. stack.Add(eStack.Message);
  149. eStack = eStack.InnerException;
  150. }
  151. stack.Reverse();
  152. var message = string.Join("\n", stack);
  153. Logger.Send(LogType.Information, UserID,
  154. string.Format("{0}.{1} Invoke Exception: {2}", typeof(T).EntityName(), scriptname, message));
  155. }
  156. }
  157. return entities;
  158. }
  159. private CoreTable RunScript(ScriptType type, CoreTable table)
  160. {
  161. var scriptname = type.ToString();
  162. var variable = typeof(T).EntityName().Split('.').Last() + "s";
  163. var key = string.Format("{0} {1}", typeof(T).EntityName(), scriptname);
  164. if (DbFactory.LoadedScripts.ContainsKey(key))
  165. {
  166. var script = DbFactory.LoadedScripts[key];
  167. Logger.Send(LogType.Information, UserID, string.Format("{0}.{1} Executing..", typeof(T).EntityName(), scriptname));
  168. try
  169. {
  170. script.SetValue("Store", this);
  171. script.SetValue(variable, table);
  172. var result = script.Execute();
  173. Logger.Send(LogType.Information, UserID,
  174. string.Format("{0}.{1} returns {2}: {3}", typeof(T).EntityName(), scriptname, result, table));
  175. return result ? script.GetValue(variable) as CoreTable : table;
  176. }
  177. catch (Exception eExec)
  178. {
  179. Logger.Send(LogType.Information, UserID,
  180. string.Format("{0}.{1} Invoke Exception: {2}", typeof(T).EntityName(), scriptname, eExec.Message));
  181. }
  182. }
  183. return table;
  184. }
  185. //#region Session / Transaction Handling
  186. //void //OpenSession(String action, bool write)
  187. //{
  188. // if (!IsSubStore)
  189. // Provider.OpenSession<T>(action, write);
  190. //}
  191. //void //CloseSession(String action, bool write)
  192. //{
  193. // if (!IsSubStore)
  194. // Provider.CloseSession<T>(action, write);
  195. //}
  196. //#endregion
  197. #region List Functions
  198. private IEnumerable<object[]> DoList(Filter<T>? filter = null, Columns<T>? columns = null, SortOrder<T>? sort = null)
  199. {
  200. UpdateUserTracking(UserTrackingAction.Read);
  201. //last = DateTime.Now;
  202. //OpenSession("List", false);
  203. //LogStep("OpenSession");
  204. try
  205. {
  206. var flt = PrepareFilter(filter);
  207. flt = RunScript(ScriptType.BeforeQuery, flt);
  208. var result = Provider.List(flt, columns, sort);
  209. //LogStep("PopulateTable");
  210. AfterList(result);
  211. //CloseSession("List", false);
  212. //LogStep("CloseSession");
  213. return result;
  214. }
  215. catch (Exception e)
  216. {
  217. //CloseSession("List", false);
  218. throw new Exception(e.Message + "\n\n" + e.StackTrace + "\n");
  219. }
  220. }
  221. public IEnumerable<object[]> List(Filter<T>? filter = null, Columns<T>? columns = null, SortOrder<T>? sort = null)
  222. {
  223. return DoList(filter, columns, sort);
  224. }
  225. public IEnumerable<object[]> List(Filter<Entity>? filter = null, Columns<Entity>? columns = null, SortOrder<Entity>? sort = null)
  226. {
  227. return DoList((Filter<T>?)filter, (Columns<T>)columns, (SortOrder<T>)sort);
  228. }
  229. protected virtual void AfterList(IEnumerable<object[]> data)
  230. {
  231. }
  232. #endregion
  233. #region Query Functions
  234. protected virtual CoreTable OnQuery(Filter<T>? filter, Columns<T>? columns, SortOrder<T>? sort)
  235. {
  236. return Provider.Query(filter, columns, sort);
  237. }
  238. private CoreTable DoQuery(Filter<T>? filter = null, Columns<T>? columns = null, SortOrder<T>? sort = null)
  239. {
  240. UpdateUserTracking(UserTrackingAction.Read);
  241. //last = DateTime.Now;
  242. //OpenSession("Query", false);
  243. //LogStep("OpenSession");
  244. try
  245. {
  246. var flt = PrepareFilter(filter);
  247. flt = RunScript(ScriptType.BeforeQuery, flt);
  248. var result = OnQuery(filter, columns, sort);
  249. //LogStep("PopulateTable");
  250. AfterQuery(result);
  251. result = RunScript(ScriptType.AfterQuery, result);
  252. //CloseSession("Query", false);
  253. //LogStep("CloseSession");
  254. return result;
  255. }
  256. catch (Exception e)
  257. {
  258. //CloseSession("Query", false);
  259. throw new Exception(e.Message + "\n\n" + e.StackTrace + "\n");
  260. }
  261. }
  262. public CoreTable Query(Filter<T>? filter = null, Columns<T>? columns = null, SortOrder<T>? sort = null)
  263. {
  264. return DoQuery(filter, columns, sort);
  265. }
  266. public CoreTable Query(Filter<Entity>? filter = null, Columns<Entity>? columns = null, SortOrder<Entity>? sort = null)
  267. {
  268. return DoQuery((Filter<T>?)filter, (Columns<T>)columns, (SortOrder<T>)sort);
  269. }
  270. protected virtual void AfterQuery(CoreTable data)
  271. {
  272. }
  273. #endregion
  274. #region Load Functions
  275. protected virtual Filter<T>? PrepareFilter(Filter<T>? filter)
  276. {
  277. return filter;
  278. }
  279. private T[] DoLoad(Filter<T>? filter = null, SortOrder<T>? sort = null)
  280. {
  281. UpdateUserTracking(UserTrackingAction.Read);
  282. //OpenSession("Load", false);
  283. T[] results = null;
  284. try
  285. {
  286. var flt = PrepareFilter(filter);
  287. flt = RunScript(ScriptType.BeforeQuery, flt);
  288. results = Provider.Load(flt, sort);
  289. AfterLoad(results);
  290. //CloseSession("Load", false);
  291. results = RunScript(ScriptType.AfterLoad, results) as T[];
  292. return results;
  293. }
  294. catch (Exception e)
  295. {
  296. //CloseSession("Load", false);
  297. throw new Exception(e.Message + "\n\n" + e.StackTrace + "\n");
  298. }
  299. }
  300. public Entity[] Load(Filter<Entity>? filter = null, SortOrder<Entity>? sort = null)
  301. {
  302. return DoLoad((Filter<T>?)filter, sort != null ? (SortOrder<T>)sort : null);
  303. }
  304. public T[] Load(Filter<T>? filter = null, SortOrder<T>? sort = null)
  305. {
  306. return DoLoad(filter, sort);
  307. }
  308. protected virtual void AfterLoad(IEnumerable<T> items)
  309. {
  310. foreach (var item in items)
  311. item.SetObserving(true);
  312. }
  313. #endregion
  314. #region Saving Functions
  315. private static readonly Regex IsNumeric = new(@"^\d+$");
  316. private void CheckAutoIncrement(params T[] entities)
  317. {
  318. if (ProcessNumericAutoInc(entities))
  319. return;
  320. ProcessStringAutoIncrement(entities);
  321. }
  322. public static string AutoIncrementPrefix { get; set; }
  323. private bool ProcessStringAutoIncrement(params T[] entities)
  324. {
  325. if (!entities.Any())
  326. return false;
  327. var autoinc = entities.First() as IStringAutoIncrement<T>;
  328. if (autoinc != null)
  329. {
  330. var prop = CoreUtils.GetPropertyFromExpression<T, string>(autoinc.AutoIncrementField());
  331. var bRequired = false;
  332. foreach (var entity in entities)
  333. bRequired = bRequired || string.IsNullOrWhiteSpace(prop.GetValue(entity) as string);
  334. if (bRequired)
  335. {
  336. var filter = new Filter<T>(prop.Name).IsGreaterThanOrEqualTo(String.Format("{0}0", AutoIncrementPrefix))
  337. .And(prop.Name).IsLessThanOrEqualTo(String.Format("{0}9999999999", AutoIncrementPrefix));
  338. var filter2 = autoinc.AutoIncrementFilter();
  339. if (filter2 != null)
  340. filter = filter.And(filter2);
  341. // if (!string.IsNullOrWhiteSpace(AutoIncrementPrefix))
  342. // {
  343. // var prefixfilter = new Filter<T>(prop.Name).BeginsWith(AutoIncrementPrefix);
  344. // filter = filter == null ? prefixfilter : filter.And(prefixfilter);
  345. // }
  346. var newvalue = 0;
  347. var row = Provider.Query(
  348. filter,
  349. new Columns<T>(new[] { prop.Name }),
  350. new SortOrder<T>(prop.Name, SortDirection.Descending),
  351. 1
  352. ).Rows.FirstOrDefault();
  353. if (row != null)
  354. {
  355. var id = row.Get<string>(prop.Name);
  356. if (!string.IsNullOrWhiteSpace(AutoIncrementPrefix))
  357. id = id.Substring(AutoIncrementPrefix.Length);
  358. id = new string(id.Where(c => char.IsDigit(c)).ToArray());
  359. int.TryParse(id, out newvalue);
  360. }
  361. foreach (var entity in entities)
  362. if (string.IsNullOrWhiteSpace(prop.GetValue(entity) as string))
  363. {
  364. newvalue++;
  365. prop.SetValue(entity, AutoIncrementPrefix + string.Format(autoinc.AutoIncrementFormat(), newvalue));
  366. }
  367. return true;
  368. }
  369. }
  370. return false;
  371. }
  372. private bool ProcessNumericAutoInc(params T[] entities)
  373. {
  374. if (!entities.Any())
  375. return false;
  376. var autoinc = entities.First() as INumericAutoIncrement<T>;
  377. if (autoinc != null)
  378. {
  379. var prop = CoreUtils.GetPropertyFromExpression(autoinc.AutoIncrementField());
  380. var bRequired = false;
  381. foreach (var entity in entities)
  382. bRequired = bRequired || prop.GetValue(entity).Equals(0);
  383. if (bRequired)
  384. {
  385. var row = Provider.Query(
  386. autoinc.AutoIncrementFilter(),
  387. new Columns<T>(new[] { prop.Name }),
  388. new SortOrder<T>(prop.Name,SortDirection.Descending),
  389. 1
  390. ).Rows.FirstOrDefault();
  391. int newvalue = row != null ? row.Get<int>(prop.Name) : 0;
  392. foreach (var entity in entities)
  393. {
  394. if (prop.GetValue(entity).Equals(0))
  395. {
  396. newvalue++;
  397. prop.SetValue(entity, newvalue);
  398. }
  399. }
  400. return true;
  401. }
  402. }
  403. return false;
  404. }
  405. protected virtual void BeforeSave(T entity)
  406. {
  407. // Process any AutoIncrement Fields before we apply the Unique Code test
  408. // Thus, if we have a unique autoincrement, it will be populated prior to validation
  409. CheckAutoIncrement(entity);
  410. // Check for (a) blank code fields and (b) duplicate codes
  411. // There may be more than one code on an entity (why would you do this?)
  412. // so it's a bit trickier that I would hope
  413. Filter<T> codes = null;
  414. var columns = new Columns<T>(x => x.ID);
  415. var props = CoreUtils.PropertyList(typeof(T), x => x.GetCustomAttributes<UniqueCodeEditor>().Any(), true);
  416. foreach (var key in props.Keys)
  417. if (entity.HasOriginalValue(key) || entity.ID == Guid.Empty)
  418. {
  419. var code = CoreUtils.GetPropertyValue(entity, key) as string;
  420. if (string.IsNullOrWhiteSpace(code))
  421. throw new NullCodeException(typeof(T), key);
  422. var expr = CoreUtils.GetPropertyExpression<T, object>(key); //CoreUtils.GetMemberExpression(typeof(T),key)
  423. codes = codes == null ? new Filter<T>(expr).IsEqualTo(code) : codes.Or(expr).IsEqualTo(code);
  424. columns.Add(key);
  425. }
  426. if (codes != null)
  427. {
  428. var filter = new Filter<T>(x => x.ID).IsNotEqualTo(entity.ID);
  429. filter = filter.And(codes);
  430. var others = Provider.Query(filter, columns);
  431. var duplicates = new Dictionary<string, object>();
  432. foreach (var row in others.Rows)
  433. foreach (var key in props.Keys)
  434. {
  435. var eval = CoreUtils.GetPropertyValue(entity, key);
  436. var cval = row.Get<string>(key);
  437. if (Equals(eval, cval))
  438. duplicates[key] = eval;
  439. }
  440. if (duplicates.Any())
  441. throw new DuplicateCodeException(typeof(T), duplicates);
  442. }
  443. }
  444. protected virtual void AfterSave(T entity)
  445. {
  446. }
  447. protected virtual void OnSave(T entity, ref string auditnote)
  448. {
  449. CheckAutoIncrement(entity);
  450. Provider.Save(entity);
  451. }
  452. private void DoSave(T entity, string auditnote)
  453. {
  454. UpdateUserTracking(UserTrackingAction.Write);
  455. entity = RunScript(ScriptType.BeforeSave, new[] { entity }).First();
  456. //OpenSession("Save", true);
  457. //try
  458. //{
  459. var changes = entity.ChangedValues();
  460. //UpdateInternalLinks(entity);
  461. BeforeSave(entity);
  462. //OpenSession("Save", true);
  463. try
  464. {
  465. OnSave(entity, ref auditnote);
  466. }
  467. catch (Exception e)
  468. {
  469. //CloseSession("Save", true);
  470. throw e;
  471. }
  472. //CloseSession("Save", true);
  473. if (DbFactory.IsSupported<AuditTrail>())
  474. {
  475. var notes = new List<string>();
  476. if (!string.IsNullOrEmpty(auditnote))
  477. notes.Add(auditnote);
  478. if (!string.IsNullOrEmpty(changes))
  479. notes.Add(changes);
  480. if (notes.Any()) AuditTrail(entity, notes);
  481. }
  482. AfterSave(entity);
  483. //UpdateExternalLinks(entity, false);
  484. entity.CommitChanges();
  485. //CloseSession("Save", false);
  486. entity = RunScript(ScriptType.AfterSave, new[] { entity }).First();
  487. //}
  488. //catch (Exception e)
  489. //{
  490. // //CloseSession("Save", false);
  491. // throw new Exception(e.Message + "\n\n" + e.StackTrace + "\n");
  492. //}
  493. }
  494. protected void AuditTrail(IEnumerable<Entity> entities, IEnumerable<string> notes)
  495. {
  496. var updates = new List<AuditTrail>();
  497. foreach (var entity in entities)
  498. {
  499. var audit = new AuditTrail
  500. {
  501. EntityID = entity.ID,
  502. Timestamp = DateTime.Now,
  503. User = UserID,
  504. Note = string.Join(": ", notes)
  505. };
  506. updates.Add(audit);
  507. }
  508. Provider.Save(updates);
  509. }
  510. protected void AuditTrail(Entity entity, IEnumerable<string> notes)
  511. {
  512. AuditTrail(new[] { entity }, notes);
  513. }
  514. public void Save(T entity, string auditnote)
  515. {
  516. DoSave(entity, auditnote);
  517. }
  518. public void Save(Entity entity, string auditnote)
  519. {
  520. var ent = (T)entity;
  521. DoSave(ent, auditnote);
  522. }
  523. public void Save(IEnumerable<T> entities, string auditnote)
  524. {
  525. DoSave(entities, auditnote);
  526. }
  527. public void Save(IEnumerable<Entity> entities, string auditnote)
  528. {
  529. var updates = new List<T>();
  530. foreach (var entity in entities)
  531. updates.Add((T)entity);
  532. DoSave(updates, auditnote);
  533. }
  534. protected virtual void OnSave(IEnumerable<T> entities, ref string auditnote)
  535. {
  536. CheckAutoIncrement(entities.ToArray());
  537. Provider.Save(entities);
  538. }
  539. private void DoSave(IEnumerable<T> entities, string auditnote)
  540. {
  541. UpdateUserTracking(UserTrackingAction.Write);
  542. entities = RunScript(ScriptType.BeforeSave, entities.ToList());
  543. //OpenSession("Save", true);
  544. //try
  545. //{
  546. var changes = new Dictionary<T, string>();
  547. foreach (var entity in entities)
  548. {
  549. changes[entity] = entity.ChangedValues();
  550. //UpdateInternalLinks(entity);
  551. BeforeSave(entity);
  552. }
  553. try
  554. {
  555. //OpenSession("Save", true);
  556. OnSave(entities, ref auditnote);
  557. //CloseSession("Save", true);
  558. }
  559. catch (Exception e)
  560. {
  561. //CloseSession("Save", true);
  562. throw e;
  563. }
  564. if (DbFactory.IsSupported<AuditTrail>())
  565. {
  566. var audittrails = new List<AuditTrail>();
  567. foreach (var entity in entities)
  568. {
  569. var notes = new List<string>();
  570. if (!string.IsNullOrEmpty(auditnote))
  571. notes.Add(auditnote);
  572. if (changes.ContainsKey(entity) && !string.IsNullOrEmpty(changes[entity]))
  573. notes.Add(changes[entity]);
  574. if (notes.Any())
  575. {
  576. var audit = new AuditTrail
  577. {
  578. EntityID = entity.ID,
  579. Timestamp = DateTime.Now,
  580. User = UserID,
  581. Note = string.Join(": ", notes)
  582. };
  583. audittrails.Add(audit);
  584. //Provider.Save<AuditTrail>(audit);
  585. }
  586. }
  587. if (audittrails.Any())
  588. Provider.Save(audittrails);
  589. }
  590. foreach (var entity in entities)
  591. {
  592. AfterSave(entity);
  593. //UpdateExternalLinks(entity, false);
  594. entity.CommitChanges();
  595. }
  596. entities = RunScript(ScriptType.AfterSave, entities);
  597. //}
  598. //catch (Exception e)
  599. //{
  600. // throw e;
  601. //}
  602. }
  603. #endregion
  604. #region Delete Functions
  605. protected virtual void BeforeDelete(T entity)
  606. {
  607. }
  608. protected virtual void OnDelete(T entity)
  609. {
  610. Provider.Delete(entity, UserID);
  611. }
  612. protected virtual void OnDelete(IEnumerable<T> entities)
  613. {
  614. Provider.Delete(entities, UserID);
  615. }
  616. private void DoDelete(T entity, string auditnote)
  617. {
  618. UpdateUserTracking(UserTrackingAction.Write);
  619. entity = RunScript(ScriptType.BeforeDelete, new[] { entity }).First();
  620. //OpenSession("Delete",false);
  621. try
  622. {
  623. BeforeDelete(entity);
  624. //OpenSession("Delete",true);
  625. try
  626. {
  627. OnDelete(entity);
  628. }
  629. catch (Exception e)
  630. {
  631. Logger.Send(LogType.Error, "", $"Error in DoDelete(T entity, string auditnote):\n{CoreUtils.FormatException(e)}");
  632. //CloseSession("Delete", true);
  633. //throw e;
  634. }
  635. //CloseSession("Delete",true);
  636. AfterDelete(entity);
  637. //UpdateExternalLinks(entity, true);
  638. //CloseSession("Delete", false);
  639. entity = RunScript(ScriptType.AfterDelete, new[] { entity }).First();
  640. }
  641. catch (Exception e)
  642. {
  643. //CloseSession("Delete", false);
  644. throw new Exception(e.Message + "\n\n" + e.StackTrace + "\n");
  645. }
  646. }
  647. private void DoDelete(IEnumerable<T> entities, string auditnote)
  648. {
  649. UpdateUserTracking(UserTrackingAction.Write);
  650. entities = RunScript(ScriptType.BeforeDelete, entities);
  651. //OpenSession("Delete", false);
  652. try
  653. {
  654. foreach (var entity in entities)
  655. BeforeDelete(entity);
  656. //OpenSession("Delete", true);
  657. try
  658. {
  659. OnDelete(entities);
  660. }
  661. catch (Exception e)
  662. {
  663. Logger.Send(LogType.Error, "", $"Error in DoDelete(IEnumerable<T> entities, string auditnote):\n{CoreUtils.FormatException(e)}");
  664. ////CloseSession("Delete", true);
  665. }
  666. ////CloseSession("Delete", true);
  667. foreach (var entity in entities) AfterDelete(entity);
  668. //UpdateExternalLinks(entity, true);
  669. ////CloseSession("Delete", false);
  670. entities = RunScript(ScriptType.AfterDelete, entities);
  671. }
  672. catch (Exception e)
  673. {
  674. ////CloseSession("Delete", false);
  675. throw new Exception(e.Message + "\n\n" + e.StackTrace + "\n");
  676. }
  677. }
  678. public void Delete(T entity, string auditnote)
  679. {
  680. DoDelete(entity, auditnote);
  681. }
  682. public void Delete(Entity entity, string auditnote)
  683. {
  684. DoDelete((T)entity, auditnote);
  685. }
  686. public void Delete(IEnumerable<T> entities, string auditnote)
  687. {
  688. DoDelete(entities, auditnote);
  689. }
  690. public void Delete(IEnumerable<Entity> entities, string auditnote)
  691. {
  692. var updates = new List<T>();
  693. foreach (var entity in entities)
  694. updates.Add((T)entity);
  695. DoDelete(updates, auditnote);
  696. }
  697. protected virtual void AfterDelete(T entity)
  698. {
  699. }
  700. #endregion
  701. #region BulkUpdate Functions
  702. public void BulkUpdate(IEnumerable<Entity> entities)
  703. {
  704. BulkUpdate((IEnumerable<T>)entities);
  705. }
  706. public virtual void BulkUpdate(IEnumerable<T> entities)
  707. {
  708. UpdateUserTracking(UserTrackingAction.Write);
  709. Provider.Save(entities);
  710. }
  711. #endregion
  712. }
  713. }