EF Code First, IoC and DbConnection

4
我正在使用Entity Framework Code First。我想在实例化从System.Data.Entity.DbContext派生的上下文时能够注入一个System.Data.Common.DbConnection对象。这样,我就可以根据代码运行的环境传递不同类型的连接,例如在开发中使用System.Data.SqlClient(SQL Server),在单元测试时使用System.Data.SQLite,以及在生产中使用其他内容。Context的相关部分如下:
public class Context : DbContext
{
    public Context(DbConnection dbConnection)
        : base(dbConnection, true)
    {
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<Context, Configuration>());

        base.OnModelCreating(modelBuilder);
    }

    public DbSet<Test> Tests { get; set; }
}

这给了我以下错误:The target context 'Services.Persistence.Context' is not constructible. Add a default constructor or provide an implementation of IDbContextFactory. 我想这发生在模型初始化期间,当Entity Framework显然感到需要新建自己的Context,独立于我试图实现的IoC模式。缺少默认构造函数是有意设计的。IDbContextFactory接口同样无用 - 它也必须有一个默认构造函数。

Entity Framework Code First是否完全依赖于从配置文件中读取连接字符串来设置其配置(或者直接传递连接字符串),还是可以绕过此功能?

更新,以下是Windsor配置:

container.Register(Component
    .For<DbConnection>()
    .UsingFactoryMethod(() =>
        new SqlConnection("Data Source=(localdb)\\v11.0;Database=ThatProject;MultipleActiveResultSets=true"))
    .LifeStyle.Transient);

container.Register(Component
    .For<Context>()
    .UsingFactoryMethod(k => new Context(k.Resolve<DbConnection>()))
    .LifeStyle.PerWebRequest);

container.Register(Component
    .For<IRepository>()
    .UsingFactoryMethod(k => new Repository(k.Resolve<Context>()))
    .LifeStyle.PerWebRequest);

我的意思是,你使用的实际配置是什么?你是如何配置DbConnection进行传递的? - Erik Funkenbusch
@MystereMan 我已经将Windsor配置添加到问题文本中。 - friism
温莎不是我首选的容器,但你配置的方式看起来不太对。使用服务解析通常是不好的做法,而仅仅创建新实例也不是正确的解决方案。 - Erik Funkenbusch
@DiegoMijelshon 这对于单元测试来说有点不切实际。 - friism
@friism 我不得不反驳一下; 我为我的测试项目使用单独的.config文件,从未遇到过任何问题。我也使用Windsor。你认为什么使它不切实际? - Diego Mijelshon
显示剩余4条评论
1个回答

2

我非常确定你的问题与EF无关,但我不是Windsor的用户,所以我不能确定你的配置问题。我已经使用Ninject复制了一个类似的配置,它的工作方式与你期望的完全相同,具体如下:

class Program
    {
        static void Main(string[] args)
        {
            IKernel kernel = new StandardKernel();
            kernel.Bind<DbConnection>().ToMethod((ctx) =>{return new SqlConnection("Data Source=(localdb)\\v11.0;Database=ThatProject;MultipleActiveResultSets=true");});
            kernel.Bind<Context>().ToSelf();//not really needed
            kernel.Bind<TestRepository>().ToSelf();//not really needed
            kernel.Get<TestRepository>();
        }
    }
    public class Context : DbContext
    {
        public Context(DbConnection dbConnection)
            : base(dbConnection, true){}

        public DbSet<Test> Tests { get; set; }
    }
    public class TestRepository
    {
        public TestRepository(Context c)
        {
            c.Tests.Add(new Test());
            c.SaveChanges();

            var all = c.Tests;
        }
    }
    public class Test
    {
        public int Id { get; set; }
    }

这意味着EF不会尝试使用任何上下文创建的技巧(因为对我来说,非空构造函数可以正常工作)。
从你的Windsor配置中,我会预期你需要像下面这样做,但我不太确定确切的语法:
container.Register(Component
    .For<DbConnection>()
    .UsingFactoryMethod(() =>
        new SqlConnection("Data Source=(localdb)\\v11.0;Database=ThatProject;MultipleActiveResultSets=true"))
    .LifeStyle.Transient);

container.Register(Component
    .For<Context>()
    .ImplementedBySelf()//this probably isn't the correct syntax
    .LifeStyle.PerWebRequest);//per request is good, i have some details around why this is good practice if you are interested

container.Register(Component
    .For<IRepository>()
    .ImplementedBy<ConcreteRepository>()//you arent really calling a method and creating the object yourself you are letting Windsor create the object and sort out the dependancy tree
    .LifeStyle.PerWebRequest);

1
事实证明,只有在启用自动迁移时才会出现问题,我已将此添加到代码示例中。 - friism

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接