背景:DTO和域對象之間的映射關係,如何使該過程對我的存儲庫透明?
我正在使用ASP.NET MVC編寫一個社交網絡式的web應用程序。我的項目是奠定列如下:
- 表示層 - 視圖和前端框架。數據位於從BO映射的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,因爲我不希望子對象以聚合方式存儲 - 我希望它們被關聯存儲,以便每個對象都是獨立的,但與其他對象相關。
要做到這一點,我創建兩組對象:
域對象(BOS)
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數據庫中。作爲對象的域對象上的任何屬性都作爲字符串存儲在其各自的DTO上鍵。每個對象(DTO和DO)都從具有Key
屬性的Base
類繼承。這樣我就可以存儲對象屬性之間的關係,而不會聚合和複製每個對象。
當我遇到麻煩:
做,DTO之間移動,我使用AutoMapper和一套定製的ITypeConverter
類,string
之間進行映射和任何類對同一個名字相應的DO屬性(反之亦然,字符串類),其中string
是對象在Redis DB中檢索的關鍵。這應該很好地工作,但是我現在有兩套方法,在我的數據層:
- 基本操作爲獲得域對象駐留在我的倉庫 - 這是我希望我的業務層交互的方法。
- 獲取DTOs的基本操作,它們駐留在單獨的類中,僅用於通過客戶端訪問Redis數據庫。
我希望映射這兩組操作,以便在存儲庫中的DO上操作後,在DTO之間傳輸數據後執行必要的操作。
實質上,我希望知識庫對DTO幾乎一無所知。理想情況下,這將是存儲/檢索操作的流程。
檢索獲取(會員DTO)從Redis的 - >地圖(會員BO) - >迴歸到存儲庫
商店商店(會員BO)的應用程序 - >地圖(會員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決定如何獲得和填充根據一些規則
您能否顯示參與者類的當前流(讀/寫)和僞代碼中匹配的期望流? – galenus
@galenus我添加的代碼流 –