ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

在Blazor中构建数据库应用程序——第2部分——服务——构建CRUD数据层

2021-09-12 23:04:42  阅读:246  来源: 互联网

标签:Task get await CRUD 构建 new TRecord Blazor public


目录

存储库和数据库

目标

服务

泛型

数据访问

DbTaskResult

数据类

WeatherForecast

实体框架层

WeatherForecastDBContext

LocalWeatherDbContext

InMemoryWeatherDbContext

DbContextExtensions

IFactoryDataService

FactoryDataService

FactoryServerDataService

API控制器

FactoryServerInMemoryDataService

控制器服务

IFactoryControllerService

FactoryControllerService

WeatherForecastControllerService

总结


这是构建Blazor数据库应用程序系列中的第二篇文章。它描述了如何将数据和业务逻辑层构建为通用库代码,从而使部署特定于应用程序的数据服务变得简单。它是对早期版本的完全重写。

该系列文章如下:

  1. 项目结构和框架。
  2. 服务——构建CRUD数据层。
  3. 查看组件——UI中的CRUD编辑和查看操作。
  4. UI 组件——构建HTML/CSS控件。
  5. 查看组件——UI中的CRUD列表操作。

存储库和数据库

存储库已移至CEC.Database存储库。您可以将其用作开发您自己的应用程序的模板。以前的存储库已过时,将被删除。

存储库中的/SQL中有一个用于构建数据库的SQL脚本。该应用程序可以使用真正的SQL数据库或内存中的SQLite数据库。

您可以在同一站点上看到在此处运行的项目的Server和WASM版本

目标

在深入研究细节之前,让我们先看看我们的目标:构建库代码,这样声明一个标准的UI控制器服务就像这样简单:

public class WeatherForecastControllerService : FactoryControllerService<WeatherForecast>
{
    public WeatherForecastControllerService(IFactoryDataService factoryDataService) : base(factoryDataService) { }
}

并声明一个如下所示的数据库DbContext:

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

    // A DbSet per database entity
    public DbSet<WeatherForecast> WeatherForecast { get; set; }

}

我们添加新数据库实体的过程是:

  1. 将必要的表添加到数据库中。
  2. 定义一个数据类。
  3. 在DbContext中定义DbSet。
  4. 定义一个public class nnnnnnControllerService服务并将其注册到服务容器。

某些实体会出现复杂情况,但这不会使该方法无效——库中80%以上的代码。

服务

Blazor建立在DI [依赖注入]和IOC [控制反转]原则之上。如果您不熟悉这些概念,请在深入研究Blazor之前进行一些了解。从长远来看,它会为您节省时间!

Blazor Singleton和Transient服务相对简单。您可以在Microsoft 文档中阅读有关它们的更多信息。Scoped稍微复杂一些。

  1. 作用域服务对象存在于客户端应用程序会话的生命周期内——请注意客户端而不是服务器。任何应用程序重置,例如F5或离开应用程序的导航,都会重置所有范围服务。浏览器中的重复选项卡会创建一个新应用程序和一组新的范围服务。
  2. 范围服务可以进一步限定为代码中的单个对象。OwningComponentBase组件类都有功能来限制范围的服务的生命到组件的寿命。

Services是Blazor IOC [控制反转]容器。服务实例声明如下:

  1. 在Blazor服务器Startup.cs的ConfigureServices中
  2. 在Blazor WASM Program.cs中。

该解决方案使用服务集合扩展方法,例如AddApplicationServices,将所有特定于应用程序的服务集中在一个屋檐下。

// Blazor.Database.Web/startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    // the local application Services defined in ServiceCollectionExtensions.cs
    // services.AddApplicationServices(this.Configuration);
    services.AddInMemoryApplicationServices(this.Configuration);
}

扩展在静态类中被声明为静态扩展方法。这两种方法如下所示。

//Blazor.Database.Web/Extensions/ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration configuration)
    {
        // Local DB Setup
        var dbContext = configuration.GetValue<string>("Configuration:DBContext");
        services.AddDbContextFactory<LocalWeatherDbContext>(options => options.UseSqlServer(dbContext), ServiceLifetime.Singleton);
        services.AddSingleton<IFactoryDataService, LocalDatabaseDataService>();
        services.AddScoped<WeatherForecastControllerService>();
        return services;
    }

    public static IServiceCollection AddInMemoryApplicationServices(this IServiceCollection services, IConfiguration configuration)
    {
        // In Memory DB Setup
        var memdbContext = "Data Source=:memory:";
        services.AddDbContextFactory<InMemoryWeatherDbContext>(options => options.UseSqlite(memdbContext), ServiceLifetime.Singleton);
        services.AddSingleton<IFactoryDataService, TestDatabaseDataService>();
        services.AddScoped<WeatherForecastControllerService>();
        return services;
    }
}

在WASM项目的program.cs中:

// program.cs
public static async Task Main(string[] args)
{
    .....
    // Added here as we don't have access to builder in AddApplicationServices
    builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
    // the Services for the Application
    builder.Services.AddWASMApplicationServices();
    .....
}
// ServiceCollectionExtensions.cs
public static IServiceCollection AddWASMApplicationServices(this IServiceCollection services)
{
    services.AddScoped<IFactoryDataService, FactoryWASMDataService>();
    services.AddScoped<WeatherForecastControllerService>();
    return services;
}

要点:

  1. 每个项目/库都有一个IServiceCollection扩展方法来封装项目所需的特定服务。
  2. 只有数据层服务不同。由Blazor服务器和WASM API服务器使用的服务器版本与数据库和实体框架接口。它的范围是单例。
  3. 一切都是异步的,使用DbContextFactory和在使用时管理DbContext实例。WASM客户端版本使用HttpClient(这是一个作用域服务)来调用API,因此是作用域的。
  4. 实现IFactoryDataService的IFactoryDataService通过泛型处理所有数据请求。TRecord定义检索和返回哪个数据集。工厂服务样板所有核心数据服务代码。
  5. 既有真正的SQL数据库,也有内存中的SQLite DbContext。

泛型

工厂库代码严重依赖泛型。定义了两个通用实体:

  1. TRecord表示模型记录类。它必须是一个类,实现IDbRecord并定义一个空的new()。 TRecord用于方法级别。
  2. TDbContext是数据库上下文。它必须从DbContext类继承。

类声明如下所示:

//Blazor.SPA/Services/FactoryDataService.cs
public abstract class FactoryDataService<TContext>: IFactoryDataService<TContext>
    where TContext : DbContext
......
    // example method template  
    public virtual Task<TRecord> GetRecordAsync<TRecord>(int id) where TRecord : class, IDbRecord<TRecord>, new()
        => Task.FromResult(new TRecord());

数据访问

在深入研究细节之前,让我们先看看我们需要实现的主要CRUDL方法:

  1. GetRecordList——获取数据集中的记录列表。这可以被分页和排序。
  2. GetRecord——通过ID获取单个记录
  3. CreateRecord——创建新记录
  4. UpdateRecord——根据ID更新记录
  5. DeleteRecord——根据ID删除记录

在我们阅读本文时,请记住这些。

DbTaskResult

数据层CUD操作返回一个DbTaskResult对象。大多数属性是不言而喻的。它旨在由UI使用以构建CSS框架实体,例如警报和Toast。NewID从创建操作返回新ID 。

public class DbTaskResult
{
    public string Message { get; set; } = "New Object Message";
    public MessageType Type { get; set; } = MessageType.None;
    public bool IsOK { get; set; } = true;
    public int NewID { get; set; } = 0;
}

数据类

数据类实现IDbRecord。

  1. ID是标准的数据库标识字段。通常一个int。
  2. GUID 是此记录副本的唯一标识符。
  3. DisplayName为记录提供通用名称。我们可以在标题和其他UI组件中使用它。

public interface IDbRecord<TRecord>
    where TRecord : class, IDbRecord<TRecord>, new()
{
    public int ID { get; }
    public Guid GUID { get; }
    public string DisplayName { get; }
}

WeatherForecast

这是WeatherForecast数据实体的数据类。

要点:

  1. 用于属性标签的实体框架属性。
  2. IDbRecord的实现。
  3. IValidation的实现。我们将在第三篇文章中介绍自定义验证。

public class WeatherForecast : IValidation, IDbRecord<WeatherForecast>
{
    [Key] public int ID { get; set; } = -1;
    public DateTime Date { get; set; } = DateTime.Now;
    public int TemperatureC { get; set; } = 0;
    [NotMapped] public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    public string Summary { get; set; } = string.Empty;
    [NotMapped] public Guid GUID { get; init; } = Guid.NewGuid();
    [NotMapped] public string DisplayName => $"Weather Forecast for {this.Date.ToShortDateString()} ";

