Bring2mind.CodeGen.Cli 0.2.3

There is a newer version of this package available.
See the version list below for details.
dotnet tool install --global Bring2mind.CodeGen.Cli --version 0.2.3
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest # if you are setting up this repo
dotnet tool install --local Bring2mind.CodeGen.Cli --version 0.2.3
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Bring2mind.CodeGen.Cli&version=0.2.3
nuke :add-package Bring2mind.CodeGen.Cli --version 0.2.3

Code generation

This tool uses Razor templating to generate code from a (SQL Server) database model. This is useful for scaffolding out your domain model, your services layer and/or other parts of your application that should always mirror your data model.

DNN Platform

The DNN Platform is a web application framework built on the .net framework. If you know how to create modules, it is a very powerful tool to build advanced web applications. The default templates supplied with this tool are intended to help module developers keep their code synchronized with their data model. The default templates use the PetaPoco patterns used by DNN (sometimes referred to as DAL2).

Opinionated

This project was borne out of frustration with limitations of similar tools and EF. And as a result it is highly opinionated. Below is a list of assumptions and how these work in the tool.

Module Qualifier

In the settings you supply a ModuleObjectQualifier. This is a string which should end with an underscore and precedes all DB items that you've created for your module. Tables, Views, Stored Procedures.

Widget vs Widgets

The tool assumes your tables use the naming convention of plural for the table. So Widgets, NOT Widget. This will automatically generate objects called "Widget" for your code.

Tables and Views

The tool assumes that if there is a View that is called "vw_MyModule_Widgets" and a table called "MyModule_Widgets" that these two are related. In fact it will assume that the view columns are a superset of the table columns. In the default templates this will generate a "WidgetBase" class tied to the table and a "Widget" class tied to the view. This encourages using the view for data retrieval and the table for data addition and updating.

Installation

Install this tool as a dotnet cli tool as follows:

dotnet tool install --global Bring2mind.CodeGen.Cli

You should then be able to run the tool in the directory of your project

codegen

Configuration

Use the .codegen.json file generated by the tool to fill in the following:

Parameter Description
Template A full path to the first template to run. Note that templates can call other templates, so you only need one as an entry point
OutputDirectory Relative path to where you wish the generator to write files
OrgName Organization name
ModuleName Module name
RootNameSpace Root namespace for your code
SiteConfig Full path to the web.config of the site you're using to develop on. If specified the tool will use this to parse the connection string, db owner and object qualifier. If not specified use those parameters below
ConnectionString Connectionstring of the SQL server on which you model your data
ObjectQualifier ObjectQualifier used in that database if any. Leave blank if not used.
DatabaseOwner Database owner of the schema. Normally dbo.
ModuleObjectQualifier Module's object qualifier (see above) that helps the tool to parse out which objects to use for code generation
EnumValues If some columns need to be mapped to Enums, you can specify that here. The code generator can insert the right type in code.
IncludeSqlScripts Include SqlDataProvider scripts. These will be written to a "Scripts" folder under the OutputDirectory. This tool will create a script that generates the tables and keys (Install), a script with views, functions and sprocs (Upgrade), and finally an uninstall script.

(Opinionated) DB pattern

The engine works best when you adhere to the following naming convention in your (SQL) database:

Tables           : [dbOwner].[objectQualifier or nothing][moduleQualifier][object in plural]
Views            : [dbOwner].[objectQualifier or nothing]vw_[moduleQualifier][object in plural]
SProcs/Functions : [dbOwner].[objectQualifier or nothing][moduleQualifier][anything]

The engine will look for tables and views that have the same object. The default templates will then "stack" them using inheritance with a base class pointing to the table and the derived class pointing to the view. The retrieval methods will then use the derived class and the create/update/delete methods will use the base class.

Note this means you need to include all columns from a table into the corresponding view. I.e. if you have a table dbo.MyModule_Widgets and a view dbo.vw_MyModule_Widgets then the engine expects the view to include all columns from the MyModule_Widgets table plus any other columns from parent tables for instance that you wish to include in the derived object. If you wish NOT to include all columns, then give the view another name to avoid confusion.

On another note: you do not need to have a table corresponding to a view nor the other way around. The matching logic is only there for convenience.

Templates

You can find a collection of templates in a zip file with each release of this tool on the Github page of this project. These templates should be cshtml (Razor) files and as such can be comprised of C# code. The template you specify in the settings is the "entry point" of your code generation. For the default templates you'd specify the "\path\to\Main.cshtml" file. This template itself is not rendered to a file. Instead it calls the following code to generate files:

Engine.RenderTemplate("MyTemplate.cshtml", "Path\\to\\file.cs", myObject);

Where the myObject object is used as a Model in the MyTemplate.cshtml file. As you can see this follows basic Razor rendering patterns.

Template class

All templates much inherit from this project's RazorTemplate or RazorTemplate<T> class where T is the Model type. These classes can be found in the Bring2mind.CodeGen.Cli.Razor namespace.

Base objects

You have these three objects available to you in your template:

Object Type Description
DnnDb Bring2mind.CodeGen.Cli.Data.Database This holds all information gathered about your data model.
Settings Bring2mind.CodeGen.Cli.Common.Settings The settings file described above. E.g. Settings.OrgName will give you the OrgName you put in your settings.
Engine Bring2mind.CodeGen.Cli.Razor.RazorEngine This gives you access to the rendering engine of this tool and allows you to fire off rendering of other templates.

Database Object

DnnDb holds the information to your data model. The main properties are:

Property Description
Objects List of your model's objects
ForeignObjects List of objects (i.e. tables) found in the database but not part of your model. This means all of DNN's tables. This can be used when referencing Portals or Modules tables in DNN
StoredProcedures List of stored procedures that are part of your project.

Note these are stored as Dictionaries where the name is the key.

Object Definition

Objects and ForeignObjects are available as Bring2mind.CodeGen.Cli.Data.ObjectDefinition type. The primary property of this class is the Table from which it is taken. Optionally it also has a View associated with it. During the reading of the database, the tool will attempt to gather as much information about it so that it is easy to create templates for it. Such as a Singular and Plural name, it's primary key, whether it has the DNN audit fields, whether it's a link table. This allows you to create powerful templates. E.g.

@inherits RazorTemplate<ObjectDefinition>
@using Bring2mind.CodeGen.Cli.Common
@using Bring2mind.CodeGen.Cli.Data
@using Bring2mind.CodeGen.Cli.Razor

...

namespace @(Settings.RootNameSpace).Repositories
{
	public partial class @(Model.SingularName)Repository : ServiceLocator<I@(Model.SingularName)Repository, @(Model.SingularName)Repository>, I@(Model.SingularName)Repository
    {
        protected override Func<I@(Model.SingularName)Repository> GetFactory()
        {
            return () => new @(Model.SingularName)Repository();
        }
        public IEnumerable<@(Model.SingularName)> Get@(Model.PluralName)(@(Model.GetScopeDeclaration(true, true, false, false)))
        {
            using (var context = DataContext.Instance())
            {
                var rep = context.GetRepository<@(Model.SingularName)>();
                return rep.Get(@(Model.GetScopeDeclaration(true, false, false, false)));
            }
        }
@foreach (KeyValuePair<String, ObjectDefinition> fo in Model.ForeignKeyObjects)
{
 if (fo.Key != Model.Scope)
 {
@:        public IEnumerable<@(Model.SingularName)> Get@(Model.PluralName)By@(fo.Value.SingularName)(@(Model.Table.Parameter(fo.Key, true, true, "")))
@:        {
@:            using (var context = DataContext.Instance())
@:            {
@:                return context.ExecuteQuery<@(Model.SingularName)>(System.Data.CommandType.Text,
@:                    "SELECT * FROM {databaseOwner}{objectQualifier}@Model.Prefix@Model.ModuleQualifier@Model.Name WHERE @(Model.Table.Parameter(fo.Key, false, false, ""))=@@0",
@:                    @(Model.Table.Parameter(fo.Key, false, true, "")));
@:            }
@:        }

...

which results for the Journal module in this code:

namespace Bring2mind.Test.Repositories
{
    public partial class CommentRepository : ServiceLocator<ICommentRepository, CommentRepository>, ICommentRepository
    {
        protected override Func<ICommentRepository> GetFactory()
        {
            return () => new CommentRepository();
        }
        public IEnumerable<Comment> GetComments()
        {
            using (var context = DataContext.Instance())
            {
                var rep = context.GetRepository<Comment>();
                return rep.Get();
            }
        }
        public IEnumerable<Comment> GetCommentsByJournal(int journalId)
        {
            using (var context = DataContext.Instance())
            {
                return context.ExecuteQuery<Comment>(System.Data.CommandType.Text,
                    "SELECT * FROM {databaseOwner}{objectQualifier}Journal_Comments WHERE JournalId=@0",
                    journalId);
            }
        }

...

The default templates are a good example of what can be done with this tool.

SQL Scripts

If selected the tool will also create install, upgrade and uninstall scripts for your project and write them to the "Scripts" folder. This is divided as follows:

Script Included items Role
Install.SqlDataProvider Tables, Primary and Foreign Keys and Triggers Use this script to create an install script in DNN.
Upgrade.SqlDataProvider Views, Functions and Stored Procedures If you include this script as an upgrade script in your manifest, DNN will run this for every upgrade. The generated script will first delete and then install all items.
Uninstall.SqlDataProvider All This script will uninstall all items.

Note this is not "templateable", but it's included in this tool as it is useful to generate alongside your DAL code.

Building This Project

If you wish to fork this project and work on it, compile it using

dotnet pack

and install it on your dev machine using

dotnet tool install --global --add-source ./nupkg Bring2mind.CodeGen.Cli

Uninstall

In the unlikely event you'll want to uninstall this tool, use the following:

dotnet tool uninstall -g Bring2mind.CodeGen.Cli
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last updated
0.2.7 45 5/15/2024
0.2.6 138 2/20/2024
0.2.5 119 2/17/2024
0.2.4 544 10/18/2022
0.2.3 475 6/17/2022
0.2.2 526 6/2/2022
0.2.1 508 5/13/2022
0.2.0 623 1/29/2022
0.1.0 596 1/28/2022
0.0.1 597 5/13/2022