与自定义实体视图模型映射器

时间:2017-03-14 13:51:16

标签: c# mapping

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

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

有更好的方法吗?

1 个答案:

答案 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); 

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