Estoy escribiendo una aplicación WPF, utilizando un diseño MVVM con Entity Framework 4 como ORM. Tengo propiedades de recopilación en mi modelo de vista que contendrán colecciones de entidades devueltas de EF4 como colecciones IEnumerable<T>
en respuesta a consultas enviadas desde la capa empresarial.Entity Framework 4 y WPF
Esperaba simplemente envolver el conjunto de resultados IEnumerable<T>
en un ObservableCollection<T>
. Sin embargo, me encontré escribiendo código de seguimiento de cambios en mi repositorio, o manteniendo colecciones ocultas de objetos cambiados, solo para mantener el modelo de vista y la capa de persistencia sincronizados. Cada vez que se agrega una entidad a la colección en el modelo de vista, tuve que ir a mi repositorio para agregarlo al conjunto de objetos EF4. Tenía que hacer el mismo tipo de cosas con las actualizaciones y eliminaciones.
Para simplificar las cosas, me pidió prestado una clase EdmObservableCollection<T>
del marco de aplicaciones WPF proyecto en CodePlex (http://waf.codeplex.com/). La clase ajusta un ObservableCollection<T>
con una referencia a un EF4 ObjectContext
, de modo que el OC se puede actualizar a medida que se actualiza la colección. He reimpreso la clase EdmObservableCollection
a continuación. La clase funciona bastante bien, pero tiene un poco de olor a código, porque termino con una referencia a EF4 en mi modelo de vista.
Aquí está mi pregunta: En una aplicación WPF, ¿cuál es la forma habitual de mantener una colección de entidades EF4 sincronizada con el contexto de su objeto? ¿Es EdmObservableCollection un enfoque adecuado, o hay una mejor manera? ¿Me estoy perdiendo algo fundamental al trabajar con EF4? Gracias por tu ayuda.
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
}
}
Puntos interesantes y atentos; +1 de mi parte Veo algunos de los problemas de manera diferente. Específicamente, realizo mucha validación de reglas de datos y negocios. Uso el evento CollectionChanging en el modelo de vista para invocar un método de servicio para hacer la validación. Si la validación falla, el método de servicio cancela la operación y devuelve la llamada a la vista para mostrar un mensaje de error apropiado. –
@David Veeneman: ¡No sabía este evento, interesante! ¿Está disponible en WPF? Solo estaba buscando y solo pude encontrarlo en los espacios de nombres de Windows Forms. – Slauma
Vaya, ese es un evento que había implementado en otra versión de EdmObservableCollection. Bastante fácil de hacer: declare los argumentos del evento para pasar la operación (agregar, actualizar, eliminar), el objeto afectado y un indicador de Cancelar (leer/escribir). Levante el evento en cada método y afecte a la colección, antes de llamar a la clase base, y salga si Cancel se establece en verdadero. Voy a hacer un artículo de CodeProject sobre este tema, e incluiré el evento en ese código. Gracias por la captura! –