    public bool Validate(ValidationMessageStore validationMessageStore, string fieldname, object model = null)
    {
        model = model ?? this;
        bool trip = false;

        this.Summary.Validation("Summary", model, validationMessageStore)
            .LongerThan(2, "Your description needs to be a little longer! 3 letters minimum")
            .Validate(ref trip, fieldname);

        this.Date.Validation("Date", model, validationMessageStore)
            .NotDefault("You must select a date")
            .LessThan(DateTime.Now.AddMonths(1), true, "Date can only be up to 1 month ahead")
            .Validate(ref trip, fieldname);

        this.TemperatureC.Validation("TemperatureC", model, validationMessageStore)
            .LessThan(70, "The temperature must be less than 70C")
            .GreaterThan(-60, "The temperature must be greater than -60C")
            .Validate(ref trip, fieldname);

        return !trip;
    }

实体框架层

该应用程序实现了两个实体框架DBContext类。

WeatherForecastDBContext

该DbContext有DbSet每个记录类型。每个DbSet都链接到OnModelCreating()中的一个视图。WeatherForecast应用程序具有一种记录类型。

LocalWeatherDbContext

该类非常基础,为每个数据类创建一个DbSet。DBSet必须与数据类同名。

public class LocalWeatherDbContext : DbContext
{
    private readonly Guid _id;

    public LocalWeatherDbContext(DbContextOptions<LocalWeatherDbContext> options)
        : base(options)
        => _id = Guid.NewGuid();

    public DbSet<WeatherForecast> WeatherForecast { get; set; }
}

InMemoryWeatherDbContext

内存版本稍微复杂一些,它需要即时构建和填充数据库。

public class InMemoryWeatherDbContext : DbContext
{
    private readonly Guid _id;

    public InMemoryWeatherDbContext(DbContextOptions<InMemoryWeatherDbContext> options)
        : base(options)
    {
        this._id = Guid.NewGuid();
        this.BuildInMemoryDatabase();
    }

    public DbSet<WeatherForecast> WeatherForecast { get; set; }

    private void BuildInMemoryDatabase()
    {
        var conn = this.Database.GetDbConnection();
        conn.Open();
        var cmd = conn.CreateCommand();
        cmd.CommandText = "CREATE TABLE [WeatherForecast]([ID] INTEGER PRIMARY KEY AUTOINCREMENT, [Date] [smalldatetime] NOT NULL, [TemperatureC] [int] NOT NULL, [Summary] [varchar](255) NULL)";
        cmd.ExecuteNonQuery();
        foreach (var forecast in this.NewForecasts)
        {
            cmd.CommandText = $"INSERT INTO WeatherForecast([Date], [TemperatureC], [Summary]) VALUES('{forecast.Date.ToLongDateString()}', {forecast.TemperatureC}, '{forecast.Summary}')";
            cmd.ExecuteNonQuery();
        }
    }

    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    private List<WeatherForecast> NewForecasts
    {
        get
        {
            {
                var rng = new Random();

                return Enumerable.Range(1, 10).Select(index => new WeatherForecast
                {
                    //ID = index,
                    Date = DateTime.Now.AddDays(index),
                    TemperatureC = rng.Next(-20, 55),
                    Summary = Summaries[rng.Next(Summaries.Length)]
                }).ToList();
            }
        }
    }

DbContextExtensions

我们使用泛型,因此我们需要一种方法来获取声明为TRecord的数据类的DbSet。这是作为扩展方法实现的DbContext。为此,每个DbSet名称都应与数据类具有相同的名称。如果名称不同,则dbSetName提供备份。  

该方法使用反射来为 TRecord查找DbSet。

public static DbSet<TRecord> GetDbSet<TRecord>(this DbContext context, string dbSetName = null) where TRecord : class, IDbRecord<TRecord>, new()
{
    var recname = new TRecord().GetType().Name;
    // Get the property info object for the DbSet 
    var pinfo = context.GetType().GetProperty(dbSetName ?? recname);
    DbSet<TRecord> dbSet = null; 
    // Get the property DbSet
    try
    {
        dbSet = (DbSet<TRecord>)pinfo.GetValue(context);
    }
    catch
    {
        throw new InvalidOperationException($"{recname} does not have a matching DBset ");
    }
    Debug.Assert(dbSet != null);
    return dbSet;
}

IFactoryDataService

IFactoryDataService定义了DataServices必须实现的基本CRUDL方法。数据服务使用接口在服务容器中定义并通过接口使用。注意每种方法及其约束的TRecord。有两种GetRecordListAsync方法。一个获取整个数据集,另一个使用PaginstorData对象对数据集进行分页和排序。更多关于Paginator的内容在第五篇文章中。

public interface IFactoryDataService 
{
    public Task<List<TRecord>> GetRecordListAsync<TRecord>() where TRecord : class, IDbRecord<TRecord>, new();
    public Task<List<TRecord>> GetRecordListAsync<TRecord>(PaginatorData paginatorData) where TRecord : class, IDbRecord<TRecord>, new();
    public Task<TRecord> GetRecordAsync<TRecord>(int id) where TRecord : class, IDbRecord<TRecord>, new();
    public Task<int> GetRecordListCountAsync<TRecord>() where TRecord : class, IDbRecord<TRecord>, new();
    public Task<DbTaskResult> UpdateRecordAsync<TRecord>(TRecord record) where TRecord : class, IDbRecord<TRecord>, new();
    public Task<DbTaskResult> CreateRecordAsync<TRecord>(TRecord record) where TRecord : class, IDbRecord<TRecord>, new();
    public Task<DbTaskResult> DeleteRecordAsync<TRecord>(TRecord record) where TRecord : class, IDbRecord<TRecord>, new();
}

FactoryDataService

FactoryDataService是IFactoryDataService的抽象实现。它提供默认记录、列表或未实现的 DBTaskResult消息。  

public abstract class FactoryDataService: IFactoryDataService
{
    public Guid ServiceID { get; } = Guid.NewGuid();
    public IConfiguration AppConfiguration { get; set; }

    public FactoryDataService(IConfiguration configuration) => this.AppConfiguration = configuration;

    public virtual Task<List<TRecord>> GetRecordListAsync<TRecord>() where TRecord : class, IDbRecord<TRecord>, new()
        => Task.FromResult(new List<TRecord>());
    public virtual Task<List<TRecord>> GetRecordListAsync<TRecord>(PaginatorData paginatorData) where TRecord : class, IDbRecord<TRecord>, new()
        => Task.FromResult(new List<TRecord>());
    public virtual Task<TRecord> GetRecordAsync<TRecord>(int id) where TRecord : class, IDbRecord<TRecord>, new()
        => Task.FromResult(new TRecord());
    public virtual Task<int> GetRecordListCountAsync<TRecord>() where TRecord : class, IDbRecord<TRecord>, new()
        => Task.FromResult(0);
    public virtual Task<DbTaskResult> UpdateRecordAsync<TRecord>(TRecord record) where TRecord : class, IDbRecord<TRecord>, new()
        => Task.FromResult(new DbTaskResult() { IsOK = false, Type = MessageType.NotImplemented, Message = "Method not implemented" });
    public virtual Task<DbTaskResult> CreateRecordAsync<TRecord>(TRecord record) where TRecord : class, IDbRecord<TRecord>, new()
        => Task.FromResult(new DbTaskResult() { IsOK = false, Type = MessageType.NotImplemented, Message = "Method not implemented" });
    public virtual Task<DbTaskResult> DeleteRecordAsync<TRecord>(TRecord record) where TRecord : class, IDbRecord<TRecord>, new()
        => Task.FromResult(new DbTaskResult() { IsOK = false, Type = MessageType.NotImplemented, Message = "Method not implemented" });
}

FactoryServerDataService

这是具体的服务器端实现。每个数据库操作都是使用单独的DbContext实例实现的。GetDBSet用于为TRecord获取正确DBSet的注意事项。

public class FactoryServerDataService<TDbContext> : FactoryDataService where TDbContext : DbContext
{
    protected virtual IDbContextFactory<TDbContext> DBContext { get; set; } = null;

    public FactoryServerDataService(IConfiguration configuration, IDbContextFactory<TDbContext> dbContext) : base(configuration)
        => this.DBContext = dbContext;

    public override async Task<List<TRecord>> GetRecordListAsync<TRecord>()
        => await this.DBContext
            .CreateDbContext()
            .GetDbSet<TRecord>()
            .ToListAsync() ?? new List<TRecord>();

