目录
存储库和数据库
目标
服务
泛型
数据访问
DbTaskResult
数据类
WeatherForecast
实体框架层
WeatherForecastDBContext
LocalWeatherDbContext
InMemoryWeatherDbContext
DbContextExtensions
IFactoryDataService
FactoryDataService
FactoryServerDataService
API控制器
FactoryServerInMemoryDataService
控制器服务
IFactoryControllerService
FactoryControllerService
WeatherForecastControllerService
总结
这是构建Blazor数据库应用程序系列中的第二篇文章。它描述了如何将数据和业务逻辑层构建为通用库代码,从而使部署特定于应用程序的数据服务变得简单。它是对早期版本的完全重写。
该系列文章如下:
存储库已移至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; } }
我们添加新数据库实体的过程是:
某些实体会出现复杂情况,但这不会使该方法无效——库中80%以上的代码。
Blazor建立在DI [依赖注入]和IOC [控制反转]原则之上。如果您不熟悉这些概念,请在深入研究Blazor之前进行一些了解。从长远来看,它会为您节省时间!
Blazor Singleton和Transient服务相对简单。您可以在Microsoft 文档中阅读有关它们的更多信息。Scoped稍微复杂一些。
Services是Blazor IOC [控制反转]容器。服务实例声明如下:
该解决方案使用服务集合扩展方法,例如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; }
要点:
工厂库代码严重依赖泛型。定义了两个通用实体:
类声明如下所示:
//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方法:
在我们阅读本文时,请记住这些。
数据层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。
public interface IDbRecord<TRecord> where TRecord : class, IDbRecord<TRecord>, new() { public int ID { get; } public Guid GUID { get; } public string DisplayName { get; } }
这是WeatherForecast数据实体的数据类。
要点:
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类。
该DbContext有DbSet每个记录类型。每个DbSet都链接到OnModelCreating()中的一个视图。WeatherForecast应用程序具有一种记录类型。
该类非常基础,为每个数据类创建一个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; } }
内存版本稍微复杂一些,它需要即时构建和填充数据库。
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(); } } }
我们使用泛型,因此我们需要一种方法来获取声明为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定义了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是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" }); }
这是具体的服务器端实现。每个数据库操作都是使用单独的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; }
控制器在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); }
为了测试和演示,还有另一个使用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 定义基本表单代码使用的公共接口。
注意:
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是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() {} }
样板化的回报来自于以下WeatheForcastControllerService声明:
public class WeatherForecastControllerService : FactoryControllerService<WeatherForecast> { public WeatherForecastControllerService(IFactoryDataService factoryDataService) : base(factoryDataService) { } }
本文展示了如何使用一组实现CRUDL操作样板代码的抽象类来构建数据服务。我特意将代码中的错误检查保持在最低限度,以使其更具可读性。您可以根据需要尽可能少地或尽可能多地实现。
需要注意的一些关键点:
如果您在未来阅读本文,请查看存储库中的自述文件以获取文章集的最新版本。
https://www.codeproject.com/Articles/5279596/Building-a-Database-Application-in-Blazor-Part-2-S