рдХрд┐рд╕реА рдкрд░рд┐рдпреЛрдЬрдирд╛ рдореЗрдВ рдЙрдЪреНрдЪ рдЕрдиреБрдХреВрд▓рди рдпреЛрдЧреНрдп рдХреИрд╢рд┐рдВрдЧ рдХреИрд╕реЗ рдХрд░реЗрдВ рдФрд░ рд╕рд╣рдХрд░реНрдорд┐рдпреЛрдВ рдХреЛ рдЙрд╕реА рдкреНрд░рдХрд╛рд░ рдХреЗ рдХреЛрдб рдХреЛ рд▓рд┐рдЦрдиреЗ рд╕реЗ рдмрдЪрд╛рдПрдВ

"рдЕрдЧрд░ рдкреНрд░реЛрдЧреНрд░рд╛рдорд░ рдХреЗ рдХрд╛рдо рдХрд╛ рд╕рд╛рд░ рдЕрдиреНрдп рд▓реЛрдЧреЛрдВ рдХреЗ рдХрд╛рдо рдХреЛ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рдХрд░рдирд╛ рд╣реИ, рддреЛ рдореЗрд░рд╛ рдХрд╛рдо рдЗрддрдирд╛ рдХрдо рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рдХреНрдпреЛрдВ рд╣реИ", рдореИрдВрдиреЗ рд╕реЛрдЪрд╛, рдПрдХ рдмрд╛рд░ рдлрд┐рд░ рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рдПрдХ рдирдИ рдЗрдХрд╛рдИ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдореЗрдВ рдЖрд╡рд╢реНрдпрдХ рд╕рднреА рдмрд╛рдЗрдВрдбрд┐рдВрдЧ рдХреА рдкреНрд░рддрд┐рд▓рд┐рдкрд┐ рдмрдирд╛ рд░рд╣рд╛ рд╣реВрдВред рдФрд░ рдореИрдВрдиреЗ рдЯреЗрдореНрдкреНрд▓реЗрдЯ рд╡рд░реНрдЧреЛрдВ рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдХреА рдЗрд╕ рджрд┐рдирдЪрд░реНрдпрд╛ рд╕реЗ рдЫреБрдЯрдХрд╛рд░рд╛ рдкрд╛рдиреЗ рдХрд╛ рдлреИрд╕рд▓рд╛ рдХрд┐рдпрд╛, рдПрдХ рд╣реА рд╕рдордп рдореЗрдВ "рдЕрдЪреНрдЫрд╛" рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдЕрдирд╛рд╡рд╢реНрдпрдХ рд░реАрдб рдСрдкрд░реЗрд╢рдВрд╕ рд╕реЗ рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЛ рдЕрдирд▓реЛрдб рдХрд░рдирд╛ред

рдЙрд╕ рдкреНрд░рдгрд╛рд▓реА рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдПрдХ рдЫреЛрдЯреА рд╕реА рд╡рд┐рд╖рдпрд╛рдВрддрд░рддрд╛ рдЬрд┐рд╕реЗ рд╣рдо рд╡рд┐рдХрд╕рд┐рдд рдХрд░ рд░рд╣реЗ рд╣реИрдВ рдФрд░ рдЙрд╕ рд╕рдордп рдЗрд╕рдХреА рд╕реНрдерд┐рддрд┐ рд╢реБрд░реВ рд╣реБрдИ рдереА:

  • рдПрдХ рдкреНрд░рдгрд╛рд▓реА рдЬрд┐рд╕рдореЗрдВ 90% рдбреЗрдЯрд╛ рд╕рдХреНрд░рд┐рдп рд░реВрдк рд╕реЗ рдмрджрд▓ рджрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ рдФрд░ рд╕рдВрд╕рд╛рдзрд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ (рд▓реЗрдирджреЗрди, рд╡реНрдпрдХреНрддрд┐рдЧрдд рдбреЗрдЯрд╛, рдЧрдгрдирд╛ рдПрдХрддреНрд░реАрдХрд░рдг), рд▓реЗрдХрд┐рди рд╢рд╛рдпрдж рд╣реА рдХрднреА рдкрдврд╝рд╛ рдЬрд╛рддрд╛ рд╣реИ, рдФрд░ рд╢реЗрд╖ 10% рдмрд╣реБрдд рдХрдо рд╣реА рдмрджрд▓рддреЗ рд╣реИрдВ, рд▓реЗрдХрд┐рди рд╣рд░ рдЕрд╡рд╕рд░ рдкрд░ рдкрдврд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред
  • .Net рдлреНрд░реЗрдорд╡рд░реНрдХ рдкрд░ рд▓рдЧрднрдЧ рдЕрдЦрдВрдб рд╕реЗрд╡рд╛, рдЬрд┐рд╕рдореЗрдВ рдпрд╣ рд╕рдм рд▓рд╛рдЧреВ рд╣реИ
  • рдиреНрдпреВрдирддрдо рдмрд╛рдзреНрдпрдХрд╛рд░реА рдХреЗ рд╕рд╛рде Nberbernate, рдбреЗрдЯрд╛рдмреЗрд╕ рдФрд░ рдЙрд╕ рдкрд░ рдЖрдзрд╛рд░рд┐рдд рдХреИрд╢ рдХреА рдХреБрдЫ рд░рд╛рд╢рд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ (рдмреАрдУ рд╕рдВрд╕реНрдерд╛рдУрдВ рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрди рдХреЗ рд▓рд┐рдП рд╕рджрд╕реНрдпрддрд╛, рд▓реЗрди-рджреЗрди рдкрд░ рдкреНрд░рддрд┐рдмрджреНрдз рд╕рдВрдЪрд╛рд▓рдХреЛрдВ рдХреЛ рдмреБрд▓рд╛рдпрд╛)
  • рдПрдХ рджрд░реНрдЬрди рд╕реЗ рдЕрдзрд┐рдХ рдирд┐рдпрдо "рдПрдирдПрдЪ рд╕рд╛рдЗрдмреЗрд░рдиреЗрдЯ рд╕реБрд╡рд┐рдзрд╛рдУрдВ рдХреЗ рд╕рд╛рде рдкреНрд░рджрд░реНрд╢рди рдХреЛ рдорд╛рд░рдиреЗ рдХреЗ рдмрд┐рдирд╛ рдбреЗрдЯрд╛рдмреЗрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХреЛрдб рд▓рд┐рдЦрдиреЗ рдХреЗ рд▓рд┐рдП" рдирд┐рдпрдо, рдирд┐рдпрдорд┐рдд рд░реВрдк рд╕реЗ рдХреЛрд░ рд╕рдореАрдХреНрд╖рд╛ рдкрд░
  • рдЕрдиреБрдХреВрд▓рди рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдореЗрдВ рдХреБрдЫ рд╣рдж рддрдХ рд╕реБрд╕реНрдд рдбреЗрдЯрд╛рдмреЗрд╕

рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рд╕рд╛рде рд╕реНрдерд┐рддрд┐ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╕реЛрдЪрддреЗ рд╕рдордп, рдпрд╣ рд╡рд┐рдЪрд╛рд░ рдЙрддреНрдкрдиреНрди рд╣реБрдЖ рдХрд┐ рдХреНрдпрд╛ рдбреЗрдЯрд╛рдмреЗрд╕ рдкрд░ рд▓реЛрдб рд╕реЗ рдЗрди 10% рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЛ рд╣рдЯрд╛рдирд╛ рд╣реИ (рдФрд░ рдПрдХ рд╣реА рд╕рдордп рдореЗрдВ рдбреЗрдЯрд╛рдмреЗрд╕ рдХреЗ рд▓рд┐рдП рдХрдиреЗрдХреНрд╢рди рдЦреЛрд▓реЗрдВ рдЬреЛ рдЙрдирдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рд╣реИрдВ, рдЦреБрд▓реЗ рд▓реЗрдирджреЗрди рдХреЛ рдкрдХрдбрд╝реЗрдВ рдФрд░ рд╣рдорд╛рд░реЗ рд░рд┐рдкреЙрдЬрд┐рдЯрд░реА рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдбреЗрдЯрд╛рдмреЗрд╕ рддрдХ рдкрд╣реБрдВрдЪрдиреЗ рдХреЗ рд▓рд┐рдП рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреЛрдб рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВ)ред рдЗрд╕ рдорд╛рдорд▓реЗ рдореЗрдВ, рдЗрд╕ рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░рдирд╛ рдЖрд╡рд╢реНрдпрдХ рдерд╛:

  • рд╣рдордиреЗ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдирд┐рд╣рд░реНрдиреЗрдЯ рдХреИрд╢ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреА рдХреЛрд╢рд┐рд╢ рдХреА, рд▓реЗрдХрд┐рди рдкрд╛рдпрд╛ рдХрд┐ рдЗрд╕рдХрд╛ рд╡реНрдпрд╡рд╣рд╛рд░ рд╣рдореЗрд╢рд╛ рд╕реНрдкрд╖реНрдЯ рдФрд░ рдЕрдиреБрдорд╛рдирд┐рдд рдирд╣реАрдВ рд╣реИред
  • рдореИрдВ рдкреНрд▓реЗрдЯрдлрд╝реЙрд░реНрдо рдпрд╛ рдмреБрдирд┐рдпрд╛рджреА рдврд╛рдВрдЪреЗ рдореЗрдВ рдЕрдЪреНрдЫреЗ рдХрд╛рд░рдг рдХреЗ рдмрд┐рдирд╛ рдХреБрдЫ рднреА рдореМрд▓рд┐рдХ рд░реВрдк рд╕реЗ рдмрджрд▓рдирд╛ рдирд╣реАрдВ рдЪрд╛рд╣рддрд╛ рдерд╛
  • рдХрд┐рд╕реА рднреА рдЖрд▓рд╕реА рдкреНрд░реЛрдЧреНрд░рд╛рдорд░ рдХреА рддрд░рд╣ рд╣рд╕реНрддрд▓рд┐рдЦрд┐рдд рдХреЛрдб рдХреА рд╕рдВрдЦреНрдпрд╛ рдореЗрдВ рдХрдореА рдЖрдИ рд╣реЛрдЧреА, рдЕрдкрдиреЗ рд╣рд╛рдереЛрдВ рд╕реЗ рдореМрдЬреВрджрд╛ рдХреИрд╢ рдХреЗ рд▓рд┐рдП рд░реИрдкрд░ рдФрд░ рд╕рдмреНрд╕рдХреНрд░рд┐рдкреНрд╢рди рдХреА рд╕реИрдХрдбрд╝реЛрдВ рд▓рд╛рдЗрдиреЗрдВ рд▓рд┐рдЦрдирд╛ред

рдЗрди рдкреБрд░рд╛рдиреЗ рдХреИрд╢ рдореЗрдВ рд╕реЗ рдПрдХ рдХрд╛ рдПрдХ рдЙрджрд╛рд╣рд░рдг рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди
/// <summary> /// Base class for caches, containing rarely changed entities. Updated by subscription to nHibernate session commits. /// </summary> /// <typeparam name="T">Type, used as a base for the cache. Sessions, containing changes to any instance of this class will cause cache refresh.</typeparam> /// <typeparam name="K">Key used for cache search.</typeparam> /// <typeparam name="V">Value, stored in cache.</typeparam> public abstract class RareChangedObjectsCache<T, K, V> : EmptySessionNotificationListener, ITransactionNotificationListener, IRareChangedObjectsCache<K, V> where T : class { [NotNull] private static readonly ILog Log = IikoBizLogManager.GetLogger(typeof(RareChangedObjectsCache<T, K, V>)); [NotNull] protected readonly ReaderWriterLockSlim LockObj = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); [NotNull] protected readonly Dictionary<K, V> Cache = new Dictionary<K, V>(); [NotNull] protected abstract QueryOver<T> GetQuery(); [NotNull] private readonly ConcurrentDictionary<String, bool> changesByTransaction = new ConcurrentDictionary<string, bool>(); private DateTime lastRefreshTime = DateTime.MinValue; [NotNull] public HibernateSessionManager SessionManager { get; set; } /// <summary> /// Interval of automatic data renewal for cases of read access to cache. Also, cache is forcibly refreshed on any commit, changing base entities of this cache. /// </summary> public TimeSpan AutoRefreshInterval { get; set; } public void Reset() { lastRefreshTime = DateTime.MinValue; } protected void ReloadCacheIfNeeded(ISession session = null) { if (SystemTime.Now - lastRefreshTime <= AutoRefreshInterval) return; LockObj.EnterWriteLock(); try { if (SystemTime.Now - lastRefreshTime <= AutoRefreshInterval) return; IList<object> result; if (session == null) result = SessionManager.CallTransacted(s => GetQuery().GetExecutableQueryOver(s).List<object>()); else //TODO At that moment, the transaction may have been closed, so a new transaction opens implicitly result = GetQuery().GetExecutableQueryOver(session).List<object>(); Cache.Clear(); ProcessResult(result); lastRefreshTime = SystemTime.Now; } catch (Exception e) { Log.Error("Exception on cache invalidation: ", e); } finally { LockObj.ExitWriteLock(); } } protected abstract void ProcessResult(IList<object> result); public override void OnSaveOrUpdate(ISession session, object entity, Guid id, object[] currentState, object[] previousState, string[] propertyNames, IType[] types) { if (entity is T) { var transactionName = HibernateSessionManager.GetTransactionName(); if (!string.IsNullOrEmpty(transactionName)) { changesByTransaction.TryAdd(transactionName, true); } } } public override void OnDelete(ISession session, object entity, Guid id) { if (entity is T) { var transactionName = HibernateSessionManager.GetTransactionName(); if (!string.IsNullOrEmpty(transactionName)) { changesByTransaction.TryAdd(transactionName, true); } } } void ITransactionNotificationListener.AfterCommit(ISession session, string transactionName) { bool tmp; if (changesByTransaction.TryRemove(transactionName, out tmp)) Reset(); ReloadCacheIfNeeded(session); } void ITransactionNotificationListener.AfterRollback(ISession session, string transactionName) { } public bool Contains(K key) { ReloadCacheIfNeeded(); LockObj.EnterReadLock(); try { return Cache.ContainsKey(key); } finally { LockObj.ExitReadLock(); } } public virtual V TryGet(K key) { ReloadCacheIfNeeded(); LockObj.EnterReadLock(); try { return Cache.TryGetValue(key, out var value) ? value : default; } finally { LockObj.ExitReadLock(); } } protected TV GetOrAddEntry<TK, TV>(IDictionary<TK, TV> dictionary, TK key) where TV : new() { TV list; if (!dictionary.TryGetValue(key, out list)) dictionary[key] = (list = new TV()); return list; } } /// <summary> /// Caches all guest categories, grouped by organization or network they belong. /// </summary> [UsedImplicitly] public sealed class GuestCategoryCache : RareChangedObjectsCache<GuestCategory, Guid, HashSet<GuestCategory>> { private static readonly QueryOver<GuestCategory> SelectAllEntitiesQuery = QueryOver.Of<GuestCategory>() .Fetch(gc => gc.Organization).Eager .Fetch(gc => gc.Network).Eager; private readonly Dictionary<Guid, string> invertedCache = new Dictionary<Guid, string>(); public bool TryGetNetworkExternalId(Guid id, out string extId) { ReloadCacheIfNeeded(); LockObj.EnterReadLock(); try { return invertedCache.TryGetValue(id, out extId); } finally { LockObj.ExitReadLock(); } } protected override QueryOver<GuestCategory> GetQuery() { return SelectAllEntitiesQuery; } protected override void ProcessResult(IList<object> result) { invertedCache.Clear(); foreach (GuestCategory gc in result) { var orgOrNetworkId = gc.Organization?.Id ?? gc.Network.Id; GetOrAddEntry(Cache, orgOrNetworkId).Add(gc); } } } 


рдЙрд╕ рд╕рдордп рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдереЛрдбрд╝рд╛ рдердХ рдЧрдпрд╛ рдерд╛ред
- рдПрдХ рдирдИ рд╕реБрд╡рд┐рдзрд╛ рдХреЛ рдЕрдиреНрдп рдбреЗрд╡рд▓рдкрд░реНрд╕ рдХреЛ рд╡рд┐рд╢реНрд╡ рд╕реНрддрд░ рдкрд░ рдкреНрд░рднрд╛рд╡рд┐рдд рдирд╣реАрдВ рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП, рдПрдХ рдирдП рджреГрд╖реНрдЯрд┐рдХреЛрдг рдХреЗ рд▓рд┐рдП рдорд╛рдЗрдЧреНрд░реЗрд╢рди рд╕реБрдЪрд╛рд░реВ рд░реВрдк рд╕реЗ рдФрд░ рдзреАрд░реЗ рд╕реЗ рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдПред

