none
c# wpf mvvm. Базы данных. Шаблоны проектирования. Наследование классов RRS feed

  • Вопрос

  • Здравствуйте! 

    Есть несколько баз данных, которые на разных провайдерах, но все данные в них в одной предметной области. Задействованы: Access, MySql, SqlCompact, MS SQL. Из каких-то БД надо брать данные, анализировать и писать что-то в другие БД. 

    На данный момент момент реализация классов по работе с базами данных такова: для каждого провайдера данных (SqlCompact, MySql и т.д.) написан свой класс (DbSqlCe, DbMySql и т.д). В каждом классе есть жесткое соединение с БД, методы по работе с сущностями (таблицами) базы (например, GetUsers(), IsFaceExist(string Name)). Соответственно все это реализовано без наследования, шаблонов и т.п. Смотрится монструозно. Поддержка этого кода затруднена (постоянно добавляется "левый" код, если возникает необходимость нового функционала).  Еще одна проблема из-за которой растет код - это методы выборки данных из таблиц. Например, тот же самый метод GetUsers(). У меня он выглядит примерно так:

    public List<User> GetUsers()
    {
    List<User> result = new List<User>(); 
        try
        {
             
            SqlCommand command = new SqlCommand(
              "SELECT Name, LastName FROM Users;",
              connection);
            connection.Open();
    
            SqlDataReader reader = command.ExecuteReader();
    
            if (reader.HasRows)
            {
                while (reader.Read())
                {
                    result.Add(new User
                    { 
                         Name = reader..., 
                         LaseName = reader..                
                    })
                }
                 reader.Close();
                 return result;
            }
            catch(SqlException ex)
            {
                // Обработка исключения
            }
        }
    }

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

    Я хотел создать метод, который бы просто выполнял передаваемый в него Sql-запрос и возвращал DataReader

    SqlDataReader ExecCommand (string query)
    {
            SqlCommand command = new SqlCommand(query);
            connection.Open();
    
            SqlDataReader reader = command.ExecuteReader();
            reader.Close();
            return reader;
    }

    Но в методах обработчиках (которые бы создавали объекты по данным из DataReader'а), возникает ошибка - нельзя работать с закрытым DataReader. Разделение на ExecCommand и GetUsersFromReader не получается. Можно было бы частично избежать клонов кода, хотя бы при исполнении запроса. Как же быть? Как добавить элегантности коду? 

    Потом пробовал перевести Db-классы на наследование. Выделить общие поля, методы, вынести в общий класс предок, но что-то пока толком не выходит. Примерно так:

    /// <summary>
        /// Абстрактный класс, описывающий базу данных
        /// </summary>
        public abstract class Database
        {
            /// <summary>
            /// Строка соединения с базой данных
            /// </summary>
            public string ConnectionString { get; set; }
    
            /// <summary>
            /// Абстрактный метод. Должен быть переопределен в классе-потомке. Создает строку соединения
            /// </summary>
            /// <returns>Возвращает строку соединения</returns>
            public abstract string CreateConnnection();
        }
    
        public class OleDb : Database // Для подключения к Access
        {
    
            public string FilePath { get; set; }
           
            public OleDb(string filePath)
            {
                FilePath = filePath;
                ConnectionString = CreateConnnection();
            }
    
            public override string CreateConnnection()
            {
                OleDbConnectionStringBuilder builder = new OleDbConnectionStringBuilder();
                builder.Provider = "Microsoft.Jet.OLEDB.4.0";
                builder.DataSource = FilePath;
               
                return builder.ConnectionString;
            }
    
            // ... РЕАЛИЗАЦИЯ МЕТОДОВ ПО РАБОТЕ С СУЩНОСТЯМИ БД
        }
    
        public class SqlCe : Database // Для подключения к SqlCompact 3.5
        {
            public string FilePath { get; set; }
            public string Password { get; set; }
    
            public SqlCe(string filePath, string password)
            {
                ConnectionString = CreateConnnection();
            }
    
            public override string CreateConnnection()
            {
                return string.Format("Data Source={0}; Password={1}", FilePath, Password);
            }
    
            // ... РЕАЛИЗАЦИЯ МЕТОДОВ ПО РАБОТЕ С СУЩНОСТЯМИ БД
        }
    
        public class MSSQL : Database // Для подключения к Sql Server 2008
        {
            public string Host { get; set; }
    
            public string Database { get; set; }
    
            public string User { get; set; }
    
            public string Password { get; set; }
    
            public MSSQL(string host, string database, string user, string password)
            {
                Host = host;
                Database = database;
                User = user;
                Password = password;
                ConnectionString = CreateConnnection();
            }
    
            public override string CreateConnnection()
            {
                SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
                builder.DataSource = Host;
                builder.InitialCatalog = Database;
                builder.UserID = User;
                builder.Password = Password;
    
                return builder.ConnectionString;
            }
    
            // ... РЕАЛИЗАЦИЯ МЕТОДОВ ПО РАБОТЕ С СУЩНОСТЯМИ БД
        }
    
        public class MySql : Database // Для подключения к MySql Server
        {
            public string Host { get; set; }
    
            public string Database { get; set; }
    
            public string User { get; set; }
    
            public string Password { get; set; }
    
            public MySql(string host, string database, string user, string password)
            {
                Host = host;
                Database = database;
                User = user;
                Password = password;
                ConnectionString = CreateConnnection();
            }
    
            public override string CreateConnnection()
            {
                MySqlConnectionStringBuilder builder = new MySqlConnectionStringBuilder();
                builder.Server = Host;
                builder.Database = Database;
                builder.UserID = User;
                builder.Password = Password;
    
                return builder.ConnectionString;
            }
    
            // ... РЕАЛИЗАЦИЯ МЕТОДОВ ПО РАБОТЕ С СУЩНОСТЯМИ БД
        }

    2 класса (OleDb, SqlCompact) - работают с файлом и нужен путь к нему, а SqlCompact - дополнительно нужен пароль. Другие 2 класса работают с серверами (нужны данные пользователь, пароль, хост, имя бд). Т.е. однозначного общего кроме DataSource, наличия строки соединения ничего нет. Может стоит завести каждой паре свой базовый класс? Вторая проблема - это конструкторы классов. Я туда передаю параметры для создания соединения (хост, имя, пароль и т.д.). Начинается гемморой с CreateConnection, который абстрактный и не может иметь разное число параметров. Вернее может, но в каждом классе-потомке надо тогда реализовывать все прототипы, что неверно. В итоге пришлось сделать  CreateConnection без параметров. Не знаю, верно ли.. Иначе не знаю как выкрутиться.

    Вот, честно - в шаблонах проектирования практике нет совсем. Что мне подходит Repository, DataMapper? Смотрю объекты многие через Factory порождают. Я пробовал такой подход:

     public class DatabaseFactrory
        {
            public static Database CreateDataAccessLayer(DatabaseType type)
            {
                Database database = null;
    
                switch (type)
                {
                    case DatabaseType.OleDb:
                        database = new OleDb(); // Неверный конструктор. 0 параметров
                        break;
                    case DatabaseType.MSSQL:
                        database = new MSSQL(); // Неверный конструктор. 0 параметров
                        break;
                    case DatabaseType.MySql:
                        database = new MySql(); // Неверный конструктор. 0 параметров
                        break;
                    case DatabaseType.SqlCe:
                        database = new SqlCe(); // Неверный конструктор. 0 параметров
                        break;
                    default:
                        break;
                }
                return database;
            }
        }
    
        public enum DatabaseType
        {
            OleDb,
            MSSQL,
            MySql,
            SqlCe,
            Odbs
        }

    В MainViewModel:

     public MainVM()
            {
                Database dbUsers = new DatabaseFactrory.CreateDataAccessLayer(DatabaseType.OleDb);
    }

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

    Наставьте на путь истинный. Как избавится от этих Db-классов, как их упростить.

    Спасибо!


    • Изменено kremlinbot 23 июля 2017 г. 21:30
    23 июля 2017 г. 21:26

Ответы

  • Подход нормальный. В любом случае нельзя написать так, чтобы не вносить изменения в код при изменениях в предметной области, не надо бросаться переделывать только из-за этого. Если проблема в постоянно изменяющихся наборах полей, вместо List в методах Get... используйте DataTable. 
    • Помечено в качестве ответа kremlinbot 30 июля 2017 г. 20:17
    24 июля 2017 г. 3:02

Все ответы

  • Подход нормальный. В любом случае нельзя написать так, чтобы не вносить изменения в код при изменениях в предметной области, не надо бросаться переделывать только из-за этого. Если проблема в постоянно изменяющихся наборах полей, вместо List в методах Get... используйте DataTable. 
    • Помечено в качестве ответа kremlinbot 30 июля 2017 г. 20:17
    24 июля 2017 г. 3:02
  • Вот еще вопрос вдогонку, про разделение получения DataReader и перегонки его полей в коллекцию. Я приводил пример выше, в котором закрывался DataReader и невозможно было потом пройтись по нему, т.к. он закрыт.

    SqlDataReader ExecCommand (string query)
    {
            SqlCommand command = new SqlCommand(query);
            connection.Open();
    
            SqlDataReader reader = command.ExecuteReader();
            reader.Close();
            return reader;
    }

    А что если его не закрывать? )) И обрабатывать так примерно:

    public List<User> AnalizeDataReaderForUsers(SqlDataReader reader)
    {
        List<User> result = new List<User>(); 
        try
        {
            if (reader.HasRows)
            {
                while (reader.Read())
                {
                    result.Add(new User
                    { 
                         Name = reader..., 
                         LaseName = reader..                
                    })
                }
                 reader.Close();
                 return result;
            }
            catch(SqlException ex)
            {
                // Обработка исключения
            }
        }
    }
    Не слишком ли корявый подход? даже не тестил, работает ли, пишу уже с телефона. Единственная проблема - будет куча анализаторов dataReader для каждой сущности. Это уже меньшее будет зло, т.к. часть кода по выполнению команды не клонируется и используется один блок кода

    24 июля 2017 г. 6:32
  •  Если проблема в постоянно изменяющихся наборах полей, вместо List в методах Get... используйте DataTable. 
    Точно, как вариант! Только у меня WPF MVVM Binding и используются ObservableCollection, соответственно придется перегонять DataTable -> ObservableCollection. 
    • Изменено kremlinbot 24 июля 2017 г. 6:36
    24 июля 2017 г. 6:33