分层 POCO 对象 &存储库

发布于 2024-12-17 02:53:08 字数 4929 浏览 1 评论 0 原文

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

  • 物理模型中的所有表都将列名“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 代码生成,因为 T4 代码生成为大量手动工作开辟了空间。

如果您让我知道您对以下问题的想法,我将不胜感激:

  • 您认为代码的美丽是否真的值得手动实现它?
  • 还有其他方法可以消除代码重复吗?

Suppose I have a domain model that has 1:1 correspondence to the physical model. Also:

  • all the tables in the physical model has the column name 'Id' as a primary key
  • Many of the tables have 'LastChanged' timestamp column
  • Some of the tables contain localized data

Objective: create domain model classes (POCO) and appropriate repositories. Tools - VS2010, EF4.1

The most obvious approach to take is to generate EF model from the DB and then run T4 POCO Generator over it. The output is a set of POCO classes.

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

So far so good but we come across some code duplications when implementing repositories. Both on the interface definition level:

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

And on the implementation level:

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

Taking into account that most of the code above can be written more generically, we can also take the following approach.

Create a POCO objects hierarchy:

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

And then repository hierarchy with a base implemetation:

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)
    {
    }
}

The compelling advantage of the latter approach is that the code is much more structured which allows to avoid code duplication.

But this scenario is hardly amenable to T4 code gen which opens up a surface for a large amount of manual work.

I would appreciate if you let me know you thinking on the following:

  • How do you think, is the beauty of the code really worth the trouble of implementing it manually?
  • Is there any other ways to get rid of the code duplication?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

厌倦 2024-12-24 02:53:08

如果您最关心的是 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 添加接口分辨率,而无需定义那些部分部分。

If your biggest concern is manual implementation of POCOs use interfaces to define shared features instead of base classes:

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

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

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

Now you don't need to implement POCOs manually. Generator will create entities for you and you will only need to define your partial part for each entity to define which interfaces it implements.

public partial class Country : ILocalizedEntity
{ } 

If you are happy with T4 modification you can even add interface resolution directly to T4 and you will not need to define those partial parts.

何以心动 2024-12-24 02:53:08

如果您正在生成代码,则可以复制,只需不要触摸生成的代码就可以了。

If you are generating code it's ok to duplicate, just don't touch generated code and you'll be just fine.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文