Перепрограммирование класса Бога
Я изо всех сил пытаюсь понять, как реализовать принципы инкапсуляция, наследование и полиморфизм разбить этот код на логические части. Мой опыт работы в VBS/Classic ASP и разработке баз данных, а не в ООП.
Я не прошу никого писать код, а скорее указать мне правильное направление. У меня есть этот класс, который, как я полагаю, я слышал, называется объектом Бога.
Мой код работает, но он неуправляем и будет только хуже.
Этот класс:
1. получает информацию об источнике данных, например путь к базе данных Access или имя сервера БД.
2. определяет, какой движок может обрабатывать файл... Access, MSSQL, MySQL...
3. Выберите поставщика для подключения к СУБД
4. установите специальные настройки БД, например, следует ли использовать одинарные или двойные кавычки вокруг имен объектов и т. д.
5. Создайте строку подключения для подключения к БД.
Нормально ли иметь все это в одном классе?
Я думал, что это будет легче понять позже, если я разделю его на несколько разных классов:
1. класс, чтобы выяснить, к какому источнику данных он будет подключаться
2. Класс В комплект дБ специальные параметры и сгенерировать строку подключения
Это звучит правильно? Если да, то не могли бы вы порекомендовать, возможно, вытащить все эти перечисления и сопровождающие их методы в отдельный статический класс, чтобы их не нужно было создавать?
using System; using System.Diagnostics; using System.IO; namespace RickRoll { public class Db { public string DataSource { get; } public string Database { get; } public string Username { get; } public string Password { get; } public string ConnectionString { get; private set; } public string ExtendedProperties { get; } private WrapColumn wrapcol; private WrapObject wrapobj; public DataProvider Provider { get; set; } public DataFileType FileType { get; private set; } public DataEngine Engine { get; private set; } public WrapColumn WrapCol { get => wrapcol; private set => wrapcol = value; } public WrapObject WrapObj { get => wrapobj; private set => wrapobj = value; } // This can't be right, but I needed to do it to get the enun values (temporary fix) public Db() { // only used for public enum access } // For file databases without password set public Db(string filepath) { FileType = GetDbFileType (filepath); Engine = GetDbEngine (FileType); Provider = GetDbProvider (FileType); DataSource = filepath; Database = Path.GetFileName (filepath); Username = null; Password = null; ConnectionString = GetConnectionString (Provider); GetWrapCol (Engine); GetWrapObj (Engine); } // For file databases with password set public Db(string filepath, string password) { FileType = GetDbFileType (filepath); Engine = GetDbEngine (FileType); Provider = GetDbProvider (FileType); DataSource = filepath; Database = Path.GetFileName (filepath); Username = null; Password = password; ConnectionString = GetConnectionString (Provider); GetWrapCol (Engine); GetWrapObj (Engine); } // For all connections that use Windows AUthentication or no password public Db(string path, string server , string database) { FileType = GetDbFileType (path); Engine = GetDbEngine (FileType); Provider = GetDbProvider (FileType); DataSource = GetDataSource (Engine , path , server); Database = database; Username = null; Password = null; ConnectionString = GetConnectionString (Provider); GetWrapCol (Engine); GetWrapObj (Engine); Debug.WriteLine (ConnectionString); Debug.WriteLine (Environment.UserName); } // For all connection types public Db(string path, string server, string database , string username , string password) { FileType = GetDbFileType (path); Engine = GetDbEngine (FileType); Provider = GetDbProvider (FileType); DataSource = GetDataSource (Engine, path, server); Database = database; Username = username; Password = password; ConnectionString = GetConnectionString (Provider); GetWrapCol (Engine); GetWrapObj (Engine); } // For instantiating from data stored in SQL Server using Windows Authentication or no password public Db(int filetype_value, string server , string database) { FileType = GetDbFileType (filetype_value); Engine = GetDbEngine (FileType); Provider = GetDbProvider (FileType); DataSource = server; Database = database; Username = null; Password = null; ConnectionString = GetConnectionString (Provider); GetWrapCol (Engine); GetWrapObj (Engine); Debug.WriteLine (ConnectionString); Debug.WriteLine (Environment.UserName); } // For instantiating from data stored in SQL Server using SQL Authentication or JET DB password public Db(int filetype_value , string server , string database , string username , string password) { FileType = GetDbFileType (filetype_value); Engine = GetDbEngine (FileType); Provider = GetDbProvider (FileType); DataSource = server; Database = database; Username = username; Password = password; ConnectionString = GetConnectionString (Provider); GetWrapCol (Engine); GetWrapObj (Engine); } /// <summary> /// This changes the database connection string to use a different Provider. /// </summary> /// <param name="p"></param> public void ReSetConnectionString(DataProvider p) { Provider = p; ConnectionString = GetConnectionString ( Provider ); } // Gets file extension from file name or an ext can be passed in place of path private DataFileType GetDbFileType(string path) { DataFileType ft; string ext; if (path.Contains (".")) { // allows for attached databases string fpath = path.Replace ("|DataDirectory|" , AppDomain.CurrentDomain.BaseDirectory); ext = Path.GetExtension (fpath).Replace ("." , "").ToUpper ( ); if (Enum.TryParse<DataFileType> (ext , out ft) == true) return ft; } else { ft = (DataFileType) Enum.Parse (typeof (DataFileType) , path.ToUpper()); /// throw exception if extention is not in DataFileType enum or is null } return ft; } // Returns the path to a file for MS Access, etc or a db server name private string GetDataSource(DataEngine e, string path, string server) { if (DataSourceIsFile (e)) return path; return server; } // I think this is unnecessary. Use this instead: DataFileType dft = (DataFileType) value; private DataFileType GetDbFileType(int value) { DataFileType ft = (DataFileType) Enum.Parse (typeof (DataFileType) , value.ToString ( )); // throws and exception if int isn't in the DataFileType enum. return ft; } // This is the db engine public enum DataEngine { None, ACCESS = 1, // 0 = not applicable or not determined MSSQL, EXCEL, ORACLE, MYSQL, TEXT } // <summary> /// These are the int enum values that get stored in the database for each type of data file /// </summary> public enum DataFileType { None, MDB = 1, // 0 = not a configured data file ACCDB, MDF, // Primary Data FIle NDF, // File Group (secondary data files) XLS, // Excel 97-2003 worksheet XLSX, // Excel 2007 workbook XLSXM, // Macro enabled workbook XLTM, // Binary worksheet (BIFF12) XLW, // Excel works space, previously known as workbook CSV, // Comma separated values TAB, // Tab separated values TSV, // Tab separated values TXT // Delimited Text file } /// <summary> /// These are the int values that get stored in the database for each db connection /// </summary> public enum DataProvider { None, Microsoft_ACE_OLEDB_12_0 = 1, // Microsoft.ACE.OLEDB.12.0 - MS OLEDB DataProvider for MDB or ACCDB or EXCEL Microsoft_ACE_OLEDB, // Microsoft.ACE.OLEDB VersionIndependentProgID Microsoft_Jet_OLEDB_4_0, // MS Access - Does not work with ACCDB or any SQL Server version Microsoft_Jet_OLEDB, // Version Independent ProgID SQLNCLI11, // SQL Server Native Client for OleDb SQLNCLI, // Version Independent ProgID SQLOLEDB_1, // SQL Server OleDb - Does not work with SQL Server Express SQLOLEDB, // VersionIndependentProgID SQL__Server__Native__Client__11_0, // SQL Server Native Client using ODbC SQL__Server__Native__Client, // Version Independent ProgID MSDASQL_1, // Microsoft OleDb Data Access Components using ODbC MSDASQL // Version Independent ProgID } // This can be simplified private DataEngine GetDbEngine(int enumvalue) { DataEngine result = (DataEngine) Enum.Parse (typeof (DataEngine) , enumvalue.ToString ( )); // throws and exception if int isn't in the DataEngine enum. return result; } // Gets the Database Engine from type of file private DataEngine GetDbEngine(DataFileType ft) { switch (ft) { case DataFileType.MDB: return DataEngine.ACCESS; case DataFileType.ACCDB: return DataEngine.ACCESS; case DataFileType.MDF: return DataEngine.MSSQL; case DataFileType.NDF: return DataEngine.MSSQL; case DataFileType.XLS: return DataEngine.EXCEL; case DataFileType.XLSX: return DataEngine.EXCEL; case DataFileType.CSV: return DataEngine.TEXT; case DataFileType.TAB: return DataEngine.TEXT; case DataFileType.TSV: return DataEngine.TEXT; case DataFileType.TXT: return DataEngine.TEXT; default: throw new ArgumentException ($"* * * DataFileType is not a supported data file format. Database DataEngine could not be determined."); } } // Returns the database provider to use for each supported file type private DataProvider GetDbProvider(DataFileType ft) { switch (ft) { case DataFileType.MDB: case DataFileType.ACCDB: return DataProvider.Microsoft_ACE_OLEDB_12_0; case DataFileType.MDF: return DataProvider.SQLNCLI11; // SQLOLEDB_1 and SQLOLEDB did not work with SQL Server Express case DataFileType.NDF: return DataProvider.SQLNCLI11; case DataFileType.XLS: case DataFileType.XLSX: case DataFileType.CSV: case DataFileType.TAB: case DataFileType.TSV: case DataFileType.TXT: return DataProvider.Microsoft_ACE_OLEDB_12_0; default: throw new ArgumentException ($"* * * DataFileType is not a supported data file format. Database DataProvider could not be determined."); } } // Sets the character used to 'wrap' column names in query strings in case they have spaces or are reserved words private void GetWrapCol(DataEngine e) { switch (e) { case DataEngine.ACCESS: WrapCol = new WrapColumn { left = "`" , middle = "`,`" , right = "`" }; break; case DataEngine.MSSQL: WrapCol = new WrapColumn { left = "[" , middle = "],[" , right = "]" }; break; case DataEngine.EXCEL: WrapCol = new WrapColumn { left = "`" , middle = "`,`" , right = "`" }; break; case DataEngine.ORACLE: WrapCol = new WrapColumn { left = @"""" , middle = @""",""" , right = @"""" }; break; case DataEngine.MYSQL: WrapCol = new WrapColumn { left = "`" , middle = "`,`" , right = "`" }; // might be brackets for sysnames and ` for columns break; case DataEngine.TEXT: WrapCol = new WrapColumn { left = @"""" , middle = @""",""" , right = @"""" }; // not sure how to handle this yet break; default: throw new ArgumentException ($"* * * DataEngine was not valid. Could not determine a COLUMN escape character."); } } // Sets the character used to 'wrap' object names (table names, schema names, etc.) in query strings in case they have spaces or are reserved words private void GetWrapObj(DataEngine e) { switch (e) { case DataEngine.ACCESS: WrapObj = new WrapObject { left = "`" , middle = "`,`" , right = "`" }; break; case DataEngine.MSSQL: WrapObj = new WrapObject { left = "[" , middle = "],[" , right = "]" }; break; case DataEngine.EXCEL: WrapObj = new WrapObject { left = "`" , middle = "`,`" , right = "`" }; break; case DataEngine.ORACLE: WrapObj = new WrapObject { left = @"""" , middle = @""",""" , right = @"""" }; break; case DataEngine.MYSQL: WrapObj = new WrapObject { left = "[" , middle = "],[" , right = "]" }; // might be brackets for sysnames and ` for columns break; case DataEngine.TEXT: WrapObj = new WrapObject { left = @"""" , middle = @""",""" , right = @"""" }; // not sure how to handle this yet break; default: throw new ArgumentException ($"* * * DataEngine was not valid. Could not determine a OBJECT escape character."); } } // Returns false for server bases DBMS and true for file based Db's like Access private bool DataSourceIsFile(DataEngine e) { switch (e) { case DataEngine.ACCESS: return true; case DataEngine.MSSQL: return false; case DataEngine.EXCEL: return true; case DataEngine.ORACLE: return false; case DataEngine.MYSQL: return false; case DataEngine.TEXT: return true; default: throw new ArgumentException ($"* * * DataEngine was not valid. Could not determine if this is a file or server DataSource."); } } // Connection strings for any of the supported Providers //https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/connection-string-syntax?view=netframework-4.7.1 private string GetConnectionString(DataProvider Provider) { string ProgId = Provider.ToString ( ).Replace ("_" , "."); string result = ""; switch (Provider) { case DataProvider.Microsoft_ACE_OLEDB_12_0: // Microsoft MS OLEDB for MDB or ACCDB or EXCEL case DataProvider.Microsoft_ACE_OLEDB: // VersionIndependentProgID return $@"Provider={ProgId};Data Source={DataSource};Persist Security Info=False;Jet OLEDB:Database Password={Password.EmptyIfNull ( )};"; case DataProvider.Microsoft_Jet_OLEDB_4_0: // MS Access Jet OLEDB - Does not work with ACCDB or any SQL Server version case DataProvider.Microsoft_Jet_OLEDB: // VersionIndependentProgID // Jet db with user-lvel security requires a Workgroup information file designation: // Jet OLEDB:System Database=|DataDirectory|\System.mdw;" <--- that could be stored in the Server field // Jet user-level security uses the User ID and Password setting. A new constructer that includes the Username field will need to be added to support this if (Engine == DataEngine.ACCESS && Password.NullIfEmpty() == null) return $@"Provider={ProgId};Data Source={DataSource};User ID=Admin;Password=;"; else return $@"Provider={ProgId};Data Source={DataSource};Persist Security Info=False;Jet OLEDB:Database Password={Password.EmptyIfNull ( )};"; case DataProvider.SQLNCLI11: // SQL Server OleDb Native Client case DataProvider.SQLNCLI: // VersionIndependentProgID result = $@"Provider={ProgId};Server={DataSource};Database={Database};"; if (Password != null) result += $"Uid={Username.EmptyIfNull ( )};Pwd={Password.EmptyIfNull ( )};"; if (Password == null) result += "Integrated Security=SSPI;"; return result; case DataProvider.SQLOLEDB_1: // Microsoft OLE DB DataProvider for SQL Server (I've seen this work for ACCESS also) - DID NOT work for SQL Server 2016 Express, but DID work for SQL Server 2016 Developer Edition case DataProvider.SQLOLEDB: // VersionIndependentProgID result = $@"Provider={ProgId};Data Source={DataSource};Initial Catalog={Database};"; if (Password != null) result += $"User Id={Username.EmptyIfNull()};Password={Password.EmptyIfNull ( )};"; if (Password == null) result += "Integrated Security=SSPI;"; return result; case DataProvider.MSDASQL_1: // Microsoft Data Access OleDb using ODbC case DataProvider.MSDASQL: // VersionIndependentProgID if (Engine == DataEngine.ACCESS) return $@"Driver={{Microsoft Access Driver (*.mdb, *.accdb)}};DbQ={DataSource}"; if (Engine == DataEngine.EXCEL) return $@"Driver={{Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)}};DbQ={DataSource}"; if (Engine == DataEngine.MSSQL) if (string.IsNullOrEmpty (Username)) return $@"[DataProvider = MSDASQL;] DRIVER={{SQL Server}}; SERVER={DataSource}; DATABASE={Database};UID=;Integrated Security=SSPI"; else return $@"[DataProvider = MSDASQL;] DRIVER={{SQL Server}}; SERVER={DataSource}; DATABASE={Database};UID={Username};PWD={Password}"; else throw new ArgumentException ($" * * * The MSDASQL Provider has only been set up for MS Access or Excel or MSSQL connections. Could not create Connection String"); case DataProvider.SQL__Server__Native__Client__11_0: // SQl Server OleDb using ODbC if (Engine == DataEngine.MSSQL && (Username.NullIfEmpty ( ) != null)) return $@"Driver={{{ProgId}}};Server={DataSource};Database={Database};Uid={Username};Pwd={Password};"; else return $@"Driver={{{ProgId}}};Server={DataSource};Database={Database};Trusted_Connection=yes;"; default: throw new ArgumentException ($" * * * DataProvider is not valid. Could not create Connection String"); } } public string Columns(string value) { string[ ] c = value.Split (','); // if col names have "," in them, maybe replace it with another char and modify this method to replace it back. return $"{WrapCol.left}{string.Join(WrapCol.middle , c)}{WrapCol.right}"; } } public static class StringExt { public static string NullIfEmpty(this string value) { return string.IsNullOrEmpty (value) ? null : value; } public static string EmptyIfNull(this string value) { if (string.IsNullOrEmpty (value)) return string.Empty; else return value; } } }
Что я уже пробовал:
Мой класс работает так, как задумано, но вся программа заключена в этом и двух других классах. Я чувствую, что мог бы написать базовый класс, а затем иметь некоторые производные классы, но я никогда не делал этого раньше, и я недостаточно знаком с механизмом, с помощью которого классы разговаривают друг с другом, чтобы определить это.
#realJSOP
Я работаю разработчиком уже почти 40 лет и никогда не слышал термина "класс Бога". Может быть, я просто жила уединенной жизнью...
[no name]
Я узнал об этом всего пару дней назад. Я видел объект Бога чаще, чем класс Бога, но это имеет смысл. Это класс, который нарушает принцип единой ответственности, потому что он делает все.