J4JSoftware.EFCore.Utilities 1.3.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package J4JSoftware.EFCore.Utilities --version 1.3.1                
NuGet\Install-Package J4JSoftware.EFCore.Utilities -Version 1.3.1                
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="J4JSoftware.EFCore.Utilities" Version="1.3.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add J4JSoftware.EFCore.Utilities --version 1.3.1                
#r "nuget: J4JSoftware.EFCore.Utilities, 1.3.1"                
#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 J4JSoftware.EFCore.Utilities as a Cake Addin
#addin nuget:?package=J4JSoftware.EFCore.Utilities&version=1.3.1

// Install J4JSoftware.EFCore.Utilities as a Cake Tool
#tool nuget:?package=J4JSoftware.EFCore.Utilities&version=1.3.1                

J4JSoftware.EFCore.Utilities

This assembly provides several capabilities to simplify using Entity Frameowork Core in projects.

This assembly targets Net 7 and has nullability enabled.

The library's repository is available on github.

The change log is available here.

Generalized Design-time Factory

Debugging a DbContext can be complicated if things like the location of the database is different at design time. I often find that's the case in my projects because a configuration class may be defined in its own assembly so it can be shared across multiple projects.

DesignTimeFactory<TDbContext> abstracts this process. All you need to do is implement a simple class deriving from DesignTimeFactory<TDbContext>. Here's an example of doing this when you're using Sqlite3:

public class QbDbDesignTimeFactory : DesignTimeFactory<QbDbContext>
{
    public QbDbDesignTimeFactory()
        : base( GetSourceCodeDirectoryOfClass() )
    {
    }

    protected override void ConfigureOptionsBuilder( DbContextOptionsBuilder<QbDbContext> builder, string dbDirectory )
    {
        dbPath = Path.EndsInDirectorySeparator( dbPath )
            ? Path.Combine( dbPath, "QbDatabase.db" )
            : dbPath;

        if( File.Exists( dbPath ) )
            File.Delete( dbPath );

        var connBuilder = new SqliteConnectionStringBuilder() { DataSource = dbPath };

        builder.UseSqlite(connBuilder.ConnectionString);
    }
}

All the derived class does is call the base constructor with the directory the derived design time factory is defined in, and then define the path to the database via a connection string. You could make other changes to the connection string at this point, too.

The easiest way to get the source code directory of the derived design time factory is by calling the protected method GetSourceCodeDirectoryOfClass() method, as shown in the example. Otherwise you'd end up hardcoding the path, which is not a good idea.

You use this with the ef tools package like this:

  • within the Package Manager Console, move into the directory where your database assembly is defined
  • if needed, create a new migration
  • run the update command specifying the path to where the Sqlite3 db file should be created

If the database is defined in a folder called QbDatabase and the consuming assembly is called ImportNames this would look like this (the example starts off by printing out the current working directory, which I generally find to be a good cautionary step to take):

PM> pwd

Path                                    
----                                    
C:\Programming\Quickbooks2LGL\QbDatabase


PM> dotnet ef migrations add Initial
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
PM> dotnet ef database update -- ../ImportNames
Build started...
Build succeeded.
Applying migration '20220802204340_Initial'.
Done.
PM> 

If you don't include the path to where you want the database file created it will be created in the directory defining the database.

Configuring Entity Framework Classes

Database entities almost always require you configure the database traits of properties (e.g., is that string nullable or not?), relationships, etc.

EF Core lets you do this in at least two ways, one by decorating entity classes and their properties with attributes and one by calling various fluent design configuration methods (you can mix and match, too, I think).

For various reasons I strongly prefer exclusively using the fluent design approach, rather than the attribute-based approach. But by default that would result in a very large OnModelCreating() method override in my DbContext-derived class.

That's messy. So I wrote several classes to allow me to put the necessary fluent design method calls in a class I associate with an entity class. Each entity gets its own configuration class. And a single extension method call in OnModelCreating() knits it all together:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.ConfigureEntities(this.GetType().Assembly);
}

Here's an example of how the support classes are used:

[EntityConfiguration(typeof(QuestionDbConfigurator))]
public class QuestionDb
{
    public int ID { get; set; }
    public string SurveyPlanetID { get; set; }
    public string Text { get; set; }
    public string Type { get; set; }
    public bool Required { get; set; }

    public List<AnswerDb> Answers { get; set; }
    public List<QuestionChoiceDb> Choices { get; set; }

    public int SurveyID { get; set; }
    public SurveyDb Survey { get; set; }
}

internal class QuestionDbConfigurator : EntityConfigurator<QuestionDb>
{
    protected override void Configure( EntityTypeBuilder<QuestionDb> builder )
    {
        builder.HasOne(x => x.Survey)
            .WithMany(x => x.Questions)
            .HasPrincipalKey(x => x.ID)
            .HasForeignKey(x => x.SurveyID);

        builder.HasIndex( x => x.SurveyPlanetID )
            .IsUnique();
    }
}

QuestionDbConfigurator holds the fluent-design configuration information for the entity class QuestionDb. It's internal because only the DbContext-derived class in the database assembly needs access to it.

The EntityConfigurationAttribute attribute decorating QuestionDb lets the utility code know where to get the configuration information for QuestionDb.

Formatting DbUpdateExceptions

Figuring out what caused an Entity Framework exception to be thrown can be messy, because many of the details are buried within inner exceptions, the entities involved aren't included in the exception messages, etc.

The DbUpdateException.FormatDbException() extension method extracts more detailed information from a DbUpdateException and formats it into a string you can log or otherwise display.

If you find additional Entity Framework exception information useful, check out Entity Framework Exceptions, which provides even more detailed information.

Product Compatible and additional computed target framework versions.
.NET net7.0 is compatible.  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
1.4.1 196 4/11/2023
1.4.0 187 4/4/2023
1.3.1 292 12/29/2022
1.3.0 281 12/27/2022
1.1.0 348 11/12/2021
1.0.1 340 9/29/2021
1.0.0 370 9/28/2021
0.8.1 359 2/3/2021
0.8.0 354 1/9/2021
0.5.0 452 7/8/2020

fixed namespace problem