【问题标题】:Automate CRUD creation in a layered architecture under .NET Core在 .NET Core 下的分层架构中自动创建 CRUD
【发布时间】:2018-05-15 07:57:15
【问题描述】:

我正在一个典型的三层架构下的新项目中工作:businessdataclient,使用 Angular 作为前端。

在这个项目中,我们将有一个我们想要自动化的重复性任务:创建 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>();
}

还有工作单元:

private GenericRepository<User> userRepository;

public GenericRepository<User> UserRepository
{
    get
    {

        if (this.userRepository== null)
        {
            this.userRepository= new GenericRepository<User>(context);
        }
        return userRepository;
    }
}

大部分结构都将是相同的,除了一些必须手动完成的特定情况的控制器。

【问题讨论】:

  • 您可以通过以下方式轻松做到这一点->c-sharpcorner.com/article/scaffolding-asp-net-core-mvc
  • 如果我没记错的话,这不允许我添加映射、上下文和招摇注解。此外,架构不会让我们以这种方式工作,因为我们没有为每个实体创建一个 dbset,我们使用的是泛型。
  • 好吧,如果您找不到现成的解决方案 - 我认为 T4 是可行的方法。
  • google for 脚手架 - 他们如何称呼这种方法。作为 T4 的替代方案,您可以尝试创建控制台应用程序项目并使用 Roslyn API 生成代码。它非常好,你可以用它做一些非常高级的事情,但它比 T4 更难阅读。我已经通过这种方式从 XML 模式生成了一些自定义 C# 代码,并且效果很好。
  • 这个项目可能是一个有用的起点,github.com/amelmusic/REST-Framework

标签: c# .net t4


【解决方案1】:

这是项目的简化版本,您需要编写它才能生成之前的代码。首先创建一个目录,任何未来的实体都会去往其中。为简单起见,我调用了 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") #>

我注意到源文件没有命名空间或 usings,因此如果不将 usings 添加到 User.cs 文件中,UserModel 文件将无法编译,但该文件确实按照规范生成

控制器模板

<#@ 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>();
<#
}
#>}

这基本上会遍历实体文件夹中的每个文件,并在实体和实体模型之间创建映射器

通用存储库的模板

<#@ 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);
    }
#>

【讨论】:

  • 这对我来说是一个很好的指导点,我会尽快进行测试。
【解决方案2】:

这可能有点离题,并没有真正直接回答相关问题。

但是为什么要这样解决你的问题呢?

为什么不简单地创建一个基本的 CRUD 控制器。为其提供与其数据模型对应部分相关的通用模型。

因此,BI 模型具有与 DAL 模型等相同的属性。 然后,您可以制作一个按属性名称映射的通用转换器。或者在属性上设置自定义属性以映射到预期的名称。

那么您只需要说,将 a 表导入您的实体模型。 并且 presto,所有层都可以访问,因为所有转换和 CRUDS 都是通用的。

更好的是,如果您需要针对特定​​表的 CRUD 操作发生特定的事情,您可以简单地将控制器重载到特定的模型类型,并且您有一个明确定义的区域来编写例外代码到通用方式?

我并没有真正解决这个建议的根本问题?

说你的 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;
        }

【讨论】:

    【解决方案3】:

    如果您使用三层架构,则创建核心并添加 Interface Repository 这一行 ` 公共部分接口 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;} 这是基于DI

    谢谢

    【讨论】:

      猜你喜欢
      • 2020-04-02
      • 2019-08-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-22
      • 1970-01-01
      • 2023-01-24
      • 1970-01-01
      相关资源
      最近更新 更多