Thursday, October 14, 2010

DomainDataSourceView.PerformCudOperation error on updating a FormView.

If you have a REQUIRED field in your ADO.NET Entity Model, you MUST have it on your Form when you use the DomainDataSource, FormView, and Domain Service Class and it must be EDIT mode if you are using the DynamicDataControl. If you leave it off the form or set the mode to ReadOnly you will get a totally useless error like this.

[EntityOperationException: An error has occurred.] Microsoft.Web.UI.WebControls.DomainDataSourceView.PerformCudOperation(ChangeSetEntry operation, Action`1 operationPerformingEvent, Action`1 operationPerformedEvent) +331 Microsoft.Web.UI.WebControls.DomainDataSourceView.UpdateObject(Object oldEntity, Object newEntity) +195 System.Web.UI.WebControls.QueryableDataSourceView.ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues) +115 Microsoft.Web.UI.WebControls.DomainDataSourceView.ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues) +40 System.Web.UI.DataSourceView.Update(IDictionary keys, IDictionary values, IDictionary oldValues, DataSourceViewOperationCallback callback) +95 System.Web.UI.WebControls.FormView.HandleUpdate(String commandArg, Boolean causesValidation) +1154 System.Web.UI.WebControls.FormView.HandleEvent(EventArgs e, Boolean causesValidation, String validationGroup) +408 System.Web.UI.WebControls.FormView.OnBubbleEvent(Object source, EventArgs e) +95 System.Web.UI.Control.RaiseBubbleEvent(Object source, EventArgs args) +37 System.Web.UI.WebControls.FormViewRow.OnBubbleEvent(Object source, EventArgs e) +112 System.Web.UI.Control.RaiseBubbleEvent(Object source, EventArgs args) +37 System.Web.UI.WebControls.Button.OnCommand(CommandEventArgs e) +125 System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +167 System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) +10 System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) +13 System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) +36 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +5563

If you are lucky the field you set to ReadOnly will be a DataTime field or something and it will tell you that the date is not valid. If it is just a string field then you may get the useless error as shown above.

This probably won’t show up unless you try to create a Custom Page for one of your entities. The nice thing is that when you create a Custom Page you get full control over the layout of your entity. The problem is that you can create all kinds of frustrating issues like this one.

BTW, this is only an issue for datatypes that use the Text.ascx and DateTime.ascx controls. The reason is because these controls are implemented using a literal control instead of a Label, etc which has viewstate. Any datatype that has a _Edit.ascx counterpart won’t be affected and also any that have something that has viewstate as the underlying readonly implementation.

Work Arounds

Work Around – If you don’t want the user to see a field

You can always add the control to the FormView even if you don’t want the user to see it. The set the visible to false. This will allow it to work properly. All form validators will not be created which is good in for the most part.

If you are just doing an edit template then the above will work fine. However, if you are doing an insert template, then you will get errors about not being able to set the value because the value is illegal or invalid. An example of this is a DateTime can’t be set to null or an int can’t be set to null. If the field is required then non-nullable types will be used. This means you need to specify a default value.

