Написание собственного класса провайдера в ASP.NET

Примечание. Я не хочу писать собственный поставщик членства.

Я хочу написать свой собственный класс Provider, чтобы я мог определить его в web.config и получить к нему доступ как к классу Membership.

Вот пример моего класса (у него много других статических методов):

public static class MySqlHelper
{
    private static string constring = ConfigurationManager.ConnectionStrings["MyConnString"].ConnectionString;

    public static int ExecuteNonQuery(string mysqlquery)
    {
        SqlConnection conn = new SqlConnection(connString);
    SqlCommand cmd = new SqlCommand(mysqlquery, conn);
    int result;

    try
    {
        conn.Open();
        result= cmd.ExecuteNonQuery();
    }
    finally
    {
        conn.Close();
    }
    return result;

    }
}

Использование: MySqlHelper.ExecuteNonQuery("select * from customers");

Теперь, как вы видите, я жестко запрограммировал имя строки подключения, то есть «MyConnString». Я планирую сделать его динамичным.

Поэтому мне было интересно, могу ли я сделать это как статический встроенный класс Membership, где я могу определить connectionStringName в web.config. Таким образом, класс можно сделать повторно используемым, не всегда называя мою строку подключения в web.config как «MyConnString».

1: Я НЕ хочу передавать строку подключения в каждый статический метод в качестве параметра.

2: я должен иметь доступ к методам, аналогичным Membership.CreateUser, то есть статическим.

Я параллельно просматриваю Интернет, но любые советы/рекомендации помогут.

Отредактировано: я обновил свой пример кода, чтобы устранить некоторую путаницу в отношении проблем, связанных с использованием статического класса. Вот новый вопрос, который я разместил, чтобы уточнить, что . Извините за путаницу.


person gbs    schedule 27.05.2011    source источник
comment
похоже, ты просишь магию или что-то в этом роде. вы в основном устранили 98% ответов с вашей квалификацией. в какой-то момент вы должны сообщить классу, как получить строку подключения. он не может просто угадывать информацию   -  person nathan gonzalez    schedule 27.05.2011
comment
@nathan: Возьмем, к примеру, метод Membership.CreateUser. Я могу установить connectionStringName в web.config для использования MembershipProvider. Ищем что-то похожее.   -  person gbs    schedule 27.05.2011
comment
я обновил свой ответ, чтобы учесть ваш последний комментарий. Я думаю, что могу получить то, что вам нужно сейчас.   -  person nathan gonzalez    schedule 27.05.2011


Ответы (3)


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

редактировать

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

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

person nathan gonzalez    schedule 27.05.2011
comment
Я думаю, что appSettings подойдет. Я дам ему попробовать. - person gbs; 27.05.2011

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

SqlConnections объединены, поэтому вы можете настроить провайдер должен иметь минимальное / максимальное количество экземпляров, доступных для запроса клиентов. Имейте в виду, что это также контролируется любой базой данных, к которой вы подключаетесь; предполагая, что вы подключаетесь к экземпляру SQL Server, SQL Server имеет собственную настройку максимального количества подключений.

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

public IEnumerable<SqlResults> ExecuteStoredProcedure(string procedure, params SqlParameter[] parameters) {
    using(SqlConnection connection = new SqlConnection(MyConnectionStringProperty)) {
        try {
            connection.Open();

            using(SqlCommand command = new SqlCommand(procedure, connection)) {
                command.CommandType = CommandType.StoredProcedure;

                if(parameters != null) {
                    command.Parameters.AddRange(parameters);
                }

                // yield return to handle whatever results from proc execution
                // can also consider expanding to support reader.NextResult()
                using(SqlDataReader reader = command.ExecuteReader()) {
                    yield return new SqlResults {
                        Reader = reader;
                    };
                }
            }
        }
        finally {
            if(connection.State != ConnectionState.Closed) {
                connection.Close();
            }
        }
    }
}

