Report Builder – Print Group Footer At the Bottom of the Page with RAP – Adding your own functions to RAP

By | May 15, 2020

Printing the invoice total

Quite a while ago I was working with Digital Metaphors Report Builder with Delphi. In this instance I had to generate invoices. The report needed to be able to generate invoices for a single invoice or a range of invoices. To achieve this the report had a group on the invoice number, putting a page break after each group footer. But the invoice total had to appear on the bottom of the page. This means the group footer had to print on the bottom of the page. So how to get Report Builder to print the group footer at the bottom of the page in RAP

The solution to get Report Builder To Print Group Footer At the Bottom of the Page in RAP

I had found some instructions in the rbWiki to do that using a Delphi code type report. But he report was embedded into the delphi form and the logic done in the delphi code. But the way I was working with 100 or so reports was with separate report files. 90% of the logic is using the Digital Metaphors RAP language. My own report designer application allows me to build and edit the reports. I needed to do the same thing as in the instructions I had found but in RAP only. I couldn’t find anything but except some pointers which led me to the solution. Extend the RAP.

Extend the RAP

The answer was to add extra functions to the RAP language. D.M. provide the means to extend their language. Information on how to do that is provided in the report builder RAP help file and the D.M. rbWiki. Essentially you follow their instructions and put the unit(s) where put your extensions in the uses clause of the unit where you have your have and load your TppReport component either on a form/data module or you create it via code. I posted an overview of my solution in a digital metaphors post which you can find at http://www.digital-metaphors.com/forums/discussion/2390/previous-incomplete-putting-group-footer-on-page-bottom-through-rap

Let’s elaborate on this.

Firstly we need a to create a ppFromMMThousandths function that RAP can use. This will enable us to convert the Report.Engine.PageBottom mmThousandths to the units of the report. So in a new unit we create a class inheriting from TRptUtilityFunction that inherits from TraSystemFunction. I will give the full code to everything in the article later but the declaration of this is:

  TppFromMMThousandthsFunction = class(TRptUtilityFunction)
  published
    procedure ExecuteFunction(aParams: TraParamList); override;
    class function GetSignature: String; override;
    class function HasParams: Boolean; override;
    class function IsFunction : boolean; override;
  end;

Secondly we need to expose the PageBottom property to the RAP Report Engine class. We do that by extending the report engine RAP class with the following declaration:

  TppCustomEngineRTTI = class(TraTppCacheableRTTI)
  public
    class procedure GetPropList(aClass: TClass; aPropList: TraPropList); override;
    class function  GetPropRec(aClass: TClass; const aPropName: String; var aPropRec: TraPropRec): Boolean; override;
    class function  GetParams(const aMethodName: String): TraParamList; override;
    class function  CallMethod(aObject: TObject; const aMethodName: String; aParams: TraParamList; aGet: Boolean): Boolean; override;
    class function  RefClass: TClass; override;
    class function GetPropValue(aObject: TObject; const aPropName: String; var aValue): Boolean; override;
    class function SetPropValue(aObject: TObject; const aPropName: String; var aValue): Boolean; override;
  end;

Thirdly we add in our report an event handler on the GroupFooter.OnBeforePrint event to set the group footer position. This is the final step to get Report Builder To Print Group Footer At the Bottom of the Page in RAP.

 var  
   lPageBottom: Single;  
 begin  
   Footer.Visible := False;  
  {Get the bottom of the page from the report engine. This value is adjusted 
   for the footer and bottom margin. Convert the units from mmThousandths to 
   Report.Units}  
  lPageBottom := ppFromMMThousandths(Report.Engine.PageBottom, Report.Units, False, nil); 
   {Set the group footer band print position to align it to the bottom of the page}  
  GroupFooterBand1.PrintPosition := lPageBottom - GroupFooterBand1.Height - 0.01; 
end;

