0
votes

I am following the sample blog below to remove and add properties in request ODataEntry class.

http://blogs.msdn.com/b/odatateam/archive/2013/07/26/using-the-new-client-hooks-in-wcf-data-services-client.aspx

But even if the code works fine and adds and removes the properties correctly when I put breakpoint, all the entity properties goes to server un changed.

Only difference I see this I am using the OData V4 and new Ondata client to hook up.

My code looks below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Client.Default;

namespace Client
{

using Client.MvcApplication1.Models;

using Microsoft.OData.Core;

internal class Program
{
    private static void Main(string[] args)
    {
        Container container = new Container(new Uri("http://localhost:55000/api/"));

        container.Configurations.RequestPipeline.OnEntryEnding(
            w =>
            {
                w.Entry.RemoveProperties("Name");
            });

        Test test = new Test();
        test.Name = "Foo";
        CustomFields cs = new CustomFields { ServiceId = 3 };
        cs.Foo1 = 2;
        test.S_1 = cs;
        container.AddToTests(test);
        container.SaveChanges();
    }
}

public static class Extensions
{

    public static void RemoveProperties(this ODataEntry entry, params string[] propertyNames)
    {

        var properties = entry.Properties as List<ODataProperty>;
        if (properties == null)
        {
            properties = new List<ODataProperty>(entry.Properties);
        }

        var propertiesToRemove = properties.Where(p => propertyNames.Any(rp => rp == p.Name));
        foreach (var propertyToRemove in propertiesToRemove.ToArray())
        {
            properties.Remove(propertyToRemove);
        }

        entry.Properties = properties;

    }

    public static void AddProperties(this ODataEntry entry, params ODataProperty[] newProperties)
    {
        var properties = entry.Properties as List<ODataProperty>;
        if (properties == null)
        {
            properties = new List<ODataProperty>(entry.Properties);
        }

        properties.AddRange(newProperties);

        entry.Properties = properties;

    }
}
}

If I change and start listening to RequestPipeline.OnEntryStarting I get the validation error that new property is not defined in owning entity. But as per code for Microsoft.OData.CLient this error should not occure as there is a check for IEdmStructuredType.IsOpen but still error occurs. So issue seems deep in how owningStructuredType is calculated. On my container I do see the correct edm model with entities marked as IsOpen = true.

Odata lib code which should pass but is failing

internal static IEdmProperty ValidatePropertyDefined(string propertyName, IEdmStructuredType owningStructuredType)
        {
            Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)");

            if (owningStructuredType == null)
            {
                return null;
            }

            IEdmProperty property = owningStructuredType.FindProperty(propertyName);

            // verify that the property is declared if the type is not an open type.
            if (!owningStructuredType.IsOpen && property == null)
            {
                throw new ODataException(Strings.ValidationUtils_PropertyDoesNotExistOnType(propertyName, owningStructuredType.ODataFullName()));
            }

            return property;
        }

Client code:

container.Configurations.RequestPipeline.OnEntryStarting(
                w =>
                {
                    w.Entry.RemoveProperties("Name");
                    w.Entry.AddProperties(new ODataProperty
                                              {
                                                  Name = "NewProperty",
                                                  Value = 1
                                              });
                });

Error:

