70

シードデータを使用してデータベースを初期化するためのC#コードを記述したいと思います。明らかに、これには、挿入時にさまざまなID列の値を設定できる機能が必要になります。私はコードファーストのアプローチを使用しています。デフォルトでDbContextは、データベース接続を処理するため、処理できませんSET IDENTITY_INSERT [dbo].[MyTable] ON。したがって、これまでに行ったことは、使用DbContextするDB接続を指定できるコンストラクターを使用することです。次に、そのDB接続に設定IDENTITY_INSERTし、エンティティフレームワークを使用してレコードを挿入しようとします。ONこれが私がこれまでに得たものの例です:

public class MyUserSeeder : IEntitySeeder {
    public void InitializeEntities(AssessmentSystemContext context, SqlConnection connection) {
        context.MyUsers.Add(new MyUser { MyUserId = 106, ConceptPersonId = 520476, Salutation = "Mrs", Firstname = "Novelette", Surname = "Aldred", Email = null, LoginId = "520476", Password="28c923d21b68fdf129b46de949b9f7e0d03f6ced8e9404066f4f3a75e115147489c9f68195c2128e320ca9018cd711df", IsEnabled = true, SpecialRequirements = null });
        try {
            connection.Open();
            SqlCommand cmd = new SqlCommand("SET IDENTITY_INSERT [dbo].[MyUser] ON", connection);
            int retVal = cmd.ExecuteNonQuery();
            context.SaveChanges();
        }
        finally {
            connection.Close();
        }
    }
}

非常に近く、まだこれまでのところ-正常に動作しますが、cmd.ExecuteNonQuery()実行するcontext.SaveChanges()と、「IDENTITY_INSERTがONに設定されている場合、またはレプリケーションユーザーがNOTFORREPLICATIONID列に挿入します。」

おそらく、MyUserId(MyUserテーブルのIdentity列)が主キーであるため、エンティティにプロパティの値を指定しcontext.SaveChanges()ても、エンティティフレームワークは呼び出し時にそれを設定しようとしません。MyUserMyUserId

では、エンティティフレームワークにエンティティの主キー値を挿入しようとする方法はありますか?または、主キー値ではないことを一時的にマークする方法がMyUserIdあるので、EFはそれを挿入しようとしますか?

4

11 に答える 11

20

接続で面白いビジネスを行う必要はありません。仲介者を切り取って使用するだけObjectContext.ExecuteStoreCommandです。

次に、これを行うことで目的を達成できます。

context.ExecuteStoreCommand("SET IDENTITY_INSERT [dbo].[MyUser] ON");

ただし、EFにID挿入を設定するように指示する組み込みの方法は知りません。

完璧ではありませんが、現在のアプローチよりも柔軟で「ハッキー」ではありません。

アップデート:

あなたの問題には2番目の部分があることに気づきました。ID の挿入を行うことを SQL に伝えたので、EF はその ID の値を挿入しようとさえしません (なぜそうするのでしょうか? まだ指示していません)。

コードファーストのアプローチの経験はありませんが、いくつかの簡単な検索から、ストアから列を生成してはならないことを EF に伝える必要があるようです。このようなことをする必要があります。

Property(obj => obj.MyUserId)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)
    .HasColumnName("MyUserId");

うまくいけば、これが正しい方向に向けられるでしょう:-)

于 2012-10-26T11:41:28.810 に答える
7

これが問題の解決策です。私はEF6で試してみましたが、うまくいきました。以下は、動作するはずの疑似コードです。

まず、デフォルトの dbcontext のオーバーロードを作成する必要があります。基本クラスを確認すると、既存の dbConnection を渡すクラスが見つかります。次のコードを確認してください-

public MyDbContext(DbConnection existingConnection, bool contextOwnsConnection)
        : base(existingConnection, contextOwnsConnection = true)
    {
        //optional
        this.Configuration.ProxyCreationEnabled = true;
        this.Configuration.LazyLoadingEnabled = true;
        this.Database.CommandTimeout = 360;
    }

そして、On modelcreating で、db generated オプションを次のように削除します。

protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<MyTable>()
            .Property(a => a.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

        base.OnModelCreating(modelBuilder);
    }

コードでは、接続オブジェクトを明示的に渡す必要があります。

using (var connection = new System.Data.SqlClient.SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionStringName"].ConnectionString))
        {
            connection.Open();
            using (var context = new MyDbContext(connection, true))
            {
                context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[MyTable] ON");
                context.MyTable.AddRange(objectList);
                context.SaveChanges();
                context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[MyTable] OFF");
            }

            connection.Close();
        }
于 2017-01-16T11:25:51.040 に答える
4

This idea only works reliably if the target table is empty, or records are being inserted with ids higher than all already existing ids in the table!

3 years on and I hit a similar problem transferring production data into a test system. The users wanted to be able to copy the production data into the test system whenever they wanted to, so instead of setting up a transfer job in SQL Server I looked for a way to accomplish the transfer in the application using the existing EF classes. This way I could provide the users a menu item to start the transfer whenever they wanted.

The application uses a MS SQL Server 2008 database and EF 6. As the two databases generally have the same structure I thought I could easily transfer data from one DbContext instance to another by reading the records of each entity using AsNoTracking() and just Add() (or AddRange()) the records to the appropriate property on the target DbContext instance.

Here is a DbContext with one entity to illustrate:

public class MyDataContext: DbContext
{
    public virtual DbSet<Person> People { get; set; }
}

To copy the People data I did the following:

private void CopyPeople()
{
    var records = _sourceContext.People.AsNoTracking().ToArray();
    _targetContext.People.AddRange(records);
    _targetContext.SaveChanges();
}

As long as the tables were copied in the right order (to avoid problems with foreign key constraints) this worked very well. Unfortunately tables using identity columns made things a little difficult, as EF ignored the id values and just let SQL Server insert the next identity value. For tables with identity columns I ended up doing the following:

  1. Read all the records of a given entity
  2. Order the records by id in ascending order
  3. set the identity seed for the table to the value of the first id
  4. keeping track of the next identity value, add the records one by one. If the id is not the same as the expected next identity value set the identity seed to the next required value

As long as the table is empty (or all the new records have ids higher that current hisghest id), and the ids are in ascending order, EF and MS SQL will insert the required ids and neither system will complain.

Here is a bit of code to illustrate:

private void InsertRecords(Person[] people)
{
    // setup expected id - presumption: empty table therefore 1
    int expectedId = 1;

    // now add all people in order of ascending id
    foreach(var person in people.OrderBy(p => p.PersonId))
    {
        // if the current person doesn't have the expected next id
        // we need to reseed the identity column of the table
        if (person.PersonId != expectedId)
        {
            // we need to save changes before changing the seed value
            _targetContext.SaveChanges();

            // change identity seed: set to one less than id
            //(SQL Server increments current value and inserts that)
            _targetContext.Database.ExecuteSqlCommand(
                String.Format("DBCC CHECKIDENT([Person], RESEED, {0}", person.PersonId - 1)
            );

            // update the expected id to the new value
            expectedId = person.PersonId;
        }

        // now add the person
        _targetContext.People.Add(person);

        // bump up the expectedId to the next value
        // Assumption: increment interval is 1
        expectedId++;
    }

    // now save any pending changes
    _targetContext.SaveChanges();
}

Using reflection I was able to write a Load and a Save method that worked for all the entities in the DbContext.

It's a bit of a hack, but it allows me to use the standard EF methods for reading and writing entities and overcomes the problem of how to set identity columns to particular values under a set of given circumstances.

I hope this will be of help to someone else faced with a similar problem.

于 2015-06-04T16:27:40.640 に答える
3

慎重に検討した結果、エンティティ フレームワークが ID 列の挿入を拒否しているのはバグではなく機能であると判断しました。:-) ID 値を含むすべてのエントリをデータベースに挿入する場合、エンティティ フレームワークが自動的に作成したすべてのリンク テーブルのエンティティも作成する必要があります。それは正しいアプローチではありません。

私がやっていることは、C# コードを使用して EF エンティティを作成するだけのシード クラスを設定し、次に a を使用しDbContextて新しく作成されたデータを保存することです。ダンプされた SQL を取得して C# コードに変換するには、もう少し時間がかかりますが、データを "シード" するためだけにデータが多すぎることはありません (そうあるべきではありません)。デバッグ/開発目的で新しい DB にすばやく配置できる、ライブ DB にあるような種類のデータ。これは、エンティティを一緒にリンクしたい場合、既に挿入されているものに対してクエリを実行する必要があることを意味します。そうしないと、コードが生成された ID 値を認識しません。context.SaveChangesこの種のことは、次のように設定して実行した後、シード コード内に表示されますMyRoles

