I'm struggling with updating existing lecturer's data in database.
Every lecturer has Name, AcademicDegree and Courses which are taught by him/her (Courses==Lessons).
In fact there are more properties in class Lecturer
but they are not relevant. For simplicity lets assume that POCO class is defined this way:
// 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; }
}
In Data Access Layer I have method void UpdateLecturer(Lecturer lecturer)
which should update lecturer whose Id_Lecturer
is equal to lecturer.Id_Lecturer
(using lecturer.Name
, lecturer.AcademicDegree
and lecturer.Courses
).
It's very handy method because in ViewModel I can call _dataAccess.UpdateLecturer(SelectedLecturer)
(where SelectedLecturer
is binded in XAML; SelectedLecturer
properties can be set by user in TextBox
es and Checkbox
).
Unfortunatelly this method:
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();
}
}
updates everything (i.e. Id_Lecturer
, Name
, Academic_Degree_Id
and AcademicDegree
) except Courses
which are unchanged after db.SaveChanges()
.
Why? How can I fix it?
Similar problems:
- 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
-- Edit --
I also tried this way (idea came from 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();
}
}
but still Courses don't overwrite old values.
-- Edit 2 --
In response to the @Slauma question I'll describe how I loaded SelectedLecturer
(which is passed to UpdateLecturer(Lecturer lecturer)
method as an argument).
I'm implementing MVVM concept so I have View project within my Solution with DataContext
set to LecturerListViewModel
. In View there is DataGrid
with list of all lecturers fetched from databse. DataGrid
is binded this way:
<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
are fetched from database in LecturerListViewModel
constructor in this way:
///
/// 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();
}
}
Setting the state to
Modified
andSetValues
only updates scalar properties of theLecturer
entity. Updating theCourses
collection (which is not a scalar property) needs more work. You must handle the cases that a course could have been removed from the collection, a course could have been added or the scalar properties of a course could have been modified.Also the way to update the collection depends on a course being dependent on the lecturer or not. Does the course need to be deleted from the database when it has been removed from the collection or does only the relationship between lecturer and course need to be removed? Does a new course need to be created when it has been added to the collection or does only a relationship need to be established?
If no courses must be deleted and no new courses be created the Update method could look like this:
Depending on the details of your scenario the correct solution might be slightly different.
Edit
If the courses in the
lecturer.Courses
collection have a reference to thelecturer
(having aLecturer
navigation property maybe) you could have problems when you attach a course from this collection to the context becauselecturerInDb
is already attached and has the same key. You can try to change the lastelse
block like so to solve the problem hopefully: