2015-01-29 20 views
9

Khi viết một số bài kiểm tra đơn vị cho ứng dụng của chúng tôi, tôi tình cờ gặp một số hành vi lạ trong EF6 (được thử nghiệm với 6.1 và 6.1.2): dường như không thể tạo và xóa liên tục cơ sở dữ liệu tên/cùng chuỗi kết nối) trong cùng một ngữ cảnh ứng dụng.Lặp lại việc tạo và xóa cơ sở dữ liệu trong Entity Framework

thử nghiệm thiết lập:

public class A 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
} 

class AMap : EntityTypeConfiguration<A> 
{ 
    public AMap() 
    { 
     HasKey(a => a.Id); 
     Property(a => a.Name).IsRequired().IsMaxLength().HasColumnName("Name"); 
     Property(a => a.Id).HasColumnName("ID"); 
    } 
} 

public class SomeContext : DbContext 
{ 
    public SomeContext(DbConnection connection, bool ownsConnection) : base(connection, ownsConnection) 
    { 

    } 

    public DbSet<A> As { get; set; } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     base.OnModelCreating(modelBuilder); 
     modelBuilder.Configurations.Add(new AMap()); 
    } 
} 

[TestFixture] 
public class BasicTest 
{ 
    private readonly HashSet<string> m_databases = new HashSet<string>(); 

    #region SetUp/TearDown 

    [TestFixtureSetUp] 
    public void SetUp() 
    { 
     System.Data.Entity.Database.SetInitializer(
      new CreateDatabaseIfNotExists<SomeContext>()); 
    } 


    [TestFixtureTearDown] 
    public void TearDown() 
    { 
     foreach (var database in m_databases) 
     { 
      if (!string.IsNullOrWhiteSpace(database)) 
       DeleteDatabase(database); 
     } 
    } 

    #endregion 


    [Test] 
    public void RepeatedCreateDeleteSameName() 
    { 
     var dbName = Guid.NewGuid().ToString(); 
     m_databases.Add(dbName); 
     for (int i = 0; i < 2; i++) 
     { 
      Assert.IsTrue(CreateDatabase(dbName), "failed to create database"); 
      Assert.IsTrue(DeleteDatabase(dbName), "failed to delete database"); 
     } 

     Console.WriteLine(); 
    } 

    [Test] 
    public void RepeatedCreateDeleteDifferentName() 
    { 
     for (int i = 0; i < 2; i++) 
     { 
      var dbName = Guid.NewGuid().ToString(); 
      if (m_databases.Add(dbName)) 
      { 
       Assert.IsTrue(CreateDatabase(dbName), "failed to create database"); 
       Assert.IsTrue(DeleteDatabase(dbName), "failed to delete database"); 
      } 
     } 

     Console.WriteLine(); 
    } 

    [Test] 
    public void RepeatedCreateDeleteReuseName() 
    { 
     var testDatabases = new HashSet<string>(); 
     for (int i = 0; i < 3; i++) 
     { 
      var dbName = Guid.NewGuid().ToString(); 
      if (m_databases.Add(dbName)) 
      { 
       testDatabases.Add(dbName); 
       Assert.IsTrue(CreateDatabase(dbName), "failed to create database"); 
       Assert.IsTrue(DeleteDatabase(dbName), "failed to delete database"); 
      } 
     } 
     var repeatName = testDatabases.OrderBy(n => n).FirstOrDefault(); 
     Assert.IsTrue(CreateDatabase(repeatName), "failed to create database"); 
     Assert.IsTrue(DeleteDatabase(repeatName), "failed to delete database"); 

     Console.WriteLine(); 
    } 

    #region Helpers 

    private static bool CreateDatabase(string databaseName) 
    { 
     Console.Write("creating database '" + databaseName + "'..."); 
     using (var connection = CreateConnection(CreateConnectionString(databaseName))) 
     { 
      using (var context = new SomeContext(connection, false)) 
      { 
       var a = context.As.ToList(); // CompatibleWithModel must not be the first call 
       var result = context.Database.CompatibleWithModel(false); 
       Console.WriteLine(result ? "DONE" : "FAIL"); 
       return result; 
      } 
     } 
    } 