рдЙрд╕реА рд╕рдордп, рдореИрдВ рдпрд╣ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ рдерд╛ рдХрд┐ рдХреИрд╢ рддрдВрддреНрд░ рдореЗрдВ рдПрдХ рдирдИ рдЗрдХрд╛рдИ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдиреНрдпреВрдирддрдо рдкреНрд░рдпрд╛рд╕ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛, рдХреЛрдб рдкрдардиреАрдп рдФрд░ рд╕реАрдзрд╛ рдерд╛, рдФрд░ рдЬрдм рдХреИрд╢ рд╕реЗ рдбреЗрдЯрд╛ рдкреБрдирд░реНрдкреНрд░рд╛рдкреНрдд рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдЖрдк рдиреНрдпреВрдирддрдо рд╕реЛрдЪ рд╕рдХрддреЗ рд╣реИрдВ рдХрд┐ рд╕рд╣рд╛рдпрдХ рдХреЛрдб рдХреНрдпрд╛ рд▓рд┐рдЦрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдПред рдкрд┐рдЫрд▓реЗ рджреЛ рдмрд┐рдВрджреБрдУрдВ рдиреЗ рд╡рд┐рдХрд▓реНрдкреЛрдВ рдХреА рд╢реНрд░реЗрдгреА рдореЗрдВ рдмрд╣реБрдд рдХрдЯреМрддреА рдХреАред рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ, рдЖрдкрдХреЛ рдпрд╛ рддреЛ рдкреНрд░рддрд┐рдмрд┐рдВрдм рдФрд░ рдЬреЗрдиреЗрд░рд┐рдХ рдХрдХреНрд╖рд╛рдУрдВ рдХреЗ рд╕рд╛рде рдЕрдВрддрд░ рдХрд░рдирд╛ рд╣реЛрдЧрд╛, рдпрд╛ рдЕрдЪреНрдЫреЗ рдкреБрд░рд╛рдиреЗ рдореЗрдЯрд╛рдкреНрд░реЛрдЧреНрд░рд╛рдорд┐рдВрдЧ рдХреА рдУрд░ рдореБрдбрд╝рдирд╛ рд╣реЛрдЧрд╛ред

рдЪреВрдВрдХрд┐ рдореБрдЦреНрдп рд╡рд┐рдХрд╛рд╕ рдЙрдкрдХрд░рдг рд╡рд┐рдЬрд╝реБрдЕрд▓ рд╕реНрдЯреВрдбрд┐рдпреЛ рд╣реИ, рд▓реЗрдХрд┐рди рдореИрдВ рдЗрд╕ рддрдереНрдп рдкрд░ рдмрд╣реБрдд рдЕрдзрд┐рдХ рдКрд░реНрдЬрд╛ рдЦрд░реНрдЪ рдирд╣реАрдВ рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ рдерд╛ рдХрд┐ рдпрд╣ рдмрд╣реБрдд рдмрдбрд╝рд╛ рдкреНрд░рднрд╛рд╡ рдирд╣реАрдВ рд╣реИ, рдореИрдВрдиреЗ рдирд┐рд░реНрдгрдп рд▓рд┐рдпрд╛ "рдорд╛рдереЗ рдкрд░" рд╕рдмрд╕реЗ рдорд╛рдирдХ рдЯреВрд▓ рдХреЗ рд╕рд╛рде рдФрд░ рдХреЗрд╡рд▓ рд╕рдмрд╕реЗ рдЕрдзрд┐рдХ рдЗрд╕реНрддреЗрдорд╛рд▓ рдХрд┐рдП рдЧрдП рдПрдХ рдЬреЛрдбрд╝реЗ рдкрд░ рдЕрд╡рдзрд╛рд░рдгрд╛ рдХреЗ рд╕рдорд╛рдкреНрдд рд╣реЛрдиреЗ рдХреЗ рдкреНрд░рдорд╛рдг рдХреЗ рд╕реНрддрд░ рдкрд░ред рд╕рдВрд╕реНрдерд╛рдПрдВ - рдЕрджрд╛рд▓рдд рдореЗрдВ рд╕рд╣рдпреЛрдЧрд┐рдпреЛрдВ рдХреЗ рд▓рд┐рдП рдПрдХ рдирд┐рд░реНрдгрдп рдкреНрд░рд╕реНрддреБрдд рдХрд░рддреА рд╣реИрдВред

рдЖрдЧреЗ рдХреБрдЫ рдиреИрддрд┐рдХ рджреБрд╡рд┐рдзрд╛ рдереАред рдПрдХ рд╕реНрд░реЛрдд рдХреЗ рд░реВрдк рдореЗрдВ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЪрд╛рд╣реЗ рдХреБрдЫ рд╡рд░реНрдЧ рд╕рднреА рдЕрд╡рд╕рд░реЛрдВ рдХреЗ рд▓рд┐рдП рд╡рд┐рд╢реЗрд╖рддрд╛рдУрдВ рдХреЗ рд╕рд╛рде рд▓рдЯрдХрд╛рдП рдЧрдП рд╣реЛрдВ (рд╕реНрдЯреЗрд░реЙрдпрдб рдкрд░ рдирд┐рдмрд░реНрдиреЗрдЯ рдХреА рдлрд╝реНрд▓рд┐рдк рдореИрдкрд┐рдВрдЧ рдХреА рд╢реИрд▓реА рдореЗрдВ), рдпрд╛ рдПрдХ рдЕрдЪреНрдЫрд╛ рд╕рд╛рдл XML рд▓рд┐рдЦреЗрдВред рдпрд╣ рдпрд╛рдж рд░рдЦрдирд╛ рдХрд┐ рдЖрд▓рд╕реНрдп рдПрдХ рдкреНрд░реЛрдЧреНрд░рд╛рдорд░ рдХрд╛ рд╕рдмрд╕реЗ рдЕрдЪреНрдЫрд╛ рджреЛрд╕реНрдд рд╣реИ, рдФрд░ рд╡рд┐рд╢реЗрд╖рддрд╛рдУрдВ рдХреЗ рд╕рд╛рде рдХрдХреНрд╖рд╛рдУрдВ рдХрд╛ рд╡рд░реНрдгрди рдереЛрдбрд╝рд╛ рдПрдХреНрд╕рдПрдордПрд▓ рд▓рд┐рдЦрдиреЗ рдХреА рддреБрд▓рдирд╛ рдореЗрдВ рдЕрдзрд┐рдХ рд╕рдордп рд▓реЗрдиреЗ рд╡рд╛рд▓рд╛ рд╣реИ, рдореИрдВрдиреЗ рдмрд╛рдж рдХреЗ рд▓рд┐рдП рдЪреБрдирд╛ред

рд╕рдВрдХреНрд╖реЗрдк рдореЗрдВ, рд╕рдВрд╕реНрдерд╛рдУрдВ рдХреЗ рд╡рд░реНрдгрди рд╕реЗ рдореБрдЭреЗ рдХреНрдпрд╛ рдЪрд╛рд╣рд┐рдП рдерд╛?

  • рдХреИрд╢реНрдб рдЦреЗрддреЛрдВ рдХрд╛ рд╡рд┐рд╡рд░рдг
  • рдЧреБрдгреЛрдВ рдХреЛ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рдЬрд┐рд╕рдХреЗ рджреНрд╡рд╛рд░рд╛ рд╣рдо рдЗрд╕ рдбреЗрдЯрд╛ рд╕реЗ рдирдореВрдиреЗ рдмрдирд╛рдПрдВрдЧреЗ, рдпрджрд┐ рдЖрд╡рд╢реНрдпрдХ рд╣реЛ, рддреЛ рд╕реВрдЪрд┐рдпреЛрдВ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рд░реИрдЦрд┐рдХ рдорд╛рд░реНрдЧ рдХреЛ рд╕рдорд╛рдкреНрдд рдХрд░рдХреЗ рдЗрди рдирдореВрдиреЛрдВ рдХреЛ рдЕрдиреБрдХреВрд▓рд┐рдд рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ (рдпрджрд┐ рдЖрдк рдЕрдиреБрдХреВрд▓рди рдореЗрдВ рдЦреЗрд▓рддреЗ рд╣реИрдВ, рддреЛ рдкреВрд░реНрдг)
  • рд╡рд┐рднрд┐рдиреНрди рдлрд╝реЛрд▓реНрдбрд░реЛрдВ рдореЗрдВ рдХрдХреНрд╖рд╛рдПрдВ рд╡рд┐рддрд░рд┐рдд рдХрд░рдиреЗ рдФрд░ рдХреЛрдб рдХреА рдореМрдЬреВрджрд╛ рдШрдЯрдирд╛рдУрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЗрд╕рдХреА рд╕рдВрдЦреНрдпрд╛ рдХрдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХреЛрдИ рдЕрддрд┐рд░рд┐рдХреНрдд рд╕реБрд╡рд┐рдзрд╛

