使用实体框架更新数据库记录(延迟加载和虚拟属性)

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.Namelecturer.AcademicDegreelecturer.Courses)。

这是非常方便的方法,因为在 ViewModel 中我可以调用 _dataAccess.UpdateLecturer(SelectedLecturer)(其中 SelectedLecturer 绑定在 XAML 中;SelectedLecturer 属性可以由用户在 TextBoxes 和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_LecturerNameAcademic_Degree_IdAcademicDegree除了 CoursesCourses 之后保持不变。

为什么?我该如何解决?


类似的问题:

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>

LecturersLecturerListViewModel 构造函数中以这种方式从数据库中获取:

///
/// 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】:

将状态设置为ModifiedSetValues 只会更新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:我有一个新想法 :) 请参阅上面的编辑。

以上是关于使用实体框架更新数据库记录(延迟加载和虚拟属性)的主要内容,如果未能解决你的问题,请参考以下文章

实体框架急切加载不返回数据,延迟加载有

延迟加载实体,正在加载引用但实体属性不是

当延迟加载禁用时,如何通过实体框架仅将导航属性的特定属性包含到查询中?

Hibernate Lazy属性

使用 DTO 在实体框架中延迟加载

实体框架 - 通过集合导航和包含属性