    public override async Task<List<TRecord>> GetRecordListAsync<TRecord>(PaginatorData paginatorData)
    {
        var startpage = paginatorData.Page <= 1
            ? 0
            : (paginatorData.Page - 1) * paginatorData.PageSize;
        var context = this.DBContext.CreateDbContext();
        var dbset = this.DBContext
            .CreateDbContext()
            .GetDbSet<TRecord>();
        var x = typeof(TRecord).GetProperty(paginatorData.SortColumn);
        var isSortable = typeof(TRecord).GetProperty(paginatorData.SortColumn) != null;
        if (isSortable)
        {
            var list = await dbset
                .OrderBy(paginatorData.SortDescending ? $"{paginatorData.SortColumn} descending" : paginatorData.SortColumn)
                .Skip(startpage)
                .Take(paginatorData.PageSize).ToListAsync() ?? new List<TRecord>();
            return list;
        }
        else
        {
            var list = await dbset
                .Skip(startpage)
                .Take(paginatorData.PageSize).ToListAsync() ?? new List<TRecord>();
            return list;
        }
    }

    public override async Task<TRecord> GetRecordAsync<TRecord>(int id)
        => await this.DBContext.
            CreateDbContext().
            GetDbSet<TRecord>().
            FirstOrDefaultAsync(item => ((IDbRecord<TRecord>)item).ID == id) ?? default;

    public override async Task<int> GetRecordListCountAsync<TRecord>()
        => await this.DBContext.CreateDbContext().GetDbSet<TRecord>().CountAsync();

    public override async Task<DbTaskResult> UpdateRecordAsync<TRecord>(TRecord record)
    {
        var context = this.DBContext.CreateDbContext();
        context.Entry(record).State = EntityState.Modified;
        return await this.UpdateContext(context);
    }

    public override async Task<DbTaskResult> CreateRecordAsync<TRecord>(TRecord record)
    {
        var context = this.DBContext.CreateDbContext();
        context.GetDbSet<TRecord>().Add(record);
        return await this.UpdateContext(context);
    }

    public override async Task<DbTaskResult> DeleteRecordAsync<TRecord>(TRecord record)
    {
        var context = this.DBContext.CreateDbContext();
        context.Entry(record).State = EntityState.Deleted;
        return await this.UpdateContext(context);
    }

    protected async Task<DbTaskResult> UpdateContext(DbContext context)
        => await context.SaveChangesAsync() > 0 ? DbTaskResult.OK() : DbTaskResult.NotOK();
}

该FactoryWASMDataService看起来有点不同。它实现了接口,但用于HttpClient获取/发布到服务器上的API。

服务映射如下所示:

UI控制器服务 => WASMDataService => API控制器 => ServerDataService => DBContext

public class FactoryWASMDataService : FactoryDataService, IFactoryDataService
{
    protected HttpClient HttpClient { get; set; }

    public FactoryWASMDataService(IConfiguration configuration, HttpClient httpClient) : base(configuration)
        => this.HttpClient = httpClient;

    public override async Task<List<TRecord>> GetRecordListAsync<TRecord>()
        => await this.HttpClient.GetFromJsonAsync<List<TRecord>>($"{GetRecordName<TRecord>()}/list");

    public override async Task<List<TRecord>> GetRecordListAsync<TRecord>(PaginatorData paginatorData)
    {
        var response = await this.HttpClient.PostAsJsonAsync($"{GetRecordName<TRecord>()}/listpaged", paginatorData);
        return await response.Content.ReadFromJsonAsync<List<TRecord>>();
    }

    public override async Task<TRecord> GetRecordAsync<TRecord>(int id)
    {
        var response = await this.HttpClient.PostAsJsonAsync($"{GetRecordName<TRecord>()}/read", id);
        var result = await response.Content.ReadFromJsonAsync<TRecord>();
        return result;
    }

    public override async Task<int> GetRecordListCountAsync<TRecord>()
        => await this.HttpClient.GetFromJsonAsync<int>($"{GetRecordName<TRecord>()}/count");

    public override async Task<DbTaskResult> UpdateRecordAsync<TRecord>(TRecord record)
    {
        var response = await this.HttpClient.PostAsJsonAsync<TRecord>($"{GetRecordName<TRecord>()}/update", record);
        var result = await response.Content.ReadFromJsonAsync<DbTaskResult>();
        return result;
    }

    public override async Task<DbTaskResult> CreateRecordAsync<TRecord>(TRecord record)
    {
        var response = await this.HttpClient.PostAsJsonAsync<TRecord>($"{GetRecordName<TRecord>()}/create", record);
        var result = await response.Content.ReadFromJsonAsync<DbTaskResult>();
        return result;
    }

