2010-06-25 3 views
1

Я точно не знаю, как описать этот вопрос, но здесь. У меня есть иерархия классов объектов, которые отображаются в базе данных SQLite. У меня уже есть нетривиальный код, который связывается между объектами .NET и базой данных.Инициализация конструктора из хранимого кеша в C#

У меня есть базовый интерфейс следующим образом:

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

Это основные операции CRUD на любом объекте. Затем я реализовал базовый класс, который инкапсулирует большую часть функциональности.

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 
} 

Теперь, наконец, у меня есть свои конкретные дочерние объекты, каждый из которых имеет свои собственные таблицы в базе данных:

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

Это прекрасно работает для всех моих целей до сих пор. У ребенка есть несколько методов обратного вызова, которые используются базовым классом для правильного создания данных.

Теперь я хочу сделать это немного эффективным. Например, в следующем коде:

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(); 
} 

В этом случае, я буду строить две совершенно новую инстанциацию памяти и в зависимости от порядка SomeFunction1 и SomeFunction2 называют, либо property1 или свойство2 не может быть сохранен. То, что я хочу достичь, - это способ для обоих этих экземпляров как-то указывать на одно и то же место памяти - я не думаю, что это будет возможно, если я использую «новое» ключевое слово, поэтому я искал намеки относительно как действовать.

В идеале я хотел бы хранить кеш всех загруженных объектов в классе ABackendObject и возвращать ссылки на уже загруженные объекты по запросу или загружать объект из памяти, если он еще не существует, и добавить его к кешу. У меня есть много кода, который уже использует эту структуру, поэтому мне, конечно, придется изменить много вещей, чтобы заставить это работать, но я просто хотел получить несколько советов о том, как действовать.

Спасибо!

+0

Я не совсем понимаю, в чем проблема. Если вы вызываете Save() сразу после каждого изменения свойства, то это немедленно возвращает это изменение в базу данных? Если это так, не будет ли новое значение в следующий раз при создании нового объекта ChildObject? – tlayton

+0

Я не называю Save() после каждого изменения свойства, так как объекты могут быть частью длительных процессов. Кроме того, основная проблема заключается в том, что объекты могут создаваться в нескольких разных областях (поскольку мои объекты имеют ассоциации и могут быть связаны друг с другом). В этом случае мне приходилось ударять диск каждый раз, когда я загружаю объект, что было бы еще одним хитом производительности. – sohum

ответ

6

Если вы хотите сохранить «кеш» загруженных объектов, вы можете просто просто сохранить в каждом типе Dictionary<int, IBackendObject>, который содержит загруженные объекты с ключом по их идентификатору.

Вместо того чтобы использовать конструктор, строить фабричный метод, который проверяет кэш:

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; 
    } 
} 

Если сделать базовый класс родовым, и читать виртуальными, вы должны быть в состоянии обеспечить большую часть этой функциональности без особого дублирование кода.

+0

+1 Черт ... ты печатаешь слишком быстро. Я был так близко. Ха-ха –

+0

Это выглядит красиво и изысканно. У меня был только один вопрос: я могу сделать некоторые из этих промежуточных функций статичными. Из вашего примера кода я предполагаю, что каждый «ChildObject» будет реализовывать методы «CheckCache» и «SaveToCache» ('Read' уже реализован' ABackendObject' с обратными вызовами в каждый 'ChildObject'). Было бы неплохо, если бы я мог называть 'ChildObject.LoadFromDB (id)' вместо 'new ChildObject(). LoadFromDB (id)'. Любые предложения на этом фронте? В противном случае это выглядит великолепно! – sohum

+0

@sohum: вы должны иметь возможность сделать большинство этих статических. Я бы поставил CheckCache и SaveToCache в базовый класс - он должен был быть общим, и вы бы поместили фактический тип в подкласс: [public class ChildObject: ABackendObject {...] –

2

Что вы хотите, это фабрика объектов. Сделайте конструктор ChildObject приватным, затем напишите статический метод ChildObject.Create(int index), который возвращает ChildObject, но который внутренне гарантирует, что разные вызовы с одним и тем же индексом возвращают один и тот же объект. Для простых случаев достаточно простого статического хэша объекта index =>.

+0

Я пошел вперед и реализовал это, и он работает как сон. Единственная проблема - мне пришлось изменить все мои «ChildObjects», которых около 20. Я полагаю, что все в порядке, пока мне не нужно менять его снова! – sohum

+0

Использование предложения Рида Копси может помочь вам сократить объем дублирования кода. –

0

Звуки идеально подходит для счетчика ссылок, как это ...

#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

Я думаю, что ваш на правильном пути более или менее. Вы можете либо создать фабрику, которая создает дочерние объекты (и может отслеживать «живые» экземпляры), либо вы можете отслеживать экземпляры, которые были сохранены, так что, когда вы вызываете метод Save, он распознает, что ваш первый экземпляр ChildObject то же, что и ваш второй экземпляр ChildObject, и выполняет глубокую копию данных со второго экземпляра до первого. Оба они довольно нетривиальны с точки зрения кодирования, и оба, вероятно, включают в себя переопределение методов равенства на ваших сущностях. Я склонен думать, что использование первого подхода вряд ли приведет к ошибкам.

Еще один вариант - использовать существующий пакет отображения Obect-Relational, такой как NHibernate или Entity Framework, чтобы сделать ваше сопоставление между объектами и вашей базой данных. Я знаю, что NHibernate поддерживает Sqlite, и по моему опыту, как правило, требуется минимальное количество изменений в структурах сущности. По ходу этого маршрута вы получаете преимущество экземпляров отслеживания уровня ORM для вас (и генерируете SQL для вас), плюс вы, вероятно, получите некоторые дополнительные функции, которые может отсутствовать в вашем текущем коде доступа к данным. Недостатком является то, что эти рамки, как правило, имеют связанную с ними кривую обучения, и в зависимости от того, с чем вы сталкиваетесь, это может оказать незначительное влияние на остальную часть вашего кода. Таким образом, было бы полезно оценить преимущества использования стоимости обучения и преобразовать ваш код в API.

+0

Я посмотрел на NHibernate перед тем, как начать этот проект, и решил не использовать его, поскольку в этот момент это был относительно небольшой персональный проект. Возможно, пора переоценить это! – sohum

+0

Да, на данный момент это далеко не маленький :). Я использовал Linq2Sql и Entity Framework, а также NHibernate для проектов раньше, и NHibernate на сегодняшний день является для меня победителем, он просто лучше подходит для того, как мне нравится писать код. – ckramer