Asp.Net: возвращение читателя из класса

Мне просто интересно, как правильно вернуть читателя из класса?

Мой код ниже работает, но я не уверен, что это правильно.

Также. Я не могу закрыть соединение в своем методе класса и по-прежнему получать к нему доступ с моей страницы ascx, это

это нормально?

// В моем классе есть следующий метод для возврата записи/считывателя -- в данном случае это одна запись.

public SqlDataReader GetPost()
    {
        SqlConnection conn = new SqlConnection(connectionString);
        SqlCommand cmd = new SqlCommand("con_spPost", conn);
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Parameters.AddWithValue("@blogid", blogid);
        try
        {
            conn.Open();
            return cmd.ExecuteReader();
        }
        finally
        {
          //  conn.Close();
        }
    }

// Затем я вызываю метод GetPost на моей странице ascx следующим образом:

protected void Page_Load(object sender, EventArgs e)
{

    //instantiate our class
    MyClass DB = new MyClass();

    //pass in the id of the post we want to view
    DB.PostID = Int32.Parse(Request.QueryString["p"]);

    ///call our GetPost method
    SqlDataReader reader = DB.GetPost();

   //output the result
    reader.Read();
    this.viewpost.InnerHtml = "<span id='post1_CreatedDate'>" + reader["CreatedDate"].ToString() + "</span><br>";
    this.viewpost.InnerHtml += "<span class='blogheads'>" + reader["BlogTitle"].ToString() + "</span><p><p>";
    this.viewpost.InnerHtml += reader["BlogText"].ToString();
    reader.Close();
}

Буду признателен за любые комментарии к моему коду или советы, спасибо.

Таять


person Melt    schedule 30.12.2009    source источник
comment
хм... ТАК, кажется, испортил мой код :-(   -  person Melt    schedule 30.12.2009
comment
SO использует некоторые знаки препинания в качестве кодов разметки. Иногда с этим приходится возиться, как это сделал услужливый Одед.   -  person DOK    schedule 30.12.2009


Ответы (7)


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

Для этого вы должны передать IDbConnection методу GetPost, а затем убедиться, что ваш вызывающий объект располагает как соединением, так и считывателем. Ключевое слово using является наиболее удобным способом сделать это:

protected void Page_Load(object sender, EventArgs e) {

    // Create the DB, get the id, etc.    

    using (IDbConnection connection = new SqlConnection(connectionString))
    using (IDataReader reader = DB.GetPost(connection)) {
        reader.Read();
        this.viewpost.InnerHtml = reader["BlogText"].ToString();
        // finishing doing stuff with the reader  
    }
}

Как указывали другие, это начинает загромождать уровень представления вашего приложения слишком большой инфраструктурой доступа к данным, поэтому здесь это не подходит. Пока вы не столкнетесь с проблемой производительности или вам не понадобится отображать неразумное количество данных, вы не должны обрабатывать считыватели данных на уровне представления. Просто заставьте DB.GetPost возвращать строку и инкапсулируйте в нее весь код доступа к данным.

person Jeff Sternal    schedule 30.12.2009

Чтобы убедиться, что соединение закрыто, замените вызов ExecuteReader следующим:

return cmd.ExecuteReader(CommandBehavior.CloseConnection);

Вы также должны удалить блок try/finally.

Кроме того, в вашем обработчике Page_Load вы должны использовать оператор using, например:

using (SqlDataReader reader = DB.GetPost()) {

    //output the result
    reader.Read();
    this.viewpost.InnerHtml = "<span id='post1_CreatedDate'>" + reader["CreatedDate"].ToString() + "</span><br>"
        + "<span class='blogheads'>" + reader["BlogTitle"].ToString() + "</span><p><p>"
        +  reader["BlogText"].ToString();
}

Кроме того, вы должны проверить, действительно ли SQL-запрос вернул что-то вроде этого:

if (!reader.Read()) {
    Something's wrong
}

Наконец, и это, безусловно, важнее, вы должны избегать своего HTML, чтобы предотвратить дыры XSS, вызывая Server.HtmlEncode.

Например:

    this.viewpost.InnerHtml = "<span id='post1_CreatedDate'>" + reader["CreatedDate"].ToString() + "</span><br>"
        + "<span class='blogheads'>" + Server.HtmlEncode(reader["BlogTitle"].ToString()) + "</span><p><p>"
        + Server.HtmlEncode(reader["BlogText"].ToString());
person SLaks    schedule 30.12.2009
comment
Спасибо за полезные комментарии, даже не подумал про XSS-дыры. - person Melt; 30.12.2009
comment
+1 за отличный комментарий и упоминание html escape. Кроме того, я читал, что Textile.NET — это хороший и простой способ предотвратить дыры XSS при сохранении разметки в базе данных. Я собирался использовать его, но на самом деле не было возможности (... пока). textilenet.codeplex.com - person Jim Schubert; 11.01.2010

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

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

Вот учебник: http://www.asp.net/learn/data-access/tutorial-01-cs.aspx

person Jim Schubert    schedule 30.12.2009
comment
Спасибо за ссылку, посмотрю позже - person Melt; 30.12.2009

Существует проблема. Ваше соединение не закрывается. Как вы знаете, вы не можете закрыть его в своем GetPost, потому что тогда у вас больше не будет данных из-за природы DataReader. Один из способов решить эту проблему — включить параметр в метод ExecuteReader следующим образом:

cmd.ExecuteReader(CommandBehavior.CloseConnection)

Затем, когда ваш ридер будет закрыт, соединение будет закрыто.

Существует фундаментальная проблема с возвращаемым инкапсулированным кодом средством чтения данных, заключающимся в том, что через все это должно быть открыто соединение, что затрудняет обработку ошибок. Рассмотрите вариант (A) с использованием таблицы данных, которая почти так же эффективна для небольших наборов данных. Таким образом, вы можете сразу же закрыть соединение в методе GetPost и не беспокоиться об этом, с очень простой обработкой ошибок. Или (B) передать соединение в GetPost, чтобы весь синтаксис использования/удаления и обработка ошибок для соединения были явными в одном месте. Я бы предложил вариант А.

person Patrick Karcher    schedule 30.12.2009

Это очень простая архитектура. Как предложил CSharpAtl, вы можете сделать его более сложным. Тем не менее, это, кажется, работает для вас.

Одним важным дополнением, которое я бы сделал, было бы использование блоков try-finally. Помещение Close в finally гарантирует, что соединение будет разорвано, даже если во время обработки возникнет исключение.

SqlDataReader reader;
try
{
///call our GetPost method
    reader = DB.GetPost();

   //output the result
    reader.Read();
    this.viewpost.InnerHtml = "<span id='post1_CreatedDate'>" + reader["CreatedDate"].ToString() + "</span><br>";
    this.viewpost.InnerHtml += "<span class='blogheads'>" + reader["BlogTitle"].ToString() + "</span><p><p>";
    this.viewpost.InnerHtml += reader["BlogText"].ToString();
}
finally
{
    reader.Close();
}
person DOK    schedule 30.12.2009

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

person CSharpAtl    schedule 30.12.2009

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

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

Удачи и надеюсь, что это поможет некоторым.

person Chris    schedule 30.12.2009