I cureently have a set up like below
Public ClassA
property _classB as ClassB
End Class
Public ClassB
property _someProperty as someProperty
End Class
what I want to do is to databind object A to a gridview with one of the columns being databound to ClassB._someProperty. When I try to databind it as Classb._someProperty I get a "Field or Property not found on Selected Datasource" error
I have tried to use the objectContainerDataSource and also directly binding to the gridview with no success.
Has anyone come across this in the past?
Ordinary databinding doesn't generally allow for expressions. Under the hood the datagrid is using reflection (rather the executing code the way DataBinder.Eval does on an ASP.NET page) to find the property that you specify to bind to a column. To do what you want it would need to evaluate the binding as an expression, work out that you were looking for a parent -> child relation, and then reflect down to that level. AFAIK the inbuilt databinding on the grid is too dumb to know how to do this.
I've had the same issue recently, and my solution was to do a LINQ projection and bind that to the grid instead. Something like the following (in C# because I'm not comfortable with the LINQ syntax in VB):
IList<ClassA> listOfClassAObjects = GetMyListOfClassAObjectsFromSomewhere();
var projection = from ClassA a in listOfClassAObjects
select new { SomeProperty = a.SomeProperty,
SomeOtherProperty = a.SomeOtherProperty,
SomePropertyFromB = a.ClassB.SomeProperty };
datagrid.DataSource = projection;
datagrid.DataBind();
You'll get back a list of anonymous types containing that projection, and you can bind the appropriate column to SomePropertyFromB.
For extra encapsulation (if you do this a lot) put the projection into an extension method so you can do something like
var data = GetMyListOfClassAObjectsFromSomewhere().ProjectionForDataGrid();
datagrid.DataSource = data;
datagrid.DataBind();
I found the way to do this is to use a template field and eval (see below)
Set the datafield as property classB and then:
<asp:TemplateField HeaderText="_someProperty">
<ItemTemplate>
<%#Eval("classB._someProperty")%>
</ItemTemplate>
</asp:TemplateField>
Related
I have a GridView using model binding and strongly typed EF5 dbContext classes in a webforms app. The gridview is using the SelectMethod property to return classes from my EF5 model:
<asp:GridView ID="Server_grid" runat="server" AllowSorting="True"
ItemType="server_table" SelectMethod="getServers">
My EF server_table class has a relationship to another table called user_table. In my grid I am pulling the related username property from the user_table for one of my columns like this:
<asp:TemplateField HeaderText="User Name">
<ItemTemplate>
<%# Item.user_table.username %>
</asp:TemplateField>
My select method looks something like:
public IQueryable<server_table> getServers()
{
return dataContext.server_table.Include(x => x.user_table);
}
This all works just fine, but I need to sort on this column. So... I either need to ditch model binding completely (boo!) and select into an anonymous type in my query (in other words go back to using LinqDataSource and anon types and lose the typechecking that model binding gives me, sigh) which will allow me to simply add a simple SortExpression property on the GridView, or... figure out some way to do a custom sort.
So I've been hacking around with that idea and have come up with something that might work. What I'm doing is declaring a sort event like this:
protected void serverSort(object sender, GridViewSortEventArgs e)
{
sortExpression = e.SortExpression; //stash in a global
e.Cancel = true; //don't actually sort
Server_grid.DataBind(); //I'll sort there based on the stashed sortExpression
}
Then in my SelectMethod, I'm trying to build a lambda based on that saved sortexpression, so for example in this case I'd put "user_table.username" in my column SortExpression property, which gets stored in sortExpression above, then parse it and use it for the below:
public IQueryable<server_table> getServers()
{
var query = dataContext.server_table.Include(x => x.user_table);
//how do I use the sortExpression string below to build a lambda?
query = query.OrderBy(SOMETHING CLEVER HERE);
return query;
}
So basically, if I have the name of the table property OR the name of the table relationship with a property name on that related table, how can I build a lambda from that? So if my SortExpression is a simple property name on the server_table, it will work, or if it's a property name on a related table, I could parse the sortExpression string apart to have a related table + property name on that table. I need to be able to parse relationships of any length, i.e. user_table.other_table.another_table.yadday-yadday.property. There's got to be a way but my LINQ internals foo is not so great (any suggestions on a book for that?).
OR is there a better/simpler way to be able to sort like this, and continue using model binding, which I love? I wish I could just use user_table.username in the grid SortExpression property!! Agh!
You can use attribtes collection in order by p => p.Attributes["ColumnName"]
public IQueryable<server_table> getServers()
{
var query = dataContext.server_table.Include(x => x.user_table);
//how do I use the sortExpression string below to build a lambda?
query = query.OrderBy(p => p.Attributes[ColumnNameStoredInSortExpressVariable]);
return query;
}
For a data bound control it is common scenario where we provide data text field and data value field ( in simple controls like Dropdownlist) but more fields in controls like Gridview. Generally the datasource is of type IEnumerable.
How does the control internally process these values or rather how do they get the value from the data source without knowing what kind of datasource they are dealing with.
Can someone explain with code how the controls evaluate these fields from the data source.
Typically, the data-bound control (or concerned components such as
DataControlField in GridView) will handle DataBinding event.
Within event handler, the data item that is being currently bound (e.g. DataRowView or entity instance) is retrieved. This is done via DataBinder.GetDataItem passing the actual control or control's NamingContainer. For example, if you are implementing a lower level control such as DataControlField for higher level data-bound control such as GridView then it would handle data-binding of a cell control and hence it will use cell's naming container to pass to DataBinder.GetDataItem method which uses current data binding context to get the same.
Once the data item object is retrieved, one need to evaluate the given data-binding expression against it to get the actual value and apply any formatting as per different properties set to the control/component. The most simple way is to use DataBinder.Eval overload. However, one may use the more efficient ways - for example, say DataField string is going to be only property name then you may look and cache the property descriptor and then use the same against different data items.
I will suggest you to use tool such as Reflector to inspect relevant control's code to get the better idea.
I never knew i could find this information so easily and LLyod was in fact wrong on using reflection to find data from a datasource. None of the data controls use it when i inspected through Reflector ;(
link that solved the problem
http://msdn.microsoft.com/en-us/library/ms366540.aspx
how you do it is below
protected override void PerformDataBinding(IEnumerable retrievedData)
{
base.PerformDataBinding(retrievedData);
// Verify data exists.
if (retrievedData != null)
{
string dataStr = String.Empty;
foreach (object dataItem in retrievedData)
{
if (DataTextField.Length > 0)
{
dataStr = DataBinder.GetPropertyValue(dataItem,
DataTextField, null);
}
else
{
PropertyDescriptorCollection props =
TypeDescriptor.GetProperties(dataItem);
if (props.Count >= 1)
{
if (null != props[0].GetValue(dataItem))
{
dataStr = props[0].GetValue(dataItem).ToString();
}
}
}
}
}
}
If the above code seem Greek and Latin , you will have to have a course on asp.net controls development to understand what is being done.
I have a LinqDataSource defined like this:
<asp:LinqDataSource ID="LinqDataSource1" runat="server" OnSelecting="LinqDataSource_FileSelecting"></asp:LinqDataSource>
In LinqDataSource_FileSelecting, it returns/sets a List<string> to e.Result, where e is the EventArgs (LinqDataSourceSelectEventArgs) in the Selecting event of the LinqDataSource. I'm getting this error when the page loads:
"The ContextTypeName property of LinqDataSource 'LinqDataSource1' must specify a data context type."
Based on this message and looking at the docs, I need to set the ContextTypeName and TableName properties of the LinqDataSource. ContextTypeName would normally be the name of a class, and TableName would be the name of an enumerable property within that class.
When the data source is a List<string> I'm not sure what ContextTypeName or TableName I can assign. I've tried System.Collections.Generic.List<string> for the ContextTypeName, but that didn't work, I received "Could not load type 'System.Collections.Generic.List<string>'"
I realize the LinqDataSource may not be the appropriate control when the data source is a List<string> generated within the Selecting event. But is there a ContextTypeName and TableName I can set for this scenario?
Found the answer. Turns out that you can omit ContextTypeName and TableName when using the Selecting event and assigning a List<string> to e.Result. However, if you assign null to e.Result, then this error will occur since ASP.NET doesn't know the type of data. My code on the Selecting event was designed to set a non-null value to e.Result, but a separate problem was leading to null being set to e.Result ... leading to this error.
Lets say I have an object
class Person {
public string Name { get; set; }
public int Age { get; set; }
}
And that object is retrieved from a Factory (ie Can't use SQLDataSource or anything like that)
Person person = PersonFactory.GetPerson();
How can I two-way DataBind the two properties to Textboxes on a web form? I looked into FormView, but that doesn't seem to fit my needs as I am not iterating over a collection of objects. And when I tried to use it, I don't seem to be getting the posted values in the Person object in the FormUpdated event. And I am binding like this
Markup
<asp:Textbox Text=<%# Bind("Name") %> />
Code behind
FormView1.DataSource = new List() { person };
FormView1.DataBind();
I feel like I am missing something really obvious. Should I be using a FormView? It doesn't seem like it a proper fit for simple data binding, but the <#% Bind %> method must be in some type of container -- is there a more suitable object?
You need to handle the updates to your FormView - the updates in the asp.net databound controls are not automatic. I'd also consider using an ObjectDataSource - keeping your binding all in the markup can make things easier to find. When you use the ObjectDataSource - it'll automatically wrap your single object in an IEnumerable, so binding to a method that returns a Person is acceptable. You could also consider using a DetailsView if you don't want to write out the form yoruself. In your case, you could do the following
<asp:FormView runat="server" DataSourceID="MyPersonDataSource"> ... </asp:FormView>
<asp:ObjectDataSource runat="server" ID="MyPersonDataSource"
TypeName="PersonFactory" DataObjectTypeName="Person"
SelectMethod="GetPerson" UpdateMethod="UpdatePerson" />
And to facilitate this, you'd need an UpdateMethod(Person) method on your PersonFactory class. Doing it this way eliminates your binding from the codebehind, and will allow your updates to your person object to be persisted to your data store without you having to handle the update events yourself.
Try calling the DataBind method on your TextBox controls.
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
MyTextBox1.DataBind();
MyTextBox2.DataBind();
}
I've never tried doing two way binding in exactly this way before, but using the Bind("property") syntax, it should work this way as far as I know.
If calling the DataBind method doesn't work, then the FormView is your best bet.
DataBinder.Eval(Container.DataItem, "Name")
This should do the trick, but I think you need some event-handling of your own to do the 2-way binding.
I have a GridView with an ObjectDataSource and I want to be able to sort it.
Paging works correctly, however Sorting gives me an exception:
The GridView gridView fired event Sorting which wasn't handled.
How do I enable sorting on the server side?
(i.e. gridView.EnableSortingAndPagingCallbacks must remains false)
Set the gridView.AllowSorting property to true. From here the grid should allow you to sort data automatically on postback if you are using an object that implements IBindingList. However, since that is most likely not the case, you should take TheTXI's advice above and handle the sorting event yourself. Either wire the GridView.Sorting event in the codebehind, like so:
gridView.Sorting += new GridViewSortEventHandler(gridView_Sorting);
Handle the sorting inside the gridView_Sorting method, which should look like this:
private void gridView_Sorting(object sender, GridViewSortEventArgs e)
{
//Sorting logic here
}
Also, you can wire the event on the page itself using OnSort="gridView_Sorting" attached to the control.
Remember, since you are setting gridView.EnableSortingAndPagingCallbacks to false, this will not be immediately fired when the user tries to sort, it instead will wait for the postback to the server.
I hope this helps!
EDIT:
Since ObjectDataSource seems to be the middleman of choice, here is a brief explanation of wiring that for sorting as well. Use the following in your page (The full example can be found here on the MSDN, near the bottom):
<asp:GridView ID="TestGridView" runat="server" DataSourceID="ObjectDataSourceTest"
AllowSorting="True">
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSourceTest" runat="server"
SelectMethod="SelectMethod"
TypeName="Samples.AspNet.CS.SortingData"
SortParameterName="sortExpression">
</asp:ObjectDataSource>
Instead of actually using the gridView.Sorting event, you'll be jumping over to the ObjectDataSource to take care of the sorting. Once the sort is triggered it should call the method found in SelectMethod in your code behind. Then, inside SelectMethod, you would handle the rebuilding of your GridView object, which would look like:
public void SelectMethod(string sortExpression)
{
//Rebuild gridView table if necessary, same method used in
//on a postback, and retrieve data from the database. Once
//completed sort the data with:
gridView.Sort(sortExpression, SortDirection.(Ascending or Descending))
}
I am using Linq2Sql and a ObjectDataSource and it does Paging and Sorting very well.
I implemented a Class to be used as the ObjectDataSource. It has a Select and a Count method calling my business layer which uses Linq2SQL queries to retrieve data from the DB. The select methods gets the first item index, page size and the sort expression as parameters automatically.
public List<EntityClass> Select(int startIndex, int pageSize, string sortBy) {}
public int Count() {}
In the ASPX, the DataSource is configured like this:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
SelectMethod="Select" EnablePaging="true"
StartRowIndexParameterName="startIndex"
MaximumRowsParameterName="pageSize"
SortParameterName="sortBy" SelectCountMethod="Count" >
</asp:ObjectDataSource>
The Select and the Count method use Linq queries to retrieve the data from the DB. I use the Skip(), Take() and Orderby() methods. For the OrderBy to accept a string sort expression I use DynamicLinq There is not much to code, Databinding, paging and Sorting are automatically working.
Pass SortExpression in method of Data Access Layer that calls StoredProcedure
and write below way SP to handle sorting on SQL. This way you can improve the performance of your sorting.
Database SP:
Select ROW_NUMBER() OVER(ORDER BY '+#sortExpression +' ) as RowNum
,* from (SELECT CUSTOMERID,
LEDGERDESCRIPTION,
CustomerDescription as CustomerName
WHERE REGIONID ='''+#RegionID+''')t
order by RowNum'
You can use LINQ for this just use OrderBy on the selected column, do this:
public static List<YourDataObject> GetSortedData(string orderBy)
{
List<YourDataObject> sortedDataList = new List<YourDataObject>();
switch (orderBy)
{
case "Col1": sortedEmployeeList = GetDefaultObjects().OrderBy(x => x.Col1).ToList();
break;
case "Col2":
//Do this for all columns
default:
sortedEmployeeList = GetDefaultObjects();
break;
}
return sortedEmployeeList;
}
This is done in the Select method of the object data source.
In the GridView add sort keys in it. And add SelectParameter in the object data source a `orderBy'
<asp:GridView AllowSorting="true" DataSourceID="objDsAllObjects" ....>
<Columns>
<asp:BoundField SortExpression="Col1"/>
<!-- Do this for all columns -->
</Columns>
</asp:GridView >
<asp:ObjectDataSource ID="objDsAllObjects" SortParameterName="orderBy" runat="server"
SelectMethod="GetAllEmployees" TypeName="YourObjectClass"></asp:ObjectDataSource>
It worked for me without changing the DAL layer and stored procedures.