Шаблон репозитория и наследование в .net - PullRequest
6 голосов
/ 27 апреля 2011

Я довольно плохо знаком с шаблоном проектирования хранилища, и я зашел в тупик, пытаясь реализовать его в отношении наследования.

Я не уверен, даже если я начал в правильном направлении.

Так что в основном у меня будет абстрактный базовый класс Product, например, с id и imagePath, и у меня будет несколько продуктов, которые наследуются от него.

namespace Common
{
    public abstract class Product
    {
        public int Id { get; set; }
        public string ImgPath { get; set; }
    }

    public class Scale : Product
    {
        public int AdditionalProperty { get; set; }
    }
}

Теперь репозитории выглядят следующим образом:

public class BaseRepository
{
    protected TEstEntities1 _dataContext = new TEstEntities1();

    public BaseRepository()
    {
        _dataContext = new TEstEntities1();
    }
}

public interface IProductRepository 
{
    Common.Product Get(int id);
    void Add(Common.Product p);
    void Update(Common.Product p);
    List<Common.Product> ListAll();
}

public class ProductRepository : BaseRepository, IProductRepository
{
    public Common.Product Get(int id)
    {
        throw new NotImplementedException();
    }

    public void Add(Common.Product p)
    {
        throw new NotImplementedException();
    }

    public void Update(Common.Product p)
    {
        throw new NotImplementedException();
    }

    public List<Common.Product> ListAll()
    {
        throw new NotImplementedException();
    }
}

Моя проблема заключается в следующем: как интегрировать операции, связанные с масштабированием?Кажется плохой идеей добавить что-то вроде Add (Common.Scale s) в IProductRepository.Кажется плохой идеей увидеть внутри Add (Common.Product p), какой тип продукта я пытаюсь добавить, затем привести к нему, затем добавить.

Я думаю, что если бы я описал эту проблемуТочнее, я хочу повторить как можно меньше кода, чтобы каким-то образом изолировать базовый продукт, добавляющий / удаляющий код в репозитории продукта, и каким-то образом поместить, например, специальный код Scale для добавления / удаления внутри другого класса или метода.

Более тщательный мой подход был такой:

public interface IProductRepository<T> where T : Common.Product
{
    T Get(int id);
    void Add(T p);
    void Delete(T p);
}

public abstract class ProductRepository : BaseRepository
{
    protected void Add(Common.Product p)
    {
        _dataContext.AddToProduct(new Product { Id = p.Id, Image = p.ImgPath });
        _dataContext.AcceptAllChanges();
    }

    protected void Delete(Common.Product p)
    {
        var c = _dataContext.Product.Where(x => x.Id == p.Id).FirstOrDefault();
        _dataContext.DeleteObject(c);
        _dataContext.AcceptAllChanges();
    }

    protected Product Get(int id)
    {
        return _dataContext.Product.Where(x => x.Id == id).FirstOrDefault();
    }
}

public class CantarRepository : ProductRepository, IProductRepository<Common.Scale>
{
    public void Add(Common.Scale p)
    {
        base.Add(p);
        _dataContext.Scale.AddObject
             (new Scale { ProductId = p.Id, AdditionalProperty = p.AdditionalProperty });
        _dataContext.AcceptAllChanges();
    }

    public void Delete(Common.Scale p)
    {
        var c = _dataContext.Scale.Where(x => x.ProductId == p.Id);
        _dataContext.DeleteObject(c);
        _dataContext.AcceptAllChanges();
        base.Delete(p);
    }

    public new Common.Scale Get(int id)
    {
        var p = base.Get(id);
        return new Common.Scale
        {
            Id = p.Id,
            ImgPath = p.Image,
            AdditionalProperty = _dataContext.Scale.Where
               (c => c.ProductId == id).FirstOrDefault().AdditionalProperty
        };
    }
}

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

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

Мне любопытно, что я делаю неправильно, как я могу решить эту проблему и как я могу реализовать это лучше.

Спасибо.

Ответы [ 3 ]

4 голосов
/ 28 апреля 2011