So let’s put steps 1 and 2 together and the implementations into one unit. I called it RapSupport. I have also added one or two other useful utilities functions. You may find these useful as well. As I mentioned earlier you must add RapSupport to the uses clause of the unit you are using the TppReport.

unit RapSupport;

interface
uses
  ppRTTI, ppChrt, Series,
  TeEngine, Chart, raFunc, ppCache, ppRelatv, ppProd, ppUtils, graphics,
  ppTypes, teCanvas, ppDB, ppDBPipe;
type
  TMyStringFunction = class(TraStringFunction)
  published
    class function Category: String; override;
  end;

  TMyMathFunction = class(TraSystemFunction)
  published
    class function Category: string; override;
  end;

  TRptUtilityFunction = class(TraSystemFunction)
  published
    class function Category: string; override;
  end;

  TppFromMMThousandthsFunction = class(TRptUtilityFunction)
  published
    procedure ExecuteFunction(aParams: TraParamList); override;
    class function GetSignature: String; override;
    class function HasParams: Boolean; override;
    class function IsFunction : boolean; override;
  end;

  TMakeAddressFunction = class(TMYStringFunction)
  published
    procedure ExecuteFunction(aParams: TraParamList); override;
    class function GetSignature: String; override;
    class function HasParams: Boolean; override;
  end;

  TLongMonthNamesFunction = class(TMyStringFunction)
  published
    procedure ExecuteFunction(aParams: TraParamList); override;
    class function GetSignature: String; override;
    class function HasParams: Boolean; override;
  end;

  TShortMonthNamesFunction = class(TMyStringFunction)
  published
    procedure ExecuteFunction(aParams: TraParamList); override;
    class function GetSignature: String; override;
    class function HasParams: Boolean; override;
  end;

  TLPadStringFunction = class(TMyStringFunction)
  published
    procedure ExecuteFunction(aParams: TraParamList); override;
    class function GetSignature: String; override;
    class function HasParams: Boolean; override;
  end;

  TAbsFunction = class(TMyMathFunction)
  published
    procedure ExecuteFunction(aParams: TraParamList); override;
    class function GetSignature: String; override;
    class function HasParams: Boolean; override;
    class function IsFunction : boolean; override;
  end;


  TppCustomEngineRTTI = class(TraTppCacheableRTTI)
  public
    class procedure GetPropList(aClass: TClass; aPropList: TraPropList); override;
    class function  GetPropRec(aClass: TClass; const aPropName: String; var aPropRec: TraPropRec): Boolean; override;
    class function  GetParams(const aMethodName: String): TraParamList; override;
    class function  CallMethod(aObject: TObject; const aMethodName: String; aParams: TraParamList; aGet: Boolean): Boolean; override;
    class function  RefClass: TClass; override;
    class function GetPropValue(aObject: TObject; const aPropName: String; var aValue): Boolean; override;
    class function SetPropValue(aObject: TObject; const aPropName: String; var aValue): Boolean; override;
  end;

implementation

uses
  SysUtils, Variants, Classes, ppClass, JvAppCommand, Windows;


{ TMakeAddressFunction }

class function TMYStringFunction.Category: String;
begin
  Result := 'MY Stuff';
end;

procedure TMakeAddressFunction.ExecuteFunction(aParams: TraParamList);
var
  Line1,
  Line2,
  Line3,
  Line4,
  Line5,
  City,
  State,
  PostCode : string;
  ReturnStrings : TStrings;
  Line6 : string;
  ReturnResult : string;
