Может ли autopper отображать внешний ключ на объект, используя хранилище? - PullRequest
19 голосов
/ 19 августа 2010

Сначала я опробую код Entity Framework 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);

EDIT Это работает, но теперь я должен сделать это для каждого внешнего ключа (кстати, контекст будет введен в окончательном решении):

        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?

Ответы [ 3 ]

20 голосов
/ 28 августа 2010

Во-первых, я предполагаю, что у вас есть интерфейс репозитория, такой как 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 и сущностью домена. Он использует «Id» объекта для загрузки его из репозитория. IRepository будет введен в конвертер с использованием контейнера IoC, но об этом позже.

Давайте настроим отображение AutoMapper с помощью:

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

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

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

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

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

6 голосов
/ 20 августа 2010

Вот как я это сделал: (используя 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 голосов
/ 12 мая 2015

Также можно определить внешний ключ в EF:

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...