如何使用EF创建数据库并开始使用它



我正在编写一个小应用程序,作为学习如何在C#中使用SQL数据库的练习。NET。我的应用程序试图打开与数据库的连接,如果失败,它会假设数据库不在那里并创建它。这个数据库只由我的应用使用,并且总是本地的,所以它比许多其他数据库情况简单得多。以下是代码,基于我在SO上以及Andrew Troelsen的"Pro C#7:With.NET and.NET Core"中读到的内容:

private static DbConnection connection = null;
private static MyAppModels context = null;
public static void SetupDatabase()
{
string dataProvider = "System.Data.SqlClient";
DbProviderFactory factory = DbProviderFactories.GetFactory(dataProvider);
// get connection object (closes automatically at end of using scope)
connection = factory.CreateConnection();
if (connection == null) { /* throw Exception */ }
string connectionString = @"Data Source=(localdb)mssqllocaldb;Initial Catalog=MyAppDB;Integrated Security=True";
connection.ConnectionString = connectionString;
try { connection.Open(); }
catch (Exception)
{
// database could not be found; try to create it...
connection.ConnectionString = @"Data Source=(localdb)mssqllocaldb;Integrated Security=True;database=master";
connection.Open();
string str = "CREATE DATABASE MyAppDB ON PRIMARY " +
"(NAME = MyApp_Data, FILENAME = 'C:\Users\Me\Documents\MyApp\MyAppData.mdf', " +
"SIZE = 2MB, MAXSIZE = 50MB, FILEGROWTH = 10%) " +
"LOG ON (NAME = MyApp_Log, FILENAME = 'C:\Users\Me\Documents\MyApp\MyAppLog.ldf', " +
"SIZE = 1MB, MAXSIZE = 10MB, FILEGROWTH = 10%)";
DbCommand myCommand = connection.CreateCommand();
myCommand.CommandText = str;
try { myCommand.ExecuteNonQuery(); }
catch (System.Exception ex) { /* throw Exception */ }
}
context = new MyAppModels(connection);
return;
}

在我尝试将数据添加到表中之前,这段代码的执行似乎没有问题,这时我得到了以下错误:

"自创建数据库以来,支持‘MyAppModels’上下文的模型已更改。请考虑使用代码优先迁移来更新数据库(http://go.microsoft.com/fwlink/?LinkId=238269)。">

如果我退出我的应用程序并重新打开它,它就可以连接到现在现有的数据库并添加记录,而不会出现问题。但我不希望我的应用程序的用户在第一次安装后被要求退出并重新打开应用程序,只是为了解决这个问题!

我在这里查看了其他几个帖子,包括:

自数据库创建以来,支持"POSContext"上下文的模型已更改

自数据库创建以来,支持上下文的模型发生了变化

这两者似乎都指向同一个解决方案。此外,第二个引用了微软EF团队成员的话:

当第一次创建模型时,我们运行DatabaseInitializer来执行一些操作,比如在数据库不存在的情况下创建数据库,或者添加种子数据。默认的DatabaseInitializer尝试将使用模型所需的数据库架构与存储在EdmMetadata表中的架构哈希进行比较,该表是与数据库一起创建的(当创建数据库的是Code First时)。现有的数据库将没有EdmMetadata表,因此也不会有散列……如果该表丢失,今天的实现将抛出。我们将在发布法定版本之前更改此行为,因为它是默认版本。在此之前,现有数据库通常不需要任何数据库初始值设定项,因此可以通过调用为您的上下文类型关闭它

Database.SetInitializer<YourDbContext>(null);

我真的不明白这一切。但我还是尝试将该代码添加到我的DbContext类中:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer<MyAppModels>(null);
base.OnModelCreating(modelBuilder);
}

现在,当我尝试将第一条记录添加到表中时,会出现以下错误:

"更新条目时出错。有关详细信息,请参阅内部异常。">

内部异常:"字符串或二进制数据将被截断。语句已终止。">

现在,如果我关闭并重新打开应用程序,每次都会出现同样的错误。所以现在情况更糟了。通过查看其他SO帖子,我发现这个错误指向了一个问题,即输入数据库的数据超过了字段大小。但我在尝试添加的第一条记录中得到了错误,它绝对不会超过该表中的任何字段大小。

我怀疑我不应该将null传递给SetInitializer()。我注意到注释,"……我们运行DatabaseInitializer来做一些事情,比如在数据库不存在的情况下创建数据库或添加种子数据。"我的代码已经在尝试处理数据库创建。我的应用程序不应该这么做吗?SetInitializer()如何知道放置数据库或MAXSIZE的位置?我认为我也不需要它来"添加种子数据"。

