J'écris une application WPF, en utilisant une conception MVVM avec Entity Framework 4 comme ORM. J'ai des propriétés de collection dans mon modèle de vue qui contiendra des collections d'entités renvoyées à partir de EF4 en tant que collections IEnumerable<T>
en réponse à des requêtes soumises à partir de la couche de gestion.Entity Framework 4 et WPF
J'avais espéré emballer simplement le jeu de résultats IEnumerable<T>
dans un ObservableCollection<T>
. Cependant, je me suis retrouvé à écrire du code de suivi des modifications dans mon dépôt, ou à maintenir des collections fantômes d'objets modifiés, juste pour garder le modèle de vue et la couche de persistance synchronisés. Chaque fois qu'une entité est ajoutée à la collection dans le modèle de vue, je devais aller dans mon référentiel pour l'ajouter au jeu d'objets EF4. Je devais faire le même genre de chose avec des mises à jour et des suppressions.
Pour simplifier les choses, j'ai emprunté une classe EdmObservableCollection<T>
à partir du projet WPF Application Framework sur CodePlex (http://waf.codeplex.com/). La classe enveloppe un ObservableCollection<T>
avec une référence à un EF4 ObjectContext
, de sorte que le CO peut être mis à jour lorsque la collection est mise à jour. J'ai réimprimé la classe EdmObservableCollection
ci-dessous. La classe fonctionne plutôt bien, mais elle a une odeur de code, parce que je me retrouve avec une référence à EF4 dans mon modèle de vue.
Voici ma question: Dans une application WPF, quelle est la manière habituelle de garder une collection d'entités EF4 en phase avec son contexte d'objet? EdmObservableCollection est-il une approche appropriée ou existe-t-il un meilleur moyen? Est-ce que je manque quelque chose de fondamental dans le travail avec EF4? Merci de votre aide.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Objects;
using System.Linq;
namespace Ef4Sqlce4Demo.ViewModel.BaseClasses
{
/// <summary>
/// An ObservableCollection for Entity Framework 4 entity collections.
/// </summary>
/// <typeparam name="T">The type of EF4 entity served.</typeparam>
/// <remarks>Developed from WPF Application Framework (WAF) http://waf.codeplex.com/</remarks>
public class EdmObservableCollection<T> : ObservableCollection<T>
{
#region Fields
// Member variables
private readonly string m_EntitySetName;
private readonly ObjectContext m_ObjectContext;
#endregion
#region Constructors
/// <summary>
/// Creates a new EDM Observable Collection and populates it with a list of items.
/// </summary>
/// <param name="objectContext">The EF4 ObjectContext that will manage the collection.</param>
/// <param name="entitySetName">The name of the entity set in the EDM.</param>
/// <param name="items">The items to be inserted into the collection.</param>
public EdmObservableCollection(ObjectContext objectContext, string entitySetName, IEnumerable<T> items)
: base(items ?? new T[] {})
{
if (objectContext == null)
{
throw new ArgumentNullException("objectContext");
}
if (entitySetName == null)
{
throw new ArgumentNullException("entitySetName");
}
m_ObjectContext = objectContext;
m_EntitySetName = entitySetName;
}
/// <summary>
/// Creates an empty EDM Observable Collection that has an ObjectContext.
/// </summary>
/// <param name="objectContext">The EF4 ObjectContext that will manage the collection.</param>
/// <param name="entitySetName">The name of the entity set in the EDM.</param>
public EdmObservableCollection(ObjectContext objectContext, string entitySetName)
: this(objectContext, entitySetName, null)
{
}
/// <summary>
/// Creates an empty EDM Observable Collection, with no ObjectContext.
/// </summary>
/// <remarks>
/// We use this constructor to create a placeholder collection before we have an
/// ObjectContext to work with. This state occurs when the program is first launched,
/// before a file is open. We need to initialize collections in the application's
/// ViewModels, so that the MainWindow can get Note and Tag counts, which are zero.
/// </remarks>
public EdmObservableCollection()
{
}
#endregion
#region Method Overrides
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
m_ObjectContext.AddObject(m_EntitySetName, item);
}
protected override void RemoveItem(int index)
{
T itemToDelete = this[index];
base.RemoveItem(index);
m_ObjectContext.DeleteObject(itemToDelete);
}
protected override void ClearItems()
{
T[] itemsToDelete = this.ToArray();
base.ClearItems();
foreach (T item in itemsToDelete)
{
m_ObjectContext.DeleteObject(item);
}
}
protected override void SetItem(int index, T item)
{
T itemToReplace = this[index];
base.SetItem(index, item);
m_ObjectContext.DeleteObject(itemToReplace);
m_ObjectContext.AddObject(m_EntitySetName, item);
}
#endregion
#region Public Methods
/// <summary>
/// Adds an object to the end of the collection.
/// </summary>
/// <param name="item">The object to be added to the end of the collection.</param>
public new void Add(T item)
{
InsertItem(Count, item);
}
/// <summary>
/// Removes all elements from the collection.
/// </summary>
/// <param name="clearFromContext">Whether the items should also be deleted from the ObjectContext.</param>
public void Clear(bool clearFromContext)
{
if (clearFromContext)
{
foreach (T item in Items)
{
m_ObjectContext.DeleteObject(item);
}
}
base.Clear();
}
/// <summary>
/// Inserts an element into the collection at the specified index.
/// </summary>
/// <param name="index">The zero-based index at which item should be inserted.</param>
/// <param name="item">The object to insert.</param>
public new void Insert(int index, T item)
{
base.Insert(index, item);
m_ObjectContext.AddObject(m_EntitySetName, item);
}
/// <summary>
/// Updates the ObjectContext for changes to the collection.
/// </summary>
public void Refresh()
{
m_ObjectContext.SaveChanges();
}
/// <summary>
/// Removes the first occurrence of a specific object from the collection.
/// </summary>
/// <param name="item">The object to remove from the collection.</param>
public new void Remove(T item)
{
base.Remove(item);
m_ObjectContext.DeleteObject(item);
}
#endregion
}
}
Points intéressants et réfléchis; +1 de moi. Je vois certains des problèmes différemment. Plus précisément, je fais beaucoup de validation de données et de règles métier. J'utilise l'événement CollectionChanging dans le modèle de vue pour appeler une méthode de service pour effectuer la validation. Si la validation échoue, la méthode de service annule l'opération et rappelle la vue pour afficher un message d'erreur approprié. –
@David Veeneman: Je ne connaissais pas cet événement, intéressant! Est-il disponible dans WPF? Je faisais juste des recherches et je ne pouvais le trouver que dans les espaces de noms Windows Forms. – Slauma
Oups - c'est un événement que j'avais implémenté sur une autre version de EdmObservableCollection. Assez facile à faire - déclarer les arguments de l'événement pour passer l'opération (ajouter, mettre à jour, supprimer), l'objet affecté, et un drapeau d'annulation (lire/écrire). Soulevez l'événement dans chaque méthode et affecte la collection, avant d'appeler la classe de base, et quittez si Cancel est défini sur true. Je vais faire un article CodeProject sur ce sujet, et j'inclurai l'événement dans ce code. Merci pour la capture! –