简体   繁体   中英

Access to DbContext with dependency injection

I don't understand the official documentation , at the paragraph about dependency injection.

They say I can use a controller (but from here I know I don't need it because I'm using Razor pages) or I can access directly to ServiceProvider:

using (var context = serviceProvider.GetService<BloggingContext>())
{
  // do stuff
}

but how to retrieve the reference to the ServiceProvider in a generic C# class of my project?

I setup the services in startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<MyDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyDbContext")));
    services.AddHangfire(options => options.UseSqlServerStorage(Configuration.GetConnectionString("MyDbContext")));
    services.AddOptions();
    services.Configure<MySettings>(options => Configuration.GetSection("MySettings").Bind(options));
    services.AddMvc().AddDataAnnotationsLocalization();
}

EDIT

To further clarify my confusion, what I'm trying to do is to add/get data from a Worker class. Here I found an example how to do it:

using (var context = new BloggingContext())
{
    var blog = new Blog { Url = "http://sample.com" };
    context.Blogs.Add(blog);
    context.SaveChanges();

    Console.WriteLine(blog.BlogId + ": " +  blog.Url);
}

But I cannot use a constructor without the argument DbContext if I'm going to use dependency injection. On the other side, if I add the argument, I have to pass the right value when I call the constructor as in the above example - and this is the initial question.

EDIT2

I'm going to post a "complete" example. It's hard to me to understand, but I'm trying anyway:

program.cs

using Hangfire;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Options;

namespace MyProject
{
    public class Program
    {

        public static void Main(string[] args)
        {
            IWebHost host = BuildWebHost(args);
            BackgroundJob.Enqueue<MyClass>(x => x.ImportOperatorList());
            host.Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .Build();

    }
}

startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Hangfire;
using MyProject.Models;

namespace MyProject
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<MyProjectContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyProjectContext")));
            services.AddHangfire(options => options.UseSqlServerStorage(Configuration.GetConnectionString("MyProjectContext")));
            services.AddOptions();
            services.Configure<MySettings>(options => Configuration.GetSection("MySettings").Bind(options));
            services.AddMvc().AddDataAnnotationsLocalization();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseStaticFiles();
            app.UseHangfireDashboard();
            app.UseHangfireServer();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller}/{action=Index}/{id?}");
            });
        }
    }
}

MyProjectContext.cs

using Microsoft.EntityFrameworkCore;

namespace MyProject.Models
{
    public class MyProjectContext : DbContext
    {
        public MyProjectContext(DbContextOptions<MyProjectContext> options) : base(options) { }

        public DbSet<Operator> Operators { get; set; }
    }

    public class Operator
    {
        public int Id { get; set; }
        [MaxLength(40)]
        public string Name { get; set; }
        public int Password { get; set; }
    }
}

MyClass.cs

using MyProject.Models;
using System;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;

namespace MyProject
{
    public class MyClass
    {
        const string REGEX_OPERATORS = "^(?<Id>.{4})(?<Name>.{40})(?<Password>.{5})";
        private readonly Regex reOperators = new Regex(REGEX_OPERATORS, RegexOptions.Compiled);

        public void ImportOperatorList()
        {
            var path = @"F:\testdata.txt";
            string[] lines = File.ReadAllLines(path);

            foreach (var line in lines)
            {
                Match match = reOperators.Match(line);
                if (match.Success)
                {
                    string rawId = match.Groups["Id"].Value;
                    string rawName = match.Groups["Name"].Value;
                    string rawPassword = match.Groups["Password"].Value;

                    int Id;
                    try
                    {
                        Id = int.Parse(rawId, System.Globalization.NumberStyles.Integer);
                    }
                    catch (Exception)
                    {
                        throw;
                    }

                    string Name = rawName;

                    int Password;
                    try
                    {
                        Password = int.Parse(rawPassword, System.Globalization.NumberStyles.Integer);
                    }
                    catch (Exception)
                    {
                        throw;
                    }

                    using (var context = new MyProjectContext(/* ??? */))
                    {
                        var op = new Operator
                        {
                            Id = Id,
                            Name = Name,
                            Password = Password
                        };

                        context.Operators.Add(op);
                        Debug.WriteLine(context.SaveChanges());
                    }
                }
            }
        }
    }
}