рд╣рдореЗрдВ рдпрд╣ xml рд╕рдВрд░рдЪрдирд╛ рдорд┐рд▓реА
 <class name="GuestCategory" logicallyDeletable="true" revisioned="true" organizationNetworkBased="true" basedOn="BO.GuestCategory" > <emitBo emitMapping="true"/> <emitRepository dependsOn="guestCategoryCache">IGuestCategoryRepository</emitRepository> <field name="IsActive" type="bool"/> <field name="IsDefaultForNewGuests" type="bool"/> <field name="Name" type="string" notNull="true"/> </class> 


рдФрд░ рдЗрд╕реЗ рдкрд╛рд░реНрд╕ рдХрд░рдиреЗ рдФрд░ рд╡рд┐рд╡рд┐рдз, рд▓реЗрдХрд┐рди рдмрд╣реБрдд рдЬрд░реВрд░реА рд╡рд░реНрдЧреЛрдВ рдХреЛ рдЙрддреНрдкрдиреНрди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдмрдбрд╝рд╛ рдбрд░рд╛рд╡рдирд╛ T4 рдЯреЗрдореНрдкрд▓реЗрдЯ:

  • BO рдХреЗ рд╕рдорд╛рди рдлрд╝реАрд▓реНрдбреНрд╕ рдХреЗ рд╕рд╛рде "рдХреИрд╢реНрдб" рдкреНрд░рдХрд╛рд░ рд▓реЗрдХрд┐рди рд╕рдВрдкрд╛рджрди рдпреЛрдЧреНрдп рдирд╣реАрдВ
  • рдлрд╝рд┐рд▓реНрдЯрд░ рдЪрдпрди рд╡рд┐рдзрд┐рдпреЛрдВ рдХреЗ рд╕рд╛рде рдПрдХ рдкреНрд░рдХрд╛рд░ рдХреЗ рд▓рд┐рдП рдХреИрд╢ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдирд╛
  • DI рдореЗрдВ рд╕реВрдЪрдирд╛ рдФрд░ рдкрдВрдЬреАрдХрд░рдг рдХреЗ рд▓рд┐рдП Nhibernate рддрдВрддреНрд░ рдХреА рд╕рджрд╕реНрдпрддрд╛ рдХреЗ рд▓рд┐рдП рдХреИрд╢ рдХреА рдПрдХ рд░рдЬрд┐рд╕реНрдЯреНрд░реА (рд╣рдорд╛рд░реЗ рдорд╛рдорд▓реЗ рдореЗрдВ рд╕реНрдкреНрд░рд┐рдВрдЧ)
  • рдЗрди рд╕рднреА рдХреЗ рд▓рд┐рдП рдЗрдВрдЯрд░рдлреЗрд╕ рдХреЛ рдЫрд┐рдкрд╛рдиреЗ рдХреЗ рд▓рд┐рдП рдФрд░ рдХреНрд╖рдорддрд╛, рдпрджрд┐ рдЖрд╡рд╢реНрдпрдХ рд╣реЛ, рддреЛ рдЙрддреНрдкрдиреНрди рдХреЛрдб рдХреЛ рд╣рд╕реНрддрд▓рд┐рдЦрд┐рдд рдФрд░ рд╡рд╛рдкрд╕ рдХреЗ рд╕рд╛рде рдмрджрд▓рдиреЗ рдХреЗ рд▓рд┐рдПред
  • рд╢реБрд░реБрдЖрдд рд╕реЗ рд╣реА рд╕реБрдЦрдж рдЕрдирд┐рдпреЛрдЬрд┐рдд рдмреЛрдирд╕ рдХреЗ рд░реВрдк рдореЗрдВ, рдпрджрд┐ рдореБрдЦреНрдп рд╕рдВрд╕реНрдерд╛рдУрдВ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рддреИрдпрд╛рд░ рдерд╛ - рдореИрдВрдиреЗ рднрд╛рдЧреНрдп рдХреЗ рд▓рд┐рдП рдПрдХ рдЖрдзрд╛ рдХрджрдо рдХрд┐рдпрд╛, рдФрд░ рдкрдХреНрд╖ рдореЗрдВ рд╕рд╛рдзрд╛рд░рдг рдмреАрдУ-рдкреНрд░рдХрд╛рд░ рдЙрддреНрдкрдиреНрди рдХрд░рдиреЗ рдФрд░ рдЙрдиреНрд╣реЗрдВ рдореИрдк рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рдХреЛ рдЬреЛрдбрд╝рд╛, рдЬрд┐рд╕рд╕реЗ рд╕рд╣рдпреЛрдЧрд┐рдпреЛрдВ рдХреЛ рдПрдХ рдЖрдзреЗ-рдХрд┐рдХ рдХреЗ рд╕рд╛рде рдирдИ рдХрдХреНрд╖рд╛рдПрдВ рдЬреЛрдбрд╝рдиреЗ рдХрд╛ рдЕрд╡рд╕рд░ рдорд┐рд▓рд╛ред

