Ask a questionAsk a question
 

Answerassociations (links) incomplete in DataServiceContext?

  • Friday, June 06, 2008 2:40 PMzac morris Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     

    The child -> parent end of an association is not completed after retrieving a list of parent objects using ObjectQuery.

    Given this scenario:

    myDataSvcContext is a strongly-typed service proxy generated by DataSvcUtil.exe from a service with Parent entities and Child entities where Parents and Childs are associated via Parent.Child and Child.Parent properties


    after this query is executed:

    var parents = myDataSvcContext.Parents.Expand("Childs")

    looking at myDataSvcContext.Entities shows we have (for example) 1 Parent entity and 1 Child entity.  Parent.Child points to the Child entity.  Child.Parent == null.

    Is this by design or a bug?

    The ultimate problem I'm trying to solve is to represent the current state the objects tracked by the context in a UI: when a Parent is select in the UI I want to show a list of its children, including changes not yet saved to the server.

    Currently if I add a Child to a Parent, the new child is not part of the results returned from Parent.Child (before being committed through the server).  To show all children of the parent I have to do something like:

                IEnumerable<EntityDescriptor> children= this.myDataSvcContext.Entities.Where(
                    d => d.Entity as Child != null
                         && d.State != EntityStates.Deleted
                         && ((Child )d.Entity).Parent== selectedParent);

                _childList.ItemsSource = children.Select(d => d.Entity);


    I'd would be a lot nicer if I could just do something like:

    <ListView x:Name="_childList" ItemsSource="{Binding Source=_parentList, Path=SelectedItem.Childs}">

    Any thoughts? 

    -Zac Morris

    (Data Services and DataServiceContext are looking great.  A little better change-tracking in general would be even better.)

    BTW, is there a reason they're called "Links" in the DataServiceContext instead of "Associations" like everywhere else in the EF and EDM?

Answers

  • Saturday, June 07, 2008 1:19 AMPablo Castro - MSFTModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    We wanted to create the simplest possible client infrastructure, so we didn't get very fancy with graph maintenance. This also minimized the need for additional metadata. In this scenario, from the payload (given the "expand" directive) we can tell that we have to add Child to the parent's "Children" collection, but it's not obvious just from the payload whether a backpointer from child to parent was supposed to point to the parent on this particular response or some other instance of the same type. That's a long way of saying that we don't require links to be bi-directional so we can't assume they are.

     

    I agree that this results in some extra pain when walking the graph in the opposite direction, but keeping the graph management part of the client to a minimum (almost none) was very important for us...

     

    In fact, the reason why we didn't use the EDM term "association" is because client-side links (and server links over non-Entity Framework models) have loser semantics; for example, they are more like references in that they can be 1-way (associations always have a way back), and constraints may or may not be enforced.

     

    Pablo Castro
    Software Architect
    Microsoft Corporation
    http://blogs.msdn.com/pablo

     

All Replies

  • Saturday, June 07, 2008 1:19 AMPablo Castro - MSFTModeratorUsers MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer

    We wanted to create the simplest possible client infrastructure, so we didn't get very fancy with graph maintenance. This also minimized the need for additional metadata. In this scenario, from the payload (given the "expand" directive) we can tell that we have to add Child to the parent's "Children" collection, but it's not obvious just from the payload whether a backpointer from child to parent was supposed to point to the parent on this particular response or some other instance of the same type. That's a long way of saying that we don't require links to be bi-directional so we can't assume they are.

     

    I agree that this results in some extra pain when walking the graph in the opposite direction, but keeping the graph management part of the client to a minimum (almost none) was very important for us...

     

    In fact, the reason why we didn't use the EDM term "association" is because client-side links (and server links over non-Entity Framework models) have loser semantics; for example, they are more like references in that they can be 1-way (associations always have a way back), and constraints may or may not be enforced.

     

    Pablo Castro
    Software Architect
    Microsoft Corporation
    http://blogs.msdn.com/pablo

     

  • Wednesday, October 21, 2009 3:10 PMAquilax Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Has Code
    This topic is quite old but I couldn't find any solution to this problem, so I post here an hack to solve it:
    DataServiceContext myService=new MyService(...uri...);
    myService.ReadingEntity += (s, e) =>
    {
        Type type1 = e.Entity.GetType();
        foreach (PropertyInfo pi1 in type1.GetProperties())
        {
            Type type2 = pi1.PropertyType;
            if (!type2.IsValueType && !type2.IsArray && type2.Name != "String")
            {
                object obj1 = pi1.GetValue(e.Entity, null);
                if (obj1 != null)
                {
                    Type type3 = type2.IsGenericType ? type2.GetGenericArguments()[0] : type2;
                    foreach (PropertyInfo pi2 in type3.GetProperties())
                    {
                        Type type4 = pi2.PropertyType;
                        if (!type2.IsValueType && !type2.IsArray && type2.Name != "String")
                        {
                            Type type5 = type4.IsGenericType ? type4.GetGenericArguments()[0] : type4;
                            if (type5 == type1)
                            {
                                Action<object> action1 = obj2 =>
                                {
                                    object obj3 = pi2.GetValue(obj2, null);
                                    if (!type4.IsGenericType && obj3 == null) pi2.SetValue(obj2, e.Entity, null);
                                    else if (type4.IsGenericType)
                                    {
                                        Type type7 = type4.GetInterface("ICollection`1", false);
                                        if (type7 != null)
                                        {
                                            if (obj3 == null)
                                            {
                                                obj3 = Activator.CreateInstance(type4);
                                                pi2.SetValue(obj2, obj3, null);
                                            }
                                            type7.GetMethod("Add").Invoke(obj3, new object[] { e.Entity });
                                        }
                                    }
                                };
    
                                Type type6 = type2.GetInterface("IEnumerable", false);
                                if (type6 == null) action1(obj1);
                                else
                                {
                                    IEnumerator ienum = (IEnumerator)type6.GetMethod("GetEnumerator").Invoke(obj1, null);
                                    while (ienum.MoveNext()) action1(ienum.Current);
                                }
                            }
                        }
                    }
                }
            }
        }
    };