Use Of Generics - Build Code Discipline In Repository Classes

Background

 
While there can be a number of usages of Generics in C# Classes, I have used it many a times to induce coding discipline and bring commonality in method signatures. As an Architect, I found it often difficult to control the burst of public methods written by multiple developers, specially when you are executing the project in Agile mode. Generics gives me a handle there. 
 
I primarily use it in the repository classes where you finally persist or fetch the data into your datastore. Whether you are using Entity Framework or any other ORM, ORACLE or SQL Server, this approach always works. So here we go!
 
Firstly I will define a Generic Class that would encapsulate all possible (that I could think of !)  Methods. This will become the base.
  1. public interface IEntityOperations<T> where T: new()  
  2. {  
  3.     //Get all the items of type T  
  4.     List<T> GetAll();  
  5.   
  6.     //Get all the items based on the Where condition. Use the dictionary input param to send the multiple  
  7.     //conditions, parameters etc.  
  8.     List<T> GetAllWhere(Dictionary<stringstring> kvpairs);  
  9.   
  10.     //Save the entity and return the PK of the newly created record  
  11.     string Save(T obj);  
  12.   
  13.     //Update based on the Where condition. Return whether the update was success or not.  
  14.     bool UpdateWhere(Dictionary<stringobject> kvSet, Dictionary<stringobject> kvWhere);  
  15.   
  16.     //Delete based on the where condition. Return whether the delete was success or not  
  17.     bool DeleteWhere(Dictionary<stringobject> kvWhere);  
  18.   
  19. }  
For my illustration, I have taken two simple entity class - A Student and a Department. The respective operations for each of this entity is named StudentOperation and DepartmentOperation.
  1. public class Student  
  2. {  
  3.     public long StudentID { getset; }  
  4.     public string FullName { getset; }  
  5.     public DateTime DOB { getset; }  
  6. }  
  7.   
  8. public class Department  
  9. {  
  10.     public long DepmtID { getset; }  
  11.     public string DepmtName { getset; }  
  12.     public string DepmtSubject { getset; }  
  13. }  
The code intellisense helps you implement the operation class, as soon as you inherit from the Generic class. Choose the option "Implement interface" and it will populate the full code for you. That rocks !
 
Use Of Generics - Build Code Discipline In Repository Classes
 
Following the above action, here are the operation classes. I have only implemented the Save() method for this blog purpose in both the operation classes. Also, note that in the DepartmentOperation class, I have added a new override method GetAllWhere() with a different signature, to show that you can write more public functions as you desire, as long as you complete the contract of the interface that you inherited.
  1. public class StudentOperation : IEntityOperations<Student>  
  2. {  
  3.     public bool DeleteWhere(Dictionary<stringobject> kvWhere)  
  4.     {  
  5.         throw new NotImplementedException();  
  6.     }  
  7.   
  8.     public List<Student> GetAll()  
  9.     {  
  10.         throw new NotImplementedException();  
  11.     }  
  12.   
  13.     public List<Student> GetAllWhere(Dictionary<stringstring> kvpairs)  
  14.     {  
  15.         throw new NotImplementedException();  
  16.     }  
  17.   
  18.     public string Save(Student obj)  
  19.     {  
  20.         Console.WriteLine("I am currently in " + nameof(Save) + " method of class " + nameof(StudentOperation));  
  21.         return obj.StudentID.ToString();  
  22.         //throw new NotImplementedException();  
  23.     }  
  24.   
  25.     public bool UpdateWhere(Dictionary<stringobject> kvSet, Dictionary<stringobject> kvWhere)  
  26.     {  
  27.         throw new NotImplementedException();  
  28.     }  
  29.   
  30. }  
  31.   
  32. public class DepartmentOperation : IEntityOperations<Department>  
  33. {  
  34.     public bool DeleteWhere(Dictionary<stringobject> kvWhere)  
  35.     {  
  36.         throw new NotImplementedException();  
  37.     }  
  38.   
  39.     public List<Department> GetAll()  
  40.     {  
  41.         throw new NotImplementedException();  
  42.     }  
  43.   
  44.     public List<Department> GetAllWhere(Dictionary<stringstring> kvpairs)  
  45.     {  
  46.         throw new NotImplementedException();  
  47.     }  
  48.   
  49.     //this is a over ride method just to show the implementation that you can write  
  50.     //more variance of functions based on your need.   
  51.     public List<Department> GetAllWhere(params object[] csv)  
  52.     {  
  53.         throw new NotImplementedException();  
  54.     }  
  55.   
  56.     public string Save(Department obj)  
  57.     {  
  58.         //throw new NotImplementedException();  
  59.         Console.WriteLine("I am currently in " + nameof(Save) + " method of class " + nameof(DepartmentOperation));  
  60.         return obj.DepmtID.ToString();  
  61.     }  
  62.   
  63.     public bool UpdateWhere(Dictionary<stringobject> kvSet, Dictionary<stringobject> kvWhere)  
  64.     {  
  65.         throw new NotImplementedException();  
  66.     }  
  67.   
  68. }  
 Finally, our client program (Console app), where we will make the calls to respective classes.
  1. class Program  
  2. {  
  3.     static void Main(string[] args)  
  4.     {  
  5.   
  6.         //Demo   
  7.         DemoOfGenerics();  
  8.   
  9.         Console.ReadLine();  
  10.     }  
  11.   
  12.     static void DemoOfGenerics()  
  13.     {  
  14.         Console.WriteLine("Student (S) / Department (D) ?");  
  15.         string choice = Console.ReadLine().ToUpper();  
  16.   
  17.         if (choice == "S")  
  18.         {  
  19.             IEntityOperations<Student> studentOperation = new StudentOperation();  
  20.             Student s = new Student() { StudentID = 12000, FullName = "Jack Turner", DOB = Convert.ToDateTime("12-Mar-2002") };  
  21.             var val = studentOperation.Save(s);  
  22.             Console.WriteLine("Student added with ID: S" + val.ToString());  
  23.         }  
  24.         else if (choice == "D")  
  25.         {  
  26.             IEntityOperations<Department> depmtOperation = new DepartmentOperation();  
  27.             Department d = new Department() { DepmtID = 45000, DepmtName = "Electronics Engineering", DepmtSubject = "Electronics" };  
  28.             var val = depmtOperation.Save(d);  
  29.             Console.WriteLine("Depmt added with ID: D" + val.ToString());  
  30.         }  
  31.         else  
  32.         {  
  33.             Console.WriteLine("Please insert only S or D.");  
  34.         }  
  35.     }  
  36. }  
Here is the Console output after running the program with the different input options,
  1. Student (S) / Department (D) ?  
  2. s  
  3. I am currently in Save method of class StudentOperation  
  4. Student added with ID: S12000  
  5.   
  6. Student (S) / Department (D) ?  
  7. d  
  8. I am currently in Save method of class DepartmentOperation  
  9. Depmt added with ID: D45000  

Conclusion

  1. You can bring in discipline in method definitions and avoid unnecessary public methods explosion. This approach is handy when you are working with a large team or a complex / mission critical project code.
  2. You are free to add more overrides of the methods
  3. You can assess the commonality of the methods and create common utility functions. i.e. If the Save() method has similar checks of valid parameters or the Get() methods have similar methods for null colaesing; you can create common functions for such operations and further improve reusability.
  4. The generics can play an important role if the logic to chose the entity is dynamically decided. 
 
Please share your comments and let me know your feedback. It would be great if you can share your user stories that you think fit the bill, or have a question on how to do it. I would love to learn. Until then, stay safe and stay blessed!
 
Thank You !