Visual Studio: structuring an Entity Framework project for Windows forms
Introduction
When creating a windows form project or a class project for interacting with a backend database using code first approach scaffolding generates a model class and entity classes in the base project folder.
This article intention is to provide insight on how to organize generated class files into a manageable structure.
Standard folder structure
The following image of a solution depicts the organization of Entity Framework class.
Issues with a standard folder structure
The issues are as a project grows Interfaces and other classes added to the solution can make maintaining the solution difficult. Introducing more databases can be troublesome too.
Moving classes to folders
In the following image the model/context class has been moved to a folder named Contexts by right clicking on the project name in solution explorer to create the folder. Next, select NorthWindContext.vb from the main project folder and drag the file to the new folder.
Next a folder is created for the model.
The next step is to place each of the classes into one namespace no matter the folder. In this case the namespace is NorthWindModel.
The context class.
Imports System.Data.Entity
Imports NorthWind.Data.NorthWindModel
Namespace Contexts
Partial Public Class NorthWindContext
Inherits DbContext
Public Sub New()
MyBase.New("name=NorthWindModel")
End Sub
Public Overridable Property Categories As DbSet(Of Category)
One of the table classes.
Imports System.ComponentModel.DataAnnotations
Imports System.ComponentModel.DataAnnotations.Schema
Namespace NorthWindModel
<Table("Contact")>
Partial Public Class Contact
Public Sub New()
ContactContactDevices = New HashSet(Of ContactContactDevice)()
Customers = New HashSet(Of Customer)()
End Sub
<Key>
Public Property ContactIdentifier As Integer
Namespace separation
Using a namespace allows a developer to use another database with Entity Framework where there may be common class names. In the Visual Studio solution provided there are two databases setup with Entity Framework 6 (same works for Entity Framework Core).
In this solution both databases have a country class as per below. If there were two databases with classes in the main project folder in this case one of the country classes would not survive physically and moving one country class to another folder does not change scope, they need to be in different namespaces.
Imports System.ComponentModel.DataAnnotations
Imports System.ComponentModel.DataAnnotations.Schema
Namespace HumanResourcesModel
Partial Public Class Country
Public Sub New()
locations = New HashSet(Of Location)()
End Sub
<Key>
<Column("Country_id")>
<StringLength(2)>
Public Property CountryId As String
<StringLength(40)>
<Column("Country_name")>
Public Property CountryName As String
<Column("Region_id")>
Public Property RegionId As Integer
Public Overridable Property Region As Region
Public Overridable Property Locations As ICollection(Of Location)
End Class
End Namespace
And
Namespace NorthWindModel
Partial Public Class Country
Public Sub New()
Customers = New HashSet(Of Customer)()
End Sub
Public Property Id As Integer
Public Property CountryName As String
Public Overridable Property Customers As ICollection(Of Customer)
End Class
End Namespace
Working data
A best practice is to separate data operations from the model into folders. In this case there are two database operations done in separate folders.
North wind operations
Imports NorthWind.Data.Contexts
Namespace NorthWindOperations
Public Class Operations
Public Function CustomerJoined() As List(Of CustomerEntity)
Using context = New NorthWindContext()
Return (
From customer In context.Customers
Join contactType In context.ContactTypes On customer.ContactTypeIdentifier _
Equals contactType.ContactTypeIdentifier
Join contact In context.Contacts On customer.ContactIdentifier _
Equals contact.ContactIdentifier
Join country In context.Countries On customer.CountryIdentfier _
Equals country.Id
Select New CustomerEntity With
{
.CustomerIdentifier = customer.CustomerIdentifier,
.CompanyName = customer.CompanyName,
.ContactIdentifier = customer.ContactIdentifier,
.FirstName = contact.FirstName,
.LastName = contact.LastName,
.ContactTypeIdentifier = contactType.ContactTypeIdentifier,
.ContactTitle = contactType.ContactTitle,
.Address = customer.Street,
.City = customer.City,
.PostalCode = customer.PostalCode,
.CountryIdentifier = customer.CountryIdentfier,
.CountyName = country.CountryName}
).ToList()
End Using
End Function
End Class
End Namespace
Then for human resources
Imports NorthWind.Data.HumanResourcesModel
Namespace HumanResourcesOperations
Public Class Operations
Public Function EmployeeJoined() As List(Of EmployeeItem)
Using context As New HumanResourcesContext
Return (From employee In context.Employees
Join dept In context.Departments On employee.DepartmentId _
Equals dept.DepartmentId
Join job In context.Jobs On employee.JobId _
Equals job.JobId
Select New EmployeeItem With
{
.Id = employee.EmployeeId,
.Title = job.JobTitle,
.FirstName = employee.FirstName,
.LastName = employee.LastName,
.Department = dept.DepartmentName,
.Location = dept.Location
}
).ToList()
End Using
End Function
End Class
End Namespace
Even though both database data operations have the same class name they are separated by a namespace which means to use both in the same project use the appropriate namespace. Keeping things simple a test project is used to show how to use both databases in the same class.
Imports NorthWind.Data
<TestClass()> Public Class UnitTest1
<TestMethod()> Public Sub HumanResourcesTest()
Dim operations As New HumanResourcesOperations.Operations
Dim employeeItems = operations.EmployeeJoined()
Assert.IsTrue(employeeItems.Count = 40,
"Expected 40 employees items")
End Sub
<TestMethod()> Public Sub NorthWindTest()
Dim operations As New NorthWindOperations.Operations
Dim customerItems = operations.CustomerJoined()
Assert.IsTrue(customerItems.Count = 98,
"Expected 98 customer items")
End Sub
End Class
In a production application import aliasing would be better than having to type out the full namespace. Here North and Human are aliases to the actual namespaces.
Imports North = NorthWind.Data.NorthWindOperations
Imports Human = NorthWind.Data.HumanResourcesOperations
<TestClass()> Public Class UnitTest1
<TestMethod()> Public Sub HumanResourcesTest()
Dim operations As New Human.Operations
Dim employeeItems = operations.EmployeeJoined()
Assert.IsTrue(employeeItems.Count = 40,
"Expected 40 employees items")
End Sub
<TestMethod()> Public Sub NorthWindTest()
Dim operations As New North.Operations
Dim customerItems = operations.CustomerJoined()
Assert.IsTrue(customerItems.Count = 98,
"Expected 98 customer items")
End Sub
End Class
Summary
This article provided alternate folder solution for a Visual Studio solution with one or more Entity Framework models which provides separation of models using physical folders and namespaces which allows models to live harmoniously in one Visual Studio solution. An alternate would be to create two distinct class project, one for each model while a limitation would be having to access both class projects in a front end project to gather data rather than from one main namespace. Both have their places, this article has offered a alternate solution in one class project.
See also
Entity Framework: Wiki portal
Entity Framework TechNet
Source code
GitHub repository which has data scripts in the folder beneath the solution by the name of DatabaseScripts. Before running the scripts, inspect them along with the database connections in the class project and test project to match your database server be it SQL-Express, SQL-Server or localdb.