Databinding code appears to be throwing an exception while not using databinding.

Databinding code appears to be throwing an exception while not using databinding.

Old forum URL: forums.lhotka.net/forums/t/5686.aspx


dculler posted on Tuesday, October 28, 2008

I'm in the process of learning CSLA.net and changing a small WinForms app that logs applicants to use CSLA.net.
I'm not using data binding at all, and am running into a problem where a "Nullable object must have a value."
exception is being thrown in what appears to be data binding code in the CSLA.net framework.

I've got an Applicant class which derives from BusinessBase
and an ApplicantList class which derives from BusinessListBase which contains a collection of Applicant objects.

I've got a form (frmApplicantLog) with a DataGridView for displaying applicants, a DataGridView for displaying notes
for the selected applicant, and a few buttons.  I'm manually loading the DataGridView controls using code like this:
    dgvApplicants.Rows.Add(applicantList.Count);
    for (int index = 0; index < applicantList.Count; index++)
    {
        using (DataGridViewRow row = dgvApplicants.Rows[index])
        {
            row.Cells[colApplicantsName.Index].Value = applicant.Name;
        }
    }
   
I have an Add New Applicant button on this form that creates a new instance of my frmNewApplicant form passing an ApplicantList instance, and displays that new instance of the frmNewApplicant form.  When frmNewApplicant's save button is clicked, I take user entered information for the new applicant,
add it to the ApplicantList instance, and save that ApplicantList.

When I try to add my new Applicant instance to the ApplicantList, stepping through the code takes me to:
    BusinessListBase.InsertItem which on line 477 calls    base.InsertItem(index, item); which takes me to the add portion
    of BindableBase.PropertyChangedEventHandler PropertyChanged event, then it's on to LinqBindingList.SourceChanged,
    LinqBindingList.ItemShouldBeInList, and finally we get to BusinessListBase.SearchByExpression where the
    "Nullable object must have a value." exception is thrown on line 757 (foreach (C item in result)).
   
At first glance it seemed rather odd to me that the code even went into BindableBase & LinqBindingList. Looking into the BusinessListBase.SearchByExpression method further, there is an expr parameter
which contains:
    expr = {x => (x.ApplicantID.Value = ToInt64(myNameSpace.frmApplicantLog, Text: Applicant Log
        .dgvApplicants.CurrentRow.Cells
        .get_Item(myNameSpace.frmApplicantLog, Text: Applicant Log.colApplicantsApplicationID.Index)
        .Value))}
       
It seems to me that the framework is trying to do some data binding.  I can't think of any other reason it would be accessing a value from a DataGridView on another form.  If I comment out the code that loads existing applicants into the DataGridView,
the new Applicant is added to the ApplicantList, the BindableBase & LinqBindingList methods listed above are not called, and everything works properly.
My dgvApplicants grid's DataSource is set to (none) and I don't have any code to manually set data binding. 

I don't remember, but I may have tried to play around with data binding on this project when it was originally written some time ago (pre-CSLA).  Just in case, I did try creating a copy of my project, deleting frmApplicantLog entirely, and re-creating it, but got the same result.

Any ideas?

RockfordLhotka replied on Tuesday, October 28, 2008

I don't know that data binding has anything to do with it.

The use of BindableBase is merely to set up the PropertyChanged event hook. That happens with or without data binding - it is just a basic behavior.

The code that appears to be failing is part of LINQ to CSLA.

What version of CSLA are you using? There've been a couple L2C null ref bugs fixed in the last couple versions - this could already be solved.

dculler replied on Wednesday, October 29, 2008

I was using 3.5.2, but just tried with 3.6.0 Beta 2 and got the same result.

Looking at the value of expr makes me think the framework is trying to find a row in my DataGridView with the same ApplicantID that my new Applicant has.  This is an Int64? that is set to null initially.  I assign it when it's saved to the database and the database assigns a unique ID for that Applicant.

The expr value contains x.ApplicantID.Value which is probably where the exception is being thrown.

RockfordLhotka replied on Wednesday, October 29, 2008

Thanks – I’ll add a bug in the bug list. Can you provide a more detailed repro so we can easily create a unit test to replicate the issue?

 

Rocky

 

 

From: dculler [mailto:cslanet@lhotka.net]
Sent: Wednesday, October 29, 2008 10:57 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] Databinding code appears to be throwing an exception while not using databinding.

 

I was using 3.5.2, but just tried with 3.6.0 Beta 2 and got the same result.


wleary replied on Thursday, October 30, 2008

Hi Rocky,

I believe we came across a related problem today, in the SearchByExpression method. We are trying out the latest release of the 3.6 codebase, upgrading from 3.0.

Here is our code:

[TestMethod]
public void V2_TestWeirdPblockError()
{
  Market mkt = THelper.CreateMarket();
  Advertiser adv = THelper.CreateAdvertiser(mkt.Id, "foo");
  Scene scene1 = THelper.CreateAdvertiserScene(adv.Id);
  Playblock pb = THelper.CreateAdvertiserPlayblock(adv.Id, scene1.Id);

  TestPbItem item = new TestPbItem { Id = pb.Items[0].Id };

  List<TestPbItem> items = new List<TestPbItem>();
  items.Add(new TestPbItem { Id = pb.Items[0].Id });

  for (int i = 0; i < items.Count; i++)
  {
    // This line will cause a failure on the pb.Items.RaiseReset()
    PlayblockItem pbItem = pb.Items.Where(b => b.Id == items[ i ].Id).FirstOrDefault();
  }

  // This line fails if the pb.Items collection was queried using LINQ in the for loop.
  // The 'i' variable is now outside the bounds of 'items' size. The code being
  // called inside this method is OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)).
  pb.Items.RaiseReset();   

  Market.DeleteMarket(mkt.Id);
}

