商洛市网站建设_网站建设公司_建站流程_seo优化
2025/12/17 23:00:46 网站建设 项目流程

一、充血模型和失血模型

1. 充血模型的优势

充血模型更加OOP

充血模型代码可读性更好

1.1 充血模型伪代码

var messageDto = controller.ReadDto();

var message = messageDto.ToEntity();

message.Save();

1.2 失血模型伪代码

var messageDto = controller.ReadDto();

var message = messageDto.ToEntity();

var messageService = controller.GetMessageService();

messageService.Save(message);

2. 充血模型爱你不容易

大部分程序员都知道充血模型好,想实现却很难

大部分业务逻辑都需要依赖外部服务

充血模型需要用到外部服务,又不想依赖外部服务的具体实现

很容易想到使用IOC的依赖注入来解决

我们给DTO注入服务还行,因为IOC参与了controller过程

当DTO发生转化时,新增的服务IOC还是有点力不从心

没法引用外部服务的充血模型气血不通,业务表达能力大大下降

这也是大部分人弃用充血模型的主要原因,不好用还不如不用

这个任督二脉PocoEmit可以帮你打通

二、首先来个Case演示一下

Dto转化为实体

但是实体有更多逻辑依赖外部服务,这些外部服务Dto不见得提供的了

这就需要注入

PocoEmit支持构造函数参数注入和属性注入

IMapper对象是默认支持注入的服务

1. Entity比Dto多出来的Mapper可以注入

class MessageDto

{

public string Message { get; set; }

}

class MessageEntity

{

public IMapper Mapper { get; set; }

public string Message { get; set; }

}

2. 转化并注入的代码

var mapper = Mapper.Create();

var dto = new MessageDto { Message = "Hello UseMapper" };

MessageEntity message = mapper.Convert<MessageDto, MessageEntity>(dto);

Assert.NotNull(message.Mapper);

三、再演示注入自定义的服务

1. UserDomain比Dto多出来的Repository可以注入

class UserDTO

{

public int Id { get; set; }

public string Name { get; set; }

}

class UserDomain(UserRepository repository, int id, string name)

{

private readonly UserRepository _repository = repository;

public UserRepository Repository

=> _repository;

public int Id { get; } = id;

public string Name { get; } = name;

// ...

}

class UserRepository

{

void Add(UserDomain user) { }

void Update(UserDomain entity) { }

void Remove(UserDomain entity) { }

public static readonly UserRepository Instance = new();

}

2. 注册、转化并注入的代码

通过UseDefault可以注入服务

IMapper mapper = Mapper.Create()

.UseDefault(UserRepository.Instance);

var dto = new UserDTO { Id = 1, Name = "Jxj" };

UserDomain user = mapper.Convert<UserDTO, UserDomain>(dto);

Assert.NotNull(user.Repository);

四、注入IOC容器的Case

注入IOC容器需要安装nuget包PocoEmit.ServiceProvider

1. 包含IOC容器的实体

class UserWithServiceProvider

{

public int Id { get; set; }

public string Name { get; set; }

public IServiceProvider ServiceProvider { get; set; }

}

2. 注册、转化并注入的代码

UseSingleton是把容器作为唯一容器注入

UseScope是使用当前Scope子容器

UseContext是在Mvc下,使用当前HttpContext的RequestServices子容器

var services = new ServiceCollection();

var serviceProvider = services.BuildServiceProvider();

var mapper = Mapper.Create();

mapper.UseSingleton(serviceProvider);

var dto = new UserDTO { Id = 1, Name = "Jxj" };

UserWithServiceProvider user = mapper.Convert<UserDTO, UserWithServiceProvider>(dto);

Assert.NotNull(user.ServiceProvider);

五、当然还可以注入容器内的服务

1. UserDomain多出来的Repository需要注入

这次我们用IOC来管理Repository

这样才能更好的利用依赖注入

Repository可能还会依赖其他的服务

手动维护服务对象可能会很麻烦,IOC容器擅长维护这些复杂关系

