问题 如何防止DbContext改变数据库?


我正在学习实体框架(目前正在使用EF6 beta),而我正在使用现有数据库上的代码优先模式。实体和 DbContext 使用T4模板自动创建类。

我想阻止 DbContext 从运行时创建/更改任何内容到数据库。

我怎样才能做到这一点?


6168
2017-09-18 03:48


起源

你是如何创建数据库的?默认情况下,EF Code First会向数据库添加一个表,以便能够判断您的模型是否已更改。如果数据库未通过codefirst / migrations进行创建,则db中没有数据,EF将不会触及数据库的模式。如果您正在谈论数据 - 任何时候调用.SaveChanges()数据都可以添加/修改/删除 - Pawel
如果没有创建数据库,CodeFirst将不会更改您的数据库。 - Pawel
它确实创建了新对象,因为我最初没有正确映射模式。我最终在默认的dbo架构中重新创建了所有表。 - Crono
是的,它创建了新对象,因为它找不到任何对象,因为它们没有在默认模式中定义。请注意,由于它创建了表,因此还添加了迁移历史记录表,其中包含有关模型的信息。因此,它能够告诉模型已经改变,但与它创建的表格相比,而不是已经存在的表格。它还将使用它创建的表而不是原始表。如果更改模型,禁用初始化程序后,模型将不再与数据库匹配,并可能导致运行时问题或数据损坏。 - Pawel
你应该用 .ToTable(tableName, schemaName) 映射到非默认架构中的表。 - Pawel


答案:


当你这样做 启用的迁移 命令,您将看到文件夹 /迁移 在其下可以找到名为的文件 Configuration.cs。在该文件的构造函数中,默认情况下,有一个属性设置为value:

 AutomaticMigrationsEnabled = false;

这将确保您的数据库不会自动迁移,而是通过手动调用每个迁移。

但是,如果您的数据库大于您的域模型,即您只是在现有数据库的一部分上运行,那么应用程序中的某个位置就会启动(如果它是ASP.NET应用程序, 的Application_Start 事件处理程序),您需要添加以下代码:

Database.SetInitializer<YourDbContext>(null);

否则,实体框架会抱怨域模型中的数据库定义与数据库的实际当前状态不匹配。

编辑:

如果要确保YourDbContext不尝试更改数据库,请执行以下操作:

protected override void OnModelCreating(DbModelBuilder modelBuilder) 
{ 
    throw new YourCustomException("Code first changes are not allowed."); 
}

再次编辑:

我试图了解你想要完成的场景。如果您有现有数据库,并且只想手动更新它,则这是方法:

  1. 使用DbContext生成器模板生成DbContext和实体 从现有数据库。
  2. 运行enable-migrations
  3. 添加迁移初始化
  4. 如果一切都完成,第3步应该生成空迁移 正常。你可以删除它。
  5. 现在,无论何时进行某些更改,都需要执行添加迁移 更换名字。除非你这样做,否则这不会进入数据库 更新数据库。

10
2017-09-18 04:24



如果我在OnModelCreating方法中抛出异常,那么我还能在哪里通过流畅的api定义配置? - Crono
好的,我正在努力了解你要做的事情。我再次编辑了我的帖子,看看这对你现在是否有意义。 - Admir Tuzović
正如我所说,我有一个生成实体和DbContext的T4模板。我需要它,因为我有命名和接口实现要求。默认的生成器并没有给我一个有效的方法来允许它发生(除非我对它的工作原理如此无知)。不过,我认为你的答案非常完整,所以我会将其标记为已被接受。非常感谢您的帮助。 :) - Crono
我希望EF创建表,但我不希望它添加迁移历史表。我怎样才能做到这一点? - Akbari


将上下文的初始化设置为 null

应该在应用程序启动时将数据库保留在其现有状态。

请注意,如果在更改dbContext类和/或数据库模式后尝试运行应用程序,则会出现异常,因为数据库模式将不再与上下文所期望的相匹配。


3
2017-09-18 04:07



我是对的,我不能阻止引用上下文程序集的任何其他程序集更改它的初始化程序吗?我真的不满意。 :(我希望我可以在SaveChanges上抛出异常,如果以允许数据库更改的方式设置上下文。猜猜它并不意味着像这样使用。 - Crono
如果您担心要更改的数据库的结构,您只需将上下文的连接字符串更改为不赋予其创建/更改表权限的连接字符串。如果您担心要更改现有表的内容,则可以使用对要保护的表具有只读权限的连接字符串。编辑:澄清一下,您需要在SQL服务器中设置新的(受限制的)帐户,并更改连接字符串,以便使用这些帐户进行连接。 - ThinTim
为了解决这个问题:“如果以允许数据库更改的方式设置上下文,我希望我可以在SaveChanges上抛出异常。”,我修​​改了我的帖子。检查EDIT之间的解决方案,它可能会帮助你。 - Admir Tuzović
@ThinTim我没有为该dbcontext使用专用连接字符串,它可以与任何DBConnection实例一起使用。如果由于某种原因一个用户没有被授予特定表的权限,那么我更喜欢让应用程序抛出SecurityException而不是仅仅提取/写入数据。这是一个重要的问题,我不能只是背弃,我害怕。 :■ - Crono
我不确定我理解你的问题。如果您不希望应用程序更新任何数据,为什么不简单地避免调用 SaveChanges()?如果您需要检查用户是否有权在呼叫之前进行更改 SaveChanges(),为什么不将上下文包装在强制执行这些规则的业务逻辑层中?如果您需要sql server来防止写入/更改,为什么不为EF分配自定义连接字符串?如果您需要隐藏其他程序集的上下文,为什么不这样做 internal? - ThinTim


我在DBFirst和EF 6.1中遇到了这个问题,它正在创建一个基于EntityName的新表。所以我修改了OnModelCreating虚拟方法来映射我的表。

因此,如果EntityName被命名为MyStuffs,那么默认情况下EnitityNames是多元化的,您可以选择在EF生成期间取消选择。在我的例子中,向导正在生成表格的复数版本。

public class MyContext: DbContext
{
    public MyContext()
        : base("name=MyEntityConnectionName")
    {
    }
    public MyContext(string connectionString)
        : base(connectionString)
    {
    }
    #region methods

    public static MyContext Create()
    {
        return new MyContext();
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        // Map Entities to their tables.
        modelBuilder.Entity<MyModel>().ToTable("MyStuff"); //prevent a new table from being added since we added a mapping
    }

    #endregion
    public virtual DbSet<MyModel> MyEntities{ get; set; }
}

0
2017-11-01 01:04