Entity Framework Code First - Почему я не могу обновить сложные свойства таким образом? - PullRequest
27 голосов
/ 01 апреля 2011

Я работаю над небольшим примером проекта с использованием Entity Framework 4.1 (сначала код).Мои классы выглядят так:

public class Context : DbContext
{
    public IDbSet<Person> People { get; set; }
    public IDbSet<EmployeeType> EmployeeTypes { get; set; }
}

public class Person
{
    [Key]
    public int Key { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    virtual public EmployeeType EmployeeType { get; set; }
}

public class EmployeeType
{
    [Key]
    public int Key { get; set; }
    public string Text { get; set; }

    virtual public ICollection<Person> People { get; set; }
}

Я сохранил пару EmployeeTypes («first», «second») в базе данных, и я сохранил Person, который имеет первый тип.Теперь я хочу изменить Человека.Я знаю, что могу сделать это, загрузив Person, изменив свойства, а затем сохранив.Но вместо этого я хочу сделать следующее:

var c = new Context();
var e = c.EmployeeTypes.Single(x => x.Text.Equals("second"));
var p = new Person { 
            Key = originalKey,       // same key
            FirstName = "NewFirst",  // new first name
            LastName = "NewLast",    // new last name
            EmployeeType = e };      // new employee type
c.Entry(p).State = EntityState.Modified;
c.SaveChanges();

Как ни странно, это меняет FirstName и LastName, но не EmployeeType.Если я получаю новый Контекст и запрашиваю этого Person, EmployeeType остается установленным на «first», как это было до запуска этого кода.

Что мне нужно сделать, чтобы обновить свойства навигации,а не только скалярные свойства? (Это особенно озадачивает, потому что для EmployeeType единственное, что действительно нужно изменить, - это внешний ключ в таблице Person, и этот ключ является скалярным свойством.)

(Кстати, я знаю, что могу сделать это, сначала извлекая Person, затем меняя свойства по одному, но, поскольку я использую привязку модели в ASP.NET MVC, кажется, что этот способ будет проще, потому чтоУ меня будет обновленный объект person уже в моем методе POST.)

Ответы [ 3 ]

19 голосов
/ 01 апреля 2011

Вы можете попробовать это по-другому:

var c = new Context();
var e = c.EmployeeTypes.Single(x => x.Text.Equals("second"));
var p = new Person { 
            Key = originalKey,       // same key
            FirstName = "NewFirst",  // new first name
            LastName = "NewLast"};   // new last name
c.People.Attach(p); // Attach person first so that changes are tracked 
c.Entry(p).Reference(e => e.EmployeeType).Load();               
p.EmployeeType = e; // Now context should know about the change
c.Entry(p).State = EntityState.Modified;
c.SaveChanges();

Другой подход заключается в предоставлении внешнего ключа в вашей Person сущности, например:

public class Person
{
    [Key]
    public int Key { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    [ForeignKey("EmployeeType")]
    public int EmployeeTypeKey { get; set; }
    public virtual EmployeeType EmployeeType { get; set; }
}

Это изменит тип отношения междуPerson и EmployeeType от Независимой ассоциации до Ассоциации внешних ключей.Вместо назначения свойства навигации назначьте свойство внешнего ключа.Это позволит вам изменить отношение вашим текущим кодом.

Проблема в том, что независимые ассоциации (не использующие свойство внешнего ключа) обрабатываются как отдельный объект в диспетчере состояний / отслеживании изменений.Таким образом, ваша модификация человека не повлияла на состояние существующего отношения и не установило новое отношение. Я спросил в MSDN , как это сделать с помощью API DbContext, но это возможно только в том случае, если вы приведете DbContext к ObjectContext и используете ObjectStateManager и ChangeRelationshipState.

2 голосов
/ 11 апреля 2011

Попробовав дюжину различных способов сделать это способом EF, я пришел к выводу, что нет разумного кода EF. Первый способ сделать то, что я пытаюсь сделать. Поэтому я использовал отражение. Я создал этот метод для своего класса, который наследуется от DbContext:

public void UpdateFrom<T>(T updatedItem) where T : KeyedItem
{
    var originalItem = Set<T>().Find(updatedItem.Key);
    var props = updatedItem.GetType().GetProperties(
        BindingFlags.Public | BindingFlags.Instance);
    foreach (var prop in props)
    {
        var value = prop.GetValue(updatedItem, null);
        prop.SetValue(originalItem, value, null);
    }
}

Все мои объекты наследуются от абстрактного класса и имеют общее свойство первичного ключа, поэтому он находит существующий объект с тем же ключом, что и переданный, и затем обновляет существующие объекты из нового. SaveChanges должен быть вызван впоследствии.

1 голос
/ 13 апреля 2011

Это работает для коллекций, хотя я чувствую, что должен быть лучший способ.


var properties = typeof(TEntity).GetProperties();
foreach (var property in properties)
{
    if (property.GetCustomAttributes(typeof(OneToManyAttribute), false).Length > 0)
    {
        dynamic collection = db.Entry(e).Collection(property.Name).CurrentValue;
        foreach (var item in collection)
        {
            if(item.GetType().IsSubclassOf(typeof(Entity))) 
            {
                if (item.Id == 0)
                {
                    db.Entry(item).State = EntityState.Added;
                }
                else
                {
                    db.Entry(item).State = EntityState.Modified;
                }
             }
        }
    }
}
db.Entry(e).State = EntityState.Modified;            
db.SaveChanges();

...