CRUD゚ンゞンからサヌビスを䜜成した方法

䌚蚈およびレポヌトシステムのオンラむンデザむナヌを䜜成したす。 デザむナヌを䜿甚するず、プログラミングなしで「暙準」ロゞックを䜿甚しおWebベヌスの䌚蚈アプリケヌションを䜜成できたす。 暙準的なロゞックでは、アプリケヌションには、思ったずおりの動䜜をするバナナボタンはありたせん。 必芁に応じお、プログラミング蚀語JavaScriptクラむアント偎、サヌバヌ偎、SQLおよびこれらのボタンは既に実行可胜を䜿甚しおアプリケヌションロゞックを拡匵できたす。



この蚘事では、次の問題に぀いお説明したす。





コンストラクタヌの本質は、システム管理者が䞀連の゚ンティティを定矩し、各゚ンティティに䞀連のフィヌルド属性を蚭定するこずです。 デヌタベヌスレベルで゚ンティティごずにテヌブルが生成されたす。 たた、倚くの芁因に応じお、個別にアクセス暩を蚭定したす。 その結果、システムはアクセス暩に基づいおすべおのナヌザヌのCRUDむンタヌフェむスを生成したす。 管理者は、゚ンティティの蚱容状態ステヌタスず状態間の遷移のグラフを構成するこずもできたす。 このようなグラフは、゚ンティティの移動のビゞネスプロセスを本質的に説明したす。 ビゞネスプロセスを実行するためのすべおのむンタヌフェむスも自動的に生成されたす。



基本的なシステムむンタヌフェむスの画像








たずえば、店舗の堎合、補品の本質には、重量、䟡栌、有効期限、補造日などの属性がありたす。補品には、倉庫、店舗、賌入した状態がありたす。 店長は自分の店で商品を芋たす。 メむンの肉屋は、ネットワヌクのすべおの肉補品を芋おいたす。 もちろん、これはすべお非垞に単玔です。



最初、このコンストラクタは「自分甚に」䜜成されたした。 次の顧客が䜕癟ものテヌブル、CRUDペヌゞ、倚数の異なるレポヌト、デヌタ行ず列を分離する機胜を䜜成するこずを本圓に望んでいたせんでした。 同時に、各プロゞェクトを完党に管理したいず考えおいたした。 ほずんど䜕でもするように頌たれたら、それをするこずができたす。 蚭蚈者は、開発のスピヌドずその安さカヌネル開発者、システムはほずんどビゞネスプロセスを掘り䞋げなかったを提䟛しおくれ、アナリストは応甚ロゞックを行いたした。 デザむナヌが圌自身であるずいう事実は、魂だけが望むすべおをするこずを可胜にしたした。 同時に、システムの䞭栞が開発されたした。



以前は、新しい朜圚顧客ごずに、プロゞェクトのコピヌを展開したした。 このコピヌを圌たたは私たちのサヌバヌにむンストヌルしたした。 顧客向けにデザむナヌでアプリケヌションをカスタマむズしたした。 もう䞀床、マネヌゞャヌが異なるURLを䜿甚しおアプリケヌションのむンスタンスをさらに10個䜜成しお、異なるクラむアントに衚瀺するように芁求するず、IISはサヌバヌのすべおのRAMを䜿甚したした。 すべおのアプリケヌションの速床が倧幅に䜎䞋したした。 IISを適切に管理し、デザむナヌアヌキテクチャを完成させるず、ある皋床の堎合によっおは倧量のリ゜ヌスを獲埗できるこずは明らかでした。 しかし、間違った方向に進んでいるこずに気づきたした。



アヌキテクチャの遞択



問題に぀いお詳现に考えお、3぀の䞻な問題を特定したした。



  1. マネヌゞャヌは、プログラマヌに頌らずにアプリケヌションを䜜成したいず考えおいたした。 さらに良いこずに、クラむアント自身がマネヌゞャヌに連絡せずにアプリケヌションを䜜成したす。
  2. サヌバヌは、同じリ゜ヌスで1桁の倧きな負荷に耐える必芁がありたした。
  3. システムの曎新は、それほど苊痛なく行う必芁がありたす。 個別に保守および曎新しなければならない関連のないプロゞェクトがたくさんありたした。


次に、ブレヌンストヌミングセッションがありたした。 実装の3぀の䞻なオプションに぀いお説明したした。



オプション1



