Udostępnij za pośrednictwem


Customizing Serialization of Entities in the ADO.NET Data Services Client Library

This post is inspired by this forum thread :Data services client -- exception on saveChanges

Problem Statement

I have added some custom proeprties to the Entity Types Generated by DataSvcUtil.exe /Add Service Reference / Hand Coded ,
these properties do not exist on the server and should not be sent to the server .
When I try to save an object of a Type which has custom client-Side only properties to the Data Service using AddObject and SaveChanges,
I get the following error :

 Error processing request stream. The property name '{PropertyName}' specified for type '{TypeName} is not valid

This error is basically from the server telling the client library that its sending a payload which does not seem to match the definition
of the Entity Type on the server.

We can solve this problem by customizing the Payload that the client generates when it sends the entity to the server.
The DataServiceContext exposes an event called WritingEntity that is fired right before we send the payload over the wire.

Its argumentlist contains ReadingWritingEntityEventArgs which gives you access to the Entity Instance being Serialized ( e.Entity ) 
and the ATOM Payload (e.Data ) we are about to send to the Server.

Removing a property from the ATOM Payload that is being sent to the server.
 void dataContext_WritingEntity(object sender, ReadingWritingEntityEventArgs e) {

    // e.Data gives you the XElement for the Serialization of the Entity 
    //Using XLinq  , you can  add/Remove properties to the element Payload  
    XName xnEntityProperties = XName.Get("properties", e.Data.GetNamespaceOfPrefix("m").NamespaceName);
    XElement xePayload = e.Data.Descendants().Where<XElement>(xe => xe.Name == xnEntityProperties).First<XElement>();
   //The XName of the property we are going to remove from the payload
    XName xnProperty = XName.Get(“{PropertyName}”, e.Data.GetNamespaceOfPrefix("d").NamespaceName);

    //Get the Property of the entity  you don't want sent to the server
    XElement xeRemoveThisProperty = xePayload.Descendants().Where<XElement>(xe => xe.Name == xnProperty).First<XElement>();
    //Remove this property from the Payload sent to the server 
     xeRemoveThisProperty.Remove();

     }
}

Generalizing this further , we want to make it easier to customize the serialization of multiple entity types without having to write
multiple copies of the WritingEntity event handler.
We will add an attribute that specfies that a certain CLR property should not be serialized in the ATOM Payload when the client library
sends the entity to the server.

     /// <summary>
    /// Properties marked with this Attribute are not serialized in the payload when sent to the server
    /// </summary>
    [AttributeUsage(AttributeTargets.Property)]
    public class DoNotSerializeAttribute : Attribute
    {
    }

Now , we change the WritingEntity Event handler to remove any property  that has this attribute on it .

         void DataServiceContextEx_WritingEntity(object sender, ReadingWritingEntityEventArgs e)
        {
            // e.Data gives you the XElement for the Serialization of the Entity 
            //Using XLinq  , you can  add/Remove properties to the element Payload  
            XName xnEntityProperties = XName.Get("properties", e.Data.GetNamespaceOfPrefix("m").NamespaceName);
            XElement xePayload = null;
            foreach (PropertyInfo property in e.Entity.GetType().GetProperties())
            {
                object[] doNotSerializeAttributes = property.GetCustomAttributes(typeof(DoNotSerializeAttribute), false);
                if (doNotSerializeAttributes.Length > 0)
                {
                    if (xePayload == null)
                    {
                        xePayload = e.Data.Descendants().Where<XElement>(xe => xe.Name == xnEntityProperties).First<XElement>();
                    }
                    //The XName of the property we are going to remove from the payload
                    XName xnProperty = XName.Get(property.Name, e.Data.GetNamespaceOfPrefix("d").NamespaceName);
                    //Get the Property of the entity  you don't want sent to the server
                    XElement xeRemoveThisProperty = xePayload.Descendants().Where<XElement>(xe => xe.Name == xnProperty).First<XElement>();
                    //Remove this property from the Payload sent to the server 
                    xeRemoveThisProperty.Remove();
                }
            }
        }

We can encapsulate this functionality into its own class and any DataServiceContext type that inherits from this class should be able to
inherit this functionality too .
Complete Source Code :

Using this in your applications :

Create a partial class with the same name as shown above and include it in your application to get this behavior.

Attribute your types with the DoNotSerializeAttribute  attribute so that this property is not serialized.

  public class TestType
    {
        public int ID { get; set; }
        /// <summary>
        /// This Property is client-only , should not be sent to the server
        /// </summary>
        [DoNotSerialize]
        public string ClientOnlyProperty { get; set; }
    }

