Я сначала пробую код 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);
РЕДАКТИРОВАТЬ Это работает, но теперь я должен сделать это для каждого внешнего ключа (кстати, контекст будет добавлен в окончательное решение):
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?
Во-первых, я предполагаю, что у вас есть интерфейс репозитория, например 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));
Я использовал этот подход, и он работает достаточно хорошо.
Вот как я это сделал: (используя 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);
}
}