Ошибка Entity Framework 4.1 в обработке ObjectContext.SavingChanges (?) - PullRequest
1 голос
/ 04 мая 2011

У меня проблема с чем-то, что кажется ошибкой в ​​Entity Framework 4.1: я добавил обработчик на ObjectContext.SavingChanges, который обновляет свойство "LastModified" всякий раз, когда объект добавляется или изменяется в базе данных.Затем я делаю следующее:

  1. Добавление двух объектов в базу данных и отправка (вызов SaveChanges())
  2. Изменение первого добавленного объекта
  3. Извлечениедва объекта, упорядоченные по LastModified

Полученные объекты возвращаются в неправильном порядке.Глядя на объекты, я вижу, что свойство LastModified обновлено.Другими словами, событие SavingChanges было запущено правильно.Но, глядя в базу данных, столбец LastModified не был изменен.То есть теперь существует разница между кэшированными объектами EF и строками в базе данных.

Я попытался выполнить то же обновление до LastModified в переопределенном методе "SaveChanges":

public override int SaveChanges()
{
  SaveChangesHandler();//updating LastModified property on all objects
  return base.SaveChanges();
}

Это привело к корректному обновлению базы данных, и запросы вернули объекты в правильном порядке.

Вот целая тестовая программа, показывающая ошибку:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Reflection;
using System.Threading;

namespace TestApplication
{
  class Program
  {
    private PersistenceContext context;

    private static void Main(string[] args)
    {
      var program = new Program();
      program.Test();
    }

    public void Test()
    {
      SetUpDatabase();
      var order1 = new Order {Name = "Order1"};
      context.Orders.Add(order1);
      var order2 = new Order {Name = "Order2"};
      context.Orders.Add(order2);
      context.SaveChanges();

      Thread.Sleep(1000);
      order1 = GetOrder(order1.Id); // Modified 1.
      order1.Name = "modified order1";
      context.SaveChanges();

      List<Order> orders = GetOldestOrders(1);
      AssertEquals(orders.First().Id, order2.Id);//works fine - this was the oldest object from the beginning


      Thread.Sleep(1000);
      order2 = GetOrder(order2.Id); // Modified 2.
      order2.Name = "modified order2";
      context.SaveChanges();

      orders = GetOldestOrders(1);
      AssertEquals(orders.First().Id, order1.Id);//FAILS - proves that the database is not updated with timestamps
    }

    private void AssertEquals(long id1, long id2)
    {
      if (id1 != id2) throw new Exception(id1 + " != " + id2);
    }

    private Order GetOrder(long id)
    {
      return context.Orders.Find(id);
    }

    public List<Order> GetOldestOrders(int max)
    {
      return context.Orders.OrderBy(order => order.LastModified).Take(max).ToList();
    }

    public void SetUpDatabase()
    {
      //Strategy for always recreating the DB every time the app is run.
      var dropCreateDatabaseAlways = new DropCreateDatabaseAlways<PersistenceContext>();

      context = new PersistenceContext();

      dropCreateDatabaseAlways.InitializeDatabase(context);
    }
  }


  ////////////////////////////////////////////////
  public class Order
  {
    public virtual long Id { get; set; }
    public virtual DateTimeOffset LastModified { get; set; }
    public virtual string Name { get; set; }
  }

  ////////////////////////////////////////////////
  public class PersistenceContext : DbContext
  {
    public DbSet<Order> Orders { get; set; }

    public PersistenceContext()
    {
      Init();
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    }

    public void Init()
    {
      ((IObjectContextAdapter) this).ObjectContext.SavingChanges += SavingChangesHandler;
      Configuration.LazyLoadingEnabled = true;
    }

    private void SavingChangesHandler(object sender, EventArgs e)
    {
      DateTimeOffset now = DateTimeOffset.Now;

      foreach (DbEntityEntry entry in ChangeTracker.Entries()
        .Where(entity => entity.State == EntityState.Added || entity.State == EntityState.Modified))
      {
        SetModifiedDate(now, entry);
      }
    }

    private static void SetModifiedDate(DateTimeOffset now, DbEntityEntry modifiedEntity)
    {
      if (modifiedEntity.Entity == null)
      {
        return;
      }

      PropertyInfo propertyInfo = modifiedEntity.Entity.GetType().GetProperty("LastModified");

      if (propertyInfo != null)
      {
        propertyInfo.SetValue(modifiedEntity.Entity, now, null);
      }
    }
  }
}

Я должен добавить, что SavingChangesОбработчик работал нормально до того, как мы обновились до EF4.1 и использовали Code-First (то есть он работал в EF4.0 с model-first)

Вопрос в том, нашел ли я здесь ошибку, или ясделал что-то не так?

1 Ответ

2 голосов
/ 04 мая 2011

Я не уверен, что это можно считать ошибкой. Кажется, что происходит то, что способ, которым вы манипулируете свойством LastModified, не вызывает INotifyPropertyChanged и, следовательно, изменения не заполняются в вашей базе данных.

Для доказательства используйте:

    order2.Name = "modified order2";
    ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(order2).SetModifiedProperty("LastModified");

Чтобы использовать эти знания в вашем SavingChangesHandler:

private void SavingChangesHandler(object sender, EventArgs e)
{
  DateTimeOffset now = DateTimeOffset.Now;

  foreach (DbEntityEntry entry in ChangeTracker.Entries()
    .Where(entity => entity.State == EntityState.Added || entity.State == EntityState.Modified))
  {
    SetModifiedDate(now, entry);
    if (entry.State == EntityState.Modified)
    {
      ((IObjectContextAdapter) this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity).SetModifiedProperty("LastModified");
    }
  }
}

Edit:

Я посмотрел на это немного больше, и вы правы. По какой-то причине MS решила больше не запускать события PropertyChanged при использовании PropertyInfo.SetValue. Только один способ выяснить, является ли это ошибкой или дизайнерским решением: подать отчет об ошибке / опубликовать на форуме msdn.

Хотя изменение свойства напрямую через CurrentValue, похоже, работает нормально:

private static void SetModifiedDate(DateTimeOffset now, DbEntityEntry modifiedEntity)
{
  if (modifiedEntity.Entity == null)
  {
    return;
  }

  modifiedEntity.Property("LastModified").CurrentValue = now;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...