Of course isn't complete nor compilable, because there are a lot of other files in the project (even without my own specific application).

Building upon your self answer.

Refactor MyClass to be dependent on abstractions and not too tightly coupled to concretions.

Here is the refactored MyClass

public class MyClass {
    const string REGEX_OPERATORS = "^(?<Id>.{4})(?<Name>.{40})(?<Password>.{5})";
    private readonly Regex reOperators = new Regex(REGEX_OPERATORS, RegexOptions.Compiled);
    private readonly IFileSystem File;
    private readonly IProjectContext context;

    public MyClass(IFileSystem File, IProjectContext context) {
        this.File = File;
        this.context = context;
    }

    public void ImportOperatorList() {
        var path = @"F:\testdata.txt";
        var lines = File.ReadAllLines(path);
        foreach (var line in lines) {
            var match = reOperators.Match(line);
            if (match.Success) {
                string rawId = match.Groups["Id"].Value;
                string rawName = match.Groups["Name"].Value;
                string rawPassword = match.Groups["Password"].Value;
                var op = new Operator {
                    Id = int.Parse(rawId, System.Globalization.NumberStyles.Integer),
                    Name = rawName,
                    Password = int.Parse(rawPassword, System.Globalization.NumberStyles.Integer)
                };
                context.Operators.Add(op);
            }
        }
        if (lines.Length > 0)
            Debug.WriteLine(context.SaveChanges());
    }
}

With the following modifications

public interface IFileSystem {
    string[] ReadAllLines(string path);
}

public class FileWrapper : IFileSystem {
    public string[] ReadAllLines(string path) {
        var lines = File.ReadAllLines(path);
        return lines;
    }
}

public interface IProjectContext : IDisposable {
    DbSet<Operator> Operators { get; set; }
    int SaveChanges();
    //...add other functionality that needs to be exposed as needed
    //eg: Database Database { get; }
    //...
}

public class MyProjectContext : DbContext, IProjectContext {
    public MyProjectContext(DbContextOptions<MyProjectContext> options) : base(options) { }

    public DbSet<Operator> Operators { get; set; }
}

You would make sure all the abstractions are registered with the service container at the composition root.

public void ConfigureServices(IServiceCollection services) {
    services.AddDbContext<MyProjectContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyProjectContext")));
    services.AddHangfire(options => options.UseSqlServerStorage(Configuration.GetConnectionString("MyProjectContext")));
    services.AddOptions();
    services.Configure<MySettings>(options => Configuration.GetSection("MySettings").Bind(options));
    services.AddMvc().AddDataAnnotationsLocalization();

    //...adding additional services
    services.AddScoped<IProjectContext, MyProjectContext>();
    services.AddTransient<IFileSystem, FileWrapper>();
    services.AddTransient<MyClass, MyClass>();
}

Now when using the scoped service provider you can ask for your class and all the dependencies will be injected when resolving MyClass

using (var scope = host.Services.CreateScope()) {
    var services = scope.ServiceProvider;
    var myClass = services.GetRequiredService<MyClass>();
    myClass.ImportOperatorList();
}

As the above is scoped, the container will manage the disposal of any services created by the container when it goes out of scope.

You have to pass the context argument to the function manually, dependency injection doesn't do this for you. Hence, in program.cs you might add:

using (var scope = host.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var context = services.GetRequiredService<MyProjectContext>();

    // pass context to relevant Classes/Functions, i.e.
    MyClass myClass = new MyClass();
    myClass.ImportOperatorList(context);
}

In MyClass.cs now you can directly use that variable:

public void ImportOperatorList(MyProjectContext context)
{
    // ...
    context.Operators.Add(op);
    context.SaveChanges();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM