Подтвердить что ты не робот

Как передать данные между классами и уровнями приложений?

Например, если я создаю трехуровневое приложение (data/business/UI), а слой данных захватывает одну или несколько записей. Я переписываю все из уровня данных в общий список/коллекции перед отправкой на бизнес-уровень? Можно ли отправлять таблицы данных? Как насчет отправки информации обратно на уровень данных?

Если я использую объекты/списки, являются ли эти элементы слоев данных или бизнеса? Могу ли я использовать одни и те же объекты для перехода к слоям и из них?

Вот несколько псевдокодов:

пользователь объекта с электронной почтой/паролем

в слое пользовательского интерфейса, пользователь вводит адрес электронной почты/пароль. Уровень пользовательского интерфейса выполняет валидацию, а затем я предполагаю, что пользователь нового объекта переходит на бизнес-уровень, который выполняет дополнительную проверку и передает один и тот же объект на слой данных для вставки записи. Правильно ли это?

Я новичок в .NET(исходит из более чем 8-летнего опыта работы ASP VBScript) и стараюсь, чтобы ускорить "правильный" способ сделать что-то.

4b9b3361

Ответ 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 (и т.д.) На вашем уровне доступа к данным, вы будете нести лишние накладные расходы (именно поэтому вы не должны этого делать).

Ответ 2

В общем, я думаю, что лучше отправлять объекты, а не таблицы данных. С объектами каждый слой знает, что он получает (какие объекты с какими свойствами и т.д.). Вы получаете безопасность времени компиляции с объектами, вы не можете случайно пропустить имя свойства и т.д., И это заставляет присущий контракт между двумя уровнями.

Joshua также поднимает хороший момент, используя свой пользовательский объект, вы также отделяете другие уровни от уровня данных. Вы всегда можете заполнить свой пользовательский объект из другого источника данных, а другие уровни не будут более мудрыми. С таблицей данных SQL это, вероятно, будет не так просто.

Джоэл также сделал хороший момент. Наличие уровня данных, осведомленного о ваших бизнес-объектах, не является хорошей идеей по той же причине, что и ваш бизнес и уровни пользовательского интерфейса, осведомленные о специфике вашего уровня данных.

Ответ 3

Существует почти столько "правильных" способов сделать это, поскольку в мире есть команды программирования. Тем не менее, мне нравится создавать factory для каждого из моих бизнес-объектов, которые выглядят примерно так:

public static class SomeBusinessObjectFactory
{
   public static SomeBusinessObject FromDataRow(IDataRecord row)
   {
       return new SomeBusinessObject() { Property1 = row["Property1"], Property2 = row["Property2"] ... };
   }
}

У меня также есть общий метод перевода, который я использую для вызова этих заводов:

public static IEnumerable<T> TranslateQuery(IEnumerable<IDatarecord> source, Func<IDatarecord, T> Factory)
{
    foreach (IDatarecord item in source)
        yield return Factory(item);
}

В зависимости от того, что предпочитает ваша команда, размера проекта и т.д., эти объекты и переводчик factory могут жить с уровнем бизнес-уровня или уровня данных или даже с дополнительной сборкой/слоем перевода.

Тогда мой уровень данных будет иметь код, который выглядит следующим образом:

private SqlConnection GetConnection()
{
    var conn = new SqlConnection( /* connection string loaded from config file */ );
    conn.Open();
    return conn;
}

private static IEnumerable<IDataRecord> ExecuteEnumerable(this SqlCommand command)
{
    using (var rdr = command.ExecuteReader())
    { 
        while (rdr.Read())
        {
            yield return rdr;
        }
    }
}

public  IEnumerable<IDataRecord> SomeQuery(int SomeParameter)
{
    string sql = " .... ";

    using (var cn = GetConnection())
    using (var cmd = new SqlCommand(sql, cn))
    {
        cmd.Parameters.Add("@Someparameter", SqlDbType.Int).Value = SomeParameter;
        return cmd.ExecuteEnumerable();
    }
}

И тогда я могу собрать все это следующим образом:

 SomeGridControl.DataSource = TranslateQuery(SomeQuery(5), SomeBusinessObjectFactory.FromDataRow);

Ответ 4

Я добавлю новый слой, Реляционное сопоставление объектов ORM, с возможностью преобразования данных из уровня данных в коллекции объектов bussiness. Я думаю, что использование объектов в вашей бизнес-модели - лучшая практика.

Ответ 5

Независимо от того, что вы используете для передачи данных между уровнями вашего приложения, просто убедитесь, что детали реализации каждого уровня не просачиваются в другие. Вы должны иметь возможность изменить способ хранения данных в реляционной базе данных без изменения какого-либо кода в слоях бизнес-объектов (кроме, конечно, сериализации).

Плотная связь между дизайном бизнес-объектов и моделью реляционных данных крайне раздражает и является пустой тратой хорошей РСУБД.

Ответ 6

Здесь много замечательных ответов, я бы добавил, что прежде чем тратить много времени на создание слоев перевода и фабрик, важно понять цель и будущее вашего приложения.

Где-то, будь то файл сопоставления конфигурации, factory или непосредственно на вашем уровне data/business/ui, некоторый объект/файл/класс/etc должен иметь представление о том, что происходит между каждым слоем. Если перераспределение слоев реалистично, то создание слоев перевода полезно. В других случаях имеет смысл иметь некоторый уровень (я обычно делаю это в бизнесе), который знает обо всех интерфейсах (или, по крайней мере, достаточно, чтобы бронировать между данными и ui).

Опять же, это не значит, что все это плохо, просто, что возможно YAGNI. Некоторые рамки DI и ORM делают этот материал настолько легким, что глупо это не делать. Если вы используете его, то, вероятно, имеет смысл получить его за все, что стоит.

Ответ 7

Приложение, над которым я сейчас работаю, довольно старое (в терминах .NET) и использует сильно типизированные наборы данных для передачи данных между слоем данных и бизнес-слоем. В бизнес-слое данные в наборах данных вручную "отображаются или отображаются" в бизнес-объекты перед передачей в интерфейс.

Это, вероятно, не популярное дизайнерское решение, хотя из-за того, что сильно типизированный набор данных всегда был несколько спорным.

Ответ 8

Я настоятельно рекомендую вам делать это с объектами. Другой подход предполагает, что только интерфейсы являются публичными, а ваши реализации являются внутренними, и вы предоставляете свои методы через factory своего объекта, а затем соединяете свои фабрики с фасадом, чтобы, наконец, иметь единственную и уникальную точку входа в вашу библиотеку. Затем через ваш фасад проходят только объекты данных, поэтому вы всегда знаете, чего ожидать внутри, как за пределами вашего фасада.

Таким образом, любой пользовательский интерфейс может вызывать фасад вашей библиотеки, и единственное, что останется для кода, это ваш интерфейс.

Здесь ссылка, которую я нахожу лично очень интересной, что вкратце объясняет различные шаблоны проектирования: Шаблоны дизайна GoF.NET для С# и VBNET.

Если вы предпочитаете образец кода, иллюстрирующий то, что я заявляю, не стесняйтесь спрашивать.