    public override async Task<DbTaskResult> DeleteRecordAsync<TRecord>(TRecord record)
    {
        var response = await this.HttpClient.PostAsJsonAsync<TRecord>($"{GetRecordName<TRecord>()}/update", record);
        var result = await response.Content.ReadFromJsonAsync<DbTaskResult>();
        return result;
    }

    protected string GetRecordName<TRecord>() where TRecord : class, IDbRecord<TRecord>, new()
        => new TRecord().GetType().Name;
}

API控制器

控制器在Web项目中实现,每个DataClass一个。

WeatherForecast控制器如下所示。它基本上通过IFactoryService接口将请求传递到FactoryServerDataService。

[ApiController]
public class WeatherForecastController : ControllerBase
{
    protected IFactoryDataService DataService { get; set; }
    private readonly ILogger<WeatherForecastController> logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger, IFactoryDataService dataService)
    {
        this.DataService = dataService;
        this.logger = logger;
    }

    [MVC.Route("weatherforecast/list")]
    [HttpGet]
    public async Task<List<WeatherForecast>> GetList() => await DataService.GetRecordListAsync<WeatherForecast>();

    [MVC.Route("weatherforecast/listpaged")]
    [HttpGet]
    public async Task<List<WeatherForecast>> Read([FromBody] PaginatorData data) => await DataService.GetRecordListAsync<WeatherForecast>( paginator: data);

    [MVC.Route("weatherforecast/count")]
    [HttpGet]
    public async Task<int> Count() => await DataService.GetRecordListCountAsync<WeatherForecast>();

    [MVC.Route("weatherforecast/get")]
    [HttpGet]
    public async Task<WeatherForecast> GetRec(int id) => await DataService.GetRecordAsync<WeatherForecast>(id);

    [MVC.Route("weatherforecast/read")]
    [HttpPost]
    public async Task<WeatherForecast> Read([FromBody]int id) => await DataService.GetRecordAsync<WeatherForecast>(id);

    [MVC.Route("weatherforecast/update")]
    [HttpPost]
    public async Task<DbTaskResult> Update([FromBody]WeatherForecast record) => await DataService.UpdateRecordAsync<WeatherForecast>(record);

    [MVC.Route("weatherforecast/create")]
    [HttpPost]
    public async Task<DbTaskResult> Create([FromBody]WeatherForecast record) => await DataService.CreateRecordAsync<WeatherForecast>(record);

    [MVC.Route("weatherforecast/delete")]
    [HttpPost]
    public async Task<DbTaskResult> Delete([FromBody] WeatherForecast record) => await DataService.DeleteRecordAsync<WeatherForecast>(record);
    }

FactoryServerInMemoryDataService

为了测试和演示,还有另一个使用SQLite in-memory DbContext的服务器数据服务。

该代码类似于FactoryServerDataService,但对所有事务使用单个DbContext。

public class FactoryServerInMemoryDataService<TDbContext> : FactoryDataService, IFactoryDataService where TDbContext : DbContext
{
    protected virtual IDbContextFactory<TDbContext> DBContext { get; set; } = null;

    private DbContext _dbContext;

    public FactoryServerInMemoryDataService(IConfiguration configuration, IDbContextFactory<TDbContext> dbContext) : base(configuration)
    {
        this.DBContext = dbContext;
        _dbContext = this.DBContext.CreateDbContext();
    }

    public override async Task<List<TRecord>> GetRecordListAsync<TRecord>()
    {
        var dbset = _dbContext.GetDbSet<TRecord>();
        return await dbset.ToListAsync() ?? new List<TRecord>();
    }

    public override async Task<List<TRecord>> GetRecordListAsync<TRecord>(PaginatorData paginatorData)
    {
        var startpage = paginatorData.Page <= 1
            ? 0
            : (paginatorData.Page - 1) * paginatorData.PageSize;
        var dbset = _dbContext.GetDbSet<TRecord>();
        var isSortable = typeof(TRecord).GetProperty(paginatorData.SortColumn) != null;
        if (isSortable)
        {
            var list = await dbset
                .OrderBy(paginatorData.SortDescending ? $"{paginatorData.SortColumn} descending" : paginatorData.SortColumn)
                .Skip(startpage)
                .Take(paginatorData.PageSize).ToListAsync() ?? new List<TRecord>();
            return list;
        }
        else
        {
            var list = await dbset
                .Skip(startpage)
                .Take(paginatorData.PageSize).ToListAsync() ?? new List<TRecord>();
            return list;
        }
    }

