Share via


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.