假设我有通过EF加载的Patient
和Cycle
个域实体:
public class Patient
{
public int PatientId { get; set; }
public string Name { get; set; }
public List<Cycle> Cycles { get; set; }
public Patient()
{
Cycles = new List<Cycle>();
}
}
public class Cycle
{
public int CycleId { get; set; }
public int PatientId { get; set; }
public bool IsActive { get; set; }
public Patient Patient { get; set; }
}
如您所见,Patient
具有Cycles
集合的导航属性,而Cycles
的导航属性引用返回Patient
。
以前,我使用过AutoMapper,但想完全控制我的域实体 - 视图模型映射,所以我正在创建自定义映射器。这是Patient
的一个:
public class PatientMapper : IPatientMapper
{
private readonly ICycleMapper cycleMapper;
public PatientMapper(ICycleMapper cycleMapper)
{
this.cycleMapper= cycleMapper;
}
public PatientViewModel GetViewModel(Patient patient)
{
if (patient == null)
{
return null;
}
var viewModel = new PatientViewModel();
viewModel.PatientId = patient.PatientId;
viewModel.Name = patient.Name;
viewModel.Cycles = patient.Cycles.Select(x => cycleMapper.GetViewModel(x)).ToList();
return viewModel;
}
}
如您所见,我需要注入一个CycleMapper
。在CycleMapper
中,我需要注入PatientMapper
的实例来映射Patient
导航属性。这会导致循环DI问题。
我过去通过创建每个实体的“基本”版本来解决这个问题。例如,BasicCycle
没有Patient
导航属性。这可行,但需要更多的实体,映射器等。
有更好的方法吗?
答案 0 :(得分:0)
所以这里有2个问题:构造函数中的递归依赖注入和递归映射。要解决递归DI - 有几种选择。首先是将对象工厂而不是对象本身传递给构造函数。 Autofac原生支持:
private readonly Func<IPatientMapper> patientMapper;
public CycleMapper(Func<IPatientMapper> patientMapper)
{
this.patientMapper = patientMapper;
}
这样就可以延迟依赖构建直到需要,从而解决问题。
替代方法 - 使用属性注入而不是构造函数注入:
public IPatientMapper PatientMapper { get; set; }
并像这样注册(所有地图制作者):
builder.RegisterType<CycleMapper>().As<ICycleMapper>().SingleInstance().PropertiesAutowired(PropertyWiringOptions.AllowCircularDependencies);
现在,当第一个问题得到解决时 - 如果您尝试使用周期对患者进行映射,则会出现堆栈溢出异常(因为周期反馈患者)。解决这个问题的最简单方法是跟踪已映射的内容,例如:(对于所有映射器,而不仅仅是一个映射器):
public interface IPatientMapper {
PatientViewModel GetViewModel(Patient patient, IDictionary<object, object> map = null);
}
public interface ICycleMapper {
CycleViewModel GetViewModel(Cycle cycle, IDictionary<object, object> map = null);
}
public class CycleMapper : ICycleMapper
{
public IPatientMapper PatientMapper { get; set; }
public CycleViewModel GetViewModel(Cycle cycle, IDictionary<object, object> map = null)
{
if (cycle == null) {
return null;
}
// If called without map - create new one
if (map == null)
map = new Dictionary<object, object>();
// if we already mapped this cycle before - don't do this again
// and instead return already mapped entity
if (map.ContainsKey(cycle))
return (CycleViewModel)map[cycle];
var viewModel = new CycleViewModel();
viewModel.PatientId = cycle.PatientId;
viewModel.CycleId = cycle.CycleId;
viewModel.IsActive = cycle.IsActive;
// add this entity to map before calling any other mappers
map.Add(cycle, viewModel);
// pass map to other mapper
viewModel.Patient = PatientMapper.GetViewModel(cycle.Patient, map);
return viewModel;
}
}
当你需要映射某些东西时,你只需要打电话
var model = mapper.GetViewModel(patient);
像往常一样,不再关心循环依赖。