source

엔티티 프레임워크가 ID 열을 삽입하도록 강제하려면 어떻게 해야 합니까?

nicesource 2023. 7. 8. 10:57
반응형

엔티티 프레임워크가 ID 열을 삽입하도록 강제하려면 어떻게 해야 합니까?

시드 데이터로 데이터베이스를 초기화하기 위해 C# 코드를 작성하고 싶습니다.이를 위해서는 삽입 시 다양한 Identity 열의 값을 설정할 수 있는 기능이 필요합니다.저는 코드 퍼스트 방식을 사용하고 있습니다.기적으로본,DbContext" " " " " " " " 을 사용할 수 .SET IDENTITY_INSERT [dbo].[MyTable] ON제가 까지 한 은 지제한지가일은금까그래를 입니다.DbContext사용할 DB 연결을 지정할 수 있는 생성자입니다. 다음, 저는 러면제가그로 했습니다.IDENTITY_INSERTONDB 연결에서 엔티티 프레임워크를 사용하여 내 레코드를 삽입합니다.여기 제가 지금까지 얻은 것의 예가 있습니다.

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() 아이덴티티_INSERT가 ON으로 설정되어 있거나 복제 사용자가 NOT FOR REPICATION ID 열에 삽입하는 경우 'MyUser' 테이블의 ID 열에 명시적 값을 지정해야 합니다."라는 메시지가 표시됩니다.

열가키이기 는 내가 MyUserId(MyUserId(MyUserIdentity)를 때 이 context.SaveChanges()비록 내가 주었지만,MyUser" 대값실을합니다화체에한"에 대한 합니다.MyUserId소유물.

그렇다면 엔티티 프레임워크가 엔티티의 주요 키 값까지 삽입하도록 강제하는 방법이 있습니까?아니면 일시적으로 표시를 하는 방법일 수도 있습니다.MyUserId기본 키 값이 아니기 때문에 EF가 삽입하려고 합니까?

msdn 문서를 사용하는 EF 6 방법:

using (var dataContext = new DataModelContainer())
using (var transaction = dataContext.Database.BeginTransaction())
{
    var user = new User()
    {
        ID = id,
        Name = "John"
    };

    dataContext.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[User] ON");

    dataContext.User.Add(user);
    dataContext.SaveChanges();

    dataContext.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[User] OFF");

    transaction.Commit();
}

업데이트: "IDENTITY_INSERT가 ON으로 설정되어 있거나 복제 사용자가 복제 ID 열에 삽입하는 경우 'TableName' 테이블의 ID 열에 명시적 값을 지정해야 합니다." 오류를 방지하려면 모델 디자이너에서 ID 열의 StoreGeneratedPattern 속성 값을 ID에서 없음으로 변경해야 합니다.

StoreGeneratedPattern을 None으로 변경하면 지정된 ID가 없는 개체 삽입이 실패합니다(일반적인 방법). "IDITY_INSERT가 OFF로 설정되어 있으면 테이블 'TableName'에 ID 열에 대한 명시적 값을 삽입할 수 없습니다." 오류가 발생합니다.

연결을 가지고 재미있는 사업을 할 필요는 없고, 중간자를 도려내고 그냥 사용하면 됩니다.

그런 다음 다음과 같은 작업을 통해 원하는 것을 달성할 수 있습니다.

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

하지만 나는 EF에게 아이덴티티 삽입을 설정하라고 말하는 내장된 방법을 알지 못합니다.

완벽하지는 않지만 현재의 접근 방식보다 더 유연하고 덜 "해킹"할 것입니다.

업데이트:

저는 방금 당신의 문제에 두 번째 부분이 있다는 것을 깨달았습니다.SQL에 ID 삽입을 수행하고 싶다고 말했으므로 EF는 해당 ID에 대한 값을 삽입하려고도 하지 않습니다(왜 그렇게 하겠습니까? 우리는 그렇게 하지 않았습니다).

코드 퍼스트 방식에 대한 경험은 없지만, 일부 빠른 검색을 통해 EF에 칼럼을 스토어에서 생성해서는 안 된다고 말해야 할 것 같습니다.당신은 이런 것을 해야 할 것입니다.

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

이것이 당신을 올바른 방향으로 이끌기를 바랍니다 :-)