    public override async Task<TRecord> GetRecordAsync<TRecord>(int id)
    {
        var dbset = _dbContext.GetDbSet<TRecord>();
        return await dbset.FirstOrDefaultAsync(item => ((IDbRecord<TRecord>)item).ID == id) ?? default;
    }

    public override async Task<int> GetRecordListCountAsync<TRecord>()
    {
        var dbset = _dbContext.GetDbSet<TRecord>();
        return await dbset.CountAsync();
    }

    public override async Task<DbTaskResult> UpdateRecordAsync<TRecord>(TRecord record)
    {
        _dbContext.Entry(record).State = EntityState.Modified;
        var x = await _dbContext.SaveChangesAsync();
        return new DbTaskResult() { IsOK = true, Type = MessageType.Success };
    }

    public override async Task<DbTaskResult> CreateRecordAsync<TRecord>(TRecord record)
    {
        var dbset = _dbContext.GetDbSet<TRecord>();
        dbset.Add(record);
        var x = await _dbContext.SaveChangesAsync();
        return new DbTaskResult() { IsOK = true, Type = MessageType.Success, NewID = record.ID };
    }

    public override async Task<DbTaskResult> DeleteRecordAsync<TRecord>(TRecord record)
    {
        _dbContext.Entry(record).State = EntityState.Deleted;
        var x = await _dbContext.SaveChangesAsync();
        return new DbTaskResult() { IsOK = true, Type = MessageType.Success };
    }
}

控制器服务

控制器服务是数据服务和UI之间的接口。他们实现了管理他们负责的数据类所需的逻辑。虽然大部分代码都驻留在FactoryControllerService中,但不可避免地会有一些特定于数据类的代码。

IFactoryControllerService

IFactoryControllerService 定义基本表单代码使用的公共接口。

注意:

  1. 泛型的TRecord.
  2. 保存当前记录和记录列表的属性。
  3. 用于简化状态管理的布尔逻辑属性。
  4. 记录和列表更改的事件。
  5. 重置方法以重置服务/记录/列表。
  6. 更新/使用当前记录/列表的CRUDL方法。

public interface IFactoryControllerService<TRecord> where TRecord : class, IDbRecord<TRecord>, new()
{
    public Guid Id { get; }
    public TRecord Record { get; }
    public List<TRecord> Records { get; }
    public int RecordCount => this.Records?.Count ?? 0;
    public int RecordId { get; }
    public Guid RecordGUID { get; }
    public DbTaskResult DbResult { get; }
    public Paginator Paginator { get; }
    public bool IsRecord => this.Record != null && this.RecordId > -1;
    public bool HasRecords => this.Records != null && this.Records.Count > 0;
    public bool IsNewRecord => this.IsRecord && this.RecordId == -1;

    public event EventHandler RecordHasChanged;
    public event EventHandler ListHasChanged;

    public Task Reset();
    public Task ResetRecordAsync();
    public Task ResetListAsync();

    public Task GetRecordsAsync() => Task.CompletedTask;
    public Task<bool> SaveRecordAsync();
    public Task<bool> GetRecordAsync(int id);
    public Task<bool> NewRecordAsync();
    public Task<bool> DeleteRecordAsync();
}

FactoryControllerService

FactoryControllerService是IFactoryControllerService的抽象实现。它包含所有样板代码。大部分代码是不言自明的。  

public abstract class FactoryControllerService<TRecord> : IDisposable, IFactoryControllerService<TRecord> where TRecord : class, IDbRecord<TRecord>, new()
{
    // unique ID for this instance
    public Guid Id { get; } = Guid.NewGuid();

    // Record Property.   Triggers Event when changed.
    public TRecord Record
    {
        get => _record;
        private set
        {
            this._record = value;
            this.RecordHasChanged?.Invoke(value, EventArgs.Empty);
        }
    }
    private TRecord _record = null;

    // Recordset Property.  Triggers Event when changed.
    public List<TRecord> Records
    {
        get => _records;
        private set
        {
            this._records = value;
            this.ListHasChanged?.Invoke(value, EventArgs.Empty);
        }
    }
    private List<TRecord> _records = null;

