## My problem is I need to know ## - is my code in my CRUD functions structured well and effectively? I plan to implement CRUD for Locations entity too. hopefully without redundant code. - you see how one CRUD function in the program class calls the other CRUD function in the DataAccess Class? I"m trying to set it up so that i can control which entity gets edited or read in the control class, and access it in the dataAccess class. Is this bad logic? 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. - **ServiceManager** - class that has a method that checks to see if SQL server is running, before doing LINQ calls to DataBase. - **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 CRUD functions as soon as I can get working clean logic for the CRUD functions that handle the Man class. And hopefully `Man` & `Location` can share the same CRUD functions. ## 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 Data-Accessing logic separate from User Interface logic. My thought was to pass whatever message needed to be shown to the user, to the Interface class for it to be displayed. ## Program Class ## class Program { 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); } do { DecideFromMenuChoice(_MyUI.GetUserAction()); } while (_MyUI._MyMenuStage != UserInterface.MenuStage.MENU_EXIT); } Console.ReadLine(); // press any key to exit, basically. } static private void DecideFromMenuChoice(int choice) { if (_MyUI._MyMenuStage == UserInterface.MenuStage.MENU_CRUD) { switch (choice) { case 1: DoCreate(); break; case 2: DoRead(); break; case 3: DoUpdate(); break; case 4: DoDelete(); break; default: break; } } /* else if (_MyUI._MyMenuStage == UserInterface.MenuStage.MENU_TABLE) { switch (choice) { // 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 isValidID; var dbEntities = new TestDatabaseEntities(); string newName; DataAccess MyDA = new DataAccess(dbEntities); DbResult CreationResult, SaveResult; do { isValidID = _MyUI.GetValidInput<int>("Enter ID: ", int.TryParse, out myID); } while (!isValidID); newName = _MyUI.GetInput<string>("Enter Name:", x => x.Trim()); CreationResult = MyDA.Create(new Man() {ManID = myID, Name = newName }); _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() { /* The code in this DoUpdate function is "simplified" and "cleaner" * than the commented block below, I'm told. is it?*/ int myID = _MyUI.GetInput<int>("Enter ID to update: ", int.Parse); string newName = _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 = newName; 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 newName = ""; DataAccess MyDA = new DataAccess(dbEntities); DbResult updateResult, saveResult; myID = _MyUI.GetInput<int>("Enter ID to update: ", int.Parse); newName = _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 isValidInput; var dbEntities = new TestDatabaseEntities(); DataAccess myDA = new DataAccess(dbEntities); DbResult deleteResult, saveResult; do { isValidInput = _MyUI.GetValidInput<int>("Enter ID to delete: ", int.TryParse, out myID); } while (!isValidInput); 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 dbResult; try { _MyDBEntities.Men.Add(new Man { ManID = M.ManID, Name = M.Name }); dbResult = DbResult.Success("Record created"); } catch (Exception e) { dbResult = DbResult.Failed(e.ToString()); } return dbResult; } public DbResult Update(IQueryable<Man> myQuery, Man man) { DbResult dbResult; try { foreach (Man M in myQuery) { M.Name = man.Name; } dbResult = DbResult.Success("Record updated"); } catch (Exception e) { dbResult = DbResult.Failed(e.ToString()); } return dbResult; } public DbResult Delete(IQueryable myQuery) { DbResult dbResult; try { foreach (Man M in myQuery) { _MyDBEntities.Men.Remove(M); } dbResult = DbResult.Success("Record deleted"); } catch (Exception e) { dbResult = DbResult.Failed(e.ToString()); } return dbResult; } public DbResult Read(IQueryable myQuery, out string[,] records) { DbResult dbResult; 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; } dbResult = DbResult.Success("Read Success"); } catch (Exception e) { dbResult = DbResult.Failed(e.ToString()); } return dbResult; } // ============================ // SAVECHANGES FUNCTION // ============================ public DbResult SaveChanges() { DbResult dbResult; try { _MyDBEntities.SaveChanges(); dbResult = DbResult.Success("Saved successfully"); } catch (Exception e) { dbResult = DbResult.Failed(e.ToString()); } return dbResult; } } ## UserInterface Class ## public class UserInterface { public enum MenuStage { MENU_EXIT, MENU_CRUD, MENU_TableSelect } public MenuStage _MyMenuStage{ get; private set; } public UserInterface() { _MyMenuStage = MenuStage.MENU_CRUD; } /// <summary> /// Prompts user for input with given message, and converts input to type T /// </summary> /// <typeparam name="T">Value type to convert to, and return</typeparam> /// <param name="message">Message to be printed to console</param> /// <param name="transform">The type conversion function to use on user's input</param> /// <returns>Type T</returns> public T GetInput<T>(string message, Converter<string, T> transform) { DisplayPrompt(message); return transform(Console.ReadLine()); } /// <summary> /// Asks the user for valid input /// </summary> /// <typeparam name="T">The type of result to return as out parameter</typeparam> /// <param name="message">The message to prompt the user with</param> /// <param name="errorMessage">The message to Display to user with if input is invalid</param> /// <param name="typeValidator">The TryParse function to use to test the input.</param> /// <returns>True if input is valid as per function given to TypeValidator, Result as type T</returns> public bool GetValidInput<T>(string message, InputValidation.TryParse<T> typeValidator, out T result, int upper = -1, int lower = -1) { InputValidation myInputValidator = new InputValidation(); bool isValid = false; bool shouldTestRange = (upper != -1 && lower != -1); string input; input = GetInput(message, x => (x.Trim())); isValid = myInputValidator.ValidateInputType(input, typeValidator, out result); if (!isValid) { DisplayMessage("Error, invalid input type entered, type required is " + typeof(T).ToString()); return false; } if (shouldTestRange) isValid = myInputValidator.ValidateInputRange(ref result, lower, upper); if (!isValid) { DisplayMessage("Error, input is out of range. Range: " + lower.ToString() + " - " + upper.ToString()); return false; } return isValid; } public void DisplayMessage(string message) { Console.WriteLine(message); } public void DisplayPrompt(string message) { Console.Write(message); } public void DisplayRecords(string[,] message) { if (message == null) return; DisplayDivider(); for (int i = 0; i < message.GetLength(0); i++) { for (int j = 0; j < message.GetLength(1); j++) { Console.Write(message[i, j] + " "); } Console.Write("\n"); } DisplayDivider(); } public void DisplayMenuOptions(string[] items) { byte index = 0; DisplayDivider('~'); Console.WriteLine("Select an action from menu"); foreach (string s in items) { Console.WriteLine(index++ + ") " + s); } DisplayDivider('~'); } public void DisplayDivider(char myChar = '|') { String myDivider = new String(myChar, 30); DisplayMessage(myDivider); } public int GetUserAction() { string[] menuOptions; int choice = -1; //TODO: implement logic that Chooses which MenuStage we're at. menuOptions = DecideMenuOptions(); DisplayMenuOptions(menuOptions); choice = GetInputFromRange(0, menuOptions.Length - 1); if (choice == 0) { _MyMenuStage = MenuStage.MENU_EXIT; } return choice; } private int GetInputFromRange(int lowerBound, int upperBound) { int choice; bool isValid = false; do { isValid = (GetValidInput<int>("Enter choice> ", int.TryParse, out choice, lower: lowerBound, upper: upperBound)); } while (!isValid); return choice; } /// <summary> /// Determines which menu options need to be displayed /// </summary> /// <returns>Menu options that need to be displayed</returns> private string[] DecideMenuOptions() { string[] menuCRUD = new string[] { "Exit", "Create", "Read", "Update", "Delete" }; string[] menuTables = new string[] { "Quit", "Men", "Locations" }; string[] myMenuChoices = null; if (_MyMenuStage == MenuStage.MENU_CRUD) { myMenuChoices = menuCRUD; } else if (_MyMenuStage == MenuStage.MENU_TableSelect) { myMenuChoices = menuTables; } return myMenuChoices; } } ## 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); } } ## ServiceManager Class ## public class ServiceManager { public DbResult SQLRunning() { DbResult myResult; ServiceController sc = new ServiceController("SQL Server (SQLEXPRESS)"); if (sc.Status == ServiceControllerStatus.Running) myResult = DbResult.Success("SQL Server is running"); else myResult = DbResult.Failed("SQL Server is NOT running."); return myResult; } } ## 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; } } ## **UPDATE:** ## 11/20/12 9:01 CST: Improved seperation of logic in UserInterface class and Program Class. At least I think so. 11/21/12 6:46 CST: Improved UserInterface class slightly.