파티에 조금 늦었지만, 누군가가 DB를 사용하여 EF5에서 이 문제를 먼저 마주칠 경우:두 가지 솔루션을 모두 사용할 수는 없었지만 다른 해결 방법을 찾았습니다.

에 실하전.SaveChanges()ID 했습니다.

Entities.Database.ExecuteSqlCommand(String.Format("DBCC CHECKIDENT ([TableNameHere], RESEED, {0})", newObject.Id-1););
Entities.YourTable.Add(newObject);
Entities.SaveChanges();

은 즉을 합니다..SaveChanges()매번 추가할 때마다 적용해야 하지만 적어도 작동합니다!

여기 문제의 해결책이 있습니다.저는 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 모델 생성에서 DB 생성 옵션을 제거합니다.

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

심사숙고 끝에, 나는 엔티티 프레임워크가 ID 열 삽입을 거부하는 것은 버그가 아니라 기능이라고 결정했습니다. :-) 만약 내가 그것들의 ID 값을 포함한 모든 항목을 데이터베이스에 삽입한다면, 나는 또한 엔티티 프레임워크가 나를 위해 자동으로 작성한 모든 링크 테이블에 대한 엔티티를 만들어야 할 것입니다.그것은 올바른 접근법이 아닙니다.

제가 은 C#엔티티를 에 EF 엔티티를 입니다.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);

이렇게 하면 스키마를 변경할 때 시드 데이터를 업데이트할 가능성이 높아집니다. 변경할 때 시드 코드가 깨질 수 있기 때문입니다(필드나 엔티티를 제거하면 해당 필드/엔티티를 사용하는 기존 시드 코드는 컴파일에 실패합니다).시드를 위한 SQL 스크립트의 경우 SQL 스크립트가 데이터베이스에 구애받지 않을 수도 마찬가지입니다.

따라서 만약 당신이 DB 시드 데이터를 수행하기 위한 엔티티의 ID 필드를 설정하려고 한다면, 당신은 분명히 잘못된 접근 방식을 취했다고 생각합니다.

실제로 SQL Server에서 PostgreSQL(일부 시드 데이터가 아닌 전체 활성 DB)로 대량의 데이터를 끌어다 놓으면 EF를 통해 이 작업을 수행할 수 있지만, 두 개의 컨텍스트를 동시에 열고 소스 컨텍스트에서 모든 다양한 엔티티를 가져와 대상 컨텍스트에 저장할 수 있는 코드를 작성해야 합니다.

일반적으로 ID 값을 삽입하는 것이 적절한 유일한 경우는 동일한 DBMS 내의 한 DB에서 다른 DB로 복사하는 경우(SQL Server -> SQL Server, PostgreSQL -> PostgreSQL 등)입니다. 그러면 EF 코드 우선이 아닌 SQL 스크립트로 이 작업을 수행합니다(SQL 스크립트는 DB에 구애받지 않습니다).하지만 그럴 필요는 없습니다. 서로 다른 DBMS 사이를 이동하는 것이 아닙니다.

이 아이디어는 대상 테이블이 비어 있거나 테이블의 모든 기존 ID보다 높은 ID로 레코드를 삽입하는 경우에만 안정적으로 작동합니다!

3년 후에도 프로덕션 데이터를 테스트 시스템으로 전송하는 데 비슷한 문제가 발생했습니다.사용자가 원할 때마다 프로덕션 데이터를 테스트 시스템에 복사할 수 있기를 원했기 때문에 SQL Server에서 전송 작업을 설정하는 대신 기존 EF 클래스를 사용하여 애플리케이션에서 전송을 수행할 수 있는 방법을 찾았습니다.이렇게 하면 사용자가 원할 때 언제든지 전송을 시작할 수 있는 메뉴 항목을 제공할 수 있습니다.

애플리케이션은 MS SQL 서버 2008 데이터베이스 및 EF 6을 사용합니다. 있기 때문에 저는 데터베일로동있가구일지때두조기고다사문음을한각인쉽데전생있수인다레스송다니습할했게각스터를턴이스로른고다서에턴스엔읽써코로티드티이를 하여 각 엔티티의 으로써 한 인스턴스로 할 수 했습니다.AsNoTracking()그리고 그냥Add()(또는)AddRange() 인스턴스의 대상 DbContext 인스턴스의 해당 속성에 대한 레코드입니다.

다음은 DbContext에 설명할 엔티티가 하나 있습니다.

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

사용자 데이터를 복사하기 위해 다음 작업을 수행했습니다.

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

테이블이 올바른 순서로 복사되는 한(외부 키 제약 조건의 문제를 방지하기 위해) 이것은 매우 잘 작동했습니다.안타깝게도 ID 열을 사용하는 테이블은 EF가 ID 값을 무시하고 SQL Server가 다음 ID 값을 삽입하도록 허용했기 때문에 상황을 약간 어렵게 만들었습니다.ID 열이 있는 테이블의 경우 다음 작업을 수행했습니다.

  1. 지정된 엔티티의 모든 레코드 읽기
  2. 레코드를 id별로 오름차순으로 정렬
  3. 테이블의 ID 시드를 첫 번째 ID 값으로 설정합니다.
  4. 다음 ID 값을 추적하여 레코드를 하나씩 추가합니다.ID가 예상되는 다음 ID 값과 동일하지 않으면 ID 시드를 다음 필수 값으로 설정합니다.

테이블이 비어 있고(또는 모든 새 레코드의 ID가 현재 hisgest ID보다 높음) ID가 오름차순인 한 EF 및 MS SQL은 필요한 ID를 삽입하고 두 시스템 모두 불만을 제기하지 않습니다.

다음은 설명할 코드입니다.

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

을 통해 ▁a를 쓸 수 있었습니다.Load a 리고a.SaveDbContext의 모든 엔티티에 대해 작동하는 메서드입니다.

약간의 해킹이지만 엔티티를 읽고 쓰는 데 표준 EF 방법을 사용할 수 있게 해주고 주어진 상황에서 ID 열을 특정 값으로 설정하는 방법에 대한 문제를 극복합니다.

저는 이것이 비슷한 문제에 직면한 다른 사람에게 도움이 되기를 바랍니다.

이 사이트에서 발견된 몇 가지 옵션을 실험한 결과, 다음 코드가 작동했습니다(EF 6).항목이 이미 있는 경우 먼저 일반 업데이트를 시도합니다.그렇지 않으면 일반 삽입을 시도하고 오류가 IDENTITY_INSERT로 인해 발생한 경우 해결 방법을 시도합니다.db도 주목하세요.변경 내용 저장이 실패하여 db가 됩니다.데이터베이스.연결.() 문 및 선택적 확인 단계를 엽니다.이것이 컨텍스트를 업데이트하는 것은 아니지만, 저의 경우에는 그럴 필요가 없습니다.이것이 도움이 되길 바랍니다!

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

저는 상속된 컨텍스트를 만들어 이 작업을 수행했습니다.

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

트랜잭션을 수행하기 전에 "일반" 컨텍스트 및 구성을 사용하여 마이그레이션을 확인하는 것이 좋습니다.

엔티티 프레임워크가 엔티티의 주요 키 값까지 삽입하도록 강제하는 방법이 있습니까?

네, 하지만 제가 보고 싶은 만큼 깨끗하진 않습니다.

