In my previous questions, I feel I have repeatedly been making the mistake of posting portions of my code that I felt was relevant to the immediate problem I was facing, and as a result, I suspect that people weren't seeing the big picture of the problem with my code overall. and adjusting my code has felt like a game of whack-a-mole. (if that makes any sense) ## My problem is I need: ## - clear separation of logic - code reusability - cleanliness - single responsibility principle - to make code that a real coding jedi master would produce. So I'm posting all my code with formatting to make it much easier to navigate. Info on classes ------- - **Program** - I'm trying to put all controlling logic here - **DataAccess** - I'm trying to put my Data-accessing logic here, away from everything else. - **UserInterface** - I'm trying to put my Interface logic here, away from everything else. Such as the way information gets displayed to the user, the way info gets received from user. - **InputValidation** - I want to put all logic that validates user input here, ranging from Type validation to input range validation. - **DbResult** - a repository wrapper class containing a string message, and a boolean. Used to report failure/success results with a message like "success!" or whatever exception.ToString() prints. *I am thinking this class should be removed*? - **Man** - *auto-generated class by entity framework.* - **Location** - *auto-generated class by entity framework.* I plan to implement this class in my code as soon as I can get working clean logic for the CRUD functions that handle the Man class. ## Notes ## - I'd like to enable CRUD functions to handle more than one entity class, without copying and pasting 4 CRUD functions for each new entity. - I'm considering removing DbResult, but not sure how to without keeping logic seperate. ## Program Class ## class Program { private enum MenuStage { MENU_EXIT, MENU_CRUD //MENU_TableSelect } static private MenuStage _MyMenuStage; static private UserInterface _MyUI; static void Main(string[] args) { DbResult ResultSQLRunning; ServiceManager MySM = new ServiceManager(); _MyUI = new UserInterface(); ResultSQLRunning = MySM.SQLRunning(); _MyUI.DisplayMessage(ResultSQLRunning.Message); if (ResultSQLRunning.bSuccess) { using (TestDatabaseEntities MyDB = new TestDatabaseEntities()) { DataAccess MyDA = new DataAccess(MyDB); DbResult MyResult = MyDA.DatabaseExists(); _MyUI.DisplayMessage(MyResult.Message); } DoUICycle(); } Console.ReadLine(); } // ================================ // MENU DISPLAY AND PROCESSING // ================================ private static void DoUICycle() { string [] sCRUDMenu = new string[] {"Exit", "Create", "Read", "Update", "Delete" }; string [] sTableMenu = new string[] {"Quit", "Men", "Locations"}; string[] sMyMenuChoices; UserInterface MyUI = new UserInterface(); int iChoice = -1; _MyMenuStage = MenuStage.MENU_CRUD; while (_MyMenuStage != MenuStage.MENU_EXIT) { if (_MyMenuStage == MenuStage.MENU_CRUD) { sMyMenuChoices = sCRUDMenu; } else //if (MyStage == MenuStage.MENU_Tables) { sMyMenuChoices = sTableMenu; } while (iChoice != 0) { iChoice = MyUI.GetMenuInput(sMyMenuChoices); DecideFromMenuChoice(iChoice); } if (iChoice == 0) { _MyMenuStage = MenuStage.MENU_EXIT; } } } static private void DecideFromMenuChoice(int selection) { if (_MyMenuStage == MenuStage.MENU_CRUD) { switch (selection) { case 1: DoCreate(); break; case 2: DoRead(); break; case 3: DoUpdate(); break; case 4: DoDelete(); break; default: break; } } /* else if (_MyStage == MenuStage.MENU_TableSelect) { // should i really use enum? or IAmAnEntity interface variable switch (selection) { // not sure what to do here, somehow select a table to be used in CRUD functions? // do i need to call more CRUD functions for each new table I add? } }*/ } // ============================ // CRUD FUNCTIONS // ============================ static private void DoCreate() { int myID; bool bIsValidID; var dbEntities = new TestDatabaseEntities(); string sNewName; DataAccess MyDA = new DataAccess(dbEntities); DbResult CreationResult, SaveResult; do { bIsValidID = _MyUI.GetValidInput<int>("Enter ID: ", int.TryParse, out myID); } while (!bIsValidID); sNewName = _MyUI.GetInput<string>("Enter Name:", x => x.Trim()); CreationResult = MyDA.Create(new Man() {ManID = myID, Name = sNewName }); _MyUI.DisplayMessage(CreationResult.Message); if (!CreationResult.bSuccess) return; SaveResult = MyDA.SaveChanges(); _MyUI.DisplayMessage(SaveResult.Message); } static private void DoRead() { var dbEntities = new TestDatabaseEntities(); DataAccess MyDA = new DataAccess(dbEntities); string [,] Records; DbResult ReadResult; var query = from person in dbEntities.Men where true select person; ReadResult = MyDA.Read(query, out Records); if (ReadResult.bSuccess) { _MyUI.DisplayRecords(Records); } if (!ReadResult.bSuccess) _MyUI.DisplayMessage(ReadResult.Message); } static private void DoUpdate() { int myID = _MyUI.GetInput<int>("Enter ID to update: ", int.Parse); string sNewName = _MyUI.GetInput<string>("Enter new name: ", x => x.Trim()); try { using (var dbEntities = new TestDatabaseEntities()) { //TODO: If ManID is unique then we can use FirstOrDefault here. var allMatchingMen = from person in dbEntities.Men where person.ManID == myID select person; foreach (var man in allMatchingMen) man.Name = sNewName; dbEntities.SaveChanges(); _MyUI.DisplayMessage("Record(s) updated"); } } /*catch (OptimisticConcurrencyException ex) { //TODO: add auto-retry logic _MyUI.DisplayMessage("Someone updated the record, let's retry"); }*/ catch (Exception ex) { _MyUI.DisplayMessage("Something went wrong, could not update"); } } /*{ int myID; var dbEntities = new TestDatabaseEntities(); string sNewName = ""; DataAccess MyDA = new DataAccess(dbEntities); DbResult UpdateResult, SaveResult; myID = _MyUI.GetInput<int>("Enter ID to update: ", int.Parse); sNewName = _MyUI.GetInput<string>("Enter new name: ", x => x.Trim()); var query = from person in dbEntities.Men where person.ManID == myID select person; UpdateResult = MyDA.Update(query, new Man() { ManID = myID, Name = sNewName }); _MyUI.DisplayMessage(UpdateResult.Message); if (!UpdateResult.bSuccess) return; SaveResult = MyDA.SaveChanges(); _MyUI.DisplayMessage(SaveResult.Message); }*/ static private void DoDelete() { int myID; bool bValidInput; var dbEntities = new TestDatabaseEntities(); DataAccess MyDA = new DataAccess(dbEntities); DbResult DeleteResult, SaveResult; do { bValidInput = _MyUI.GetValidInput<int>("Enter ID to delete: ", int.TryParse, out myID); } while (!bValidInput); var Query = from person in dbEntities.Men where person.ManID == myID select person; DeleteResult = MyDA.Delete(Query); _MyUI.DisplayMessage(DeleteResult.Message); if (!DeleteResult.bSuccess) return; SaveResult = MyDA.SaveChanges(); _MyUI.DisplayMessage(SaveResult.Message); } } ## DataAccess Class ## public class DataAccess { private readonly TestDatabaseEntities _MyDBEntities; public DataAccess(TestDatabaseEntities entities) { _MyDBEntities = entities; } public DbResult DatabaseExists() { DbResult MyResult; if (_MyDBEntities.Database.Exists()) MyResult = DbResult.Success("Database Found"); else MyResult = DbResult.Failed("Database Not Found"); return MyResult; } // ============================ // CRUD FUNCTIONS for MAN TABLE // ============================ public DbResult Create(Man M) { DbResult DbRes; try { _MyDBEntities.Men.Add(new Man { ManID = M.ManID, Name = M.Name }); DbRes = DbResult.Success("Record created"); } catch (Exception e) { DbRes = DbResult.Failed(e.ToString()); } return DbRes; } public DbResult Update(IQueryable<Man> myQuery, Man man) { DbResult DbRes; try { foreach (Man M in myQuery) { M.Name = man.Name; } DbRes = DbResult.Success("Record updated"); } catch (Exception e) { DbRes = DbResult.Failed(e.ToString()); } return DbRes; } public DbResult Delete(IQueryable myQuery) { DbResult DbRes; try { foreach (Man M in myQuery) { _MyDBEntities.Men.Remove(M); } DbRes = DbResult.Success("Record deleted"); } catch (Exception e) { DbRes = DbResult.Failed(e.ToString()); } return DbRes; } public DbResult Read(IQueryable myQuery, out string[,] Records) { DbResult DbRes; Records = null; try { List<Man> men = myQuery.OfType<Man>().ToList(); Records = new string[men.Count, 2]; for (int i = 0; i < men.Count; i++) { Records[i, 0] = men[i].ManID.ToString(); Records[i, 1] = men[i].Name; } DbRes = DbResult.Success("Read Success"); } catch (Exception e) { DbRes = DbResult.Failed(e.ToString()); } return DbRes; } // ============================ // SAVECHANGES FUNCTION // ============================ public DbResult SaveChanges() { DbResult DbRes; try { _MyDBEntities.SaveChanges(); DbRes = DbResult.Success("Saved successfully"); } catch (Exception e) { DbRes = DbResult.Failed(e.ToString()); } return DbRes; } } ## UserInterface Class ## public class UserInterface { // ============================ // INPUT FUNCTIONS // ============================ public T GetInput<T>(string message, Converter<string, T> transform) { DisplayPrompt(message); return transform(Console.ReadLine()); } public bool GetValidInput<T>(string message, InputValidation.TryParse<T> TypeValidator, out T TResult, int upper = -1, int lower = -1) { InputValidation MyInputValidator = new InputValidation(); bool bIsValid = false; bool bTestValidRange = (upper != -1 && lower != -1); string sInput; sInput = GetInput(message, x => (x.Trim())); bIsValid = MyInputValidator.ValidateInputType(sInput, TypeValidator, out TResult); if (!bIsValid) { DisplayMessage("Error, invalid input type entered, type required is " + typeof(T).ToString()); return false; } if (bTestValidRange) bIsValid = MyInputValidator.ValidateInputRange(ref TResult, lower, upper); if (!bIsValid) { DisplayMessage("Error, input is out of range. Range: " + lower.ToString() + " - " + upper.ToString()); return false; } return bIsValid; } // ============================ // OUTPUT FUNCTIONS // ============================ public void DisplayMessage(string Msg) { Console.WriteLine(Msg); } public void DisplayPrompt(string Msg) { Console.Write(Msg); } public void DisplayRecords(string [,] Msg) { if (Msg == null) return; DisplayDivider(); for (int i = 0; i < Msg.GetLength(0); i++) { for (int j = 0; j < Msg.GetLength(1); j++) { Console.Write(Msg[i,j] + " "); } Console.Write("\n"); } DisplayDivider(); } public void DisplayMenuItems(string[] items) { byte ChoiceIndex = 0; DisplayDivider('~'); Console.WriteLine("Select an action from menu"); foreach (string s in items) { Console.WriteLine(ChoiceIndex++ + ") " + s); } DisplayDivider('~'); } public void DisplayDivider(char cCharacter = '|') { String sDivider = new String(cCharacter, 30); DisplayMessage(sDivider); } // ============================ // INPUT/OUTPUT // ============================ public int GetMenuInput(string[] sChoices) { int selection; bool bValid = false; int LowerBound = 0; int UpperBound = sChoices.Length - 1; DisplayMenuItems(sChoices); do { bValid = (GetValidInput<int>("Enter> ", int.TryParse, out selection, lower: LowerBound, upper: UpperBound)); } while (!bValid); return selection; } } ## InputValidation Class ## public class InputValidation//<T> where T: class { /// <summary> /// Delegate that matches the signature of TryParse, method defined for all primitives. /// </summary> /// <typeparam name="T">Output type of This Delegate</typeparam> /// <param name="input">input for this Delegate to translate to type T</param> /// <param name="output">The translated variable to return via out parameter</param> /// <returns>Whether the Parse was successful or not, and output as output</returns> public delegate bool TryParse<T>(string sInput, out T output); public bool ValidateInputType<T>(string sInput, TryParse<T> TypeValidator, out T result) { return TypeValidator(sInput, out result); } public bool ValidateInputRange<T>(ref T result, int lower, int upper) { // How can I use relational operators like > < = on T? without forcing T result to an int? return isValidRange(int.Parse(result.ToString()), lower, upper); } public bool isValidRange(int item, int Lower, int Upper) { return (Lower <= item && item <= Upper); } } ## DbResult (wrapper class) ## public class DbResult { public bool bSuccess { get; private set; } public String Message { get; private set; } private DbResult(bool success, string message) { this.bSuccess = success; this.Message = message; } public static DbResult Failed(string message) { return new DbResult(false, message); } public static DbResult Success(string message) { return new DbResult(true, message); } } ## Man Class ## public partial class Man { public Man() { this.Locations = new HashSet<Location>(); } public int ManID { get; set; } public string Name { get; set; } public virtual ICollection<Location> Locations { get; set; } } ## Location Class ## public partial class Location { public Location() { this.Men = new HashSet<Man>(); } public int PlaceID { get; set; } public string Place { get; set; } public virtual ICollection<Man> Men { get; set; } }