var roleBasic = context.MyRoles.Where(rl => rl.Name == "Basic").First();
var roleAdmin = context.MyRoles.Where(rl => rl.Name == "Admin").First();
var roleContentAuthor = context.MyRoles.Where(rl => rl.Name == "ContentAuthor").First();

MyUser thisUser = context.MyUsers.Add(new MyUser {
    Salutation = "Mrs", Firstname = "Novelette", Surname = "Aldred", Email = null, LoginUsername = "naldred", Password="c1c966821b68fdf129c46de949b9f7e0d03f6cad8ea404066f4f3a75e11514748ac9f68695c2128e520ca0275cd711df", IsEnabled = true, SpecialRequirements = null
});
thisUser.Roles.Add(roleBasic);

このようにすると、スキーマを変更したときにシード データを更新する可能性が高くなります。これは、変更するとシード コードが壊れる可能性が高いためです (フィールドまたはエンティティを削除すると、そのフィールドを使用する既存のシード コード/entity はコンパイルに失敗します)。シード処理を行うための SQL スクリプトでは、そうはなりません。また、SQL スクリプトがデータベースに依存しないこともありません。

したがって、DB シード データを実行するためにエンティティの ID フィールドを設定しようとしている場合は、間違いなく間違ったアプローチを取っていると思います。

たとえば SQL Server から PostgreSQL (一部のシード データだけでなく、完全なライブ DB) に大量のデータを実際にドラッグする場合、EF を介してそれを行うことができますが、同時に 2 つのコンテキストを開く必要があります。ソースコンテキストからさまざまなエンティティをすべて取得して宛先コンテキストに配置するコードを記述し、変更を保存します。

一般に、ID 値を挿入するのが適切な場合は、同じ DBMS 内で 1 つの DB から別の DB にコピーする場合 (SQL Server -> SQL Server、PostgreSQL -> PostgreSQL など) だけです。 EFコードファーストではなく、SQLスクリプトでそれを実行します(SQLスクリプトはDBに依存しませんが、そうである必要はありません。異なるDBMS間を移動することはありません)。

于 2012-10-27T16:01:26.177 に答える
1

このサイトで見つかったいくつかのオプションを試した後、次のコードが機能しました ( EF 6 )。アイテムが既に存在する場合は、最初に通常の更新を試みることに注意してください。そうでない場合は、通常の挿入を試みます。エラーが IDENTITY_INSERT によるものである場合は、回避策を試みます。db.SaveChanges が失敗することにも注意してください。したがって、db.Database.Connection.Open() ステートメントとオプションの検証手順が必要です。これはコンテキストを更新していないことに注意してください。ただし、私の場合は必要ありません。お役に立てれば!

public static bool UpdateLeadTime(int ltId, int ltDays)
{
    try
    {
        using (var db = new LeadTimeContext())
        {
            var result = db.LeadTimes.SingleOrDefault(l => l.LeadTimeId == ltId);

            if (result != null)
            {
                result.LeadTimeDays = ltDays;
                db.SaveChanges();
                logger.Info("Updated ltId: {0} with ltDays: {1}.", ltId, ltDays);
            }
            else
            {
                LeadTime leadtime = new LeadTime();
                leadtime.LeadTimeId = ltId;
                leadtime.LeadTimeDays = ltDays;

                try
                {
                    db.LeadTimes.Add(leadtime);
                    db.SaveChanges();
                    logger.Info("Inserted ltId: {0} with ltDays: {1}.", ltId, ltDays);
                }
                catch (Exception ex)
                {
                    logger.Warn("Error captured in UpdateLeadTime({0},{1}) was caught: {2}.", ltId, ltDays, ex.Message);
                    logger.Warn("Inner exception message: {0}", ex.InnerException.InnerException.Message);
                    if (ex.InnerException.InnerException.Message.Contains("IDENTITY_INSERT"))
                    {
                        logger.Warn("Attempting workaround...");
                        try
                        {
                            db.Database.Connection.Open();  // required to update database without db.SaveChanges()
                            db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT[dbo].[LeadTime] ON");
                            db.Database.ExecuteSqlCommand(
                                String.Format("INSERT INTO[dbo].[LeadTime]([LeadTimeId],[LeadTimeDays]) VALUES({0},{1})", ltId, ltDays)
                                );
                            db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT[dbo].[LeadTime] OFF");
                            logger.Info("Inserted ltId: {0} with ltDays: {1}.", ltId, ltDays);
                            // No need to save changes, the database has been updated.
                            //db.SaveChanges(); <-- causes error

                        }
                        catch (Exception ex1)
                        {
                            logger.Warn("Error captured in UpdateLeadTime({0},{1}) was caught: {2}.", ltId, ltDays, ex1.Message);
                            logger.Warn("Inner exception message: {0}", ex1.InnerException.InnerException.Message);
                        }
                        finally
                        {
                            db.Database.Connection.Close();
                            //Verification
                            if (ReadLeadTime(ltId) == ltDays)
                            {
                                logger.Info("Insertion verified. Workaround succeeded.");
                            }
                            else
                            {
                                logger.Info("Error!: Insert not verified. Workaround failed.");
                            }
                        }
                    }
                }
            }
        }
    }
    catch (Exception ex)
    {
        logger.Warn("Error in UpdateLeadTime({0},{1}) was caught: {2}.", ltId.ToString(), ltDays.ToString(), ex.Message);
        logger.Warn("Inner exception message: {0}", ex.InnerException.InnerException.Message);
        Console.WriteLine(ex.Message);
        return false;
    }
    return true;
}
于 2016-07-29T16:16:29.017 に答える
0