begin
  ReturnStrings := TStringList.Create;
  try
    GetParamValue(0,Line1);
    GetParamValue(1,Line2);
    GetParamValue(2,Line3);
    GetParamValue(3,Line4);
    GetParamValue(4,Line5);
    GetParamValue(5,City);
    GetParamValue(6,State);
    GetParamValue(7,PostCode);
    ReturnStrings.Clear;
    if Line1 <> '' then
      ReturnStrings.Add(Line1);
    if Line2 <> '' then
      ReturnStrings.Add(Line2);
    if Line3 <> '' then
      ReturnStrings.Add(Line3);
    if Line4 <> '' then
      ReturnStrings.Add(Line4);
    if Line5 <> '' then
      ReturnStrings.add(Line5);
    if City <> '' then
      ReturnStrings.Add(City);

    {now lets determine what the last line is as long as there is more than
      1 line}
    if ReturnStrings.Count <= 1 then
      Line6 := ''
    else
    begin
      Line6 := ReturnStrings.Strings[ReturnStrings.Count-1];
      ReturnStrings.Delete(ReturnStrings.Count - 1);
    end;

    if State <> '' then
    begin
      if Line6 <> '' then
        Line6 := Line6 + ' ';
      Line6 := Line6 + State;
    end;
    if PostCode <> ''  then
    begin
      if Line6 <> '' then
        Line6 := Line6 + ' ';
      Line6 := Line6 + PostCode;
    end;

    if Line6 <> '' then
      ReturnStrings.Add(Line6);

    ReturnResult := ReturnStrings.Text;
    SetParamValue(8,ReturnResult);
  finally
    ReturnStrings.Free;
  end;
end;

class function TMakeAddressFunction.GetSignature: String;
begin
  Result := 'function MakeSqueezedAddress(Line1 : string;'+
                                          'Line2 : string;'+
                                          'Line3 : string;'+
                                          'Line4 : string;'+
                                          'Line5 : string;'+
                                          'City : string;'+
                                          'State : string;'+
                                          'PostCode : string) : string;';
end;

class function TMakeAddressFunction.HasParams: Boolean;
begin
  Result := True;
end;

{ TLongMonthNamesFunction }

procedure TLongMonthNamesFunction.ExecuteFunction(aParams: TraParamList);
var
  s : string;
  m : integer;
  fs : TFormatSettings;
begin
  GetParamValue(0,m);
  GetLocaleFormatSettings(GetThreadLocale,fs);
  s := fs.LongMonthNames[m];
  SetParamValue(1,s);
end;

class function TLongMonthNamesFunction.GetSignature: String;
begin
  Result := 'function GetLongMonthName(MonthNo : integer) : string;';
end;

class function TLongMonthNamesFunction.HasParams: Boolean;
begin
  Result := True;
end;

{ TShortMonthNamesFunction }

procedure TShortMonthNamesFunction.ExecuteFunction(aParams: TraParamList);
var
  s : string;
  m : integer;
  fs : TFormatSettings;
begin
  GetParamValue(0,m);
  GetLocaleFormatSettings(GetThreadLocale,fs);
  s := fs.ShortMonthNames[m];
  SetParamValue(1,s);
end;

class function TShortMonthNamesFunction.GetSignature: String;
begin
  Result := 'function GetShortMonthName(MonthNo : integer) : string;';
end;

class function TShortMonthNamesFunction.HasParams: Boolean;
begin
  Result := True;
end;

{ TLPadStringFunction }

procedure TLPadStringFunction.ExecuteFunction(aParams: TraParamList);
var
  s : string;
  p : char;
  MaxLen : integer;
  Res : string;
  l : integer;
begin
  GetParamValue(0,s);
  GetParamValue(1,p);
  GetParamValue(2,MaxLen);

  l := Length(s);
  if L < MaxLen then
  begin
    Res := StringOfChar(p,MaxLen-l) +
           s;
  end
  else
  begin
    Res := s;
  end;
  SetParamValue(3,Res);
end;

class function TLPadStringFunction.GetSignature: String;
begin
  Result := 'function LPadString(s : string; PadChar : char; MaxLength : integer) : string;';
end;

class function TLPadStringFunction.HasParams: Boolean;
begin
  Result := True;
end;

{ TMYMathFunction }

class function TMYMathFunction.Category: string;
begin
  Result := 'MYMathStuff';
end;

{ TAbsFunction }