рддрд╛рд░реНрдХрд┐рдХ рджреГрд╖реНрдЯрд┐рдХреЛрдг рд╕реЗ, рдЯреЗрдореНрдкрд▓реЗрдЯ рдореЗрдВ рджреЛ рднрд╛рдЧ рд╣реЛрддреЗ рд╣реИрдВ: рдореВрд▓ xml рдХреЛ рдХрдХреНрд╖рд╛рдУрдВ рдореЗрдВ рдкрд╛рд░реНрд╕ рдХрд░рдирд╛, рдЬреЛ рд╡рд╛рдВрдЫрд┐рдд рд╡рд░реНрдЧ рд╕рдВрд░рдЪрдирд╛ рдХрд╛ рд╡рд░реНрдгрди рдХрд░рддрд╛ рд╣реИ (рдХрд┐рд╕реА рднреА рдирд┐рд╣рд┐рдд рд╡реНрдпрд╡рд╣рд╛рд░ рдХреЗ рдЬреЛрдЦрд┐рдо рдХреЛ рдХрдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдЗрд╕ рдорд╛рдорд▓реЗ рдореЗрдВ, рдпрд╣ рдЯреИрдЧ рдХреЗ рд╕реНрдкрд╖реНрдЯ рдкрд╛рд░реНрд╕рд┐рдВрдЧ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдХрд░рдиреЗ рдХрд╛ рдирд┐рд░реНрдгрдп рд▓рд┐рдпрд╛ рдЧрдпрд╛ рдерд╛, рдФрд░ рдореИрдкрд┐рдВрдЧ рдХреЗ рдорд╛рдзреНрдпрдо рд╕реЗ рдирд╣реАрдВред рдЬрд┐рдореНрдореЗрджрд╛рд░ рдмрддрд╛рддреЗ рд╣реИрдВред

рд╕рдВрд░рдЪрдирд╛рддреНрдордХ рдХрдХреНрд╖рд╛рдПрдВ
 class DalClass { public string Name { get; } public bool LogicallyDeletable { get; } public string BasedOn { get; } public string DalBOType { get; } public string[] Implements { get; } public string[] Include { get; } public bool CustomConsistencyManager { get; } public List<DalField> Fields { get; } public List<DalField> ExplicitlyDefinedFields { get; } public DalGetBy[] GetBy { get; } public DalGetBy[] GetAllBy { get; } public bool GenerateInterface { get; } public DalEmitBo EmitBo { get; } public DalEmitRepository EmitRepository { get; } public DalClass(XmlElement sourceXml) { Implements = sourceXml.SelectNodes("*[local-name()='implements']") .Cast<XmlElement>() .Select(f => f.InnerText + ",") .ToArray(); Include = sourceXml.SelectNodes("*[local-name()='include']") .Cast<XmlElement>() .Select(f => f.InnerText) .ToArray(); Name = sourceXml.GetAttribute("name"); LogicallyDeletable = sourceXml.HasAttribute("logicallyDeletable"); BasedOn = sourceXml.GetAttribute("basedOn"); DalBOType = sourceXml.HasAttribute("dalBoType") ? sourceXml.GetAttribute("dalBoType") : BasedOn; CustomConsistencyManager = sourceXml.HasAttribute("customConsistencyManager") ? sourceXml.GetAttribute("customConsistencyManager") == "true" : false; Fields = sourceXml.SelectNodes("*[local-name()='field']") .Cast<XmlElement>() .Select(f => new DalField(f)) .ToList(); ExplicitlyDefinedFields = Fields.ToList(); Fields = Fields.OrderBy(f=>f.Name).ToList(); GetBy = sourceXml.SelectNodes("*[local-name()='getBy']") .Cast<XmlElement>() .Select(f => new DalGetBy(f)) .ToArray(); GetAllBy = sourceXml.SelectNodes("*[local-name()='getAllBy']") .Cast<XmlElement>() .Select(f => new DalGetBy(f)) .ToArray(); EmitBo = sourceXml.SelectNodes("*[local-name()='emitBo']") .Cast<XmlElement>() .Select(f => new DalEmitBo(f)) .SingleOrDefault(); EmitRepository = sourceXml.SelectNodes("*[local-name()='emitRepository']") .Cast<XmlElement>() .Select(f => new DalEmitRepository(f, Name)) .SingleOrDefault(); GenerateInterface = true; } public string GetIncludedNamespaces() { return string.Join("/n", Include.Select(i => "using " + i + ";")); } public string GetBoClassDefinition() { return Name + " :\n\t\tBaseEntity,\n\t\t" + (LogicallyDeletable ? "ILogicallyDeletable,\n\t\t" : string.Empty) + (Implements.Any() ? string.Join("\n\t\t", Implements) + "\n\t\t" : string.Empty) + "I" + Name; } public string GetCachedClassDefinition() { return "Cached" + Name + " :\n\t\t" + (LogicallyDeletable ? "Deletable" : string.Empty) + "CachedEntity<" + BasedOn + ">,\n\t\t" + (Implements.Any() ? string.Join("\n\t\t", Implements) + "\n\t\t" : string.Empty) + "I" + Name; } public string TryGetIsDeletedParameter() { if (LogicallyDeletable) return ",bool getDeleted"; else return String.Empty; } public string TryGetIsDeletedFilter() { if (LogicallyDeletable) return @" if (!getDeleted) entities = entities.Where(e => !e.IsDeleted); "; else return String.Empty; } public string GetFilterParameters(DalGetBy getBy) { var filters = new List<FieldDescription>(); foreach (var filter in getBy.Filters) filters.Add(new FieldDescription { TypeName = Fields.Single(f => f.Name == filter.Key).FieldType, Alias = filter.Value }); return string.Join(", ", filters.Select(f => f.TypeName + " " + f.Alias)); } private struct FieldDescription { public string TypeName; public string Alias; } } class DalField { public string Name { get; } public string FieldType { get; } public string Source { get; } public string PropertySource { get; } public bool NotNull { get; } public DalField(string name, string type) { Name = name; FieldType = type; Source = name; } public DalField(XmlElement sourceXml) { Name = sourceXml.GetAttribute("name"); FieldType = sourceXml.GetAttribute("type"); Source = sourceXml.HasAttribute("source") ? sourceXml.GetAttribute("source") : sourceXml.GetAttribute("name"); PropertySource = sourceXml.HasAttribute("propertySource") ? sourceXml.GetAttribute("propertySource") : null; NotNull = sourceXml.HasAttribute("notNull") ? sourceXml.GetAttribute("notNull") == "true" : false; } public string GetConstructorInitValueExpression() { var fieldIsArray = FieldType.EndsWith("[]"); var fieldRealType = FieldType.Replace("[]", ""); if (PropertySource!=null && fieldRealType.StartsWith("I")) fieldRealType = "Cached" + fieldRealType.Substring(1); return UpperInitial(Name) + " = source." + Source + (fieldIsArray ? ".Select(i=>new " + fieldRealType + "(i)).ToArray()" : "") + ";"; } public string GetPropertyDefinitionExpression() { return "public " + GetType() + " " + UpperInitial(Name) + (PropertySource != null ? " => " + PropertySource + ";" : " { get; private set; }"); } public string GetBOPropertyDefinitionExpression() { return "public virtual " + GetBoType() + " " + UpperInitial(Name) + " { get; set; }"; } public string GetInterfacePropertyDefinitionExpression() { return GetNullAttribute() + GetType() + " " + UpperInitial(Name) + " { get; }"; } public string GetType() { var fieldIsArray = FieldType.EndsWith("[]"); if (fieldIsArray) return "IEnumerable<" + FieldType.Replace("[]", "") + ">"; return FieldType; } public string GetBoType() { var fieldIsArray = FieldType.EndsWith("[]"); if (fieldIsArray) return "IList<" + FieldType.Replace("[]", "") + ">"; return FieldType; } private string GetNullAttribute() => TypeCanBeNull() ? NotNull ? "[NotNull] " : "[CanBeNull] " : string.Empty; private static string[] NotNullTypes = { "Guid", "DateTime", "DateTimeOffset", "bool", "int", "long", "short", "ProgramType", "GuestSubscriptionTypes", "SenderType", "ApiClientType" }; public bool TypeCanBeNull() => !NotNullTypes.Contains(FieldType); private string UpperInitial(string name) { return name[0].ToString().ToUpperInvariant() + name.Substring(1); } } class DalGetBy { public bool IsTry { get; } public string Alias { get; } public Dictionary<string, string> Filters { get; } = new Dictionary<string, string>(); public DalGetBy(XmlElement sourceXml) { IsTry = sourceXml.HasAttribute("try"); Alias = sourceXml.GetAttribute("alias"); foreach (XmlElement filterNode in sourceXml.SelectNodes("*[local-name()='field']")) Filters.Add(filterNode.GetAttribute("field"), filterNode.GetAttribute("alias")); } public string GetConditions() { return string.Join(" && ", Filters.Select(f => $"e.{f.Key} == {f.Value}")); return string.Join(" && ", Filters.Select(f => $"e.{f.Key} == {f.Value}")); } } class DalEmitBo { public string Namespace { get; } public bool EmitMapping { get; } private XmlElement sourceXml; public DalEmitBo(XmlElement sourceXml) { Namespace = sourceXml.GetAttribute("ns"); EmitMapping = sourceXml.HasAttribute("emitMapping"); this.sourceXml = sourceXml; } public string GetMapping(DalField field) { bool notNull = !field.TypeCanBeNull() || field.NotNull; var overridenXml = sourceXml.SelectNodes("*[local-name()='column']").Cast<XmlElement>().SingleOrDefault(e => e.GetAttribute("name") == field.Name); var props = overridenXml != null ? new OverridenProperties(overridenXml) : null; switch(field.FieldType) { case "string": return $"<property name=\"{field.Name}\"><column name=\"{field.Name}\" {(notNull ? "not-null=\"true\"" : string.Empty)} {(props?.SqlType !=null ? "sql-type=\"" + props.SqlType+"\"" : string.Empty)}/></property>"; case "bool": return $"<property name=\"{field.Name}\" not-null=\"true\" type=\"boolean\"><column name=\"{field.Name}\" not-null=\"true\" default=\"{props?.DefaultValue??"0"}\" sql-type=\"bit\" /></property>"; case "DateTime": return $"<property name=\"{field.Name}\" not-null=\"true\"/>"; case "DateTime?": return $"<property name=\"{field.Name}\" />"; case "ProgramType": return $"<property name=\"{field.Name}\"><column name=\"{field.Name}\" default=\"{props?.DefaultValue??"0"}\" {(notNull ? "not-null=\"true\"" : string.Empty)}/></property>"; case "Guid?": return $"<property name=\"{field.Name}\" />"; default: throw new ArgumentOutOfRangeException($"Not supported type {field.FieldType}. Edit DAL.tt to add mapping definition."); } } private class OverridenProperties { public string DefaultValue { get; } public string SqlType { get; } public OverridenProperties(XmlElement sourceXml) { DefaultValue = sourceXml.GetAttribute("default"); SqlType = sourceXml.GetAttribute("sql-type"); } } } class DalEmitRepository { public string Interface { get; } public string DependsOn { get; } public DalEmitRepository(XmlElement sourceXml, string className) { Interface = string.IsNullOrEmpty(sourceXml.InnerText) ? "IRepository<"+className+">" : sourceXml.InnerText; DependsOn = sourceXml.GetAttribute("dependsOn"); } public string GetRepositoryClassName() => Interface.Substring(1); } 


