• GraphQL Part VI: 使用 Postgres 和 EF Core 持久化数据


    这次我们关注持久化数据而不是 GraphQL 方面。我们将使用 Postgres 数据库作为后端存储,您可能问:为什么要使用 Postgres?因为大家都熟悉 SQL Server,我们尝试一下新东西。

    在数据访问层,我们将使用一个数据源类,或者说是一个仓储。基于抽象的最佳实践,我们首先创建数据源的接口,IDataStore

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

    我们已经用过 GetItemByBarcode 方法了,GetItems 方法返回库存的所有条目。我们随后将会添加一个 GraphQL 的集合。

    实现该接口非常简单。

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

    我们使用 Entity Framework Core,这里我们引入 ApplicationDbContext。该类扩展自 Entity Framework 的 DbContext。现在它包含单个的关于 Item 实体的 DbSet 。这将会创建一个名为 Items 的表。

    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        {
    
        }
        public DbSet<Item> Items { get; set; }
    }

    DbContextOptions 是很酷的传递参数的方式,例如数据库连接串 ConnectionString,在 Startup.cs 的 ConfigureServices 方法中配置 ApplicationDbContext。

    services.AddEntityFrameworkNpgsql()
    .AddDbContext<ApplicationDbContext>(
    options => options.UseNpgsql(Configuration["DefaultConnection"]));

    AddEntityFrameworkNpgsql() 扩展方法来自独立的 Npgsql.EntityFrameworkCore.PostgreSQL 包。可以通过 Nuget 或者 dotnet CLI 安装:

    dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL --version 2.0.2

    Configuration 属性来自类型 IConfigurationRoot。我们在 Startup.cs 的构造函数中构建配置对象,并赋予 Configuration 属性。

    public IConfigurationRoot Configuration { get; set; }
    
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
    
        if (env.IsDevelopment())
        {
            builder.AddUserSecrets<Startup>();
        }
    
        builder.AddEnvironmentVariables();
        Configuration = builder.Build();
    }

    我们可以把数据库连接串保存在 appsettings.json 文件中,对于我来说,由于保密问题,我总是将它们保存在用户密钥文件中。这里的 builder.AddUserSecrets<Startup>()。你可以在用户密钥文件中如下添加数据库连接串。

    dotnet user-secrets set DefaultConnection 'your-connection-string'

    然后如下执行初始迁移的命令。

    dotnet ef migrations add Initial --output-dir DataMigrations

    使用如下的命令应用迁移命令以创建数据库。

    dotnet ef database update

    AddDbContext<ApplicationDbContext>() 使用受限的服务生命周期注册 DbContext 。Singleton 和 scope 之间的区别是:

    • Singleton 服务实例仅仅创建一次,在应用启动的时候。同样的实例被以后所有的请求所共享。
    • Scopt 服务实例在每次被请求的时候重新创建

    到目前为止,我们一直使用单例模式,但是,如果我们这样用于 IDataStore,会导致下面的问题:

    • 如果你小心检查,我们将 ApplicationDbContext 直接注入到 DataStore,简单来说,我们在通过一个单例的服务访问 scoped 的服务。
    • 尽管 scope 的服务实例会在每次请求的时候创建,但我们是从单例对象来访问它的,这导致永远返回第一次创建的实例,结果与单例一样了。

    所以,我们必须也将 IDataStore 也注册为 scoped 。

    services.AddScoped<IDataStore, DataStore>();

    直至现在的 EF Core 2.0,我们仍然没有默认的 Seed 方法。所以,我们使用下面的方式准备一些数据。

    public class ApplicationDatabaseInitializer
    {
        public async Task SeedAsync(IApplicationBuilder app)
        {
            using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
            {
                var applicationDbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
    
                await applicationDbContext.Database.EnsureDeletedAsync();
                await applicationDbContext.Database.MigrateAsync();
                await applicationDbContext.Database.EnsureCreatedAsync();
    
                var items = new List<Item>
                {
                    new Item { Barcode= "123", Title="Headphone", SellingPrice=50},
                    new Item { Barcode= "456", Title="Keyboard", SellingPrice= 40},
                    new Item { Barcode= "789", Title="Monitor", SellingPrice= 100}
                };
    
                await applicationDbContext.Items.AddRangeAsync(items);
    
                await applicationDbContext.SaveChangesAsync();
            }
        }
    }

    一旦我们应用启动,我们期望初始化数据,所以,在 Configure 方法中添加如下代码行。

    new ApplicationDatabaseInitializer().SeedAsync(app).GetAwaiter();

    一旦进入产品阶段,我们就不再需要这些种子数据。所以,这里并不介意使用 new 来创建对象实例。

    其它的调整包括变更服务的生命周期:

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

    此刻,应用可以运行但是不能注册该架构。要记住 .net core 中间件仅在第一次启动的时候注册。但是在中间件中使用 scoped/transient 服务,我们就需要调用 InvokeAsync() 方法了。

    public async Task InvokeAsync(HttpContext httpContext, ISchema schema)
    {
        ....
        ....
    }

    最后,我希望添加一个新的集合类型的字段,以用于 Items,该字段的类型应该是 ItemType 的 ListGraphType 。

    Field<ListGraphType<ItemType>>(
        "items",
        resolve: context =>
        {
            return dataStore.GetItems();
        }
    );

    运行应用,查询 items,你将收到如下的结果。

  • 相关阅读:
    XCode5中新建工程后强制使用了ARC,如何去掉?
    面向对象程序的设计原则--Head First 设计模式笔记
    ios控件自定义指引
    iOS UITableViewDelegate && UITableViewDataSource 执行顺序
    awakeFromNib方法和viewDidLoad方法区别
    ios 视图的旋转及应用
    线段树模板 (刘汝佳)
    poj 3468
    hdu 2829(四边形优化 && 枚举最后一个放炸弹的地方)
    poj 3517(约瑟夫环问题)
  • 原文地址:https://www.cnblogs.com/haogj/p/9178001.html
Copyright © 2020-2023  润新知