具有自定义实体视图映射器的循环依赖项



假设我有PatientCycle域实体,我通过EF加载:

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导航属性。这有效,但需要更多的实体,映射器等。

有更好的方法吗?

,因此您在这里有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); 

像往常一样,不再关心循环依赖。

最新更新