Skip to content

Latest commit

 

History

History
306 lines (229 loc) · 9.31 KB

ef-classic-usage.md

File metadata and controls

306 lines (229 loc) · 9.31 KB

Classic EntityFramework Classic Usage

Interactions with SqlLocalDB via Classic Entity Framework.

EfClassicLocalDb package NuGet Status

https://nuget.org/packages/EfClassicLocalDb/

Schema and data

The snippets use a DbContext of the following form:

using System.Data.Entity;

public class TheDbContext(DbConnection connection) :
    DbContext(connection, false)
{
    public DbSet<TheEntity> TestEntities { get; set; } = null!;

    protected override void OnModelCreating(DbModelBuilder model) => model.Entity<TheEntity>();
}

snippet source | anchor

public class TheEntity
{
    public int Id { get; set; }
    public string? Property { get; set; }
}

snippet source | anchor

Initialize SqlInstance

SqlInstance needs to be initialized once.

To ensure this happens only once there are several approaches that can be used:

Static constructor

In the static constructor of a test.

If all tests that need to use the SqlInstance existing in the same test class, then the SqlInstance can be initialized in the static constructor of that test class.

public class Tests
{
    static SqlInstance<TheDbContext> sqlInstance;

    static Tests() =>
        sqlInstance = new(
            connection => new(connection));

    public async Task Test()
    {
        var entity = new TheEntity
        {
            Property = "prop"
        };
        var data = new List<object>
        {
            entity
        };
        using var database = await sqlInstance.Build(data);
        Assert.Single(database.Context.TestEntities);
    }
}

snippet source | anchor

Static constructor in test base

If multiple tests need to use the SqlInstance, then the SqlInstance should be initialized in the static constructor of test base class.

public abstract class TestBase
{
    static SqlInstance<TheDbContext> sqlInstance;

    static TestBase() =>
        sqlInstance = new(
            constructInstance: connection => new(connection));

    public Task<SqlDatabase<TheDbContext>> LocalDb(
        [CallerFilePath] string testFile = "",
        string? databaseSuffix = null,
        [CallerMemberName] string memberName = "") =>
        sqlInstance.Build(testFile, databaseSuffix, memberName);
}

public class Tests :
    TestBase
{
    [Fact]
    public async Task Test()
    {
        using var database = await LocalDb();
        var entity = new TheEntity
        {
            Property = "prop"
        };
        await database.AddData(entity);

        Assert.Single(database.Context.TestEntities);
    }
}

snippet source | anchor

Seeding data in the template

Data can be seeded into the template database for use across all tests:

public class BuildTemplate
{
    static SqlInstance<BuildTemplateDbContext> sqlInstance;

    static BuildTemplate() =>
        sqlInstance = new(
            constructInstance: connection => new(connection),
            buildTemplate: async context =>
            {
                await context.CreateOnExistingDb();
                var entity = new TheEntity
                {
                    Property = "prop"
                };
                context.TestEntities.Add(entity);
                await context.SaveChangesAsync();
            });

    [Fact]
    public async Task Test()
    {
        using var database = await sqlInstance.Build();

        Assert.Single(database.Context.TestEntities);
    }
}

snippet source | anchor

Usage in a Test

Usage inside a test consists of two parts:

Build a SqlDatabase

using var database = await sqlInstance.Build();

snippet source | anchor

See: Database Name Resolution

Using DbContexts

using (var data = database.NewDbContext())
{

snippet source | anchor

Full Test

The above are combined in a full test:

#if(!NETCOREAPP3_1)
using EfLocalDb;

public class EfSnippetTests
{
    static SqlInstance<MyDbContext> sqlInstance;

    static EfSnippetTests() => sqlInstance = new(connection => new(connection));

    [Fact]
    public async Task TheTest()
    {

        using var database = await sqlInstance.Build();



        using (var data = database.NewDbContext())
        {


            var entity = new TheEntity
            {
                Property = "prop"
            };
            data.TestEntities.Add(entity);
            await data.SaveChangesAsync();
        }

        using (var data = database.NewDbContext())
        {
            Assert.Single(data.TestEntities);
        }
    }

    [Fact]
    public async Task TheTestWithDbName()
    {
        using var database = await sqlInstance.Build("TheTestWithDbName");
        var entity = new TheEntity
        {
            Property = "prop"
        };
        await database.AddData(entity);

        Assert.Single(database.Context.TestEntities);
    }
}
#endif

snippet source | anchor

Using a pre-constructed template

It is possible to pass the path to a pre-existing template to SqlInstance. This is useful if the database contains stored procedures, or requires a large amount of test data.

using EfLocalDb;
// ReSharper disable NotAccessedField.Local

static class SuppliedTemplate
{
    static SqlInstance<MyDbContext> sqlInstance;

    static SuppliedTemplate() =>
        sqlInstance = new(
            connection => new(connection),
            existingTemplate: new("template.mdf", "template_log.ldf"));
}

snippet source | anchor

QuietDbConfiguration

QuietDbConfiguration applies the approaches outlined in Reducing Code First Database Chatter.

Usage

Add the following to the same Assembly that the DbContext implementation exists in:

public class DbConfiguration :
    QuietDbConfiguration;

snippet source | anchor