{"id":33839,"date":"2019-10-31T21:54:56","date_gmt":"2019-10-31T18:54:56","guid":{"rendered":"https:\/\/prohoster.info\/blog\/objectrepository-net-in-memory-repository-pattern-dlya-vashih-domashnih-proektov\/"},"modified":"2019-10-31T21:54:56","modified_gmt":"2019-10-31T18:54:56","slug":"objectrepository-net-in-memory-repository-pattern-dlya-vashih-domashnih-proektov","status":"publish","type":"post","link":"https:\/\/prohoster.info\/de\/blog\/news\/objectrepository-net-in-memory-repository-pattern-dlya-vashih-domashnih-proektov","title":{"rendered":"ObjectRepository \u2014 .NET In-Memory-Repository-Muster f\u00fcr Ihre Home-Projekte","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<h2 id=\"zachem-hranit-vse-dannye-v-pamyati\">Warum alle Daten im Speicher speichern?<\/h2>\n<p><\/p>\n<p>F\u00fcr die Speicherung von Website- oder Backend-Daten wird die Mehrheit der gesunden Menschen SQL-Datenbanken bevorzugen.\u00a0<\/p>\n<p><\/p>\n<p>Doch manchmal kommt einem der Gedanke, dass das Datenmodell nicht zu SQL passt: Zum Beispiel ben\u00f6tigt man bei der Erstellung von Suchanfragen oder sozialen Graphen die Suche nach komplexen Beziehungen zwischen Objekten.\u00a0<\/p>\n<p><\/p>\n<p><strong>Am \u00e4rgerlichsten ist die Situation, wenn man im Team arbeitet und ein Kollege keine schnellen Abfragen erstellen kann. Wie viel Zeit haben Sie damit verbracht, die Probleme N+1 zu l\u00f6sen und zus\u00e4tzliche Indizes zu erstellen, damit das SELECT auf der Hauptseite in angemessener Zeit funktioniert?<\/strong><\/p>\n<p><\/p>\n<p>Ein anderer beliebter Ansatz ist NoSQL. Vor einigen Jahren gab es viel Hype um dieses Thema \u2013 bei jeder geeigneten Gelegenheit wurde MongoDB implementiert und man freute sich \u00fcber die Antworten in Form von JSON-Dokumenten. <em>(\u00dcbrigens, wie viele Notl\u00f6sungen mussten wegen zirkul\u00e4rer Verweise in den Dokumenten eingef\u00fchrt werden?)<\/em>.<\/p>\n<p><\/p>\n<p>Ich schlage vor, einen weiteren alternativen Ansatz auszuprobieren \u2013 warum nicht versuchen, alle Daten im Anwendungsspeicher zu halten und sie gelegentlich in einen beliebigen Speicher (Datei, entfernte Datenbank) zu speichern?\u00a0<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<p>Der Speicher ist g\u00fcnstig geworden, und alle m\u00f6glichen Daten der meisten kleinen und mittleren Projekte passen in 1 GB RAM. <em>(Zum Beispiel mein Lieblings-Heimprojekt \u2013 <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/DiverOfDark\/BudgetTracker\">ein Finanztracker<\/a><\/noindex>, der t\u00e4gliche Statistiken und die Historie meiner Ausgaben, Salden und Transaktionen \u00fcber anderthalb Jahre hinweg f\u00fchrt, ben\u00f6tigt nur 45 MB Speicher.)<\/em><\/p>\n<p><\/p>\n<p>Vorteile:<\/p>\n<p><\/p>\n<ul>\n<li>Der Zugriff auf Daten wird einfacher \u2013 man muss sich keine Gedanken \u00fcber Abfragen, Lazy Loading, Besonderheiten von ORM machen; die Arbeit erfolgt mit normalen C#-Objekten;<\/li>\n<li>Es gibt keine Probleme mit dem Zugriff aus verschiedenen Threads;<\/li>\n<li>Sehr schnell \u2013 es gibt keine Netzwerkabfragen, es entf\u00e4llt die \u00dcbersetzung des Codes in eine Abfragesprache, es ist keine (De-)Serialisierung von Objekten erforderlich;<\/li>\n<li>Es ist erlaubt, Daten in beliebiger Form zu speichern \u2013 sei es in XML auf der Festplatte, in SQL Server oder in Azure Table Storage.<\/li>\n<\/ul>\n<p><\/p>\n<p>Nachteile:<\/p>\n<p><\/p>\n<ul>\n<li>Horizontale Skalierung geht verloren, und infolgedessen ist ein Zero-Downtime-Deployment nicht m\u00f6glich;<\/li>\n<li>Wenn die Anwendung abst\u00fcrzt, k\u00f6nnen Daten teilweise verloren gehen. <em>(Aber unser Anwendung st\u00fcrzt doch nie ab, oder?)<\/em><\/li>\n<\/ul>\n<p><\/p>\n<h2 id=\"kak-eto-rabotaet\">Wie funktioniert das?<\/h2>\n<p><\/p>\n<p>Der Algorithmus ist wie folgt:<\/p>\n<p><\/p>\n<ul>\n<li>Beim Start wird eine Verbindung zum Datenspeicher aufgebaut, und die Daten werden geladen;<\/li>\n<li>Ein Objektmodell, prim\u00e4re Indizes und Beziehungsindizes (1:1, 1:Viele) werden erstellt;<\/li>\n<li>Ein Abonnement f\u00fcr \u00c4nderungen an den Eigenschaften von Objekten (INotifyPropertyChanged) sowie f\u00fcr das Hinzuf\u00fcgen oder Entfernen von Elementen in einer Sammlung (INotifyCollectionChanged) wird erstellt;<\/li>\n<li>Wenn das Abonnement ausgel\u00f6st wird, wird das ge\u00e4nderte Objekt in die Warteschlange zum Schreiben in den Datenspeicher aufgenommen;<\/li>\n<li>Periodisch (\u00fcber einen Timer) werden die \u00c4nderungen im Hintergrundspeicher gespeichert;<\/li>\n<li>Beim Verlassen der Anwendung werden ebenfalls die \u00c4nderungen im Speicher gesichert.<\/li>\n<\/ul>\n<p><\/p>\n<h2 id=\"primer-koda\">Beispielcode<\/h2>\n<p>\n<b class=\"spoiler_title\">Notwendige Abh\u00e4ngigkeiten hinzuf\u00fcgen<\/b><\/p>\n<pre><code class=\"cs\">\/\/ \u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430\nInstall-Package OutCode.EscapeTeams.ObjectRepository\n\u00a0 \u00a0\u00a0\n\/\/ \u0425\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 \u0434\u0430\u043d\u043d\u044b\u0445, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c\u0441\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\n\/\/ \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0442\u043e, \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0431\u0443\u0434\u0435\u0442\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f.\nInstall-Package OutCode.EscapeTeams.ObjectRepository.File\nInstall-Package OutCode.EscapeTeams.ObjectRepository.LiteDb\nInstall-Package OutCode.EscapeTeams.ObjectRepository.AzureTableStorage\n\u00a0 \u00a0\u00a0\n\/\/ \u041e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e - \u0435\u0441\u043b\u0438 \u043d\u0443\u0436\u043d\u043e \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f Hangfire\n\/\/ Install-Package OutCode.EscapeTeams.ObjectRepository.Hangfire<\/code><\/pre>\n<p>\n<b class=\"spoiler_title\">Das Datenmodell beschreiben, das im Speicher gespeichert werden soll<\/b><\/p>\n<pre><code class=\"cs\">public class ParentEntity : BaseEntity\n{\n    public ParentEntity(Guid id) =&gt; Id = id;\n}\n    \npublic class ChildEntity : BaseEntity\n{\n    public ChildEntity(Guid id) =&gt; Id = id;\n    public Guid ParentId { get; set; }\n    public string Value { get; set; }\n}<\/code><\/pre>\n<p>\n<b class=\"spoiler_title\">Dann das objekthafte Modell:<\/b><\/p>\n<pre><code class=\"cs\">public class ParentModel : ModelBase\n{\n    public ParentModel(ParentEntity entity)\n    {\n        Entity = entity;\n    }\n    \n    public ParentModel()\n    {\n        Entity = new ParentEntity(Guid.NewGuid());\n    }\n    \n    public Guid? NullableId =&gt; null;\n    \n    \/\/ Beispiel einer 1:n Beziehung\n    public IEnumerable Children =&gt; Multiple(x =&gt; x.ParentId);\n    \n    protected override BaseEntity Entity { get; }\n}\n    \npublic class ChildModel : ModelBase\n{\n    private ChildEntity _childEntity;\n    \n    public ChildModel(ChildEntity entity)\n    {\n        _childEntity = entity;\n    }\n    \n    public ChildModel() \n    {\n        _childEntity = new ChildEntity(Guid.NewGuid());\n    }\n    \n    public Guid ParentId\n    {\n        get =&gt; _childEntity.ParentId;\n        set =&gt; UpdateProperty(() =&gt; _childEntity.ParentId, value);\n    }\n    \n    public string Value\n    {\n        get =&gt; _childEntity.Value;\n        set =&gt; UpdateProperty(() =&gt; _childEntity.Value, value);\n    }\n    \n    \/\/ Zugriff mit Indexsuche\n    public ParentModel Parent =&gt; Single(ParentId);\n    \n    protected override BaseEntity Entity =&gt; _childEntity;\n}<\/code><\/pre>\n<p>\n<b class=\"spoiler_title\">Und schlie\u00dflich die Repository-Klasse f\u00fcr den Datenzugriff:<\/b><\/p>\n<pre><code class=\"cs\">public class MyObjectRepository : ObjectRepositoryBase\n{\n    public MyObjectRepository(IStorage storage) : base(storage, NullLogger.Instance)\n    {\n        IsReadOnly = true; \/\/ F\u00fcr Tests, erm\u00f6glicht das Nicht-Speichern von \u00c4nderungen in der Datenbank\n    \n        AddType((ParentEntity x) =&gt; new ParentModel(x));\n        AddType((ChildEntity x) =&gt; new ChildModel(x));\n    \n        \/\/ Wenn Hangfire verwendet wird und das Datenmodell f\u00fcr Hangfire im ObjectRepository gespeichert werden muss\n        \/\/ this.RegisterHangfireScheme();\n    \n        Initialize();\n    }\n}<\/code><\/pre>\n<p><\/p>\n<p>Ein Objekt des ObjectRepository erstellen:<\/p>\n<p><\/p>\n<pre><code class=\"cs\">var memory = new MemoryStream();\nvar db = new LiteDatabase(memory);\nvar dbStorage = new LiteDbStorage(db);\n    \nvar repository = new MyObjectRepository(dbStorage);\nawait repository.WaitForInitialize();<\/code><\/pre>\n<p>\n<b class=\"spoiler_title\">Wenn im Projekt HangFire verwendet wird<\/b><\/p>\n<pre><code class=\"cs\">public void ConfigureServices(IServiceCollection services, ObjectRepository objectRepository)\n{\n    services.AddHangfire(s =&gt; s.UseHangfireStorage(objectRepository));\n}<\/code><\/pre>\n<p><\/p>\n<p>Einf\u00fcgen eines neuen Objekts:<\/p>\n<p><\/p>\n<pre><code class=\"cs\">var newParent = new ParentModel()\nrepository.Add(newParent);<\/code><\/pre>\n<p><\/p>\n<p>Bei diesem Aufruf wird das Objekt <strong>ParentModel<\/strong> dem lokalen Cache sowie der Warteschlange f\u00fcr die Datenbankeintragung hinzugef\u00fcgt. Daher hat dieser Vorgang eine Zeitkomplexit\u00e4t von O(1), und mit diesem Objekt kann sofort gearbeitet werden.<\/p>\n<p><\/p>\n<p>Zum Beispiel, um dieses Objekt im Repository zu finden und sicherzustellen, dass das zur\u00fcckgegebene Objekt dasselbe Exemplar ist:<\/p>\n<p><\/p>\n<pre><code class=\"cs\">var parents = repository.Set();\nvar myParent = parents.Find(newParent.Id);\nAssert.IsTrue(ReferenceEquals(myParent, newParent));<\/code><\/pre>\n<p><\/p>\n<p>Was passiert dabei? <em>Set()<\/em> gibt zur\u00fcck <em>TableDictionary<\/em>, das in sich enth\u00e4lt <em>ConcurrentDictionary<\/em> und zus\u00e4tzliche Funktionen f\u00fcr prim\u00e4re und sekund\u00e4re Indizes bereitstellt. Dies erm\u00f6glicht Methoden zur Suche nach Id (oder anderen beliebigen benutzerdefinierten Indizes), ohne alle Objekte vollst\u00e4ndig durchlaufen zu m\u00fcssen.<\/p>\n<p><\/p>\n<p>Wenn Objekte in <em>ObjectRepository<\/em> eingef\u00fcgt werden, wird ein Abonnement f\u00fcr \u00c4nderungen ihrer Eigenschaften hinzugef\u00fcgt, sodass jede \u00c4nderung der Eigenschaften ebenfalls zur Hinzuf\u00fcgung dieses Objekts in die Warteschlange f\u00fcr die Speicherung f\u00fchrt.\u00a0<br \/>\nDas Aktualisieren der Eigenschaften von au\u00dfen sieht genauso aus wie das Arbeiten mit einem POCO-Objekt:<\/p>\n<p><\/p>\n<pre><code class=\"cs\">myParent.Children.First().Property = \"Aktualisierter Wert\";<\/code><\/pre>\n<p><\/p>\n<p>Ein Objekt kann auf folgende Weisen entfernt werden:<\/p>\n<p><\/p>\n<pre><code class=\"cs\">repository.Remove(myParent);\nrepository.RemoveRange(otherParents);\nrepository.Remove(x =&gt; !x.Children.Any());<\/code><\/pre>\n<p><\/p>\n<p>Dabei wird auch das Objekt zur L\u00f6schwarteschlange hinzugef\u00fcgt.<\/p>\n<p><\/p>\n<h2 id=\"kak-rabotaet-sohranenie\">Wie funktioniert die Speicherung?<\/h2>\n<p><\/p>\n<p><em>ObjectRepository<\/em> Bei \u00c4nderungen der verfolgten Objekte (sowohl beim Hinzuf\u00fcgen oder Entfernen als auch beim \u00c4ndern von Eigenschaften) wird ein Ereignis ausgel\u00f6st <em>ModelChanged<\/em>, auf das <em>IStorage<\/em>abonnieren. Implementierungen <em>IStorage<\/em> speichern bei Auftreten des Ereignisses die \u00c4nderungen in 3 Warteschlangen - f\u00fcr Hinzuf\u00fcgungen, Aktualisierungen und L\u00f6schungen. <em>ModelChanged<\/em> Au\u00dferdem erstellen die Implementierungen bei der Initialisierung einen Timer, der alle 5 Sekunden die Speicherung der \u00c4nderungen ausl\u00f6st.<\/p>\n<p><\/p>\n<p>Zudem gibt es ein API f\u00fcr den erzwungenen Speichervorgang: <em>IStorage<\/em> ObjectRepository.Save()\u00a0<\/p>\n<p><\/p>\n<p><em>Vor jeder Speicherung erfolgt zuerst die Bereinigung der Warteschlangen von sinnlosen Operationen (z. B. doppelte Ereignisse \u2013 wenn ein Objekt zweimal ge\u00e4ndert wurde oder durch schnelles Hinzuf\u00fcgen\/L\u00f6schen von Objekten), und erst dann erfolgt die eigentliche Speicherung. <strong>In allen F\u00e4llen wird das aktuelle Objekt vollst\u00e4ndig gespeichert, daher kann es vorkommen, dass Objekte in einer anderen Reihenfolge gespeichert werden, als sie ge\u00e4ndert wurden, einschlie\u00dflich der M\u00f6glichkeit, dass neuere Versionen von Objekten gespeichert werden als zu dem Zeitpunkt, als sie in die Warteschlange eingeordnet wurden.<\/strong>.<\/em><\/p>\n<p><\/p>\n<p>Was gibt es noch?\u00a0<\/p>\n<p><\/p>\n<p><em>Alle Bibliotheken basieren auf .NET Standard 2.0. K\u00f6nnen in jedem modernen .NET-Projekt verwendet werden.<\/em><\/p>\n<p><\/p>\n<h2 id=\"chto-est-eschyo\">Was gibt es sonst noch?<\/h2>\n<p><\/p>\n<ul>\n<li>Alle Bibliotheken basieren auf .NET Standard 2.0. Sie k\u00f6nnen in jedem modernen .NET-Projekt verwendet werden.<\/li>\n<li>Die API ist thread-sicher. Interne Sammlungen sind auf <em>ConcurrentDictionary<\/em>implementiert, und die Ereignisbehandler ben\u00f6tigen entweder Sperren oder ben\u00f6tigen sie nicht.\u00a0<br \/>\nDas Einzige, woran man denken sollte, ist, dass beim Beenden der Anwendung <em>ObjectRepository.Save(); aufgerufen werden sollte.<\/em><\/li>\n<li>Beliebige Indizes (erfordern Einzigartigkeit):<\/li>\n<\/ul>\n<p><\/p>\n<pre><code class=\"cs\">repository.Set&lt;ChildModel&gt;().AddIndex(x =&gt; x.Value);\nrepository.Set&lt;ChildModel&gt;().Find(x =&gt; x.Value, \"myValue\");<\/code><\/pre>\n<p><\/p>\n<h2 id=\"kto-eto-ispolzuet\">Wer verwendet das?<\/h2>\n<p><\/p>\n<p>Ich pers\u00f6nlich habe begonnen, diesen Ansatz in allen Hobbyprojekten zu verwenden, da er bequem ist und keine gro\u00dfen Ausgaben f\u00fcr das Schreiben einer Datenzugriffsschicht oder f\u00fcr den Aufbau schwerer Infrastruktur erfordert. F\u00fcr mich reicht es in der Regel aus, die Daten in litedb oder in einer Datei zu speichern.\u00a0<\/p>\n<p><\/p>\n<p>Doch in der Vergangenheit, als ich mit dem Team das mittlerweile eingestellte Start-up EscapeTeams gemacht habe (<em>dachte ich, das sind sie, das Geld \u2014 aber nein, es war wieder Erfahrung<\/em>) \u2014 haben wir Azure Table Storage zur Speicherung der Daten verwendet.<\/p>\n<p><\/p>\n<h2 id=\"plany-na-buduschee\">Zukunftspl\u00e4ne<\/h2>\n<p><\/p>\n<p>Einen der Hauptnachteile dieses Ansatzes zu beheben, das horizontale Scaling, erfordert entweder verteilte Transaktionen (sic!), oder es muss die Entscheidung getroffen werden, dass dieselben Daten in verschiedenen Instanzen nicht ver\u00e4ndert werden d\u00fcrfen, oder sie d\u00fcrfen nach dem Prinzip \"Wer zuletzt kommt, mahlt zuletzt\" ver\u00e4ndert werden.<\/p>\n<p><\/p>\n<p>Aus technischer Sicht sehe ich die folgende M\u00f6glichkeit:<\/p>\n<p><\/p>\n<ul>\n<li>Statt eines Objektmodells EventLog und Snapshot speichern<\/li>\n<li>Andere Instanzen finden (sollen die Endpunkte aller Instanzen in den Einstellungen hinzugef\u00fcgt werden? UDP-Discovery? Master\/Slave?)<\/li>\n<li>EventLog zwischen den Instanzen \u00fcber einen Konsensalgorithmus replizieren, zum Beispiel RAFT.<\/li>\n<\/ul>\n<p><\/p>\n<p>Es gibt au\u00dferdem ein weiteres Problem, das mir Sorgen bereitet \u2014 das kaskadierte L\u00f6schen oder das Erkennen von F\u00e4llen, in denen Objekte, auf die aus anderen Objekten verwiesen wird, gel\u00f6scht werden.\u00a0<\/p>\n<p><\/p>\n<h2 id=\"ishodnyy-kod\">Quellcode<\/h2>\n<p><\/p>\n<p>Wenn Sie bis hierher gelesen haben \u2014 dann bleibt nur noch der Code zu lesen, den Sie auf GitHub finden k\u00f6nnen:<br \/>\n<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/DiverOfDark\/ObjectRepository\">https:\/\/github.com\/DiverOfDark\/ObjectRepository<\/a><\/noindex><\/p>\n<p>Quelle: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/452232\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0417\u0430\u0447\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432\u0441\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438? \u0414\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0430\u0439\u0442\u0430 \u0438\u043b\u0438 \u0431\u0435\u043a\u044d\u043d\u0434\u0430 \u043f\u0435\u0440\u0432\u044b\u043c \u0436\u0435\u043b\u0430\u043d\u0438\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0430 \u0437\u0434\u0440\u0430\u0432\u043e\u043c\u044b\u0441\u043b\u044f\u0449\u0438\u0445 \u043b\u044e\u0434\u0435\u0439 \u0432\u044b\u0431\u0435\u0440\u0435\u0442 SQL \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445.\u00a0 \u041d\u043e \u0438\u043d\u043e\u0433\u0434\u0430 \u0432 \u0433\u043e\u043b\u043e\u0432\u0443 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043c\u044b\u0441\u043b\u044c \u0447\u0442\u043e \u043c\u043e\u0434\u0435\u043b\u044c \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0435 \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f SQL: \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0440\u0438 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u0438\u0438 \u043f\u043e\u0438\u0441\u043a\u0430 \u0438\u043b\u0438 \u0441\u043e\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0433\u0440\u0430\u0444\u0430 \u043d\u0443\u0436\u0435\u043d \u043f\u043e\u0438\u0441\u043a \u043f\u043e \u0441\u043b\u043e\u0436\u043d\u044b\u043c \u0441\u0432\u044f\u0437\u044f\u043c \u043c\u0435\u0436\u0434\u0443 \u043e\u0431\u044a\u0435\u043a\u0442\u0430\u043c\u0438.\u00a0 \u0425\u0443\u0436\u0435 \u0432\u0441\u0435\u0433\u043e \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u044f, \u043a\u043e\u0433\u0434\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442\u0435 \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435, [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[702],"tags":[],"class_list":["post-33839","post","type-post","status-publish","format-standard","hentry","category-news"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0417\u0430\u0447\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432\u0441\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438? \u0414\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0430\u0439\u0442\u0430 \u0438\u043b\u0438 \u0431\u0435\u043a\u044d\u043d\u0434\u0430 \u043f\u0435\u0440\u0432\u044b\u043c \u0436\u0435\u043b\u0430\u043d\u0438\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0430 \u0437\u0434\u0440\u0430\u0432\u043e\u043c\u044b\u0441\u043b\u044f\u0449\u0438\u0445 \u043b\u044e\u0434\u0435\u0439 \u0432\u044b\u0431\u0435\u0440\u0435\u0442 SQL \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/de\/blog\/news\/objectrepository-net-in-memory-repository-pattern-dlya-vashih-domashnih-proektov\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"de_DE\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47ObjectRepository \u2014 .NET in-memory repository pattern \u0434\u043b\u044f \u0432\u0430\u0448\u0438\u0445 \u0434\u043e\u043c\u0430\u0448\u043d\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0417\u0430\u0447\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432\u0441\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438? \u0414\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0430\u0439\u0442\u0430 \u0438\u043b\u0438 \u0431\u0435\u043a\u044d\u043d\u0434\u0430 \u043f\u0435\u0440\u0432\u044b\u043c \u0436\u0435\u043b\u0430\u043d\u0438\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0430 \u0437\u0434\u0440\u0430\u0432\u043e\u043c\u044b\u0441\u043b\u044f\u0449\u0438\u0445 \u043b\u044e\u0434\u0435\u0439 \u0432\u044b\u0431\u0435\u0440\u0435\u0442 SQL \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/de\/blog\/news\/objectrepository-net-in-memory-repository-pattern-dlya-vashih-domashnih-proektov\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T18:54:56+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T18:54:56+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47ObjectRepository \u2014 .NET In-Memory Repository-Muster f\u00fcr Ihre Heimprojekte | ProHoster","description":"Warum alle Daten im Speicher speichern? F\u00fcr die Speicherung von Website- oder Backend-Daten entscheiden sich die meisten vern\u00fcnftigen Menschen zuerst f\u00fcr eine SQL-Datenbank.","canonical_url":"https:\/\/prohoster.info\/de\/blog\/news\/objectrepository-net-in-memory-repository-pattern-dlya-vashih-domashnih-proektov","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"de_DE","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47ObjectRepository \u2014 .NET in-memory repository pattern \u0434\u043b\u044f \u0432\u0430\u0448\u0438\u0445 \u0434\u043e\u043c\u0430\u0448\u043d\u0438\u0445 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 | ProHoster","og:description":"\u0417\u0430\u0447\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432\u0441\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u043f\u0430\u043c\u044f\u0442\u0438? \u0414\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0441\u0430\u0439\u0442\u0430 \u0438\u043b\u0438 \u0431\u0435\u043a\u044d\u043d\u0434\u0430 \u043f\u0435\u0440\u0432\u044b\u043c \u0436\u0435\u043b\u0430\u043d\u0438\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0430 \u0437\u0434\u0440\u0430\u0432\u043e\u043c\u044b\u0441\u043b\u044f\u0449\u0438\u0445 \u043b\u044e\u0434\u0435\u0439 \u0432\u044b\u0431\u0435\u0440\u0435\u0442 SQL \u0431\u0430\u0437\u0443 \u0434\u0430\u043d\u043d\u044b\u0445.","og:url":"https:\/\/prohoster.info\/de\/blog\/news\/objectrepository-net-in-memory-repository-pattern-dlya-vashih-domashnih-proektov","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T18:54:56+00:00","article:modified_time":"2019-10-31T18:54:56+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"33839","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-21 16:56:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 02:32:27","updated":"2026-01-21 16:56:19","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts\/33839","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/comments?post=33839"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts\/33839\/revisions"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/media?parent=33839"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/categories?post=33839"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/tags?post=33839"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}