使用Hot Chocolate和.NET 6構建GraphQL應用(2) —— 實體相關功能實現


系列導航

使用Hot Chocolate和.NET 6構建GraphQL應用文章索引

需求

在本文中,我們將會准備好用於實現GraphQL接口所依賴的底層數據,為下一篇文章具體實現GraphQL接口做准備。

實現

實體定義

在上一篇文章使用Hot Chocolate和.NET 6構建GraphQL應用(1) —— GraphQL及示例項目介紹我們給出的實體關系圖稍微進行一些簡化,去掉了關於評論回復的實體定義。我們先來實現關於Post/Comment/Tag的實體以及對應的Configuration:

  • Post.cs
namespace PostGraphi.Domain.Post.Entities;

public class Post : AuditableEntity, IEntity<Guid>, IHasDomainEvent
{
    public Guid Id { get; set; }
    public string? Title { get; set; }
    public string? Author { get; set; }
    public string? Abstraction { get; set; }
    public string? Content { get; set; }
    public string? Link { get; set; }
    public DateTime PublishedAt { get; set; }

    public ICollection<Tag> Tags { get; set; } = new HashSet<Tag>();
    public ICollection<Comment> Comments { get; set; } = new List<Comment>();

    public List<DomainEvent> DomainEvents { get; set; } = new();
}
  • Comment.cs
namespace PostGraphi.Domain.Post.Entities;

public class Comment : IEntity<Guid>
{
    public Guid Id { get; set; }
    public string? Content { get; set; }
    public string? Name { get; set; }
    public DateTime CreatedAt { get; set; }
    
    public Guid PostId { get; set; }
    public Post Post { get; set; }
}
  • Tag.cs
namespace PostGraphi.Domain.Post.Entities;

public class Tag : IEntity<Guid>
{
    public Guid Id { get; set; }
    public string? Name { get; set; }

    public ICollection<Post> Posts { get; set; } = new HashSet<Post>();
}
  • PostConfiguration.cs
namespace PostGraphi.Infrastructure.Persistence.Configurations;

public class PostConfiguration : IEntityTypeConfiguration<Post>
{
    public void Configure(EntityTypeBuilder<Post> builder)
    {
        builder.Ignore(e => e.DomainEvents);

        builder.Property(t => t.Title).HasMaxLength(200).IsRequired();

        builder.HasMany(t => t.Tags).WithMany(a => a.Posts);
        builder.HasMany(t => t.Comments).WithOne(c => c.Post).HasForeignKey(c => c.PostId);
    }
}
  • CommentConfiguration.cs
namespace PostGraphi.Infrastructure.Persistence.Configurations;

public class CommentConfiguration : IEntityTypeConfiguration<Comment>
{
    public void Configure(EntityTypeBuilder<Comment> builder)
    {
        builder.Property(t => t.Content).HasMaxLength(255).IsRequired();

        builder.HasOne(t => t.Post).WithMany(a => a.Comments).HasForeignKey(t=>t.PostId);
    }
}
  • TagConfiguration.cs
namespace PostGraphi.Infrastructure.Persistence.Configurations;

public class TagConfiguration : IEntityTypeConfiguration<Tag>
{
    public void Configure(EntityTypeBuilder<Tag> builder)
    {
        builder.Property(t => t.Name).HasMaxLength(30).IsRequired();

        builder.HasMany(t => t.Posts).WithMany(a => a.Tags);
    }
}

並在DbContext類中添加:

  • PostGraphiDbContext.cs
public DbSet<Post> Posts => Set<Post>();
public DbSet<Tag> Tags => Set<Tag>();
public DbSet<Comment> Comments => Set<Comment>();

數據庫注入

我們需要修改模版里默認的數據庫注入的方式,為了演示重點內容起見,我將數據庫修改為SQLite:

  • InfrastructureDependencyInjections.cs
// 省略其他...
// 為了注入數據庫實例時還能在構造函數中使用依賴注入
services.AddEntityFrameworkSqlite();
// 使用AddPooledDbContextFactory進行數據庫的注入,因為在GraphQL的並發請求下,直接AddDbContext在執行時會報錯。
services.AddPooledDbContextFactory<PostGraphiDbContext>((serviceProvider, options) =>
{
    options.UseSqlite("Data Source=PostGraphi.db");
    // 允許在DbContext的構造函數中使用依賴注入容器
    options.UseInternalServiceProvider(serviceProvider);
});

准備種子數據

修改PostGraphiDbContextSeed內容,准備一些種子數據:

  • PostGraphiDbContextSeed.cs