    public int RecordId => this.Record?.ID ?? 0;
    public Guid RecordGUID => this.Record?.GUID ?? Guid.Empty;
    public DbTaskResult DbResult { get; set; } = new DbTaskResult();

    /// Property for the Paging object that controls paging and interfaces with the UI Paging Control 
    public Paginator Paginator { get; private set; }

    public bool IsRecord => this.Record != null && this.RecordId > -1;
    public bool HasRecords => this.Records != null && this.Records.Count > 0;
    public bool IsNewRecord => this.IsRecord && this.RecordId == -1;

    /// Data Service for data access
    protected IFactoryDataService DataService { get; set; }

    public event EventHandler RecordHasChanged;
    public event EventHandler ListHasChanged;

    public FactoryControllerService(IFactoryDataService factoryDataService)
    {
        this.DataService = factoryDataService;
        this.Paginator = new Paginator(10, 5);
        this.Paginator.PageChanged += this.OnPageChanged;
    }

    /// Method to reset the service
    public Task Reset()
    {
        this.Record = null;
        this.Records = null;
        return Task.CompletedTask;
    }

    /// Method to reset the record list
    public Task ResetListAsync()
    {
        this.Records = null;
        return Task.CompletedTask;
    }

    /// Method to reset the Record
    public Task ResetRecordAsync()
    {
        this.Record = null;
        return Task.CompletedTask;
    }

    /// Method to get a recordset
    public async Task GetRecordsAsync()
    {
        this.Records = await DataService.GetRecordListAsync<TRecord>(this.Paginator.GetData);
        this.Paginator.RecordCount = await GetRecordListCountAsync();
        this.ListHasChanged?.Invoke(null, EventArgs.Empty);
    }

    /// Method to get a record
    /// if id < 1 will create a new record
    public async Task<bool> GetRecordAsync(int id)
    {
        if (id > 0)
            this.Record = await DataService.GetRecordAsync<TRecord>(id);
        else
            this.Record = new TRecord();
        return this.IsRecord;
    }

    /// Method to get the current record count
    public async Task<int> GetRecordListCountAsync()
        => await DataService.GetRecordListCountAsync<TRecord>();


    public async Task<bool> SaveRecordAsync()
    {
        if (this.RecordId == -1)
            this.DbResult = await DataService.CreateRecordAsync<TRecord>(this.Record);
        else
            this.DbResult = await DataService.UpdateRecordAsync(this.Record);
        await this.GetRecordsAsync();
        return this.DbResult.IsOK;
    }

    public async Task<bool> DeleteRecordAsync()
    {
        this.DbResult = await DataService.DeleteRecordAsync<TRecord>(this.Record);
        return this.DbResult.IsOK;
    }

    public Task<bool> NewRecordAsync()
    {
        this.Record = default(TRecord);
        return Task.FromResult(false);
    }

    protected async void OnPageChanged(object sender, EventArgs e)
        => await this.GetRecordsAsync();

    protected void NotifyRecordChanged(object sender, EventArgs e)
        => this.RecordHasChanged?.Invoke(sender, e);

    protected void NotifyListChanged(object sender, EventArgs e)
        => this.ListHasChanged?.Invoke(sender, e);

    public virtual void Dispose() {}
}

WeatherForecastControllerService

样板化的回报来自于以下WeatheForcastControllerService声明:

public class WeatherForecastControllerService : FactoryControllerService<WeatherForecast>
{
    public WeatherForecastControllerService(IFactoryDataService factoryDataService) : base(factoryDataService) { }
}

总结

本文展示了如何使用一组实现CRUDL操作样板代码的抽象类来构建数据服务。我特意将代码中的错误检查保持在最低限度,以使其更具可读性。您可以根据需要尽可能少地或尽可能多地实现。

需要注意的一些关键点:

  1. 尽可能使用Aysnc代码。数据访问功能都是异步的。
  2. 泛型使大部分样板成为可能。它们会造成复杂性,但值得付出努力。
  3. 接口对于依赖注入和UI样板化至关重要。

如果您在未来阅读本文,请查看存储库中的自述文件以获取文章集的最新版本。

https://www.codeproject.com/Articles/5279596/Building-a-Database-Application-in-Blazor-Part-2-S

标签:Task,get,await,CRUD,构建,new,TRecord,Blazor,public
来源: https://blog.csdn.net/mzl87/article/details/120257626

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有