バックグラウンド:
ASP.NET MVC を使用してソーシャル ネットワーク風の Web アプリケーションを作成しています。私のプロジェクトは次のようにレイアウトされています:
- プレゼンテーション層- ビューとフロントエンド フレームワーク。データは、BO からマップされた Viewmodels に格納されます。
- ビジネス層- プレゼンテーション層で使用するための BO の操作と集約、およびデータ層からの BO のハイドレーション。
- データ層- リポジトリはここにあり、db からデータを取得するためのコードもあります。POCO はここで定義されます。
以前は、プロジェクトは SQL と Dbcontext を使用して、データ層で定義された POCO クラスから作成された BO をハイドレートしていました。ただし、プロジェクトの性質上 (プロジェクトが成長するにつれて)、データを格納するための要件が SQL ベースのアーキテクチャを超えています。Redisに切り替えることを決定しましたが、現在、Redis と POCO クラスの間のマッピングに問題があります。
問題の根本:
Redis db と対話するためにService Stack の Redis Clientを使用することにしました。クライアントは、サーバーから格納/取得するオブジェクトを指定し、それをシリアル化/逆シリアル化できるようにする厳密に型指定されたクライアントを提供します (これは素晴らしいことです)。ただし、これの問題は、パブリック ゲッターを持つすべてのプロパティが、格納されているオブジェクトと共にシリアル化されることです。
私にとって、子オブジェクトを集合的に保存したくないので、これは Redis を使用するポイントを無効にします。各オブジェクトが独立しているが他のオブジェクトに関連するように、子オブジェクトをリレーショナルに保存したいのです。
これを行うために、2 つのオブジェクト セットを作成しました。
ドメイン オブジェクト (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と、それぞれの DO プロパティで同じ名前を持つクラス (およびその逆、クラスから文字列) のITypeConverter
間でマップする一連のカスタムクラスを使用しています。 Redis DB。これはうまくいくはずですが、データレイヤーには 2 つのメソッドセットがあります。string
string
- リポジトリに存在するドメイン オブジェクトを取得するための基本的な操作- これらは、ビジネス レイヤーに対話させたいメソッドです。
- 別のクラスに存在し、クライアントを介して Redis db にアクセスするためにのみ使用されるDTOを取得するための基本的な操作。
これら 2 つの操作セットをマップして、リポジトリから DO を操作すると、2 つの間でデータを転送した後に DTO で必要な操作が実行されるようにしたいと考えています。
本質的に、私はリポジトリが DTO をほとんど知らないようにしたいと考えています。理想的には、これはストア/検索操作のフローです。
Redis からの取得(メンバー DTO) -> (メンバー BO) へのマップ -> リポジトリに戻る
アプリからStore (メンバー BO) -> (メンバー DTO) にマップ -> Redis にストア
しかし、このマッピング プロセスを適切に設計する方法が見つからず、今何をすべきか途方に暮れています。
私が暫定的に行ったことは、リポジトリ内の基本的な操作メソッドでジェネリクスを使用してリフレクションを使用し、このように 2 つのクラス セットを一致させることです。
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);
}
}
したがって、私がすでに行っていることと非常に似ています。私が直面している障壁は、2 つのタイプのモデル間で変換し、ジェネリック型パラメーターをやり取りすることRepositoryMapper
です。
この質問を書いて以来、AutoMapper の実装にもっと投資することを考えていたので、それを 2 つのリポジトリの唯一の仲介役として使用できるかもしれません。つまり、基本的Map
に 2 つのジェネリックの間で呼び出され、AutoMapper にどのように実行するかを決定させます。構成に配置したいくつかのルールに基づいて、戻りオブジェクトを取得して入力します...