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

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

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

Вот некоторый псевдо код:

возразите пользователю с электронной почтой / пароль

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

Я плохо знаком с.NET (приезжайте от 8 + годы фона VBScript ASP), и пытающийся набрать скорость на 'правильном' способе сделать вещи.

35
задан jpshook 5 March 2010 в 17:11
поделиться

8 ответов

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

Краткий ответ на ваш вопрос: Да вы захотите использовать экземпляры класса (объекты) в качестве посредника между вашим пользовательским интерфейсом и уровнем бизнес-логики. 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 - класс, реализующий логику, ну, Content Pages. Метод извлечения данных - « 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) и заполнять объект или список объектов. Это Business Logic , потому что отображение часто сопровождается дополнительной логикой для конкретизации модели (например, определение значения производных полей).

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

Я использовал другой подход. В моем BLL я определяю классы Logic и классы Model . Обычно они определяются в совпадающих парах, где оба класса определены в одном файле и чьи имена отличаются своим суффиксом (например, ClassModel и ClassLogic). Классы логики знают, как работать с классами модели - делать такие вещи, как Fill, Save («Upsert»), Delete и генерировать обратную связь для экземпляра модели.

В частности, для выполнения Fill я использую методы, найденные в моем основном классе DAL (показанном ниже), которые позволяют мне взять любой класс и любой запрос SQL и найти способ создания / заполнения экземпляров класса с использованием возвращенных данных. по запросу (либо в виде отдельного экземпляра, либо в виде списка). То есть класс Logic просто получает определение класса Model, определяет 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 (и т. Д.) На уровне доступа к данным, вы будете нести ненужные накладные расходы (вот почему вам не следует этого делать).

24
ответ дан 27 November 2019 в 07:18
поделиться

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

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

1
ответ дан 27 November 2019 в 07:18
поделиться

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

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

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

13
ответ дан 27 November 2019 в 07:18
поделиться

«Правильных» способов сделать это почти столько же, сколько команд программистов в мире. Тем не менее, мне нравится строить фабрику для каждого из моих бизнес-объектов, которая выглядит примерно так:

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);
}

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

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

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);
7
ответ дан 27 November 2019 в 07:18
поделиться

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

1
ответ дан 27 November 2019 в 07:18
поделиться

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

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

0
ответ дан 27 November 2019 в 07:18
поделиться

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

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

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

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

0
ответ дан 27 November 2019 в 07:18
поделиться

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

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

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

1
ответ дан 27 November 2019 в 07:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: