Schema Design
All of the queries specified below can be found in the queries directory.
To help isolate a simple schema to use for proving out the goals of this repository, I built the following query to explore extended properties associated with a [Person].[Person]
object:
SELECT
*
FROM [Person].[Person] as person
LEFT JOIN [Person].[BusinessEntityContact] [contact]
ON [person].BusinessEntityID = [contact].PersonID
LEFT JOIN [Person].[EmailAddress] [email]
ON [person].BusinessEntityID = [email].BusinessEntityID
LEFT JOIN [Person].[Password] [password]
ON [person].BusinessEntityID = [password].BusinessEntityID
LEFT JOIN [Person].[PersonPhone] [phone]
ON [person].BusinessEntityID = [phone].BusinessEntityID
LEFT JOIN [HumanResources].[Employee] [employee]
ON [person].BusinessEntityID = [employee].BusinessEntityID
LEFT JOIN [Sales].[Customer] [customer]
ON [person].BusinessEntityID = [customer].PersonID
LEFT JOIN [Sales].[PersonCreditCard] [card]
ON [person].BusinessEntityID = [card].BusinessEntityID
From this, I determined that I wanted to capture simple personnel data for employees and their contact info. Employee
would have ContactInfo
and ContactInfo
would aggregate both the [Person].[EmailAddress]
and [Person].[PersonPhone]
tables.
I built the following query to explore extended properties associated with a [HumanResources].[Employee]
object:
SELECT
*
FROM [HumanResources].[Employee] [employee]
LEFT JOIN [HumanResources].[EmployeeDepartmentHistory] [deptHistory]
ON [employee].[BusinessEntityID] = [deptHistory].[BusinessEntityID]
LEFT JOIN [HumanResources].[Department] [department]
ON [deptHistory].[DepartmentID] = [department].[DepartmentID]
LEFT JOIN [HumanResources].[EmployeePayHistory] [pay]
ON [employee].[BusinessEntityID] = [pay].[BusinessEntityID]
LEFT JOIN [HumanResources].[JobCandidate] [candidate]
ON [employee].[BusinessEntityID] = [candidate].[BusinessEntityID]
LEFT JOIN [Person].[Person] [person]
ON [employee].[BusinessEntityID] = [person].[BusinessEntityID]
LEFT JOIN [Sales].[SalesPerson] [sales]
ON [employee].[BusinessEntityID] = [sales].[BusinessEntityID]
LEFT JOIN [Purchasing].[PurchaseOrderHeader] [po]
ON [employee].[BusinessEntityID] = [po].[EmployeeID]
ORDER BY [employee].[LoginID]
From this, I determined that I wanted to capture the current [HumanResources].[Department]
the Employee
currently belongs to as well.
Identification
In order to isolate the final properties for my schema models, I built queries that translated the AdventureWorks data into the format that I wanted. In addition to these properties, I also specified Source*
properties that would be used to facilitate data migration from AdventureWorks into my own database.
Department Schema
SELECT DISTINCT
CAST([department].[DepartmentID] as nvarchar(MAX)) [SourceId],
[department].[Name] [Name],
[department].[GroupName] [GroupName]
FROM [HumanResources].[Department] [department]
ORDER BY [department].[Name]
Employee Schema
SELECT
CAST([person].[BusinessEntityID] as nvarchar(MAX)) [SourceId],
CAST([history].[DepartmentID] as nvarchar(MAX)) [SourceDepartmentId],
[employee].[NationalIdNumber] [NationalId],
[person].[LastName] [LastName],
[person].[FirstName] [FirstName],
[person].[MiddleName] [MiddleName],
[employee].[LoginId] [Login],
[employee].[JobTitle] [JobTitle]
FROM [Person].[Person] [person]
LEFT JOIN [HumanResources].[Employee] [employee]
ON [person].[BusinessEntityID] = [employee].[BusinessEntityID]
LEFT JOIN [HumanResources].[EmployeeDepartmentHistory] [history]
ON [employee].[BusinessEntityID] = [history].[BusinessEntityID]
WHERE [person].[PersonType] = 'EM'
AND [history].[EndDate] IS NULL
ORDER BY [person].[LastName]
In the above query, I wanted to filter the results so that I only get:
- People who are Employees:
WHERE [person].[PersonType] = 'EM'
- EmployeeDepartmentHistory only for the current Department:
AND [history].[EndDate] IS NULL
Contact Info Schema
(
SELECT
CAST([person].[BusinessEntityID] as nvarchar(MAX)) [SourceEmployeeId],
CAST([phone].[PhoneNumber] as nvarchar(MAX)) [Value],
CAST([phoneType].[Name] as nvarchar(MAX)) [ContactType]
FROM [Person].[Person] [person]
LEFT JOIN [Person].[PersonPhone] [phone]
ON [person].[BusinessEntityID] = [phone].[BusinessEntityID]
LEFT JOIN [Person].[PhoneNumberType] [phoneType]
ON [phone].[PhoneNumberTypeID] = [phoneType].[PhoneNumberTypeID]
)
UNION
(
SELECT
CAST([person].[BusinessEntityID] as nvarchar(MAX)) [SourceEmployeeId],
CAST([email].[EmailAddress] as nvarchar(MAX)) [Value],
CAST('Email' as nvarchar(MAX)) [ContactType]
FROM [Person].[Person] [person]
LEFT JOIN [Person].[EmailAddress] [email]
ON [person].[BusinessEntityID] = [email].[BusinessEntityID]
)
ORDER BY [Value]
The above query merges the data from [Person.PersonPhone]
, [Person].[PhoneNumberType]
, and [Person].[EmailAddress]
into a common schema that can be used for ContactInfo
.
Implementation
To prepare the project for building out the data migration infrastructure, I initialized the App project with dotnet new webapi
. It serves as an API for the app schema and contains the Entity Framework configuration. It has the following structure:
- Schema - class definitions for entity models.
- Data - contains the
AppDbContext
Entity FrameworkDbContext
.- Config - contains
IEntityTypeConfiguration
entity configurations.
- Config - contains
- Migrations - contains the Entity Framework migrations.
- Services - contains classes that define entity service logic and a class for registering the services with the ASP.NET Core Dependency Injection service container.
- Controllers - contains API controllers that expose entity logic.