locked
Can't expand an existing entity's collection using Data Services in Silverlight client RRS feed

  • Question

  • I am having a good bit of difficulty doing something that seems like it should be fairly straightforward.  I am trying to drill into an entity that I had previously loaded.  That is, I load the entity shallowly initially and then later I try to reload that entity, but this time filling in a collection that that entity contains.

     

    Here is the schema that I'm working with:

    • "Forms" entities are linked 1:many to "FormFields" (a form can have many fields).
    • A "FormFields" entity is linked 1:1 to "FormFieldTypes" (a field has a particular type; i.e. Label, TextBox, CheckBox, etc.).

    I want to:

    • Query a particular form without getting its fields.
    • Later query that same form and get its fields (and their associated field types).

    Here is my code that illustrates the different possibilities I'm trying.  Note that this is running in a Silverlight user control with 4 buttons.  The click handlers are shown here as well as the code that initializes the DataServiceContext:

     

    Code Snippet

    private BridgetechEntities m_context;

    private Forms m_selectedForm;

    public TestDataLoadAndMerge() {

    InitializeComponent();

    m_context = BridgetechEntities.CreateContext();

    }

     

    private void LoadFormWithNoFormFields_Click(object sender, RoutedEventArgs e) {

    var q = from f in m_context.Forms where f.FormId == 51 select f;

    DataServiceQuery<Forms> query = (DataServiceQuery<Forms>)q;

    query.BeginExecute((ar) =>

    {

    m_selectedForm = query.EndExecute(ar).First();

    formName.Text = m_selectedForm.Name;

    }, null);

    }

     

    private void expandFormFields_AppendOnly_Click(object sender, RoutedEventArgs e) {

    RunQuery(false, true);

    }

     

    private void expandFormFields_OverwriteChanges_FormFieldsOnly_Click(object sender, RoutedEventArgs e) {

    RunQuery(true, false);

    }

     

    private void expandFormFields_OverwriteChanges_FormFieldsAndTypes_Click(object sender, RoutedEventArgs e) {

    RunQuery(true, true);

    }

     

    private void RunQuery(bool overwriteChanges, bool expandDeep) {

    var q = from f in m_context.Forms where f.FormId == 51 select f;

    DataServiceQuery<Forms> query = (DataServiceQuery<Forms>)q;

    string expand = (expandDeep ? "FormFields/FormFieldTypes" : "FormFields");

    query = query.Expand(expand);

    if (overwriteChanges) m_context.MergeOption = MergeOption.OverwriteChanges;

    query.BeginExecute((ar) =>

    {

    IEnumerable<Forms> result = query.EndExecute(ar);

    Forms theForm = result.First();

    if (null != theForm) formFields.ItemsSource = theForm.FormFields;

    }, null);

    }

     

     

    None of the three expansion handlers meet my objective, which is to have form.FormFields filled in, and have all of the FormFieldTypes references filled in for all of those FormFields.  Note that Fiddler does show that the URLs being sent and the data being received appears correct and appropriate. Here's what I am seeing:

    1. I first query the form itself (shallowly) in LoadFormWithNoFormFields_Click.  That works fine.  At that point I have the name of the form, and form.FormFields is empty.
    2. I click the second button, executing expandFormFields_AppendOnly_Click.  In that case the MergeOption is AppendOnly.  I expand into "FormFields/FormFieldTypes".  The query succeeds, but form.FormFields is still empty.  I can look at m_context.Entities and see that all of the FormFields and FormFieldTypes have been added, but I can't see them in form.FormFields.
    3. I click the third button, executing expandFormFields_OverwriteChanges_FormFieldsOnly_Click.  In that case MergeOption is OverwriteChanges and I only expand into "FormFields".  The query succeeds, and form.FormFields now has all of the FormFields objects.  But all of the FormFieldTypes properties for the FormFields are null, so I can't tell the type of the FormFields.
    4. I click the fourth button, executing expandFormFields_OverwriteChanges_FormFieldsAndTypes_Click.  In that case MergeOption is OverwriteChanges and I expand into "FormFields/FormFieldTypes".  The query gets an exception:

    System.ArgumentException was unhandled by user code
      Message="An item with the same key has already been added."
      StackTrace:
           at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
           at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
           at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
           at System.Data.Services.Client.MaterializeAtom.ResolveOrCreateInstance(ClientType type, Uri identity, Uri editLink, String etag, Object& currentValue, EntityStates& state)
           at System.Data.Services.Client.MaterializeAtom.ReadNext(ClientType currentType, Type expectedType, AtomParseState atom, EntityStates& entityState, Object& currentValue)
           at System.Data.Services.Client.MaterializeAtom.ReadNext(ClientType currentType, Type expectedType, AtomParseState atom, EntityStates& entityState, Object& currentValue)
           at System.Data.Services.Client.MaterializeAtom.ReadNext(ClientType currentType, Type expectedType, AtomParseState atom, EntityStates& entityState, Object& currentValue)
           at System.Data.Services.Client.MaterializeAtom.MoveNext()
           at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
           at Bridgetech.Silverlight.FormEditor.TestDataLoadAndMerge.<>c__DisplayClass4.<RunQuery>b__3(IAsyncResult ar)
           at System.Data.Services.Client.BaseAsyncResult.HandleCompleted()
           at System.Data.Services.Client.QueryAsyncResult.AsyncEndRead(IAsyncResult asyncResult)
           at System.IO.Stream.BeginRead(Byte[] buffer, Int32 offset, Int32 count, AsyncCallback callback, Object state)
           at System.Data.Services.Client.QueryAsyncResult.AsyncEndGetResponse(IAsyncResult asyncResult)
      InnerException:

     

    That exception only occurs if I have at least two FormFields with the same FormFieldTypes value (for example, there are two fields that are Label fields or two fields that are TextBox fields). 

     

    I have also experimented with forcing all FormFieldTypes to load separately before running any of the other code, but that didn't prevent the exception in case 4 from occuring.  Also note that I'm not running all of these experiments in the same debugging session.  I'm actually doing it 1+2, then 1+3, then 1+4 in separate debugging sessions.

     

    I think I'm misunderstanding something basic about the way data services is supposed to work.  Can anyone provide any illumination?

     

    Thanks,

     

    David Cater

     

     

    Thursday, November 13, 2008 3:37 PM

