8
\$\begingroup\$

A C# WPF user interface has been developed for the Book Inventory MySQL database previously shown in this question. Since the database had already been developed this was a database first implementation.

I did find that additional stored procedures were necessary to implement the Add Book dialog, due to the table normalization strategy used in the design of the database I was unable to utilize the stored procedures of adding a book to the library or buying a book. This question is specifically about the database interaction model.

Why didn’t I use the Entity Framework?

  • At the time I started creating the models I didn’t know the entity framework could use stored procedures. I learned this after half the data table models were created.
  • I looked through the code generated for the entity framework and didn’t see how I could implement the early error checking I wanted to perform. I really didn’t want to catch database errors for every possible problem to perform error checking.

Questions

  • Was inheritance abused or over used?
  • Is this a SOLID OOP design?
  • Are there any odors?
  • Were C# parameters used correctly?
  • Are there any possible performance issues?
  • Are the methods for Dictionaries or Lists that I didn’t use that would have reduced the amount of code or simplified the code?

The entire code for this project can be found on GitHub, including a newer version of the SQL files that create the database.

Select Author control.

Figure 1 Select Author control.

The Select Author control, the Select Author Series List Box, each button in the more options group and each list box on the Add Book dialog all represent tables in the database. The values of each list box are stored as foreign keys in some of the tables.

Add Book Dialog

Figure 2 Add Book Dialog

The architecture of the application is divided into models and views to separate the data and business model from the user interface. Within the models there is an additional division, there are models that represent the database tables and there are models that represent a row of data within each database table. A specialized super class was developed for the tables that contain the data for the list boxes in the Add Book dialog except for the Select Author List Box and the Select Author Series List Box, these are called dictionary table models as a class.

The database table models provide the actual interface to the database. In addition to calling stored procedures to store and retrieve the data they provide data about each table and stored procedure to the row data models for early error checking and validation. The database table models always reside in memory, but the data from the database is retrieved as necessary.

The database row data models provide storage of data until the data is inserted and perform error checking on the data as it is added to the row data model and prior to the insertion into the database. The base class for each database row data model is the DataTableItemBaseModel class.

The CDataTableModel is the base class for all database table classes. It contains aggregations of the DbColumnParameterData and SqlCmdParameter classes. The purpose of the DbColumnParameterData class is to provide necessary information for each column in a database table. The purpose of the SqlCmdParameter class is to provide information about every parameter in a stored procedure call. The three dictionaries in each CDataTableModel provide quick look up for the SqlCmdParameter aggregations based on 3 different naming schemes, the common name within the application, the column name in the database table and the parameter name in the stored procedure. This file contains almost all the calls to stored procedure. Each class that inherits from this class defines the names of the tables and the stored procedures. This allows for generic SQL database calls.

Class diagram for CDataTableModel, DbColumnParameterData and SqlCmdParameter

Figure 3 Class diagram for CDataTableModel, DbColumnParameterData and SqlCmdParameter

Each instance of a database table row model references its data table model to acquire the DbColumnParameterData and the SqlCmdParameter for error checking and validation purposes.

The CDataTableModel and the DataTableItemBaseModel both aggregate the SqlCmdParameter class

Figure 4 The CDataTableModel and the DataTableItemBaseModel both aggregate the SqlCmdParameter class

Each public parameter in a DataTableItemBaseModel super class references an instance of the SqlCmdParameter class.

All of the super classes of the DataTableItemBaseModel currently in use

Figure 5 All of the super classes of the DataTableItemBaseModel currently in use

All of the Data Table Models and Inheritance

Figure 6 All of the Data Table Models and Inheritance

The Code:

CategoryTableModel.cs

This is an example of a super class of the DictionaryTableModel. In the user interface category was renamed as Genre.

using System.Data;
using MySql.Data.MySqlClient;
using ExperimentSimpleBkLibInvTool.ModelInMVC.DictionaryTabelBaseModel;

namespace ExperimentSimpleBkLibInvTool.ModelInMVC.Category
{
    public class CategoryTableModel : DictionaryTableModel
    {
        public DataTable CategoryTable { get { return DataTable; } }

        public CategoryTableModel() : base("bookcategories", "getAllBookCategoriesWithKeys", "addCategory")
        {
        }

        public string CategoryTitle(uint Key)
        {
            return KeyToName(Key);
        }

        public uint CategoryKey(string CategoryTitle)
        {
            return NameToKey(CategoryTitle);
        }

        public void AddCategory(CategoryModel Category)
        {
            AddItemToDictionary(Category);
        }

        protected override void InitializeSqlCommandParameters()
        {
            MySqlParameterCollection parameters = AddItemParameters;

            _addSqlCommandParameter("Name", GetDBColumnData("CategoryName"), parameters["@categoryName"]);
            _addSqlCommandParameter("Primary Key", GetDBColumnData("idBookCategories"), parameters["@primaryKey"]);
        }
    }
}

AuthorTableModel.cs

This is an example of one of the more complex implementations of the CDataTableModel class.

using System;
using System.Collections.Generic;
using System.Data;
using MySql.Data.MySqlClient;
using ExperimentSimpleBkLibInvTool.ModelInMVC.DataTableModel;

namespace ExperimentSimpleBkLibInvTool.ModelInMVC.Author
{
    public class AuthorTableModel : CDataTableModel
    {
        private int AuthorIDColumnIndex;
        private int LastNameColumnIndex;
        private int FirstNameColumnIndex;
        private int MiddleNameColumnIndex;
        private int DobColumnIndex;
        private int DodColumnIntex;

        public DataTable AuthorTable { get { return DataTable; } }

        public AuthorTableModel() : base("authorstab", "getAllAuthorsData", "addAuthor")
        {
            AuthorIDColumnIndex = GetDBColumnData("idAuthors").IndexBasedOnOrdinal;
            LastNameColumnIndex = GetDBColumnData("LastName").IndexBasedOnOrdinal;
            FirstNameColumnIndex = GetDBColumnData("FirstName").IndexBasedOnOrdinal;
            MiddleNameColumnIndex = GetDBColumnData("MiddleName").IndexBasedOnOrdinal;
            DobColumnIndex = GetDBColumnData("YearOfBirth").IndexBasedOnOrdinal;
            DodColumnIntex = GetDBColumnData("YearOfDeath").IndexBasedOnOrdinal;
        }

        public bool AddAuthor(AuthorModel NewAuthor)
        {
            return addItem(NewAuthor);
        }


#region Author Selector tool support