рдкрд░рд┐рдгрд╛рдореА рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреЗ рд▓рд┐рдП рдХреЛрдб
 <#@ template debug="false" hostspecific="true" language="C#" #> <#@ assembly name="System.Xml" #> <#@ assembly name="System.Core" #> <#@ assembly name="EnvDTE" #> <#@ import namespace="System.Xml" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Linq" #> <#@ output extension="/" #> <# EnvDTE.DTE dte = (EnvDTE.DTE) ((IServiceProvider) this.Host) .GetService(typeof(EnvDTE.DTE)); XmlDocument doc = new XmlDocument(); doc.Load(System.IO.Path.Combine(dte.ActiveDocument.Path, "DAL.xml")); var classes = doc.SelectNodes("//*[local-name()='class']").Cast<XmlElement>().Select(classXml=>new DalClass(classXml)).ToArray(); //fields foreach(var classNode in classes) { #> /* The file was generated automatically and should not be edited manually. For any changes edit DAL.xml or DAL.tt. */ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using JetBrains.Annotations; <#=classNode.GetIncludedNamespaces()#> namespace Domain.DALV2 { public class <#=classNode.GetCachedClassDefinition()#> { public Cached<#=classNode.Name#>(<#=classNode.DalBOType#> source) : base(source) { <#=string.Join("\n\t\t\t", classNode.Fields.Where(f => f.PropertySource == null).Select(f=>f.GetConstructorInitValueExpression()))#> } <#=string.Join("\n\n\t\t", classNode.Fields.Select(f=>f.GetPropertyDefinitionExpression()))#> } } <#if (classNode.GenerateInterface){#> namespace Domain.DAL { public interface I<#=classNode.Name#> <#if (classNode.LogicallyDeletable){#> :ILogicallyDeletableReadonly <#}#> { Guid Id { get; } <#=string.Join("\n\n\t\t", classNode.Fields.Select(f=>f.GetInterfacePropertyDefinitionExpression()))#> } } <#SaveOutput("Entities//gen//"+classNode.Name+".g.cs");#> <#}#> /* The file was generated automatically and should not be edited manually. For any changes edit DAL.xml or DAL.tt. */ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using Resto.Framework.Common; using JetBrains.Annotations; <#=classNode.GetIncludedNamespaces()#> namespace Domain.DALV2 { public partial class <#=classNode.Name#>DAL : DAL<Cached<#=classNode.Name#>, <#=classNode.BasedOn#>, <#=classNode.DalBOType#>> { internal <#=classNode.Name#>DAL(ISessionManager sessionManager, ICacheConsistencyManager<<#=classNode.BasedOn#>, <#=classNode.DalBOType#>> consistencyManager) : base(sessionManager, Repositories.<#=classNode.Name#>, consistencyManager) { } <#foreach (var getBy in classNode.GetBy){#> <#=getBy.IsTry?"[CanBeNull]":"[NotNull]"#> public Cached<#=classNode.Name#> <#=getBy.IsTry?"Try":""#>GetBy<#=getBy.Alias#>(<#=classNode.GetFilterParameters(getBy)#>) { UpdateCacheIfNeeded(); using (ConsistencyManager.GetReadLock()) { return Cache.Values.Single<#=getBy.IsTry?"OrDefault":""#>(e => <#=getBy.GetConditions()#>); } } <#}#> <#foreach (var fieldNode in classNode.GetAllBy){#> [NotNull] [ItemNotNull] public IList<Cached<#=classNode.Name#>> GetAllBy<#=fieldNode.Alias#>(<#=classNode.GetFilterParameters(fieldNode)#>) { UpdateCacheIfNeeded(); using (ConsistencyManager.GetReadLock()) { return Cache.Values.Where(e => <#=fieldNode.GetConditions()#>).ToList(); } } <#}#> [NotNull] protected override Cached<#=classNode.Name#> Convert(<#=classNode.DalBOType#> source) { return new Cached<#=classNode.Name#>(source); } } } <#SaveOutput("Repositories//gen//"+ classNode.Name+".g.cs");#> <#if(classNode.EmitBo != null){#> <?xml version="1.0" encoding="utf-8"?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Domain" namespace="<#=classNode.EmitBo.Namespace#>"> <class name="<#=classNode.Name#>" dynamic-update="true" dynamic-insert="true"> <id name="Id"> <generator class="assigned" /> </id> <#=string.Join("\n\t\t", classNode.ExplicitlyDefinedFields.Select(f=>classNode.EmitBo.GetMapping(f)))#> <#if (classNode.LogicallyDeletable){#> <property name="IsDeleted" not-null="true" type="boolean"> <column name="IsDeleted" not-null="true" default="0" /> </property> <property name="WhenDeleted" type="DateTime" not-null="false"/> <#}#> </class> </hibernate-mapping> <#SaveOutput("..\\..\\DAL.Hibernate\\Mapping\\gen\\"+classNode.Name+".hbm.xml");#> /* The file was generated automatically and should not be edited manually. For any changes edit DAL.xml or DAL.tt. */ using System; using Common.Domain.BO.DB; using Domain.DAL; using Domain.DALV2; using JetBrains.Annotations; <#=classNode.GetIncludedNamespaces()#> namespace <#=classNode.EmitBo.Namespace#> { public partial class <#=classNode.GetBoClassDefinition()#> { [UsedImplicitly] protected <#=classNode.Name#>(){} <#=string.Join("\n\n\t\t", classNode.ExplicitlyDefinedFields.Select(f=>f.GetBOPropertyDefinitionExpression()))#> <#if (classNode.LogicallyDeletable){#> public virtual bool IsDeleted { get; set; } public virtual DateTime? WhenDeleted { get; set; } <#}#> } } <# SaveOutput("..//BO//gen//"+ classNode.Name+".g.cs"); } } #> <!-- The file was generated automatically and should not be edited manually. For any changes edit DAL.xml or DAL.tt. --> <objects xmlns="http://www.springframework.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:db="http://www.springframework.net/database" xsi:schemaLocation="http://www.springframework.net http://www.springframework.net/xsd/spring-objects.xsd"> <# foreach(var classNode in classes.Where(c => !c.CustomConsistencyManager)) { #> <object id="<#=LowerInitial(classNode.Name)#>CacheManager" type="Domain.DALV2.TransactionSubscribedManager<<#=classNode.BasedOn#>, <#=classNode.DalBOType#>>, Domain" singleton="true"> </object> <#}#> <object id="dalGeneratedListeners" type="System.Collections.Generic.List<Common.Hibernate.DAL.ISessionNotificationListener>, mscorlib"> <constructor-arg> <list element-type="Common.Hibernate.DAL.ISessionNotificationListener, Common.Hibernate"> <# foreach(var classNode in classes) { #> <ref object="<#=LowerInitial(classNode.Name)#>CacheManager"/> <#}#> </list> </constructor-arg> </object> </objects> <#SaveOutput("..//Config//DALSpringDefinitions.g.xml");#> /* The file was generated automatically and should not be edited manually. For any changes edit DAL.xml or DAL.tt. */ using Common; using Common.Domain.DAL; using Domain.BO; using Domain.DALV2; using Spring.Context.Support; using JetBrains.Annotations; <#=string.Join("\n", classes.Select(c=>c.GetIncludedNamespaces()).Where(s=>!string.IsNullOrEmpty(s)))#> namespace Domain.DAL { public partial class DALs { private static readonly SafeLazy<DALs> instance = new SafeLazy<DALs>(() => new DALs()); private DALs() { var sessionManager = (ISessionManager)ContextRegistry.GetContext().GetObject("sessionManager"); <# foreach(var classNode in classes){ #> this.<#=LowerInitial(classNode.Name)#> = new <#=classNode.Name#>DAL(sessionManager, (ICacheConsistencyManager<<#=classNode.BasedOn#>, <#=classNode.DalBOType#>>)ContextRegistry.GetContext().GetObject("<#=LowerInitial(classNode.Name)#>CacheManager")); <# } #> } <# foreach(var classNode in classes) { #> [NotNull] private readonly <#=classNode.Name#>DAL <#=LowerInitial(classNode.Name)#>; [NotNull] public static <#=classNode.Name#>DAL <#=classNode.Name#> => instance.Value.<#=LowerInitial(classNode.Name)#>; <#}#> public static void ResetAll() => instance.Value.ResetAllImpl(); private void ResetAllImpl() { <#foreach(var classNode in classes){#> this.<#=LowerInitial(classNode.Name)#>.Reset(); <#}#> } } } <#SaveOutput("DALs.g.cs");#> /* The file was generated automatically and should not be edited manually. For any changes edit DAL.xml or DAL.tt. */ using JetBrains.Annotations; namespace Domain.DAL { public partial interface IRepositoryFactory { <#foreach(var classNode in classes.Where(c => c.EmitRepository != null)){#> [NotNull] <#=classNode.EmitRepository.Interface#> <#=classNode.Name#> { get; } <#}#> } } <#SaveOutput("IRepositoryFactory.g.cs");#> /* The file was generated automatically and should not be edited manually. For any changes edit DAL.xml or DAL.tt. */ using JetBrains.Annotations; namespace Domain.DAL { public static partial class Repositories { <#foreach(var classNode in classes.Where(c => c.EmitRepository != null)){#> [NotNull] public static <#=classNode.EmitRepository.Interface#> <#=classNode.Name#> => Instance.Value.<#=classNode.Name#>; <#}#> } } <#SaveOutput("Repositories.g.cs");#> /* The file was generated automatically and should not be edited manually. For any changes edit DAL.xml or DAL.tt. */ using Common.Domain.DAL; using Common.Hibernate.DAL; using DAL.Hibernate.Cache.CachingProviders; using Domain.BO; using Domain.DAL; using Domain.DAL.Cache; using Domain.SaveManager; using System; using System.Collections.Generic; using JetBrains.Annotations; using Engine.Main; <#=string.Join("\n", classes.Select(c=>c.GetIncludedNamespaces()).Where(s=>!string.IsNullOrEmpty(s)))#> namespace DAL.Hibernate.DAL { public partial class RepositoryFactory : IRepositoryFactory { public void Init([NotNull] IOrganizationsByNetworkCache organizationsByNetworkCache, [NotNull] INetworkCache networkCache, [NotNull] ITransactionSaveManager transactionSaveManager, [NotNull] ILoginEntryDataProvider loginEntryDataProvider, ListenerNotifier listenerNotifier) { <#foreach(var classNode in classes.Where(c => c.EmitRepository != null)){#> this.<#=classNode.Name#> = new <#=classNode.EmitRepository.GetRepositoryClassName()#>(<#=classNode.EmitRepository.DependsOn#>); <#}#> } <#foreach(var classNode in classes.Where(c => c.EmitRepository != null)){#> [NotNull] public <#=classNode.EmitRepository.Interface#> <#=classNode.Name#> { get; private set; } <#}#> } } <#SaveOutput("..\\..\\DAL.Hibernate\\DAL\\RepositoryFactory.g.cs");#> <#+ private string LowerInitial(string name) { return name[0].ToString().ToLowerInvariant() + name.Substring(1); } private string UpperInitial(string name) { return name[0].ToString().ToUpperInvariant() + name.Substring(1); } void SaveOutput(string outputFileName) { string templateDirectory = Path.GetDirectoryName(Host.TemplateFile); string outputFilePath = Path.Combine(templateDirectory, outputFileName); File.WriteAllText(outputFilePath, this.GenerationEnvironment.ToString()); this.GenerationEnvironment.Remove(0, this.GenerationEnvironment.Length); } #> 