The property 'NewProperty' does not exist on type 'Client.MvcApplication1.Models.Test'. Make sure to only use property names that are defined by the type.

   at Microsoft.OData.Core.WriterValidationUtils.ValidatePropertyDefined(String propertyName, IEdmStructuredType owningStructuredType)
   at Microsoft.OData.Core.JsonLight.ODataJsonLightPropertySerializer.WriteProperty(ODataProperty property, IEdmStructuredType owningType, Boolean isTopLevel, Boolean allowStreamProperty, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, ProjectedPropertiesAnnotation projectedProperties)
   at Microsoft.OData.Core.JsonLight.ODataJsonLightPropertySerializer.WriteProperties(IEdmStructuredType owningType, IEnumerable`1 properties, Boolean isComplexValue, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, ProjectedPropertiesAnnotation projectedProperties)
   at Microsoft.OData.Core.JsonLight.ODataJsonLightWriter.StartEntry(ODataEntry entry)
   at Microsoft.OData.Core.ODataWriterCore.<>c__DisplayClass14.<WriteStartEntryImplementation>b__12()
   at Microsoft.OData.Core.ODataWriterCore.InterceptException(Action action)
   at Microsoft.OData.Core.ODataWriterCore.WriteStartEntryImplementation(ODataEntry entry)
   at Microsoft.OData.Core.ODataWriterCore.WriteStart(ODataEntry entry)
   at Microsoft.OData.Client.ODataWriterWrapper.WriteStart(ODataEntry entry, Object entity)
   at Microsoft.OData.Client.Serializer.WriteEntry(EntityDescriptor entityDescriptor, IEnumerable`1 relatedLinks, ODataRequestMessageWrapper requestMessage)
   at Microsoft.OData.Client.BaseSaveResult.CreateRequestData(EntityDescriptor entityDescriptor, ODataRequestMessageWrapper requestMessage)
   at Microsoft.OData.Client.BaseSaveResult.CreateChangeData(Int32 index, ODataRequestMessageWrapper requestMessage)
   at Microsoft.OData.Client.SaveResult.CreateNonBatchChangeData(Int32 index, ODataRequestMessageWrapper requestMessage)
   at Microsoft.OData.Client.SaveResult.CreateNextChange()
1

1 Answers

0
votes

I use partial classes defined on the client to add the extra properties that I need there. This allows me to put any logic in them as well as have property changed notification as well. I the use the following extension methods to remove those properties. I think I actually got the original code from the article that you linked.

public static class DbContextExtensions
{
    public static void RemoveProperties(this ODataEntry entry, params string[] propertyNames)
    {
        var properties = entry.Properties as List<ODataProperty>;
        if (properties == null)
        {
            properties = new List<ODataProperty>(entry.Properties);
        }
        var propertiesToRemove = properties.Where(p => propertyNames.Any(rp => rp == p.Name));
        foreach (var propertyToRemove in propertiesToRemove.ToArray())
        {
            properties.Remove(propertyToRemove);
        }
        entry.Properties = properties;
    }

    public static DataServiceClientResponsePipelineConfiguration RemoveProperties<T>(this DataServiceClientResponsePipelineConfiguration responsePipeline, Func<string, Type> resolveType, params string[] propertiesToRemove)
    {
        return responsePipeline.OnEntryEnded((args) =>
        {
            Type resolvedType = resolveType(args.Entry.TypeName);
            if (resolvedType != null && typeof(T).IsAssignableFrom(resolvedType))
            {
                args.Entry.RemoveProperties(propertiesToRemove);
            }
        });
    }

    public static DataServiceClientRequestPipelineConfiguration RemoveProperties<T>(this DataServiceClientRequestPipelineConfiguration requestPipeline, params string[] propertiesToRemove)
    {
        return requestPipeline.OnEntryStarting((args) =>
        {
            if (typeof(T).IsAssignableFrom(args.Entity.GetType()))
            {
                args.Entry.RemoveProperties(propertiesToRemove);
            }
        });
    }
}

Notice that in the method below it is hooking OnEntryStarted. The code in the article hooks OnEntryEnded which worked for me at one point and then broke when I updated to a newer version of ODataClient. OnEntryStarted is the way to go in this method.

public static DataServiceClientRequestPipelineConfiguration RemoveProperties<T>(this DataServiceClientRequestPipelineConfiguration requestPipeline, params string[] propertiesToRemove)
    {
        return requestPipeline.OnEntryStarting((args) =>
        {
            if (typeof(T).IsAssignableFrom(args.Entity.GetType()))
            {
                args.Entry.RemoveProperties(propertiesToRemove);
            }
        });
    }

I also created a partial class for the Container as well and implement the partial method OnContextCreated. This is where you use the extension methods to remove the properties that won't get sent to the server.

  partial void OnContextCreated()
    {
        Configurations.RequestPipeline.RemoveProperties<Customer>(new string[] { "FullName", "VersionDetails" });
        Configurations.RequestPipeline.RemoveProperties<SomeOtherType>(new string[] { "IsChecked", "IsReady" });
    }

Make sure that your partial classes and the DBContextExtensions class are in the same namespace as our container and everything should just work.

Hope that helps.