public static async Task SeedSampleDataAsync(PostGraphiDbContext context)
{
    if (!context.Posts.Any())
    {
        var posts = new List<Post>
        {
            new()
            {
                Title = "1 - introduction to graphql",
                Abstraction = "this is an introduction post for graphql",
                Content = "some random content for post 1",
                Author = "code4nothing",
                PublishedAt = DateTime.Now.AddDays(-2),
                Link = "http://link-to-post-1.html",
                Comments = new List<Comment>
                {
                    new() { CreatedAt = DateTime.Now.AddHours(-3), Content = "comment 01 for post 1", Name = "kindUser01" },
                    new() { CreatedAt = DateTime.Now.AddHours(-2), Content = "comment 02 for post 1", Name = "kindUser01" },
                    new() { CreatedAt = DateTime.Now, Content = "comment 03 for post 1", Name = "kindUser02" }
                },
                Tags = new List<Tag>
                {
                    new() { Name = "graphql" },
                    new() { Name = ".net6" }
                }
            },
            new()
            {
                Title = "2 - integrate graphql with hot chocolate to .net6",
                Abstraction = "this is an introduction post for how to integrate graphql to .net6",
                Content = "some random content for post 2",
                Author = "code4nothing",
                PublishedAt = DateTime.Now.AddDays(-1),
                Link = "http://link-to-post-2.html",
                Comments = new List<Comment>
                {
                    new() { CreatedAt = DateTime.Now.AddHours(-5), Content = "comment 01 for post 2", Name = "kindUser02" },
                    new() { CreatedAt = DateTime.Now.AddHours(-1), Content = "comment 02 for post 2", Name = "kindUser03" },
                    new() { CreatedAt = DateTime.Now, Content = "comment 03 for post 2", Name = "kindUser04" }
                },
                Tags = new List<Tag>
                {
                    new() { Name = "graphql" },
                    new() { Name = ".net6" },
                    new() { Name = "hot chocolate" }
                }
            },
            new()
            {
                Title = "3 - use Dapr with .net6",
                Abstraction = "this is an introduction post for how to use dapr in .net6 applications",
                Content = "some random content for post 3",
                Author = "code4dapr",
                PublishedAt = DateTime.Now.AddDays(-1),
                Link = "http://link-to-post-3.html",
                Comments = new List<Comment>
                {
                    new() { CreatedAt = DateTime.Now.AddHours(-3), Content = "comment 01 for post 3", Name = "kindUser01" },
                    new() { CreatedAt = DateTime.Now.AddHours(-2), Content = "comment 02 for post 3", Name = "kindUser02" },
                    new() { CreatedAt = DateTime.Now, Content = "comment 03 for post 3", Name = "kindUser04" },
                    new() { CreatedAt = DateTime.Now, Content = "comment 04 for post 3", Name = "kindUser03" }
                },
                Tags = new List<Tag>
                {
                    new() { Name = "dapr" },
                    new() { Name = ".net6" }
                }
            },
            new()
            {
                Title = "4 - use dapr service invocation in .net6",
                Abstraction = "this is an introduction post for how to use dapr service invocation in .net6",
                Content = "some random content for post 4",
                Author = "code4dapr",
                PublishedAt = DateTime.Now.AddDays(-1),
                Link = "http://link-to-post-4.html",
                Comments = new List<Comment>
                {
                    new() { CreatedAt = DateTime.Now.AddHours(-3), Content = "comment 01 for post 4", Name = "kindUser04" }
                },
                Tags = new List<Tag>
                {
                    new() { Name = "dapr" },
                    new() { Name = ".net6" },
                    new() { Name = "service invocation" }
                }
            }
        };
        
        context.Posts.AddRange(posts);
        await context.SaveChangesAsync();
    }
}

應用數據庫遷移和種子數據生成

  • Program.cs
public static void ApplyDatabaseMigration(this WebApplication app)
{
    using var scope = app.Services.CreateScope();
    
    var retryPolicy = CreateRetryPolicy(app.Configuration, Log.Logger);
    // 注意因為數據庫的注入方式變了,所以獲取數據庫Context的方法也相應修改
    using var context = scope.ServiceProvider.GetRequiredService<IDbContextFactory<PostGraphiDbContext>>().CreateDbContext();
    // 應用Migration
    retryPolicy.Execute(context.Database.Migrate);
    // 生成種子數據
    PostGraphiDbContextSeed.SeedSampleDataAsync(context).Wait();
}

執行dotnet migrations add命令行去生成第一次migration數據,運行程序,可以通過數據庫工具看到種子數據已經被成功生成到數據庫了。

總結

下一篇文章起,我們就開始使用Hot Chocolate來完成GraphQL接口的實現。在進入之前,我想先簡單介紹一下在Hot Chocolate中編寫GraphQL相關功能的三種方式,為下一節內容作准備:

Schema First

這種實現方式完全采用了GraphQL Schema定義語言,寫起來比較繁瑣,我們一般不采用這種方式。

Code First

這種方式不需要寫Schema,但是每個C#定義的實體類必須有對應Mapping的GraphQL C#類。

Annotation First

不需要寫Schema,也不要求有對應的GraphQL C#類,僅僅需要定義的實體類。實現方式比較簡單,具體的Schema生成由GraphQL服務器自動實現。

這三種方式可以混搭着使用,為了演示盡量多的Hot Chocolate特性,在系列文章中會以第二種和第三種方式為主。

參考文章

  1. Hot Chocolate Document
  2. A Demo On Hot Chocolate GraphQL Integration In Asp.Net Core Application Using Dapper Micro ORM


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM