2

私は、EF4.3.1のCodeFirstを使用したデータのバージョン管理をサポートするデータベースのスケルトンを開発してきました。

数日前にモデルを永続化して適切にロードしていましたが、それ以来何かが壊れており、何が問題なのか理解できません。すべてのクラスがマップされ、テーブルが作成され、データも保持されます。したがって、ストーリング方向では、すべてが正常に機能します。しかし、エンティティを読み込もうとするとRegistration、値はすべてデフォルトのコンストラクターが設定している値になります。コンストラクターが呼び出された後、データがロードされていない可能性があると思いますがRegistration、現在の能力の終わりで、何が起こっているのかを理解できません。

基本はこれらの2つのクラスであり、そこから私のバージョン可能なクラスが派生します...

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>();
    }
}

洞察をありがとう!

4

1 に答える 1

2

2つの変更が必要です。

  • FirstNameコンストラクターからのインスタンス化を削除Registrationして、コンストラクターが次のようになるようにします。

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

    エンティティのデフォルトコンストラクタでナビゲーション参照(コレクションではない)のインスタンスを作成すると、既知の問題が発生します。EntityFrameworkが、アンロードされた(ただし遅延ロード可能な)参照を既存のデータに保存する原因は何ですか?

  • 最初のポイントを修正した場合、カスタム例外はに変更されますNullReferenceException。これを修正するには、2番目のブロックのコードに遅延読み込みが必要なため、FirstNameプロパティを作成します。Registration virtualusing

    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れ、オブジェクトが既に作成された後にインスタンス化する必要があります。

于 2012-04-19T16:33:11.540 に答える