Подтвердить что ты не робот

Может ли automapper отображать внешний ключ для объекта с помощью репозитория?

Я пробовал сначала Entity Framework Code CTP4. Предположим, что у меня есть:

public class  Parent
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Child
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Parent Mother { get; set; }
}

public class TestContext : DbContext
{
    public DbSet<Parent> Parents { get; set; }
    public DbSet<Child> Children { get; set; }
}

public class ChildEdit
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int MotherId { get; set; }
}

Mapper.CreateMap<Child, ChildEdit>();

Сопоставление с моделью редактирования не является проблемой. На моем экране я выбираю мать через какой-то элемент управления (выпадающий список, автозаполнение и т.д.), А идентификатор матери отправляется обратно:

[HttpPost]
public ActionResult Edit(ChildEdit posted)
{
    var repo = new TestContext();

    var mapped = Mapper.Map<ChildEdit, Child>(posted);  // <------- ???????
}

Как мне решить последнее сопоставление? Я не хочу помещать Mother_Id в объект Child. На данный момент я использую это решение, но, надеюсь, его можно решить в Automapper.

        Mapper.CreateMap<ChildEdit, Child>()
            .ForMember(i => i.Mother, opt => opt.Ignore());

        var mapped = Mapper.Map<ChildEdit, Child>(posted);
        mapped.Mother = repo.Parents.Find(posted.MotherId);

ИЗМЕНИТЬ Это работает, но теперь я должен сделать это для каждого внешнего ключа (BTW: контекст будет введен в окончательное решение):

        Mapper.CreateMap<ChildEdit, Child>();
            .ForMember(i => i.Mother,
                       opt => opt.MapFrom(o => 
                              new TestContext().Parents.Find(o.MotherId)
                                         )
                      );

Мне бы очень хотелось:

        Mapper.CreateMap<int, Parent>()
            .ForMember(i => i, 
                       opt => opt.MapFrom(o => new TestContext().Parents.Find(o))
                      );

        Mapper.CreateMap<ChildEdit, Child>();

Возможно ли это с помощью Automapper?

4b9b3361

Ответ 1

Во-первых, я предполагаю, что у вас есть интерфейс репозитория, например IRepository<T>

Затем создайте следующий класс:

public class EntityConverter<T> : ITypeConverter<int, T>
{
    private readonly IRepository<T> _repository;
    public EntityConverter(IRepository<T> repository)
    {
        _repository = repository;
    }
    public T Convert(ResolutionContext context)
    {
        return _repository.Find(System.Convert.ToInt32(context.SourceValue));       
    }
}

В основном этот класс будет использоваться для выполнения всех преобразований между объектом int и доменом. Он использует "идентификатор" объекта для загрузки из репозитория. IRepository будет впрыскиваться в конвертер, используя контейнер IoC, но больше и позже.

Позвольте настроить отображение AutoMapper, используя:

Mapper.CreateMap<int, Mother>().ConvertUsing<EntityConverter<Mother>>();

Я предлагаю создать это "общее" отображение вместо этого, так что если у вас есть другие ссылки на "Мать" на других классах, они автоматически отображаются без лишних усилий.

Что касается инъекции зависимостей для IRepository, если вы используете Castle Windsor, конфигурация AutoMapper также должна иметь:

IWindsorContainer container = CreateContainer();
Mapper.Initialize(map => map.ConstructServicesUsing(container.Resolve));

Я использовал этот подход, и он работает очень хорошо.

Ответ 2

Вот как я это сделал: (используя ValueInjecter)
Я сделал требования немного больше, чтобы показать, как это работает


[TestFixture]
public class JohnLandheer
{
    [Test]
    public void Test()
    {
        var child = new Child
        {
            Id = 1,
            Name = "John",
            Mother = new Parent { Id = 3 },
            Father = new Parent { Id = 9 },
            Brother = new Child { Id = 5 },
            Sister = new Child { Id = 7 }
        };
        var childEdit = new ChildEdit();

        childEdit.InjectFrom(child)
                 .InjectFrom<EntityToInt>(child);

        Assert.AreEqual(1, childEdit.Id);
        Assert.AreEqual("John", childEdit.Name);
        Assert.AreEqual(3, childEdit.MotherId);
        Assert.AreEqual(9, childEdit.FatherId);
        Assert.AreEqual(5, childEdit.BrotherId);
        Assert.AreEqual(7, childEdit.SisterId);
        Assert.AreEqual(0, childEdit.Sister2Id);

        var c = new Child();

        c.InjectFrom(childEdit)
            .InjectFrom<IntToEntity>(childEdit);

        Assert.AreEqual(1, c.Id);
        Assert.AreEqual("John", c.Name);
        Assert.AreEqual(3, c.Mother.Id);
        Assert.AreEqual(9, c.Father.Id);
        Assert.AreEqual(5, c.Brother.Id);
        Assert.AreEqual(7, c.Sister.Id);
        Assert.AreEqual(null, c.Sister2);
    }

    public class Entity
    {
        public int Id { get; set; }
    }

    public class Parent : Entity
    {
        public string Name { get; set; }
    }

    public class Child : Entity
    {
        public string Name { get; set; }
        public Parent Mother { get; set; }
        public Parent Father { get; set; }
        public Child Brother { get; set; }
        public Child Sister { get; set; }
        public Child Sister2 { get; set; }
    }

    public class ChildEdit
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int MotherId { get; set; }
        public int FatherId { get; set; }
        public int BrotherId { get; set; }
        public int SisterId { get; set; }
        public int Sister2Id { get; set; }
    }

    public class EntityToInt : LoopValueInjection
    {
        protected override bool TypesMatch(Type sourceType, Type targetType)
        {
            return sourceType.IsSubclassOf(typeof(Entity)) && targetType == typeof(int);
        }

        protected override string TargetPropName(string sourcePropName)
        {
            return sourcePropName + "Id";
        }

        protected override bool AllowSetValue(object value)
        {
            return value != null;
        }

        protected override object SetValue(object sourcePropertyValue)
        {
            return (sourcePropertyValue as Entity).Id;
        }
    }

    public class IntToEntity : LoopValueInjection
    {
        protected override bool TypesMatch(Type sourceType, Type targetType)
        {
            return sourceType == typeof(int) && targetType.IsSubclassOf(typeof(Entity));
        }

        protected override string TargetPropName(string sourcePropName)
        {
            return sourcePropName.RemoveSuffix("Id");
        }

        protected override bool AllowSetValue(object value)
        {
            return (int)value > 0;
        }

        protected override object SetValue(object sourcePropertyValue)
        {
            // you could as well do repoType = IoC.Resolve(typeof(IRepo<>).MakeGenericType(TargetPropType))
            var repoType =  typeof (Repo<>).MakeGenericType(TargetPropType);
            var repo = Activator.CreateInstance(repoType);
            return repoType.GetMethod("Get").Invoke(repo, new[] {sourcePropertyValue});
        }
    }

    class Repo<T> : IRepo<T> where T : Entity, new()
    {
        public T Get(int id)
        {
            return new T{Id = id};
        }
    }

    private interface IRepo<T>
    {
        T Get(int id);
    }
}

Ответ 3

Можно также определить внешний ключ в EF следующим образом:

[ForeignKey("MotherId")]
public virtual Parent Mother { get; set; }
public int MotherId { get; set; }

В этом случае не нужно делать дополнительный запрос, чтобы найти Мать. Просто назначьте ViewModel MotherId модели MotherId.