如何使用实体框架使用DTO更新数据库中的实体
我有一个三层应用程序。这些图层是DAL,BL和UI。我使用automapper将从数据库上下文中取得的实体转换为上层图层。据我所知,被映射的实体不能被EF跟踪。如何使用实体框架使用DTO更新数据库中的实体
无论如何,我面临的问题发生在更新DB中存在的实体时,更确切地说是由DTO实体更新EF实体。来自上层的DTO被映射回实体以完成更新操作。但是,从DTO实体映射而来,其所有导航属性和集合都不存在于上下文中,因为它们未被跟踪。
EF接受此实体及其实体图形作为新信息,而不考虑该实体及其中包含的某些实体可能已经存在于上下文中的事实。
这里举一些例子。
我有两个型号,Student
和Standard
。
public class Student
{
[Key]
public Guid StudentID { get; set; }
public string StudentName { get; set; }
public DateTime DateOfBirth { get; set; }
public decimal Height { get; set; }
public float Weight { get; set; }
public Standard Standard { get; set; }
}
public class Standard
{
[Key]
public Guid StandardId { get; set; }
public string StandardName { get; set; }
public ICollection<Student> Students { get; set; }
}
我的背景:
public class SchoolContext : DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Standard> Standards { get; set;}
}
我也有一个StudentMap
和StandardMap
看起来一样上面提到的车型。
Automapper简介:
public class AutomapperProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<Student, StudentMap>();
Mapper.CreateMap<StudentMap, Student>();
Mapper.CreateMap<Standard, StandardMap>();
Mapper.CreateMap<StandardMap, Standard>();
}
}
在这里,我运行代码:
Mapper.AddProfile(new AutomapperProfile());
var student1 = new Student
{
DateOfBirth = DateTime.Now,
Height = 195,
StudentID = Guid.NewGuid(),
StudentName = "Bob",
Weight = 144
};
var student2 = new Student
{
DateOfBirth = DateTime.UtcNow,
Height = 170,
StudentID = Guid.NewGuid(),
StudentName = "John",
Weight = 95,
};
var standard = new Standard
{
StandardId = Guid.NewGuid(),
StandardName = "New Standard",
Students = new List<Student> { student1 }
};
using (var schoolContext = new SchoolContext())
{
schoolContext.Standards.Add(standard);
schoolContext.Students.Add(student2);
schoolContext.SaveChanges();
var standardInContext = schoolContext.Standards.First();
var studentInContext = schoolContext.Students.First(student => student.StudentID == student2.StudentID);
var mappedStandad = Mapper.Map<Standard, StandardMap>(standardInContext);
var mappedStudent = Mapper.Map<Student, StudentMap>(studentInContext);
mappedStandad.Students.Add(mappedStudent);
var standardEf = Mapper.Map<StandardMap, Standard>(mappedStandad);
//On attach attempt exception will be thrown
//because standard with the same Id already exist in context
schoolContext.Set<Standard>().Attach(standardEf);
schoolContext.Entry(standardEf).State = EntityState.Modified;
// with this solution two additional student will be added to the context,
// exception will be thrown on SaveChanges, because students with
// same ID already exist
// var standardEf = Mapper.Map(mappedStandad, standardInContext);
// schoolContext.Set<Standard>().Attach(standardEf);
// schoolContext.Entry(standardEf).State = EntityState.Modified;
schoolContext.SaveChanges();
}
我会感谢您的帮助!我已经检查了我所能做的,但是徒劳无功。
附加完整图是一项复杂的任务。在这种情况下,EF将Student实例作为Added添加,这是将对象附加到集合时的行为。
为避免招收新生,您应首先将所有标准的学生附加到上下文中,以便开始跟踪(如未修改),然后附加standardEf实体。
行schoolContext.Set<Standard>().Attach(standardEf);
给出了例外,因为您已经添加了Standard
实体与之前的StandardID
相同schoolContext.Standards.Add(standard);
。
解决办法:
请求从上下文
Standard
实体通过StandardMap.StandardID
地图属性值从
StandardMap
传输对象所请求的Standard
实体(手动或使用Mapper.Map<Source, Destination>(source, destination);
)保存已更改
ADDED
如果你想使用AutoMapper你已经正确配置映射器请求嵌套实体从上下文而不是创建他们,像:
Mapper.CreateMap<StudendMap, Student>.
ForMember(
x => x.Standard,
m => m.ResolveUsing(
s => Context.Set<Standard>.Find(s.StandardID)))
很好,你的情况发生在一个问题用Mapper.Map(source,destination)方法更新实体时的时间。由于源对象(DTO)中包含的所有对象都未由EF进行跟踪,因此它会将它们添加到上下文中(事实上,某些对象已经存在于上下文中)。所以它会给你例外,因为键保存到数据库时重复键 –
谢谢,看起来像我的问题的解决方案。但是当我的实体包含很多对其他对象的引用时,它就变成了常规 –