Generator

This is the fun bit that magically writes probably hundreds of thousands of lines of code for you

In zero time, at zero cost and 100% accurately

There’s an example Generator in the GitHub repo in the JennyDemo/JennyDemo.sln solution

  • Jenny.Coder.DbContextPerStatement

This is where your templates live. They are C# strings that Jenny compiles into executable functions that take parameters from the metadata and write your code for you

Take a look, they’re not that scary! Most of them are just strings, but you can also include code inside a pair of #’s

The rest of this page is about the DbContextPerStatement Generator, so it’s opinionated, but it doesn’t have to be your opinion

You can edit the templates to match your project requirements, in fact please do!

In the JennyDemo solution, there are three parallel implementations for the three supported EF and .NET versions

  • EF6 running on .NET Framework
  • EF6 running on .NET Core
  • EF Core running on .NET 6

There’s a shared assembly call JennyDemo.DOG that builds for both .NET Framework and .NET Core

DOG stands for Data Object Graph and has partial classes extending all your entity types – your POCO table classes

For each table it adds these members, taking a table called A1Candidate as an example

In GitHub the example generated file is here

  • JennyDemo/JennyDemo.DOG/_DOG.cs
partial class A1Candidate :
    global::NBootstrap.Global.IDog<A1Candidate>,
    global::NBootstrap.Global.IColumnProperties
{

public class RowIdentityClass : IRowIdentityObject<A1Candidate>
{
// this handles primary keys including composite keys
// it also serializes your keys to and from strings
}
public bool IsPrimaryKeyEqual( A1Candidate o ) { ... }
public static IEqualityComparer<A1Candidate> PrimaryKeyComparer { get; }
public static string[] StaticColumnPropertyNames { get; }
// PropertyInfos are useful for Expression Trees
public static PropertyInfo[] StaticColumnPropertyInfos { get; }
public A1Candidate( A1Candidate o ) { copy constructor }
public A1Candidate CreateColumnCopy()
public A1Candidate CopyAllColumnsFrom( A1Candidate o )
public A1Candidate CopyKeyColumnsFrom( A1Candidate o )
public A1Candidate CopyDataColumnsFrom( A1Candidate o )
public bool AreColumnsEqual( A1Candidate o )
...
for each DateTime or DateTime? column it will add a property with _Local at the end which is populated with the correct local time automatically when the row comes from the database

for example, for a column
public DateTime InterviewDateTime { get; set; }

Jenny will add this C# property
[NotMapped] public DateTime InterviewDateTime_Local { get; set; }

Good so far? Not ground-breaking, but useful code humans won’t have to write!

Now comes the really opinionated bit. EF has a hard time when you load large numbers of objects into a DbContext instance. However, creating DbContexts is cheap – so how about a fresh DbContext for each read and write?

Wrapped in a repository class to encapsulate database connections and transactions for Units Of Work.

All written for you by Jenny – isn’t she nice!

In GitHub the example generated files are here

  • JennyDemo/JennyDemo.DAL.EF6.Framework/_REPO.cs
  • JennyDemo/JennyDemo.DAL.EF6.Core/_REPO.cs
  • JennyDemo/JennyDemo.DAL.EFCore/_REPO.cs

You can check out the implementation, but here are some use cases

As always you can rewrite the templates if you would prefer something else

var repo = new JennyRepo( TimeZoneInfo - from authenticated user? )
// the TimeZoneInfo is used to populate the _Local DateTime properties

repo.A1Candidate.Read() 
// gives you an IQueryable<A1Candidate>

using IContext ctx = repo.CreateContext();
repo.A1Candidate.Read( ctx );
repo.A1Interview.Read( ctx );
// entities loaded into the same IContext will have related navigation properties populated

// enables tracking who is causing the database write
var token = LoginUserToken;

var template = new DOG.A1Candidate { ... };
var dog = repo.A1Candidate.Create( token, template );

// simple update all columns in a row
repo.A1Candidate.UpdateAllColumns( token, template );

// specify which columns should be updated
repo.A1Candidate.UpdateMapColumns( token, template, x => x
    .Column( o => o.FirstName, "Fred" )
    .Column( o => o.LastName, "Brooks" )
);

// delete
repo.A1Candidate.Delete( token, template );

// transactions including nested transactions in C#
// SQL Server only uses one transaction per connection
using var tx1 = repo.BeginTransaction();
using var tx2 = repo.BeginTransaction();
repo.A1Candidate.Create( token, template );
tx2.Commit();
repo.A1Candidate.Delete( token, template );
tx1.Commit();

// hooks to run code when a write occurs
partial class JennyRepo
{
    partial class A1CandidateTable
    {
        static A1CandidateTable() // cctor
        {
            OnBeforeCreate += ( ... ) => { }
            OnBeforeUpdate += ( ... ) => { }
            OnBeforeDelete += ( ... ) => { }

            OnAfterCreate += ( ... ) => { }
            OnAfterUpdate += ( ... ) => { }
            OnAfterDelete += ( ... ) => { }

// useful for invalidating caches, populating Created and Updated columns - you could even record each write in a NoSql database if that made sense for your project

And my favourite… make Includes in EFCore work the same way as in EF6 – why did they break that?

// backwards compatibility for EF6 => EFCore

repo.A1Candidate.Read()
    .Include( o => o.Interviews.Select( o => o.Answers ) )

That’s the opinionated bit done

There’s a lot of information in your database schema and a computer program is capable of using that to do some of the coding for you, while complying with architecture and design decisions