사용자가 자동 생성된 ID 키를 사용한다고 가정하면 EF는 키 값을 저장하려는 사용자의 시도를 완전히 무시합니다.위에서 설명한 여러 가지 이유로 인해 이는 "설계상"으로 보이지만 시드 데이터(또는 초기 로드)를 완전히 제어하려는 경우가 있습니다.저는 EF가 향후 버전에서 이러한 종류의 시드를 제공할 것을 제안합니다.하지만 그들이 할 때까지는 프레임워크 내에서 작동하고 지저분한 세부사항을 자동화하는 작은 코드를 작성하기만 하면 됩니다.

벤더 ID가 EF에 의해 무시되더라도 기본 루프 및 카운트와 함께 사용하여 실시간 레코드 간에 추가할 플레이스홀더 레코드 수를 결정할 수 있습니다.플레이스홀더는 추가될 때 사용 가능한 다음 ID 번호가 할당됩니다.라이브 레코드에 요청된 ID가 있으면 정크를 삭제하기만 하면 됩니다.

    public class NewsprintInitializer: DropCreateDatabaseIfModelChanges<NewsprintContext>
    {
        protected override void Seed(NewsprintContext context)
        {
            var vendorSeed = new List<Vendor>
            {
                new Vendor { VendorID = 1, Name = "#1 Papier Masson / James McClaren" },
                new Vendor { VendorID = 5, Name = "#5 Abitibi-Price" },
                new Vendor { VendorID = 6, Name = "#6 Kruger Inc." },
                new Vendor { VendorID = 8, Name = "#8 Tembec" }
            };

            //  Add desired records AND Junk records for gaps in the IDs, because .VendorID is ignored on .Add
            int idx = 1;
            foreach (Vendor currentVendor in vendorSeed)
            {
                while (idx < currentVendor.VendorID)
                {
                    context.Vendors.Add(new Vendor { Name = "**Junk**" });
                    context.SaveChanges();
                    idx++;
                }
                context.Vendors.Add(currentVendor);
                context.SaveChanges();
                idx++;
            }
            //  Cleanup (Query/Find and Remove/delete) the Junk records
            foreach (Vendor del in context.Vendors.Where(v => v.Name == "**Junk**"))
            {
                context.Vendors.Remove(del);
            }
            context.SaveChanges();

            // setup for other classes

        }
    }

ID를 정리하기 위해 "Save Changes"를 자주 해야 한다는 점을 제외하고는 예상대로 작동했습니다.

저는 DBA일 뿐이지만, 이런 일이 생길 때마다 코드 냄새라고 생각합니다.즉, 특정 ID 값을 갖는 특정 행에 의존하는 항목이 있는 이유는 무엇입니까?즉, 위의 예에서 Novelette 부인은 왜 106의 정체성 값이 필요합니까?항상 그런 경우에만 의존하는 것이 아니라, 그녀의 신원 가치를 얻고 106을 하드 코딩한 곳에서 사용할 수 있습니다.좀 더 번거롭지만 훨씬 더 유연합니다.

이것은 효과가 있었습니다(현재 저는 EFCore 3.x를 사용하고 있으며, 이후 버전에서는 확실히 유효합니다).를 사용하여호출을입의 .true set (즉, 합)IDENTITY_INSERTON든지.SaveAsync로 다시 .false완료되면(필요하지 않을 수 있음).은 핵은심입니다.OpenConnection이 일을 할 수 있도록 준비해야 했습니다.한 컨텍스트에 여러 번 저장할 경우, 제 경험상 처음에 한 번만 호출하면 되지만, 이를 여러 번 true로 호출해도 문제가 되지 않았습니다.

public void Set_IDENTITY_INSERT(bool turnOn, DbContext context, string tableName)
{
    var db = context.Database;

    string command = $"SET IDENTITY_INSERT {tableName} {(turnOn ? "ON" : "OFF")}";

    if(turnOn)
        db.OpenConnection();

    db.ExecuteSqlCommand(command);

    if(!turnOn)
        db.CloseConnection();
}