private class TestPbItem
{
  public int Id { get; set; }
}

Hopefully that made sense. The key part is the fact that the LINQ expression now contains code that will result in a runtime exception when it is evaluated, that is getting called when we raise the ListChanged event. We were able step through the code, and see that the last LINQ expression was still getting evaluated for some reason within that method. I'm not sure if this is a bug, or if I just need to learn more about the LINQ changes to CSLA.

Thanks,

William

dculler replied on Monday, November 03, 2008

I've attached a small project that I've been able to re-produce this issue with.  It appears the variable named _expression in LinqBindingList.cs is not being reset somewhere causing the SearchByExpression method to use it's value from a previous query.

In the attached example the code searches for an Applicant in the ApplicantList that matches the row in the DataGridView that the user just selected.  It looks like when a new Applicant is added to the list, SearchByExpression is re-using that same _expression.

Please let me know if this needs more explanation.
Thanks.

AaronErickson replied on Monday, November 03, 2008

Ah, after some digging, the problem is in your selectionchanged event. The lambda was assuming the value of the key would be present, which it is not in this case.

Try this instead:

Applicant applicant = applicantList.Where(x => x.ApplicantID.HasValue ? x.ApplicantID.Value == Convert.ToInt64(dataGridView1.CurrentRow.Cells[colApplicantId.Index].Value) : false).Single();

Which makes sense since you dont really know whether the id has a value or not due to it's being nullable.

dculler replied on Monday, November 03, 2008

Thanks for the reply.  The lambda was correct in assuming their would be a value.  When the application loads, it loads Applicants into an ApplicantList from the database which will always have an Id, as the database automatically assigns it.

I would not add a new Applicant to the grid until it has been successfully added to the ApplicantList, and that ApplicantList has been saved (via an event on a seperate form in my real code), so every row in the grid would have a valid Id as well.  I did not show all this in the code I attached as I wanted to make a small example that could be used to see the exception that was being thrown.

The real point of the line you identified above is to find the Applicant in the list that matches the selected DataGridViewRow (which would always have an Id), then to display any notes about that Applicant (which are child BusinessListBase belonging to the Applicant class in my real app) in the lower grid.  I pulled that out to simplify the example.

So I don't believe the CSLA framework should be using that same query expression upon adding a new Applicant to the ApplicantList, which does not have an Id because it has not yet been saved, and cannot be saved until it's added to the List.  It appears to me that the _expression variable was not cleared after my grid's SelectionChanged event fired, which was prior to adding a new Applicant to the ApplicantList.

Hope that explains it a little better.

AaronErickson replied on Tuesday, November 04, 2008

If you want to do it that way, try using SearchByExpression rather than Where. What is occuring is that by doing a Linq query that returns an identity projection, you implicitly create a LinqBindingList - which by its very nature, has to retain it's _expression so it can properly synchronize.

SearchByExpression simply searches, without implying synchonization to the collection.

If you need synchonization back to the collection, you HAVE to assume that any intermediate state - even during construction - is valid as far as any derived lists are concerned, and structure your queries accordingly.

dculler replied on Tuesday, November 04, 2008

To illustrate this further, I've attached a modified version of the app I attached earlier.  Running the application will succeed when using a foreach loop to find the appropriate Applicant in the ApplicantList.  You'll notice I am still searching for it by Id.  I included a Console.WriteLine(applicant.Name) below that to show that it always finds an applicant, meaning the linq query should also always find an Applicant.

So if it works with a foreach loop, a for loop, and it worked using linq to objects before I started using CSLA, it would seem the bug is in the custom linq provider the CSLA framework is using.

Thanks.

AaronErickson replied on Tuesday, November 04, 2008

I think we are going to have to agree to disagree then. SearchByExpression provides a side-effect free implementation of searching by a LINQ expression - one of the reasons we opened it up in the BLB implementation is exactly that purpose.

Whenever you perform a standard Linq query against a BLB, it is going to create a LinqBindingList as a result. In order for sync to work (something that does not work with standard L2O) - the *generated* result has to retain the _expression it was generated with, and upon anything being added, test whether that add operation puts the given item in the list.

The CSLA query provider, by design, has this side effect for sync. The moment you add an item to either the base list, or any list that is generated from it, a change notification goes to all (base and all generated) where each one tests whether the new item belongs. If it does, the filter includes it, if not, it ignores it.

Having a nullable, even if the intent is to only have that nullable be in that state for a short time, pre some initialization from a database, puts you at risk for having that test fail.

I recognize this side effect of the Linq query goes against the idea of pure functional programming. This was a tradeoff we made so that we could make it easier for people to bind to Linq query results. The current workarounds are to use SearchByExpression, or provide a predicate that can't fail against any possible intermediate state of the object.

Now, the thing I do think might be a problem is that the LinqBindingList that exists - albiet temporarily during the call to Single - seems to have an over-extended lifetime. The LinqBindingList you create for each child item when you get back the Single result should go away - but I suspect that the change notification infrastructure is somehow keeping it around (you can see it in the stack trace). That IS something I am looking at. If the change notification for LinqBindingLists that are no longer in anyone's scope went away, then there would be no side effect for you to worry about. Will let you know how that progresses.

dculler replied on Tuesday, November 04, 2008

I had written my last post before I saw your post recommending SearchByExpression.  It looks like that will do the job. 

Thank you for your help, and please do keep me posted if anything turns up in what you're looking into .

Copyright (c) Marimer LLC