2011-05-04 3 views
1

J'ai un problème avec quelque chose qui semble être un bug dans Entity Framework 4.1: J'ai ajouté un gestionnaire sur ObjectContext.SavingChanges qui met à jour une propriété "LastModified" à chaque fois qu'un objet est ajouté à ou modifié dans la base de données. Ensuite, je fais ce qui suit:Entity Framework 4.1 bug dans ObjectContext.SavingChanges manutention (?)

  1. Ajouter deux objets à la base de données, et soumettre (appel SaveChanges())
  2. Modifier le premier objet qui a été ajouté
  3. Extrait les deux objets commandés par LastModified

Les objets résultants sont renvoyés dans le mauvais ordre. En regardant les objets, je peux voir que la propriété LastModified a été mise à jour. En d'autres termes, l'événement SavingChanges a été déclenché correctement. Mais en regardant dans la base de données, la colonne LastModified n'a pas été modifiée. C'est-à-dire qu'il existe maintenant une différence entre les objets mis en cache d'EF et les lignes de la base de données.

J'ai essayé d'effectuer la même mise à jour LastModified dans une méthode « SaveChanges » surchargée:

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

Faire cela a provoqué la base de données mise à jour correctement et les requêtes ont retourné les objets dans le bon ordre.

Voici un programme de test complet montrant l'erreur:

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

Je dois ajouter que le gestionnaire SavingChanges a bien fonctionné avant nous sommes passés à EF4.1 et en utilisant le code-première (qui est, il a travaillé dans EF4 .0 avec le modèle-premier)

La question est: Ai-je trouvé un bug ici, ou ai-je fait quelque chose de mal?

Répondre

2

Je ne suis pas sûr si cela peut être considéré comme un bug. Ce qui semble se produire est que la façon dont vous manipulez la propriété LastModified ne déclenche pas INotifyPropertyChanged et que les modifications ne sont donc pas remplies dans votre base de données.

Pour le prouver utilisation:

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

Pour utiliser cette connaissance dans votre 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:

Je regardai dans ce un peu plus et vous avez raison . Pour une raison quelconque, MS a décidé de ne plus déclencher les événements PropertyChanged lors de l'utilisation de PropertyInfo.SetValue. Un seul moyen de savoir s'il s'agit d'un bug ou d'une décision de conception: Déposer un rapport de bogue/Publier sur les forums msdn.

Bien que changer la propriété directement via CurrentValue semble fonctionner très bien:

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

    modifiedEntity.Property("LastModified").CurrentValue = now; 
} 
+0

Merci pour votre réponse. Votre suggestion a réellement fonctionné! Mais je pense qu'il ressemble un peu à un hack - ne devrait-il pas être géré automatiquement par le cadre lorsque je modifie une propriété? Comme je l'ai écrit dans ma question, le même code a bien fonctionné dans EF4.0. – JRV

+0

@JRV Jetez un oeil à mon édition ci-dessus – Till

+0

Great - merci encore. C'est une approche beaucoup plus propre, et je vais y aller.Je vais juste accepter le fait que PropertyInfo.SetValue ne fonctionne plus, maintenant que je connais l'autre façon de le faire :-) – JRV

Questions connexes