【问题标题】:Dependency Injection - where to put my repository object依赖注入 - 将我的存储库对象放在哪里
【发布时间】:2013-11-27 16:57:35
【问题描述】:

我目前正在将大型 Web 服务转换为使用存储库模式和依赖注入。我们正在扩大我们的团队,可靠的单元测试带来的好处超过了重构代码所需的努力。

根据同事的推荐,我选择了 Ninject 作为我的框架,并开始重构我的代码。这涉及创建一个包含对象本身的“Common”项目、一个包含数据访问逻辑的 Repository.Database 项目,以及一个使用这两者的 Web 服务。我使用了基于约定的映射,因此 IPersonRepository 应该映射到我的具体 PersonRepository 类。

我目前正在使用 [Inject] 属性在每个类上创建一个“存储库”属性的方法,然后替换我的构造函数以使用所述存储库,但遇到了我的第一个绊脚石并且我不相信我在做事情正确的方式。在我开始这一切之前,我会像这样实例化一个对象:

var p = new Person(ID);

使用我建议的格式,我的班级看起来像这样:

[Inject]
public IPersonRepository Repository { get; set; }

public string Name;

public Person(int ID)
{
    // This feels wrong
    var p = Repository.Get(ID);
    Name = p.Name;
}

您可能会看到我的难题。如何使用构造函数而不必从存储库返回新对象,然后将每个字段映射到当前对象?我不能替换“this”,虽然我可以使用 AutoMapper 之类的东西一次性映射每个字段,但感觉就像我在这里做的事情本身就是错误的。

我可以使用静态方法代替注入器:

[Inject]
public static IPersonRepository Repository { get; set; }

public string Name;

public static Person GetByID(int ID)
{
    return Repository.Get(ID);
}

但是正如您所看到的,它需要将 Repository 设为静态,并且感觉我应该使用构造函数而不是静态的“GetByID”方法。那可能只是因为我太习惯使用构造函数了。

或者,我可以将 Repository 传递给 Person 构造函数,但是每次我在代码中实例化 Person 时,都会感觉很麻烦。

我想要实现的是让我现有的 WCF 项目使用一个存储库加载其所有数据,并让我的单元测试项目使用另一个存储库加载其所有数据。我不想在其中传递 IPersonRepository 的具体实现。这是可以实现的,甚至是推荐的吗?

【问题讨论】:

  • 您的域模型对象不应依赖于您的存储库
  • 该类依赖于 IPersonRepository 但不依赖于它的实现。这不是可以接受的吗?
  • 不,这不好。 Person 不应该知道 IPersonRepository

标签: c# dependency-injection ninject repository-pattern


【解决方案1】:

您的实体不需要知道它们的存储位置或存储方式。存储库模式的想法是将持久性的责任从业务逻辑中移开。实际上,这意味着您将按如下方式设置服务:

  • 为每个“顶级”实体创建单独的存储库
  • 将存储库注入到您的 Web 服务/控制器/业务对象中
  • 将您的实体定义为具有状态和行为的常规业务对象
  • 让 Web 服务等使用存储库通过 ID 根据需要获取实体
  • 您不应该使用业务逻辑中的 ID 调用构造函数...这是一种糟糕的模式
  • 您仍将使用构造函数来实例化一个新实体,然后将其提交到存储库,例如personRepository.Add(new Person("Bob"))

这将具有额外的好处,即通过注入模拟存储库使您的 Web 服务可测试,而不必担心如何在您的代码本身中检索实体。

【讨论】:

  • 我认为你的答案和项目符号有一些冲突,特别是“存储库模式的想法是将持久性的责任从业务逻辑中移开。”,但是在项目符号 #2 中你说: “将存储库注入到您的 Web 服务/控制器/业务对象中”——将存储库注入业务对象不会直接与将持久性责任从业务逻辑中分离出来相冲突吗?
  • 我不认为它有冲突,但我想你可以这样理解“责任”......我只是说业务对象应该能够启动持久性,而不必担心如何或持久性实际上起作用的地方。如果效果更好,您可以将“责任”替换为“实施细节知识”
【解决方案2】:

我创建了一个小助手类:“EntityResolver”,它实现了最近在 AutoMapper 中引入的“IValueResolver”接口。当提供 Id 时,此帮助程序类可以从存储库中检索实体。

1) 将 ViewModel 映射到 Entity 的 AutoMapper 配置定义如下:

 Mapper.CreateMap<EmployeeVM, Employee>()
    .ForMember(e => e.EmployeeNumber, opt => opt.MapFrom(vm => vm.Number))
    // Other propeties omitted
    .ForMember(e => e.Company, opt => opt.ResolveUsing<EntityResolver<Company>>().FromMember(vm => vm.CompanyId))
    ;

2) EntityResolver 的代码

public class EntityResolver<TEntity> : IValueResolver where TEntity : class, IEntity, new()
{
    public ResolutionResult Resolve(ResolutionResult source)
    {
        return source.New(ResolveObject(source));
    }

    private object ResolveObject(ResolutionResult source)
    {
        if (!source.Context.Options.Items.ContainsKey("Services")) return null;

        var services = (List<object>)source.Context.Options.Items["Services"];
        var item = services.FirstOrDefault(s => s is IBaseService<TEntity>);
        if (item == null) return null;

        var id = (long)source.Value;
        if (id <= 0) return null;

        var service = (IBaseService<TEntity>)item;
        return service.GetById(id);
    }
}

3) 在将 ViewModel 映射到实体时,我提供了额外的 IMappingOperationOptions

Mapper.Map<TEntity>(viewModel, opt => opt.Items["Services"] = GetServices());

4) GetServices 方法只返回解析 Employee 对象中使用的实体所需的所有服务。

protected override List<object> GetServices()
{
    var services = base.GetServices();
    services.Add(_companyService);
    services.Add(_functionService);
    services.Add(_subfunctionService);
    services.Add(_countryService);

    return services;
}

更多详情请见my test project

【讨论】:

    猜你喜欢
    • 2016-07-23
    • 2012-05-27
    • 1970-01-01
    • 1970-01-01
    • 2018-10-15
    • 2016-04-06
    • 1970-01-01
    • 2018-05-02
    • 1970-01-01
    相关资源
    最近更新 更多