c#.net ListView - pull different information back from different tables
I’m using c#.net.
I have been looking round the web and can’t find anything that helps me out.
I have a list of contractors, daily hours, daily slots (three different tables).
- First row - contains the contractors names.
- Second row - unlimited - contains the slots.
For example
I thought I could use a ListView however I am having trouble working out where I would place th开发者_如何学Pythone code.
<asp:ListView ID="contractorListView" runat="server">
<LayoutTemplate>
<table runat="server">
<tr>
<td>Times</td>
// Contractors names pulled from another
<th><asp:PlaceHolder id="itemPlaceholder" runat="server" /></th>
</tr>
</LayoutTemplate>
<ItemTemplate>
<tr>
<td>Times pulled from one database table</td>
<td align="left" style="width: 200px;">
// Customers name - attached to correct time
<asp:Label runat="server" Text='<%#Eval("person_name")%>' />
</td>
</tr>
</ItemTemplate>
</asp:ListView>
Uses Linq model so can connect to customers 'slotted time'
ObjectDataSource contractorDataSource = new ObjectDataSource();
contractorDataSource.SelectMethod = "GetContractorByDateCategory";
contractorDataSource.TypeName = "contractBook.classes.contractorRepository";
contractorListView.DataSource = contractorDataSource;
contractorDataSource.DataBind();
contractorListView.DataBind();
Anyone got any ideas / example?
Thanks in advance for any help.
Clare
Here's how I tend to solve problems like this:
manually pull our the data you want to show (calling the repository methods, not using ObjectDataSource to do it). For efficiency, it often makes sense to make one big query which returns denormalized records, each containing all the columns you'll need (e.g.
SELECT TimeSlot, CustomerName, ContractorName FROM (joins go here) ...
)create a custom collection class which puts that data into a format which is easy to data-bind. "easy to data bind" usually means that the data is organized in the same order that you're going to display it. You also might need to do hacks like, for example, making all your rows the same length in order to databind the same number of table cells per row.
in your data-binding expressions, cast Container.DataItem to whatever type you need, in order to pull out properties of that type. This also has the added advantage of making databinding expressions with typos fail at compile time rather than waiting until runtime to find bugs.
for nesting, set the DataSource property of a nested template control (e.g. a repeater) to a property of the parent Container.DataItem
for header rows, here's a trick: put header code directly into the ItemTemplate, and use Container.ItemIndex==0 to know when to show the header line or not. Since only Repeater (but not ListView) supports the ItemIndex property, I tend to use Repeater instead of ListView for most read-only data-binding tasks. That's why I changed your ListView in my code sample below to use Repeater. The same thing can be done by adding an Index or RowNumber property to your custom data-binding classes described above, but that's harder.
The general idea is that you want to push as much intelligence as possible out of your databinding code and into actual methods in your page (or code-behind) code, which is easier to write and debug.
Here's a working sample (with your classes and repository classes mocked up) to give you an idea what I'm talking about. You should be able to adapt this to your situation.
<%@ Page Language="C#"%>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="System.Linq" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
public class Person // replace with your class
{
public string person_name {get; set;}
}
public class Repository // replace with your class
{
public static IEnumerable<Record> GetCustomerByDateCategory()
{
// fake some data
return new Record[]
{
new Record { Time = new DateTime(2000, 1, 1, 8, 0, 0), Contractor = new Person {person_name = "Joe the Plumber"}, Customer = new Person {person_name = "Joe Smith"} },
new Record { Time = new DateTime(2000, 1, 1, 8, 30, 0), Contractor = new Person {person_name = "Bob Vila"}, Customer = new Person {person_name = "Frank Johnson"} },
new Record { Time = new DateTime(2000, 1, 1, 8, 30, 0), Contractor = new Person {person_name = "Mr. Clean"}, Customer = new Person {person_name = "Elliott P. Ness"} },
};
}
public class Record // replace this class with your record's class
{
public DateTime Time {get; set;}
public Person Contractor { get; set; }
public Person Customer { get; set; }
}
}
// key = time, value = ordered (by contractor) list of customers in that time slot
public class CustomersByTime : SortedDictionary<DateTime, List<Person>>
{
public List<Person> Contractors { get; set; }
public CustomersByTime (IEnumerable <Repository.Record> records)
{
Contractors = new List<Person>();
foreach (Repository.Record record in records)
{
int contractorIndex = Contractors.FindIndex(p => p.person_name == record.Contractor.person_name);
if (contractorIndex == -1)
{
Contractors.Add(record.Contractor);
contractorIndex = Contractors.Count - 1;
}
List<Person> customerList;
if (!this.TryGetValue(record.Time, out customerList))
{
customerList = new List<Person>();
this.Add(record.Time, customerList);
}
while (customerList.Count < contractorIndex)
customerList.Add (null); // fill in blanks if needed
customerList.Add (record.Customer); // fill in blanks if needed
}
MakeSameLength();
}
// extend each list to match the longest one. makes databinding easier.
public void MakeSameLength()
{
int max = 0;
foreach (var value in this.Values)
{
if (value.Count > max)
max = value.Count;
}
foreach (var value in this.Values)
{
while (value.Count < max)
value.Add(null);
}
}
}
protected void Page_Load(object sender, EventArgs e)
{
CustomersByTime Customers = new CustomersByTime(Repository.GetCustomerByDateCategory());
CustomerListView.DataSource = Customers;
CustomerListView.DataBind();
}
</script>
<html>
<head>
<style type="text/css">
td, th, table { border:solid 1px black; border-collapse:collapse;}
</style>
</head>
<body>
<asp:Repeater ID="CustomerListView" runat="server">
<HeaderTemplate><table cellpadding="2" cellspacing="2"></HeaderTemplate>
<ItemTemplate>
<asp:Repeater runat="server" visible="<%#Container.ItemIndex==0 %>"
DataSource="<%#((CustomersByTime)(CustomerListView.DataSource)).Contractors %>" >
<HeaderTemplate>
<tr>
<th>Times</th>
</HeaderTemplate>
<ItemTemplate>
<th><%#((Person)Container.DataItem).person_name %></th>
</ItemTemplate>
<FooterTemplate>
</tr>
</FooterTemplate>
</asp:Repeater>
<tr>
<td><%#((KeyValuePair<DateTime, List<Person>>)(Container.DataItem)).Key.ToShortTimeString() %></td>
<asp:Repeater ID="Repeater1" runat="server" DataSource="<%# ((KeyValuePair<DateTime, List<Person>>)(Container.DataItem)).Value %>">
<ItemTemplate>
<td align="left" style="width: 200px;">
<%#Container.DataItem == null ? "" : ((Person)(Container.DataItem)).person_name%>
</td>
</ItemTemplate>
</asp:Repeater>
</tr>
</ItemTemplate>
<FooterTemplate></table></FooterTemplate>
</asp:Repeater>
</body>
</html>
BTW, if you're building a brand-new app and have some time for learning, I definitely suggest looking at ASP.NET MVC, which has a non-trivial learning curve but makes a lot of things easier... in partiuclar this kind of complex data rendering.
Is this not just a cross-tab situation? Look to my solution here:
Cross Tab - Storing different dates (Meeting1, Meeting2, Meeting 3 etc) in the same column
Why not create a single query which extracts all your information?
You can perform a JOIN operation over multiple tables.
If the join gets really complex, you can create a VIEW, which creates a new virtual table to consolidate your information.
精彩评论