C# ASP.NET核心Web API端点与数据库的连接,最佳实践?

C# ASP.NET核心Web API端点与数据库的连接,最佳实践?,c#,asp.net-core,asp.net-core-webapi,endpoint,C#,Asp.net Core,Asp.net Core Webapi,Endpoint,这个问题可能是基于观点的,但我正在寻找意见,看看我是否能以更好的方式来解决这个问题。 我试图弄清楚如何让我的WebAPI控制器从数据库中检索对象,同时使它们保持良好的分离 我的sqlite数据库中有一组人员(通过EFC): 和一个用于检索人员的WebAPI端点,基于一些查询过滤器,我将在下面进一步展示 在许多教程中,DbContext只是直接注入控制器中,然后在endpoint方法中,他们将获得DbSet并执行一些LINQ操作来检索对象。 但对我来说,DbContext似乎与控制器纠缠在一起,这

这个问题可能是基于观点的,但我正在寻找意见,看看我是否能以更好的方式来解决这个问题。 我试图弄清楚如何让我的WebAPI控制器从数据库中检索对象,同时使它们保持良好的分离

我的sqlite数据库中有一组人员(通过EFC):

和一个用于检索人员的WebAPI端点,基于一些查询过滤器,我将在下面进一步展示

在许多教程中,DbContext只是直接注入控制器中,然后在endpoint方法中,他们将获得DbSet并执行一些LINQ操作来检索对象。 但对我来说,DbContext似乎与控制器纠缠在一起,这样暴露DbSet感觉是不对的。最好将其隐藏在界面后面,而不要暴露DbSet

所以我创建了一个类AdultRepo:IAdultRepo,它了解DbContext。在这个界面中,我有一个方法,在搜索成人时使用Func作为过滤器:

public interface IAdultRepo {
 
    Task<IEnumerable<Adult>> GetAllByCriteriaAsync(Func<Adult, bool> criteria);
    
}
公共接口IAdultRepo{
任务GetAllByCriteriaAsync(Func标准);
}
以下是成人报告中的方法:

public async Task<IEnumerable<Adult>> GetAllByCriteriaAsync(Func<Adult, bool> criteria) {
        IEnumerable<Adult> adults = famCtx.Adults.Where(criteria);
        
        return adults.ToList();
}
公共异步任务GetAllByCriteriaAsync(Func标准){
IEnumerable成人=famCtx.成人。其中(标准);
返回成人。ToList();
}
(我没有找到使此部件异步的方法)

然后在我的控制器中,我有我的endpoint GET方法:

[HttpGet]
public async Task<ActionResult<IEnumerable<Adult>>> GetAdultsAsync
    (
        [FromQuery] string firstname,
        [FromQuery] string lastname,
        [FromQuery] string jobTitle,
        [FromQuery] string hairColor,
        [FromQuery] string eyeColor,
        [FromQuery] string sex,
        [FromQuery] int? age
    ) {
        Func<Adult, bool> criteria = adult => {
            if (firstname != null && !firstname.Equals(adult.FirstName, StringComparison.OrdinalIgnoreCase)) return false;
            if (lastname != null && !lastname.Equals(adult.LastName, StringComparison.OrdinalIgnoreCase)) return false;
            if (jobTitle != null && !jobTitle.Equals(adult.JobTitle, StringComparison.OrdinalIgnoreCase)) return false;
            if (hairColor != null && !hairColor.Equals(adult.HairColor, StringComparison.OrdinalIgnoreCase)) return false;
            if (eyeColor != null && !eyeColor.Equals(adult.EyeColor, StringComparison.OrdinalIgnoreCase)) return false;
            if (sex != null && !sex.Equals(adult.Sex, StringComparison.OrdinalIgnoreCase)) return false;
            if (age != null && age != adult.Age) return false;

            return true;
        };

        IEnumerable<Adult> adults;
        try {
            adults = await iAdultRepo.GetAllByCriteriaAsync(criteria);
        } catch (Exception e) {
            Console.WriteLine(e);
            return BadRequest(e.Message);
        }

        return Ok(adults);
    }
[HttpGet]
公共异步任务GetAdultsAsync
(
[FromQuery]字符串名,
[FromQuery]字符串lastname,
[FromQuery]字符串作业标题,
[FromQuery]字符串颜色,
[FromQuery]字符串eyeColor,
[FromQuery]字符串性别,
[FromQuery]int?年龄
) {
Func标准=成人=>{
if(firstname!=null&&!firstname.Equals(成人.firstname,StringComparison.OrdinalIgnoreCase))返回false;
if(lastname!=null&&!lastname.Equals(成人.lastname,StringComparison.OrdinalIgnoreCase))返回false;
if(jobTitle!=null&!jobTitle.Equals(成人.jobTitle,StringComparison.OrdinalIgnoreCase))返回false;
if(hairColor!=null&!hairColor.Equals(成人.hairColor,StringComparison.OrdinalIgnoreCase))返回false;
if(eyeColor!=null&!eyeColor.Equals(成人.eyeColor,StringComparison.ordinallingorecase))返回false;
if(sex!=null&!sex.Equals(成人.sex,StringComparison.OrdinalIgnoreCase))返回false;
如果(age!=null&&age!=成人.age)返回false;
返回true;
};
无数的成年人;
试一试{
成人=等待iAdultRepo.GetAllByCriteriaAsync(标准);
}捕获(例外e){
控制台写入线(e);
返回请求(e.Message);
}
返回Ok(成人);
}

