C# 如何为API端点方法动态选择DbContext

C# 如何为API端点方法动态选择DbContext,c#,.net,entity-framework,api,database-connection,C#,.net,Entity Framework,Api,Database Connection,我开发了一个API,它使用一个helper类来获取每个端点函数的数据库上下文。现在我正试图为每个端点编写单元测试,我想在单元测试项目中使用内存中的db 我遇到的问题是,为了调用API函数,我必须在API控制器类中添加一个构造函数。这将允许我将内存中数据库的dbContext传递给控制器函数以供其使用。但是,由于添加了构造函数,我在尝试命中端点时出现了以下错误: "exceptionMessage": "Unable to resolve service for type 'Appointment

我开发了一个API,它使用一个helper类来获取每个端点函数的数据库上下文。现在我正试图为每个端点编写单元测试,我想在单元测试项目中使用内存中的db

我遇到的问题是,为了调用API函数,我必须在API控制器类中添加一个构造函数。这将允许我将内存中数据库的dbContext传递给控制器函数以供其使用。但是,由于添加了构造函数,我在尝试命中端点时出现了以下错误:

"exceptionMessage": "Unable to resolve service for type 'AppointmentAPI.Appt_Models.ApptSystemContext' while attempting to activate 'AppointmentAPI.Controllers.apptController'."
更新

控制器

 public class apptController : Controller
    {
        private readonly ApptSystemContext _context;

        public apptController(ApptSystemContext dbContext)
        {
            _context = dbContext;
        }


        #region assingAppt
        /*
         * assignAppt()
         *
         * Assigns newly created appointment to slot
         * based on slotId
         *
         */
        [Authorize]
        [HttpPost]
        [Route("/appt/assignAppt")]
        public string assignAppt([FromBody] dynamic apptData)
        {
            int id = apptData.SlotId;
            string json = apptData.ApptJson;
            DateTime timeStamp = DateTime.Now;

            using (_context)
            {
               var slot = _context.AppointmentSlots.Single(s => s.SlotId == id);

                // make sure there isn't already an appointment booked in appt slot
                if (slot.Timestamp == null)
                {
                    slot.ApptJson = json;
                    slot.Timestamp = timeStamp;

                    _context.SaveChanges();
                    return "Task Executed\n";
                }
                else
                {
                    return "There is already an appointment booked for this slot.\n" +
                           "If this slot needs changing try updating it instead of assigning it.";
                }
            }
        }
   }
UnitTest.cs

using System;
using Xunit;
using AppointmentAPI.Controllers;
using AppointmentAPI.Appt_Models;
using Microsoft.EntityFrameworkCore;

namespace XUnitTest
{
    public abstract class UnitTest1
    {
        protected UnitTest1(DbContextOptions<ApptSystemContext> contextOptions)
        {
            ContextOptions = contextOptions;

            SeedInMemoryDB();
        }

        protected DbContextOptions<ApptSystemContext> ContextOptions { get; }

        private void SeedInMemoryDB()
        {
            using(var context = new ApptSystemContext(ContextOptions))
            {
                context.Database.EnsureDeleted();
                context.Database.EnsureCreated();

                var seventh = new AppointmentSlots
                {
                    SlotId = 7,
                    Date = Convert.ToDateTime("2020-05-19 00:00:00.000"),
                    Time = TimeSpan.Parse("08:45:00.0000000"),
                    ApptJson = null,
                    Timestamp = null
                };

                context.AppointmentSlots.Add(seventh);
                context.SaveChanges();
            }
        }

        [Fact]
        public void Test1()
        {
            DbContextOptions<ApptSystemContext> options;
            var builder = new DbContextOptionsBuilder<ApptSystemContext>();
            builder.UseInMemoryDatabase();
            options = builder.Options;

            var context = new ApptSystemContext(options);

            var controller = new apptController(context);

            // Arrange
            var request = new AppointmentAPI.Appt_Models.AppointmentSlots
            {
                SlotId = 7,
                ApptJson = "{'fname':'Emily','lname':'Carlton','age':62,'caseWorker':'Brenda', 'appStatus':'unfinished'}",
                Timestamp = Convert.ToDateTime("2020-06-25 09:34:00.000")
            };

            string expectedResult = "Task Executed\n";

            // Act
            var response = controller.assignAppt(request);

            Assert.Equal(response, expectedResult);

        }
    }
}

使用系统;
使用Xunit;
使用AppointAPI.Controllers;
使用AppointAPI.Appt_模型;
使用Microsoft.EntityFrameworkCore;
名称空间XUnitTest
{
公共抽象类UnitTest1
{
受保护的UnitTest1(DbContextOptions contextOptions)
{
ContextOptions=ContextOptions;
SeedInMemoryDB();
}
受保护的DbContextOptions ContextOptions{get;}
私有void SeedInMemoryDB()
{
使用(var-context=new-ApptSystemContext(ContextOptions))
{
context.Database.EnsureDeleted();
context.Database.recreated();
var seventh=新任命职位
{
SlotId=7,
日期=转换为当前时间(“2020-05-19 00:00:00.000”),
Time=TimeSpan.Parse(“08:45:00.0000000”),
ApptJson=null,
时间戳=空
};
上下文。任命槽。添加(第七);
SaveChanges();
}
}
[事实]
公共void Test1()
{
DbContextOptions选项;
var builder=new DbContextOptionsBuilder();
builder.UseInMemoryDatabase();
options=builder.options;
var context=new-ApptSystemContext(选项);
var控制器=新的apptController(上下文);
//安排
var请求=新任命API.Appt_Models.AppointmentSlots
{
SlotId=7,
ApptJson=“{'fname':'Emily','lname':'Carlton','age':62,'Caseworner':'Brenda','appStatus':'unfinished'”,
时间戳=转换到当前时间(“2020-06-25 09:34:00.000”)
};
字符串expectedResult=“已执行任务\n”;
//表演
var响应=controller.assignAppt(请求);
Assert.Equal(响应、预期结果);
}
}
}
InMemoryClass.cs

using System;
using System.Data.Common;
using Microsoft.EntityFrameworkCore;
using AppointmentAPI.Appt_Models;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace XUnitTest
{
    public class InMemoryClass1 : UnitTest1, IDisposable
    {
        private readonly DbConnection _connection;

        public InMemoryClass1()
            :base(
                 new DbContextOptionsBuilder<ApptSystemContext>()
                    .UseSqlite(CreateInMemoryDB())
                    .Options
            )
        {
            _connection = RelationalOptionsExtension.Extract(ContextOptions).Connection;
        }

        private static DbConnection CreateInMemoryDB()
        {
            var connection = new SqliteConnection("DataSource=:memory:");

            connection.Open();

            return connection;
        }

        public void Dispose() => _connection.Dispose();

    }
}

使用系统;
使用System.Data.Common;
使用Microsoft.EntityFrameworkCore;
使用AppointAPI.Appt_模型;
使用Microsoft.Data.Sqlite;
使用Microsoft.EntityFrameworkCore.Infrastructure;
名称空间XUnitTest
{
MemoryClass1中的公共类:UnitTest1,IDisposable
{
专用只读DbConnection\u连接;
公共内存类1()
:基本(
新的DbContextOptionsBuilder()
.UseSqlite(CreateInMemoryDB())
.选项
)
{
_connection=RelationalOptionsExtension.Extract(ContextOptions).connection;
}
私有静态数据库连接CreateInMemoryDB()
{
var connection=newsqliteconnection(“数据源=:内存:”);
connection.Open();
回路连接;
}
public void Dispose()=>_connection.Dispose();
}
}

异常表明您尚未在Startup.cs中注册DBContext(如上所述)。我还建议您将私有readonly属性的名称更改为DbContext以外的名称(这是类名,可能会引起混淆) 使用类似以下内容:

private readonly ApptSystemContext\u context;
除此之外,你的方法应该改变

首先,您将在注册DBContext时设置连接字符串。让依赖注入为您解决这个问题。您的控制器应如下所示:

公共apptController(ApptSystemContext dbContext)
{
_context=dbContext;
}
如果在启动时注册,dbContext将不会为null

接下来,单元测试是一个棘手的概念,但一旦您编写了单元测试,您将开始更好地理解它

您已经说过希望使用内存中的SQL数据库进行单元测试,这是一种很好的方法(请注意,内存中的SQL有一些限制,比如没有FK约束)。接下来,我假设您要测试控制器,因此,由于必须传入DBContext才能实例化控制器,因此可以创建一个新的DBContext实例,该实例配置为使用内存中的数据库

比如说

public void ApptControllerTest()
{
//创建新的dbcontext
DbContextOptions选项;
var builder=new DbContextOptionsBuilder();
builder.UseInMemoryDatabase();
options=builder.options;
var context=new-ApptSystemContext(选项);
//实例化你的控制器
var控制器=新的appController(上下文);
//调用要测试的方法
var retVal=controller.assignAppt(参数转到此处);
}
将方法主体更改为:

公共字符串assignAppt([FromBody]动态apptData)
{
int id=apptData.SlotId;
字符串json=apptData.ApptJson;
DateTime timeStamp=DateTime.Now;
使用(_上下文)
{
var slot=\u context.AppointmentSlots.Single(s=>s.SlotId==id);
//确保应用程序插槽中没有预约
if(slot.Timestamp==null)
{
slot.ApptJson=json;