        public DataRow[] FindAuthors(string lastName, string firstname=null)
        {
            DataTable dt = AuthorTable;
            string filterString = "LastName LIKE '" + lastName + "*'";
            DataRow[] authors = dt.Select(filterString);

            return authors;
        }

        public uint AuthorKey(AuthorModel author)
        {
            uint key = author.AuthorId;
            if (key < 1)
            {
                DataTable dt = AuthorTable;
                string filterString = "LastName = '" + author.LastName + "' AND FirstName = '" + author.FirstName + "' AND MiddleName Like '" + author.MiddleName + "'";
                DataRow[] authors = dt.Select(filterString);
                if (authors.Length > 0)
                {
                    if (!uint.TryParse(authors[0][AuthorIDColumnIndex].ToString(), out key))
                    {
                        key = 0;
                    }
                }
                else
                {
                    key = 0;
                }
            }

            return key;
        }

        public AuthorModel GetAuthorFromId(uint key)
        {
            AuthorModel author = null;
            DataTable dt = AuthorTable;
            string filterString = "idAuthors = '" + key.ToString() + "'";
            DataRow[] authors = dt.Select(filterString);

            if (authors.Length > 0)
            {
                author = ConvertDataRowToAuthor(authors[0]);
            }

            return author;
        }

        // Keeping all internal information about columns and rows encapsulated.
        public AuthorModel ConvertDataRowToAuthor(DataRow AuthorInfo)
        {
            AuthorModel author = new AuthorModel(AuthorInfo[AuthorIDColumnIndex].ToString(), AuthorInfo[FirstNameColumnIndex].ToString(), AuthorInfo[LastNameColumnIndex].ToString(), AuthorInfo[MiddleNameColumnIndex].ToString(),
                AuthorInfo[DobColumnIndex].ToString(), AuthorInfo[DodColumnIntex].ToString());

            return author;
        }

        public List<string> AuthorNamesForSelector(DataRow[] AuthorDataRows)
        {
            List<string> authorNames = new List<string>();
            foreach (DataRow author in AuthorDataRows)
            {
                string LastFirstMiddle = author[LastNameColumnIndex].ToString() + ", " + author[FirstNameColumnIndex].ToString() + " " + author[MiddleNameColumnIndex].ToString();
                authorNames.Add(LastFirstMiddle);
            }

            return authorNames;
        }

        public string AuthorNamesCombinedString(DataRow author)
        {
            string LastFirstMiddle = author[LastNameColumnIndex].ToString() + ", " + author[FirstNameColumnIndex].ToString() + " " + author[MiddleNameColumnIndex].ToString();

            return LastFirstMiddle;
        }

        protected override void InitializeSqlCommandParameters()
        {
            MySqlParameterCollection parameters = AddItemParameters;

            _addSqlCommandParameter("Last Name", GetDBColumnData("LastName"), parameters["@authorLastName"]);
            _addSqlCommandParameter("First Name", GetDBColumnData("FirstName"), parameters["@authorFirstName"]);
            _addSqlCommandParameter("Middle Name", GetDBColumnData("MiddleName"), parameters["@authorMiddleName"]);
            _addSqlCommandParameter("Year of Birth", GetDBColumnData("YearOfBirth"), parameters["@dob"]);
            _addSqlCommandParameter("Year of Death", GetDBColumnData("YearOfDeath"), parameters["@dod"]);
            _addSqlCommandParameter("ID", GetDBColumnData("idAuthors"), parameters["@primaryKey"]);
        }

        #endregion
    }
}

AuthorModel.cs

This is the implementation of the DataTableItemBaseModel for the previous table.

using System.Windows;
using ExperimentSimpleBkLibInvTool.ModelInMVC.ItemBaseModel;

namespace ExperimentSimpleBkLibInvTool.ModelInMVC.Author
{
    public class AuthorModel : DataTableItemBaseModel, IAuthorModel
    {
        private bool errorWasReported;

        public string FirstName {
            get { return GetParameterValue("First Name"); }
            set { SetFirstName(value); }
        }

        public string MiddleName {
            get { return GetParameterValue("Middle Name"); }
            set { SetParameterValue("Middle Name", value); }
        }

        public string LastName {
            get { return GetParameterValue("Last Name"); }
            set { SetLastName(value); }
        }

        public string YearOfBirth {
            get { return GetParameterValue("Year of Birth"); }
            set { SetParameterValue("Year of Birth", value); }
        }

        public string YearOfDeath {
            get { return GetParameterValue("Year of Death"); }
            set { SetParameterValue("Year of Death", value); }
        }

        public uint AuthorId {
            get { return GetParameterKValue("ID"); }
            private set { SetParameterValue("ID", value); }
        }

        public AuthorModel()
            : base(((App)Application.Current).Model.AuthorTable)
        {
            errorWasReported = false;
            AuthorId = 0;
        }

        public AuthorModel(string firstName, string lastName, string middleName=null, string yearOfBirth=null, string yearOfDeath=null)
            : base(((App)Application.Current).Model.AuthorTable)
        {
            errorWasReported = false;
            AuthorId = 0;

            FirstName = firstName;
            LastName = lastName;

            if (!string.IsNullOrEmpty(middleName))
            {
                MiddleName = middleName;
            }

            if (!string.IsNullOrEmpty(yearOfBirth))
            {
                YearOfBirth = yearOfBirth;
            }

            if (!string.IsNullOrEmpty(yearOfDeath))
            {
                YearOfDeath = yearOfDeath;
            }
        }

        public AuthorModel(string idAuthor, string firstName, string lastName, string middleName = null, string yearOfBirth = null, string yearOfDeath = null)
            : base(((App)Application.Current).Model.AuthorTable)
        {
            errorWasReported = false;

            uint IdAuthor;
            uint.TryParse(idAuthor, out IdAuthor);
            AuthorId = IdAuthor;

            FirstName = firstName;
            LastName = lastName;

            if (!string.IsNullOrEmpty(middleName))
            {
                MiddleName = middleName;
            }

            if (!string.IsNullOrEmpty(yearOfBirth))
            {
                YearOfBirth = yearOfBirth;
            }

            if (!string.IsNullOrEmpty(yearOfDeath))
            {
                YearOfDeath = yearOfDeath;
            }
        }

        public override bool AddToDb()
        {
            return ((App)Application.Current).Model.AuthorTable.AddAuthor(this);
        }

        private void SetFirstName(string textBoxInput)
        {
            if (string.IsNullOrEmpty(textBoxInput))
            {
                string errorMsg = "The first name of the author is a required field!";
                MessageBox.Show(errorMsg);
                errorWasReported = true;
            }
            else
            {
                SetParameterValue("First Name", textBoxInput);
            }
        }

        private void SetLastName(string textBoxInput)
        {
            if (string.IsNullOrEmpty(textBoxInput))
            {
                string errorMsg = "The last name of the author is a required field!";
                MessageBox.Show(errorMsg);
                errorWasReported = true;
            }
            else
            {
                SetParameterValue("Last Name", textBoxInput);
            }
        }

        protected override bool _dataIsValid()
        {
            bool isValid = _defaultIsValid();

            if (isValid)
            {
                return isValid;
            }

            isValid = GetParameterIsValid("First Name");
            if (isValid)
            {
                isValid = GetParameterIsValid("Last Name");
            }

            if (!isValid && !errorWasReported)
            {
                string errorMsg = "Add Series error: The first and last names of the author are required fields";
                MessageBox.Show(errorMsg);
            }

            return isValid;
        }
    }
}

SqlCmdParameter.cs

using System;
using System.Data;
using System.Windows;
using MySql.Data.MySqlClient;

/*
 * This class is used to generate SQL command parameters to a call of a 
 * stored procedure.
 * 
 * This class is a data value for a single column in a single row of data.
 * Incoming data will generally be user input and there will be 2 forms of input, either
 * a string from a text field or a boolean value from a checkbox.
 * 
 * During the creation of the SQL command parameter the data will be returned as the proprer
 * type for the stored procedure. The coversion from input string to the expected SQL type
 * will occur during the input phase as an additional check on the validity of the input.
 */
namespace ExperimentSimpleBkLibInvTool.ModelInMVC.DataTableModel
{
    public class SqlCmdParameter 
    {
        protected string _publicName;           // The name the user knows this field by
        protected string _dataBaseColumnName;
        protected string _storedProcedureParameterName;
        protected ParameterDirection _direction;
        protected int _valueInt;
        protected string _value;                // used for input as the basis of the conversion, and storage for string parameters.
        protected double _valueDouble;
        protected uint _valueKey;
        protected bool _isRequired;             // Is this field required to have a value in the database? This is used in the validity check
        protected MySqlDbType _type;
        protected bool _isValueSet;             // internal, used in the validity check
        protected bool _skipInsertOfPrimaryKey;

        public SqlCmdParameter(string PublicName, string DataBaseColumnName, string SBParamName, MySqlDbType Type, bool IsRequired = false, ParameterDirection Direction=ParameterDirection.Input, bool SkipInserOfPrimaryKey=false)
        {
            if (string.IsNullOrEmpty(PublicName))
            {
                ArgumentNullException ex = new ArgumentNullException("PublicName");
                throw ex;
            }

            if (string.IsNullOrEmpty(SBParamName))
            {
                ArgumentNullException ex = new ArgumentNullException("SBParamName");
                throw ex;
            }

            switch (Type)
            {
                case MySqlDbType.Int16:
                case MySqlDbType.Int32:
                case MySqlDbType.Double:
                case MySqlDbType.String:
                case MySqlDbType.UInt32:
                case MySqlDbType.Byte:      // TinyInt for boolean representation
                    break;
                case MySqlDbType.VarChar:
                case MySqlDbType.Date:
                    // In the user interface handle VarChar as a string.
                    Type = MySqlDbType.String;
                    break;

                default:
                    ArgumentOutOfRangeException ex = new ArgumentOutOfRangeException("Type");
                    throw ex;
            }

            _publicName = PublicName;
            _storedProcedureParameterName = SBParamName;
            _direction = Direction;
            _isRequired = IsRequired;
            _type = Type;
            _isValueSet = false;
            _value = null;
            _valueKey = 0;
            _valueInt = 0;
            _valueDouble = 0.0;
            _skipInsertOfPrimaryKey = SkipInserOfPrimaryKey;
        }

        public SqlCmdParameter(SqlCmdParameter original)
        {
            _publicName = original._publicName;
            _storedProcedureParameterName = original._storedProcedureParameterName;
            _direction = original._direction;
            _isRequired = original._isRequired;
            _type = original._type;
            _isValueSet = original._isValueSet;
            _value = original._value;
            _valueKey = original._valueKey;
            _valueInt = original._valueInt;
            _valueDouble = original._valueDouble;
            _skipInsertOfPrimaryKey = original._skipInsertOfPrimaryKey;
        }

        public string PublicName
        {
            get { return _publicName; }
        }

        public ParameterDirection Direction
        {
            get { return _direction;  }
            set { _direction = value; }
        }

        public bool IsValid { get { return _dataIsValid(); } }

        public bool IsRequired
        {
            get { return _isRequired; }
            set { _isRequired = value; }
        }

        public string Value
        {
            get { return _value; }
            set { SetValue(value); }
        }

        public bool BValue
        {
            get { return (_valueInt > 0); }
            set { SetValue(value); }
        }

        public uint KeyValue
        {
            get { return _valueKey; }
            set { _valueKey = value; }
        }

        public MySqlDbType Type
        {
            get { return _type; }
        }

        public bool AddParameterToCommand(MySqlCommand cmd)
        {
            if (_skipInsertOfPrimaryKey)
            {
                return true;
            }

            // If it is an output variable validity doesn't matter.
            if (_direction != ParameterDirection.Input)
            {
                string IndexByNameValue = _storedProcedureParameterName;
                cmd.Parameters.Add(new MySqlParameter(IndexByNameValue, _type));
                cmd.Parameters[IndexByNameValue].Direction = _direction;
                return true;
            }

            if (!IsValid)
            {
                return IsValid;
            }

            switch (_type)
            {
                case MySqlDbType.Byte:
                case MySqlDbType.Int16:
                case MySqlDbType.Int32:
                    cmd.Parameters.AddWithValue(_storedProcedureParameterName, _valueInt);
                    break;
                case MySqlDbType.Double:
                    cmd.Parameters.AddWithValue(_storedProcedureParameterName, _valueDouble);
                    break;
                case MySqlDbType.UInt32:
                    cmd.Parameters.AddWithValue(_storedProcedureParameterName, _valueKey);
                    break;
                case MySqlDbType.String:
                    cmd.Parameters.AddWithValue(_storedProcedureParameterName, _value);
                    break;
            }
            return true;
        }

        protected void SetValue(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return;
            }

            _value = value;

