7

I have this error when I try to update a FormView

Could not find a property named 'MainContact.FirstName' on the type specified by the DataObjectTypeName property in ObjectDataSource 'odsForm'.

I think it is because I use in the EditTemplate a Textbox like this

<asp:TextBox Text='<%# Bind("MainContact.FirstName") %>' ID="txtFirstName" runat="server" />

It shows the right text in the Textbox, but apparently it doesn't work when it updates.

This is the datasource of the FormView

<asp:ObjectDataSource ID="odsForm" runat="server" DataObjectTypeName="Helpers.BusinessObjects.EntryItem"
    SelectMethod="GetEntryByEmail" TypeName="Helpers.DataAccessers.EntryHelper"
    UpdateMethod="UpdateEntry">
    <SelectParameters>
        <asp:SessionParameter SessionField="email" Name="email" Type="String" />
    </SelectParameters>
</asp:ObjectDataSource>

This is the EntryItem Class

 public class EntryItem
    {
        public int Id { get; set; }
        public string Email { get; set; }
        public string Password { get; set; }
        public Person MainContact { get; set; } 
        ...
    }

And the Person Class

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    ...
}

The debugger gets in the FormView ItemUpdating event handler, but never in Helpers.DataAccessers.EntryHelper.UpdateEntry.

How can I solve this?

Ryan Kohn
  • 13,079
  • 14
  • 56
  • 81
Vinzcent
  • 1,438
  • 6
  • 33
  • 59

3 Answers3

4

You could write your own control able to perform binding as you want, to be used this way (I made one of this):

    <ItemTemplate>
      <%# Eval("MainContact.FirstName")%>
    </ItemTemplate>
    <EditItemTemplate>
      <xx:BinderHelper runat="server" DataSource='<%# Bind("MainContact") %>'>
        <ItemTemplate>
          <asp:TextBox Text='<%# Bind("FirstName") %>' ID="txtFirstName" 
             runat="server" />
        </ItemTemplate>
      </xx:BinderHelper>
    </EditItemTemplate>

Anyway, I suggest you not to use domain objects directly in pages, and overall not to write them with an ObjectDataSource. The problem is that when you will change your domain for example to add a field:

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    // just added
    public DateTime? BirthDate { get; set; }
}

Then you will need to change all the GridViews, FormViews, etc. to store the BirthDate, otherwise the framework will call your ObjectDataSource Update method putting null in the BirthDate. For example:

<asp:GridView runat="server" DataSourceID="odsForm" AutoGenerateColumns="False">
    <Columns>
      <asp:CommandField runat="server" ShowEditButton="True" />
      <asp:BoundField DataField="FirstName" />
      <asp:BoundField DataField="LastName" />
 </Columns>
  </asp:GridView>

It will read your persons from the database. Each person will have a birth date set. When you save, the person will updated with BirthDate to null, because the GridView does not store the new field.

I think the best solution is to write DTOs for databinding (and leave them in the Presentation Layer) and DataObjects. In your case:

public class EntryItemView
{
    public int Id { get; set; }
    public string Email { get; set; }
    public string Password { get; set; }
    public string MainContactFirstName { get; set; } 
}

[DataObject]
public class EntryItemViewDataObject {
   [DataObjectMethod(DataObjectMethodType.Select)]
   public EntryItemView GetItem(...) {
       // TODO: read from the database, convert to DTO
   }

   [DataObjectMethod(DataObjectMethodType.Update)]
   public void Update( EntryItemView entry) {

      EntryItem domainObject = getById(entry.Id);
      // TODO: use EmitMapper or AutoMapper
      domainObject.MainContact.FirstName = entry.MainContactFirstName;

      // TODO: save
   }
}

In this way any addition to your domain will be safe for your views, and DataObjects will read/write only the fields they need.

onof
  • 17,167
  • 7
  • 49
  • 85
  • +1 : I always get burned wasting my time with the `ObjectDataSource` I just hope next time I remember not to bother with them. – capdragon Jun 03 '13 at 19:04
1

Two possible approaches.

First, you could drop DataObjectTypeName="Helpers.BusinessObjects.EntryItem" from the definition of your ObjectDataSource. I've NEVER used that and bindings always work.

But this will probably not help, as the Bind/Eval is probably not able to follow references (Bind("MainContact.FirstName")).

Instead, rewrite this as

<%# ((EntryItem)Container.DataItem).MainContract.FirstName #>

The downside is that you loose automatic two-way binding so you have to help the binder a little. Just add a Inserting/Updating handlers to your ObjectDataSource and inside handlers:

protected void TheObjectDataSource_Updating( object sender, BlahBlahEventArgs e )
{
    // find the control in the data bound parent
    TextBox txt = (TextBox)YourFormView.FindControl( "txtFirstName" );

    // read the value and add it to parameters
    e.Parameters.Add( "nameofyourparameter", txt.Text );
}
Wiktor Zychla
  • 47,367
  • 6
  • 74
  • 106
0

According to a couple sources, it is actually not possible to do two-way binding with nested properties.

Here's one answer to a similar question here on SO: https://stackoverflow.com/a/1195119/370671.

Also, there's a blog post describing the issue:

Now, still in most cases using ObjectDataSource will not cause you any problems when what [you] bind is a simple property such as a name of a client i.e.: you have a collection of clients that you bind to a GridView and one of the columns displays the name of the client. To do so you use something like Bind("Name"). The problem arises when you need to bind to a subproperty such as in Bind("Address.StreetName"). This won't work

Community
  • 1
  • 1
Ryan Kohn
  • 13,079
  • 14
  • 56
  • 81