Comments

  • Anonymous
    December 12, 2008
    One of the most common feature requests we hear for Astoria is the ability to mark properties on an entity

  • Anonymous
    March 27, 2009
    You know this completes the circle since whe can partialize a data entity to implemente INotifyPropertyChanged and we can mark an attribute to dont be part of the context we now really can customize and use a data entity in business layer so we dont need extra classes to achieve this, thats one of the many things i love data services, great post! thank you!!

  • Anonymous
    April 22, 2009
    This is a very cool idea, but I'm curious if it can be used on a field in an ADO.NET entity model?  If so, how would I set a field there to have the DoNotSerialize property?

  • Anonymous
    April 22, 2009
    Hi Charles, This property is client-side only.   By design , the server does not expose any events for us to hook in and change the markup . Which scenario are you trying to solve with this ? -Phani

  • Anonymous
    May 12, 2009
    I may be missing something but I'm trying to use this in a Silverlight project and my derived class that inherits from DataServiceContext is auto generated for my Service Reference.  If I update the code that defines my Service Reference to inherit from my new custom class it is simply blown away the next time I refresh the service.   What am I missing?

  • Anonymous
    May 21, 2009
    Hi John, You are right . I wanted to update this blog entry to use a partial class instead of an inherited class for a long time . Using a partial class preserves this code in another code file and doesnt get affected if you re-generate the client proxy classes. -Phani

  • Anonymous
    July 15, 2009
    I was having the same issue with ADO.NET Service generated classes that we wanted to extend (since they are partial classes). I tried several techniques to do the serialization in order to exclude the newly created (extended) properties to the ADO.NET generated classes, since ADO.NET detects the newly created properties; which causes an exception specifying the NONE existing properties. Some didn't work, other were very tricky. So I found this solution much cleaner and practical. The only problem I found was the LINQ code to access the extended properties to remove them from the ATOM Payload, doesn't work anymore. The methods to process the Lambda expression don't exist or the version I have in silverlight is different. So I fix it this way to eliminate a propertyname (string): XName xNameProperty = XName.Get(propertyName, rootElement.GetNamespaceOfPrefix("d").NamespaceName); //Get descendants for xNameProperty object within ALL xElements IEnumerable<XElement> PropertyElements = rootElement.Descendants(xNameProperty); //Initialize variable XElement elementxHasProperty = null; //Find xElement for the searched propertyName foreach (XElement element in PropertyElements) {        //Verify if it is the property looked for        if (element.Name.LocalName == xNameProperty.LocalName)        {            elementxHasProperty = element;            break;         } } //Verify if the propertyName was found to remove the property from being sent to ADO.NET Services if (elementxHasProperty != null)                elementxHasProperty.Remove(); Thank you so much for your guidance

  • Anonymous
    July 13, 2010
    Hi, We had a tough time trying to solve this issue of not sending the custom properties of the entities to the server. your article was of great help :). Thanks a lot

  • Anonymous
    May 12, 2011
    This post inspired me to write an [Encrypt] attribute to encrypt the contents of each table property. Source code: azuretableencrypt.codeplex.com I'm tracking issues on this MSDN thread. social.msdn.microsoft.com/.../5ba62aa0-46c1-4599-b44f-8df4798cacad

  • Anonymous
    May 27, 2011
    When ADO tried to write one of my properties (it was a 'get' only) it crashed the original Phani's code. After slight change all came to normal and worked. Great job Phani! Thanks a lot! XElement xeRemoveThisProperty = xePayload                                           .Descendants()                                           .Where<XElement>(xe => xe.Name == xnProperty)                                           .FirstOrDefault<XElement>();                            //Remove this property from the Payload sent to the server                            if (xeRemoveThisProperty != null)                                xeRemoveThisProperty.Remove();

  • Anonymous
    August 18, 2011
    Hi Phani,    I describe some goals to my table storage entity class in stackoverflow.com/.../effective-custom-azure-table-storage-entity-serialization, two of them I think that can't accomplish with this approach. There are another approach?! Thank's

  • Anonymous
    September 13, 2011
    AHHH what a great solution. Too bad I found this right at the end of my project! There was some bad code written because of the need for client-side metadata that I thought couldn't be placed in the entity. It also allows helps solve a problem of controls such as the telerik RadGridView wanting to sort and group data based on the binding source and not the converted value. Sometimes it can't sort and group on the binding source. I am thinking about storing the converted value in one of these extended properties.

  • Anonymous
    November 22, 2011
    The comment has been removed