C# Dotnet core - Issues with implementing generic

2020-05-03 09:41发布

问题:

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:

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<>));