Skip to main content
6 of 8
corrected date of edit
Matt Rohde
  • 455
  • 2
  • 6
  • 17

Trying to clearly seperate my logic in my program

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.

My problem is I need:

  • clear separation of logic
  • code reusability
  • cleanliness
  • single responsibility principle

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.
  • 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()
    {
        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;
    }

    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 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;
    }

    // ============================
    // OUTPUT FUNCTIONS
    // ============================
    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);
    }

    // ============================
    // INPUT/OUTPUT
    // ============================
    private int GetMenuInput(string[] myOptions)
    {
        int choice;
        bool isValid = false;
        int lowerBound = 0;
        int upperBound = myOptions.Length - 1;

        DisplayMenuOptions(myOptions);

        do
        {
            isValid = (GetValidInput<int>("Enter choice> ", int.TryParse, out choice, lower: lowerBound, upper: upperBound));
        } while (!isValid);
        return choice;
    }

    public int GetUserAction()
    {
        string[] menuOptions;
        int choice = -1;

        //TODO: implement logic that Chooses which MenuStage we're at.

        menuOptions = DecideMenuOptions();

        choice = GetMenuInput(menuOptions);

        if (choice == 0)
        {
            _MyMenuStage = MenuStage.MENU_EXIT;
        }

        return choice;
    }

    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/122 9:01 CST: Improved seperation of logic in UserInterface class and Program Class. At least I think so.

Matt Rohde
  • 455
  • 2
  • 6
  • 17