The trick to specifying the default value is that you can’t do it in your entity’s constructor because since the FormView has the required field on the EditTemplate a value (null) will be set on your Entity after it is created (after the valid value you may have tried to default in the entities constructor. That is what is causing the error.

One solution is to use the ItemInserting event of the FormView. Here you will find a parameter that is of type FormViewInsertEventArgs. It has a collection of all the values that are being submitted.

For each of the items you added to the FormView and hide, you will need to set the default value they should have using the Values collection. The key is the name of Entity Property you bound to in the FormView. The exception to that is if you re binding to a Navigation property such as with a DropDownList. In this case the foreign key is actually what you will find in the Values collection, not the Navigation property name. The value is foreign key, not the related entity.

If you don’t want to think that hard, you can always just set the values of the DynamicControls you want to default using what ever method gets called when you click your insert button. In my case, I am going with the same LinkButtons that are used in the default insert template for Dynamic Data. This means that FormView1_ItemCommand is a good place to set the values of your DynamicControls. This is a bit easier because you don’t have to worry about DropDownLists in the same way. However, since you are dealing with different controls that is more thought also, so it is really up to you on which approach you want to take. The bottom line is that the value has to be there when the FormView passes it on to the DomainDataSource.

FYI, I have made working with DynamicControls must easier. It makes setting and getting values on forms that have DynamicControls on them trivial. Here is an example of what I am talking about.

FormView1.SetValue("DateCreatedUtc", DateTime.UtcNow);

Here is the link to the source code. I implemented it as an extension of the Control class.

Work Around – If you want it on the Edit mode of the FormView, but show as readonly.

Option 1

If you need the field on the FormView, but want it to be readonly when the FormView is in Edit mode, there is another solution.

Copy DyanmicData\FieldTemplates\Text_Edit.ascx (and the .ascx.cs file) to DyanmicData\FieldTemplates\TextAppearReadOnly_Edit.ascx (and .ascx.cs file). What comes before the underscore is not important, but what the name must end is _Edit.ascx and _Edit.ascx.cs.

Remove the validator controls as they are not needed. Add a literal control and bind it to the same value as the TextBox. Once you have done that, your .ascx file will look something like this:

<asp:Literal runat="server" ID="Literal1" Text="<%# FieldValueEditString %>" />
<asp:TextBox ID="TextBox1" runat="server" Text='<%# FieldValueEditString %>' ReadOnly="true" Visible="false" />

Now open the .ascx.cs file. Remove all items that aren’t needed or don’t compile. You class will look something like this:

public partial class TextAppearReadOnly_EditField : System.Web.DynamicData.FieldTemplateUserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        TextBox1.ToolTip = Column.Description;
    }

    protected override void ExtractValues(IOrderedDictionary dictionary)
    {
        dictionary[Column.Name] = ConvertEditedValue(TextBox1.Text);
    }

    public override Control DataControl
    {
        get
        {
            return TextBox1;
        }
    }

}

Now, where ever you want a read only field on do something like this:

<asp:DynamicControl ID="DateCreatedDynamicControl" runat="server" DataField="DateCreated" Mode="Edit" UIHint="TextAppearReadOnly" />

The part to pay attention to is that the Mode is set to Edit and the UIHint specifies to use our control we created.

Option 2

I actually prefer this method as the semantics of option 1 don’t seem quite right. In this option, we will actually modify the default controls to implement what I call a bug fix. :)

  1. Open DynamicData\FieldTemplates\DateTime.ascx
  2. Change the line:

    <asp:Literal runat="server" ID="Literal1" Text="<%# FieldValueString %>" />

    to

    <asp:Label runat="server" ID="Label1" Text="<%# FieldValueString %>" />

    Notice the only change I made is changing the type from Literal to Label and updated the ID to reflect the change also.
  3. Open DynamicData\FieldTemplates\DateTime.ascx.cs
  4. Add the following method:
    protected override void ExtractValues(IOrderedDictionary dictionary) {
                dictionary[Column.Name] = ConvertEditedValue(Label1.Text);
    }

  5. Add the DataControl property (or modify it if it already exists) as shown below

    public override Control DataControl { get { return Label1; } }

Do the Same for Text.ascx (and Text.ascx.cs).

Using this fix, you don’t have to change any of the properties on the DyanmicControl and the semantics are right. Here is an example of what you may have.

<asp:DynamicControl ID="DateCreatedDynamicControl" runat="server" DataField="DateCreated" Mode="ReadOnly" />

Notice that the Mode is ReadOnly which makes sense to me. This means that you can use the DynamicControl exactly the same regardless of the datatype and regardless of whether it is a required field or not.

4 comments:

massimo said...

Great! You solved my problem

Brent V said...

Just to let you all know I have updated the Option 2 solution slightly so that it uses a Label control instead of a Literal control. I have removed the HiddenField since the Label control has viewstate and can keep its own state.

Anonymous said...

FYI, You can also get this same error if you do any un-necessary "re-binding" of a parent databound control your FormView may be nested in. For instance, if you use FormView.UpdateItem(...) and then follow up after the update with a ListView.DataBind(...) -- you'll effectively disrupt the validation and get the error.

Blogger said...

Are you trying to make money from your websites by running popup advertisments?
In case you do, did you ever use PopAds?