ALSI.DataRepository 0.1.0.2

dotnet add package ALSI.DataRepository --version 0.1.0.2
NuGet\Install-Package ALSI.DataRepository -Version 0.1.0.2
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ALSI.DataRepository" Version="0.1.0.2" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ALSI.DataRepository --version 0.1.0.2
#r "nuget: ALSI.DataRepository, 0.1.0.2"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install ALSI.DataRepository as a Cake Addin
#addin nuget:?package=ALSI.DataRepository&version=0.1.0.2

// Install ALSI.DataRepository as a Cake Tool
#tool nuget:?package=ALSI.DataRepository&version=0.1.0.2

Introduction

ALSI.DataRepository is a C# .NET 6.0 Class Library containing models and functions relating to the ALSI databases.

Installation

ALSI.DataRepository can be installed with nuget the nuget.org site

dotnet nuget source Add -Name "Nuget" -Source "https://api.nuget.org/v3/index.json" -api-key <your key>

This package is also installable using the Nuget package manager via Visual Studio, using the published versions in nuget, by switching over the Package source to your link to nuget and then searching for ALSI.DataRepository.

Development

The solution is built using Visual Studio 2022 Professional. Project targets .NET 6.0.3.

The solution uses Stylecop for styling and code analysis. If making changes please ensure any warnings are addressed.

UnitOfWork

The UnitOfWork format for this repository allows for multiple contexts to be registered for the ALSI databases. Each time you wish to create a new UnitOfWork instance, you must create a context and then switch to it, as there is only one context active at any given time. You can still access and work on each context and switch between them before committing anything, however. For example, if I wish to add the ALSIInvoices context to my UnitOfWork and then start acting on the ALSIInvoices database using my UnitOfWork object:

using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using ALSI.DataRepository;
using ALSI.DataRepository.ALSIInvoices;
using ALSI.DataRepository.ALSIInvoices.Models;
using ALSI.DataRepository.ALSIInvoices.Repository;
using ALSI.DataRepository.UnitOfWork;


public class Test
{
 public ALSIInvoicesContext context;

 public IUnitOfWork unitOfWork;

 public Test()
 {
  DBContextOptions<ALSIInvoicesContext> ALSIInvoicesContextOptions = new DBContextOptionsBuilder<ALSIInvoicesContext>()
   .UseSqlite("Filename=ALSIInvoices.db")
   .Options;
  this.context = new ALSIInvoicesContext(ALSIInvoicesContextOptions);
  // important for Sqlite testing, do not do this generally
  this.context.EnsureDeleted();
  this.context.EnsureCreated();
  // create the unit of work and add the ALSIInvoicescontext, named "ALSIInvoices"
  this.unitOfWork = new UnitOfWork();
  this.unitOfWork.CreateUnitOfWork("ALSIInvoices", this.context); // can be named anything
  this.unitOfWork.SwitchContext("ALSIInvoices");
  this.DoSomething();
 }

 public void DoSomething(){
  // add code here
 }
}

Generic Repositories

If you need to add a generic repository, this can be done dynamically. There are no repositories that are automatically added to your unit of work, and you will need to add them yourself. You can do this by using the AddGenericRepository<TableName>() method when in the right context for your database. These repositories will only be added to the currently active context, so ensure that you have switched to the correct context for the database that contains the table.

You can then use this repository by calling the GetGenericRepository<TableName>() method to return the reference to that instance of the table repository. If a repository doesn't exist yet, then the GetGenericRepository method will create it for you and store it in the repository dictionary for later use. It is best to instantiate your repositories at the start of your UnitOfWork instantiation to save on operation time during potentially time-critical operations, as it takes longer to create new repositories than to fetch existing ones.

Custom Repositories

To add any repository, including custom ones that are specific to the context, you can use the AddRepository<TableName, RepositoryInterface>(RepositoryInstance) function. This can also be called with the generic repository, but note that you will have to instantiate your own generic repository to add using this method. These repositories can then be called with GetRepository<TableName, RepositoryInterface>(). This would also work for Generic repositories, but this method does not create a new repository if none exists and will simply return null if you have not yet created the repository. An example of using custom repositories:

public void DoSomething(){
  this.TestAddGenericRepository();
  this.TestAddCustomRepository
}

public void TestAddCustomRepository()
{
  this.unitOfWork.AddRepository<Charge, IChargeRepository>(new ChargeRepository(this.context));
  // create test parameter
  var chargeTypeTest = new ChargeType()
  {
      chargeType = 1,
      description = "test"
  };
  // retrieve repo and add test charge type
  var TestRepo = this.unitOfWork.GetRepository<Charge, IChargeRepository>();
  TestRepo.Add(chargeTypeTest);
  this.unitOfWork.Complete(); // save the context and commit
}

Multiple Contexts

Introduction to Multiple Contexts

To interact with multiple databases, you can add a new context for each database as needed and then switch between them freely by calling SwitchContext("ContextName"). You have two options for committing to your databases, that is using the Complete() method for committing just the current context, or by using CompleteAll() to save changes to every context available. CompleteAll() will not change the currently loaded context. There are also asynchronous equivalents for these two options for committing your transactions using CompleteAsync() and CompleteAllAsync(). There are also two new disposal options: disposing the currently loaded context or disposing of all instantiated contexts, using DisposeUnitOfWork() and DisposeAll(). An example of saving across multiple contexts:

using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using ALSI.DataRepository;
using ALSI.DataRepository.ALSIInvoices;
using ALSI.DataRepository.ALSIInvoices.Models;
using ALSI.DataRepository.ALSIInvoices.Repository;
using ALSI.DataRepository.UnitOfWork;

