Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Базы данных под .NET > Статический обьект SqlConnection


Автор: thomas 17.5.2009, 14:56
Приветствую всех.

Вот столкнулся с непонятной проблемой.
Пишу приложение работающее с БД на MS SQL server. Для описания объектов использую статические классы, которые содержат статические методы для чтения/записи данных в БД.
Поначалу каждый раз создавал новое соединение с БД. Статической использовал строку соединения. 
Код

using System;
using System.Collections.Generic;
using System.Text;

namespace BusinessLayer
{
    public class Common
    {
        public static readonly string connectionString = @"Data Source=LPC;Initial Catalog=VKL;integrated security=true;";
    }
}

// использование в коде класса описывающего объект Категория 
public static List<Categorie> GetEntityList()
                {
                    List<Categorie> categorienLijst = new List<Categorie>();
                    using (SqlConnection con = new SqlConnection(Common.connectionString))
                    {
                        string proc = "usp_Categorie_GetFullList";
                        using (SqlCommand cmd = new SqlCommand(proc, con))
                        {
                            cmd.CommandType = CommandType.StoredProcedure;
                            con.Open();
                            SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
                            while (rdr.Read())
                            {
                                Categorie _categorie = FillCategorie(rdr);
                                categorienLijst.Add(_categorie);
                            }
                            if (!rdr.IsClosed)
                                rdr.Close();
                        }
                    }
                    return categorienLijst;
                }

Потом подумал что это не совсем правильно. Нужно один раз создать соединение и потом его использовать по мере необходимости (открывать/закрывать). Решил сделать не строку подключения статической, а весь объект соединения статическим.
В умных книжках написано что C# гарантированно вызовет статический конструктор перед первым использование поля, свойства или метода статического класса. И потом можно постоянно его(поле например) использовать в других классах.
Я попробовал создать статический объект типа SqlConnection.
Сначала делал только статическое поле, а потом добавил статический конструктор и далее еще и метод. 
Код

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Text;

namespace BusinessLayer
{
    public class Verbinding
    {        
        public static readonly SqlConnection connection;// = new SqlConnection("Data Source=LPC;Initial Catalog=VKL;integrated security=true");                    

        static Verbinding()
        {
            connection = GetConnection();
        }

        private static SqlConnection GetConnection()
        {
            string conStr = "Data Source=LPC;Initial Catalog=VKL;integrated security=true";
            SqlConnection con = new SqlConnection();
            con.ConnectionString = conStr;
            return con;
        }
    }
}

// использование статчиеского соединения в методе класса, описывающего объект Artikel 
public static List<Artikel> GetEntityList()
                {
                    List<Artikel> artikelenLijst = new List<Artikel>();                    
                    string sp = "usp_Artikel_GetList";
                    using (SqlCommand cmd = new SqlCommand(sp, Verbinding.connection))
                    {
                        cmd.CommandType = CommandType.StoredProcedure;
                        Verbinding.connection.Open();                           
                        SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
                        while (rdr.Read())
                        {
                            Artikel _artikel = FillArtikel(rdr);
                            artikelenLijst.Add(_artikel);
                        }
                        if (!rdr.IsClosed)
                        {
                            rdr.Close();
                            Verbinding.connection.Close();
                        }
                    }                    
                    return artikelenLijst;
                }

 Перед первым использованием он создается нормально. Все хорошо.
Но вот когда надо второй раз использовать это соединение, оно теряет свое свойство connectionString.
И вот в чем тут проблема мне никак не понять. Изменения вносимые к класс Verbinding к решению проблемы не привели.
В принципе все осталось тем же самым, а запись увеличилась.

Прошу подсказать или объяснить в чем я не прав.   И как это исправить?
Заранее спасибо.

Автор: Unlocker 17.5.2009, 15:28
А почему нельзя создать класс-singleton?
Код

public class ModifiedConn
{
// Закрытое поле целевого соединения.
private static SqlConnection mInstance = new SqlConnection();
// Конструктор закрыть, иначе синглетон будет плохо работать.
protected ModifiedConn()
{
mInstance.ConnectionString = conString; // conString - заданная наперед константа
}
// Открытое поле для обращения к соединению.
public static SqlConnection Instance
{
get
}
return mInstance;
}
}
}

Смысл всего этого шаманства в том, что при первом обращении к классу объект-одиночка инициализируется, а затем к нему можно обращаться из любого места программы, не боясь ощутить потерю параметров инициализации.
PS: мог где-то накосячить в синтаксисе, но без подстветки сложно набирать. Если интересно узнать про синглетон, то милости прошу открыть ставший уже классическим труд "банды четырех". Удачи  smile 

Автор: thomas 17.5.2009, 15:58
Unlocker, 
Приветствую.
Сделал как ты посоветовал. Через свойство.
Код

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Text;

namespace BusinessLayer
{
    public class Verbinding
    {        
        private static SqlConnection connection = new SqlConnection();                    

        static Verbinding()
        {
            connection.ConnectionString = "Data Source=LPC;Initial Catalog=VKL;integrated security=true";
        }

        public static SqlConnection Connection
        {
            get { return connection; }
        }
    }
}

Но результат тот же.
При первом использовании соединение создается, данные из БД считываются и отображаются на экране.
Но при попытке сохранить изменения после редактирования данных или новой строки вылетает ошибка об отсутствии инициализации свойства connectionString для соединения.
И действительно если использовать брейкпойнты то видно в первом случае свойство присвоено как и написано в конструкторе, а во втором случае оно пустое.

Код

// вот тут все срабатывает нормально - ЭТО самый ПЕРВЫЙ РАЗ
 public static List<Artikel> GetEntityList()
                {
                    List<Artikel> artikelenLijst = new List<Artikel>();                    
                    string sp = "usp_Artikel_GetList";
                    using (SqlCommand cmd = new SqlCommand(sp, Verbinding.Connection))
                    {
                        cmd.CommandType = CommandType.StoredProcedure;
                        Verbinding.Connection.Open();                           
                        SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
                        while (rdr.Read())
                        {
                            Artikel _artikel = FillArtikel(rdr);
                            artikelenLijst.Add(_artikel);
                        }
                        if (!rdr.IsClosed)
                        {
                            rdr.Close();
                            Verbinding.Connection.Close();
                        }
                    }                    
                    return artikelenLijst;
                }

// А вот тут нет - ЭТО ПОВТОРНЫЙ случай использования объекта Verbinding.Connection
 private static Artikel AddEntity(Artikel _artikel)
                {
                    int artikelId = 0;
                    
                    string sp = "usp_Artikel_Add";
                    using (SqlCommand cmd = new SqlCommand(sp, Verbinding.Connection))
                    {
                        cmd.CommandType = CommandType.StoredProcedure;
                        cmd.Parameters.AddWithValue("@subCategorieId", _artikel.SubCategorieId);
                        cmd.Parameters.AddWithValue("@artikelNaam", _artikel.ArtikelNaam);
                        cmd.Parameters.AddWithValue("@omschrijving", GetValue(_artikel.Omschrijving));
                        cmd.Parameters.AddWithValue("@eenheid", _artikel.Eenheid);
                        cmd.Parameters.AddWithValue("@minvoorraad", GetValue(_artikel.MinVoorraad));
                        cmd.Parameters.AddWithValue("@voorraad", _artikel.Voorraad);
                        cmd.Parameters.AddWithValue("@winst", _artikel.Winst);
                        cmd.Parameters.AddWithValue("@aankoopPrijs", _artikel.AankoopPrijs);
                        cmd.Parameters.Add(new SqlParameter("@artikelId", SqlDbType.Int));
                        cmd.Parameters["@artikelId"].Direction = ParameterDirection.Output;

                        Verbinding.Connection.Open(); // ВОТ ТУТ ВЫСКАКИВАЕТ ИСКЛЮЧЕНИЕ.

                        cmd.ExecuteNonQuery();
                        artikelId = Convert.ToInt32(cmd.Parameters["@artikelId"].Value);
                    }                    
                    _artikel = GetEntity(artikelId);
                    return _artikel;
                }


Вопрос, куда девается присвоенное ранее значение и почему это происходит? 

Автор: Idsa 17.5.2009, 16:11
thomas, видимо, где-то этому свойству присваивается значение пустой строки (в ходе или после выполнения первого запроса). Само по себе статическое свойство изменяться не может.

Автор: thomas 17.5.2009, 16:15
Unlocker, 
Даже если в классе Verbinding написать только одно статическое поле
Код

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Text;
namespace BusinessLayer
{
    public class Verbinding
    {        
        public static SqlConnection connection = new SqlConnection("Data Source=LPC;Initial Catalog=VKL;integrated security=true");                    
    }
}

то один раз срабатывает на УРА, а при повторном использовании вылетает исключение. Куда то девается значение свойства ConnectionString у connection.
Хотя аналогичную конструкцию я использовал и в другом проекте и там все было чики чики.

