1

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 TextBoxes 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:


-- 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();
    }
}
Community
  • 1
  • 1
patryk.beza
  • 4,876
  • 5
  • 37
  • 56

1 Answers1

2

Setting the state to Modified and SetValues only updates scalar properties of the Lecturer entity. Updating the Courses 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:

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();
}

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 the lecturer (having a Lecturer navigation property maybe) you could have problems when you attach a course from this collection to the context because lecturerInDb is already attached and has the same key. You can try to change the last else block like so to solve the problem hopefully:

            else
            {
                // Add courses relationships
                var courseToAttach = new Course { Id_Course = course.Id_Course };
                db.Courses.Attach(courseToAttach);
                lecturerInDb.Courses.Add(courseToAttach);
            }
Slauma
  • 175,098
  • 59
  • 401
  • 420
  • It's almost that what I wanted to achieve. The problem is the **exception** during executing line `db.Courses.Attach(course);`. Exception: _An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key._ I tried to remove line `db.Courses.Attach(course);` from code but then I have exception during executing `db.SaveChanges()`. Exception: _The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects._ – patryk.beza Nov 14 '12 at 22:09
  • @patryk.beza: Hm, the last exception ("attached to **different** ObjectContext") indicates that your `SelectedLecturer` you pass into the method (and/or related courses) is attached to another context than the one you are creating in the method (in the `using` block). If so, you cannot use the method above at all. The method is designed for a detached scenario. I expected you are in such a scenario because you create a new context in the method. Can you maybe describe in your question how you load the `SelectedLecturer` and if you dispose the context it is loaded in or not? – Slauma Nov 14 '12 at 22:46
  • I updated my first post (*Edit 2*). There is detailed explanation how I loaded `SelectedLecturer`. – patryk.beza Nov 15 '12 at 13:30
  • @patryk.beza: Can you try to add in your `GetAllLecturers()` method: `dbb.Configuration.ProxyCreationEnabled = false;` (as first line in the `using` block) and test again? – Slauma Nov 15 '12 at 13:57
  • It didn't help. ;/ Still *An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.* in line `db.Courses.Attach(course);`. – patryk.beza Nov 15 '12 at 14:44
  • @patryk.beza: Can you check the `SelectedLecturer.Courses` collection when you pass the `SelectedLecturer` into the `UpdateLecturer` method. Do the `Course` objects in that collection all have a **different** key value `Id_Course`? – Slauma Nov 15 '12 at 14:52
  • Yes, they have unique key values. Primary key `Id_Course` has `Identity Increment = 1` (auto increment key). – patryk.beza Nov 15 '12 at 17:43
  • 1
    @patryk.beza: I have a new idea :) See my Edit above. – Slauma Nov 15 '12 at 18:22
  • Wow! Amazing! You are my Hero. xD Thank you very much. I appreciate your effort & time to understand my (quite) complex problem. Thank you again! ;) – patryk.beza Nov 15 '12 at 19:07