⬆️ ⬇️

StubDb - database on stubs for rapid prototyping and not only

What is it about



Good day!



I would like to talk about StubDb, a library for rapid prototyping of applications and easy unit testing. It allows you to replace work with a real database with work with data stored in memory / file. This makes it possible to concentrate on the classes of the domain model, and not on the features of data storage. StubDb uses the Entity Framework Code First principle, which makes it convenient to share them, but can be used separately.



To write Data Persistence Layer, you need to spend a lot of effort. Working with the database is troublesome: from connecting and editing configs to writing queries. Previously, while ORMs were not yet so popular, it took a lot of time to write monotonous SQL queries. With the development of ORM, less time is spent directly on SQL, but instead you need to learn the features of the ORM frameworks themselves.

')

In theory, everything is simple: the programmer works with domain objects, changes in them are translated into the database easily and painlessly. But in practice, the features of frameworks often cause confusion and prostration. For example, at the moment on StackOverflow on ASP.NET MVC 77,852 issues, and on Entity Framework 33,276, less, but not significantly. But ideally, EF should just do the work unnoticed.



Of course, you can't do without a database. But when an application or a new feature is in the initial design and development stage, there is no need for a database. At this stage, it is enough: to have a domain model of classes, to store data in this model, together with interconnections between individual classes (one to one, many to many), to receive data from the domain model, taking into account these relationships. StubDb implements this minimum set of requirements. Using StubDb at the initial stage of development, you can avoid the difficulties of working with the database and ORM, but at the same time be able to store data in the domain model and easily change it without disturbing the performance of the application.



Why do you need it



Application Prototyping

Working with my projects, I wanted to be able to quickly create prototypes of running applications in order to quickly demonstrate them to potential users and get feedback. SutbDb is already used in this way in several projects and it is really convenient to work with it.



Unit testing

Unit tests can be another good use case: you can run unit tests for business logic on stubs, eliminating work with the database from testing. Work with the database can be tested separately in integration tests.



Stub mode for the application - deploy the application in 3 clicks

I had to work quite a lot on enterprise level projects and, unfortunately, in my practice this meant a big bunch of poorly and diversely written code that works in production and on which they earn real money. At the same time, to fix any bug, even in the user interface, you need to spend a lot of time deploying the project on the developer's machine. I wish that in an ideal world for the project it was possible to get the source code from the repository, change the operation mode to the developer mode in the config file and the project would immediately start.

With StubDb, this can be a reality if you implement Stub DAL along with real DAL, and, due to this, have the opportunity to work with the project without access to the real database, web services and other data sources used on the project. Using Dependency Injection, and implementing data access class interfaces (repositories), for real data and for stubs, you can easily switch from real data sources to stubs. Often, access to real data is not necessary, and sometimes it is difficult (no Internet connection, problems on the server), at this time you can switch to stubs. I have already started using StubDb in this mode on several projects. Time will tell how this is applicable in practice.



When teaching programming

Also StubDb can be useful when learning programming. For a person who is not yet familiar with databases, this may be a temporary solution so that you can concentrate on exploring other areas with an already working analogue of the database.



How to use it



It is best to show an example. Take as an example a simple domain model (DM):



public class Student { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public virtual List<Enrollment> Enrollments { get; set; } } public class Course { public int Id { get; set; } public string Title { get; set; } public int Credits { get; set; } public virtual Department Department { get; set; } public virtual List<Enrollment> Enrollments { get; set; } } public class Department { public int Id { get; set; } public string Name { get; set; } public decimal Budget { get; set; } public virtual List<Course> Courses { get; set; } } public class Enrollment { public int Id { get; set; } public Grade Grade { get; set; } public Student Student { get; set; } public Course Course { get; set; } } public enum Grade { NoGrade, A, B, C, D, F } 




There are students and courses in this domain model, each course has a corresponding department (department), a student can be enrolled for several courses through the Enrollment, if the student has completed the course, then the grade for this course will be stored in the Enrollment.



In order to work with the above DM example, let's create a context class. By analogy with the Entity Framework, you need to inherit from the base StubContext.



For each entity to which we will build separate requests, we create a property of the StubSet type, again, by analogy with EF.



It should turn out like this:



 public class SampleStubContext: StubContext { public StubSet<Course> Courses { get; set; } public StubSet<Department> Departments { get; set; } public StubSet<Enrollment> Enrollments { get; set; } public StubSet<Student> Students { get; set; } } 




These are all the minimum settings required for operation.



Now we add data to the context:



  var context = new SampleStubContext(); var departmentIt = new Department() {Budget = 40000, Name = "IT"}; var departmentMath = new Department() {Budget = 14000, Name = "Math"}; context.Departments.Add(departmentIt); context.Departments.Add(departmentMath); //now departmentMath.Id == 2 like with EF it is updated after adding to context var courseJavascript = new Course() {Department = departmentIt, Title = "JavaScript basics"}; var courseAlgorithms = new Course() {Department = departmentIt, Title = "Algorithms"}; var courseAlgebra = new Course() {Department = departmentMath, Title = "Algebra"}; context.Courses.Add(courseAlgebra); context.Courses.Add(courseAlgorithms); context.Courses.Add(courseJavascript); var studentAlex = new Student() {FirstName = "Alex", LastName = "Black"}; var studentPer = new Student() {FirstName = "Per", LastName = "Sundin"}; context.Students.Add(studentAlex); context.Students.Add(studentPer); context.Enrollments.Add(new Enrollment(){Student = studentAlex, Course = courseAlgebra, Grade = Grade.B}); context.Enrollments.Add(new Enrollment(){Student = studentAlex, Course = courseAlgorithms, Grade = Grade.A}); context.Enrollments.Add(new Enrollment() { Student = studentPer, Course = courseAlgorithms, Grade = Grade.C }); context.Enrollments.Add(new Enrollment() { Student = studentPer, Course = courseJavascript, Grade = Grade.A }); 




We have 2 departments Math and IT, in Math Algebra course, in IT JavaScript and Algorithms. There are two students Alex and Per, Alex is enrolled in the courses Algebra and Algorithms, Per - Algorithms and JavaScript



Let's write a couple of queries to the data in context:



  var departmentItFromContext = context.Departments.Query().Single(x => x.Name == "IT"); var coursesForIt = departmentItFromContext.Courses; //coursesForIt == { Javascript, Algorithms }; var courseAlgorithmsFromContext = context.Courses.Query(2).First(x => x.Title == "Algorithms"); var studentsForAlgorithms = courseAlgorithmsFromContext.Enrollments.Select(x => x.Student).ToList(); // studentsForAlgorithms == {Alex, Per} 




By requesting data from the domain model through the context, we obtain it taking into account the relationships between the objects. Courses for IT department - Javascript, Algorithms; students recorded on Algorithms - Alex and Per.



Change the data in context:



  var alexAlgorithmsEnrollment = context.Enrollments.Query() .FirstOrDefault(x => x.Student.FirstName == "Alex" && x.Course.Title == "Algorithms"); context.Enrollments.Remove(alexAlgorithmsEnrollment); var courseAlgorithmsFromContextUpdated = context.Courses.Query(2).First(x => x.Title.Contains("Algorithms")); var studentsForAlgorithmsUpdated = courseAlgorithmsFromContextUpdated.Enrollments.Select(x => x.Student); //studentsForAlgorithms == {Per} courseAlgorithms.Department = departmentMath; courseAlgorithms.Title = "Methods of optimisation"; context.Courses.Update(courseAlgorithms); var departmentMathFromContext = context.Departments.Query().Single(x => x.Name == "Math"); var coursesForMath = departmentMathFromContext.Courses; //coursesForMath == { Algebra, Methods of optimisation }; 




We have deleted Enrollment for Alex on Algorithms, after that Alex is no longer in the list of students subscribed to Algorithms. We changed the department for Algorithms, then this course appeared in the list of courses of its new department.



Examples of using



GitHub project: github.com/yegor-sytnyk/StubDb



For usability created StubDb NuGet package.



The text of the console application with the domain model from this article is here: gist.github.com/yegor-sytnyk/c3f9ba12f5bb6b188286 .



MyUniversity: github.com/yegor-sytnyk/MyUniversity . Fully working project based on a domain model from ContosoUniversity ASP.NET MVC tutorial. Implemented with DAL on StubDb, for example, this project shows how StubDb can be used in conjunction with the Entity Framework.



I will be glad to comments and suggestions. I hope this can be useful to someone else.



How it works



For those who are interested in implementation details.



Data from the DM is stored in Entities and Connections.



Entities are classes from the DM (Student, Course, etc.). For each type of entity, a dictionary is stored with all the current values ​​of this entity and the key of the entity id. As in the entity framework, an entity key can be called either Id or {Class Name} Id, for example StudentId, for Student entity. In order not to overly complicate the implementation, Id must have type Int.



Regardless of the type of connection (one to one, many to many, or one to many), each element of the collection of links stores data about the connections between two types of entities. For example, if a student has a list of courses for which he / she is subscribed, then the corresponding links for the student and the course will contain paired links {student (Id) -course (Id)}. Thus, it is possible to model all types of relationships: one to one, one to many, many to many. For the case of one to one, a single link element will be stored, for the case of a multiple link type, several links will be stored.



Using entities and relationships, you can write, read data from the context of DM, taking into account the relationship between objects.

When adding data using the StubSet's Add / Update methods, the internal entities / connections in the context are updated. When reading data through the Query method, navigation properties are initialized in entities, based on existing relationships. If, for example, a course has a department type property, then in the Query method there will be a search by relationships - is there a link that links the course with this ID to some of the departments. If there is such a connection, the corresponding department will be set, otherwise the department property will be set to null. According to the same principle, navigation properties with a collection of objects are initialized, for example, a list of enrollments for courses for a student.



There are a number of features, but this is better read in the documentation or see the source code on the project page.

Source: https://habr.com/ru/post/212437/



All Articles