In the previous lesson, we saw the benefits of using a Tools Driven development approach, which is a great approach if you are looking at a departmentally scoped application that you need to create quickly. This approach requires very little logic, and is feasible for simple “CRUD” style database operations (create, read, update, delete), where you don't really anticipate any changes in the long term. However, not every application is like that. Many applications are not scoped to that of a small department, but rather at an Enterprise level. In that case, a fast turn-around is far less important than getting it right and having it flexible enough to anticipate inevitable changes in the system.
We’re looking at two different extremes to solving development problems. In reality, there are gradations – in between the completely Tools Driven and completely Maintenance Driven approaches – that utilize a bit of both in order to get the job done.
For this lesson, we will start with where we left off in Lesson 61 – backtracking away from what we learned about the Tools Driven approach, and re-assessing the problem through the lens of a Maintenance Driven approach instead. A Maintenance Driven approach should start with a logical separation of concerns between the three main layers. Start by renaming the project to “LocalDbExample.Presentation”:
Next, let’s add the two other layers by selecting from Visual Studio’s menu:
File > Add > New Project
In the New Project Dialogue, navigate to a Class Library, and make sure to change the selection for Solution to 'Add to Solution' not 'Create New Solution'. Name the file LocalDbExample.Domain:
Repeat these steps to create a LocalDbExample.Persistence layer as well. Afterwards, rename the Class1.cs files in these layers as follows:
At this point, it’s probably a good idea to build the Solution to make sure everything is set up correctly. The shortcut key for building a solution is:
Building is important so that each project can “see” the other’s and allow dependencies to be established. When one project takes a dependency from another one, it simply means it makes reference to it in code. Ideally, you will want to keep your code as independent as possible but a certain amount of dependency is going to be necessary. Set up a Domain dependency on the Persistence layer by right-clicking on “References” and selecting “Add Reference…”:
From here, select the Persistence layer as a dependency and after that, you should see it in your list of References under LocalDbExample.Domain:
Repeat this process, adding a dependency to the Domain layer in the Presentation layer’s References. Of course it would be better to have the Presentation, and Persistence layers depend on the Domain layer, which at least keeps the dependency chain central to the Domain layer. However, for now, we are going to simplify things and have the Presentation layer depend on the Domain layer, and the Domain Layer depend on the Persistence layer, represented in red arrows below:
Now we will have to re-create the Entity model in the Persistence layer. In the Solution Explorer, right-click on the LocalDbExample.Persistence layer and add a new item. From the available templates, select the ADO.NET Entity Data Model as we did in the previous lesson:
On the next screen choose “EF Designer from database”:
Next, we’re going to save the database connection settings to an App.config file (typically, you would not want to use an App.config but rather a Web.config for an actual web application. However, it will work for the purposes of this demonstration):
The next step asks if you would like to copy the file to the project, select “No” to this step:
At the next step select Entity Framework 6.x, and then click “Next”:
The setup wizard will then ask you which database object you would like to include in your model. Select all of the Tables, and then click “Finish”:
After that you will end up with an Entity model in the Persistence layer, complete with a Customer class it automatically created to model the database table of the same name:
Go now to the CustomerRepository.cs class and write a method that returns a List<Customer> obtained from the database via the Entities model:
Now in the Domain layer's CustomerManager.cs class, we will need to reference the Persistence layer through a using statement added to the top of the script:
And then in the CustomerManager class, write a method that simply calls Persistence.CustomerRepository.GetCustomers() and returns the List<Customer>:
At this point, we’ve exposed the Entity Framework Persistence layer to the Domain layer via this reference in the CustomerManager class. Exposing these inner workings is not a great idea, because we lose the clear separation of concerns that makes this approach valuable. What we need to do is add one more project called a Data Transfer Object Layer (DTO). The purpose of this layer is to abstract away the three main layers from each other, as they really should not have to know about one another to do their individual job. The DTO layer acts as the common reference point instead, and its dependency relationship to the other layers can be viewed as follows:
Let’s now add this DTO layer to our project – using the steps outlined previously in the lesson – calling it “LocalDbExample.DTO.” We will want to copy and paste the exact same class structure for the Customer class in the Persistence layer , and place it in the Class1.cs file (rename this file to Customer.cs as well):
Now we will want to add a reference to LocalDbExample.DTO under each other project’s “References” in the Solution Explorer (remember, each project should point to this one, rather than to one another).
The Persistence Layer's CustomerRepository class currently references the Presentation layer's Customer in the GetCustomers() method. What we should do instead at this point is reference the DTO.Customer class, instead of storing the pulled databased data to a local Customer Class reference. To do this, modify CustomerRepository as follows:
Retain the code that pulls database data and stores it to a List<Customer> object.
Create a new, locally scoped List<DTO.Customer> object.
In a foreach() create a temporary DTO.Customer object.
Populate each property for this object using the properties from the iterated List<Customer>
Add the DTO.Customer to the List<DTO.Customer>.
Return the List<DTO.Customer>, making sure to also change the return type in the method signature.
The Domain Layer is what calls GetCustomers(), but if you look at the method that initiates the call, you'll see that we're currently returning the wrong type, Persistence.Customer. Change the return type to a List<DTO.Customer> because the CustomerRepository will be returning that data to the caller.
Turning our attention now to the Presentation layer, first delete the ACMEEntities.edmx as we will be using the database from the Persistence layer instead:
Now change the Page_Load() method, within Default.aspx.cs, to reflect the reference to the Domain layer:
The last thing to do is set the Presentation layer as the entry-point for the application by right-clicking it and selecting from the menu “Set as Startup Project”:
Now, when you run the application you will see the same results as the previous lesson but with a cleaner, more separated application structure behind the scenes:
If you received an error upon running the application, there might be a mismatch between the connectionString stored in the App.config and Web.config files, respectively. You will find the connectionString in each file, represented here in App.config as “…” (your actual connection string will be a much longer):
To fix the error, simply (1) copy the entire connectionString from App.config in the Persistence layer and (2) paste it over the connectionString in Web.config in the Presentation layer.
You might be thinking that this is a lot of effort just to build a simple application. Especially because we already saw that it could be made more easily with a tools driven approach. While this methodology won't make a lot of sense for a simple application, keep in mind that applications are not typically this small. They are often very large, or in the case of Enterprise level applications, absolutely massive in scope. Think of the time spent setting up the code and keeping layers separate as an investment. Over time, when changes need to be made to your application, the time spent in this step will be well worth your while, because you won't need to completely rebuild your application from the ground up.
Lesson 64 - Using a Maintenance-Driven Approach to Building a Database Application