这有意义吗?有更好/更干净/更简单的方法吗?

在使用存储库模式时,始终返回
IQueryable
而不是
IEnumerable
它使您可以更好地控制查询,并且您可以决定何时使用
toListSync()
FirstOrDefaultAsync()具体化
…等,以及之前要调用的扩展名(例如,决定何时调用
AsNoTracking()
或不调用),以及内存中的较轻扩展名。看

这将是所有db实体的合同:

  public interface IRepositoryBase<T>
    {
        IQueryable<T> FindAll();
        IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression);
        void Create(T entity);
        void Update(T entity);
        void Delete(T entity);
    }
公共接口IRepositoryBase
{
IQueryable FindAll();
IQueryable FindByCondition(表达式);
无效创建(T实体);
无效更新(T实体);
无效删除(T实体);
}
这是每个存储库的基类:

public abstract class RepositoryBase<T> : IRepositoryBase<T> where T : class
    {
        protected RepositoryContext RepositoryContext { get; set; }
        public RepositoryBase(RepositoryContext repositoryContext)
        {
            this.RepositoryContext = repositoryContext;
        }
        public IQueryable<T> FindAll()
        {
            return this.RepositoryContext.Set<T>();
        }
        public IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression)
        {
            return this.RepositoryContext.Set<T>().Where(expression);
        }
        public void Create(T entity)
        {
            this.RepositoryContext.Set<T>().Add(entity);
        }
        public void Update(T entity)
        {
            this.RepositoryContext.Set<T>().Update(entity);
        }
        public void Delete(T entity)
        {
            this.RepositoryContext.Set<T>().Remove(entity);
        }
    }
公共抽象类RepositoryBase:IRepositoryBase其中T:class
{
受保护的RepositoryContext RepositoryContext{get;set;}
公共RepositoryBase(RepositoryContext RepositoryContext)
{
this.RepositoryContext=RepositoryContext;
}
公共可查询FindAll()
{
返回此.RepositoryContext.Set();
}
公共IQueryable FindByCondition(表达式)
{
返回此.RepositoryContext.Set().Where(表达式);
}
公共无效创建(T实体)
{
this.RepositoryContext.Set().Add(实体);
}
公共无效更新(T实体)
{
this.RepositoryContext.Set().Update(实体);
}
公共作废删除(T实体)
{
this.RepositoryContext.Set().Remove(实体);
}
}

我想更好的方法是将这些条件字段放入单独的对象
成人标准
;然后添加到存储库类方法,如
GetAllByCriteria
,该方法将作为
AdultCriteria
类的输入,您在控制器类中拥有的所有神奇的比较都将在该方法中完成。所以控制器只需初始化新的
AdultCriteria
对象,并将其传递到
GetAllByCriteria
方法中。所有详细信息都将隐藏在存储库方法中。您不应使用Func,因为这将导致加载表中的所有数据并在客户端进行筛选。您需要使用表达式,但在构建过滤器时确实存在一些限制。这个问题已经被回答了一百万次,例如@devNull post链接。关于使用存储库,存在着一场完整的争论。他们到底给了你什么?EF上下文实现工作单元。数据库集是您的存储库。像这样包装它们有价值吗?@SimonHalsey包装器的整个想法有一些好的好处:能够为测试交换实现,从不同的数据源构造实体,使用EF以外的数据源(比如具有不同的上下文),这可以将repo的消费者与
public abstract class RepositoryBase<T> : IRepositoryBase<T> where T : class
    {
        protected RepositoryContext RepositoryContext { get; set; }
        public RepositoryBase(RepositoryContext repositoryContext)
        {
            this.RepositoryContext = repositoryContext;
        }
        public IQueryable<T> FindAll()
        {
            return this.RepositoryContext.Set<T>();
        }
        public IQueryable<T> FindByCondition(Expression<Func<T, bool>> expression)
        {
            return this.RepositoryContext.Set<T>().Where(expression);
        }
        public void Create(T entity)
        {
            this.RepositoryContext.Set<T>().Add(entity);
        }
        public void Update(T entity)
        {
            this.RepositoryContext.Set<T>().Update(entity);
        }
        public void Delete(T entity)
        {
            this.RepositoryContext.Set<T>().Remove(entity);
        }
    }