2010-06-25 10 views
1

Je ne sais pas exactement comment décrire cette question, mais voilà. J'ai une hiérarchie de classe d'objets qui sont mappés dans une base de données SQLite. J'ai déjà écrit tout le code non-trivial qui communique entre les objets .NET et la base de données.Initialisation du constructeur à partir du cache stocké en C#

J'ai une interface de base comme suit:

public interface IBackendObject 
{ 
    void Read(int id); 
    void Refresh(); 
    void Save(); 
    void Delete(); 
} 

Ce sont les opérations CRUD de base sur tout objet. J'ai ensuite implémenté une classe de base qui encapsule la plupart des fonctionnalités.

public abstract class ABackendObject : IBackendObject 
{ 
    protected ABackendObject() { } // constructor used to instantiate new objects 
    protected ABackendObject(int id) { Read(id); } // constructor used to load object 

    public void Read(int id) { ... } // implemented here is the DB code 
} 

Maintenant, enfin, j'ai mes objets enfants en béton, dont chacun ont leurs propres tables dans la base de données:

public class ChildObject : ABackendObject 
{ 
    public ChildObject() : base() { } 
    public ChildObject(int id) : base(id) { } 
} 

Cela fonctionne bien pour tous mes objectifs jusqu'à présent. L'enfant dispose de plusieurs méthodes de rappel utilisées par la classe de base pour instancier correctement les données.

Je veux maintenant rendre cela un peu efficace. Par exemple, dans le code suivant:

public void SomeFunction1() 
{ 
    ChildObject obj = new ChildObject(1); 
    obj.Property1 = "blah!"; 
    obj.Save(); 
} 

public void SomeFunction2() 
{ 
    ChildObject obj = new ChildObject(1); 
    obj.Property2 = "blah!"; 
    obj.Save(); 
} 

Dans ce cas, je serai construire deux instanciations de mémoire complètement nouveaux et en fonction de l'ordre de SomeFunction1 et SomeFunction2 appelés, soit Propriété1 ou Propriété2 ne peuvent pas être sauvés. Ce que je veux réaliser est un moyen pour ces deux instanciations de pointer d'une manière ou d'une autre vers le même emplacement de mémoire - je ne pense pas que ce sera possible si j'utilise le "nouveau" mot-clé, donc je cherchais des indices la façon de procéder. Idéalement, je voudrais stocker un cache de tous les objets chargés dans ma classe ABackendObject et renvoyer des références de mémoire aux objets déjà chargés lorsque demandé, ou charger l'objet de la mémoire si elle n'existe pas déjà et l'ajouter à la cache. J'ai beaucoup de code qui utilise déjà ce framework, donc je vais bien sûr devoir changer beaucoup de choses pour que cela fonctionne, mais je voulais juste quelques conseils sur la façon de procéder.

Merci!

+0

Je ne comprends pas très bien le problème. Si vous appelez Save() juste après chaque fois que vous modifiez une propriété, est-ce que cela replace immédiatement cette modification dans la base de données? Si oui, la nouvelle valeur ne sera-t-elle pas la prochaine fois que vous créerez un nouveau ChildObject? – tlayton

+0

Je n'appelle pas Save() après chaque modification de propriété car les objets peuvent faire partie de processus de longue durée. En outre, le problème sous-jacent est que les objets peuvent être créés dans plusieurs portées différentes (puisque mes objets ont des associations et peuvent être liés les uns aux autres). Dans ce cas, je devrais frapper le disque chaque fois que je charge un objet, ce qui serait un autre coup de performance. – sohum

Répondre

6

Si vous souhaitez stocker un "cache" d'objets chargés, vous pouvez facilement faire en sorte que chaque type maintienne un Dictionary<int, IBackendObject> qui contient les objets chargés, chiffrés par leur ID.

Au lieu d'utiliser un constructeur, construire une méthode d'usine qui vérifie le cache:

public abstract class ABackendObject<T> where T : class 
{ 
    public T LoadFromDB(int id) { 
     T obj = this.CheckCache(id); 
     if (obj == null) 
     { 
      obj = this.Read(id); // Load the object 
      this.SaveToCache(id, obj); 
     } 
     return obj; 
    } 
} 

Si vous faites votre classe de base générique et de lecture virtuelle, vous devriez être en mesure de fournir plus de cette fonctionnalité sans beaucoup duplication de code.

+0

+1 Merde ... vous tapez entièrement trop vite. J'étais si près. Haha –

+0

Cela semble agréable et raffiné. J'avais juste une question, à savoir si je pouvais rendre certaines de ces fonctions intermédiaires statiques. À partir de votre exemple de code, je suppose que chaque objet ChildObject implémente les méthodes CheckCache et SaveToCache (Read est déjà implémenté par ABackendObject avec des rappels dans chaque objet ChildObject). Ce serait bien si je pouvais appeler 'ChildObject.LoadFromDB (id)' au lieu de 'new ChildObject(). LoadFromDB (id)'. Des suggestions sur ce front? Sinon, ça a l'air génial! – sohum