    private static bool DeleteDatabase(string databaseName) 
    { 
     using (var connection = CreateConnection(CreateConnectionString(databaseName))) 
     { 
      if (System.Data.Entity.Database.Exists(connection)) 
      { 
       Console.Write("deleting database '" + databaseName + "'..."); 
       var result = System.Data.Entity.Database.Delete(connection); 
       Console.WriteLine(result ? "DONE" : "FAIL"); 
       return result; 
      } 
      return true; 
     } 
    } 

    private static DbConnection CreateConnection(string connectionString) 
    { 
     return new SqlConnection(connectionString); 
    } 

    private static string CreateConnectionString(string databaseName) 
    { 
     var builder = new SqlConnectionStringBuilder 
     { 
      DataSource = "server", 
      InitialCatalog = databaseName, 
      IntegratedSecurity = false, 
      MultipleActiveResultSets = false, 
      PersistSecurityInfo = true, 
      UserID = "username", 
      Password = "password" 
     }; 
     return builder.ConnectionString; 
    } 

    #endregion 

} 

RepeatedCreateDeleteDifferentName hoàn tất thành công, hai người kia thất bại. Theo đó, bạn không thể tạo một cơ sở dữ liệu có cùng tên, đã được sử dụng một lần trước đó. Khi cố gắng tạo cơ sở dữ liệu lần thứ hai, kiểm tra (và ứng dụng) ném một SqlException, lưu ý một đăng nhập thất bại. Đây có phải là một lỗi trong Entity Framework hoặc là hành vi này có chủ ý (với những gì giải thích)?

Tôi đã thử nghiệm điều này trên Ms SqlServer 2012 và Express 2014, chưa có trên Oracle. Nhân tiện: EF dường như có vấn đề với CompatibleWithModel là cuộc gọi đầu tiên tới cơ sở dữ liệu.

Cập nhật: Đăng một vấn đề trên tracker EF lỗi (link)

+0

Có lý do nào mà tất cả người giúp đỡ của bạn là phương pháp tĩnh không? – timothyclifford

+0

@timothyclifford Có lẽ vì chúng không dựa vào bất kỳ trạng thái nào, vì vậy chúng có thể được đánh dấu là tĩnh? Có lý do nào bạn phản đối phương pháp tĩnh không? – spender

+0

Nói chung chỉ không thích các lớp/phương pháp tĩnh khi nói đến bài kiểm tra đơn vị nhưng đây chỉ là sở thích cá nhân :) xem mã có vẻ tốt, sẽ tự mình kiểm tra và xem liệu tôi có thể tìm hiểu thêm – timothyclifford

Trả lời

3

Trình khởi tạo cơ sở dữ liệu chỉ chạy một lần cho mỗi ngữ cảnh cho mỗi AppDomain. Vì vậy, nếu bạn xóa cơ sở dữ liệu tại một số điểm tùy ý, chúng sẽ không tự động chạy lại và tạo lại cơ sở dữ liệu. Bạn có thể sử dụng DbContext.Database.Initialize(force: true) để buộc trình khởi chạy chạy lại.

+0

Cảm ơn người đàn ông, đã giải quyết được vấn đề của tôi. Tôi không biết làm thế nào tôi có thể bỏ lỡ điều đó. Hãy tiếp tục công việc tốt với EF! Nhưng vẫn có vẻ là một sự phụ thuộc vào chuỗi kết nối khi tạo cơ sở dữ liệu đã làm việc với cùng một ngữ cảnh trong cùng một AppDomain nhưng tên cơ sở dữ liệu khác nhau. Tuy nhiên nó hoạt động như một sự quyến rũ bây giờ. Chúc mừng :) – hoekki

1

