C# 存储库设计问题

发布于 2024-10-01 23:00:51 字数 2194 浏览 6 评论 0原文

我正在为 MVC 2 Web 应用程序编写 EF4 数据层,我需要有关选择继承与抽象基类的建议。我的存储库按照“通用存储库”结构运行良好,但现在我想添加“审核”功能,该功能会在每次执行 CRUD 操作时进行记录。

这是我到目前为止一直在使用的合同:

public interface IRepository<T>
{
    void Create(T entity);
    void Update(T entity);
    void Delete(Func<T, bool> predicate);
    T Get(Func<T, bool> predicate);
    IQueryable<T> Query();
}

我的仓库。实现如下所示:

sealed class EFRepository<TEntity> : IRepository<TEntity>
    where TEntity : EntityObject
{
    ObjectContext _context;
    ObjectSet<TEntity> _entitySet;

    public EFRepository(ObjectContext context)
    {
        _context = context;
        _entitySet = _context.CreateObjectSet<TEntity>();
    }

    public void Create(TEntity entity)
    {
        _entitySet.AddObject(entity);
        _context.SaveChanges();
    }

    public void Update(TEntity entity)
    {
        _entitySet.UpdateObject(entity);
        _context.SaveChanges();
    }

    public void Delete(Func<TEntity, bool> predicate)
    {
        TEntity entity = _entitySet.Single(predicate);
        _entitySet.DeleteObject(entity);
        _context.SaveChanges();
    }

    public TEntity Get(Func<TEntity, bool> predicate)
    {
        return _entitySet.SingleOrDefault(predicate);
    }

    public IQueryable<TEntity> Query()
    {
        return _entitySet;
    }
}

我想创建 AuditableRepository 的概念。我应该这样创建它:

interface IAuditable<T>
interface IRepository<T>
AuditableRepository<T> : IRepository<T>, IAuditable<T>
EFRepository<T> : AuditableRepository<T>

还是这样更好:

interface IAuditable<T>
interface IRepository<T>
EFRepository<T> : IRepository<T>, IAuditable<T>

甚至:

interface IAuditable<T>
interface IRepository<T>
AuditableRepository<T> : IRepository<T>, IAuditable<T>
EFRepository<T> : IRepository<T>
AuditableEFRepository<T> : AuditableRepository<T>

并非我的所有 EFRepositories 都需要进行审核。我应该如何进行?

I am writing an EF4 datalayer for an MVC 2 web application and I need suggestions on choosing inheritance vs. abstract base classes. My repository has worked well following the 'generic repo' structure but now I want to add "Audit" functionality which records everytime a CRUD operation is performed.

This is the contract I've been using so far:

public interface IRepository<T>
{
    void Create(T entity);
    void Update(T entity);
    void Delete(Func<T, bool> predicate);
    T Get(Func<T, bool> predicate);
    IQueryable<T> Query();
}

My repo. implementation looks like this:

sealed class EFRepository<TEntity> : IRepository<TEntity>
    where TEntity : EntityObject
{
    ObjectContext _context;
    ObjectSet<TEntity> _entitySet;

    public EFRepository(ObjectContext context)
    {
        _context = context;
        _entitySet = _context.CreateObjectSet<TEntity>();
    }

    public void Create(TEntity entity)
    {
        _entitySet.AddObject(entity);
        _context.SaveChanges();
    }

    public void Update(TEntity entity)
    {
        _entitySet.UpdateObject(entity);
        _context.SaveChanges();
    }

    public void Delete(Func<TEntity, bool> predicate)
    {
        TEntity entity = _entitySet.Single(predicate);
        _entitySet.DeleteObject(entity);
        _context.SaveChanges();
    }

    public TEntity Get(Func<TEntity, bool> predicate)
    {
        return _entitySet.SingleOrDefault(predicate);
    }

    public IQueryable<TEntity> Query()
    {
        return _entitySet;
    }
}

I want to create the concept of an AuditableRepository<T>. Should I create it like this:

interface IAuditable<T>
interface IRepository<T>
AuditableRepository<T> : IRepository<T>, IAuditable<T>
EFRepository<T> : AuditableRepository<T>

