[英]Automate CRUD creation in a layered architecture under .NET Core
我正在一個典型的三層架構下的新項目中工作:使用Angular作為前端的business
, data
和client
。
在這個項目中,我們將有一個我們想要自動執行的重復任務:創建CRUD。 我們想要做的是生成模型和控制器(put,get,post,delete)以及來自實體及其屬性的其他基本項目信息。
我最好的選擇是什么? 我曾經考慮過模板T4,但是我對它們的無知使我懷疑它是否是最好的選擇。
例如,從這個實體:
public class User
{
public int Id { get; set; }
public string Name {get;set;}
public string Email{ get; set; }
public IEnumerable<Task> Task { get; set; }
}
我想生成以下模型:
public class UserModel
{
public int Id { get; set; }
public string Name {get;set;}
public string Email{ get; set; }
public IEnumerable<Task> Task { get; set; }
}
還有控制器:
{
/// <summary>
/// User controller
/// </summary>
[Route("api/[controller]")]
public class UserController: Controller
{
private readonly LocalDBContext localDBContext;
private UnitOfWork unitOfWork;
/// <summary>
/// Constructor
/// </summary>
public UserController(LocalDBContext localDBContext)
{
this.localDBContext = localDBContext;
this.unitOfWork = new UnitOfWork(localDBContext);
}
/// <summary>
/// Get user by Id
/// </summary>
[HttpGet("{id}")]
[Produces("application/json", Type = typeof(UserModel))]
public IActionResult GetById(int id)
{
var user = unitOfWork.UserRepository.GetById(id);
if (user == null)
{
return NotFound();
}
var res = AutoMapper.Mapper.Map<UserModel>(user);
return Ok(res);
}
/// <summary>
/// Post an user
/// </summary>
[HttpPost]
public IActionResult Post([FromBody]UserModel user)
{
Usuario u = AutoMapper.Mapper.Map<User>(user);
var res = unitOfWork.UserRepository.Add(u);
if (res?.Id > 0)
{
return Ok(res);
}
return BadRequest();
}
/// <summary>
/// Edit an user
/// </summary>
[HttpPut]
public IActionResult Put([FromBody]UserModel user)
{
if (unitOfWork.UserRepository.GetById(user.Id) == null)
{
return NotFound();
}
var u = AutoMapper.Mapper.Map<User>(user);
var res = unitOfWork.UserRepository.Update(u);
return Ok(res);
}
/// <summary>
/// Delete an user
/// </summary>
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
if (unitOfWork.UserRepository.GetById(id) == null)
{
return NotFound();
}
unitOfWork.UserRepository.Delete(id);
return Ok();
}
另外,我們需要添加AutoMapper
映射:
public AutoMapper()
{
CreateMap<UserModel, User>();
CreateMap<User, UserModel>();
}
而UnitOfWork:
private GenericRepository<User> userRepository;
public GenericRepository<User> UserRepository
{
get
{
if (this.userRepository== null)
{
this.userRepository= new GenericRepository<User>(context);
}
return userRepository;
}
}
大多數結構都是相同的,除了一些必須手動完成的控制器的特定情況。
這是項目的簡化版本,您需要編寫該版本才能生成以前的代碼。 首先創建一個目錄,其中和任何未來的實體將去。 為簡單起見,我調用目錄Entities並創建了一個名為User.cs的文件,其中包含User類的源代碼。
對於每個模板,創建一個.tt文件,以實體名稱開頭,后跟函數名稱。 因此,用戶模型的tt文件將被稱為UserModel.tt,您可以將模型模板放入其中。 對於用戶控制器,USerController.tt,您將控制器模板放入其中。 將只有自動化文件,用戶通用存儲庫將被稱為UserGenericRepository.tt(您已經猜到了)您放置了通用存儲庫模板
模型的模板
<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<#
var hostFile = this.Host.TemplateFile;
var entityName = System.IO.Path.GetFileNameWithoutExtension(hostFile).Replace("Model","");
var directoryName = System.IO.Path.GetDirectoryName(hostFile);
var fileName = directoryName + "\\Entities\\" + entityName + ".cs";
#>
<#= System.IO.File.ReadAllText(fileName).Replace("public class " + entityName,"public class " + entityName + "Model") #>
我注意到源文件沒有名稱空間或使用,因此如果沒有將用法添加到User.cs文件,UserModel文件將無法編譯,但是文件確實按照規范生成
Controller的模板
<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<#
var hostFile = this.Host.TemplateFile;
var entityName = System.IO.Path.GetFileNameWithoutExtension(hostFile).Replace("Controller","");
var directoryName = System.IO.Path.GetDirectoryName(hostFile);
var fileName = directoryName + "\\" + entityName + ".cs";
#>
/// <summary>
/// <#= entityName #> controller
/// </summary>
[Route("api/[controller]")]
public class <#= entityName #>Controller : Controller
{
private readonly LocalDBContext localDBContext;
private UnitOfWork unitOfWork;
/// <summary>
/// Constructor
/// </summary>
public <#= entityName #>Controller(LocalDBContext localDBContext)
{
this.localDBContext = localDBContext;
this.unitOfWork = new UnitOfWork(localDBContext);
}
/// <summary>
/// Get <#= Pascal(entityName) #> by Id
/// </summary>
[HttpGet("{id}")]
[Produces("application/json", Type = typeof(<#= entityName #>Model))]
public IActionResult GetById(int id)
{
var <#= Pascal(entityName) #> = unitOfWork.<#= entityName #>Repository.GetById(id);
if (<#= Pascal(entityName) #> == null)
{
return NotFound();
}
var res = AutoMapper.Mapper.Map<<#= entityName #>Model>(<#= Pascal(entityName) #>);
return Ok(res);
}
/// <summary>
/// Post an <#= Pascal(entityName) #>
/// </summary>
[HttpPost]
public IActionResult Post([FromBody]<#= entityName #>Model <#= Pascal(entityName) #>)
{
Usuario u = AutoMapper.Mapper.Map<<#= entityName #>>(<#= Pascal(entityName) #>);
var res = unitOfWork.<#= entityName #>Repository.Add(u);
if (res?.Id > 0)
{
return Ok(res);
}
return BadRequest();
}
/// <summary>
/// Edit an <#= Pascal(entityName) #>
/// </summary>
[HttpPut]
public IActionResult Put([FromBody]<#= entityName #>Model <#= Pascal(entityName) #>)
{
if (unitOfWork.<#= entityName #>Repository.GetById(<#= Pascal(entityName) #>.Id) == null)
{
return NotFound();
}
var u = AutoMapper.Mapper.Map<<#= entityName #>>(<#= Pascal(entityName) #>);
var res = unitOfWork.<#= entityName #>Repository.Update(u);
return Ok(res);
}
/// <summary>
/// Delete an <#= Pascal(entityName) #>
/// </summary>
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
if (unitOfWork.<#= entityName #>Repository.GetById(id) == null)
{
return NotFound();
}
unitOfWork.<#= entityName #>Repository.Delete(id);
return Ok();
}
}
<#+
public string Pascal(string input)
{
return input.ToCharArray()[0].ToString() + input.Substring(1);
}
#>
AutoMapper的模板
<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<#
var directoryName = System.IO.Path.GetDirectoryName(this.Host.TemplateFile) + "\\Entities";
var files = System.IO.Directory.GetFiles(directoryName, "*.cs");
#>
public class AutoMapper
{
<#
foreach(var f in files)
{
var entityName = System.IO.Path.GetFileNameWithoutExtension(f);
#>
CreateMap<<#= entityName #>Model, <#= entityName #>>();
CreateMap<<#= entityName #>, <#= entityName #>Model>();
<#
}
#>}
這基本上遍歷Entities文件夾中的每個文件,並在Entity和Entity Model之間創建映射器
通用存儲庫的模板
<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<#
var hostFile = this.Host.TemplateFile;
var entityName = System.IO.Path.GetFileNameWithoutExtension(hostFile).Replace("GenericRepository","");
var directoryName = System.IO.Path.GetDirectoryName(hostFile);
var fileName = directoryName + "\\" + entityName + ".cs";
#>
public class GenericRepository
{
private GenericRepository<<#= entityName #>> <#= Pascal(entityName) #>Repository;
public GenericRepository<<#= entityName #>> UserRepository
{
get
{
if (this.<#= Pascal(entityName) #>Repository == null)
{
this.<#= Pascal(entityName) #>Repository = new GenericRepository<<#= entityName #>>(context);
}
return <#= Pascal(entityName) #>Repository;
}
}
}<#+
public string Pascal(string input)
{
return input.ToCharArray()[0].ToString() + input.Substring(1);
}
#>
這可能有點偏離主題,而不是直接回答相關問題。
但為什么這樣解決你的問題呢?
為什么不簡單地創建一個基礎CRUD控制器。 為它提供與其數據模型計數器部件相關的通用模型。
因此,BI模型具有與DAL模型等相同的屬性。然后,您可以創建一個通過屬性名稱映射的通用轉換器。 或者在屬性上設置自定義屬性以映射到目標名稱。
然后你只需說,將一個表導入你的實體模型。 而且,所有層都可以一直訪問,因為所有轉換和CRUD都是通用的。
更好的是,如果您需要在CRUD操作上發生某些特定事件(例如特定表),您可以簡單地將控制器重載到特定的模型類型,並且presto您有一個明確定義的區域來編寫通用的例外代碼辦法?
我真的沒有用這個建議解決潛在的問題嗎?
假設您的db CRUD的基本控制器看起來像(偽代碼):
public TEntity Get<TContext>(Expression<Func<TEntity, bool>> predicate, TContext context) where TContext : DbContext
{
TEntity item = context.Set<TEntity>().FirstOrDefault(predicate);
return item;
}
public List<TEntity> GetList<TContext>(Expression<Func<TEntity, bool>> predicate, TContext context) where TContext : DbContext
{
List<TEntity> item = context.Set<TEntity>().Where(predicate).ToList();
return item;
}
public List<TEntity> GetAll<TContext>(TContext context) where TContext : DbContext
{
List<TEntity> item = context.Set<TEntity>().ToList();
return item;
}
public TEntity Insert<TContext>(TEntity input, TContext context) where TContext : DbContext
{
context.Set<TEntity>().Add(input);
context.SaveChanges();
return input;
}
public TEntity UpSert<TContext>(TEntity input, Expression<Func<TEntity, bool>> predicate, TContext context) where TContext : DbContext
{
if (input == null)
return null;
TEntity existing = context.Set<TEntity>().FirstOrDefault(predicate);
if (existing != null)
{
input.GetType().GetProperty("Id").SetValue(input, existing.GetType().GetProperty("Id").GetValue(existing));
context.Entry(existing).CurrentValues.SetValues(input);
context.SaveChanges();
}
else
{
RemoveNavigationProperties(input);
context.Set<TEntity>().Add(input);
context.SaveChanges();
return input;
}
return existing;
}
如果你使用三層架構,那么創建核心並添加Interface Repository這個`public partial interface IRepository,其中T:BaseEntity {
T GetById(object id);
void Insert(T entity);
void Insert(IEnumerable<T> entities);
void Update(T entity);
void Update(IEnumerable<T> entities);
void Delete(T entity);
void Delete(IEnumerable<T> entities);
IQueryable<T> Table { get; }
IQueryable<T> TableNoTracking { get; }
}
public interface IDbContext
{
IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity;
int SaveChanges();
IList<TEntity> ExecuteStoredProcedureList<TEntity>(string commandText, params object[] parameters)
where TEntity : BaseEntity, new();
IEnumerable<TElement> SqlQuery<TElement>(string sql, params object[] parameters);
int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, int? timeout = null, params object[] parameters);
void Detach(object entity);
bool ProxyCreationEnabled { get; set; }
bool AutoDetectChangesEnabled { get; set; }
}`
這些接口可以在服務模塊中使用,例如public partial class BlogService : IBlogService{ private readonly IRepository<BlogPost> _blogPostRepository; private readonly IRepository<BlogComment> _blogCommentRepository;}
public partial class BlogService : IBlogService{ private readonly IRepository<BlogPost> _blogPostRepository; private readonly IRepository<BlogComment> _blogCommentRepository;}
這是基於DI的
謝謝
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.