рдФрд░ рдЬрд╣рд╛рдВ рдбреЗрдЯрд╛рдмреЗрд╕ рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕рджрд╕реНрдпрддрд╛ рд▓реЗрдиреЗ рдХреЗ рд▓рд┐рдП рдореБрд╢реНрдХрд┐рд▓ рдЬреЗрдиреЗрд░рд┐рдХ рдХреЛрдб рдХреА рдПрдХ рдирд┐рд╢реНрдЪрд┐рдд рд░рд╛рд╢рд┐ рдХреЗ рдмрд┐рдирд╛, рдорд╛рдВрдЧ рдкрд░ рдХреИрд╢ рдЕрдкрдбреЗрдЯ рдХрд░реЗрдВ, рдФрд░ рдЬреАрд╡рди рдХреЗ рдЕрдиреНрдп рд╕реБрдЦред рдпрд╣рд╛рдВ рдЬреЗрдиреЗрд░рд┐рдХ рд╕рднреА рдЙрдкрдпреЛрдЧ рдХреЗ рдорд╛рдорд▓реЛрдВ рдХреЛ рдмрдВрдж рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдкрд░реНрдпрд╛рдкреНрдд рд░реВрдк рд╕реЗ рдирд┐рдХрд▓рд╛, рдЬреЛ рдкрд╣рд▓реЗ рд╕реЗ рдЙрдкрдпреЛрдЧ рдХрд┐рдП рдЧрдП рд▓реЛрдЧреЛрдВ рдХреЗ рд╕рд╛рде рдорд┐рд▓рдХрд░ рд╣реИ