Một vài ngày trước, tôi đã viết các bài kiểm tra tích hợp bao gồm truy cập DB thông qua EF6. Đối với điều này, tôi đã phải tạo và thả một cơ sở dữ liệu LocalDB trên mỗi trường hợp thử nghiệm, và nó đã làm việc cho tôi.

Tôi không sử dụng tính năng EF6 initializer cơ sở dữ liệu, nhưng thay vì thực hiện một giọt/CREATE DATABASE kịch bản, với sự giúp đỡ của this post - Tôi sao chép ví dụ ở đây:

using (var conn = new SqlConnection(@"Data Source=(LocalDb)\v11.0;Initial Catalog=Master;Integrated Security=True")) 
{ 
    conn.Open(); 
    var cmd = new SqlCommand(); 
    cmd.Connection = conn; 
    cmd.CommandText = string.Format(@" 
     IF EXISTS(SELECT * FROM sys.databases WHERE name='{0}') 
     BEGIN 
      ALTER DATABASE [{0}] 
      SET SINGLE_USER 
      WITH ROLLBACK IMMEDIATE 
      DROP DATABASE [{0}] 
     END 

     DECLARE @FILENAME AS VARCHAR(255) 

     SET @FILENAME = CONVERT(VARCHAR(255), SERVERPROPERTY('instancedefaultdatapath')) + '{0}'; 

     EXEC ('CREATE DATABASE [{0}] ON PRIMARY 
      (NAME = [{0}], 
      FILENAME =''' + @FILENAME + ''', 
      SIZE = 25MB, 
      MAXSIZE = 50MB, 
      FILEGROWTH = 5MB)')", 
     databaseName); 

    cmd.ExecuteNonQuery(); 
} 

Các mã sau đây là trách nhiệm tạo đối tượng cơ sở dữ liệu theo mô hình:

var script = objectContext.CreateDatabaseScript(); 

using (var command = connection.CreateCommand()) 
{ 
    command.CommandType = CommandType.Text; 
    command.CommandText = script; 

    connection.Open(); 
    command.ExecuteNonQuery(); 
} 

Không cần thay đổi tên cơ sở dữ liệu giữa các thử nghiệm.

+0

Cảm ơn câu trả lời của bạn.Phiên bản của bạn của một workaround nên làm việc, trên thực tế, tôi đã thực hiện một cái gì đó tương tự cho ứng dụng của tôi. Tuy nhiên, điều này không thực sự trả lời câu hỏi của tôi. Theo ý kiến ​​của tôi, một ORM buộc các nhà phát triển viết và thực thi các tập lệnh SQL được mã hóa cứng không thực hiện công việc của mình. Bạn có thể, tất nhiên, làm như vậy vì lý do hiệu suất, nhưng điều này không phải là một yêu cầu. Đặc biệt là nếu có các chức năng được cung cấp bởi khung công tác được thiết kế để thực hiện công việc ngay từ đầu ... – hoekki

+0

Phần 2: Trong nghiên cứu của tôi về điều này, tôi đã tìm thấy các giải pháp khác nhau yêu cầu ví dụ: cơ sở dữ liệu được đặt thành chế độ người dùng đơn để loại bỏ các kết nối hiện có hoặc gọi ClearAllPools. Nhưng vấn đề thực sự không phải là xóa cơ sở dữ liệu, thay vì bằng việc tạo lại chúng. Việc không thể tạo ra một cơ sở dữ liệu không tồn tại (nữa) là điều khiến tôi bối rối. Như đã đề cập trước đó, tôi sử dụng EF không chỉ trên MsSQL mà còn trên Oracle và SqlCE (và trong cùng bối cảnh ứng dụng) ... – hoekki

+0

Phần 3: Vì vậy, tôi thực sự muốn sử dụng chức năng EF càng nhiều càng tốt và không phải quay trở lại tập lệnh SQL cho từng nhà cung cấp cơ sở dữ liệu, cho mỗi phiên bản, ... v.v. – hoekki

Các vấn đề liên quan