efcore 6 이전

await _dbCtx.Database.ExecuteSqlRawAsync($"SET IDENTITY_INSERT [table_name] ON");

_dbCtx.Set<TableName>().Add(new TableName { Id = 1, Name = "qwe"});

await _dbCtx.SaveChangesAsync()

await _dbCtx.Database.ExecuteSqlRawAsync($"SET IDENTITY_INSERT [table_name] OFF");

에코코어 7 +

당신은 그것을 거래에서 포장해야 합니다.(Ef 7은 성능을 향상시키기 위해 'SET IMPLICAL_TRANSACTS OFF'를 추가하므로 이전 명령을 방해합니다.)

using var transaction = _dbCtx.Database.BeginTransaction();

await _dbCtx.Database.ExecuteSqlRawAsync($"SET IDENTITY_INSERT [table_name] ON");

_dbCtx.Set<TableName>().Add(new TableName { Id = 1, Name = "qwe"});

await _dbCtx.SaveChangesAsync()

await _dbCtx.Database.ExecuteSqlRawAsync($"SET IDENTITY_INSERT [table_name] OFF");

await transaction.CommitAsync();

EF6 코드 퍼스트에 대한 나의 솔루션.다른 열을 채울 필요가 없는 표에 따라 다릅니다.

  db.Database.ExecuteSqlCommand($"SET IDENTITY_INSERT [**table**] ON; insert into [**table**] ([ID]) values (@id)", new SqlParameter("id", entity.ID));

  db.Entry(entity).State = EntityState.Modified;

  await db.SaveChangesAsync();

첫 번째 줄은 테이블에 원하는 ID의 빈 행을 만듭니다.두 번째 줄은 모델을 더티로 표시합니다. 그렇지 않으면 변경 사항이 감지되지 않습니다.세 번째 줄은 변경사항을 저장합니다.

이 솔루션은 완벽하지 않지만 다음과 같은 문제를 해결합니다.NOT FOR REPLICATIONCode First에 대한 오류입니다.

16/08/22 업데이트

'제한된' 솔루션에 대한 저의 추론을 설명하기 위해, 그리고 왜 제가 그것을 여기에 게시할 가치가 있다고 생각했는지 설명하기 위해.

우리의 요구 사항은 유지보수 스크립트였기 때문에 모델이나 컨텍스트 클래스에 영구적인 영향을 주고 싶지 않았습니다.우리의 정체성 칼럼은 이유가 있습니다.

다행히도 (아마도) 테이블에 기본 키 외에 필요한 열이 없다는 것을 이미 알고 있는 위치에 있었습니다.지적했듯이 항상 그런 것은 아니지만 이번에는 그랬습니다.

걱정해야 할 추가 열이 있었다면 SQL 스크립트에 추가하는 것을 꺼렸을 것입니다.테이블 질문이 거의 바뀌지 않더라도 여전히 코드 냄새가 분명합니다.

그러나 모든 것을 고려할 때, 이것은 트레이드 오프입니다(다른 모든 답변 참조). 만약 당신이 이것을 읽고 있고 추가 열을 다루는 방법을 알고 있다면.

저의 첫 번째 선택은 저의 주요 맥락 수업을 계승하고, 거기서 정체성 열을 해체하는 것을 보는 것입니다.초기 솔루션이 매우 잘 작동하기 때문에 즉각적인 요구사항에 대한 과잉 살상이 가능합니다.

위에서 언급했듯이, 제 두 번째 선택은 SQL 스크립트에 적절한 기본값으로 열을 추가하는 것입니다.그런 다음 SQL이 동기화되도록 수동 프로세스를 제안합니다.

테이블에 레코드를 삽입할 방법을 찾을 수 없었습니다.기본적으로 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());
        }

저는 EF 6를 사용하고 있습니다.

언급URL : https://stackoverflow.com/questions/13086006/how-can-i-force-entity-framework-to-insert-identity-columns

반응형