Answers

  • Hello,

     

    Looking back at your scenario, the number 2 behavior is by design.SInce first query retrieves all the Forms, the second query won't update the existing entities. You should use PreserveChanges to get the behavior you want. Here is a short description of all options for your reference:

     

    NoTracking

                            Create new entities + expansion, never interacting with the context

     

    AppendOnly

                            Resolve entities+expansion where new entities are added to context with identity & etag

                            Do not update existing entity values or etag.

                            Do not add to entity collections or update entity references on existing entities.

     

    PreserveChanges

                            Resolve entities+expansion where new entities are added to context with identity & etag

                            Do not update existing entity values or etag.

                            Add to entity collections and update entity references on existing entities.

     

    OverwriteChanges

                            Resolve entities+expansion where new entities are added to context with identity & etag

                            Update existing entity values and update etag in context.

                            Add to entity collections and update entity references on existing entities.

     

    Thanks,

    Monica

    Wednesday, December 3, 2008 12:37 AM
    Moderator

All replies

  • Sorry, no illumination; I am anxiously awaiting an answer to this as well.  It does seem like a common need.  To follow your example, I was hoping for something as simple as:

     

    Code Snippet

    m_context.BeginLoadProperty(m_selectedForm, "FormFields/FormFieldTypes", LoadPropertyCallback, null); // raises an exception

    // OR

    m_context.BeginLoadProperty(m_selectedForm, "FormFields", LoadPropertyCallback, null).Expand("FormFieldTypes"); // doesn't compile

     

    Trying to use Begin/EndLoadProperty to iteratively populate an object graph in Silverlight is a royal pain due to the async callbacks (i.e., tracking when they are all finished), not to mention the chattiness it causes over the wire.  Hopefully someone will respond with a solution that works today, not just in some future version.

     

    Thanks,

    Brad Gentry

     

    Tuesday, November 18, 2008 4:22 PM
  • Hello,

     

    The issue with no. 4 is a known bug that will be fixed in the new release.

    No.3 behavior is expected.Since you only expand FormFields, you won't see FormFieldTypes.

    I will take a look at no.2 issue.

     

    Thanks,

    Monica

     

    Tuesday, November 25, 2008 12:36 AM
    Moderator
  • Hello,

     

    Looking back at your scenario, the number 2 behavior is by design.SInce first query retrieves all the Forms, the second query won't update the existing entities. You should use PreserveChanges to get the behavior you want. Here is a short description of all options for your reference:

     

    NoTracking

                            Create new entities + expansion, never interacting with the context

     

    AppendOnly

                            Resolve entities+expansion where new entities are added to context with identity & etag

                            Do not update existing entity values or etag.

                            Do not add to entity collections or update entity references on existing entities.

     

    PreserveChanges

                            Resolve entities+expansion where new entities are added to context with identity & etag

                            Do not update existing entity values or etag.

                            Add to entity collections and update entity references on existing entities.

     

    OverwriteChanges

                            Resolve entities+expansion where new entities are added to context with identity & etag

                            Update existing entity values and update etag in context.

                            Add to entity collections and update entity references on existing entities.

     

    Thanks,

    Monica

    Wednesday, December 3, 2008 12:37 AM
    Moderator