12

次の 2 つのクラスで EF5 と SQL Server 2012 を使用しています。

public class Question
{
    public Question()
    {
        this.Answers = new List<Answer>();
    }
    public int QuestionId { get; set; }
    public string Title { get; set; }
    public virtual ICollection<Answer> Answers { get; set; }

}
public class Answer
{
    public int AnswerId { get; set; }
    public string Text { get; set; }
    public int QuestionId { get; set; }
    public virtual Question Question { get; set; }
}

マッピングは次のとおりです。

public class AnswerMap : EntityTypeConfiguration<Answer>
{
    public AnswerMap()
    {
        // Primary Key
        this.HasKey(t => t.AnswerId);

        // Identity
        this.Property(t => t.AnswerId)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

データベース DDL

CREATE TABLE Answer (
    [AnswerId] INT IDENTITY (1, 1) NOT NULL,
    [QuestionId] INT NOT NULL,
    [Text] NVARCHAR(1000),
    CONSTRAINT [PK_Answer] PRIMARY KEY CLUSTERED ([AnswerId] ASC)
)";

ここに私が試した結果があります:

これは 1 人の子供に有効です。

var a = new Answer{
    Text = "AA",
    QuestionId = 14
};
question.Answers.Add(a);
_uow.Questions.Update(question);
_uow.Commit();

これは複数の子には機能しません:

エラー: 同じキーを持つオブジェクトが ObjectStateManager に既に存在します。ObjectStateManager は、同じキーを持つ複数のオブジェクトを追跡できません。

var a = new Answer{
    AnswerId = 0,
    Text = "AAA",
    QuestionId = 14
};
var b = new Answer {
    AnswerId = 0,
    Text = "BBB",
    QuestionId = 14
};
question.Answers.Add(a);
question.Answers.Add(b);
_uow.Questions.Update(question);
_uow.Commit();

これは複数の子には機能しません:

AnswerID の 1000 と 1001 を作成しますが、データベースによって新しい ID を作成したいと考えています。

var a = new Answer{
    AnswerId = 1000,
    Text = "AAA",
    QuestionId = 14
};
var b = new Answer {
    AnswerId = 1001,
    Text = "BBB",
    QuestionId = 14
};
question.Answers.Add(a);
question.Answers.Add(b);
_uow.Questions.Update(question);
_uow.Commit();

動作しません:

コンパイラ エラー。null を int に変換できません

var a = new Answer{
    AnswerId = null,
    Text = "AAA",
    QuestionId = 14    
};
var b = new Answer
{
    AnswerId = null,
    Text = "BBB",
    QuestionId = 14
};
question.Answers.Add(a);
question.Answers.Add(b);
_uow.Questions.Update(question);
_uow.Commit();

動作しません:

ObjectStateManager は、同じキーを持つ複数のオブジェクトを追跡できません。

var a = new Answer{
    Text = "AAA",
    QuestionId = 14
};
var b = new Answer
{
    Text = "BBB",
    QuestionId = 14
};
question.Answers.Add(a);
question.Answers.Add(b);
_uow.Questions.Update(question);
_uow.Commit();

私のアプリケーションでは、クライアントで生成された 1 つ以上の新しい Answer オブジェクトがあり、これらはサーバーに送信されます。上記では、クライアントを質問に追加せずに何が起こるかをシミュレートしています。Question オブジェクトへのすべての回答の追加はクライアントで行われ、JSON 文字列でサーバーに送信されることに注意してください。次に、次のように Question オブジェクトにデシリアライズされます。

public HttpResponseMessage PutQuestion(int id, Question question) {
            _uow.Questions.Update(question);
            _uow.Commit();

各 Answer オブジェクトを で作成し、new identity IDこれらを Question オブジェクトに追加して、Question オブジェクトを通常の方法で返すようにしたいと考えています。

どうすればこれができるのかわかりません。これまでのすべての簡単なテストは機能しません。これは、グループメンバーによる以前の質問のバリエーションであり、明確ではなく、私が締めくくろうとしていることに注意してください. この質問は、もっと明確になることを願っています。

ノート:

update をコーディングする方法は次のとおりです。

public virtual void Update(T entity)
{
    DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
    if (dbEntityEntry.State == EntityState.Detached)
    {
        DbSet.Attach(entity);
    }  
    dbEntityEntry.State = EntityState.Modified;
}
4

5 に答える 5

7

私も同じアイデンティティの「制限」に遭遇しました。親と子を追加すると、親と子がすべて一緒に追加されるという事実を EF が処理できることがわかります。親を更新して 2 つの子を同時に挿入すると、問題が発生します。親をアタッチすると、EF はそれらの 2 つの子を自動的に取得し、必要に応じてアタッチします。Id を自動生成したいので、子の主キーを設定しません。ただし、親が Update の場合、EF は同じ主キーを持つアイテムを処理できず、両方の子の PK が同じ 0 であるため、失敗します。

私が見つけた唯一の方法は、子の ID を手動で異なる番号に設定することです。通常、最初の子の Id を -1 に設定し、次に 2 番目の子の Id を -2 に設定します。-1 と -2 は有効な ID 値ではないため、これにより EF は子を保存し、データベースで実行されている ID によりキーが自動的に更新されます。

ただし、レベル3以上の場合、これは大きな苦痛を引き起こします。各子でこの PK を更新する必要があるだけでなく、その子のいずれかで FK をこの新しい -1 または -2 値に更新する必要があります。そうしないと、保存が再び失敗します。

私が見る唯一の他のオプションは、実際には一度に1つの子を挿入してsaveを呼び出して、コンテキストが同じPKを持つ複数のオブジェクトを処理しないようにすることですが、そのようなものはORMの目的を無効にします...

于 2014-02-20T22:08:18.223 に答える
1

2回追加してるって言ってたaけど…!?

question.Answers.Add(a);
question.Answers.Add(a);

通常、ID が ID であるアイテムを追加するには、ID の設定をスキップする必要があります。[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]また、これらの ID に属性を追加する必要があります。

public class Answer
{
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int AnswerId { get; set; }
    public string Text { get; set; }
    public int QuestionId { get; set; }
    public virtual Question Question { get; set; }
}

そして、次のようにデータを追加します。

var a = new Answer{
    Text = "AAA",
    QuestionId = 14
};

var b = new Answer
{
    Text = "BBB",
    QuestionId = 14
};

dbContext.Answers.Add(a);
dbContext.Answers.Add(b);

dbContext.SaveChanges();

// ...
于 2013-07-30T12:46:47.320 に答える
0

Id を Key として正しく宣言し、DBGenerated ID として宣言した場合。次に、EF を使用すると 、保存する前にこれらの多くをコンテキストに 追加できます。同じキーでアイテムを添付することはできません。アタッチは、オフライン データを対象とし、コンテキストに入れ、その状態を設定し、タイプのシナリオを保存します。

Same インスタンスを 2 回使用したか、デフォルトで EF 追跡を使用して混乱を引き起こしました。または、どういうわけか ATTACH を 2 回使用しました。インスタンスをきれいに処理していることを確認してください。*

例えば

public class BaseEntityLongConfiguration<T> : EntityTypeConfiguration<T> where T : BaseObjectLong {
    public BaseEntityLongConfiguration(DatabaseGeneratedOption DGO = DatabaseGeneratedOption.Identity) {

        // Primary Key
        this.HasKey(t => t.Id);

        // Properties
        //Id is an indent allocated by DB
        this.Property(t => t.Id).HasDatabaseGeneratedOption(DGO); // default to db generated

        this.Property(t => t.RowVersion)   // for concurrency
            .IsRequired()
            .IsFixedLength()
            .HasMaxLength(8)
            .IsRowVersion();
    }
}

単純なテストを試して、動作することを確認しました(ef5で)

public class ExampleLog  {
    public virtual long Id   { get; set; }
    public virtual string MessageText { get; set; }
}

[TestMethod]
    public void ExampleLogTest() {
        var e1 = new ExampleLog();
        e1.MessageText = "example1";
        var e2 = new ExampleLog();
        e2.MessageText = "example2";
        _context.Set<ExampleLog>().Add(e1);
        _context.Set<ExampleLog>().Add(e2);
     var res =   _context.SaveChanges();
      Debug.WriteLine("result expected 2->" + res.ToString());
    }

編集: 要求に応じて、保存リポジトリ パターンの追加、BAsic サンプル、エラー処理の削除

public class RepositoryBase<TPoco> : where TPoco :    BaseObject {
    public RepositoryBase(BosBaseDbContext context) { Context = context; }

....

   /// <summary>
    /// Add new POCO 
    /// </summary>
    public virtual OperationResult Add(TPoco poco) {
        var opResult = new OperationResult();
        try {
          Context.Set<TPoco>().Add(poco);
        }
        catch (Exception ex) {
         .... custom error tool
            return opResult;
        }
        return opResult;
    }
     /// <summary>
    /// Poco must already be attached,, detect chnages is triggered
    /// </summary>
    public virtual OperationResult Change(TPoco poco) {
        var opResult = new OperationResult();
        try {    // ONLY required if NOT using chnage tracking enabled
            Context.ChangeTracker.DetectChanges();
        }
        catch (Exception ex) {
         .... custom error tool
            return opResult;
        }
        return opResult;
    }
于 2013-07-30T12:49:06.573 に答える