【问题标题】:Mapping between DTO and domain objects, how can I make the process transparent to my repository?DTO 和域对象之间的映射,如何使该过程对我的存储库透明?
【发布时间】:2013-10-29 14:11:32
【问题描述】:

背景:

我正在使用 ASP.NET MVC 编写一个社交网络式的 Web 应用程序。我的项目布局如下:

  1. 表示层 - 视图和前端框架。数据保存在从 BO 映射的 Viewmodel 中。
  2. 业务层 - 用于表示层的 BO 操作和聚合以及来自数据层的 BO 水合。
  3. 数据层 - 存储库以及从数据库中检索数据的代码。 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 中。作为对象的域对象上的任何属性都作为字符串 key 存储在其各自的 DTO 中。每个对象(DTO 和 DO)都继承自 Base 类,该类具有 Key 属性。这样我就可以存储对象属性之间的关系,而无需聚合和复制每个对象。

我遇到麻烦的地方:

要在 DO 和 DTO 之间移动,我使用 AutoMapper 和一组自定义 ITypeConverter 类,这些类在 string 和相应 DO 属性上具有相同名称的任何类之间进行映射(反之亦然反之,类到字符串),其中string 是要在 Redis DB 中检索的对象的键。这应该很好用,但是我现在在我的数据层中有两组方法:

  1. 获取驻留在我的存储库中的域对象的基本操作 -- 这些是我希望业务层与之交互的方法。
  2. 获取 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);
   }
}

使用伪代码我希望发生的事情

//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 根据我在配置中设置的更多规则来决定如何获取和填充返回对象...

【问题讨论】:

  • 您能否以伪代码显示 Participant 类的当前流程(读/写)和匹配的所需流程?
  • @galenus 我为流程添加了代码

标签: c# redis mapping servicestack data-access-layer


【解决方案1】:

在我看来,代码太复杂了。如果您是第一次使用 Redis 构建模型层,您将如何构建模型层?

我会放弃 DTO 和 AutoMapper(您将其用作 Redis 的“ORM”)并使用 Redis 数据结构为我的类建模。

例如,我的参与者会是这样的:

public class Participant : Base
{        
    public string Name { get; set; }
    public string City { get; set; }
}

我在 Redis 中的 Participant 的密钥类似于“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 来实现简单性,并且仍然可以正确处理您的域。

希望我能帮上忙,

【讨论】:

  • 你的正确之处在于我的模型太复杂了 :) 实际上我最终放弃了这种方法并重新使用 SQL 作为我的数据库,因为我的域对象是高度相关的,而 redis 不适合.不幸的是,这些关系比 SQL 提供的更开放(我猜是表格),所以它只是一个过渡。我正在研究像 neo4js 这样更适合我的需求的图形数据库。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-19
  • 2013-11-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多