public class Test
{
  public ALSIInvoicesContext contextInvoicing;
  public ALSIInvoicesContext contextReporting;
  public IUnitOfWork unitOfWork;
  
  public Test()
  {
    DBContextOptions<ALSIInvoicesContext> ALSIInvoicesContextOptions = new DBContextOptionsBuilder<ALSIInvoicesContext>()
      .UseSqlite("Filename=ALSIInvoices.db")
      .Options;
    
    DBContextOptions<ALSIReportingContext> ALSIInvoicesReportingOptions = new DBContextOptionsBuilder<ALSIReportingContext>()
      .UseSqlite("Filename=ALSIReporting.db")
      .Options;
    
    this.contextInvoicing = new ALSIInvoicesContext(ALSIInvoicesContextOptions);
    this.contextReporting = new ALSIInvoicesContext(ALSIInvoicesReportingOptions);
    
    // important for Sqlite testing, do not do this generally
    this.contextInvoicing.EnsureDeleted();
    this.contextInvoicing.EnsureCreated();
    this.contextReporting.EnsureDeleted();
    this.contextReporting.EnsureCreated();
    
    // create the unit of work and add the contexts, named as their database is named
    this.unitOfWork = new UnitOfWork();
    this.unitOfWork.CreateUnitOfWork("ALSIInvoices", this.contextInvoicing); // can be named anything
    this.unitOfWork.CreateUnitOfWork("ALSIReporting", this.contextReporting); 
    this.unitOfWork.SwitchContext("ALSIInvoices");
    this.unitOfWork.AddGenericRepository<CLIConfig>();
    this.unitOfWork.SwitchContext("ALSIReporting");
    this.unitOfWork.AddGenericRepository<ReportID>();
    this.CrossDatabaseTest();
  }
  
        public void CrossContext_SaveAllChanges()
        {
            this.unitOfWork.CreateUnitOfWork("Second", this.ALSIInvoicesContext);

            var contactTypeTest = new ContactType()
            {
                contactType = 0,
                description = "Test",
            };
            var dueDaysTest = new DueDay()
            {
                dueId = 1,
                dueDays = 1,
                dueDescription = "Test",
            };

            this.unitOfWork.SwitchContext("ALSIInvoices");
            this.unitOfWork.GetGenericRepository<ContactType>().Add(contactTypeTest);
            this.unitOfWork.SwitchContext("Second");
            this.unitOfWork.GetGenericRepository<DueDay>().Add(dueDaysTest);
            this.unitOfWork.CompleteAll();
        }
}

Best Practice in Multi-context Environments

If you're going to be working in a multi-context environment, it is best practice to have each method that acts on the context to return the UnitOfWork to its initial state. An example of how this is done is as follows:

public void DoSomething(IUnitOfWork unitOfWork)
{
    string initialContext = unitOfWork.GetContextName();
    unitOfWork.SwitchContext(nameof(desiredcontext));
    // do something on desired context
    unitOfWork.SwitchContext(initialContext);
}

Publishing

Specific commands

Build the ALSI.DataRepository solution, this should output a .nupkg file to the output directory.

Run the following command for each package registry (git)

dotnet nuget push <package_file> --api-key <key> --source <source_name>

  • <package_file> is the path to the .nupkg package
  • <source_name> should match the key values for packageSources in nuget.config

Example push to Git:

dotnet nuget push W:\Git\ALSI\ALSI.DataRepository\ALSI.DataRepository\bin\Debug\ALSI.DataRepository.1.0.0.nupkg --api-key <key> --source https://api.nuget.org/v3/index.json

The full path and version of the package will be different in your own environment.

The credentials used are stored in

<repository root>\ALSI.DataRepository\nuget.config

If you get a 401 error returned, check the credentials are current.

Scaffolding In Entity Framework

If the ALSI databases are altered, please ensure that this data repository is altered by rescaffolding.

If you are scaffolding, ensure that you keep the old context and models and compare the new <database>Context.cs file produced with the existing one and update the existing context accordingly.

There are two ways to scaffold the ALSI databases:

.NET CLI

Run the following command in ALSI.DataRepository using the Package Manager Console:

dotnet ef dbcontext scaffold "<connection string>" Microsoft.EntityFrameworkCore.SqlServer -f --project ALSI.DataRepository -o <database>\Models --context-dir ./<database> --context <database>Context --no-onconfiguring --use-database-names

Note: The dotnet ef tool is no longer part of the .NET Core SDK. To be able to manage migrations or scaffold a DbContext, install dotnet ef as a global tool typing the following command:

$ dotnet tool install --global dotnet-ef

NuGet Package Manager

Scaffold-DbContext "<CONNECTION-STRING-FOR-TARGET-DB>" Microsoft.EntityFrameworkCore.SqlServer -OutputDir <database>\Models -f -NoOnConfiguring -ContextDir ./<database> -Context <database>Context -UseDatabaseNames

Context changes

You can make changes to the context, but beware that these will need to be persisted if you are rescaffolding a database.

It may be useful to add a fake primary key to an existing table if it is keyless, as EFCore does not allow you to do anything other than read operations on keyless tables. EFCore however does allow you to affect an entity if it has a key specified in the context but none in the database, as long as you pick a reasonable primary key to spoof.

If you spoof a primary key, you are open to concurrency issues which may cause issues when attempting to delete records from the database. I.e. if you have inserted two identical objects because sql server had no objections, you will not be able to delete those through the Delete(entity) method in the DbContext.

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.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.1.0.2 529 6/26/2022