使用实体框架更新数据库记录(延迟加载和虚拟属性)
Posted
技术标签:
【中文标题】使用实体框架更新数据库记录(延迟加载和虚拟属性)【英文标题】:Updating database record using Entity Framework (lazy loading and virtual properties) 【发布时间】:2012-11-13 16:31:07 【问题描述】:我正在努力更新数据库中现有讲师的数据。 每个讲师都有自己教授的姓名、学术学位和课程(课程==课程)。
实际上class Lecturer
中还有更多属性,但它们并不相关。为简单起见,我们假设 POCO 类是这样定义的:
// POCO class (Entity Framework Reverse Engineering Code First)
public class Lecturer
public Lecturer()
this.Courses = new List<Course>();
public int Id_Lecturer get; set; // Primary Key
public string Name get; set;
public int? Academic_Degree_Id get; set;
public virtual AcademicDegree AcademicDegree get; set;
public virtual ICollection<Course> Courses get; set;
在数据访问层我有方法void UpdateLecturer(Lecturer lecturer)
应该更新Id_Lecturer
等于lecturer.Id_Lecturer
的讲师(使用lecturer.Name
、lecturer.AcademicDegree
和lecturer.Courses
)。
这是非常方便的方法,因为在 ViewModel 中我可以调用 _dataAccess.UpdateLecturer(SelectedLecturer)
(其中 SelectedLecturer
绑定在 XAML 中;SelectedLecturer
属性可以由用户在 TextBox
es 和Checkbox
)。
不幸的是这种方法:
public void UpdateLecturer(Lecturer lecturer)
using(var db = new AcademicTimetableDbContext())
// Find lecturer with Primary Key set to 'lecturer.Id_Lecturer':
var lect = db.Lecturers.Find(lecturer.Id_Lecturer);
// If not found:
if (lect == null)
return;
// Copy all possible properties:
db.Entry(lect).CurrentValues.SetValues(lecturer);
// Everything was copied except 'Courses'. Why?!
// I tried to add this, but it doesn't help:
// var stateMgr = (db as IObjectContextAdapter).ObjectContext.ObjectStateManager;
// var stateEntry = stateMgr.GetObjectStateEntry(lect);
// stateEntry.SetModified();
db.SaveChanges();
更新所有内容(即Id_Lecturer
、Name
、Academic_Degree_Id
和AcademicDegree
)除了 Courses
在Courses
之后保持不变。
为什么?我该如何解决?
类似的问题:
updating-an-entity-in-entity-framework how-do-can-i-attach-a-entity-framework-object-that-isnt-from-the-database using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx entity-framework-updating-with-related-entity entity-framework-code-first-no-detach-method-on-dbcontext-- 编辑--
我也尝试过这种方式(想法来自this post):
public void UpdateLecturer(Lecturer lecturer)
using (var db = new AcademicTimetableDbContext())
if (lecturer == null)
return;
DbEntityEntry<Lecturer> entry = db.Entry(lecturer);
if (entry.State == EntityState.Detached)
Lecturer attachedEntity = db.Set<Lecturer>().Find(lecturer.Id_Lecturer);
if (attachedEntity == null)
entry.State = EntityState.Modified;
else
db.Entry(attachedEntity).CurrentValues.SetValues(lecturer);
db.SaveChanges();
但仍然课程不会覆盖旧值。
-- 编辑2--
在回答@Slauma 问题时,我将描述我如何加载SelectedLecturer
(作为参数传递给UpdateLecturer(Lecturer lecturer)
方法)。
我正在实施MVVM 概念,因此我的解决方案 中有View 项目,其中DataContext
设置为LecturerListViewModel
。在 View 中有DataGrid
,其中包含从数据库中获取的所有讲师的列表。 DataGrid
是这样绑定的:
<DataGrid AutoGenerateColumns="False" Name="LecturersDataGrid" HeadersVisibility="Column" IsReadOnly="True" ItemsSource="Binding Lecturers,Mode=TwoWay" SelectedItem="Binding SelectedLecturer, Mode=TwoWay">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="Binding Name" />
<DataGridTextColumn Header="Academic degree" Binding="Binding AcademicDegree.Degree_Name" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="Edit" Click="EditButtonClick"/>
<Button Content="Delete" Command="Binding DataContext.RemoveLecturer, ElementName=LecturersDataGrid" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Lecturers
在LecturerListViewModel
构造函数中以这种方式从数据库中获取:
///
/// Code within LecturerListViewModel class:
///
// All lecturers from database.
public ObservableCollection<Lecturer> Lecturers
// Constructor
public LecturerListViewModel()
// Call to Data Access Layer:
Lecturers = new ObservableCollection<Lecturer>(_dataAccess.GetAllLecturers());
// Some other stuff here...
private Lecturer _selectedLecturer;
// Currently selected row with lecturer.
public Lecturer SelectedLecturer
get return _selectedLecturer;
set SetProperty(out _selectedLecturer, value, x => x.SelectedLecturer);
///
/// Data Access Layer (within DataAccess class):
///
public IEnumerable<Lecturer> GetAllLecturers()
using (var dbb = new AcademicTimetableDbContext())
var query = from b
in dbb.Lecturers.Include(l => l.AcademicDegree).Include(l => l.Timetables).Include(l => l.Courses)
select b;
return query.ToList();
【问题讨论】:
【参考方案1】:将状态设置为Modified
和SetValues
只会更新Lecturer
实体的标量属性。更新Courses
集合(它不是标量属性)需要更多的工作。您必须处理课程可能已从集合中删除、课程可能已添加或课程的标量属性可能已修改的情况。
此外,更新集合的方式取决于课程是否依赖于讲师。课程从集合中删除后是否需要从数据库中删除,还是只需要删除讲师和课程之间的关系?新课程添加到集合后是否需要创建新课程,还是只需要建立关系?
如果没有课程必须删除并且没有创建新课程,则更新方法可能如下所示:
public void UpdateLecturer(Lecturer lecturer)
using(var db = new AcademicTimetableDbContext())
if (lecturer == null)
return;
var lecturerInDb = db.Lecturers
.Include(l => l.Courses)
.Single(l => l.Id_Lecturer == lecturer.Id_Lecturer);
// Update lecturer
db.Entry(lecturerInDb).CurrentValues.SetValues(lecturer);
// Remove courses relationships
foreach (var courseInDb in lecturerInDb.Courses.ToList())
if (!lecturer.Courses.Any(c => c.Id_Course == courseInDb.Id_Course))
lecturerInDb.Courses.Remove(courseInDb);
foreach (var course in lecturer.Courses)
var courseInDb = lecturerInDb.Courses.SingleOrDefault(
c => c.Id_Course == course.Id_Course);
if (courseInDb != null)
// Update courses
db.Entry(courseInDb).CurrentValues.SetValues(course);
else
// Add courses relationships
db.Courses.Attach(course);
lecturerInDb.Courses.Add(course);
db.SaveChanges();
根据您的场景细节,正确的解决方案可能会略有不同。
编辑
如果lecturer.Courses
集合中的课程引用了lecturer
(可能具有Lecturer
导航属性),则在将此集合中的课程附加到上下文时可能会遇到问题,因为lecturerInDb
是已附加并具有相同的密钥。您可以尝试像这样更改最后一个 else
块以希望解决问题:
else
// Add courses relationships
var courseToAttach = new Course Id_Course = course.Id_Course ;
db.Courses.Attach(courseToAttach);
lecturerInDb.Courses.Add(courseToAttach);
【讨论】:
这几乎就是我想要实现的目标。问题是执行行db.Courses.Attach(course);
期间的异常。例外:ObjectStateManager 中已存在具有相同键的对象。 ObjectStateManager 无法使用相同的键跟踪多个对象。 我试图从代码中删除行 db.Courses.Attach(course);
,但在执行 db.SaveChanges()
期间出现异常。例外:无法定义两个对象之间的关系,因为它们附加到不同的 ObjectContext 对象。
@patryk.beza: 嗯,最后一个异常(“attached to different ObjectContext”)表示你的SelectedLecturer
你传入了方法(和/或相关课程) ) 附加到另一个上下文,而不是您在方法中创建的上下文(在 using
块中)。如果是这样,您根本无法使用上述方法。该方法是为分离场景设计的。我预计您会遇到这种情况,因为您在方法中创建了一个新的上下文。您能否在问题中描述您如何加载 SelectedLecturer
以及是否处理加载它的上下文?
我更新了我的第一篇文章(Edit 2)。有详细解释我是如何加载SelectedLecturer
的。
@patryk.beza: 你能尝试在你的GetAllLecturers()
方法中添加:dbb.Configuration.ProxyCreationEnabled = false;
(作为using
块中的第一行)并再次测试吗?
@patryk.beza:我有一个新想法 :) 请参阅上面的编辑。以上是关于使用实体框架更新数据库记录(延迟加载和虚拟属性)的主要内容,如果未能解决你的问题,请参考以下文章