Добавлено через 3 минуты и 9 секунд
Idsa, 
Привет.
Да уж интересно где и почему это происходит.
Ведь по идее один раз созданный статический объект меняться не должен. Сам я его ни где не меняю.
Мне надо только в нужный момент подсунуть его для создания команды и открыть/закрыть при её выполнении.
И откуда, блин, растут ноги?  smile 

Автор: Idsa 17.5.2009, 16:20
thomas, попробуй внимательно пройтись дебаггером и все-таки определить место, где меняется значение строки соединения.

Автор: thomas 17.5.2009, 17:25
Idsa, 
Не найти.
При последнем закрытии перед отрисовкой формы все нормально.
После добавления нового элемента и попытке его сохранить
в объекте соединение свойство утеряно.
 smile 

Автор: Idsa 17.5.2009, 17:39
thomas, можно попробовать закачать исходники .NET и поставить breakpoint на setter свойства ConnectionString.

Автор: Unlocker 17.5.2009, 18:28
Вопрос реально интересный. При работе с ADO я делал DataSet приложения синглетоном: было очень удобно.
Кстати, можно попробовать в геттере SqlConnection проставить инициализацию строкой подключения.
Попробуй так:
Код

public class ModifiedConn
{
// Закрытое поле целевого соединения.
private static SqlConnection mInstance = new SqlConnection();
// Конструктор закрыть, иначе синглетон будет плохо работать.
protected ModifiedConn()
{
// Конструктор оставим пустым. :) 
// mInstance.ConnectionString = conString; // conString - заданная наперед константа
}
// Открытое поле для обращения к соединению.
public static SqlConnection Instance
{
get
}
// А вот здесь зададим строку подключения.
mInstance.ConnectionString = conString;
return mInstance;
}
}
}

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

Автор: thomas 17.5.2009, 19:56
Idsa, 
Unlocker, 

Из той же оперы вопрос.
Я тут обнаружил что у меня по ходу пьесы одно соединение должно использоваться для разных команд одновременно.
Объясняю на пальцах.
Есть группа товаров, в ней различные категории, в категориях субкатегории , а в них уже товары.
Так вот считываю данные на группу из БД, использую для этого СП и ДатаРидер. Соответственно соединение открыто.
А группа то содержит категории, значиться для свойства категории объекта группа вызывается метод  из класса категория для считывания данных из БД.
И в этом методе должно использоваться тоже соединение, которое уже использует метод класса группа, но с другим объектом SqlCommand и StoredProcedure.
Да еще оно(соединение) открыто. А тут по новой открывать. Экскпшен.

Вопросец, а можно ли одно соединение использовать ОДНОВРЕМЕННО для разных объектов типа SqlCommand на основе разных StoredProcedure?




Автор: Unlocker 17.5.2009, 22:22
thomas, ты правда не ответил по предыдущему вопросу. Работает ли класс таким свойством?
А по второму вопросу: тут надо поэкспериментировать. Мне кажется, что один коннект можно использовать для разных команд. А вот открывать его по второму разу, как было замечено выше - вредно, т.к. исключение вылезает  smile 

Автор: Idsa 18.5.2009, 07:07
Цитата(Unlocker @  18.5.2009,  02:22 Найти цитируемый пост)
Мне кажется, что один коннект можно использовать для разных команд.

