Sunday, September 30, 2012

ObjectDataSource has no values to insert. Check that the 'values' dictionary contains values

The Gripe
"ObjectDataSource 'odsNameHere' has no values to insert. Check that the 'values' dictionary contains values"
This runtime error is quite confusing and obnoxious to say the least. You can't debug it the normal way because your code behind will not hit any breakpoints at all (or at least any relevant ones). At this point anyone would be quite upset, because the next question to yourself is, "@#$! Great! What the hell do I do now?"

The Gotcha
Unfortunately this error could have happened for any number of reasons, but the culprit that I find normally is that my aspx file where the Object Data Source's (ODS) target control (GridView, DetailView etc...) is located has an inappropriately used Bind(...) or Eval(...) method, or a Bound Field that must be turned into a Template Field for any variety of reasons.

The Short Answer
I can't say this will always be the solution, but the worst case scenario fix is to fully implement a Template Field for all fields and use Bind(...) for all of the sub templates.

Example:
<asp:TemplateField HeaderText="Name">
 <ItemTemplate>
  <asp:Label ID="lblName" runat="server" Text='<%# Bind("Name") %>'></asp:Label>
 </ItemTemplate>
 <EditItemTemplate>
  <asp:TextBox ID="txtName" runat="server" Text='<%# Bind("Name") %>'></asp:TextBox>
 </EditItemTemplate>
 <InsertItemTemplate>
  <asp:TextBox ID="txtName" runat="server" Text='<%# Bind("Name") %>'></asp:TextBox>
 </InsertItemTemplate>
</asp:TemplateField>

I would like to guarantee that this will always fix this problem, but I can't. So far in my experience though, this has worked. My ODS is linked directly to a business object and I let it handle all CRUD operations.

The Long Answer
This is a big can of worms to get into, essentially the problem is the usage of Bind(...), Eval(...) and missing template tags (Item, Edit, Insert, Footer, Header).

Bound Fields
Before jumping into all of this, I would just like to say that Bound Fields are great - but they are very limited. I like using Bound Fields initially when implementing something and later I generally turn them into Template Fields (using the WYSIWYG editor). Bound Fields are nice because they encapsulate all of the basic template views (Item, Edit, Insert). This is convenient until you decide you need to add formatting to your fields. So really, Bound Fields are only really good for Read Only data or very plain data - if everything is a text field for example.

Template Fields
Template Fields are great because they give you a lot of flexibility to design exactly how you want your fields to look in all scenarios. They do have very silly limitations, such as adding formatting to a field. Let's use an example to explain this - think of a Template Field that is supposed to represent a currency and you format it using "c0" which will make your field look like this: "$1,000". Now as long as you don't intend on editing or inserting this data, you are fine - there is nothing more you have to do - in fact you might as well use a Bound Field. The problem occurs when you need to edit and insert data. Now you cannot have that formatting for your Edit or Insert fields (or Modes).

//This will only work in Read Only situations - if you intend on this field existing
//along with Edit and Insert modes while using an ODS - expect problems
<asp:TemplateField HeaderText="Price" HeaderStyle-HorizontalAlign="Right">
 <ItemTemplate>
  <asp:Label ID="lblPrice" runat="server" Text='<%# Bind("Price", "{0:c0}") %>' style="float:right;"></asp:Label>
 </ItemTemplate>
        //Notice the lack of Edit and Insert templates
</asp:TemplateField>

Let's assume that same field is read only - you won't allow it to be edited or inserted - this is a derived field of some kind. Well ASP.Net doesn't care, if you don't provide an Edit or Insert Template, but you intend on using Edit and Insert modes for your control - then you will get conversion errors (which are annoying). Going back to the same example, during an Edit "$1,000" cannot be converted to a decimal value of "1000M" (or "1000D" for VB.Net) you will get an error because of the "$" sign being part of the string. The same problem will happen in the Insert Scenario, blank cannot be converted to a decimal using the stock methods. Convert.ToDecimal("") will throw an exception.
//If this field is intended to be read only, but the control is going to use 
//Edit and Insert modes - then in some cases you have to populate the field 
//for each of the modes or risk running into problems.
<asp:TemplateField HeaderText="Price" HeaderStyle-HorizontalAlign="Right">
        <ItemTemplate>
  <asp:Label ID="lblPrice" runat="server" Text='<%# Bind("Price", "{0:c0}") %>' style="float:right;"></asp:Label>
 </ItemTemplate>
        //During Edit Populate it - but hide it
 <EditItemTemplate>
  <asp:TextBox ID="txtPrice" runat="server" Text='<%# Bind("Price") %>' Visible="false"></asp:TextBox>
 </EditItemTemplate>
        //During Insert Populate it - but hide it
 <InsertItemTemplate>
  <asp:TextBox ID="txtPrice" runat="server" Text='<%# Bind("Price") %>' Visible="false"></asp:TextBox>
 </InsertItemTemplate>
</asp:TemplateField>

The most ridiculous part of all of this is the above is not always true. It is the safest thing you can do though to avoid the error of topic. To reiterate, you should be able to avoid the error of topic if you fully implement your template field. This is (mostly) true for all cases. If your field is supposed to be hidden, read only or fully malleable. I have had this problem show up, what I would consider, randomly. It is not consistent.

I have some controls, in the same project, that share the same types of fields, but I have to template those fields differently depending on the control for some reason. I think most of these problems arise because an ODS is being used. If this was separate from an ODS, then most of these problems could probably be avoided.

No comments:

Post a Comment