Вы неправильно используете наследование. Вы не можете рассматривать Scale как (is-a) Product, если важным отличием являются дополнительные свойства - это отличает открытый интерфейс Scale от Product, и в этот момент наследование просто мешает вам. Используйте наследование, чтобы поделиться поведением , а не свойствами .

Какую проблему вы пытаетесь решить с помощью наследования?

Я хочу повторить как можно меньше кода возможно

Не лучше ли сделать небольшое дублирование, чтобы добиться цели, вместо того, чтобы крутить колеса, пытаясь поработать с невероятным дизайном?

Кроме того, это все, чем вы делитесь со своим наследством:

    public int Id { get; set; }
    public string ImgPath { get; set; }

Повторение определения двух автоматически реализованных свойств почти даже не квалифицируется как дублирование, это, безусловно, не является проблемой для беспокойства.

Злоупотребление наследованием, однако, довольно печально. Следующий человек, обслуживающий ваше приложение, проклинает вас за это.

Так что в основном у меня будет аннотация Базовый класс Product, с идентификатором и imagePath, например, и будет иметь несколько продуктов, которые наследуются от это.

Так, когда вы добавляете новые типы продуктов, вам придется расширять иерархию наследования? Это кажется плохой идеей.

3 голосов
/ 27 апреля 2011

Я не большой поклонник универсального репозитория, но, посмотрев на ваш код, думаю, вы должны его использовать:

public interface IEntity
{
    int Id { get; }
}

public interface IRepository<T> where T : class, IEntity 
{
    IQueryable<T> GetQuery();
    T Get(int id);
    void Add(T entity);
    void Update(T entity);
    void Delete(T entity);
}

реализация:

public Repository<T> where T : class, IEntity
{
    private ObjectSet<T> _set;          // or DbSet
    private ObjectContext _context;  // or DbContext

    public Repository(ObjectContext context) // or DbContext
    {
        _context = context;
        _set = context.CreateObjectSet<T>();  // _context.Set<T>() for DbContext                     
    }

    public IQueryable<T> GetQuery()
    {
        return _set;
    }

    public T Get(int id)
    {
        return _set.SingleOrDefault(e => e.Id == id);
    }

    public void Add (T entity)
    {
        _set.AddObject(entity);
    }

    public void Update(T entity)
    {
        _set.Attach(entity);
        context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
        // or context.Entry(entity).State = EntityState.Modified; for DbContext
    }

    public void Delete(entity)
    {
        _set.Attach(entity);
        _set.DeleteObject(entity);
    }
}

Существует НЕТ AcceptAllChanges , потому что это сбросит ObjectStateManager и ваши изменения никогда не будут сохранены. Там нет воссоздания объектов, потому что это не имеет смысла.

Использовать этот репозиторий так же просто, как:

var repo = new BaseRepository<Product>(context);
repo.Add(new Product() { ... });
repo.Add(new Scale() { ... }); // yes this works because derived entities are handled by the same set
context.Save();
1 голос
/ 27 апреля 2011

Я недавно реализовал нечто подобное. (Используя названия типов ваших образцов) У меня есть один ProductRepository, который знает, как сохранить / отменить отображение всех Product подтипов.

В конечном итоге ваше хранилище данных должно иметь возможность хранить различные подтипы и любые свойства, которые они вводят. Ваш тип репозитория также должен знать, как воспользоваться этими функциями для каждого данного подтипа. Поэтому каждый раз, когда вы добавляете подтип, вы будете заниматься, например, добавлением столбцов таблицы в ваше хранилище данных для хранения свойств, которые оно может ввести. Это подразумевает, что вам также нужно будет внести соответствующие изменения в ваш тип репозитория. Поэтому проще всего проверить тип сущности при передаче в тип репозитория и выдать исключение, если это не поддерживаемый тип, поскольку больше ничего не сможет с этим сделать ваш репозиторий. Аналогично, при получении списка сущностей хранилище должно знать, как извлечь каждый подтип сущности и создать экземпляр. Поскольку все они получены из Product, все они могут формировать элементы с возвращаемым значением IEnumerable<Product>.

...