Иерархические объекты POCO и хранилища - PullRequest
0 голосов
/ 21 ноября 2011

Предположим, у меня есть модель предметной области, которая имеет соответствие 1: 1 физической модели.Также:

  • все таблицы в физической модели имеют имя столбца 'Id' в качестве первичного ключа
  • Многие из таблиц имеют столбец отметки времени LastChanged
  • Некоторые таблицы содержат локализованные данные

Цель : создать классы модели предметной области (POCO) и соответствующие репозитории.Инструменты - VS2010, EF4.1

Наиболее очевидный подход состоит в том, чтобы сгенерировать модель EF из БД и затем запустить над ней генератор POCO T4.Вывод представляет собой набор классов POCO.

//POCO classes
public class Country
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime LastModified { get; set; }
    ...
}

public class User
{
    public int Id { get; set; }
    public string FirtsName { get; set; }
    public DateTime LastModified { get; set; }
    ...
}

Пока все хорошо, но мы сталкиваемся с некоторыми дублирующими кодами при реализации репозиториев.И на уровне определения интерфейса:

public interface ICountryRepository
{
    IEnumerable<Country> FindAll();

    Country FindById(int id);

    void Add(Country country);

    void Delete(Country country);
}

//Here the ONLY difference is the type of the entity
public interface IUserRepository
{
    IEnumerable<User> FindAll();

    User FindById(int id);

    void Add(User user);

    void Delete(User user);
}

И на уровне реализации:

class CountryRepository : ICountryRepository
{
    IEnumerable<Country> FindAll()
    {
        //implementation
    }

    Country FindById(int id)
    {
        //find by id logic
    }

    void Add(Country country)
    {
        //logic
    }

    void Delete(Country country)
    {   
        //logic
    }
}

class UserRepository : IUserRepository
{
    IEnumerable<User> FindAll()
    {
        //the only difference in the implementation
        //is the type of returned entity
    }

    User FindById(int id)
    {
        //the only difference in the implementation
        //is the type of returned entity
    }

    void Add(User user)
    {
        //the only difference in the implementation
        //is the type of returned entity
    }

    void Delete(User user)
    {
        //the only difference in the implementation
        //is the type of returned entity
    }
}

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

Создать иерархию объектов POCO:

public class  EntityBase
{
    public int Id { get; set; }
}

public class TrackableEntity : EntityBase
{
    public DateTime LastChanged { get; set; }
}

public class LocalizedEntity : TrackableEntity
{
    public int ResourceId { get; set; }
}

public class Country : LocalizedEntity
{
}

И затем иерархию хранилища с базовой реализацией:

public interface IRepository<TEntity> where TEntity : EntityBase
{
    IEnumerable<TEntity> FindAll();

    TEntity FindById(int id);

    void Add(TEntity entity);

    void Delete(TEntity entity);
}

public interface ILocalizedRepository<TEntity> : IRepository<TEntity> where TEntity : LocalizedEntity
{
    IEnumerable<TEntity> FindByCultureIso2(string cultureIso2);
}

public interface ICountryRepository : ILocalizedRepository<Country>
{
}

internal class RepositoryBase<TEntity> : IRepository<TEntity> where TEntity : EntityBase
{
    private readonly IObjectSet<TEntity> _objectSet;

    public RepositoryBase(ObjectContext database)
    {
        _objectSet = database.CreateObjectSet<TEntity>();
    }

    protected virtual IQueryable<TEntity> All()
    {
        return _objectSet;
    }

    public virtual IEnumerable<TEntity> FindAll()
    {
        return All().ToList();
    }

    public virtual TEntity FindById(int id)
    {
        return All().Where(entity => entity.Id == id).SingleOrDefault();
    }

    public virtual void Add(TEntity entity)
    {
        _objectSet.AddObject(entity);
    }

    public virtual void Delete(TEntity entity)
    {
        _objectSet.DeleteObject(entity);
    }
}

internal class LocalizedRepositoryBase<TEntity> : RepositoryBase<TEntity>, ILocalizedRepository<TEntity> where TEntity : LocalizedEntity
{
    public LocalizedRepositoryBase(ObjectContext database) : base(database)
    {
    }

    protected override IQueryable<TEntity> All()
    {
        return (base.All() as ObjectSet<TEntity>).Include("Resource.LocalizedResources.Culture");
    }

    public IEnumerable<TEntity> FindByCultureIso2(string cultureIso2)
    {
        IEnumerable<TEntity> entities = All().Where(...);

        return entities.ToList();
    }
}

internal class CountryRepository : LocalizedRepositoryBase<Country>, ICountryRepository
{
    public CountryRepository(ObjectContext database) : base(database)
    {
    }
}

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

Но этот сценарий вряд ли поддается генерации кода T4, который открывает поверхность для большого объема ручной работы.

Буду признателен, если вы дадите мне знать, что вы думаете о следующем:

  • Как вы думаете, действительно ли красота кода действительно стоит того, чтобы реализовать его вручную?
  • Есть ли другие способы избавиться от дублирования кода?

Ответы [ 2 ]

2 голосов
/ 21 ноября 2011

Если вас больше всего беспокоит ручная реализация POCO, используйте интерфейсы для определения общих функций вместо базовых классов:

public interface IEntityBase
{
    int Id { get; set; }
}

public interface ITrackableEntity : IEntityBase
{
    DateTime LastChanged { get; set; }
}

public interface ILocalizedEntity : ITrackableEntity
{
    int ResourceId { get; set; }
}

Теперь вам не нужно внедрять POCO вручную.Генератор создаст объекты для вас, и вам нужно будет только определить свою частичную часть для каждого объекта, чтобы определить, какие интерфейсы он реализует.

public partial class Country : ILocalizedEntity
{ } 

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

0 голосов
/ 21 ноября 2011

Если вы генерируете код, все в порядке, просто не трогайте сгенерированный код, и все будет в порядке.

...