Вот как я склонен решать такие проблемы:
вручную извлекаем данные, которые вы хотите показать (вызывая методы репозитория, не используя ObjectDataSource для этого). Для эффективности часто имеет смысл сделать один большой запрос, который возвращает денормализованные записи, каждая из которых содержит все необходимые вам столбцы (например, SELECT TimeSlot, CustomerName, ContractorName FROM (joins go here) ...
)
создать собственный класс коллекции, который помещает эти данные в формат, который легко связать с данными. «Простое связывание данных» обычно означает, что данные организованы в том же порядке, в котором вы собираетесь их отображать. Вам также может понадобиться взломать, например, сделать все строки одинаковой длины, чтобы привязать данные к одинаковому количеству ячеек таблицы в строке.
в выражениях привязки данных приведите Container.DataItem к любому типу, который вам нужен, чтобы вытащить свойства этого типа. Это также имеет дополнительное преимущество, заключающееся в том, что выражения привязки данных с опечатками завершаются с ошибкой во время компиляции, а не ожидают, пока во время выполнения не будет найдены ошибки.
для вложенности установите свойство DataSource вложенного шаблонного элемента управления (например, повторитель) на свойство родительского Container.DataItem
для строк заголовка, вот хитрость: поместите код заголовка непосредственно в ItemTemplate и используйте Container.ItemIndex == 0, чтобы знать, когда показывать строку заголовка или нет. Поскольку только Repeater (но не ListView) поддерживает свойство ItemIndex, я склонен использовать Repeater вместо ListView для большинства задач привязки данных только для чтения. Вот почему я изменил ваш ListView в моем примере кода ниже, чтобы использовать Repeater. То же самое можно сделать, добавив свойство Index или RowNumber в пользовательские классы привязки данных, описанные выше, но это сложнее.
Общая идея заключается в том, что вы хотите использовать как можно больше информации вашего кода привязки к данным и к фактическим методам в коде вашей страницы (или кода сзади), который легче писать и отлаживать .
Вот рабочий пример (с вашими классами и классами репозитория), чтобы дать вам представление о чем я говорю. Вы должны быть в состоянии приспособить это к вашей ситуации.
<%@ 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>
Кстати, если вы создаете совершенно новое приложение и у вас есть время для обучения, я определенно предлагаю взглянуть на ASP.NET MVC, который имеет нетривиальную кривую обучения, но составляет лот вещей проще ... в частности это сложный рендеринг данных.