2

I am struggling with this coming back from a long layoff.

I asked a question regarding the configuring of a DBContext in my generic base repository. Only after a user has logged in can I then construct a connection string so I cannot register a service in startup.cs - I have to use a constructor argument to instantiate my DBContext.

I got this answer which I thought would address the problem however I am getting an error in the following factory class:

public class ContextFactory<T> : IContextFactory<T> : where T : DbContext
{
    public T CreateDbContext(string connectionString)
    {
        var optionsBuilder = new DbContextOptionsBuilder<T>();
        optionsBuilder.UseSqlServer(connectionString);
        return new T(optionsBuilder.Options);
    }
}

The error is on the line return new T(optionsBuilder.Options); and is:

Cannot create an instance of the variable type 'T' because it does not have the new() constraint

1
  • 2
    even if you add new() constraint you end up with 'T': cannot provide arguments when creating an instance of a variable type. You were given invalid code. Commented Apr 28, 2019 at 2:11

1 Answer 1

7

Even if you add new() constraint, you will end up with the following error

'T': cannot provide arguments when creating an instance of a variable type.

You were given invalid code.

The new constraint specifies that any type argument in a generic class declaration must have a public parameterless constructor. To use the new constraint, the type cannot be abstract.

Reference new constraint (C# Reference)

Another option to consider could be to use Activator.CreateInstance (Type, Object[]).

Given

public interface IContextFactory<TContext> where TContext : DbContext {
    TContext Create(string connectionString);
}

You would implement it as follows

public class ContextFactory<TContext> : IContextFactory<TContext>
    where TContext : DbContext {

    public TContext Create(string connectionString) {
        var optionsBuilder = new DbContextOptionsBuilder<TContext>();
        optionsBuilder.UseSqlServer(connectionString);
        return (TContext)Activator.CreateInstance(typeof(TContext), optionsBuilder.Options);
    }
}

This could be refactored further to separate concerns

public class ContextFactory<TContext> : IContextFactory<TContext>
    where TContext : DbContext {

    public TContext Create(DbContextOptions<TContext> options) {
        return (TContext)Activator.CreateInstance(typeof(TContext), options);
    }
}

so that the builder will become the responsibility of where the factory is being used.

var connection = @"....";
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connection);

//Assuming factory is `IContextFactory<BloggingContext>`    
using (var context = factory.Create(optionsBuilder.Options))
{
   // do stuff
}

EDIT

The factory can be registered as open generics in ConfigureServices method

services.AddSingleton(typeof(IContextFactory<>), typeof(ContextFactory<>));
Sign up to request clarification or add additional context in comments.

4 Comments

I think I need to register the factory in startup as a singleton - if so how?
@Nkosi: Thanks for spotting the mistake in the code that I provided to answer Simon's original question. I have edited the answer to show how the factory can be registered as open generics.
@si2030 please have a look at the updated answer to the original question.
Thank you. However, this code did not work in my project. I coded it this way services.AddTransient(typeof(DatabaseContextFactory)); and it worked in my project

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.