or is it better to have it like this:

interface IAuditable<T>
interface IRepository<T>
EFRepository<T> : IRepository<T>, IAuditable<T>

or even:

interface IAuditable<T>
interface IRepository<T>
AuditableRepository<T> : IRepository<T>, IAuditable<T>
EFRepository<T> : IRepository<T>
AuditableEFRepository<T> : AuditableRepository<T>

Not all of my EFRepositories will need to be audited. How should I proceed?

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

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

发布评论

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

评论(4

无妨# 2024-10-08 23:00:51

这是另一种可能性(使用装饰器对象向现有存储库添加附加功能):

public sealed class Auditor<T> : IRepository<T>
{
    private readonly IRepository<T> _repository;

    public Auditor(IRepository<T> repository)
    {
        _repository = repository;    
    }

    public void Create(T entity)
    {
        //Auditing here...
        _repository.Create(entity);
    }

    //And so on for other methods...
}

使用装饰器添加附加功能的优点是,它避免了当您考虑某些具有审计功能的存储库而另一些没有审计时开始看到的组合爆炸。 ,有些使用 EF,有些不使用。随着每一个可能适用或可能不适用的新功能的出现,这种情况会变得越来越糟,通常最终会演变成配置标志和混乱的内部分支。

Here is another possibility (using a Decorator object to add additional functionality to an existing repository):

public sealed class Auditor<T> : IRepository<T>
{
    private readonly IRepository<T> _repository;

    public Auditor(IRepository<T> repository)
    {
        _repository = repository;    
    }

    public void Create(T entity)
    {
        //Auditing here...
        _repository.Create(entity);
    }

    //And so on for other methods...
}

The advantage to using a Decorator to add additional features is that it avoids the combinatorial explosion you began to see when you considered some repositories with auditing, some without, some using EF, some not. This gets progressively worse with every new feature that may or may not apply, often eventually devolving into configuration flags and messy internal branching.

冰葑 2024-10-08 23:00:51

存储库是否可审计重要吗?意思是,您是否需要知道存储库是 IAuditableRepository 还是只是 IRepository?如果没有,您可以使用 DI 并添加一个采用 IAuditor 的构造函数。然后在您的存储库方法中,如果 IAuditor 可用,您就可以使用它。

sealed class EFRepository<TEntity> : Repository<TEntity>
    where TEntity : EntityObject
{
    ObjectContext _context;
    ObjectSet<TEntity> _entitySet;
    IAuditor _auditor;

    public EFRepository(ObjectContext context) : this(context, null)
    {
    }
    public EFRepository(ObjectContext context, IAuditor auditor)
    {
        _context = context;
        _entitySet = _context.CreateObjectSet<TEntity>();
        _auditor = auditor; 
    }
    public override void Create(TEntity entity)
    {
        _entitySet.AddObject(entity);
        _context.SaveChanges();

        if (_auditor != null)
        {
            // audit
        }
    }

    // etc.
}

Will it matter whether a repository is auditable or not? Meaning, do you need to know if a repository is an IAuditableRepository or just an IRepository? If not, you could use DI and add a constructor that takes an IAuditor. Then in your repository methods, if an IAuditor is available, you can use it.

sealed class EFRepository<TEntity> : Repository<TEntity>
    where TEntity : EntityObject
{
    ObjectContext _context;
    ObjectSet<TEntity> _entitySet;
    IAuditor _auditor;

    public EFRepository(ObjectContext context) : this(context, null)
    {
    }
    public EFRepository(ObjectContext context, IAuditor auditor)
    {
        _context = context;
        _entitySet = _context.CreateObjectSet<TEntity>();
        _auditor = auditor; 
    }
    public override void Create(TEntity entity)
    {
        _entitySet.AddObject(entity);
        _context.SaveChanges();

        if (_auditor != null)
        {
            // audit
        }
    }

    // etc.
}
生活了然无味 2024-10-08 23:00:51

这两种解决方案都不像完美的解决方案,正如 Dan 所说,您可能会遇到不同接口组合的问题。审计功能听起来似乎也不需要成为类的一部分。我认为,我不会采用 Dan 的方法,而是在存储库上声明并触发事件。这样你就可以将很多不同的东西连接到它上面,更加灵活。因此,声明“创建”、“删除”等事件。

将来,它还可以通过响应式扩展等方式实现一些非常好的功能。

Neither feels like a perfect solution, as Dan says you might face a problem with different combinations of the interfaces. Audit features doesn't sound like it really needs to be part of the class either. I think rather than going with Dan's approach though that I'd declare and fire events in general on the repository. That way you could hook up lots of different things to it, much more flexible. So declare events like Created, Deleted etc.

In the future it also allows some really nice functionality through things like reactive extensions.

旧夏天 2024-10-08 23:00:51

首先,如果您的 Repository 没有任何通用功能(您只有抽象方法,没有实现),我要做的第一件事就是摆脱它。最好是程序的所有其他部分仅通过 IRepository接口访问存储库,这将为您以后提供更大的自由。

第二件事,拥有 EFRepository 类意味着您希望保留有一天切换到不同 ORM 的选项。我的意见是——不要这样做。如果您选择 EF,请坚持使用 EF。如果您确实认为这是一个选项,至少创建一个具有命名空间 MyApp.EntityORM 或其他名称的单独程序集,将所有存储库类放在那里并摆脱 EF前缀。然后,如果确实如此,有一天您也许可以通过依赖项注入加载正确的 ORM,而无需更改其余代码。

根据我的经验:ORM 之间可能存在细微的差异,这会阻止您以真正透明的方式交换它们,并且您几乎不需要为单个项目更改 ORM。

第二件事,我也更喜欢将基础存储库的基础功能包装在不同的类中(正如 Dan Bryant 已经说过的装饰器模式),而不是覆盖已经存在的类。原因是重写通常会导致必须处理基类的内部,这有时会变得有点混乱。

从您的问题来看,尚不完全清楚“可审核存储库”应该如何表现,但如果它只是一个普通的 IRepository (它审核 IRepository中实现的所有方法) ;),那么你不需要一个单独的接口:

interface IRepository<T>
class Repository<T> : IRepository<T>
class AuditableRepository<T> : IRepository<T>

添加到 Dan 所说的内容中,你可以有一个工厂方法来创建适当的包装器:

class AuditableRepository<T> : IRepository<T>
{
     public static IRepository<T> CreateFrom(IRepository<T> baseRepo)
     { 
          // wrap base repo in an auditable repository
          // and return it
          return new AuditableRepository<T>(baseRepo);
     }
}

First of all, if your Repository<T> doesn't have any common functionality (you only have abstract methods and no implementation there), the first thing I would do is get rid of it. It is best that all other parts of your program access the repository through the IRepository<T> interface only, that will give you greater freedom later.

Second thing, having an EFRepository class implies that you want to leave an option to switch to a different ORM one day. My opinion is - don't do it. If you chose EF, stick to EF. If you really think this is an option, at least create a separate assembly with a namespace MyApp.EntityORM or whatever, put all your Repository classes there and get rid of the EF prefix. Then, if it ever comes to that, you can perhaps load the right ORM through dependency injection some day, without changing the rest of your code.

From my experience: there can be subtle differences between ORM's which prevent you from interchanging them in a truly transparent way, and there is little chance you will need to ever change your ORM for a single project.

Second thing, I also prefer wrapping base functionality of a base repository in a different class (the Decorator pattern as Dan Bryant already said), as opposed to overriding an already existing class. The reason is that overriding often leads to having to deal with internals of the base class, and this can sometimes get a bit messy.

From your question, it is not completely clear how an "auditable repository" should behave, but if it is just a plain IRepository<T> (it audits all methods implemented in a IRepository<T>), then you don't need a separate interface:

interface IRepository<T>
class Repository<T> : IRepository<T>
class AuditableRepository<T> : IRepository<T>

Adding to what Dan said, you could have a factory method which would create the appropriate wrapper:

class AuditableRepository<T> : IRepository<T>
{
     public static IRepository<T> CreateFrom(IRepository<T> baseRepo)
     { 
          // wrap base repo in an auditable repository
          // and return it
          return new AuditableRepository<T>(baseRepo);
     }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文