            string eMsg = null;
            switch (_type)
            {
                case MySqlDbType.Int16:
                case MySqlDbType.Byte:
                    bool tmp = false;
                    if (!bool.TryParse(_value, out tmp))
                    {
                        eMsg = _publicName + ": Value is not True or False";
                    }
                    _valueInt = (tmp) ? 1 : 0;
                    break;
                case MySqlDbType.Int32:
                    if (!int.TryParse(_value, out _valueInt))
                    {
                        eMsg = _publicName + ": Value is not in the proper format of an integer";
                    }
                    break;
                case MySqlDbType.Double:
                    if (!double.TryParse(_value, out _valueDouble))
                    {
                        eMsg = _publicName + ": Value is not in the proper format of an floating point number";
                    }
                    break;
                case MySqlDbType.UInt32:
                    _valueKey = Convert.ToUInt32(value);
                    if (!uint.TryParse(_value, out _valueKey))
                    {
                        eMsg = _publicName + ": Value is not in the proper format of an unsigned integer";
                    }
                    break;
                case MySqlDbType.String:
                default:
                    break;
            }

            if (eMsg != null)
            {
                MessageBox.Show(eMsg);
                _isValueSet = false;
            }
            else
            {
                _isValueSet = true;
            }
        }

        protected void SetValue(bool InVal)
        {
            _value = (InVal) ? "true" : "false";
            if (_type == MySqlDbType.Int16 || _type == MySqlDbType.Byte)
            {
                _valueInt = (InVal) ? 1 : 0;
            }

            _isValueSet = true;
        }

        protected bool _dataIsValid()
        {
            bool dataIsValid = true;

            if (_direction == ParameterDirection.Input && _isRequired && !_isValueSet)
            {
                    dataIsValid = false;
            }

            return dataIsValid;
        }

    }
}

DBColParameterData.cs

using System.Data;

namespace ExperimentSimpleBkLibInvTool.ModelInMVC
{
    public class DbColumnParameterData
    {
        public DbColumnParameterData(DataRow ColumnData)
        {
            bool parseWorked = true;
            ColumnName = ColumnData[0].ToString();
            parseWorked = int.TryParse(ColumnData[1].ToString(), out int ordinalPosition);
            Ordinal_Posistion = ordinalPosition;
            IsNullable = true;
        }

        public DbColumnParameterData(string columnName, int ordinal_Posistion, bool isNullable)
        {
            ColumnName = columnName;
            Ordinal_Posistion = ordinal_Posistion;
            IsNullable = isNullable;
        }

        public string ColumnName { get; private set; }

        public int Ordinal_Posistion { get; private set; }

        public bool IsNullable { get; private set; }

        public int IndexBasedOnOrdinal { get { return Ordinal_Posistion - 1; } }
    }
}

DataTableModel.cs

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Windows;
using MySql.Data.MySqlClient;
using ExperimentSimpleBkLibInvTool.ModelInMVC.ItemBaseModel;

/*
 * 
 * This file provides the database interface layer. All data retrieval and inserts
 * are performed in this file. Information about each table is stored in the 
 * super classes that inherit from this class, but the data structures are located
 * in this base class.
 * 
 */
namespace ExperimentSimpleBkLibInvTool.ModelInMVC.DataTableModel
{
    public abstract class CDataTableModel : ObservableModelObject
    {
        protected string _dbConnectionString;
        protected string _getTableStoredProcedureName;
        protected string _addItemStoredProcedureName;
        protected string _tableName;
        protected uint _newKeyValue;
        protected MySqlParameterCollection _addItemStoredProcedureParameters;
        protected List<DbColumnParameterData> _parameterProperties;
        protected Dictionary<string, int> ParametersIndexedByPublicName;
        protected Dictionary<string, int> ParametersIndexedByDatabaseTableName;
        protected Dictionary<string, int> ParametersIndexedByParameterName;
        private List<SqlCmdParameter> _sqlCmdParameters;

        public uint NewKeyValue { get { return _newKeyValue; } }

        public MySqlParameterCollection AddItemParameters { get { return _addItemStoredProcedureParameters; } }

        public List<DbColumnParameterData> ColumnParameterData { get; private set; }

        protected DataTable DataTable { get { return getDataTable(); } }

        // The impementation of this function requires detailed knowlege of the columns in the table
        // and the parameters of the stored procedure.
        protected abstract void InitializeSqlCommandParameters();

        public DbColumnParameterData GetDBColumnData(string columnName)
        {
            return ColumnParameterData.Find(x => x.ColumnName == columnName);
        }

        public List<SqlCmdParameter> SQLCommandParameters { get { return _sqlCmdParameters; } }

        public Dictionary<string, int> PublicNameParameterIndex { get { return ParametersIndexedByPublicName; } }

        public Dictionary<string, int> ParametersIndexByDbColumnName { get { return ParametersIndexedByDatabaseTableName; } }

        public Dictionary<string, int> ParametersIndexByStoredProcedureName { get { return ParametersIndexedByParameterName; } }

        protected CDataTableModel(string TableName, string GetTableStoredProcedureName, string AddItemToTableStoredProcedureName=null)
        {
            _newKeyValue = 0;
            _tableName = TableName;
            _getTableStoredProcedureName = GetTableStoredProcedureName;
            _addItemStoredProcedureName = AddItemToTableStoredProcedureName;
            _dbConnectionString = ConfigurationManager.ConnectionStrings["LibInvToolDBConnStr"].ConnectionString;
            _sqlCmdParameters = new List<SqlCmdParameter>();
            ParametersIndexedByPublicName = new Dictionary<string, int>();
            ParametersIndexedByDatabaseTableName = new Dictionary<string, int>();
            ParametersIndexedByParameterName = new Dictionary<string, int>();

            // Not all datatable classes can add items, 2 examples are the status table and the condition table.
            if (!string.IsNullOrEmpty(AddItemToTableStoredProcedureName))
            {
                GetParametersNamesFromAddCommand();
                ColumnParameterData = GetColumnParameterProperties();
                InitializeSqlCommandParameters();
                ValidateParameterCount();
            }
        }

        protected bool addItem(DataTableItemBaseModel NewDataItem)
        {
            bool canAddItemToTable = true;

            canAddItemToTable = NewDataItem.IsValid;
            if (canAddItemToTable)
            {
                canAddItemToTable = dbAddItem(NewDataItem);
            }

            return canAddItemToTable;
        }

        protected bool _addParametersInOrder(MySqlCommand cmd, DataTableItemBaseModel NewDataItem)
        {
            foreach (MySqlParameter parameter in _addItemStoredProcedureParameters)
            {
                if (!NewDataItem.AddParameterToCommand(cmd, parameter.ParameterName))
                {
                    return false;
                }
            }

            return true;
        }

        protected void _addSqlCommandParameter(string PublicName, DbColumnParameterData ColumnData, MySqlParameter parameter)
        {
            bool isRequired = false;
            string DBColumnName = (ColumnData != null) ? ColumnData.ColumnName : "primaryKey";

            if (!ParameterIsValid(PublicName, DBColumnName, parameter.ParameterName))
            {
                return;
            }

            if (ColumnData == null || ColumnData.IsNullable)
            {
                isRequired = false;
            }
            else
            {
                isRequired = true;
            }

            SqlCmdParameter NewParameter = new SqlCmdParameter(PublicName, DBColumnName, parameter.ParameterName, parameter.MySqlDbType, isRequired, parameter.Direction);
            ParametersIndexedByPublicName.Add(PublicName, _sqlCmdParameters.Count);
            ParametersIndexedByDatabaseTableName.Add(DBColumnName, _sqlCmdParameters.Count);
            ParametersIndexedByParameterName.Add(parameter.ParameterName, _sqlCmdParameters.Count);
            _sqlCmdParameters.Add(NewParameter);
        }

        private bool dbAddItem(DataTableItemBaseModel NewDataItem)
        {
            bool AddItemSuccess = true;

            if (ReportProgrammerError(_addItemStoredProcedureName, "_addItemStoredProcedureName is not set!"))
            {
                return false;
            }

            using (MySqlConnection conn = new MySqlConnection(_dbConnectionString))
            {
                try
                {
                    conn.Open();
                    using (MySqlCommand cmd = new MySqlCommand())
                    {
                        cmd.Connection = conn;
                        cmd.CommandType = CommandType.StoredProcedure;
                        cmd.CommandText = _addItemStoredProcedureName;
                        if (_addParametersInOrder(cmd, NewDataItem))
                        {
                            cmd.ExecuteNonQuery();
                            // Some of the stored procedures return the new key in the last parameter
                            // in those cases get the returned key so that the new row can be accessed.
                            int paramtercount = cmd.Parameters.Count - 1;   // indexing starts at 0 ends at count - 1
                            if (cmd.Parameters[paramtercount].Direction != ParameterDirection.Input)
                            {
                               uint.TryParse(cmd.Parameters[paramtercount].Value.ToString(), out _newKeyValue);
                            }
                            OnPropertyChanged();
                        }
                        else
                        {
                            AddItemSuccess = false;
                        }
                    }
                }
                catch (Exception ex)
                {
                    string errorMsg = "Database Error: " + ex.Message;
                    MessageBox.Show(errorMsg);
                    AddItemSuccess = false;
                }
            }
            return AddItemSuccess;
        }

        private DataTable getDataTable()
        {
            int ResultCount = 0;
            DataTable Dt = new DataTable();
            if (!ReportProgrammerError(_getTableStoredProcedureName, "_getTableStoredProcedureName is not set!"))
            {
                try
                {
                    using (MySqlConnection conn = new MySqlConnection(_dbConnectionString))
                    {
                        conn.Open();
                        using (MySqlCommand cmd = new MySqlCommand())
                        {
                            cmd.Connection = conn;
                            cmd.CommandType = CommandType.StoredProcedure;
                            cmd.CommandText = _getTableStoredProcedureName;

                            MySqlDataAdapter sda = new MySqlDataAdapter(cmd);
                            ResultCount = sda.Fill(Dt);
                            OnPropertyChanged();
                        }
                    }
                }
                catch (Exception ex)
                {
                    string errorMsg = "Database Error: " + ex.Message;
                    MessageBox.Show(errorMsg);
                }
            }

            return Dt;
        }

