1. 程式人生 > >ASP.NET Core中使用GraphQL - 第六章 使用EF Core作為持久化倉儲

ASP.NET Core中使用GraphQL - 第六章 使用EF Core作為持久化倉儲

ASP.NET Core中使用GraphQL


本篇中我將演示如何配置持久化倉儲,這裡原文中是使用的Postgres, 這裡我改用了EF Core For SqlServer。本文的例子需要在上一篇的程式碼基礎上修改。沒有程式碼的同學,可以去

https://github.com/lamondlu/GraphQL_Blogs/tree/master/Part%20V下載。

之前我們編寫了一個DataStore類,裡面硬編碼了一個數據集合,這裡我們希望改用依賴注入的方式進行解耦,所以首先我們需要建立一個抽象介面IDataStore

public interface IDataStore
{
    IEnumerable<Item> GetItems();
    Item GetItemByBarcode(string barcode);
}

由於接下來我們需要使用EF Core, 所以這裡我們需要新增一個EF Core的上下文類ApplicationDbContext

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
    {

    }
    
    public DbSet<Item> Items { get; set; }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Item>().ToTable("Items");
        modelBuilder.Entity<Item>().HasKey(p => p.Barcode);
    
        modelBuilder.Entity<Item>().HasData(new Item { 
            Barcode = "123", 
            Title = "Headphone", 
            SellingPrice = 50 });
            
        modelBuilder.Entity<Item>().HasData(new Item { 
            Barcode = "456", 
            Title = "Keyboard", 
            SellingPrice = 40 });
        modelBuilder.Entity<Item>().HasData(new Item { 
            Barcode = "789", 
            Title = "Monitor", 
            SellingPrice = 100 });

        base.OnModelCreating(modelBuilder);
    }
}

這裡為了匯入一些初始資料,我們在OnModelCreating方法中使用HasData方法添加了3個初始資料。

下面我們修改DataStore類, DataStore應該實現IDataStore介面, 其中的GetItemByBarcodeGetItems方法需要改為從資料庫中讀取。

public class DataStore : IDataStore
{
    private ApplicationDbContext _applicationDbContext;

    public DataStore(ApplicationDbContext applicationDbContext)
    {
        _applicationDbContext = applicationDbContext;
    }

    public Item GetItemByBarcode(string barcode)
    {
        return _applicationDbContext.Items.First(i => i.Barcode.Equals(barcode));
    }

    public IEnumerable<Item> GetItems()
    {
        return _applicationDbContext.Items;
    }
}

接下來,我們要在Startup.cs類中的ConfigureServices新增Entity Framework配置

services.AddDbContext<ApplicationDbContext>(option =>
{
    option.UseSqlServer(Configuration.GetConnectionString("SampleDB"));
});

TIPS: 這裡注意不要忘記建立一個appsettings.json, 在其中新增資料庫連線字串

配置完成之後,我們需要使用以下命令新增Migration,並更新資料庫

dotnet ef migrations add Initial
dotnet ef database update

現在針對資料庫的修改都已經完成了。

另外我們還需要修改服務註冊程式碼,將註冊服務的生命週期從單例(Singleton)改為作用域(Scoped), 因為當注入服務的生命週期為單例時,需要處理多執行緒問題和潛在的記憶體洩漏問題。

services.AddScoped<IDataStore, DataStore>();
services.AddScoped<HelloWorldQuery>();
services.AddScoped<ISchema, HelloWorldSchema>();

修改完成後,Startup.cs最終程式碼如下:

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

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(option =>
        {                                                                                             
             option.UseSqlServer(Configuration.GetConnectionString("SampleDB"));
        });

        services.AddSingleton<IDocumentExecuter, DocumentExecuter>();
        services.AddSingleton<IDocumentWriter, DocumentWriter>();

        services.AddScoped<IDataStore, DataStore>();
        services.AddScoped<HelloWorldQuery>();
        services.AddScoped<ISchema, HelloWorldSchema>();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseDefaultFiles();
        app.UseStaticFiles();

        app.UseMiddleware<GraphQLMiddleware>();
    }
}

現在我們啟動專案, 程式會丟擲一個錯誤

System.InvalidOperationException: Cannot resolve scoped service 'GraphQL.Types.ISchema' from root provider

這個問題的原因是,中介軟體是單例的,如果在中介軟體的建構函式中使用作用域(Scoped)的依賴注入, 會導致這個問題(具體請參見https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1)。這裡ISchema的生命週期是作用域,並且在GraphQLMiddleware類中是從建構函式注入的,所以這裡我們需要修改GraphQLMiddleware類,ISchema需要改從Invoke方法注入。

中介軟體最終程式碼如下:

public class GraphQLMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IDocumentWriter _writer;
    private readonly IDocumentExecuter _executor;
    public GraphQLMiddleware(RequestDelegate next, IDocumentWriter writer, IDocumentExecuter executor)
    {
        _next = next;
        _writer = writer;
        _executor = executor;
    }

    public async Task InvokeAsync(HttpContext httpContext, ISchema schema)
    {
        if (httpContext.Request.Path.StartsWithSegments("/api/graphql")
            && string.Equals(httpContext.Request.Method,
            "POST",
            StringComparison.OrdinalIgnoreCase))
        {
            string body;
            using (var streamReader = new StreamReader(httpContext.Request.Body))
            {
                body = await streamReader.ReadToEndAsync();

                var request = JsonConvert.DeserializeObject<GraphQLRequest>(body);

                var result = await _executor.ExecuteAsync(doc =>
                {
                    doc.Schema = schema;
                    doc.Query = request.Query;
                    doc.Inputs = request.Variables.ToInputs();
                }).ConfigureAwait(false);

                var json = await _writer.WriteToStringAsync(result);
                await httpContext.Response.WriteAsync(json);
            }
        }
        else
        {
            await _next(httpContext);
        }
    }
}

修改完成之後,我們重新啟動專案,專案正常啟動成功, GraphiQL介面出現。

現在我們還是使用上一章的查詢程式碼,查詢二維碼是123的貨物資料。

資料正常從資料庫中讀取成功。下一章我們將講解在ASP.NET Core中如何使用GraphQL新增修改資料。

本文原始碼: https://github.com/lamondlu/GraphQL_Blogs/tree/master/Part%20VI