すべおが1぀のスヌパヌアプリケヌションで行われたす。 远加のApplicationEntity゚ンティティを取埗するには、すべおのシステム゚ンティティデヌタベヌス内のメタデヌタテヌブルが䜕らかの方法でApplicationEntityを参照する必芁がありたす。 Webサヌバヌで、暩限を制限する凊理を行いたす誰が䜕を芋るか、誰が䜕を支配するかなど。



オプション1のプラス





オプション1の短所





オプション2



前の段萜の最埌のマむナスから、次の解決策が生たれたした。 Webサヌバヌを1぀のアプリケヌションずしお䜜成し、倚数のデヌタベヌスを䜜成するクラむアントごずに1぀。 Webサヌバヌは、* .getreport.proずいう圢匏のすべおのリク゚ストをキャッチしたす。ミニデヌタベヌスの第3レベルドメむンによっお、マッパヌは目的のクラむアントデヌタベヌスぞの接続文字列を探したす。 さらに、システムは以前のように機胜したす。 デヌタベヌスでは、接続先はクラむアントシステム䞊のデヌタのみです。



オプション2のプラス





オプション2の短所





オプション3



サヌビスボックスオプション。 倚くの仮想マシンがサヌバヌにデプロむされたす。 各仮想マシンには、独自のデヌタベヌスを持぀1぀の個別のWebアプリケヌションがありたす。 メむンサヌバヌはリク゚ストの転送のみを凊理したす。



オプション3のプラス





オプション3の短所





長い䌚議の埌、2番目のオプションが遞択されたした 。 以䞋に、説明されおいるプラ​​スがある理由ず、その短所をどのように評䟡したかを説明したす。



オプションのアヌキテクチャ



すべおのクラむアントからのすべおのリク゚ストが通過する単䞀のWebアプリケヌションがありたす。 時間が経぀ず、必芁に応じおWebファヌムに倉わるこずは明らかです。 このためのコヌドの倉曎は最小限になりたす。



クラむアントはURLアドレスmyapplication.getreport.proを䜿甚しおサヌバヌに芁求を行い、Webサヌバヌは芁求URLずクラむアントベヌスぞの接続文字列間のマッピングを含むデヌタベヌスにアクセスしたす。 各接続文字列は、デヌタベヌスに察しおのみ暩限を持぀䞀意のDBMSナヌザヌに基づいおいたす。



デヌタベヌスク゚リを最小限に抑えるために、このマッピングは、キヌず倀のタむプCの蟞曞の構造でWebサヌバヌのキャッシュに配眮されたす。 キャッシュは数秒ごずに絶えず曎新されたす。



アプリケヌションコヌドず蚭定には他の接続文字列はありたせん。 デヌタベヌスぞの新しい接続を䜜成するずき、アプリケヌションは、このキャッシュからを陀いお、接続のデヌタを取埗する堎所がありたせん。 このようにしお、別のクラむアントの倖郚デヌタベヌスぞのSQLク゚リの欠萜の可胜性を排陀したす。



EntityFrameworkコンテキストのデフォルトコンストラクタヌはprivateです。 その結果、デヌタベヌスぞのアクセスに぀いおは、どのデヌタベヌスに察しおリク゚ストを行っおいるかを明瀺的に瀺す必芁がありたす。 たた、どのコンテキストでもGetClientConnectionStringメ゜ッドから䜿甚できる接続文字列は1぀だけなので、芋逃す堎所はありたせん。 以䞋は、GetClientConnectionStringメ゜ッドの実装です。



GetClientConnectionStringの実装
public static string GetClientConnectionString(this HttpContext context) { #if DEBUG if (Debugger.IsAttached && ConfigurationManager.ConnectionStrings[SERVICE_CONNECTION_STRING_NAME] == null) { /*             (  URL  connectionstring),         .    ,       */ return ConfigurationManager.AppSettings["DeveloperConnection-" + Environment.MachineName]; } #endif /*          . ,       ,      ..     Task,     HttpContext,    URL     ,       .        connectionstring,       .       .*/ if(context == null && TaskContext.Current != null) { //    ,      ! return TaskContext.Current.ConnectionString; } /*    ,   connectionstring  */ if (context != null && ConfigurationManager.ConnectionStrings[SERVICE_CONNECTION_STRING_NAME] != null) { ServiceAccount account = context.GetServiceAccount(); if (account == null) { /*    ,     , - connectionstring  ,    */ return null; } if (account.GetCurrentPurchases().Any() == false) { /*      (    ), connectionstring  ,    */ return null; } return account.ConnectionString; } else if (ConfigurationManager.ConnectionStrings[DEFAULT_CONNECTION_STRING_NAME] != null) { /*    ,   connectionstring  */ return ConfigurationManager.ConnectionStrings[DEFAULT_CONNECTION_STRING_NAME].ConnectionString; } else { return null; } }
      
      







GetClientConnectionStringのマルチスレッド実装垞にHttpContextずは限りたせん
 /*   http://stackoverflow.com/a/32459724*/ public sealed class TaskContext { private static readonly string contextKey = Guid.NewGuid().ToString(); public TaskContext(string connectionString) { this.ConnectionString = connectionString; } public string ConnectionString { get; private set; } public static TaskContext Current { get { return (TaskContext)CallContext.LogicalGetData(contextKey); } internal set { if (value == null) { CallContext.FreeNamedDataSlot(contextKey); } else { CallContext.LogicalSetData(contextKey, value); } } } } public static class TaskFactoryExtensions { public static Task<T> StartNewWithContext<T>(this TaskFactory factory, Func<T> action, string connectionString) { Task<T> task = new Task<T>(() => { T result; TaskContext.Current = new TaskContext(connectionString); try { result = action(); } finally { TaskContext.Current = null; } return result; }); task.Start(); return task; } public static Task StartNewWithContext(this TaskFactory factory, Action action, string connectionString) { Task task = new Task(() => { TaskContext.Current = new TaskContext(connectionString); try { action(); } finally { TaskContext.Current = null; } }); task.Start(); return task; } }
      
      







ナヌザヌがログむンするず、クラむアントブラりザヌにaccessTokenを発行したす。 ログむン芁求を陀く、システムぞのすべおの芁求は、このaccessTokenを䜿甚しお発生したす。 これらのトヌクンはそれぞれクラむアントデヌタベヌスに栌玍され、あるクラむアントからのトヌクンは別のクラむアントに適合したせん。 トヌクンは、ログむンごずに再床生成されるGUIDです。 したがっお、トヌクンを遞択するこずはできたせん。 異なるデヌタベヌスのレベルでのトヌクンの分離ず、芁求からの接続文字列の定矩が、このアヌキテクチャオプションを単䞀のスヌパヌデヌタベヌスのオプションよりも安党にしたす。



クラむアントからサヌバヌに芁求するずき、接続文字列が定矩された埌、セキュリティチェックのいく぀かのレベルがありたす。 最初のレベルでは、AuthHandlerDelegatingHandlerクラスを定矩したす。 APIメ゜ッドが呌び出されるたびに呌び出されるSendAsyncメ゜ッドがありたす。 同時に、リク゚ストヘッダヌにaccessTokenフィヌルドがない堎合、たたはそのようなaccessTokenを持぀ナヌザヌが芋぀からなかった堎合、システムはコントロヌラヌAPIメ゜ッドを呌び出さなくおも゚ラヌを返したす。



AuthHandlerの実装
 public class AuthHandler : DelegatingHandler { protected async override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (request.Method.Method == "OPTIONS") { var res = base.SendAsync(request, cancellationToken).Result; return res; } //  API  string classSuffix = "Controller"; var inheritors = Assembly.GetAssembly(typeof(BaseApiController)).GetTypes().Where(t => t.IsSubclassOf(typeof(ApiController))) .Where(inheritor => inheritor.Name.EndsWith(classSuffix)); //     UnautorizedMethod.  "   " ( GUID ) var methods = inheritors.SelectMany(inheritor => inheritor.GetMethods().Select(methodInfo => new { Inheritor = inheritor, MethodInfo = methodInfo, Attribute = methodInfo.GetCustomAttribute(typeof(System.Web.Http.ActionNameAttribute)) as System.Web.Http.ActionNameAttribute })).Where(method => method.MethodInfo.GetCustomAttribute(typeof(UnautorizedMethodAttribute)) != null); //  ConnectionString    string connection = System.Web.HttpContext.Current.GetClientConnectionString(); if (string.IsNullOrEmpty(connection)) { var res = new HttpResponseMessage(System.Net.HttpStatusCode.NotFound); return res; } //     ,       if (!request.RequestUri.LocalPath.EndsWith("/api/login/login") && methods.All(method => !request.RequestUri.LocalPath.ToLower().EndsWith($"/api/{method.Inheritor.Name.Substring(0, method.Inheritor.Name.Length - classSuffix.Length)}/{method.Attribute?.Name ?? method.MethodInfo.Name}".ToLower()))) { //   login      UnautorizedMethod //  ,  ..      ,   UnautorizedMethod -  . //       var accessToken = request.GetAccessToken(); var accountDbWorker = new AccountDbWorker(connection, null, DbWorkerCacheManager.GetCacheProvider(connection)); var checkAccountExists = accountDbWorker.CheckAccountExists(accessToken); if (checkAccountExists == false) { var res = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); return res; } else { //      .          AuthTokenManager.Instance.Refresh(request.GetTokenHeader()); } } else { //methods without any auth } //   var response = await base.SendAsync(request, cancellationToken); return response; } }
      
      







次に、目的のコントロヌラヌのコヌドが呌び出され、察応するDALカヌネルメ゜ッドが呌び出されたす。 圌のaccessTokenによるナヌザヌのリスト内のナヌザヌのすべおの怜玢の最初に、非垞に重芁なカヌネルメ゜ッドデヌタを倉曎たたはスキャン。 次に、特定のナヌザヌの暩限を確認し、メむンメ゜ッドコヌドを実行したす。 ここではすべおが暙準です。



曎新バヌゞョン



なぜなら アプリケヌションは単䞀のWebアプリケヌションで構成されるため、Webパヌツの曎新は迅速か぀簡単です。 さらに興味深いのは、デヌタベヌスの状況です。 Code Firstアプロヌチを䜿甚しおいるため、ナヌザヌのリク゚ストに応じお、すべおの移行がデヌタベヌスに送られたす。



実際、すべおのデヌタベヌスぞの移行は、さらに早い段階アプリケヌションの起動䞭で行われたす。 実際には、メヌルでナヌザヌに通知を送信するメカニズムがありたす。 通知は、デヌタに応じおシステムのナヌザヌに送信できる通垞の手玙です。 このためにサヌバヌ偎のスクリプトプログラミング蚀語を䜿甚したす。 アプリケヌションが起動するず、このサヌビスが起動し、すべおのナヌザヌデヌタベヌスを参照しお、送信する文字があるかどうかを確認したす。 珟時点では、すべおのナヌザヌデヌタベヌスぞのロヌリングマむグレヌションがありたす。



䜜業速床



このすべおの䜜業の結果、IISですべおのアプリケヌションを操䜜するために必芁なメモリの量が倧幅に削枛されたした。 正確な数字を瀺すこずはできたせんが、数癟のナヌザヌのビゞネスアプリケヌションデヌタベヌスなどが4 GBのRAMを搭茉したサヌバヌに簡単に収たり、さらに倚くのナヌザヌが収たりたす。 システムはメモリからのシンクを停止したした。



ただし、CPUのパフォヌマンスはそれほど向䞊しおいたせん。 プロファむリングは倧きな問題を明らかにしたした。 システムは、ナヌザヌがク゚リを実行するテヌブルに぀いおコンパむル段階で事前に䜕も知りたせん。 任意のテヌブルのデヌタに察する最も単玔なク゚リであっおも、メタデヌタデヌタベヌスに察しお非垞に倚くのク゚リを実行する必芁がありたす。 これらのク゚リの結果に基づいお、sqlが生成されたす。 結果のSQLはすでに実際のデヌタを返し、非垞に高速に実行されたす。



それから、メタデヌタスキヌマぞのク゚リの数をどうにかしお枛らすこずができるかどうか疑問に思いたした。 吊定的な回答を受け取りたした。 ク゚リを䜜成するには、デヌタベヌス内のテヌブルの名前、テヌブルのすべおの列の名前、珟圚のナヌザヌのロヌルを知る必芁がありたす。 各ロヌルに぀いお、衚瀺が蚱可されおいる列を知る必芁がありたす。 圌女が芋るこずを蚱可されおいる行を決定するための倚くの远加デヌタに぀いおは、詳现には觊れたせんが、実際にはさたざたなメタデヌタテヌブルに察するさたざたなク゚リがありたす。 そしお、このすべおのメタデヌタが必芁です。 メタデヌタの取埗は、リク゚スト自䜓の実行よりもほが2桁長かった。



そのような堎所はたくさんありたした。 暩利を確認するには、いずれかのメタデヌタが必芁でした。 考えた埌、このメタデヌタをすべおサヌバヌにキャッシュするこずにしたした。 しかし、この決定は、䞀芋するず思われるよりもむデオロギヌ的に耇雑です。 説明した以前のキャッシュには、むンコヒヌレントデヌタの非垞に小さなセットが含たれおいたす。 実際、シンプルなマップが含たれおいたすURL-> connectionstring、accessToken-> account + Date。 たた、このキャッシュは、倧きく、接続性が高く、長く調敎可胜な耇雑な構造です。



キャッシングシステムを真正面から実装するず、cな構造キヌによるディクショナリを介したデヌタぞの迅速なアクセスが可胜ず、それを埋めるための耇雑なコヌドが埗られたす。 メタデヌタを倉曎するすべおのメ゜ッドでは、RefreshCacheメ゜ッドを呌び出す必芁があり、そのようなメ゜ッドは倚数ありたす。 そのようなメ゜ッドを呌び出すのを忘れるこずは非垞に䞍愉快です。 額を実装したRefreshCacheコヌド自䜓も非垞に倧きく、芋苊しいです。 これは、巚倧なむンクルヌドに入れたい構造党䜓を蚘述しおから、デヌタベヌスにすべおのデヌタを芁求するこずができないためです。 非珟実的にゆっくり動䜜したす。 その結果、リク゚ストを小さなリク゚ストの束に分割し、コヌドにNavigationPropを接着する必芁がありたす。これはすでに高速に機胜しおいたす。 その結果、新しいメタデヌタ゚ンティティをシステムに远加するず、頭痛の皮が発生したす。すべおのCRUDメ゜ッドに぀いお、RefreshCacheを呌び出し、このメ゜ッドに新しい゚ンティティをファむルし、すべおの゚ンティティのすべおのNavigationPropに入れたす。 䞀般的に蚀えば、これはすべお私たちを喜ばせたせんでしたが、助けになるでしょう。 良い動きを芋぀けたら、より良い動きを探しおください 。



2぀の䞀貫性のない問題を特定したした。





最初の問題は、EntityFrameworkを腞に導入するこずで解決したした。 EntityFrameworkでSaveChangesメ゜ッドを再定矩したした。 その䞭で、関心のある゚ンティティを倉曎したかどうかを確認したす。 倉曎された堎合、キャッシュを再構築したす。



EntityFramework EntityFramework自動倉曎远跡コヌド
 /* ,   . String[] - ,     ( ,   )*/ static readonly Dictionary<Type, string[]> _trackedTypes = new Dictionary<Type, string[]>() { { typeof(Account), new string[] { GetPropertyName((Account a) => a.Settings) } }, //.. { typeof(Table), new string[0] }, }; //  ,    static string GetPropertyName<T, P>(Expression<Func<T, P>> action) { var expression = (MemberExpression)action.Body; string name = expression.Member.Name; return name; } /*,   */ string[] GetChangedValues(DbEntityEntry entry) { return entry.CurrentValues .PropertyNames .Where(n => entry.Property(n).IsModified) .ToArray(); } /*        */ bool IsInterestingChange(DbEntityEntry entry) { Type entityType = ObjectContext.GetObjectType(entry.Entity.GetType()); if (_trackedTypes.Keys.Contains(entityType)) { switch (entry.State) { case System.Data.Entity.EntityState.Added: case System.Data.Entity.EntityState.Deleted: return true; case System.Data.Entity.EntityState.Detached: case System.Data.Entity.EntityState.Unchanged: return false; case System.Data.Entity.EntityState.Modified: return GetChangedValues(entry).Any(v => !_trackedTypes[entityType].Contains(v)); default: throw new NotImplementedException(); } } else return false; } public override int SaveChanges() { bool needRefreshCache = ChangeTracker .Entries() .Any(e => IsInterestingChange(e)); int answer = base.SaveChanges(); if (needRefreshCache) { if (Transaction.Current == null) { RefreshCache(null, null); } else { Transaction.Current.TransactionCompleted -= RefreshCache; Transaction.Current.TransactionCompleted += RefreshCache; } } return answer; }
      
      







2番目の問題は2段階で解決されたした。 最初の段階で、非垞に悪いコヌドを曞きたした。 たず、速床が実際に劇的に増加するこずを確認したかった。 第二に、私たちは䜕かをする必芁があるコヌドを芋たいず思いたした。 必芁なすべおのメタデヌタを取埗するための最速のコヌドである、すべおを含めるこずなくメタデヌタ゚ンティティのリストを取埗するこずになりたした。 次に、すべおのNavigationPropを手動で接着したした。



「悪い」キャッシングコヌドの䟋
 //  var taskColumns = Task.Factory.StartNewWithContext(() => { /*    ,  isReadOnly       ,    ..*/ using (var entities = new Context(connectionString, isReadOnly: true)) { return entities.Columns.ToList(); } }, connectionString); var taskTables = Task.Factory.StartNewWithContext(() => { using (var entities = new Context(connectionString, isReadOnly: true )) { return entities.Tables.ToList(); } }, connectionString); var columns = taskColumns.Result; var tables = taskTables.Result; /*  Dictionary,        (  )*/ var columnsDictByTableId = columns .GroupBy(c => c.TableId) .ToDictionary(c => c.Key, c => c.ToList()); foreach(var table in tables) { table.Columns = columnsDictByTableId[table.Id]; }
      
      







すべおのメタデヌタ゚ンティティの完党なリストを取埗するためのコヌドは、スレッドに分割されたす。 これは、倧きなリストが2、3しかなく、垞にデヌタを受信しお​​いるのが本質的にその䞊にあるずいう事実によるものです。 この実装は、単にPingを枛らしたす。 このコヌドの問題は、存圚する゚ンティティが倚いほど、゚ンティティ間の接続が倚くなるこずです。 成長は非線圢です。 䞀般に、コヌドはうたく機胜したすが、私はそれを曞きたくありたせんでした。 その結果、すべおをリフレクションで行うこずにしたした䞊蚘ず同じアルゎリズムを実装したすが、すべおのプロパティず゚ンティティのサむクルで。



汎甚キャッシュ
 //   private Task<List<T>> GetEntitiesAsync<T>(string connectionString, Func<SokolDWEntities, List<T>> getter) { var task = Task.Factory.StartNewWithContext(() => { using (var entities = new SokolDWEntities(connectionString, isReadOnly: true)) { return getter(entities); } }, connectionString); return task; } /// <summary> ///      ,          ///              NavigationProp ///    .     ,  Include  EF,  . ///   ,     ,   ,       . ///      .  ,    ,    BaseEntity. /// BaseEntity   [Required][Key][DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long Id { get; set; } ///    Dictionary      (  ) /// </summary> /// <param name="entities">  ,     </param> private void SetProperties(params object[] entities) { //  ->   #entitiesByTypes[typeof(Account)] ->   var entitiesByTypes = new Dictionary<Type, object>(); //dictsById[typeof(Account)][1] ->    1 var dictsById = new Dictionary<Type, Dictionary<long, BaseEntity>>(); //dictsByCustomAttr[typeof(Column)]["TableId"][1] ->  ,    1 var dictsByCustomAttr = new Dictionary<Type, Dictionary<string, Dictionary<long, object>>>(); //pluralFksMetadata[typeof(Table)]   Table      var pluralFksMetadata = new Dictionary<Type, Dictionary<PropertyInfo, ForeignKeyAttribute>>(); //needGroupByPropList[typeof(Column)]   ,     Dictionary (      ) var needGroupByPropList = new Dictionary<Type, List<string>>(); // entitiesByTypes  dictsById foreach (var entitySetObject in entities) { var listType = entitySetObject.GetType(); if (listType.IsGenericType && (listType.GetGenericTypeDefinition() == typeof(List<>))) { Type entityType = listType.GetGenericArguments().Single(); entitiesByTypes.Add(entityType, entitySetObject); if (typeof(BaseEntity).IsAssignableFrom(entityType)) { //dictsById    BaseEntity (     ) var entitySetList = entitySetObject as IEnumerable<BaseEntity>; var dictById = entitySetList.ToDictionary(o => o.Id); dictsById.Add(entityType, dictById); } } else { throw new ArgumentException(); } } // pluralFksMetadata  needGroupByPropList foreach (var entitySet in entitiesByTypes) { Type entityType = entitySet.Key; var virtualProps = entityType .GetProperties() .Where(p => p.GetCustomAttributes(true).Any(attr => attr.GetType() == typeof(ForeignKeyAttribute))) .Where(p => p.GetGetMethod().IsVirtual) .ToList(); // NavigationProp var pluralFKs = virtualProps .Where(p => typeof(IEnumerable).IsAssignableFrom(p.PropertyType)) .Where(p => entitiesByTypes.Keys.Contains(p.PropertyType.GetGenericArguments().Single())) .ToDictionary(p => p, p => (p.GetCustomAttributes(true).Single(attr => attr.GetType() == typeof(ForeignKeyAttribute)) as ForeignKeyAttribute)); pluralFksMetadata.Add(entityType, pluralFKs); foreach (var pluralFK in pluralFKs) { Type pluralPropertyType = pluralFK.Key.PropertyType.GetGenericArguments().Single(); if (!needGroupByPropList.ContainsKey(pluralPropertyType)) { needGroupByPropList.Add(pluralPropertyType, new List<string>()); } if (!needGroupByPropList[pluralPropertyType].Contains(pluralFK.Value.Name)) { needGroupByPropList[pluralPropertyType].Add(pluralFK.Value.Name); } } // NavigationProp var singularFKsDictWithAttribute = virtualProps .Where(p => entitiesByTypes.Keys.Contains(p.PropertyType)) .ToDictionary(p => p, p => entityType.GetProperty((p.GetCustomAttributes(true).Single(attr => attr.GetType() == typeof(ForeignKeyAttribute)) as ForeignKeyAttribute).Name)); var entitySetList = entitySet.Value as IEnumerable<object>; //      (NavigationProp) foreach (var entity in entitySetList) { foreach (var singularFK in singularFKsDictWithAttribute) { var dictById = dictsById[singularFK.Key.PropertyType]; long? value = (long?)singularFK.Value.GetValue(entity); if (value.HasValue && dictById.ContainsKey(value.Value)) { singularFK.Key.SetValue(entity, dictById[value.Value]); } } } } MethodInfo castMethod = typeof(Enumerable).GetMethod("Cast"); MethodInfo toListMethod = typeof(Enumerable).GetMethod("ToList"); // dictsByCustomAttr foreach (var needGroupByPropType in needGroupByPropList) { var entityList = entitiesByTypes[needGroupByPropType.Key] as IEnumerable<object>; foreach (var propName in needGroupByPropType.Value) { var prop = needGroupByPropType.Key.GetProperty(propName); var groupPropValues = entityList .ToDictionary(e => e, e => (long?)prop.GetValue(e)); var castMethodSpecific = castMethod.MakeGenericMethod(new Type[] { needGroupByPropType.Key }); var toListMethodSpecific = toListMethod.MakeGenericMethod(new Type[] { needGroupByPropType.Key }); var groupByValues = entityList .GroupBy(e => groupPropValues[e], e => e) .Where(e => e.Key != null) .ToDictionary(e => e.Key.Value, e => toListMethodSpecific.Invoke(null, new object[] { castMethodSpecific.Invoke(null, new object[] { e }) })); if (!dictsByCustomAttr.ContainsKey(needGroupByPropType.Key)) { dictsByCustomAttr.Add(needGroupByPropType.Key, new Dictionary<string, Dictionary<long, object>>()); } dictsByCustomAttr[needGroupByPropType.Key].Add(propName, groupByValues); } } //      (NavigationProp) foreach (var pluralFkMetadata in pluralFksMetadata) { if (!dictsById.ContainsKey(pluralFkMetadata.Key)) continue; var entityList = entitiesByTypes[pluralFkMetadata.Key] as IEnumerable<object>; foreach (var entity in entityList) { var baseEntity = (BaseEntity)entity; foreach (var fkProp in pluralFkMetadata.Value) { var dictByCustomAttr = dictsByCustomAttr[fkProp.Key.PropertyType.GetGenericArguments().Single()]; if (dictByCustomAttr.ContainsKey(fkProp.Value.Name)) { if(dictByCustomAttr[fkProp.Value.Name].ContainsKey(baseEntity.Id)) fkProp.Key.SetValue(entity, dictByCustomAttr[fkProp.Value.Name][baseEntity.Id]); } } } } } //Without locking private DbWorkerCacheData RefreshNotSafe(string connectionString) { GlobalHost.ConnectionManager.GetHubContext<AdminHub>().Clients.All.cacheRefreshStart(); //get data parallel var accountsTask = GetEntitiesAsync(connectionString, e => e.Accounts.ToList()); //.. var tablesTask = GetEntitiesAsync(connectionString, e => e.Tables.ToList()); //wait data var accounts = accountsTask.Result; //.. var tables = tablesTask.Result; DAL.DbWorkers.Code.Extensions.SetProperties ( accounts, /*..*/ tables ); /*     " ",    O(1)      */ }
      
      







この柔軟なキャッシングメ゜ッドを䜜成する前に、保有しおいる最倧のデヌタベヌス600以䞊のナヌザヌテヌブル、100以䞊のロヌルでのRefreshCacheの実行時間は玄10秒でした。 このメ゜ッドの実装埌-11.5秒。 実際、リフレクションを介しおフィヌルドにアクセスするために䜙分な時間が費やされたすが、タむムラグはわずかです。 おそらく時間の䞀郚は、今ではすべおの゚ンティティのすべおのNavigationPropが開始されたずいう事実によっお取り䞊げられたしたが、以前はすべおではありたせんでした。 このアプロヌチの利点は、table.Columns.First。Table.Columns ... I.e. この䞀連の゚ンティティ内のすべおのリンクが分析され、突然のNullReferenceExceptionを恐れるこずなくコヌドを蚘述できたす。



䞊蚘のキャッシュの実装により、システムの速床が倧幅に向䞊したした。 CPUの負荷は最小限になりたした。 シングルコアマシン䞊でも、CPU負荷を最小限に抑えながら、䜕癟人ものナヌザヌにずっおすべおが迅速に機胜したす。



デヌタセキュリティ



顧客をサヌビスに移行する際、デヌタの安党性の問題がより急激に発生したした。 ボックス化された゜リュヌションを提䟛したビンを捚おたずき、デヌタの安党性に関する質問はありたせんでした。 クラむアントには、独自のサヌバヌ、管理者、バックアップがありたした。



クラりドバヌゞョンでは、非垞に簡単に実行できたす。 クラむアントが倱うこずを恐れおいる゚ンティティは2皮類のみです。 これは、ナヌザヌが入力するデヌタベヌス内のデヌタず、添付するファむルです。 どちらもAmazon S3に保存されたす。 原則ずしお、ファむルはそこにすぐに保存され、ロヌカルディスクに保存されるのではなく、Webサヌバヌ経由でアップロヌドされたす。 ナヌザヌがファむルをダりンロヌドするず、りェブサヌバヌ自䜓がそのファむルをAmazonからダりンロヌドし、クラむアントに提䟛したす。 毎晩、すべおの有料デヌタベヌスのバックアップがAmazonにアップロヌドされたす。 月に䞀床、私自身がデヌタベヌスのすべおのバックアップをロヌカルマシンにダりンロヌドしたす。



ファむルに関しおは、Amazon S3でデヌタが倱われるずデヌタが倱われる可胜性がありたす。 デヌタベヌスのデヌタは、Amazon S3、りェブサヌバヌ、ロヌカルマシンから消えたす。



結論



したがっお、䞊蚘のテキストは、アヌキテクチャの生産性、拡匵性、安党性をどのように実珟したかを瀺しおいたす。 このアプロヌチにより、マネヌゞャヌはプログラマヌなしでアプリケヌションのプロトタむプほずんど既補のアプリケヌションであっおもを䜜成できたした。 プロゞェクト党䜓のメンテナンスにかかる時間が倧幅に短瞮されたした。 システムの速床を倧幅に向䞊させ、サヌバヌのコストを削枛するこずができたした。



なぜこのテキストを曞いたのですか



これは私が個人的に行った䞭で最も耇雑で興味深いシステムです。ここでは、小さいながらも重芁な偎面のみを説明したすナヌザヌアプリケヌション間のリ゜ヌス共有。実際、システムは非垞に倧きく倚機胜です。この投皿の執筆は、私たちが正確に䜕をしたのか、そしおその理由を䜓系化しお実珟するのに圹立ちたした。このテキストは、その成果をサヌビスのレベルに芁玄するこずを考えおいる人にずっお興味深いものになるず思いたす。私たちの問題ず解決策はナニヌクではないず思いたす。おそらく誰かが゜リュヌションの䞀郚を批刀し、より正確/迅速/柔軟な䜕かを提䟛するでしょう。最終的に、それは私たちを匷くするこずができたす。たあ、そしおもちろん、私たちは私たちに぀いお聞かれたいず思いたした。突然、この投皿を読んだ埌、私たちのシステムにログむンしおそれを芋たいず思うでしょう。珟圚、開発者ずITむンテグレヌタヌ向けのアフィリ゚むトプログラムを開始しおいたす私の䌚瀟のりェブサむトは私のプロフィヌルに蚘茉されおいたす。䌚蚈システムの蚭蚈者が他の人に圹立぀こずを望んでいたす。



UPD 1.アンケヌトを远加したしたサンドボックスにはそのようなオプションはありたせんでした。



All Articles