skip to Main Content

I am writing a data access layer which is tested against multiple database implementations; some SQL (Postgres, Sqlite, SQL Server) and some document (MongoDB). In order to test different databases, my test fixtures include a test entity, entity configuration and DbContext.

For example:

TestEntity:

public sealed class TestEntity(Guid id, string text, int number, DateTime created)
{
    public TestEntity(string text, int number) : this(Guid.Empty, text, number, DateTime.UtcNow)
    {
    }

    public Guid Id { get; } = id;
    public string Text { get; } = text;
    public int Number { get; } = number;
    public DateTime Created { get; } = created;
    public Optional<DateTime> Updated { get; private init; } = Optional<DateTime>.None;
    public bool IsUpdated => Updated.HasValue;

    public TestEntity Update() => new(Id, Text, Number, Created) { Updated = DateTime.UtcNow };
    public override string ToString() => this.ToRecordString();
}

TestEntityTypeConfiguration:

public sealed class TestEntityTypeConfiguration : IEntityTypeConfiguration<TestEntity>
{
    public void Configure(EntityTypeBuilder<TestEntity> builder)
    {
        builder.HasKey(entity => entity.Id);
        builder.Property(entity => entity.Id).ValueGeneratedOnAdd();
        builder.Property(entity => entity.Text).IsRequired().HasMaxLength(32);
        builder.Property(entity => entity.Number).IsRequired();
        builder.Property(entity => entity.Created).IsRequired();
        builder.Property(entity => entity.Updated).HasConversion<OptionalValueConverter<DateTime>>();
        builder.Ignore(entity => entity.IsUpdated);
    }
}

TestDbContext:

public sealed class TestDbContext(DbContextOptions<TestDbContext> options) : DbContext(options)
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfiguration(new TestEntityTypeConfiguration());
    }
}

Note that the integration tests work for all SQL databases. Only MongoDB is failing with the following error:

The entity type ‘TestEntity’ primary key property ‘Id’ must be mapped to element ‘_id’

Given that I am testing against multiple database implementations, I cannot modify TestEntity, TestEntityTypeConfiguration, or TestDbContext without good reason, as I do not want the changes to impact integration tests currently working against SQL databases.

The MongoDB test class is as follows:

IsolatedRepositoryMongoDbIntegrationTests:

public sealed class IsolatedRepositoryMongoDbIntegrationTests(ITestOutputHelper output) : IsolatedRepositoryIntegrationTests(output)
{
    private readonly MongoDbContainer container = new MongoDbBuilder()
        .WithUsername("test_user")
        .WithPassword("test_password")
        .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(27017))
        .Build();

    protected override void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<TestDbContext>(ConfigureDatabase);
        services.AddSingleton<IIsolatedRepository<Guid, TestEntity>, TestIsolatedRepository>();
    }

    private void ConfigureDatabase(DbContextOptionsBuilder builder)
    {
        IMongoClient client = new MongoClient(container.GetConnectionString());
        builder.UseMongoDB(client, "test_db");
    }

    public override async Task InitializeAsync()
    {
        await container.StartAsync();
    }

    public override async Task DisposeAsync()
    {
        await container.StopAsync();
        await container.DisposeAsync();
    }
}

Note that IsolatedRepositoryMongoDbIntegrationTests inherits the actual test cases from IsolatedRepositoryIntegrationTests, therefore this class only needs to contain the configuration for MongoDB.

Have I missed some configuration somewhere, or is there some configuration I should add to IsolatedRepositoryMongoDbIntegrationTests in order to make the tests pass?

I have searched for the error but haven’t found any documentation relating directly to MongoDB and Entity Framework Core, so if you know of anything I should read, please let me know.

2

Answers


  1. Chosen as BEST ANSWER

    I managed to get it working* by making a couple of changes.

    Since TestEntity and TestDbContext have no awareness of MongoDB (they are in a higher level dependency), I had to unseal TestDbContext, so that I could inherit from it.

    Once I had done that, I could proceed to create a derived MongoTestDbContext which derives its configuration from TestDbContext, but includes the configuration to map the Id property with an _id element name:

    public sealed class MongoTestDbContext(DbContextOptions options) : TestDbContext(options)
    {
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
    
            modelBuilder.Entity<TestEntity>(builder => 
                builder.Property(entity => entity.Id).HasElementName("_id")
            );
        }
    }
    

    However, this was not enough. The second issue I ran into was that MongoDB could not map via the TestEntity constructor, therefore mutable fields require private set:

    public sealed class TestEntity(Guid id, string text, int number, DateTime created)
    {
        public TestEntity(string text, int number) : this(Guid.Empty, text, number, DateTime.UtcNow)
        {
        }
    
        public Guid Id { get; private set; } = id;
        public string Text { get; private set; } = text;
        public int Number { get; private set; } = number;
        public DateTime Created { get; private set; } = created;
        public Optional<DateTime> Updated { get; private set; } = Optional<DateTime>.None;
        public bool IsUpdated => Updated.HasValue;
    
        public TestEntity Update() => new(Id, Text, Number, Created) { Updated = DateTime.UtcNow };
        public override string ToString() => this.ToRecordString();
    }
    

    Finally, I needed to make some DI configuration changes, since the existing DI requires a TestDbContext type, therefore this needs to map to MongoTestDbContext as above:

    protected override void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<MongoTestDbContext>(ConfigureDatabase);
        services.AddSingleton<TestDbContext, MongoTestDbContext>();
        services.AddSingleton<IIsolatedRepository<Guid, TestEntity>, TestIsolatedRepository>();
    }
    

    I say got it working because these changes are not optimal:

    1. A class that was previously sealed is now open for extension, which I don't really want.
    2. Explicit private set on the entity properties are superfluous, when Entity Framework is (mostly) capable of mapping via constructors.

    However, this is sufficient for an integration test.


  2. it looks like you can’t use Entity Framework configuration for Mongo.

    Mongo has its own entity map configuration flow

    https://www.mongodb.com/docs/drivers/csharp/current/fundamentals/serialization/class-mapping/

    https://www.mongodb.com/docs/drivers/csharp/current/fundamentals/serialization/poco/#std-label-csharp-poco

       BsonClassMap.RegisterClassMap<House>(classMap =>
    {
       classMap.AutoMap();
       classMap.MapIdMember(h => h.Id).SetIdGenerator(CombGuidGenerator.Instance);
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search