背景:
我正在使用ASP.NET MVC编写一个社交网络式Web应用程序.我的项目如下:
>表示层 – 视图和前端框架.数据存放在从BOs映射的Viewmodels中.
>业务层 – 用于表示层的BO操作和聚合以及来自数据层的BO的水合.
>数据层 – 这里存储的存储库以及从数据库中检索数据的代码. POCO在此定义.
以前,该项目使用SQL和Dbcontext来水合从数据层中定义的POCO类创建的BO.然而,由于项目的性质(因为它已经增长),需求已经超出了基于SQL的架构来存储数据.我决定切换到Redis,但现在我很难在Redis和我的POCO课程之间进行映射.
问题的根源:
我选择使用Service Stack’s Redis Client与Redis数据库进行交互.客户端提供了一个强类型客户端,允许您指定从服务器存储/检索的对象,并为您序列化/反序列化(这很棒).但问题是any property that has a public getter will be serialized along with the object being stored.
对我来说,这使得使用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)都从具有Key属性的Base类继承.这样我就可以存储对象属性之间的关系,而无需聚合和复制每个对象.
我遇到麻烦的地方:
要在DO和DTO之间移动,我使用的是AutoMapper和一套自定义的ITypeConverter类,它们在字符串和相应DO属性上具有相同名称的任何类之间进行映射(反之亦然,类到字符串)其中string是对象的键在Redis DB中检索.这应该很好,但我现在在我的数据层中有两组方法:
>获取驻留在我的存储库中的域对象的基本操作 – 这些是我希望我的业务层与之交互的方法.
>获取驻留在单独类中的DTO的基本操作,仅用于通过客户端访问Redis数据库.
我希望映射这两组操作,以便在存储库中对DO进行操作后,在两者之间传输数据后,在DTO上执行必要的操作.
从本质上讲,我希望存储库几乎不知道DTO.理想情况下,这将是存储/检索操作的流程.
从Redis检索获取(会员DTO) – >映射到(成员BO) – >返回存储库
应用商店商店(会员BO) – >映射到(会员DTO) – >存储到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);
}
}
使用Psuedo-code我想要发生的事情
//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决定如何根据我在配置中设置的更多规则来获取和填充返回对象…
最佳答案 在我看来,代码太复杂了.如果您是第一次使用Redis构建,您将如何构建模型层?
我会放弃DTO和AutoMapper(你将它用作Redis的“ORM”)并为我的类建立Redis数据结构.
例如,我的参与者将是这样的:
public class Participant : Base
{
public string Name { get; set; }
public string City { get; set; }
}
我在Redis参与者的密钥是“urn:participant:1”.在我的回购中我会:
public Participant GetById(string id)
{
return this.Redis.GetById<Participant>(id);
}
public List<Connection> GetForwardConnections(string participantId)
{
return this.Redis.GetTypedClient<Connection>().Lists["urn:participant:1:forwardconnections"].ToList();
}
public List<Connection> GetReverseConnections(string participantId)
{
return this.Redis.GetTypedClient<Connection>().Lists["urn:participant:1:reverseconnections"].ToList(); // you could also use sets
}
通过删除许多抽象,映射,dto和您仍然可以使您的域名正确,您可以实现简单性.
希望我能帮忙,