Ответ 1
Я обновляю этот ответ, потому что комментарии, оставленные разработчиком, по-видимому, указывают на то, что он хотел бы немного подробнее.
Короткий ответ на ваш вопрос Да, вы захотите использовать экземпляры класса (объекты), чтобы оповестить интерфейс между вашим пользовательским интерфейсом и вашим бизнес-логическим уровнем. BLL и DAL свяжутся, как описано ниже. Вы не должны передавать SqlDataTables или SqlDataReaders.
Простые причины того, почему: объекты безопасны по типу, предлагают поддержку Intellisense, позволяют делать дополнения или изменения на уровне бизнеса, которые не обязательно находятся в базе данных, и дают вам свободу отменить связь из базы данных, чтобы вы могли поддерживать согласованный интерфейс BLL, даже если база данных изменяется (в пределах, конечно). Это просто хорошая практика программирования.
Большая картина заключается в том, что для любой страницы вашего пользовательского интерфейса у вас будет одна или несколько "моделей", которые вы хотите отображать и взаимодействовать. Объекты - это способ захватить текущее состояние модели. В терминах процесса: пользовательский интерфейс запрашивает модель (которая может быть единственным объектом или списком объектов) из уровня бизнес-логики (BLL). Затем BLL создает и возвращает эту модель - обычно с использованием инструментов с уровня доступа к данным (DAL). Если изменения будут внесены в модель в пользовательском интерфейсе, тогда пользовательский интерфейс отправит пересмотренный объект обратно в BLL с инструкциями относительно того, что с ними делать (например, вставить, обновить, удалить).
.NET отлично подходит для такого рода Разделения проблем, потому что общие классы контейнеров, и в частности класс List < > , идеально подходят для такого рода работ. Они не только позволяют передавать данные, но и легко интегрируются со сложными элементами управления пользовательского интерфейса, такими как сетки, списки и т.д. Через класс ObjectDataSource. Вы можете реализовать весь спектр операций, необходимых для разработки пользовательского интерфейса с использованием ObjectDataSource: операции "Заполнить" с параметрами, операциями CRUD, сортировкой и т.д.).
Поскольку это довольно важно, позвольте мне быстро переубедить, чтобы продемонстрировать, как определить объект ObjectDataSource:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetArticles"
OnObjectCreating="OnObjectCreating"
TypeName="MotivationBusinessModel.ContentPagesLogic">
<SelectParameters>
<asp:SessionParameter DefaultValue="News" Name="category"
SessionField="CurPageCategory" Type="String" />
</SelectParameters>
</asp:ObjectDataSource>
Здесь MotivationBusinessModel является пространством имен для BLL, а ContentPagesLogic - это класс, реализующий логику для, ну, страниц контента. Метод вытягивания данных - "GetArticles", и для него требуется параметр CurPageCategory. В этом конкретном случае ObjectDataSource возвращает список объектов, которые затем используются сеткой. Обратите внимание, что мне нужно передать информацию состояния сеанса в класс BLL, поэтому в коде позади у меня есть метод "OnObjectCreating", который позволяет мне создать объект и передать параметры:
public void OnObjectCreating(object sender, ObjectDataSourceEventArgs e)
{
e.ObjectInstance = new ContentPagesLogic(sessionObj);
}
Итак, так оно и работает. Но это вызывает один очень большой вопрос - откуда берутся Модели/Бизнес-объекты? ORM, такие как Linq to SQL и генераторы кода Subsonic, которые позволяют создавать класс для каждой из ваших таблиц базы данных. То есть, эти инструменты говорят, что классы моделей должны быть определены в вашем DAL и карте непосредственно на таблицы базы данных. Linq to Entities позволяет вам определять свои объекты в манере, отличной от макета вашей базы данных, но, соответственно, более сложной (поэтому существует различие между Linq to SQL и Linq to Entities). По сути, это решение BLL. Мы с Джоэлем в разных местах говорили о том, что на самом деле бизнес-уровень обычно определяется моделями (хотя в действительности я использую сочетание объектов BLL и DAL).
Как только вы решите это сделать, как вы реализуете сопоставление моделей с базой данных? Ну, вы пишете классы в BLL, чтобы вытащить данные (используя DAL) и заполнить объект или список объектов. Это бизнес-логика, потому что отображение часто сопровождается дополнительной логикой для извлечения Модели (например, определение значения производных полей).
Joel создает статические классы Factory для реализации сопоставления модели с базой данных. Это хороший подход, поскольку он использует хорошо известный шаблон и помещает правильное отображение в конструкцию возвращаемого объекта (объектов). Вы всегда знаете, куда идти, чтобы увидеть карту, и общий подход прост и понятен.
Я использовал другой подход. Во всем моем BLL я определяю классы логики и классы моделей. Они обычно определяются в совпадающих парах, где оба класса определены в одном файле и имена которых отличаются суффиксом (например, ClassModel и ClassLogic). Классы Logic знают, как работать с классами Model - делать такие вещи, как Fill, Save ( "Upsert" ), Delete и генерировать обратную связь для экземпляра модели.
В частности, для выполнения Fill я использую методы, найденные в моем основном классе DAL (показано ниже), которые позволяют мне взять любой класс и любой SQL-запрос и найти способ создания/заполнения экземпляров класса с использованием возвращенных данных по запросу (либо как один экземпляр, либо как список). То есть класс Logic просто захватывает определение класса модели, определяет SQL-запрос и отправляет его в DAL. Результатом является один объект или список объектов, которые я могу передать в пользовательский интерфейс. Обратите внимание, что запрос может возвращать поля из одной таблицы или нескольких таблиц, соединенных вместе. На уровне сопоставления мне действительно все равно - я просто хочу, чтобы некоторые объекты были заполнены.
Вот первая функция. Он будет принимать произвольный класс и автоматически отображать его во все соответствующие поля, извлеченные из запроса. Согласование выполняется путем поиска полей, имя которых соответствует свойству в классе. Если есть дополнительные поля классов (например, те, которые вы заполняете с использованием бизнес-логики) или дополнительные поля запроса, они игнорируются.
public List<T> ReturnList<T>() where T : new()
{
try
{
List<T> fdList = new List<T>();
myCommand.CommandText = QueryString;
SqlDataReader nwReader = myCommand.ExecuteReader();
Type objectType = typeof (T);
PropertyInfo[] typeFields = objectType.GetProperties();
if (nwReader != null)
{
while (nwReader.Read())
{
T obj = new T();
for (int i = 0; i < nwReader.FieldCount; i++)
{
foreach (PropertyInfo info in typeFields)
{
// Because the class may have fields that are *not* being filled, I don't use nwReader[info.Name] in this function.
if (info.Name == nwReader.GetName(i))
{
if (!nwReader[i].Equals(DBNull.Value))
info.SetValue(obj, nwReader[i], null);
break;
}
}
}
fdList.Add(obj);
}
nwReader.Close();
}
return fdList;
}
catch
{
conn.Close();
throw;
}
}
Это используется в контексте моего DAL, но единственное, что вы должны иметь в классе DAL, является держателем QueryString, объекта SqlCommand с открытым соединением и любыми параметрами. Ключ должен только убедиться, что ExecuteReader будет работать, когда это вызывается. Типичное использование этой функции моим BLL выглядит так:
return qry.Command("Select AttendDate, Count(*) as ClassAttendCount From ClassAttend")
.Where("ClassID", classID)
.ReturnList<AttendListDateModel>();
Вы также можете реализовать поддержку анонимных классов:
public List<T> ReturnList<T>(T sample)
{
try
{
List<T> fdList = new List<T>();
myCommand.CommandText = QueryString;
SqlDataReader nwReader = myCommand.ExecuteReader();
var properties = TypeDescriptor.GetProperties(sample);
if (nwReader != null)
{
while (nwReader.Read())
{
int objIdx = 0;
object[] objArray = new object[properties.Count];
for (int i = 0; i < nwReader.FieldCount; i++)
{
foreach (PropertyDescriptor info in properties) // FieldInfo info in typeFields)
{
if (info.Name == nwReader.GetName(i))
{
objArray[objIdx++] = nwReader[info.Name];
break;
}
}
}
fdList.Add((T)Activator.CreateInstance(sample.GetType(), objArray));
}
nwReader.Close();
}
return fdList;
}
catch
{
conn.Close();
throw;
}
}
Вызов этого выглядит так:
var qList = qry.Command("Select QueryDesc, UID, StaffID From Query")
.Where("SiteID", sessionObj.siteID)
.ReturnList(new { QueryDesc = "", UID = 0, StaffID=0 });
Теперь qList - это общий список динамически созданных экземпляров класса, определенных на лету.
Скажем, у вас есть функция в вашем BLL, которая принимает раскрывающийся список в качестве аргумента и запрос на заполнение списка данными. Вот как вы можете заполнить выталкивание результатами, полученными выше:
foreach (var queryObj in qList)
{
pullDownList.Add(new ListItem(queryObj.QueryDesc, queryObj.UID.ToString()));
}
Короче говоря, мы можем определять анонимные классы бизнес-модели "на лету", а затем заполнять их просто путем передачи некоторого (на лету) SQL в DAL. Таким образом, BLL очень легко обновить в ответ на меняющиеся потребности в пользовательском интерфейсе.
Одна последняя заметка: если вы обеспокоены тем, что определение и передача объектов из памяти отходов не должно быть: если вы используете SqlDataReader, чтобы вытащить данные и поместить их в объекты, составляющие ваш список, вы будете есть только одна копия в памяти (список), которую читатель выполняет в режиме "только для чтения", только вперед. Разумеется, если вы используете классы DataAdapter и Table (и т.д.) На вашем уровне доступа к данным, вы будете нести лишние накладные расходы (именно поэтому вы не должны этого делать).