procedure TAbsFunction.ExecuteFunction(aParams: TraParamList);
var
  n : double;
  Res : double;
begin
  GetParamValue(0,n);
  Res := abs(n);

  {abs doesn't work in the report RAP compiler}
//  if n < 0 then
//    Res := -n
//  else
//    Res := n;
  SetParamValue(1,Res);
end;

class function TAbsFunction.GetSignature: String;
begin
  Result := 'function Abs(n : double) : double;';
end;

class function TAbsFunction.HasParams: Boolean;
begin
  Result := True;
end;

class function TAbsFunction.IsFunction: boolean;
begin
  Result := True;
end;

{ TTppCustomEngineRTTI }

class function TppCustomEngineRTTI.CallMethod(aObject: TObject;
  const aMethodName: String; aParams: TraParamList; aGet: Boolean): Boolean;
begin
  Result := inherited CallMethod(aObject, aMethodName, aParams, aGet);
end;

class function TppCustomEngineRTTI.GetParams(
  const aMethodName: String): TraParamList;
begin
  Result := inherited GetParams(aMethodName);
end;

class procedure TppCustomEngineRTTI.GetPropList(aClass: TClass;
  aPropList: TraPropList);
begin
  inherited GetPropList(aClass, aPropList);
  aPropList.AddProp('PageBottom');
end;

class function TppCustomEngineRTTI.GetPropRec(aClass: TClass;
  const aPropName: String; var aPropRec: TraPropRec): Boolean;
begin
  Result := True;
  if ppEqual(aPropName, 'PageBottom') then
    PropToRec(aPropName, daInteger, True, aPropRec)
  else
    Result := inherited GetPropRec(aClass, aPropName, aPropRec);
end;

class function TppCustomEngineRTTI.GetPropValue(aObject: TObject;
  const aPropName: String; var aValue): Boolean;
begin
  Result := True;
  if ppEqual(aPropName, 'PageBottom') then
    Integer(aValue) := TppCustomEngine(aObject).PageBottom
  else
    Result := inherited GetPropValue(aObject, aPropName, aValue);
end;

class function TppCustomEngineRTTI.RefClass: TClass;
begin
  Result := TppCustomEngine;
end;

class function TppCustomEngineRTTI.SetPropValue(aObject: TObject;
  const aPropName: String; var aValue): Boolean;
begin
  Result := inherited SetPropValue(aObject,aPropName, aValue);
end;

{ TRptUtilityFunction }

class function TRptUtilityFunction.Category: string;
begin
  Result := 'Report Utilities';
end;

{ TppFromMMThousandthsFunction }

procedure TppFromMMThousandthsFunction.ExecuteFunction(aParams: TraParamList);
var
  Value : LongInt;
  aUnits : TppUnitType;
  aResolution : TppResolutionType;
  aResolutionHorizontal : boolean;
  aPrinter : TObject;
  Res : Single;
begin
  GetParamValue(0,Value);
  GetParamValue(1,aUnits);
  GetParamValue(2,aResolutionHorizontal);
  GetParamValue(3,aPrinter);

  //now we have all the values we need call the actual function
  if aResolutionHorizontal then
    aResolution := pprtHorizontal
  else
    aResolution := pprtVertical;

  Res := ppFromMMThousandths(Value, aUnits, aResolution, aPrinter);

  SetParamValue(4,Res);
end;

class function TppFromMMThousandthsFunction.GetSignature: String;
begin
  Result := 'function ppFromMMThousandths(Value: Integer; aUnits: TppUnitType;'+
    ' aResolutionHorizontal: boolean; aPrinter: TObject) : single;';
end;

class function TppFromMMThousandthsFunction.HasParams: Boolean;
begin
  Result := True;
end;

class function TppFromMMThousandthsFunction.IsFunction: boolean;
begin
  Result := True;
end;

initialization
  raRegisterRTTI(TppCustomEngineRTTI);

  raRegisterFunction('MakeSqueezedAddress', TMakeAddressFunction);
  raRegisterFunction('GetLongMonthName',TLongMonthNamesFunction);
  raRegisterFunction('GetShortMonthName',TShortMonthNamesFunction);
  raRegisterFunction('LPadString', TLPadStringFunction);
  raRegisterFunction('ppFromMMThousandths' , TppFromMMThousandthsFunction);
  raRegisterFunction('Abs', TAbsFunction);


finalization
  raUnRegisterRTTI(TppCustomEngineRTTI);
  
  raUnRegisterFunction('MakeSqueezedAddress');
  raUnRegisterFunction('GetLongMonthName');
  raUnRegisterFunction('GetShortMonthName');
  raUnRegisterFunction('LPadString');
  raUnRegisterFunction('ppFromMMThousandths' );
  raUnRegisterFunction('Abs');
end.

So there it is. I hope this is helpful to some folks. It is also a good example of extending RAP.

Linq2db with Fluent Mapping Part 1

By | September 13, 2018

Linq2Db With Fluent Mapping

This article is about using linq2db with fluent mapping. You can find Linq2db https://github.com/linq2db/linq2db.  Linq2Db is an ORM for C#.  The provided readme gives a great introduction.  If you:

  1. Don’t want the bloat and overhead of Entity Framework,
  2. Want a fast ORM,
  3. Like the ability to use type safe linq and don’t what to hand code SQL statements,
  4. Your happy with the small trade-off in performance to get number 3 (over a micro ORM like say Dapper)

then Linq2DB is the one for you.

But it seems that many use Linq2DB in database first fashion with attributes.  It appears that the creators originally designed Linq2Db  this way and there is a T4 template available to generate all of your POCOs complete with attributes from an existing database.  You can try this for yourself using the existing documentation.  However, while for many it is not a great deal, for a lot of people, including myself, like to separate the ORM mapping from the POCO objects.  There is support for this using linq2db with fluent mapping that appears to have evolved over time but it is not well documented, but I am hoping to change that here.

An Example Using Linq2DB With Fluent Mapping

I am going to develop an example using .Net Core and PostgreSQL and take you with me.  It will be a time tracking browser based application which is based on a simplified system I have developed for myself.

My Approach

Because Linq2Db is meant to be lightweight it doesn’t have things like Code-First or automatic db migrations.  Because of this my approach is to use FluentMigrator, developing the migrations in parallel with the POCO’s and linq2db with fluent mapping.  This isn’t code-first, but is a happy work around.  It also provides us with the ability to version control the database.

Quick jumps:

Lets Start

So we create our solution.  We want a library for the models (our POCOs), utility classes and the database migrations.  We then want a web application project.  I have a utility extension method called ToDB() that simply takes a the pascal case to underscores and lower-case.  This works better with some of the databases servers.  If you don’t make the field names lower-case with FluentMigrator,  FluentMigrateor creates the database fields as quoted.  This is important because with postgresql, if the fields are created quoted you always have to reference them quoted.

Create The First POCO

Let’s start in our models library project with a base model class:

public class BaseModel
{
    public long Id { get; set; }
}

and our client class.

public class Client : BaseModel
{
    public string Name { get; set; }
    public string PhoneNo { get; set; }
    public bool IsActive { get; set; }
}

Create the Migration

I will not go in to setting up fluent migrator too much here but this will be our migration up code which I will put into our DBMigration library project.  Don’t forget to add the FluentMigrator nuget package to the project and add the dependencies for the models library as well as the Utils library

using FluentMigrator;
using TimeTrackerLite.Models;
using TimeTrackerLite.Utils;

namespace TimeTrackerLite.DBMigration
{
    public class Migration001IinitDB : AutoReversingMigration
    {
        public override void Up()
        {
            CreateClientTable();
        }

        private void CreateClientTable()
        {
            Create
                .Table("Client")
                .WithColumn(nameof(Client.Id).ToDB()).AsInt64().NotNullable()
                    .PrimaryKey().Identity()
                .WithColumn(nameof(Client.Name).ToDB()).AsString(50).NotNullable()
                .WithColumn(nameof(Client.PhoneNo).ToDB()).AsString(25).NotNullable()
                .WithColumn(nameof(Client.IsActive).ToDB()).AsBoolean();
        }
    }
}

The Mapping

Now to set up the mapping.  We will put that in a separate class using the fluent mapping, keeping our POCO nice and clean.  Add the Linq2db nuget package to the  .net core web application.  This what the mapping class looks like.

using LinqToDB.Mapping;
using TimeTrackerLite.Models;
using TimeTrackerLite.Utils;

namespace TimeTrackerLite.Data
{
    public static class MappingLinq2Db
    {
        static MappingSchema _schema = null;

        public static MappingSchema Do()
        {
            if(_schema == null)
            {
                _schema = MappingSchema.Default;
                var mapper = _schema.GetFluentMappingBuilder();

                mapper
                    .Entity()
                    .HasTableName(nameof(Client).ToDB())
                    .Property(x => x.Id).HasColumnName(nameof(Client.Id).ToDB())
                        .IsIdentity().IsPrimaryKey()
                    .Property(x => x.Name).HasColumnName(nameof(Client.Name).ToDB())
                    .Property(x => x.PhoneNo).HasColumnName(nameof(Client.PhoneNo).ToDB())
                    .Property(x => x.IsActive).HasColumnName(nameof(Client.IsActive).ToDB());

            }
            return _schema;
        }
        
    }
}

Infrastructure

Now that we have our model and migration we need to set-up our data connection settings and auto migration code.  We will do that as per the recommendations on the linq2db github site for .net core and the fluent migration site and the advice regarding secrets.  Add the FluentMigrator, FluentMigrator.Runner and the postgresql Npgsql (or whatever database provider) nuget packages.   This is what For setting up the connection stuff I use the following classes:

namespace TimeTrackerLite.Data
{
    public class DBSettings
    {
        public string Server { get; set; }
        public string Database { get; set; }
        public int? Port { get; set; }
        public string DBUserName { get; set; }
        public string DBPassword { get; set; }
    }


}

This is a class that I use to store the configuration of the database for the application. You will see the use of this class in loading the settings later.  Then we have some helper extensions to convert these database settings to/from a database connection string.

using Npgsql;

namespace TimeTrackerLite.Data
{
    public static class DBSettingsConnectionStringPgsql
    {
        public static DBSettings ToPgsqlDBSettings(this string connString)
        {
            var builder = new NpgsqlConnectionStringBuilder(connString);
            return new DBSettings()
            {
                Server = builder.Host,
                Database = builder.Database,
                Port = builder.Port,
                DBUserName = builder.Username,
                DBPassword = builder.Password
            };
        }

        public static string ToPgsqlConnectionString(this DBSettings dbSettings)
        {
            var builder = new NpgsqlConnectionStringBuilder
            {
                Username = dbSettings.DBUserName,
                Host = dbSettings.Server,
                Password = dbSettings.DBPassword,
                Database = dbSettings.Database,
                CommandTimeout = 200,
                ConnectionIdleLifetime = 30,
                Pooling = true,
                KeepAlive = 30,
                TcpKeepAlive = true
            };
            if (dbSettings.Port != null)
                builder.Port = dbSettings.Port.Value;

            return builder.ToString();
        }
    }
}

Then our IConnectionStringSettings implementations as recommended by the Linq2DB readme.

using LinqToDB;
using LinqToDB.Configuration;
using System.Collections.Generic;
using System.Linq;

namespace TimeTrackerLite.Data
{
    public class ConnectionStringSettings : IConnectionStringSettings
    {
        public string ConnectionString { get; set; }
        public string Name { get; set; }
        public string ProviderName { get; set; }
        public bool IsGlobal => false;
    }

    public class DbConnectionSettings : ILinqToDBSettings
    {
        DBSettings _dbSettings;
        public DbConnectionSettings(DBSettings dbSettings)
        {
            _dbSettings = dbSettings;
        }

        public IEnumerable DataProviders => Enumerable.Empty();

        public string DefaultConfiguration => ProviderName.PostgreSQL;
        public string DefaultDataProvider => ProviderName.PostgreSQL;

        public IEnumerable ConnectionStrings
        {
            get
            {
                yield return
                    new ConnectionStringSettings
                    {
                        Name = "AppData",
                        ProviderName = ProviderName.PostgreSQL,
                        ConnectionString = _dbSettings.ToDBConnectionString()
                    };
            }
        }
    }
}

Modify the project wizard generated startup.cs file in the TimeTrackerLite .net core web application pages project:

using FluentMigrator.Runner;
using LinqToDB.Data;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using TimeTrackerLite.Data;

namespace TimeTrackerLite
{
    public class Startup
    {
        public Startup(IHostingEnvironment env, IConfiguration configuration)
        {
            Configuration = configuration;
            DBSettings = new DBSettings();
            if (env.IsDevelopment())
                Configuration.GetSection("DBSettingsDev").Bind(DBSettings);
            else
                Configuration.GetSection("DBSettings").Bind(DBSettings);
        }

        public IConfiguration Configuration { get; }
        public DBSettings DBSettings { get; private set; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //Set up the fluent migrator for the dependency injector
            services.AddFluentMigratorCore()
            .ConfigureRunner(rb => rb
            .AddPostgres()
            // Set the connection string
            .WithGlobalConnectionString(DBSettings.ToDBConnectionString())
            // Define the assembly containing the migrations
            .ScanIn(typeof(Startup).Assembly).For.Migrations());

            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, IMigrationRunner dbmigrator)
        {
            ConfigureDB(env);
            dbmigrator.MigrateUp();
            if (env.IsDevelopment())
            {
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();

            app.UseMvc();
        }

        private void ConfigureDB(IHostingEnvironment env)
        {
            DataConnection.DefaultSettings = new DbConnectionSettings(DBSettings);
        }
    }
}

So now we have our basic infrastructure.  This gives you are primer to Linq2Db.  In the next article I’ll add add the crud pages, a task class and create an association. Until then,  happy coding.  You will find the full source on github.

My C# Journey

By | August 6, 2018

It has been a little while since I posted.  I though I’d present an overview on my programming c# journey over the last couple of years.  I have enjoyed using C# and use it most of the time now.  I have used it in a couple of paid commissioned projects and use it for my own projects I hope to sell.

As with anything new, progress seems slow and painful.  It can be fustrating when you are starting out with a new language, platform and environment, how slow it seems to accomplish anything because of the learning curve.  This is true of my c# journey.  This is complicated when you try out new tools and libraries for the various platforms and frameworks.

The paid commissioned projects along my c# journey I have done have been WPF projects.  Other stuff I have worked on is a Xamarin android project – a simple timer for starters.  I have other Xamarin projects on the go as well.  I have used sqlite and postgresql for the data storage.  I am also currently cutting my teeth on a web application using .net core razor pages.

Also, in this c# journey, some libraries I have used or played with which I will write about over time are MVVMLight, ReactiveX, ReactiveUI, AutoFac, XUnit, LiteDB, FluentValidation, FluentMigration, Dapper, Dapper Extensions, Dapper FastCrud, BrightstarDB and Linq2DB.

My favourite MVVM framework is ReactiveUI.  I like the reactive ui framework itself, but it is the reactive programming theory I like.  I will write about this later also.

My favourite database ORM library so far in the c# journey is Linq2DB.  It doesn’t have the overhead of entity framework, but allows you to use Linq.  I have only just begun to use it.  Documentation and information is hard to find but worth the dig.  I will write about what I have learnt with some how-tos in the future.

Category: C#