我还注意到了这条评论,"我们将在发布法定版本之前改变这种行为,因为这是默认版本。"这听起来像是一个bug。这个评论是9年前写的,所以我希望它已经被修复了。

与此同时,有人有什么建议可以让这项工作正常进行吗?

-更新--

我能够通过在上面显示的创建代码中添加一个小部分来实现这一点:

myCommand.ExecuteNonQuery();
// close & try again to open the database
connection.Close();
connection.ConnectionString = @"Data Source=(localdb)mssqllocaldb;Integrated Security=True;Initial Catalog=MyAppDB";
connection.Open();

看起来这应该是不必要的,但它有效。。。

然后我试了别的。我用一行代码替换了SetupDatabase()中的所有代码,正如我在几个示例教程中看到的那样:

context = new MyAppModels();

这允许/依赖EF打开数据库,并在必要时创建它(在某个未定义/默认位置)。当数据库已经存在时,这是有效的,但当需要创建数据库时,它失败了:

系统。数据SqlClient。SqlException HResult=0x80131904
消息=CREATE FILE遇到操作系统错误5(访问被拒绝。)尝试打开或创建物理文件时"C:\Users\MyAppDB.mdf"。CREATE DATABASE失败。某些文件无法创建列出的名称。检查相关错误。来源=。网SqlClient数据提供程序

注意和MyAppDB.mdf之间缺少反斜杠。我不知道为什么会发生这种情况,也不知道如何修复它。我在App.config中看不到任何明显的东西可以控制这种情况。。。

<connectionStrings>
<add name="MyAppConnection" connectionString="data source=(localdb)mssqllocaldb;initial catalog=MyAppDB;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
</connectionStrings>

有人能帮我解决这个问题吗,这样我就可以使用"纯"EF,或者我需要回到我的破解。。。?

是基于我在SO和"Pro C#7:With.NET and.NET Core"中读到的内容的代码

您似乎已经尝试过将手动DB创建和代码优先迁移混合使用。

结果很糟糕。

我们能解决这个问题吗?

是的。。。有一些额外的细节/代码,甚至可能需要从头开始重新设计很多东西,所以我不确定这是否值得花时间,因为你似乎在研究技术,而不是试图解决一些关键业务内部程序套件中的技术混乱。

所以,我建议你决定你想学什么,并使用标准教程一步一步地做:

  1. 您想学习如何使用EF上下文和代码优先迁移吗?-https://learn.microsoft.com/en-us/ef/ef6/modeling/code-first/migrations/
  2. 想学习如何使用现有的数据库吗?-https://learn.microsoft.com/en-us/ef/ef6/modeling/code-first/workflows/existing-database
  3. 想学习如何使用基本的ADO。NET类(DbConnection,DbDataReader)-https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/ado-net-code-examples
  4. 想在两者之间做点什么吗?-在混合之前,请确保你已经分别使用了每种东西,并且你真的想尝试一下(学术兴趣/实验欲望都很好,但对实际问题应用非标准方法通常是个坏主意)

p。S.:您似乎在使用EF6(就像您使用DatabaseInitializer一样),但实际上我建议您开始学习EF Core,因为它是实体框架的最新迭代。

您发布的代码是如何使用"动态"SQL"手动"创建数据库的。在使用实体框架时,您根本不需要使用这种方法。

使用实体框架最简单的方法之一是使用模型优先:

  • 将新的"ADO.NET实体数据模型"添加到项目中
  • 选择"空EF设计器模型">
  • 编辑模型以设计数据库
  • 保存后(或在VS2019中,您必须在.tt文件上"运行自定义工具"),您将创建所有实体类
  • 在模型中,选择"从模型生成数据库"生成一个SQL文件,该文件包含使用该模型创建新数据库所需的所有SQL语句。此步骤还创建数据库和C#代码之间的所有必要绑定。(所有这些都存在于幕后。)

添加到模型中的实体将成为数据库中的表和C#代码中的类。

添加的属性将成为数据库中的字段和C#代码中的属性。

添加的关联将成为数据库中的外键关系和C#代码中的集合("导航属性")。

然后,您可以使用LINQ和IQueryable在C#代码中开始构建查询。

您可以对模型进行更改,然后再次执行"从模型生成数据库"过程,以获得创建数据库的新SQL文件,但请注意,在执行脚本时,它将首先nuke任何现有数据库。这在测试时可以,但使用版本控制系统也有帮助,这样您就可以看到对SQL的更改,然后手动更改数据库结构,以确保数据库与模型匹配,而无需首先删除它。

相关内容

最新更新