Приведенный выше пример кода — это просто образец концепции, которую я использую в работе. Образец теперь имеет максимальную обработку ошибок, но очень гибок в том, как результаты возвращаются и обрабатываются. Класс SqlResults просто содержит свойство SqlDataReader и может быть расширен для включения ошибок.

Что касается создания любого из этих static, все должно быть в порядке, если вы разрешаете способ создания одноэлементного экземпляра класса поставщика и по-прежнему не имеете общих изменяемых свойств (возможно, между различными запросами/потоками). Возможно, вы захотите рассмотреть какой-то подход IoC или Dependency Injection для предоставления строки подключения с учетом вашего запроса.

ИЗМЕНИТЬ

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

// Since it's an IEnumerable we can handle multiple result sets
foreach(SqlResults results in MySqlHelper.ExecuteStoredProcedure(myProcedureName, new SqlParameter("myParamName", myParamValue)) {
    // handle results
}

без закрытия соединения, пока мы обрабатываем результаты. Если вы заметили, в примере у нас есть using операторов для наших SqlClient объектов. Этот подход позволяет отделить обработку набора результатов от MySqlHelper, поскольку класс провайдера позаботится о потенциальном дублирующем коде предоставления SQL, делегирует обработку результатов вызывающей стороне, а затем продолжит выполнение того, что он должен делать (т.е. закрыть соединение) .

Что касается IoC/DI, я лично использую Castle Windsor. Вы можете внедрять объекты зависимостей как свойства или параметры построения. Регистрация контейнера Inversion of Control в качестве диспетчера ресурсов зависимостей позволит вам (среди прочего) возвращать один и тот же объект при запросе типа ресурса. По сути, для каждого вызывающего класса, которому необходимо использовать MySqlHelper, вы можете внедрить один и тот же экземпляр при создании экземпляра вызывающего класса или когда вызывающий класс ссылается на свое общедоступное свойство MySqlHelper. Я лично предпочитаю внедрение конструктора, когда это возможно. Кроме того, когда я говорю «внедрить», я имею в виду, что вам не нужно беспокоиться об установке значения свойства, так как ваш IoC/DI сделает это за вас (если настроен правильно). Подробнее см. здесь.

В качестве еще одного примечания: подход IoC/DI действительно будет работать только в том случае, если ваш класс нестатичен, так что каждое приложение может иметь свой собственный экземпляр singleton. Если MySqlHelper является статическим, вы можете поддерживать только одну строку подключения, если не передадите ее, что в исходном вопросе вы предпочли бы не делать этого. IoC/DI позволит вам использовать член свойства MySqlHelper, как если бы он был статическим, поскольку зарегистрированный контейнер гарантирует, что свойство имеет правильный экземпляр.

person bitxwise    schedule 27.05.2011
comment
Спасибо. Я планирую включить некоторые шаблоны из вашего образца. Не могли бы вы уточнить две вещи: 1: этот блок yield. 2: Я собираюсь сохранить эту статическую часть и поэтому задаюсь вопросом о каком-либо образце подхода IoC/DI, на который вы ссылаетесь. - person gbs; 27.05.2011
comment
Спасибо @bitxwise. Это многое проясняет. Основная причина, по которой у меня есть статический класс, заключается в том, что я не хотел время от времени создавать экземпляр своего класса. Мне нужно будет узнать больше о IoC/DI. На данный момент мне придется оставить свой класс статичным, и я соглашусь с предложением Натана. Но определенно +1 за вашу замечательную проницательность. - person gbs; 28.05.2011

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

using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;

public class SqlHelper
{
    private SqlConnection connection;

    public SqlHelper()
    {
        connection = new SqlConnection();
    }

    public void OpenConnection()
    {
        // Updated code getting the ConnectionString without hard naming it.
        // Yes, if you have more than 1 you'll have problems... But, how many times it happens?

        if (WebConfigurationManager.ConnectionStrings.Length == 0)
            throw new ArgumentNullException("You need to configure the ConnectionString on your Web.config.");
        else
        {
            connection.ConnectionString = WebConfigurationManager.ConnectionStrings[0].ConnectionString;
            connection.Open();
        }
    }

    public void CloseConnection()
    {
        if (connection != null && connection.State != ConnectionState.Closed)
            connection.Close();
    }

    public DataTable ExecuteToDataTable(string sql)
    {
        DataTable data;

        SqlCommand command = null;
        SqlDataAdapter adapter = null;

        try
        {
            if (connection.State != ConnectionState.Open)
                OpenConnection();

            command = new SqlCommand(sql, connection);
            adapter = new SqlDataAdapter(command);

            retorno = new DataTable();
            adapter.Fill(data);
        }
        finally
        {
            if (command != null)
                command.Dispose();

            if (adapter != null)
                adapter.Dispose();

            CloseConnection();
        }

        return data;
    }

    public int ExecuteNonQuery(string sql)
    {
        SqlCommand command = null;

        try
        {
            if (connection.State != ConnectionState.Open)
                OpenConnection();

            command = new SqlCommand(sql, connection);
            return command.ExecuteNonQuery();
        }
        finally
        {
            if (command != null)
                command.Dispose();

            CloseConnection();
        }
    }

    public object ExecuteScalar(string sql)
    {
        SqlCommand command = null;

        try
        {
            if (connection.State != ConnectionState.Open)
                OpenConnection();

            command = new SqlCommand(sql, connection);
            return command.ExecuteScalar();
        }
        finally
        {
            if (command != null)
                command.Dispose();

            CloseConnection();
        }
    }
}

Пример использования:

SqlHelper sql = new SqlHelper();
DataTable data = sql.ExecuteToDataTable("SELECT * FROM Customers");
int affected = sql.ExecuteNonQuery("INSERT Customers VALUES ('Test')");

Но если вы действительно хотите static (если вы находитесь в среде с одним пользователем), просто поставьте static на все методы.

person Erick Petrucelli    schedule 27.05.2011
comment
хотя я согласен с тем, что не делайте соединение статическим, ваш код по-прежнему получает известную строку соединения из web.config вместо какого-то волшебного динамического метода. - person nathan gonzalez; 27.05.2011
comment
+1 Хорошие комментарии о static, поскольку то, что спросил ОП, может вызвать много проблем, а то, что вы ответили, намного проще. - person ; 27.05.2011
comment
@Erick: Да, у меня есть похожий класс, но все статично и я использую его более чем на одном веб-сайте. Пока проблем нет. Основная причина, по которой я хочу изменить его, заключается в том, что он имеет жестко запрограммированное имя connectionStringName (которое есть и у вашего класса). - person gbs; 27.05.2011
comment
@RickV: я думаю, что опубликую новый вопрос для обсуждения проблем с использованием этого класса как статического. - person gbs; 27.05.2011
comment
@gbs: я обновил код, указав простой способ не жестко кодировать connectionStringName. Поскольку у вас не так много подключений, определенных в вашем Web.config, это работает нормально. - person Erick Petrucelli; 27.05.2011
comment
@gbs: Я согласен, было бы неплохо задать новый вопрос об этом. Но у самой концепции статики есть проблема, которую показал @ErickPetru. Я думаю, что ваш сайт не получил много запросов в ту же секунду. Но будь осторожен. - person ; 27.05.2011
comment
@gbs, последний комментарий: вы также можете подумать о создании собственного раздела конфигурации для своего класса. Но я действительно не верю, что ты много выиграешь от этого. - person Erick Petrucelli; 27.05.2011
comment
@EricK: На самом деле, у меня есть две строки подключения в моем web.config. Я сделал класс статическим, поэтому мне не нужно время от времени создавать экземпляр класса. Может быть, я ошибаюсь из-за того, что у меня меньше знаний о статическом классе. - person gbs; 27.05.2011