Automapperを使用して、パブリックデータコントラクトとDBモデルの間をマッピングしたいと思います。マッピングを作成するすべてのコントラクトを自動的に通過するクラスを作成しました。私が抱えている唯一の問題は、値がnullでない場合にのみ、コントラクトからDBモデルに値をマップしたいということです。ここで他の質問を見ましたが、カスタムリゾルバーを使用する例を見ることができません。
これが私のコードの一部です
var mapToTarget = AutoMapper.Mapper.CreateMap(contract, mappedTo);
foreach (var property in contract.GetProperties().Where(property => property.CustomAttributes.Any(a => a.AttributeType == typeof(MapsToProperty))))
{
var attribute = property.GetCustomAttributes(typeof(MapsToProperty), true).FirstOrDefault() as MapsToProperty;
if (attribute == null) continue;
mapToTarget.ForMember(attribute.MappedToName,
opt =>
opt.ResolveUsing<ContractToSourceResolver>()
.ConstructedBy(() => new ContractToSourceResolver(new MapsToProperty(property.Name, attribute.SourceToContractMethod, attribute.ContractToSourceMethod))));
}
private class ContractToSourceResolver : ValueResolver<IDataContract, object>
{
private MapsToProperty Property { get; set; }
public ContractToSourceResolver(MapsToProperty property)
{
Property = property;
}
protected override object ResolveCore(IDataContract contract)
{
object result = null;
if (Property.ContractToSourceMethod != null)
{
var method = contract.GetType()
.GetMethod(Property.ContractToSourceMethod, BindingFlags.Public | BindingFlags.Static);
result = method != null ? method.Invoke(null, new object[] {contract}) : null;
}
else
{
var property =
contract.GetType().GetProperties().FirstOrDefault(p => p.Name == Property.MappedToName);
if (property != null)
{
result = property.GetValue(contract);
}
}
return result;
}
}
そして、これが私がそれを使いたい方法です
AutoMapper.Mapper.Map(dataContract, dbModel)
これは現在うまく機能していますが、dataContractにNULL値がある場合、dbModelの既存の値が置き換えられます。これは、私が望んでいることではありません。 AutoMapperにnullソース値を無視させるにはどうすればよいですか?
[〜#〜]編集[〜#〜]
答えの1つで指摘されているように、これがあります
Mapper.CreateMap<SourceType, DestinationType>().ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));
これは、.ForAllMembersにからアクセスできないという事実を除いて理想的です。
Mapper.CreateMap(SourceType, DestinationType)
更新:IsSourceValueNullは V5以降は使用できません 。
Null値を持つすべてのソースプロパティを無視する場合は、次を使用できます。
Mapper.CreateMap<SourceType, DestinationType>()
.ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));
それ以外の場合は、メンバーごとに同様のことを行うことができます。
読む this 。
インスタンスAPIを利用する新しいバージョンの場合は、代わりにこれを使用してください:
var mappingConfig = new MapperConfiguration(cfg =>
{
cfg.CreateMap<SourceType, DestinationType>().ForAllMembers(opt => opt.Condition(
(source, destination, sourceMember, destMember) => (sourceMember != null)));
});
注:この機能は5.0.2の時点で機能し、この記事の執筆時点ではそれ以降のバージョンでは機能しません。 5.0.2からアップグレードする場合は、次の5.2.xリリースを待つことをお勧めします。
解決策 ここ は、AutoMapper6.0.2を使用している私のプロジェクトで機能します。 AutoMapper 4を使用する以前のプロジェクトでは、IsSourceValueNullを使用して同様の動作を実現していました。プロジェクトでNULL可能型をNULL不可型にマップしていますが、このソリューションはそのシナリオを処理できます。
元のソリューションに小さな変更を加えました。マップするプロパティのタイプをチェックする代わりに、ForAllPropertyMapsでフィルターを設定して、ソースオブジェクトのタイプをチェックし、カスタムリゾルバーがそのソースオブジェクトからのマップにのみ適用されるようにします。ただし、フィルターは必要に応じて任意に設定できます。
var config = new MapperConfiguration(cfg =>
{
cfg.ForAllPropertyMaps(
pm => pm.TypeMap.SourceType == typeof(<class of source object>),
(pm, c) => c.ResolveUsing<object, object, object, object>(new IgnoreNullResolver(), pm.SourceMember.Name));
});
class IgnoreNullResolver : IMemberValueResolver<object, object, object, object>
{
public object Resolve(object source, object destination, object sourceMember, object destinationMember, ResolutionContext context)
{
return sourceMember ?? destinationMember;
}
}
条件文を非ジェネリック型にマッピングすることに関して、まったく同じ問題があります。これが私がそれを解決した方法です。
配線:
foreach (PropertyInfo p in type.GetProperties().Where(x => x.GetCustomAttributes<SkipMapIfNullAttribute>().Any()))
map.ForMember(p.Name, x => x.ResolveUsing(typeof(SkipMapIfNullResolver)).FromMember(p.Name));
メンバーの値が完全なモデルではなく値リゾルバーに渡されるように、2番目の.FromMemberが必要です。
リゾルバは次のようになります。
public class SkipMapIfNullResolver : IValueResolver
{
public ResolutionResult Resolve(ResolutionResult source)
{
if (source.Value == null)
source.ShouldIgnore = true;
return source;
}
}