2012-04-19 71 views
2

我一直在開發一個數據庫的框架,該框架將支持EF 4.3.1中使用Code First的數據版本控制。Code First導航屬性被保留但未加載

幾天前,我有模型持久和加載正確,但我打破了一些事情,因爲我無法弄清楚什麼是錯的。所有類都被映射並創建表,數據也被保存。所以一切順利,一切正常!但是,當我嘗試加載一個Registration實體時,這些值都是默認構造函數設置它們的值。我想也許在調用Registration構造函數之後數據沒有被加載,但是我正在結束當前的能力來弄清楚發生了什麼!

基本面這兩個類,從我的優化版本,能夠類派生...派生類的

public abstract class VersionBase<T> { 
    [Key] 
    public Int64 Id { get; protected set; } 
    public DateTime CreationDateTime { get; protected set; } 

    // Value is virtual to support overriding to let deriving classes specify attributes for the property, such as [Required] to specify a non-nullable System.String 
    public virtual T Value { get; internal set; } 

    protected VersionBase() { 
     CreationDateTime = DateTime.Now; 
    } 

    protected VersionBase(T value) 
     : this() { 
     Value = value; 
    } 
} 

public abstract class VersionedBase<TVersion, TBase> 
    where TVersion : VersionBase<TBase>, new() { 
    [Key] 
    public Int64 Id { get; protected set; } 
    public virtual ICollection<TVersion> Versions { get; protected set; } 

    protected VersionedBase() { 
     Versions = new List<TVersion>(); 
    } 

    [NotMapped] 
    public Boolean HasValue { 
     get { 
      return Versions.Any(); 
     } 
    } 

    [NotMapped] 
    public TBase Value { 
     get { 
      if (HasValue) 
       return Versions.OrderByDescending(x => x.CreationDateTime).First().Value; 
      throw new InvalidOperationException(this.GetType().Name + " has no value"); 
     } 
     set { 
      Versions.Add(new TVersion { Value = value }); 
     } 
    } 
} 

例子...

public class VersionedInt32 : VersionedBase<VersionedInt32Version, Int32> { } 

public class VersionedInt32Version : VersionBase<Int32> { 
    public VersionedInt32Version() : base() { } 
    public VersionedInt32Version(Int32 value) : base(value) { } 
    public static implicit operator VersionedInt32Version(Int32 value) { 
     return new VersionedInt32Version { Value = value }; 
    } 
} 

......還有......

public class VersionedString : VersionedBase<VersionedStringVersion, String> { } 

public class VersionedStringVersion : VersionBase<String> { 
    public VersionedStringVersion() : base() { } 
    public VersionedStringVersion(String value) : base(value) { } 
    public static implicit operator VersionedStringVersion(String value) { 
     return new VersionedStringVersion { Value = value }; 
    } 

    /// <summary> 
    /// The [Required] attribute tells Entity Framework that we want this column to be non-nullable 
    /// </summary> 
    [Required] 
    public override String Value { get; internal set; } 
} 

我的調用代碼是這樣...

static void Main(String[] args) { 
    using (var db = new VersionedFieldsContext()) { 
     Registration registration = new Registration(); 
     registration.FirstName.Value = "Test"; 
     registration.FirstName.Versions.Add("Derp"); 
     db.Registration.Add(registration); 
     db.SaveChanges(); 
    } 
    using (var db = new VersionedFieldsContext()) { 
     Registration registration = db.Registration.First(); 
     // InvalidOperationException at next line: "VersionedString has no value" 
     String asdf = registration.FirstName.Value; 
    } 
} 

public class Registration { 
    [Key] 
    public Int64 Id { get; set; } 
    public DateTime CreationDateTime { get; set; } 
    public VersionedString FirstName { get; set; } 

    public Registration() { 
     CreationDateTime = DateTime.Now; 
     FirstName = new VersionedString(); 
    } 
} 

public class VersionedFieldsContext : DbContext { 
    public DbSet<Registration> Registration { get; set; } 

    public VersionedFieldsContext() { 
     Database.SetInitializer<VersionedFieldsContext>(new DropCreateDatabaseIfModelChanges<VersionedFieldsContext>()); 
    } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) { 
     base.OnModelCreating(modelBuilder); 
     modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); 
    } 
} 

感謝您的任何見解!

+1

與問題沒有直接關係,但我很好奇......這是非常聰明的代碼,但是您不擔心在加載/加載數據庫時會遇到的連接數/往返數的性能影響最簡單的事情?想象一下你想要加載一個帶有5個字符串(你的類型爲'VersionedString')的實體:sql必須通過兩個表連接5次才能得到字符串。而且你必須指定一個巨大的'Include'鏈或者將有多個延遲加載循環,並且在過濾掉內存中的最新內容之前始終加載所有版本的集合。 – Slauma 2012-04-19 17:42:39

+0

我還沒有想太多,但因爲你問我一直在思考一些方法來提高性能。 – 2012-04-19 18:20:27

+0

我的第一個想法是在'VersionedBase '中創建'Value'屬性,該屬性保存最近的值以及代表當前值的'CreationDateTime'的'UpdateDateTime'列。這樣你只需'.Include()'Versioned [Type]'屬性來有效地獲取最新值。問題在於'Versions'屬性不能正確表示數據的版本。 – 2012-04-19 18:26:48

回答

2

需要兩個變化:從Registration構造FirstName

  • 刪除實例,這樣的構造是唯一的:

    public Registration() { 
        CreationDateTime = DateTime.Now; 
    } 
    

    創建的導航參考一個實例(不收藏)導致已知問題:What would cause the Entity Framework to save an unloaded (but lazy loadable) reference over existing data?

  • 如果您已修復第一個問題,則您的自定義例外更改爲NullReferenceException。爲了解決這個問題使FirstName財產Registrationvirtual,因爲在你的第二個using塊中的代碼需要延遲加載:

    public virtual VersionedString FirstName { get; set; } 
    

編輯

一種解決方法創建一個註冊和自動實例化FirstName可能是工廠方法:

public class Registration { 
    [Key] 
    public Int64 Id { get; set; } 
    public DateTime CreationDateTime { get; set; } 
    public VersionedString FirstName { get; set; } 

    public Registration() { 
     CreationDateTime = DateTime.Now; 
    } 

    public static Registration Create() { 
     return new Registration { 
      FirstName = new VersionedString() 
     } 
    } 
} 

EF在實現對象Registration時使用默認構造函數。在您的自定義代碼時,你需要創建的Registration一個實例,你可以使用工廠方法:

var registration = Registration.Create(); 

這一點就不太有用的,當你更改跟蹤或延遲加載代理工作,並希望創建一個代理實例手動:

var registration = db.Registration.Create(); 

這再次將調用默認的構造函數,你必須實例化FirstName已創建對象之後。

+0

謝謝!我從來沒有想過這件事。 – 2012-04-19 16:46:58

+0

我希望自動初始化'VersionedString',是否有解決方法來實現? – 2012-04-19 16:48:17

+0

爲什麼我的第二個'using'塊中的代碼需要延遲加載? – 2012-04-19 17:13:45