+0

@sohum: Vous devriez être capable de faire la plupart de ces statiques. En fait, je mettrais CheckCache et SaveToCache dans la classe de base - il aurait besoin d'être générique, et vous mettriez le type réel dans la sous-classe: [public class ChildObject: ABackendObject {...] –

2

Ce que vous voulez est une usine d'objets. Rendez le constructeur ChildObject privé, puis écrivez une méthode statique ChildObject.Create(int index) qui renvoie un ChildObject, mais qui garantit en interne que les différents appels avec le même index renvoient le même objet. Pour les cas simples, un hachage statique simple de l'index => objet sera suffisant.

+0

Je suis allé de l'avant et mis en œuvre cela et cela fonctionne comme un rêve. Le seul problème est que j'ai dû modifier tous mes 'ChildObjects', il y en a environ 20. Je suppose que c'est correct tant que je n'ai pas besoin de le changer à nouveau! – sohum

+0

L'utilisation de la suggestion de Reed Copsey peut vous aider à réduire la quantité de codes dupliqués. –

0

sons parfait pour un compte de référence comme celui-ci ...

#region Begin/End Update 
    int refcount = 0; 
    ChildObject record; 
    protected ChildObject ActiveRecord 
    { 
     get 
     { 
      return record; 
     } 

     set 
     { 
      record = value; 
     } 
    } 

    public void BeginUpdate() 
    { 
     if (count == 0) 
     { 
      ActiveRecord = new ChildObject(1); 

     } 

     Interlocked.Increment(ref refcount); 
    } 

    public void EndUpdate() 
    { 
     int count = Interlocked.Decrement(ref refcount); 

     if (count == 0) 
     { 
      ActiveRecord.Save(); 
     } 
    } 
    #endregion 


    #region operations 

    public void SomeFunction1() 
    { 
     BeginUpdate(); 

     try 
     { 
      ActiveRecord.Property1 = "blah!"; 
     } 
     finally 
     { 
      EndUpdate(); 
     } 
    } 

    public void SomeFunction2() 
    { 
     BeginUpdate(); 

     try 
     { 
      ActiveRecord.Property2 = "blah!"; 
     } 
     finally 
     { 
      EndUpdate(); 
     } 
    } 


    public void SomeFunction2() 
    { 
     BeginUpdate(); 

     try 
     { 
      SomeFunction1(); 
      SomeFunction2(); 
     } 
     finally 
     { 
      EndUpdate(); 
     } 
    } 
    #endregion 
0

Je pense que votre sur la bonne voie plus ou moins. Vous pouvez créer une fabrique qui crée vos objets enfants (et suivre les instances "en direct"), ou vous pouvez suivre les instances qui ont été enregistrées, de sorte que lorsque vous appelez votre méthode Save, elle reconnaît que votre première instance est ChildObject le même que votre deuxième instance de ChildObject et fait une copie profonde des données de la deuxième instance sur le premier. Les deux sont relativement non triviaux du point de vue du codage, et les deux impliquent probablement de remplacer les méthodes d'égalité sur vos entités. J'ai tendance à penser que l'utilisation de la première approche serait moins susceptible de causer des erreurs.

Une option supplémentaire consisterait à utiliser un package de mappage Obect-Relationnel existant tel que NHibernate ou Entity Framework pour effectuer votre mappage entre des objets et votre base de données. Je sais que NHibernate prend en charge Sqlite et, d'après mon expérience, a tendance à être celle qui nécessite le moins de changements dans les structures de votre entité. En suivant cette route, vous bénéficiez des instances de suivi de couche ORM pour vous (et générez du SQL pour vous), plus vous obtiendrez probablement des fonctionnalités plus avancées que votre code d'accès aux données actuel peut ne pas avoir. L'inconvénient est que ces cadres ont tendance à avoir une courbe d'apprentissage qui leur est associée, et en fonction de ce que vous allez avec, il pourrait y avoir un impact non négligeable sur le reste de votre code. Il serait donc utile de peser les avantages par rapport au coût d'apprentissage de l'infrastructure et de convertir votre code pour utiliser l'API.

+0

J'ai regardé NHibernate avant de commencer ce projet et j'ai décidé de ne pas l'utiliser car c'était à ce moment-là un projet personnel relativement petit. Il est peut-être temps de réévaluer cela! – sohum

+0

Ouais, c'est loin d'être petit en ce moment :). J'ai utilisé Linq2Sql et Entity Framework ainsi que NHibernate sur des projets auparavant, et NHibernate est de loin le gagnant pour moi, cela correspond mieux à la façon dont j'aime écrire du code. – ckramer

Questions connexes