Click here to Skip to main content
13,700,737 members
Click here to Skip to main content
Add your own
alternative version

Stats

11K views
18 bookmarked
Posted 12 Feb 2018
Licenced CPOL

Scaffolding ASP.NET Core 2 with CatFactory

, 15 May 2018
Rate this:
Please Sign up or sign in to vote.
Scaffolding ASP.NET Core 2 with CatFactory

Introduction

Entity Framework Core is the version of Entity Framework for .NET Core, to develop large applications, there is a common issue: write code for many kinds of objects: entities, mappings, contracts, data contracts. CatFactory provides an easy way to scaffold code in order to get focus on complex logic.

CatFactory's History

In 2005 year, I was in my college days and worked on my final project that included a lot of tables. Those days, C# didn't have automatic properties, also I worked on stored procedures that included a lot of columns, I thought if there was a way to generate all that code because it was repetitive and I wasted time in writing a lot of code.

For 2006 beginning, I've worked for a company and I worked in a prototype to generate code but I didn't have experience and I was a junior developer, so I developed a version in WebForms that didn't allow to save the structure.:) That project was my first project in C# because I came from the VB world but I bought a book about Web Services in DotNet and that book used C# code, that was new for me but it got me a very important idea, learn C# and I wrote all first code generation form in C#.

Later, there was a prototype of Entity for SQL, the grandfather of entity framework and I develop a simple ORM because I had table class and other classes such as Column, so after reviewing Entity for SQL, I decided to add the logic to read database and provide a simple way to read the database also of code generation.

In 2008, I built the first ORM based on my code generation engine, at that time, it was called F4N1. I worked on an ORM that must endure different databases engines such as SQL Server, Sybase and Oracle; so I generated a lot of classes with that engine, for that time the automated unit tests did not exist, I had a webform page that generated that code. :) I know it was ugly and crappy but at that time, that was what my knowledge allowed me to do.

In 2011, I worked on a demo for a person that worked in his company and that person used another tool for code generation, so my code generation engine wasn't used for his work.

In 2012, I worked for a company that needed to rebuild all systems with new technologies (ASP.NET MVC and Entity Framework) so I invested time in MVC and EF learning but as usual, there wasn't time for that :) and again my code generation wasn't considered for that upgrade. =(

In 2014, I thought of making a nuget package to my code generation but in those days, I didn't have the focus to accomplish that feature and always I used my code generation as a private tool, in some cases, I shared my tool with some coworkers to generate code and reduce the time for code writing.

In 2016, I decided to create a nuget package and integrate it with EF Core, using all experience from 10 years ago. :D

Please remember that from the beginning, I was continuing to improve the way of code generation, my first code was crap but with the timeline, I've improved the design and naming for objects.

Why I named CatFactory? It was because I had a cat, her name was Mindy and that cat had many kittens (sons), so the basic idea was the code generation engine generates the code as fast as Mindy provided kittens :).

Source picture for "CatFactory" concept =^^=

Behind CatFactory Concept

Background

Code generation is a common technique developers use to reduce time in code writing, I know that most programmers build a code generator in their professional lives.

EF 6.x had a wizard for code generation, that tool generates DbContext and POCOs but there isn't code for Fluent API, Repositories and other things like those; with .NET Core, there is a command line tool for code generation but we have the same scenario, there is generation only for DbContext and Entities; with CatFactory, we're looking for a simple way to generate code with enterprise patterns, please don't forget this is an alpha version of CatFactory, don't pretend to have in this date a full version of code generation engine.

Why don't we use code CodeDOM? CodeDOM is a complex code generation engine, I don't saying CodeDOM sucks or something like that, but at this moment, we're focusing on generating code in a simpler way, maybe in the incoming versions, we'll add integration with CodeDOM.

Skills Prerequisites

  • OOP
  • AOP
  • ORM
  • C#
  • Design Patterns (Repository and Unit of Work)

Software Prerequisites

  • IDE (Visual Studio 2015 or Visual Studio 2017 or VS Code)
  • Access to valid SQL Server instance

Using the Code

Please follow these steps to scaffold Entity Framework Core with CatFactory.

Step 01 - Create Sample Database

I'm using a sample database named Store, but you need to change the connection string according to your environment, project name and output directory, please make sure that output directory exists and you have permissions to write in that directory, please make sure about that to avoid common errors.

In order to scaffold code, run the following script on your SQL Server instance:

create database [Store]
go

use Store
go

create schema [HumanResources]
go

create schema [Production]
go

create schema [Sales]
go

create table [dbo].[EventLog]
(
	[EventLogID] uniqueidentifier not null,
	[EventType] int not null,
	[Key] varchar(255) not null,
	[Message] varchar(max) not null,
	[EntryDate] datetime not null
)

create table [dbo].[ChangeLog]
(
	[ChangeLogID] int not null identity(1, 1),
	[ClassName] varchar(255) not null,
	[PropertyName] varchar(255) not null,
	[Key] varchar(255) not null,
	[OriginalValue] varchar(max) null,
	[CurrentValue] varchar(max) null,
	[UserName] varchar(25) not null,
	[ChangeDate] datetime not null
)

create table [dbo].[ChangeLogExclusion]
(
	[ChangeLogExclusionID] int not null identity(1, 1),
	[EntityName] varchar(128) not null,
	[PropertyName] varchar(128) not null
)

create table [dbo].[Country]
(
	[CountryID] int not null,
	[CountryName] varchar(100) not null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [dbo].[Currency]
(
	[CurrencyID] smallint not null identity(1000, 1000),
	[CurrencyName] varchar(50) not null,
	[CurrencySymbol] varchar(1) not null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [dbo].[CountryCurrency]
(
	[CountryCurrencyID] int not null identity(1, 1),
	[CountryID] int not null,
	[CurrencyID] smallint not null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [HumanResources].[Employee]
(
	[EmployeeID] int not null identity(1, 1),
	[FirstName] varchar(25) not null,
	[MiddleName] varchar(25) null,
	[LastName] varchar(25) not null,
	[BirthDate] datetime not null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [HumanResources].[EmployeeAddress]
(
	[EmployeeAddressID] int not null identity(1, 1),
	[EmployeeID] int not null,
	[AddressLine1] varchar(50) not null,
	[AddressLine2] varchar(50) null,
	[City] varchar(25) not null,
	[State] varchar(25) not null,
	[ZipCode] varchar(5) null,
	[PhoneNumber] varchar(25) null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [HumanResources].[EmployeeEmail]
(
	[EmployeeEmailID] int not null identity(1, 1),
	[EmployeeID] int not null,
	[Email] varchar(50) not null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [Production].[ProductCategory]
(
	[ProductCategoryID] int not null identity(1, 1),
	[ProductCategoryName] varchar(100) not null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [Production].[Product]
(
	[ProductID] int not null identity(1, 1),
	[ProductName] varchar(100) not null,
	[ProductCategoryID] int not null,
	[UnitPrice] decimal(8, 4) not null,
	[Description] varchar(255) null,
	[Discontinued] bit not null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [Production].[Warehouse]
(
	[WarehouseID] varchar(5) not null,
	[WarehouseName] varchar(100) not null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [Production].[ProductInventory]
(
	[ProductInventoryID] int not null identity(1, 1),
	[ProductID] int not null,
	[WarehouseID] varchar(5) not null,
	[Quantity] int not null,
	[Stocks] int not null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [Sales].[OrderStatus]
(
	[OrderStatusID] smallint not null,
	[Description] varchar(100) not null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [Sales].[PaymentMethod]
(
	[PaymentMethodID] uniqueidentifier not null,
	[PaymentMethodName] varchar(50) not null,
	[PaymentMethodDescription] varchar(255) not null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [Sales].[Customer]
(
	[CustomerID] int not null identity(1, 1),
	[CompanyName] varchar(100) null,
	[ContactName] varchar(100) null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [Sales].[CustomerAddress]
(
	[CustomerAddressID] int not null identity(1, 1),
	[CustomerID] int not null,
	[AddressLine1] varchar(50) not null,
	[AddressLine2] varchar(50) null,
	[City] varchar(25) not null,
	[State] varchar(25) not null,
	[ZipCode] varchar(5) null,
	[PhoneNumber] varchar(25) null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [Sales].[CustomerEmail]
(
	[CustomerEmailID] int not null identity(1, 1),
	[CustomerID] int not null,
	[Email] varchar(50) not null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [Sales].[Shipper]
(
	[ShipperID] int not null identity(1, 1),
	[CompanyName] varchar(100) null,
	[ContactName] varchar(100) null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [Sales].[Order]
(
	[OrderID] bigint not null identity(1, 1),
	[OrderStatusID] smallint not null,
	[CustomerID] int not null,
	[EmployeeID] int null,
	[ShipperID] int null,
	[OrderDate] datetime not null,
	[Total] decimal(12, 4) not null,
	[CurrencyID] smallint null,
	[PaymentMethodID] uniqueidentifier null,
	[Comments] varchar(max) null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)

create table [Sales].[OrderDetail]
(
	[OrderDetailID] bigint not null identity(1, 1),
	[OrderID] bigint not null,
	[ProductID] int not null,
	[ProductName] varchar(255) not null,
	[UnitPrice] decimal(8, 4) not null,
	[Quantity] int not null,
	[Total] decimal(8, 4) not null,
	[CreationUser] varchar(25) not null,
	[CreationDateTime] datetime not null,
	[LastUpdateUser] varchar(25) null,
	[LastUpdateDateTime] datetime null,
	[Timestamp] rowversion null
)
go

alter table [dbo].[EventLog]
	add constraint [PK_EventLog] primary key (EventLogID)
go

alter table [dbo].[ChangeLog]
	add constraint [PK_ChangeLog] primary key (ChangeLogID)
go

alter table [dbo].[ChangeLogExclusion]
	add constraint [PK_ChangeLogExclusion] primary key(ChangeLogExclusionID)

alter table [dbo].[Country]
	add constraint [PK_Country] primary key([CountryID])
go

alter table [dbo].[Currency]
	add constraint [PK_Currency] primary key([CurrencyID])
go

alter table [dbo].[CountryCurrency]
	add constraint [PK_CountryCurrency] primary key([CountryCurrencyID])
go

alter table [HumanResources].[Employee]
	add constraint [PK_HumanResources_Employee] primary key (EmployeeID)
go

alter table [HumanResources].[EmployeeAddress]
	add constraint [PK_HumanResources_EmployeeAddress] primary key (EmployeeAddressID)
go

alter table [HumanResources].[EmployeeEmail]
	add constraint [PK_HumanResources_EmployeeEmail] primary key (EmployeeEmailID)
go

alter table [Production].[ProductCategory]
	add constraint [PK_Production_ProductCategory] primary key (ProductCategoryID)
go

alter table [Production].[Product]
	add constraint [PK_Production_Product] primary key (ProductID)
go

alter table [Production].[Product]
	add constraint [U_Production_Product_ProductName] unique (ProductName)
go

alter table [Production].[ProductInventory]
	add constraint [PK_Production_ProductInventory] primary key (ProductInventoryID)
go

alter table [Production].[Warehouse]
	add constraint [PK_Production_Warehouse] primary key (WarehouseID)
go

alter table [Sales].[Customer]
	add constraint [PK_Sales_Customer] primary key (CustomerID)
go

alter table [Sales].[CustomerAddress]
	add constraint [PK_Sales_CustomerAddress] primary key (CustomerAddressID)
go

alter table [Sales].[CustomerEmail]
	add constraint [PK_Sales_CustomerEmail] primary key (CustomerEmailID)
go

alter table [Sales].[Shipper]
	add constraint [PK_Sales_Shipper] primary key (ShipperID)
go

alter table [Sales].[OrderStatus]
	add constraint [PK_Sales_OrderStatus] primary key (OrderStatusID)
go

alter table [Sales].[PaymentMethod]
	add constraint [PK_Sales_PaymentMethod] primary key (PaymentMethodID)
go

alter table [Sales].[Order]
	add constraint [PK_Sales_Order] primary key (OrderID)
go

alter table [Sales].[OrderDetail]
	add constraint [PK_Sales_OrderDetail] primary key (OrderDetailID)
go

alter table [dbo].[CountryCurrency]
	add constraint [U_CountryCurrency] unique (CountryID, CurrencyID)
go

alter table [Sales].[OrderDetail]
	add constraint [U_Sales_OrderDetail] unique (OrderID, ProductID)
go

alter table [ChangeLogExclusion]
	add constraint [U_ChangeLogExclusion] unique(EntityName, PropertyName)
go

alter table [dbo].[CountryCurrency]
	add constraint [FK_CountryCurrency_Country] foreign key (CountryID)
		references [dbo].[Country]
go

alter table [dbo].[CountryCurrency]
	add constraint [FK_CountryCurrency_Currency] foreign key (CurrencyID)
		references [dbo].[Currency]
go

alter table [Production].[Product]
	add constraint [FK_Production_Product_ProductCategory] foreign key (ProductCategoryID)
		references [Production].[ProductCategory]
go

alter table [Production].[ProductInventory]
	add constraint [FK_Production_ProductInventory_Product] foreign key (ProductID)
		references [Production].[Product]
go

alter table [Production].[ProductInventory]
	add constraint [FK_Production_ProductInventory_Warehouse] foreign key (WarehouseID)
		references [Production].[Warehouse]
go

alter table [HumanResources].[EmployeeAddress]
	add constraint [FK_HumanResources_EmployeeAddress] foreign key (EmployeeID)
		references [HumanResources].[Employee]
go

alter table [HumanResources].[EmployeeEmail]
	add constraint [FK_HumanResources_EmployeeEmail] foreign key (EmployeeID)
		references [HumanResources].[Employee]
go

alter table [Sales].[CustomerAddress]
	add constraint [FK_Sales_CustomerAddress] foreign key (CustomerID)
		references [Sales].[Customer]
go

alter table [Sales].[CustomerEmail]
	add constraint [FK_Sales_CustomerEmail] foreign key (CustomerID)
		references [Sales].[Customer]
go

alter table [Sales].[Order]
	add constraint [FK_Sales_Order_OrderStatus] foreign key (OrderStatusID)
		references [Sales].[OrderStatus]
go

alter table [Sales].[Order]
	add constraint [FK_Sales_Order_Customer] foreign key (CustomerID)
		references [Sales].[Customer]
go

alter table [Sales].[Order]
	add constraint [FK_Sales_Order_Employee] foreign key (EmployeeID)
		references [HumanResources].[Employee]
go

alter table [Sales].[Order]
	add constraint [FK_Sales_Order_Shipper] foreign key (ShipperID)
		references [Sales].[Shipper]
go

alter table [Sales].[Order]
	add constraint [FK_Sales_Order_Currency] foreign key (CurrencyID)
		references [dbo].[Currency]
go

alter table [Sales].[Order]
	add constraint [FK_Sales_Order_PaymentMethod] foreign key (PaymentMethodID)
		references [Sales].[PaymentMethod]
go

alter table [Sales].[OrderDetail]
	add constraint [FK_Sales_OrderDetail_Order] foreign key (OrderID)
		references [Sales].[Order]
go

alter table [Sales].[OrderDetail]
	add constraint [FK_Sales_OrderDetail_Product] foreign key (ProductID)
		references [Production].[Product]
go

Step 02 - Create Project

Create a console application for .NET Core, in some cases, you can add one project to your existing solution but with some name or suffix that indicates it's a project to scaffold, for example: Store.CatFactory.AspNetCore.

Step 03 - Add Packages for Project

Add the following packages in your project:

Name Version Description
CatFactory.SqlServer 1.0.0-beta-sun-build02 Provides import database feature for SQL Server
CatFactory.AspNetCore 1.0.0-beta-sun-build02 Provides scaffolding for ASP.NET Core and Entity Framework Core

Step 03 - Scaffolding

Once we have the packages installed on our project, we can add code in Main method:

// Import database
var database = SqlServerDatabaseFactory
    .Import(LoggerMocker.GetLogger<SqlServerDatabaseFactory>(), "server=(local);database=Store;integrated security=yes;", "dbo.sysdiagrams");

// Create instance of Entity Framework Core Project
var entityFrameworkProject = new EntityFrameworkCoreProject
{
    Name = "Store",
    Database = database,
    OutputDirectory = "C:\\Temp\\CatFactory.AspNetCore\\Store.Core"
};

// Apply settings for project
entityFrameworkProject.GlobalSelection(settings =>
{
    settings.ForceOverwrite = true;
    settings.ConcurrencyToken = "Timestamp";
    settings.AuditEntity = new AuditEntity("CreationUser", "CreationDateTime", "LastUpdateUser", "LastUpdateDateTime");
});

entityFrameworkProject.Select("Sales.Order", settings => settings.EntitiesWithDataContracts = true);

// Build features for project, group all entities by schema into a feature
entityFrameworkProject.BuildFeatures();

// Scaffolding =^^=
entityFrameworkProject
    .ScaffoldEntityLayer()
    .ScaffoldDataLayer();

var aspNetCoreProject = entityFrameworkProject
    .CreateAspNetCoreProject("Store.Api", "C:\\Temp\\CatFactory.AspNetCore\\Store.Api", entityFrameworkProject.Database);

aspNetCoreProject.ScaffoldAspNetCore();

Step 04 - Create Web API Project

Code Review

We'll take a look at the scaffold code:

SalesController class:

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Store.DataLayer.Contracts;
using Store.DataLayer.Repositories;
using Store.Api.Responses;
using Store.Api.RequestModels;
using Store.EntityLayer.Sales;
using Store.DataLayer.DataContracts;

namespace Store.Api.Controllers
{
 [Route("api/[controller]")]
 public class SalesController : Controller
 {
  protected readonly ISalesRepository Repository;
  protected ILogger Logger;

  public SalesController(ISalesRepository repository, ILogger<SalesController> logger)
  {
   Repository = repository;
   Logger = logger;
  }

  protected override void Dispose(Boolean disposing)
  {
   Repository?.Dispose();
   
   base.Dispose(disposing);
  }
  
  // Methods for another entities
  // ...

  [HttpGet("Order")]
  public async Task<IActionResult> GetOrdersAsync(Int32? pageSize = 10, Int32? pageNumber = 1, Int16? currencyID = null, Int32? customerID = null, Int32? employeeID = null, Int16? orderStatusID = null, Guid? paymentMethodID = null, Int32? shipperID = null)
  {
   Logger?.LogDebug("'{0}' has been invoked", nameof(GetOrdersAsync));
   
   var response = new PagedResponse<OrderDto>();
   
   try
   {
    // Get query from repository
    var query = Repository.GetOrders(currencyID, customerID, employeeID, orderStatusID, paymentMethodID, shipperID);
   
    // Set paging's information
    response.PageSize = (Int32)pageSize;
    response.PageNumber = (Int32)pageNumber;
    response.ItemsCount = await query.CountAsync();
   
    // Retrieve items by page size and page number, set model for response
    response.Model = await query.Paging(response.PageSize, response.PageNumber).ToListAsync();
   
    Logger?.LogInformation("Page {0} of {1}, Total of rows: {2}", response.PageNumber, response.PageCount, response.ItemsCount);
   }
   catch (Exception ex)
   {
    response.SetError(ex, Logger);
   }
   
   return response.ToHttpResponse();
  }

  [HttpGet("Order/{id}")]
  public async Task<IActionResult> GetOrderAsync(Int64? id)
  {
   Logger?.LogDebug("'{0}' has been invoked", nameof(GetOrderAsync));
   
   var response = new SingleResponse<OrderRequestModel>();
   
   try
   {
    // Retrieve entity by id
    var entity = await Repository.GetOrderAsync(new Order(id));
   
    if (entity != null)
    {
     response.Model = entity.ToRequestModel();
   
     Logger?.LogInformation("The entity was retrieved successfully");
    }
   }
   catch (Exception ex)
   {
    response.SetError(ex, Logger);
   }
   
   return response.ToHttpResponse();
  }

  [HttpPost("Order")]
  public async Task<IActionResult> PostOrderAsync([FromBody]OrderRequestModel requestModel)
  {
   Logger?.LogDebug("'{0}' has been invoked", nameof(PostOrderAsync));
   
   // Validate request model
   if (!ModelState.IsValid)
    return BadRequest(requestModel);
   
   var response = new SingleResponse<OrderRequestModel>();
   
   try
   {
    var entity = requestModel.ToEntity();
   
    // Add entity to database
    await Repository.AddOrderAsync(entity);
   
    response.Model = entity.ToRequestModel();
   
    Logger?.LogInformation("The entity was created successfully");
   }
   catch (Exception ex)
   {
    response.SetError(ex, Logger);
   }
   
   return response.ToHttpResponse();
  }

  [HttpPut("Order")]
  public async Task<IActionResult> PutOrderAsync(Int64? id, [FromBody]OrderRequestModel requestModel)
  {
   Logger?.LogDebug("'{0}' has been invoked", nameof(PutOrderAsync));
   
   // Validate request model
   if (!ModelState.IsValid)
    return BadRequest(requestModel);
   
   var response = new SingleResponse<OrderRequestModel>();
   
   try
   {
    // Retrieve entity by id
    var entity = await Repository.GetOrderAsync(new Order(id));
   
    if (entity != null)
    {
     // todo:  Check properties to update
     // Apply changes on entity
     entity.OrderStatusID = requestModel.OrderStatusID;
     entity.CustomerID = requestModel.CustomerID;
     entity.EmployeeID = requestModel.EmployeeID;
     entity.ShipperID = requestModel.ShipperID;
     entity.OrderDate = requestModel.OrderDate;
     entity.Total = requestModel.Total;
     entity.CurrencyID = requestModel.CurrencyID;
     entity.PaymentMethodID = requestModel.PaymentMethodID;
     entity.Comments = requestModel.Comments;
   
     // Save changes for entity in database
     await Repository.UpdateOrderAsync(entity);
   
     Logger?.LogInformation("The entity was updated successfully");
   
     response.Model = entity.ToRequestModel();
    }
   }
   catch (Exception ex)
   {
    response.SetError(ex, Logger);
   }
   
   return response.ToHttpResponse();
  }

  [HttpDelete("Order")]
  public async Task<IActionResult> DeleteOrderAsync(Int64? id)
  {
   Logger?.LogDebug("'{0}' has been invoked", nameof(DeleteOrderAsync));
   
   var response = new SingleResponse<OrderRequestModel>();
   
   try
   {
    // Retrieve entity by id
    var entity = await Repository.GetOrderAsync(new Order(id));
   
    if (entity != null)
    {
     // Remove entity from database
     await Repository.RemoveOrderAsync(entity);
   
     Logger?.LogInformation("The entity was deleted successfully");
   
     response.Model = entity.ToRequestModel();
    }
   }
   catch (Exception ex)
   {
    response.SetError(ex, Logger);
   }
   
   return response.ToHttpResponse();
  }
  
  // Methods for another entities
  // ...

There is a controller per feature, the sample database contains the following schemas: dbo, Production, HumanResources and Sales.

All actions in controllers invoke repository's methods.

This version requires EntityFramework, development team is working to allow other ORM such as Dapper.

Setting Up CatFactory for Asp.Net Core

Additionally, there are more settings for Entity Framework Core project instance, we'll take a look on those settings:

Name Description Default Value
ProjectName Sets the name for project
OutputDirectory Sets the output directory for project
Database Imported database

Features List

Name Description Supported
Scaffold Controllers Scaffold code for controllers (one controller per feature) Yes
Scaffold Request Models Scaffold request models (one request model per table) Yes
Scaffold Swagger Add documentation for controller's actions Not yet
Scaffold Unit Tests Scaffold xUnit tests for controllers Not yet
Scaffold Integration Tests Scaffold xUnit tests for controllers Not yet

How to Use CatFactory Framework

We need to understand the scope for CatFactory. In few words, CatFactory is the core for code generation. If we want to have more packages, we can create them with this naming convention: CatFactory.PackageName.

As we can see above, the basic flow for existing database on CatFactory is this:

  1. Set connection string
  2. Import database
  3. Create instance of Entity Framework Core project
  4. Build features (One feature per schema)
  5. Scaffold code for entity layer and data layer, these methods read all tables and views from database and create instances for code builders
  6. We'll work on common tasks with CatFactory, these will apply once we have a database imported from database server or we have created a database in memory.

Working with Database

// Import from existing database
var database = SqlServerDatabaseFactory.Import("YourConnectionStringHere");

// Read all tables
foreach (var table in database.Tables)
{
    // Check primary key on table's definition
    if (table.PrimaryKey == null)
    {
        continue;
    }
    
    // Check identity on table's definition
    if (table.Identity != null)
    {
        var identityName = table.Identity.Name;
    }
    
    // Read all columns
    foreach (var column in table.Columns)
    {
        // Get equivalent CLR type from column type
        var resolver = new ClrTypeResolver();
        
        var clrType = resolver.Resolve(column.Type);
    }
}

Workshop

This section allows to expose the new concepts for CatFactory and in order to know if they are "good" to improve user experience, please feel free to let me know your feedback about what you think about these improvements. :)

According to feedback from developers and to provide a better experience for users, I'm working on some improvements to get a cleaner way to work with CatFactory:

Event Handlers to Scaffold

In order to provide a more flexible way in scaffolding, there are two delegates in CatFactory, one to perform an action before of scaffolding and another one to handle an action after scaffolding.

Code Sample:

// Add event handlers to before and after of scaffold

project.ScaffoldingDefinition += (source, args) =>
{
    // Add code to perform operations with code builder instance before to create code file
};

project.ScaffoldedDefinition += (source, args) =>
{
    // Add code to perform operations after of create code file
};

Database Type Map

One of the things I don't like to get equivalent between SQL data type for CLR is to use magic strings, after reviewing the more "fancy" way to resolve a type equivalence is to have a class that allows to know the equivalence between SQL data type and CLR type.

Using this table as reference, now CatFactory has a class with name DatabaseTypeMap. Database class contains a property with all mappings with name Mappings, so this property is filled by Import feature for SQL Server package.

Class Definition:

public class DatabaseTypeMap
{
    public DatabaseTypeMap()
    {
    }

    public string DatabaseType { get; set; }

    public bool AllowsLengthInDeclaration { get; set; }

    public bool AllowsPrecInDeclaration { get; set; }

    public bool AllowsScaleInDeclaration { get; set; }

    public string ClrFullNameType { get; set; }

    public bool HasClrFullNameType
        => !string.IsNullOrEmpty(ClrFullNameType);

    [XmlIgnore]
    public Type ClrType
        => HasClrFullNameType ? Type.GetType(ClrFullNameType) : null;

    public string ClrAliasType { get; set; }

    public bool HasClrAliasType
        => !string.IsNullOrEmpty(ClrAliasType);

    public bool AllowClrNullable { get; set; }

    public DbType DbTypeEnum { get; set; }

    public bool IsUserDefined { get; set; }

    public string ParentDatabaseType { get; set; }
}

Code Sample:

// Get mappings
var mappings = DatabaseTypeMapList.Definition;

// Resolve CLR type
var mapsForString = mappings.Where(item => item.ClrType == typeof(string)).ToList();

// Resolve SQL Server type
var mapForVarchar = mappings.FirstOrDefault(item => item.DatabaseType == "varchar");

Project Selection

A project selection is a limit to apply settings for objects match with pattern.

GlobalSelection is the default selection for project, contains a default instance of settings.

Patterns:

Pattern Scope
Sales.Order Applies for specific object with name Sales.Order
Sales.* Applies for all objects inside of Sales schema
*.Order Applies for all objects with name Order with no matter schema
*.* Applies for all objects, this is the global selection

Code Sample:

// Apply settings for Project
project.GlobalSelection(settings =>
{
    settings.ForceOverwrite = true;
    settings.AuditEntity = new AuditEntity
         ("CreationUser", "CreationDateTime", "LastUpdateUser", "LastUpdateDateTime");
    settings.ConcurrencyToken = "Timestamp";
});

// Apply settings for specific object
project.Select("Sales.Order", settings =>
{
    settings.ForceOverwrite = true;
    settings.AuditEntity = new AuditEntity
         ("CreationUser", "CreationDateTime", "LastUpdateUser", "LastUpdateDateTime");
    settings.ConcurrencyToken = "Timestamp";
    settings.EntitiesWithDataContracts = true;
});

Points of Interest

  • CatFactory doesn't have command line for nuget because from my point of view, it will be a big trouble to allow set values for all settings because we have a lot of settings for EntityFrameworkCoreProjectSettings, I think at this moment it is simpler to create a console project to generate the code and then developer moves generated files for existing project and makes a code refactor if applies
  • CatFactory doesn't have UI now because at the beginning of this project, .NET Core had no standard UI, but we're working on UI for CatFactory, maybe we'll choose Angular =^^=
  • Now we are focused on Entity Framework Core, Dapper and Web API
  • We're working on continuous updates to provide better help for users

Related Links

Code Improvements

  • Allow to overwrite naming convention for project
  • Add author's information for output files
  • Add unit tests
  • Add integration tests

Trivia

  • The name for this framework it was F4N1 before CatFactory
  • Framework's name is related to kitties
  • Import logic uses sp_help stored procedure to retrieve the database object's definition, I learned that in my database course at college
  • Load mapping for entities with MEF, it's inspired in "OdeToCode" (Scott Allen) article for Entity Framework 6.x
  • Expose all settings in one class inside of project's definition is inspired on DevExpress settings for Web controls (Web Forms)
  • There are three alpha versions for CatFactory as reference for Street Fighter Alpha fighting game.
  • There will be two beta versions for CatFactory, the first with name Sun and second one with name Moon as reference for characters from The King of Fighters fighting game: Kusanagi Kyo and Yagami Iori.

History

  • 12th February, 2018: Initial version
  • 26th February, 2018: Update on scaffold code sample
  • 2nd May, 2018: Update for beta version
  • 16th May, 2018: Update for EF Core 2 and addition of version for Api

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

HHerzl
Software Developer
United States United States
Full Stack Developer with Experience in ASP.NET Core and Angular

You may also be interested in...

Comments and Discussions

 
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04-2016 | 2.8.180910.1 | Last Updated 16 May 2018
Article Copyright 2018 by HHerzl
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid