使用Entity Framework 4 Code First时出现的数据库正在使用错误

51

我有一个MVC3和EF 4的Code First应用程序,将数据库初始化器设置为DropCreateDatabaseIfModelChanges<TocratesDb>可以在模型更改时更改数据库,其中TocratesDb是我的派生DbContext

我现在对模型进行了更改,添加了一个类的属性,但是当EF尝试删除和重新创建数据库时,我遇到以下错误:

Cannot drop database "Tocrates" because it is currently in use.

我在这个数据库上没有任何其他的连接。我认为我的cDbContext仍然对数据库保持着打开状态,但是我该怎么办呢?

更新: 现在我的问题是如何根据模型重新创建数据库。使用更通用的IDatabaseInitializer后,我失去了这个功能,必须自己实现。


这是因为我在数据库中调用Membership方法,导致了冲突。我通过强制初始化程序在使用Membership系统之前运行和种子化来解决这个问题。 - Chris
Chris,我有一个类似的问题,但我对asp .net非常陌生。你能告诉他如何在成员系统之前强制运行初始化程序吗?谢谢。 - Tripping
请看一下这个类似问题的答案:https://dev59.com/c2w05IYBdhLWcg3w0VKa - Camilo Sanchez
6个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
46

要能够删除数据库,您当前的上下文必须具有已打开的连接。问题在于可能会有其他打开的连接会阻止您的db初始化器。一个非常好的例子是在管理工作室中打开了来自您的数据库的任何表格。另一个可能的问题是应用程序连接池中的已打开的连接。

在MS SQL中,可以通过将DB切换到单用户模式并强制关闭所有连接并回滚不完整的事务来避免这种情况:

ALTER DATABASE Tocrates SET SINGLE_USER WITH ROLLBACK IMMEDIATE
你可以创建一个新的初始化程序,首先调用此命令,然后删除数据库。请注意,您应该自己处理数据库连接,因为必须在同一连接上调用ALTER DATABASEDROP DATABASE。 编辑: 这里有一个使用装饰器模式的示例。你可以修改它并在构造函数中初始化内部初始化程序,而不是将其作为参数传递。
public class ForceDeleteInitializer : IDatabaseInitializer<Context>
{
    private readonly IDatabaseInitializer<Context> _initializer;

    public ForceDeleteInitializer(IDatabaseInitializer<Context> innerInitializer)
    {
        _initializer = innerInitializer;    
    }

    public void InitializeDatabase(Context context)
    {
        context.Database.SqlCommand("ALTER DATABASE Tocrates SET SINGLE_USER WITH ROLLBACK IMMEDIATE");
        _initializer.InitializeDatabase(context);
    }
}

我在数据库上没有打开任何SSMS连接,但我的问题是Initializer没有提供适当的钩子来执行您推荐的代码,更不用说我不应该做框架的维护工作。 - ProfK
啊,是的,我之前不久就发现了这个,并且正在尝试它。 - ProfK
使用这个例子后,我自己无法进入数据库了。最后我通过将其设置回MULTI_USER来解决了这个问题。 - Jowen
@Jowen:看起来你的数据库并没有真正被删除和重建,而只是被修改了。 - Ladislav Mrnka
我已经实现了这个功能,但有时会出现错误System.Data.SqlClient.SqlException未被用户代码处理 HResult=-2146232060 Message=不允许在多语句事务中使用ALTER DATABASE语句。 - Jeff Pearce
显示剩余7条评论

41

我发现在EF 6中,这会导致一个“ALTER DATABASE statement not allowed within multi-statement transaction”错误。

解决方法是使用新的事务行为重载:

context.Database.ExecuteSqlCommand(TransactionalBehavior.DoNotEnsureTransaction, "ALTER DATABASE [" + context.Database.Connection.Database + "] SET SINGLE_USER WITH ROLLBACK IMMEDIATE");

2
非常好的评论。当我发出 ALTER DATABASE 来设置 ALLOW_SNAPSHOT_ISOLATION ON 以创建 EF 6 代码优先数据库时,解决了我的问题。谢谢! - RaoulRubin
喜欢 Stack Overflow 这样的网站,因为可以找到一些省时的小技巧...感谢分享! - Dav
1
重新创建后,将数据库切换回多用户模式也是有意义的,例如:context.Database.ExecuteSqlCommand(TransactionalBehavior.DoNotEnsureTransaction, "ALTER DATABASE [" + context.Database.Connection.Database + "] SET MULTI_USER WITH ROLLBACK IMMEDIATE"); - Corwin
1
这对我非常方便。只需将它放在context.Database.Delete()调用的前一行,它就解决了我的问题。 - 4imble

22

我也遇到了同样的问题。

我是通过关闭在Visual Studio的Server Explorer视图下打开的连接来解决它的。


3
从来没想过在那里找。唉。 - WernerCD

13

我知道这篇文章有点过时,但是我无法让被接受的解决方案正常工作,所以我想出了一个快速的解决方案...

using System;
using System.Data.Entity;

namespace YourCompany.EntityFramework
{
    public class DropDatabaseInitializer<T> : IDatabaseInitializer<T> where T : DbContext, new()
    {
        public DropDatabaseInitializer(Action<T> seed = null)
        {
            Seed = seed ?? delegate {};
        }

        public Action<T> Seed { get; set; }

        public void InitializeDatabase(T context)
        {
            if (context.Database.Exists())
            {
                context.Database.ExecuteSqlCommand("ALTER DATABASE [" + context.Database.Connection.Database + "] SET SINGLE_USER WITH ROLLBACK IMMEDIATE");
                context.Database.ExecuteSqlCommand("USE master DROP DATABASE [" + context.Database.Connection.Database + "]");
            }

            context.Database.Create();

            Seed(context);
        }
    }
}
这对我很有用,而且支持轻松种植。

这对我来说是一个错误,因为它说数据库已经存在,使用VS2012。 - Jon
在进行身份验证时,您是否可以访问 master?这对于使用 master DROP DATABASE 非常关键。 - Dave Jellison
4
我尝试了你的解决方案,但是它给了我一个System.Data.SqlClient.SqlException错误:不允许在多语句事务中使用ALTER DATABASE语句。你有什么想法吗? - ashutosh raina
1
实际上,您应该使用括号将数据库名称变量包装起来,例如 context.Database.ExecuteSqlCommand("ALTER DATABASE [" + context.Database.Connection.Database + "] SET SINGLE_USER WITH ROLLBACK IMMEDIATE"); 否则,我会收到一个关于 WITH 关键字的错误。 - Nullius
@ashutoshraina 尝试向 ExecuteSqlCommand 方法添加 TransactionalBehavior.DoNotEnsureTransaction 参数。更多信息:https://dev59.com/F2Ei5IYBdhLWcg3wD4gu - Jowen

3
在Visual Studio 2012中,SQL Server对象资源管理器窗口可以保持与数据库的连接。关闭该窗口及其打开的所有窗口会释放该连接。

0
一个简单的关闭整个项目并重新打开它对我很有帮助。这是确保没有连接仍然打开的最简单方法。

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