2012-04-19 67 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