私はただの DBA ですが、このような問題が発生するたびに、コードの臭いだと思います。つまり、特定の ID 値を持つ特定の行に依存するものがあるのはなぜでしょうか? つまり、上記の例で、ノヴェレット夫人が ID 値 106 を必要とするのはなぜですか? 常にそうであることに依存するのではなく、彼女の ID 値を取得して、106 をハードコードした場所ならどこでもそれを使用できます。少し面倒ですが、より柔軟です (私の意見では)。

于 2012-10-26T12:59:14.213 に答える
0

継承されたコンテキストを作成することで、この作業を行いました。

EF 移行に関する私の通常のコンテキスト:

public class MyContext : DbContext
{
    public MyContext() : base("name=MyConnexionString")
    {...}

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // best way to know the table names from classes... 
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        ...
    }
}

ID をオーバーライドするために使用される別のコンテキスト。

このコンテキストを EF 移行に登録しないでください (別のデータベースからデータを転送するために使用します)。

public class MyContextForTransfers : MyContext
{
    public MyContextForTransfers() : base()
    {
        // Basically tells the context to take the database as it is...
        Database.SetInitializer<MyContextForTransfers >(null);
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
         // Tells the Context to include Isd in inserts
         modelBuilder.Conventions.Remove<StoreGeneratedIdentityKeyConvention>();
         base.OnModelCreating(modelBuilder);
    }
}

挿入方法 (エラー管理は非常に単純化されています...):

public void Insert<D>(iEnumerable<D> items)
{
    using (var destinationDb = new MyContextForTransfers())
    {
        using (var transaction = destinationDb.Database.BeginTransaction())
        {
            try
            {
                destinationDb.Database.ExecuteSqlCommand($"SET IDENTITY_INSERT [dbo].[{typeof(D).Name}] ON");
                destinationDb.Set<D>().AddRange(items);
                destinationDb.SaveChanges();
                destinationDb.Database.ExecuteSqlCommand($"SET IDENTITY_INSERT [dbo].[{typeof(D).Name}] OFF");
                transaction.Commit();
             }
             catch
             {
                transaction.Rollback();
             }
         }
    }
}

「通常の」コンテキストと構成を使用して、トランザクションの前に移行を確認することをお勧めします。

于 2020-07-28T13:53:49.197 に答える
-2

テーブルにレコードを挿入する方法が見つかりませんでした。基本的に、私はこのようなものでSQLスクリプトを作成しました...

            sb.Append("SET IDENTITY_INSERT [dbo].[tblCustomer] ON;");

            foreach(...)
            {
                var insert = string.Format("INSERT INTO [dbo].[tblCustomer]
                     ([ID],[GivenName],[FamilyName],[NINumber],[CustomerIdent],
                      [InputterID],[CompanyId],[Discriminator]) 
                      VALUES({0}, '{1}', '{2}', '{3}', '{4}', 2, 2, 'tblCustomer'); ", 
                          customerId, firstName, surname, nINumber, Guid.NewGuid());

            sb.Append(insert);
                ...
            }

            sb.Append("SET IDENTITY_INSERT [dbo].[tblCustomer] OFF;");
            using (var sqlConnection = new SqlConnection(connectionString))
            {
                var svrConnection = new ServerConnection(sqlConnection);
                var server = new Server(svrConnection);
                server.ConnectionContext.ExecuteNonQuery(sb.ToString());
        }

EF6を使用しています。

于 2016-04-07T13:35:41.077 に答える