class UserDomain(UserRepository repository, int id, string name)

{

private readonly UserRepository _repository = repository;

public UserRepository Repository

=> _repository;

public int Id { get; } = id;

public string Name { get; } = name;

// ...

}

class UserRepository

{

void Add(UserDomain user) { }

void Update(UserDomain entity) { }

void Remove(UserDomain entity) { }

}

2. 注册、转化并注入的代码

通过UseScope注入IOC容器

通过UseDefault告知这个类型从IOC容器中注入

var services = new ServiceCollection()

.AddScoped<UserRepository>();

var serviceProvider = services.BuildServiceProvider();

var mapper = Mapper.Create();

mapper.UseScope(serviceProvider)

.UseDefault<UserRepository>();

var dto = new UserDTO { Id = 1, Name = "Jxj" };

UserDomain user = mapper.Convert<UserDTO, UserDomain>(dto);

Assert.NotNull(user.Repository);

六、支持IOC容器的特性

支持FromKeyedServices

支持FromServices

1. FromKeyedServices标记注入点和服务键

class UserDomain1([FromKeyedServices("User1")]UserRepository repository, int id, string name)

: UserDomain(repository, id, name)

{

}

class UserDomain2([FromKeyedServices("User2")] UserRepository repository, int id, string name)

: UserDomain(repository, id, name)

{

}

class UserDomain(UserRepository repository, int id, string name)

{

private readonly UserRepository _repository = repository;

public UserRepository Repository

=> _repository;

public int Id { get; } = id;

public string Name { get; } = name;

// ...

}

class UserRepository(string tableName)

{

private readonly string _tableName = tableName;

public string TableName

=> _tableName;

void Add(UserDomain user) { }

void Update(UserDomain entity) { }

void Remove(UserDomain entity) { }

}

2. 注册、转化并注入的代码

由于识别出FromKeyedServices,就不需要UseDefault

这样简洁又优雅

string table1 = "User1";

string table2 = "User2";

var services = new ServiceCollection()

.AddKeyedScoped(table1, (_, _) => new UserRepository(table1))

.AddKeyedScoped(table2, (_, _) => new UserRepository(table2));

var serviceProvider = services.BuildServiceProvider();

var mapper = Mapper.Create();

mapper.UseScope(serviceProvider);

var dto = new UserDTO { Id = 1, Name = "Jxj" };

UserDomain user = mapper.Convert<UserDTO, UserDomain1>(dto);

Assert.NotNull(user.Repository);

UserDomain user2 = mapper.Convert<UserDTO, UserDomain2>(dto);

Assert.NotNull(user2.Repository);

七、竞品类似的功能

1. AutoMapper不支持

AutoMapper的NullSubstitute用来指定源属性为null时的默认值

用AutoMapper实现类似功能需要复杂的自定义IValueResolver来实现

PocoEmit在源无法匹配或源字段为null都可能触发依赖注入

2. EF有类似功能

不过貌似只支持EF内部某些服务

请参阅 EF Core实体类的依赖注入

八、总结

1. OOM映射需要依赖注入

DTO、实体、领域模型如果有业务逻辑就需要依赖外部服务

支持按类型注入,也支持按指定的参数或属性注入

支持FromKeyedServices和FromServices

需要外部服务就需要依赖注入

2. PocoEmit的依赖注入助力程序分层架构

依赖注入的加持每一层想调用啥就调用啥

同时也让每一层更好的划分让调用啥,不让调用啥更容易控制

同时也让业务需要划分多少层就划分多层变得简单

3. IOC容器使用需要注意

简单作业单容器,使用UseSingleton即可

多线程需要使用UseScope

Mvc(含WebApi)逻辑处理使用UseContext

UseContext需要引用nuget包PocoEmit.Mvc

如果是Mvc异步处理或Quartz类似作业不要用UseContext

就怕异步中获取到了HttpContext,但执行中途被释放了,后面就可能异常了

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询