Тут все зависит от базы. thomas, начиная с SQL Server 2005, появился режим MARS (http://msdn.microsoft.com/en-us/library/ms345109(SQL.90).aspx, http://msdn.microsoft.com/en-us/library/ms131686.aspx). Инициализируется этот режим через строку соединения указанием "MultipleActiveResultSets=true;".

Цитата(Unlocker @  18.5.2009,  02:22 Найти цитируемый пост)
А вот открывать его по второму разу, как было замечено выше - вредно, т.к. исключение вылезает  smile  

Да ладно. Не проверял, но мне кажется логичным, чтобы при открытом соединении вызов Open не влек за собой ровным счетом ничего.

thomas, есть какие-нибудь успехи с дебагом на уровне исходников .NET?

Автор: PashaPash 18.5.2009, 14:09
Цитата(thomas @  17.5.2009,  14:56 Найти цитируемый пост)
Потом подумал что это не совсем правильно. Нужно один раз создать соединение и потом его использовать по мере необходимости (открывать/закрывать). Решил сделать не строку подключения статической, а весь объект соединения статическим.

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

Добавлено через 3 минуты и 26 секунд
Цитата(thomas @  17.5.2009,  19:56 Найти цитируемый пост)
Я тут обнаружил что у меня по ходу пьесы одно соединение должно использоваться для разных команд одновременно.
Объясняю на пальцах.
Есть группа товаров, в ней различные категории, в категориях субкатегории , а в них уже товары.
Так вот считываю данные на группу из БД, использую для этого СП и ДатаРидер. Соответственно соединение открыто.
А группа то содержит категории, значиться для свойства категории объекта группа вызывается метод  из класса категория для считывания данных из БД.
И в этом методе должно использоваться тоже соединение, которое уже использует метод класса группа, но с другим объектом SqlCommand и StoredProcedure.
Да еще оно(соединение) открыто. А тут по новой открывать. Экскпшен.

Создаешь SqlConnection, открываешь, выбираешь группы, закрываешь.
Создаешь SqlConnection, открываешь, выбираешь категории, закрываешь.
Чем плохо?

Автор: Idsa 18.5.2009, 14:13
Цитата(PashaPash @  18.5.2009,  18:09 Найти цитируемый пост)
Правильно - создавать SqlConnection, открывать и закрывать для каждой операции, а не держать один синглтон. B противном случае поимеешь вот как раз те проблемы, что в предыдущих постах описаны.

О каких проблемах ты говоришь? О том, что "обнуляется" свойство ConnectionString? Ты думаешь, эта логика прошита в каком-то из методов класса SqlConnection?

Автор: PashaPash 18.5.2009, 14:24
Цитата(Idsa @  18.5.2009,  14:13 Найти цитируемый пост)
О каких проблемах ты говоришь? О том, что "обнуляется" свойство ConnectionString? Ты думаешь, эта логика прошита в каком-то из методов класса SqlConnection? 

Хз, где она там прошита, но я точно знаю что для SqlConnection Close and Dispose are functionally equivalent. Странно ожидать нормальной работы от диспоузнотого объекта.

И, кстати, нет никакого смысла открывать надолго SqlConnection, в надежде получить бонус по скорости - это наоборот перечеркивает все фишки от pooling-a соединений. Там внутри все и так статическое.

Автор: Idsa 18.5.2009, 15:06
Цитата(PashaPash @  18.5.2009,  18:24 Найти цитируемый пост)
Странно ожидать нормальной работы от диспоузнотого объекта.

Судя по тому коду, который предоставил thomas, connection не диспозится, а просто открывается/закрывается.

Цитата(PashaPash @  18.5.2009,  18:24 Найти цитируемый пост)
И, кстати, нет никакого смысла открывать надолго SqlConnection

Так connection подолгу и не держится. Опять же, сужу по вышеприведенному коду: там соединение открывается до выполнения операции и закрывается сразу после завершения ее выполнения.

А вообще интересно, что побудило thomas отойти от традиционного подхода с созданием SqlConnection через using при каждом запросе...

Я тут насчет MARS подумал... пожалуй, не будет оно так работать. С открытием подключения и выполнением запроса все будет в порядке, но как только один из потоков вызовет Connection.Close, оставшиеся задачи выполнены не будут. MARS больше подходит для ситуаций, когда нужно в одном месте выполнять несколько запросов через одно соединение.

Автор: PashaPash 18.5.2009, 15:25
Цитата(Idsa @  18.5.2009,  15:06 Найти цитируемый пост)

Судя по тому коду, который предоставил thomas, connection не диспозится, а просто открывается/закрывается.

А судя по MSDN - вызов Close эквивалентен вызову Dispose.
Цитата(Idsa @  18.5.2009,  15:06 Найти цитируемый пост)

А вообще интересно, что побудило thomas отойти от традиционного подхода с созданием SqlConnection через using при каждом запросе...

Жажда приключений? smile

Автор: Idsa 19.5.2009, 05:55
Цитата(PashaPash @  18.5.2009,  19:25 Найти цитируемый пост)
А судя по MSDN - вызов Close эквивалентен вызову Dispose.

Это да...

Я тут поигрался немного... ну никак мне не удается добиться того, чтобы само по себе "обнулялось" свойство ConnectionString.

Цитата(PashaPash @  18.5.2009,  19:25 Найти цитируемый пост)
Жажда приключений? smile

Вероятно, по этой же причине thomas игнорирует ORM smile

Автор: thomas 21.5.2009, 11:24
Приветствую всех.
Была маленькая запара на сельскохозяйственной ниве, потому не мог выйти в сеть.
Всем спасибо за обсуждение моей проблемы.


Unlocker,
Цитата

Работает ли класс таким свойством?
 У меня не сработал. Но почему я так и не нашел. Обнулялась строка соединения. Хотя все вроде бы в соответствии с описаниями C#.
Idsa, 
Цитата

есть какие-нибудь успехи с дебагом на уровне исходников .NET?
Ответ выше.

PashaPash, 
Цитата

Правильно - создавать SqlConnection, открывать и закрывать для каждой операции, а не держать один синглтон.

Idsa, 
Цитата

А вообще интересно, что побудило thomas отойти от традиционного подхода с созданием SqlConnection через using при каждом запросе...

Отвечаю сразу обоим. По мнению моего руководителя проекта, создание каждый раз объекта соединения с БД слишком накладная операция, а потому нужно иметь один раз созданный объект соединения  и использовать его по мере необходимости. Возможно он и прав. Судите сами. Вот у меня только для того чтобы отобразить дерево Группа-Категории-Субкатегории в treeView и товары субкатегории в DataGridView нужно создать четыре соединения и сделать выборку данных. А если пользователей у программы не один, а с десяток. И далее для каждого действия с данными каждый раз создавать соединение по новой. Картина получается не вполне нормальная. Программа только и делает что  создает соединения с БД. А если это все происходит не на локальном сервере в локальной сети, а через интернет?

Так что PashaPash, правильно ли создавать SqlConnection и открывать/закрывать для каждой операции?

Пока что я вернулся к своему первоначальному варианту, т.е. для каждой операции создание своего соединения.

На следующей неделе буду общаться с преподом с глазу на глаз и спрошу про 
Цитата

А судя по MSDN - вызов Close эквивалентен вызову Dispose.

Интересно что он скажет?

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

Так что не все так однозначно. Похоже нужно глубже изучать этот вопрос и искать золотую середину.

Добавлено через 2 минуты и 43 секунды
Idsa, 
Цитата

Вероятно, по этой же причине thomas игнорирует ORM
а вот тут поподробней.  smile 

Автор: Idsa 21.5.2009, 11:28
Цитата(thomas @  21.5.2009,  15:24 Найти цитируемый пост)
Отвечаю сразу обоим. По мнению моего руководителя проекта, создание каждый раз объекта соединения с БД слишком накладная операция, а потому нужно иметь один раз созданный объект соединения  и использовать его по мере необходимости. Возможно он и прав. Судите сами. Вот у меня только для того чтобы отобразить дерево Группа-Категории-Субкатегории в treeView и товары субкатегории в DataGridView нужно создать четыре соединения и сделать выборку данных. А если пользователей у программы не один, а с десяток. И далее для каждого действия с данными каждый раз создавать соединение по новой. Картина получается не вполне нормальная. Программа только и делает что  создает соединения с БД. А если это все происходит не на локальном сервере в локальной сети, а через интернет?

Программа лишь создает экземпляры класса SqlConnection, сами физические соединения поддерживаются connection pool'ом и не разрываются при каждом вызове SqlConnection.Close.

Цитата(thomas @  21.5.2009,  15:24 Найти цитируемый пост)
Так что не все так однозначно. Похоже нужно глубже изучать этот вопрос и искать золотую середину. 

Так ее давно уже нашли. И имя ей - Connection Pool.

Автор: PashaPash 21.5.2009, 13:02
thomas, вот линк с подробностями: http://msdn.microsoft.com/ru-ru/library/8xx3tyca.aspx

Автор: Idsa 21.5.2009, 13:05
Цитата(thomas @  21.5.2009,  15:24 Найти цитируемый пост)
а вот тут поподробней.

Насколько я понял, ты реализуешь банальный DAL с CRUD-операциями. Все правильно? Если так, то для этих целей гораздо удобнее использовать ORM.

Автор: thomas 21.5.2009, 16:24
Idsa, 
ORM - wat is dat?  smile 

Автор: Unlocker 21.5.2009, 16:37
Цитата(thomas @  21.5.2009,  17:24 Найти цитируемый пост)
ORM - wat is dat? 

Присоединяюсь к вопросу и прошу разъяснений.

Автор: Idsa 21.5.2009, 16:46
http://ru.wikipedia.org/wiki/ORM
Реализации под .NET: Entity Framework, NHibernate, LINQ To SQL...

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)