【问题标题】:Hierarchical POCO objects & Repositories分层 POCO 对象和存储库
【发布时间】:2012-01-02 19:57:00
【问题描述】:

假设我有一个与物理模型 1:1 对应的域模型。另外:

  • 物理模型中的所有表都以列名“Id”作为主键
  • 许多表都有“LastChanged”时间戳列
  • 某些表格包含本地化数据

目标:创建领域模型类 (POCO) 和适当的存储库。工具——VS2010、EF4.1

最明显的方法是从数据库生成 EF 模型,然后在其上运行 T4 POCO 生成器。输出是一组 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 代码生成,它为大量手动工作开辟了一个表面。

如果您让我知道您正在考虑以下问题,我将不胜感激:

  • 您如何看待,代码之美真的值得手动实现它的麻烦吗?
  • 还有其他方法可以消除代码重复吗?

【问题讨论】:

  • 您是开发人员 = 您负责编写代码,而这正是您所做的!如果您想通过代码重复生成代码。
  • 这是一个很好的观点;)但有时我不想手动编写代码 - 根本没有复杂/业务逻辑,DTO,代理等。我认为没有人不同意这些最好用一些工具来完成。
  • 存储库是特定的东西。正确的存储库实现对每个实体都有特定的方法,因此您无法生成它。
  • 同意。但是在这种情况下,存储库的正确(无代码重复等)实现依赖于具有某些特定字段的 POCO,例如“Id”、“ResourceId”(可以通过通用约束和一些基本功能(例如 RepositoryBase)指定)在整体解决方案中找到了自己的位置)。这又需要手动实施 POCO。如果可能的话,我想避免。听起来我想要很多? :) 毕竟这不是什么不常见的问题,但到目前为止我还没有看到任何体面的解决方案。 ——

标签: entity-framework repository-pattern poco domain-model


【解决方案1】:

如果您最关心的是手动实现 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,您将不需要定义这些部分部分。

【讨论】:

  • 我怎么能忘记旧的好部分课程!? :) 非常感谢!附: “T4 修改”和“接口解析”是指修改 T4 以便它向 POCO 类声明添加一个接口(以某种方式从类名派生接口名)到 POCO 类声明?
  • 是的,通过修改 T4 模板,我的意思是模板内部的一些自定义逻辑将检查实体是否满足接口定义 - 这可以通过检查它是否具有正确的属性来实现。
【解决方案2】:

如果您正在生成代码,可以复制,只是不要触摸生成的代码,您会没事的。

【讨论】:

  • 对 POCO 来说没问题。那么回购呢?毕竟我不生成它们,它们是手动创建的->代码重复以防不使用层次结构/模板。
猜你喜欢
  • 1970-01-01
  • 2021-12-31
  • 1970-01-01
  • 2011-07-05
  • 1970-01-01
  • 2023-03-31
  • 1970-01-01
  • 1970-01-01
  • 2019-05-01
相关资源
最近更新 更多