代码之家  ›  专栏  ›  技术社区  ›  John Landheer

automapper能否使用存储库将外键映射到对象?

  •  18
  • John Landheer  · 技术社区  · 14 年前

    我先试用实体框架代码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>();
    

    映射到编辑模型不是问题。在我的屏幕上,我通过一些控件(dropdownlist、autocompleter等)选择了母亲,母亲的Id被贴在后面:

    [HttpPost]
    public ActionResult Edit(ChildEdit posted)
    {
        var repo = new TestContext();
    
        var mapped = Mapper.Map<ChildEdit, Child>(posted);  // <------- ???????
    }
    

    如何解决最后一个映射?我不想把母亲的Id放在子对象中。目前我使用这个解决方案,但我希望它可以在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>();
    

    3 回复  |  直到 14 年前
        1
  •  21
  •   psousa    14 年前

    首先,我假设您有一个存储库接口,如 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>>();
    

    我建议改为创建这个“泛型”映射,这样,如果在其他类上有其他对“Mother”的引用,它们将自动映射,而无需额外的工作。

    关于IRepository的依赖项注入,如果您使用的是Castle Windsor,AutoMapper配置还应该具有:

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

        2
  •  6
  •   Omu    14 年前

    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
  •  2
  •   VahidN    10 年前

    也可以这样定义EF中的外键:

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

    在这种情况下,不需要进行额外的查询来找到母亲。只需将ViewModel的MotherId指定给模型的MotherId。