Автоматизация создания CRUD в многоуровневой архитектуре под .NET Core - PullRequest
0 голосов
/ 15 мая 2018

Я работаю в новом проекте с типичной трехслойной архитектурой: business, data и client с использованием Angular в качестве фронта.

В этом проекте у нас будет повторяющаяся задачачто мы хотим автоматизировать: создание CRUD.Мы хотим генерировать модели и контроллеры (помещать, получать, публиковать, удалять), а также другую базовую информацию о проекте из сущности и ее свойств.

Какой мой лучший вариант здесь?Я думал о шаблонах 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;
    }
}

Большинство структур будут одинаковыми, за исключением некоторых особых случаев контроллеров, которые придется выполнять вручную.

Ответы [ 3 ]

0 голосов
/ 23 мая 2018

Это может быть немного не по теме и не иметь прямого ответа.

Но зачем решать вашу проблему таким образом?

Почему бы просто не создать базовый CRUD-контроллер. предоставить ему общие модели, которые относятся к их частям модели данных.

Таким образом, модели BI имеют те же свойства, что и модели DAL и т. Д. Затем вы можете сделать общий конвертер, который отображается по имени свойства. Или установите пользовательский атрибут в свойствах для сопоставления с предполагаемыми именами

Тогда вам нужно будет только сказать, импортируйте таблицу в вашу модель сущности. И, конечно же, все уровни имеют доступ на всех уровнях, потому что все преобразования и 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;
        }
0 голосов
/ 24 мая 2018

Если вы используете трехслойную архитектуру, создайте ядро ​​и добавьте строку интерфейса 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

Спасибо

0 голосов
/ 18 мая 2018

Это упрощенная версия проекта, которую вам нужно написать для генерации предыдущего кода.Прежде всего создайте каталог, куда и пойдут любые будущие сущности.Для простоты я вызвал каталог Entities и создал файл с именем User.cs, который содержит исходный код для класса User.

Для каждого из этих шаблонов создайте файл .tt, начинающийся с имени объекта, за которым следует имя функции.Таким образом, файл tt для пользовательской модели будет называться UserModel.tt, в который вы помещаете шаблон модели.Для пользовательского контроллера - USerController.tt, в который вы поместите шаблон контроллера.Там будет только файл Autopper, и универсальный репозиторий пользователя будет называться 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") #>

Я заметил, что исходный файл не имеет пространств имен или использования, поэтому файл UserModel не будет компилироваться без добавления использования в файл User.cs, однако файл генерируется согласно спецификации

Шаблон для контроллера

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