背景:
我正在使用 ASP.NET MVC 编写一个社交网络式的 Web 应用程序。我的项目布局如下:
- 表示层- 视图和前端框架。数据存放在从 BO 映射的 Viewmodel 中。
- 业务层- 用于表示层的 BO 操作和聚合以及来自数据层的 BO 水合。
- 数据层- 存储库以及用于从 db 检索数据的代码。POCO 在这里定义。
以前,该项目使用 SQL 和 Dbcontext 来水合从数据层中定义的 POCO 类创建的 BO。然而,由于项目的性质(随着项目的发展),需求已经超出了用于存储数据的基于 SQL 的架构。我决定切换到Redis,但现在很难在 Redis 和我的 POCO 类之间进行映射。
问题的根源:
我选择使用Service Stack 的 Redis 客户端与 Redis 数据库进行交互。客户端提供了一个强类型客户端,它允许您指定从服务器存储/检索的对象并为您序列化/反序列化它(这很棒)。但问题在于,任何具有公共 getter 的属性都将与被存储的对象一起序列化。
对我来说,这违背了使用 Redis 的意义,因为我不希望子对象以聚合方式存储——我希望它们以关系方式存储,以便每个对象独立但与其他对象相关。
为此,我创建了两组对象:
领域对象(BO)
public class Participant : Base
{
public string ForwardConnections { get; set; }
public string ReverseConnections { get; set; }
public string Name { get; set; }
public string City { get; set; }
}
和DTO
public class Participant : Base
{
public AceOfSets<Connection> ForwardConnections { get; set; }
public AceOfSets<Connection> ReverseConnections { get; set; }
public string Name { get; set; }
public string City { get; set; }
}
根据
public class Base
{
public long Id { get; set; }
public DateTimeOffset CreatedOn { get; set; }
public string Key { get; set; }
}
使用这种设计,我实际上将 DTO 存储在 Redis DB 中。作为对象的域对象上的任何属性都作为字符串键存储在其各自的 DTO 中。每个对象(DTO 和 DO)都继承自Base
具有Key
属性的类。这样我就可以存储对象属性之间的关系,而无需聚合和复制每个对象。
我遇到麻烦的地方:
要在 DO 和 DTO 之间移动,我使用AutoMapper和一组自定义ITypeConverter
类,这些类在各个 DO 属性上具有相同名称的任何类之间进行映射string
(反之亦然,类到字符串),其中string
是要检索的对象的键Redis 数据库。这应该很好用,但是我现在在我的数据层中有两组方法:
- 获取驻留在我的存储库中的域对象的基本操作——这些是我希望我的业务层与之交互的方法。
- 获取驻留在单独类中的DTO的基本操作,仅用于通过客户端访问 Redis 数据库。
我希望映射这两组操作,以便在存储库中对 DO 进行操作,以便在两者之间传输数据后对 DTO 执行必要的操作。
本质上,我希望存储库对 DTO 几乎一无所知。理想情况下,这将是存储/检索操作的流程。
从 Redis检索Get(Member DTO) -> 映射到 (Member BO) -> 返回存储库
Store Store(Member BO) from app -> Map to (Member DTO) -> store to Redis
但是,我还没有找到一种体面的方法来设计这个映射过程,现在不知道该怎么做。
我在此期间所做的是在存储库的基本操作方法中使用带有泛型的反射来匹配这样的两组类。
public List<T> GetSetByKey<T>(string key) where T : Base
{
Type a = RepositoryMapperHelper.MapClass(typeof(T)); //Matches up class type to respective DTO class
var obj =
typeof(RepositoryMapper).GetMethod("GetSetByKey") //RepositoryMapper class contains operations for retreiving DTOs from Redis DB using RedisClient
.MakeGenericMethod(a)
.Invoke(RepoMapper, new object[] { key });
return Mapper.DynamicMap<List<T>>(obj); //Determines types at run-time and uses custom ITypeConverter (in conjunction with RepositoryMapper) to hydrate DO properties
}
public static class RepositoryMapperHelper
{
public static Type MapClass(Type t)
{
if(t == typeof(Connection))
{
return typeof (RedisModel.Connection);
}
....
}
这是一个糟糕的解决方法。我不喜欢它的任何东西,但我想不出另一种方法来做到这一点。我需要的是一个新的设计理念来处理映射交互——或者整个事情。是否有任何映射库可用于方法或类之间的映射,就像我正在尝试做的那样?我该如何解决这个问题?
TLDR如何以对我的数据层透明的方式在域对象和 DTO 之间进行映射?
编辑:
这是当前的读取操作:
//Make a request to a repository for an object
ParticipantRepository repo = new ParticipantRepository();
repo.GetById(theId);
//My BaseRepository has all generic methods.
//There is a BaseRepository<T> class that inherits from BaseRepository and allows me to call methods without needing to specify T because it is specified when you instantiate the repository.
//In BaseRepository
public virtual T GetById<T>(long id) where T : Base
{
Type a = RepositoryMapperHelper.MapClass(typeof(T));
var obj =
typeof(RepositoryMapper).GetMethod("GetById")
.MakeGenericMethod(a)
.Invoke(RepoMapper, new object[] { id }); //Builds the Generic Method using the respective DataModel.Type returned from MapClass
return Mapper.DynamicMap<T>(obj); ///Dynamically maps from source(DataModel) to destination type(DomainModel T)
}
//In RepositoryMapper
public virtual T GetById<T>(long id) where T : DataModel.Base
{
using (var o = RedisClient.As<T>())
{
return o.GetById(id);
}
}
//In AutoMapper Configuration
protected override void Configure()
{
//An example of how Automapper deals with conversion from key -> object
Mapper.CreateMap<string, Participant>().ConvertUsing<KeyToBaseConverter<Participant, DataModel.Participant>>();
}
//The conversion
public class KeyToBaseConverter<T, U> : ITypeConverter<string, T>
where T : Base
where U : DataModel.Base
{
public RepositoryMapper Repository = new RepositoryMapper();
public T Convert(ResolutionContext context)
{
//Get the actual DTO using the Key or Id
var datamodel = Repository.GetByKey<U>(context.SourceValue.ToString());
return Mapper.DynamicMap<U, T>(datamodel);
}
}
使用伪代码我想要发生的事情
//Read Operation
//in domain repository
public T GetByKey<T>(string key) where T : Base
{
U type = DataModelMapper.Map(T);
return DataModelRepo.GetByKey<U>(string key);
}
//in DTO repository(facing Redis)
public GetByKey<U>(string key) where U : DataModel.Base
{
using(var client = RedisClient.As<U>())
{
var obj = client.GetByKey(key);
T type = DataModelMapper.ReverseMap(U);
return Mapper.Map<T>(obj);
}
}
//Write Operation
//in domain repository
public void Save<T>(T Entity) where T : Base
{
U type = DataModelMapper.Map(T);
DataModelRepo.Save<U>(Entity);
}
//in DTO repository(facing Redis)
public void Save<U>(T Entity) where U : DataModel.Base where T : Base
{
var obj = Mapper.Map<U>(Entity);
using(var client = RedisClient.As<U>())
{
client.Store(obj);
}
}
所以它与我已经在做的非常相似,我遇到的障碍是在两种类型的模型之间进行转换,并将泛型类型参数再次传递RepositoryMapper
回来。
自从我写了这个问题以来,我考虑过对我的 AutoMapper 实现进行更多投资,我可以将它用作两个存储库的唯一中间人——所以基本上Map
在两个泛型之间调用,然后让 AutoMapper 决定如何根据我在配置中设置的更多规则获取并填充返回对象...