Van egy kis probléma. Ha EF-et használunk, akkor a WCF Data Service a kliensen minden partial class publikus tulajdonságot amit a generált osztályokhoz megadunk szerializálni fog. Az ok az, hogy EF kontextus esetén a WCF Data Service nem használ reflection-t, emiatt nincs is hozzá olyan attribútum ami ezt megkerülhetné. Miért probléma ez? Csak azért, mert a SaveChanges hanyatvágódik tőle és kissé nehézkes a szokványos felületeket, mint IEditableObject, IDataErrorInfo, stb. implementálni publikus tulajdonságok nélkül, amiket pl. WPF és Silverlight vezérlőkhöz is kötni lehet. Szerencsére lehetőség van beleavatkozni abba, hogy mi menjen ki a szerverre, csak egy kicsit meg kell hekkelni a cuccot. Ehhez csináltam egy két segédosztályt. Először kell egy attribútum amit a kliens osztály tulajdonságaira lehet aggatni:
namespace System.Data.Services.Common { [AttributeUsage(AttributeTargets.Property)] public class NonSerializedDataAttribute : Attribute { } }
Ez eddig okés. Itt jön a lényeg, ráakaszkodunk a data service WritingEntity eseményére:
using System.Collections.Generic; using System.Data.Services.Client; using System.Diagnostics.Contracts; using System.Linq; using System.Xml.Linq; namespace System.Data.Services.Common { public class DataServiceContextSerialization { #region Constructors public DataServiceContextSerialization(DataServiceContext context) { Contract.Assert(context != null); NonSerializedPropertyCache = new Dictionary<Type, IList<string>>(); context.WritingEntity += new EventHandler<ReadingWritingEntityEventArgs>(Context_WritingEntity); } #endregion #region Properties private readonly Dictionary<Type, IList<string>> NonSerializedPropertyCache; #endregion #region Methods private IEnumerable<string> GetNonSerializedProperties(Type type) { var properties = (IList<string>)null; if (!NonSerializedPropertyCache.TryGetValue(type, out properties)) { properties = new List<string>(); foreach (var property in type.GetProperties()) { var attributes = property.GetCustomAttributes(typeof(NonSerializedDataAttribute), false); if (attributes.Length > 0) { properties.Add(property.Name); } } NonSerializedPropertyCache.Add(type, properties); } return properties; } #endregion #region Event Handlers private void Context_WritingEntity(object sender, ReadingWritingEntityEventArgs e) { var xnEntityProperties = XName.Get("properties", e.Data.GetNamespaceOfPrefix("m").NamespaceName); var xePayload = (XElement)null; foreach (var property in GetNonSerializedProperties(e.Entity.GetType())) { if (xePayload == null) { xePayload = e.Data.Descendants().Where<XElement>( xe => xe.Name == xnEntityProperties).First<XElement>(); } var xnProperty = XName.Get(property, e.Data.GetNamespaceOfPrefix("d").NamespaceName); var xeRemoveThisProperty = xePayload .Descendants() .Where<XElement>(xe => xe.Name == xnProperty) .FirstOrDefault<XElement>(); if (xeRemoveThisProperty != null)
{
xeRemoveThisProperty.Remove();
} } } #endregion } }
A használat egyszerű. Kell egy partial class a saját Data Service kliens kontextushoz egy kidekorált konstruktorral:
using System; using System.Data.Services.Common; namespace NetFx.Partnerinfo.ServiceClient.PartnerinfoDataService { public partial class PartnerinfoContext { public PartnerinfoContext(Uri dataServiceUri, bool customSerialization) : this(dataServiceUri) { if (customSerialization) { this.CustomSerialization = new DataServiceContextSerialization(this); } } private readonly DataServiceContextSerialization CustomSerialization; } }
Innentől kezdve pedig bármivel ki lehet bővíteni a kliens generált osztályokat, mint pl.:
using System.ComponentModel; using System.Data.Services.Common; using NetFx.Core; namespace NetFx.Partnerinfo.ServiceClient.PartnerinfoDataService { public partial class Address : IEditableObject { #region Constructors public Address() { this.EditableObjectAdapter = new EditableObjectAdapter<Address>(this); } #endregion #region Editable Adapter [NonSerializedData] public EditableObjectAdapter<Address> EditableObjectAdapter { get; private set; } #endregion #region IEditableObject public void BeginEdit() { EditableObjectAdapter.BeginEdit(); } public void CancelEdit() { EditableObjectAdapter.CancelEdit(); } public void EndEdit() { EditableObjectAdapter.EndEdit(); } #endregion } }
Egy dologra azért figyelni kell így is, setter kell legyen minden tulajdonsághoz. Ha tudtok jobbat és egyszerűbbet, akkor szívesen veszem! 😀