рдбреЗрдЯрд╛ рддрдХ рдкрд╣реБрдВрдЪрдиреЗ рдХрд╛ рд╕рд╛рдорд╛рдиреНрдп рдХреЛрдб
  public abstract class DAL<T, TBase, TCachedBo> where T : CachedEntity<TBase> where TBase : class, IEntity where TCachedBo : class, IIdEntity<Guid> { [NotNull] private readonly ILog log; [NotNull] protected readonly ISessionManager SessionManager; [NotNull] protected readonly IReadonlyRepository<TBase> BaseRepository; [NotNull] protected readonly ICacheConsistencyManager<TBase, TCachedBo> ConsistencyManager; [NotNull] protected Dictionary<Guid, T> Cache = new Dictionary<Guid, T>(); protected DAL([NotNull] ISessionManager sessionManager, [NotNull] IReadonlyRepository<TBase> baseRepository, [NotNull] ICacheConsistencyManager<TBase, TCachedBo> consistencyManager) { this.SessionManager = sessionManager; this.BaseRepository = baseRepository; this.ConsistencyManager = consistencyManager; log = LogManager.GetLogger(GetType()); } [CanBeNull] public T TryGetById(Guid id) { UpdateCacheIfNeeded(); using (ConsistencyManager.GetReadLock()) { return Cache.GetOrDefault(id); } } [NotNull] public T GetById(Guid id) { UpdateCacheIfNeeded(); using (ConsistencyManager.GetReadLock()) { return ValidateEntityFound(Cache.GetOrDefault(id), "{0} with id {1} not found", null, typeof(T), id); } } [NotNull] public TBase GetEntity([NotNull] ISession session, Guid id) { return BaseRepository.GetById(session, id); } [NotNull] public TBase GetEntity([NotNull] ISession session, [NotNull] T e) { return BaseRepository.GetById(session, e.Id); } [NotNull] public HashSet<T> GetByIds([NotNull] HashSet<Guid> ids) { UpdateCacheIfNeeded(); using (ConsistencyManager.GetReadLock()) { return ids .Select(id => Cache.GetOrDefault(id)) .ToHashSet(); } } public void Reset() { ConsistencyManager.Reset(); } protected abstract T Convert(TCachedBo source); protected void UpdateCacheIfNeeded() { if (!ConsistencyManager.UpdateRequired) return; log.Debug($"{typeof(T).Name} DAL: Update required, getting writeLock"); using (var updateScope = ConsistencyManager.GetWriteLock()) { if (updateScope.UpdateRequired ?? ConsistencyManager.UpdateRequired) SessionManager.RunTransacted(session => { try { var updatedEntities = updateScope.GetUpdatedEntities(BaseRepository, session); foreach (var updatedEntity in updatedEntities) if (updatedEntity.Value != null) Cache[updatedEntity.Key] = Convert(updatedEntity.Value); else Cache.Remove(updatedEntity.Key); Reindex(); } catch (Exception) { ConsistencyManager.Reset(); throw; } }); } } /// <summary> /// Reevaluate specific indexes, used for search in cached entities. Called after cache has been updated. /// </summary> protected virtual void Reindex() { } [NotNull] protected T1 ValidateEntityFound<T1>([CanBeNull] T1 entity, [NotNull] string errorMessage, [NotNull] string frontMessage, [NotNull] params object[] p) { if (entity == null) throw new DataAccessException(Util.GetMessage(errorMessage, p), Util.GetMessage(frontMessage, p)); return entity; } } 



рджрд┐рд▓рдЪрд╕реНрдк рд╣реИ, рдореЗрд░реА рд░рд╛рдп рдореЗрдВ, рдпрд╣ рдПрдХ рдРрд╕рд╛ рд╡рд░реНрдЧ рдирд┐рдХрд▓рд╛ рдЬреЛ рдХреИрд╢ рдХреА рдирд┐рд░рдВрддрд░рддрд╛ рдкрд░ рдирдЬрд╝рд░ рд░рдЦрддрд╛ рд╣реИ рдФрд░ рдЗрд╕рдХреЗ рдЕрдкрдбреЗрдЯ рдХрд╛ рдкреНрд░рдмрдВрдзрди рдХрд░рддрд╛ рд╣реИ, рдореБрдЦреНрдп рд╕реНрдерд╛рди рдЬрд╣рд╛рдВ рдореБрдЭреЗ рддрд╛рд▓реЗ рдХреЛ рд▓рд╛рдЧреВ рдХрд░рдиреЗ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╕реЛрдЪрдирд╛ рдерд╛ рддрд╛рдХрд┐ рд╕рдм рдХреБрдЫ рдЗрд╖реНрдЯрддрдо рдФрд░ рдиреНрдпреВрдирддрдо рд░реВрдк рд╕реЗ рдЕрд╡рд░реБрджреНрдз рд╣реЛ, рд▓реЗрдХрд┐рди рд╕рдВрд░рдХреНрд╖рд┐рдд рдерд╛

рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди
  [UsedImplicitly] public class TransactionSubscribedManager<TBase, TCachedBo> : EmptySessionNotificationListener, ICacheConsistencyManager<TBase, TCachedBo>, ITransactionNotificationListener where TBase : class, IEntity where TCachedBo : class, IIdEntity<Guid> { public bool UpdateRequired => needFullUpdate || !entitiesToUpdate.IsEmpty; [NotNull] protected readonly ReaderWriterLockSlim LockObj = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); [NotNull] private readonly ConcurrentDictionary<string, ConcurrentBag<Guid>> changesByTransaction = new ConcurrentDictionary<string, ConcurrentBag<Guid>>(); [NotNull] protected ConcurrentBag<Guid> entitiesToUpdate = new ConcurrentBag<Guid>(); protected bool needFullUpdate = true; public virtual CacheWriteLockScope<TBase, TCachedBo> GetWriteLock() { try { LockObj.EnterWriteLock(); if (needFullUpdate) { needFullUpdate = false; return new CacheWriteLockScope<TBase, TCachedBo>(LockObj); } return new IdBasedCacheWriteLockScope<TBase, TCachedBo>(LockObj, ChangedEntitiesIdsProvider); } catch (Exception) { LockObj.ExitWriteLock(); throw; } } protected ICollection<Guid> ChangedEntitiesIdsProvider() { var ids = new List<Guid>(); var newBag = new ConcurrentBag<Guid>(); var oldBag = Interlocked.Exchange(ref entitiesToUpdate, newBag); while (oldBag.TryTake(out var id)) ids.Add(id); return ids; } public CacheReadLockScope GetReadLock() { return new CacheReadLockScope(LockObj); } public override void OnSaveOrUpdate(ISession session, object entity, Guid id, object[] currentState, object[] previousState, string[] propertyNames, IType[] types) { if (entity is TBase) { var transactionName = HibernateSessionManager.GetTransactionName(); if (!string.IsNullOrEmpty(transactionName)) { if (changesByTransaction.TryAdd(transactionName, new ConcurrentBag<Guid>() { id })) return; changesByTransaction.TryGetValue(transactionName, out var transactionChangedEntities); // ReSharper disable once PossibleNullReferenceException // R# does not have attributes telling that value in dictionary is not null. But we know it. transactionChangedEntities.Add(id); } } } public override void OnDelete(ISession session, object entity, Guid id) { if (entity is TBase) { var transactionName = HibernateSessionManager.GetTransactionName(); if (string.IsNullOrEmpty(transactionName)) return; if (changesByTransaction.TryAdd(transactionName, new ConcurrentBag<Guid>() { id })) return; changesByTransaction.TryGetValue(transactionName, out var transactionChangedEntities); // ReSharper disable once PossibleNullReferenceException // R# does not have attributes telling that value in dictionary is not null. But we know it. transactionChangedEntities.Add(id); } } public void Reset() { needFullUpdate = true; } void ITransactionNotificationListener.AfterCommit(ISession session, string transactionName) { if (changesByTransaction.TryRemove(transactionName, out var transactionChangedEntities) && !transactionChangedEntities.IsEmpty) while (transactionChangedEntities.TryTake(out var id)) entitiesToUpdate.Add(id); } void ITransactionNotificationListener.AfterRollback(ISession session, string transactionName) { changesByTransaction.TryRemove(transactionName, out _); } } 

  public class IdBasedCacheWriteLockScope<TBase, TCachedBo> : CacheWriteLockScope<TBase, TCachedBo> where TBase : class, IEntity where TCachedBo : class, IIdEntity<Guid> { [NotNull] private readonly ICollection<Guid> changedEntitiesIds; public override bool? UpdateRequired => changedEntitiesIds.Any(); public IdBasedCacheWriteLockScope([NotNull] ReaderWriterLockSlim lockObj, [NotNull] Func<ICollection<Guid>> changedEntitiesIdsProvider) : base(lockObj) { changedEntitiesIds = changedEntitiesIdsProvider() ?? throw new InvalidOperationException(nameof(changedEntitiesIdsProvider)); } public override IDictionary<Guid, TCachedBo> GetUpdatedEntities(IReadonlyRepository<TBase> repository, ISession session) { var entities = repository.GetByIds(session, changedEntitiesIds, true).ToDictionary(e => e.Id, be => be as TCachedBo); foreach (var id in changedEntitiesIds.Where(i => !entities.ContainsKey(i))) entities.Add(id, null); return entities; } } 


рджрд┐рд▓рдЪрд╕реНрдк рдЬрд╛рдо рдЬреЛ рд╡рд┐рдХрд╛рд╕ рдФрд░ рд▓рдбрд╝рд╛рдИ рдореЗрдВ рд╡рд┐рд╕реНрддрд╛рд░ рдХреЗ рджреМрд░рд╛рди рд╕рд╛рдордиреЗ рдЖрдП рдереЗ:

  • рдпрд╣ рд╕рд┐рд░реНрдл рдЗрддрдирд╛ рд╣реИ рдХрд┐ рдЖрдк рд╕рдВрдмрдВрдзрд┐рдд рд╕рдВрд╕реНрдерд╛рдУрдВ рдХреЛ рдЙрдирдХреЗ рдмреАрдЪ рд╕реАрдзреЗ рд▓рд┐рдВрдХ рдХреЗ рд╕рд╛рде рдЦреАрдВрдЪ рдФрд░ рдХреИрд╢ рдирд╣реАрдВ рдХрд░ рд╕рдХрддреЗред рдЕрдиреНрдпрдерд╛, рдЬрдм рд╣рдо рдХрд┐рд╕реА рдПрдХ рд╕рдВрд╕реНрдерд╛ рдХреЛ рд╕рдВрд╢реЛрдзрд┐рдд рдХрд░рддреЗ рд╣реИрдВ, рддреЛ рдкреБрд░рд╛рдиреА рдкреНрд░рддрд┐рд▓рд┐рдкрд┐ рдЙрди рд╡рд╕реНрддреБрдУрдВ рдореЗрдВ рдмрдиреА рд░рд╣рддреА рд╣реИ рдЬреЛ рдЗрд╕реЗ рд╕рдВрджрд░реНрднрд┐рдд рдХрд░рддреА рд╣реИрдВред рдЗрд╕ рддрд░рд╣ рдХреЗ рдХрдиреЗрдХреНрд╢рдиреЛрдВ рдХреЛ рдЕрдорд╛рдиреНрдп рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдореБрд╢реНрдХрд┐рд▓ рддрд░реНрдХ рдмрдирд╛рдиреЗ рдХреЗ рдмрдЬрд╛рдп, рд╣рдордиреЗ рдмрд╕ рдпрд╣ рдирд┐рд░реНрдгрдп рд▓рд┐рдпрд╛ рдХрд┐ рд╕рдВрдкрддреНрддрд┐ рддрдХ рдкрд╣реБрдВрдЪ рд╣рдореЗрд╢рд╛ рдХреИрд╢ рд╕реЗ рдирд╡реАрдирддрдо рдЬрд╛рдирдХрд╛рд░реА рдкреНрд░рд╛рдкреНрдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП
  • , , ( , , , ). ,
  • ┬л┬╗ ( ) , ,

Source: https://habr.com/ru/post/hi453382/


All Articles