        private void GetParametersNamesFromAddCommand()
        {
            if (!string.IsNullOrEmpty(_addItemStoredProcedureName))
            {
                // Neither the status table or the condition table have stored procedures to
                // add data to the tables, these tables are included in add book.
                try
                {
                    using (MySqlConnection conn = new MySqlConnection(_dbConnectionString))
                    {
                        conn.Open();
                        using (MySqlCommand cmd = new MySqlCommand())
                        {
                            cmd.CommandType = CommandType.StoredProcedure;
                            cmd.CommandText = _addItemStoredProcedureName;
                            cmd.Connection = conn;

                            MySqlCommandBuilder.DeriveParameters(cmd);
                            _addItemStoredProcedureParameters = cmd.Parameters;
                        }
                    }
                }
                catch (Exception ex)
                {
                    string errorMsg = "Table: " + _tableName + " Stored Procedure: " + _addItemStoredProcedureName + "\nDatabase Error Initializing Command Parameter Properties: ";
                    errorMsg += ex.Message;
                    MessageBox.Show(errorMsg, "Database Error:", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        // Due to bugs/unimplemented features in MySQL MySqlCommandBuilder.DeriveParameters(Command)
        // such as IsNullable will always be false this provides a workaround for getting additional
        // information about each parameter
        private List<DbColumnParameterData> GetColumnParameterProperties()
        {
            List<DbColumnParameterData> columnSchemaDetails = new List<DbColumnParameterData>();
            DataTable Dt = new DataTable();
            int ResultCount = 0;

            if (!ReportProgrammerError(_tableName, "_tableName is not set!"))
            {
                try
                {
                    using (MySqlConnection conn = new MySqlConnection(_dbConnectionString))
                    {
                        conn.Open();
                        using (MySqlCommand cmd = new MySqlCommand())
                        {
                            cmd.Connection = conn;
                            cmd.CommandType = CommandType.StoredProcedure;
                            cmd.CommandText = "getTableColumnData";
                            cmd.Parameters.AddWithValue("tableName", _tableName);

                            MySqlDataAdapter sda = new MySqlDataAdapter(cmd);
                            ResultCount = sda.Fill(Dt);
                        }
                    }

                    foreach (DataRow dataRow in Dt.Rows)
                    {
                        columnSchemaDetails.Add(new DbColumnParameterData(dataRow));
                    }
                }
                catch (Exception ex)
                {
                    string errorMsg = "Database Error Initializing Parameter Properties: " + ex.Message;
                    MessageBox.Show(errorMsg, "Database Error:", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }

            return columnSchemaDetails;
        }

        private bool ReportProgrammerError(string nameToCheck, string errorMessage)
        {
            if (string.IsNullOrEmpty(nameToCheck))
            {
#if DEBUG
                string errorMsg = "Programmer ERROR : " + errorMessage;
                MessageBox.Show(errorMsg, "Programmer ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
#endif
                return true;
            }
            return false;
        }

        private bool ParameterIsValid(string PublicName, string DataBaseColumnName, string StoredProcedureParamName)
        {
            bool isValid = true;

            if (ReportProgrammerError(PublicName, "PublicName is null or empty in _addSqlCommandParameter"))
            {
                isValid = false;
            }

            if (ReportProgrammerError(DataBaseColumnName, "DataBaseColumnName is null or empty in _addSqlCommandParameter"))
            {
                isValid = false;
            }

            if (ReportProgrammerError(StoredProcedureParamName, "SBParamName is null or empty in _addSqlCommandParameter"))
            {
                isValid = false;
            }

            return isValid;
        }

        private bool ValidateParameterCount()
        {
            bool validCount = _sqlCmdParameters.Count == _addItemStoredProcedureParameters.Count;

#if DEBUG
            if (!validCount)
            {
                string eMsg = "Stored Procedure: " + _addItemStoredProcedureName + " Expected parameter count is " + _addItemStoredProcedureParameters.Count.ToString() +
                    " Actual parameter count is " + _sqlCmdParameters.Count.ToString();
                MessageBox.Show(eMsg, "Invalid Parameter Count", MessageBoxButton.OK, MessageBoxImage.Error);
            }
#endif

            return (validCount);
        }
    }
}

DataTableItemBaseModel.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Windows;
using MySql.Data.MySqlClient;
using ExperimentSimpleBkLibInvTool.ModelInMVC.DataTableModel;

/*
 * There is a tight coupling between each model and the table it belongs to. This
 * is due to the models ability to add parameters to the tables call to the stored
 * procedure. This is only true when a model can be added to a table.
 * 
 * This class represents a row of data in a data table. Generally it will be used
 * to add a row to a database table.
 */
namespace ExperimentSimpleBkLibInvTool.ModelInMVC.ItemBaseModel
{
    public abstract class DataTableItemBaseModel
    {
        /*
         * To save memory and correctly change the proper command parameter, only the
         * _sqlCmdParameters list contains SqlCmdParameters and the dictionaries provide
         * indexes into the command parameters list. To maintain good performance the
         * dictionaries are used rather than using a find in the list.
         */
        private List<SqlCmdParameter> _sqlCmdParameters;
        private Dictionary<string, int> _parameterIndexByPublicName;
        private Dictionary<string, int> _parameterIndexByDatabaseTableName;
        private Dictionary<string, int> _parameterIndexByParameterName;

        public bool IsValid { get { return _dataIsValid(); } }

        public uint BookId
        {
            get { return GetParameterKValue("ID"); }
            set { SetParameterValue("ID", value); }
        }

        public void setBookId(uint BookId)
        {
            SetParameterValue("ID", BookId);
        }

        public abstract bool AddToDb();

        protected abstract bool _dataIsValid();

        protected DataTableItemBaseModel(CDataTableModel DBInterfaceModel)
        {
            _sqlCmdParameters = new List<SqlCmdParameter>();
            List<SqlCmdParameter> sqlCmdParameters = DBInterfaceModel.SQLCommandParameters;
            foreach (SqlCmdParameter parameter in sqlCmdParameters)
            {
                SqlCmdParameter p = new SqlCmdParameter(parameter);
                _sqlCmdParameters.Add(p);
            }

            _parameterIndexByPublicName = new Dictionary<string, int>(DBInterfaceModel.PublicNameParameterIndex);
            _parameterIndexByParameterName = new Dictionary<string, int>(DBInterfaceModel.ParametersIndexByStoredProcedureName);
            _parameterIndexByDatabaseTableName = new Dictionary<string, int>();
            _parameterIndexByDatabaseTableName = new Dictionary<string, int>(DBInterfaceModel.ParametersIndexByDbColumnName);
        }

        /*
         * Sometimes the number of parameters in the stored procedure count doesn't
         * match the nummber of columns in the table. This function can be overriden
         * in those cases. Two examples of this are the Series and Books.
         */
        public bool AddParameterToCommand(MySqlCommand cmd, string ParameterName)
        {
            bool success = true;
            int tableIndex = getParameterIndex(ParameterName);
            if (tableIndex >= 0)
            {
                MySqlParameterCollection parameters = cmd.Parameters;
                success = _sqlCmdParameters[tableIndex].AddParameterToCommand(cmd);
            }
            else
            {
                success = false;
            }
            return success;
        }

        public string GetParameterValue(string ParameterName)
        {
            string ParameterValue = "Failure";

            int tableIndex = getParameterIndex(ParameterName);
            if (tableIndex >= 0)
            {
                ParameterValue = _sqlCmdParameters[tableIndex].Value;
            }

            return ParameterValue;
        }

        public uint GetParameterKValue(string ParameterName)
        {
            uint ParameterValue = 0;

            int tableIndex = getParameterIndex(ParameterName);
            if (tableIndex >= 0)
            {
                ParameterValue = _sqlCmdParameters[tableIndex].KeyValue;
            }

            return ParameterValue;
        }

        public int GetParameterIValue(string ParameterName)
        {
            int ParameterValue = -1;

            int tableIndex = getParameterIndex(ParameterName);
            if (tableIndex >= 0)
            {
                ParameterValue = Convert.ToInt32(_sqlCmdParameters[tableIndex].Value);
            }

            return ParameterValue;
        }

        protected ParameterDirection GetParameterDirection(string ParameterName)
        {
            ParameterDirection Direction = ParameterDirection.Input;
            int tableIndex = getParameterIndex(ParameterName);
            if (tableIndex >= 0)
            {
                Direction = _sqlCmdParameters[tableIndex].Direction;
            }
            return Direction;
        }

        protected MySqlDbType GetParameterType(string ParameterName)
        {
            MySqlDbType Type = MySqlDbType.String;
            int tableIndex = getParameterIndex(ParameterName);
            if (tableIndex >= 0)
            {
                Type = _sqlCmdParameters[tableIndex].Type;
            }
            return Type;
        }

        protected void SetParameterValue(string ParameterName, string value)
        {
            int tableIndex = getParameterIndex(ParameterName);
            if (tableIndex >= 0)
            {
                _sqlCmdParameters[tableIndex].Value = value;
            }
        }

        protected void SetParameterValue(string ParameterName, uint value)
        {
            int tableIndex = getParameterIndex(ParameterName);
            if (tableIndex >= 0)
            {
                _sqlCmdParameters[tableIndex].Value = value.ToString();
                _sqlCmdParameters[tableIndex].KeyValue = value;
            }
        }

        protected void SetParameterValue(string ParameterName, int value)
        {
            int tableIndex = getParameterIndex(ParameterName);
            if (tableIndex >= 0)
            {
                _sqlCmdParameters[tableIndex].Value = value.ToString();
            }
        }

        protected void SetParameterValue(string ParameterName, bool value)
        {
            int tableIndex = getParameterIndex(ParameterName);
            if (tableIndex >= 0)
            {
                _sqlCmdParameters[tableIndex].BValue = value;
            }
        }

        protected bool GetParameterBValue(string ParameterName)
        {
            bool ParameterValue = false;

            int tableIndex = getParameterIndex(ParameterName);
            if (tableIndex >= 0)
            {
                ParameterValue = _sqlCmdParameters[tableIndex].BValue;
            }

            return ParameterValue;
        }

        protected uint GetKeyValue()
        {
            uint KeyValue = 0;

            int tableIndex = getParameterIndex("ID");
            if (tableIndex >= 0)
            {
                KeyValue = _sqlCmdParameters[tableIndex].KeyValue;
            }

            return KeyValue;
        }

        protected void SetKeyValue(uint KeyValue)
        {
            int tableIndex = getParameterIndex("ID");
            if (tableIndex >= 0)
            {
                _sqlCmdParameters[tableIndex].KeyValue = KeyValue;
            }
        }

        public bool GetParameterIsValid(string ParameterName)
        {
            bool ParameterIsValid = false;

            int tableIndex = getParameterIndex(ParameterName);
            if (tableIndex >= 0)
            {
                ParameterIsValid = _sqlCmdParameters[tableIndex].IsValid;
            }

            return ParameterIsValid;
        }

        protected bool GetParameterIsRequired(string ParameterName)
        {
            bool ParameterIsRequired = false;

            int tableIndex = getParameterIndex(ParameterName);
            if (tableIndex >= 0)
            {
                ParameterIsRequired = _sqlCmdParameters[tableIndex].IsRequired;
            }

            return ParameterIsRequired;
        }

        private int getParameterIndex(string parameterName)
        {
            int parameterIndex = -1;
            int tableIndex;

            if (_parameterIndexByParameterName.TryGetValue(parameterName, out tableIndex))
            {
                parameterIndex = tableIndex;
            }
            else if (_parameterIndexByPublicName.TryGetValue(parameterName, out tableIndex)) 
            {
                parameterIndex = tableIndex;
            }
            else if (_parameterIndexByDatabaseTableName.TryGetValue(parameterName, out tableIndex))
            {
                parameterIndex = tableIndex;
            }
#if DEBUG
            // ASSERT
            else
            {
                string eMsg = "Programmer error in getParameterIndex(): Parameter not found: " + parameterName;
                MessageBox.Show(eMsg, "Programmer Error:", MessageBoxButton.OK, MessageBoxImage.Error);
            }
#endif

            return parameterIndex;
        }

        protected bool _defaultIsValid()
        {
            bool isValid = true;

            foreach (SqlCmdParameter parameter in _sqlCmdParameters)
            {
                isValid = parameter.IsValid;
                if (parameter.Direction == ParameterDirection.Input && !isValid)
                {
                    return isValid;
                }
            }

            return isValid;
        }
    }
}

DictionaryTableModel.cs

using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows;
using ExperimentSimpleBkLibInvTool.ModelInMVC.DataTableModel;
using ExperimentSimpleBkLibInvTool.ModelInMVC.ItemBaseModel;

namespace ExperimentSimpleBkLibInvTool.ModelInMVC.DictionaryTabelBaseModel
{
    public abstract class DictionaryTableModel : CDataTableModel
    {
        private Dictionary<uint, string> _keyToTitle;
        private Dictionary<string, uint> _titleToKey;

        public DictionaryTableModel(string TableName, string GetTableStoredProcedureName, string AddItemToTableStoredProcedureName = null) :
            base(TableName, GetTableStoredProcedureName, AddItemToTableStoredProcedureName)
        {
            _titleToKey = new Dictionary<string, uint>();
            _keyToTitle = new Dictionary<uint, string>();
            _titleToKey = DataTable.AsEnumerable().ToDictionary(row => row.Field<string>(0), row => row.Field<uint>(1));
            _keyToTitle = DataTable.AsEnumerable().ToDictionary(row => row.Field<uint>(1), row => row.Field<string>(0));
        }

        public List<string> ListBoxSelectionList()
        {
            List<string> listBoxSelectionValues = _keyToTitle.Values.ToList<string>();

            return listBoxSelectionValues;
        }

        protected string KeyToName(uint Key)
        {
            return _keyToTitle[Key];
        }

        protected uint NameToKey(string CategoryTitle)
        {
            return _titleToKey[CategoryTitle];
        }

        protected void AddItemToDictionary(DataTableItemBaseModel NewItem)
        {
            bool AddedSuccessfully = addItem(NewItem);

            if (AddedSuccessfully && _newKeyValue > 0)
            {
                _keyToTitle.Add(_newKeyValue, NewItem.GetParameterValue("Name"));
                _titleToKey.Add(NewItem.GetParameterValue("Name"), _newKeyValue);
            }
            else
            {
                string errorMsg = "Database Error: Failed to add item";
                MessageBox.Show(errorMsg);
            }
        }
    }
}
\$\endgroup\$
7
\$\begingroup\$

Application Architecture

The architecture of the application is divided into models and views to separate the data and business model from the user interface. Within the models there is an additional division, there are models that represent the database tables and there are models that represent a row of data within each database table.

You missed an opportunity to split view layout from its logic using Model-View-ViewModel:

  • View: any user or data control represented in xaml
  • ViewModel: the data context a view binds to and provides access to the application layer
  • Model: any services, entities and dependencies used in the application, and accessible to the view models

The classes that reside in the model don't require to be *Model classes or even to be in the same layer. You have distinguished between (1) data table models and (2) data record models. It is more common to split these into explicit layers:

  • Presentation Layer: the views and view models
  • Business Layer: the classes that represent your business classes (Book, Author, Rating, ..)
  • Data-Access Layer: objects that manage interaction between the database and the business entities (preferably using an ORM Framework)

Design Metrics

Was inheritance abused or over used?

Your models have a depth of 2, max 3 classes in inheritance. Given the fact you have implemented your own base classes to provide custom ORM (as explained by the OP), I find this complexity within reasonal bounds.

Is this a SOLID OOP design?

It's an attempt at SOLID and OOP design, but with several offenses against these principles.

Single responsibility principle

  • data table models have properties that represent list box values; these properties should be on view models
  • data record models serve both as business entities and records with database mapping aware properties; these objects should become business entities (preferrably POCO) with the database aware mapping extracted and stored inside the data table models
  • views should offset the logic to view-models, who in term use business entities and data layer objects in the backend

Open/closed principle

  • the models are not closed for changes; you provide public accessors to the internal state; instead you should take more care of encapsulation and provide methods to request the models to change data

Dependency inversion principle

  • your application depends your models, there is no easy way to replace any of your layers by another; to allow dependency inversion, you should let layers communicate to each other through interfaces, and use Dependency Injection to configure the layers at runtime.

Test-driven design

  • it's hard to test the seperate layers, since you don't use interfaces between layers, making it virtually impossible to mock out dependencies
  • you have included message box prompts inside model logic, which is a pain to test with a unit tests

Review

  • You should read the C# Naming Conventions. Many of your method and variable names are offenses against conventions.
  • Don't use Green and Red for button colors. They make the app look like a color book. It's better to use a set of icons that are universal.
  • Don't use center alignment for a list of buttons, instead wrap vertically with the same width.

CategoryTableModel

Using magic string literals is a design smell. If you must work with such strings, declare them in a dedicated class DataSchemaConstants.

public CategoryTableModel() : base("bookcategories", "getAllBookCategoriesWithKeys", "addCategory")
{
}

Use the arrow notation when you can.

public string CategoryTitle(uint Key) => KeyToName(Key);

AuthorTableModel

It is unclear what magic you are performing to get a key in AuthorKey when if (key < 1). Methods should be self-explaining. But this one needs comments. Why should key < 1 be able to get remapped to a valid key? Also, prefer proper string interpolation over string concatenation.

public uint AuthorKey(AuthorModel author)
{
    uint key = author.AuthorId;
    if (key < 1)
    {
        DataTable dt = AuthorTable;
        string filterString = "LastName = '" + author.LastName + // .. removed for brevity
        DataRow[] authors = dt.Select(filterString);
        if (authors.Length > 0)
        {
            if (!uint.TryParse(authors[0][AuthorIDColumnIndex].ToString(), out key))
            {
                key = 0;
            }
        }
        else
        {
            key = 0;
        }
    }

    return key;
}

AuthorModel

Here's a blatant offense against single-responsibility. Don't use representation logic in your models. MessageBox should never be called here. Instead, throw an exception, and catch it at the top most layer, at the view-model. Also think about how to test this code. A unit test with a prompt in it, is not the best idea.

private void SetFirstName(string textBoxInput)
{
    if (string.IsNullOrEmpty(textBoxInput))
    {
        string errorMsg = "The first name of the author is a required field!";
        MessageBox.Show(errorMsg);
        errorWasReported = true;
    }
    else
    {
        SetParameterValue("First Name", textBoxInput);
    }
}

Other Classes

Should be replaced with an ORM Framework.


\$\endgroup\$
  • 1
    \$\begingroup\$ Thank you for an in depth review! \$\endgroup\$ – pacmaninbw Jul 25 at 15:00
  • \$\begingroup\$ Why is string interpolation to be preferred over string concatenation? Not being difficult, there should just be a good reason for it. \$\endgroup\$ – pacmaninbw Jul 25 at 15:31
  • 1
    \$\begingroup\$ about string interpolation vs concatenation: stackoverflow.com/questions/42370614/…. also, for readability \$\endgroup\$ – dfhwze Jul 25 at 15:34
  • 1
    \$\begingroup\$ Yes, reporting should be at top level. Only if you want to intercept errors at the model (to branch to other flow, optionally retthrowing the exception or throwing a new one), should the model handle exceptions \$\endgroup\$ – dfhwze Jul 25 at 15:35
  • 1
    \$\begingroup\$ One reason to keep public methods in an API that you don't use, is to allow for them to be used later. If they make sense, keep them. \$\endgroup\$ – dfhwze Jul 25 at 16:01
5
\$\begingroup\$

I won't read this code beginning to end. Some basic advice:

Namespaces

ExperimentSimpleBkLibInvTool.ModelInMVC.DictionaryTabelBaseModel doesn't follow naming conventions. Naming convention is Company.App.Module. It should be something like: Pacmaninbw.PersonalLibrary.Model.

Naming

  1. Don't tack on crap affixes to the names.

    • ExperimentSimple... It's ok to write experimentally low quality code. But stating the experimentality of the code belongs to README.md. Putting it in the name makes it even lower quality. If you are worried people will use your code to operate Nuclear Reactors and Airliners put legal discalimers in the LICENCE.txt.

    • ...Tool It's either a "library" or a "book inventory", but never a "book library inventory tool", much less "bk lib inv tool". You can say "screwdriver" or "hammer", but never "nail driver hammer tool".

  2. Don't repeat pattern artifacts in names (ModelInMVC)

If you are following a well-known pattern no need to mangle names to advertise it. When you are following patterns follow the naming conventions in the examples. If you, for some reason, are writing a singleton, add an Instance property. Don't name the property TheSingletonInstance. If you put Models, Views, Controllers folders people will understand you are following a pattern, especially if it is the IDE default. If you are following an obscure pattern, document it with the sources where others can learn about it and the reasons you chose it in the README.

\$\endgroup\$
  • \$\begingroup\$ I really want a nail driver hammer tool now. +1 Good notes on naming. \$\endgroup\$ – Shelby115 Mar 15 at 13:15
  • \$\begingroup\$ I have changed all the namespaces to reflect your comment in the repository. Could you point me to online documentation for this naming convention? By default Microsoft Visual Studio uses the project name as the default namespace. \$\endgroup\$ – pacmaninbw Mar 18 at 16:48
  • 1
    \$\begingroup\$ @pacmaninbw namespace naming convention straight from the horse's mouth \$\endgroup\$ – abuzittin gillifirca Mar 19 at 12:47
  • 1
    \$\begingroup\$ It would be nice if Visual Studio defaulted to this naming convention based on who the horse is. \$\endgroup\$ – pacmaninbw Mar 20 at 13:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.