Could not find property named "xxx.yyy" in FormView (two-way binding for nested properties)

I have this error when I try to update FormView

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

I think this is because I use a text field like this in EditTemplate

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

It shows the correct text in the text box, but apparently it does not work when it is updated.

This is a FormView data source.

 <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 class Person

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

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

How can i solve this?

+7
source share
3 answers

You can write your own control that can bind as you wish to be used this way (I made one of them):

  <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> 

In any case, I suggest that you do not use domain objects directly on the pages, but generally do not write them with ObjectDataSource . The problem is that when you 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 GridViews, FormViews, etc., to save BirthDate, otherwise the infrastructure will call the ObjectDataSource Update method, putting null in 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 faces from the database. Each person will have a date of birth. When you save, the person will be updated using BirthDate to null , because the GridView does not save the new field.

I think the best solution is to write a DTO to bind the data (and leave it 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 } } 

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

+4
source

Two possible approaches.

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

But that probably won't help, since Bind/Eval probably won't be able to follow the links ( Bind("MainContact.FirstName") ).

Rewrite it as

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

The downside is that you lose automatic two-way snapping, so you need to help the binder a bit. Just add Inserting/Updating handlers to the ObjectDataSource and internal 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 ); } 
+1
source

According to several sources, it is actually impossible to do two-way binding with nested properties.

Here's one answer to a similar question here about SO: https://stackoverflow.com/a/212618/

In addition, there is a blog post describing the problem:

Now, in most cases, using an ObjectDataSource will not cause any problems when what you are linking is a simple property, such as the client name, that is: you have a collection of clients that you are linking to the GridView and one of the columns displays the name customer. For this, you use something like Bind("Name") . The problem arises when you need to bind to a subtask, for example, in Bind("Address.StreetName") . This will not work

0
source

All Articles