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

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

opera tab semi 內存泄漏 pub setting public invalid dsi

技術分享圖片

ASP.NET Core中使用GraphQL

  • ASP.NET Core中使用GraphQL - 第一章 Hello World
  • ASP.NET Core中使用GraphQL - 第二章 中間件
  • ASP.NET Core中使用GraphQL - 第三章 依賴註入
  • ASP.NET Core中使用GraphQL - 第四章 GrahpiQL
  • 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

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