Подтвердить что ты не робот

Как получить EF 6 для обработки DEFAULT CONSTRAINT в базе данных во время INSERT

Я новичок в EF (моя первая неделя), но не новичок в базах данных или программировании. Другие задавали аналогичные вопросы, но я не чувствую, что его спрашивают с правильной детализацией или объясняют так, как это нужно объяснять, поэтому я иду.

Вопрос: Как я могу заставить Entity Framework правильно обрабатывать столбцы в базе данных, которые имеют DEFAULT CONSTRAINT, определенные при выполнении INSERT? Смысл, если я не поставлю значение в моей модели во время операции вставки, как мне получить EF, чтобы исключить этот столбец из его сгенерированной команды TSQL INSERT, чтобы работать с DEFAULT CONSTRAINT, определенным в базе данных?

Фон

У меня есть простая таблица, которую я создал, просто для проверки Entity Framework 6 (EF6) и ее взаимодействия с столбцами, которые SQL Server способен обновлять. Это использует IDENTITY, TIMESTAMP, COMPUTED и несколько столбцов с примененным ПО DEFAULT CONSTRAINT.

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[DBUpdateTest](
    [RowID] [int] IDENTITY(200,1) NOT NULL,
    [UserValue] [int] NOT NULL,
    [DefValue1] [int] NOT NULL,
    [DefValue2null] [int] NULL,
    [DefSecond] [int] NOT NULL,
    [CalcValue]  AS 
        (((([rowid]+[uservalue])+[defvalue1])+[defvalue2null])*[defsecond]),
    [RowTimestamp] [timestamp] NULL,
    CONSTRAINT [PK_DBUpdateTest] PRIMARY KEY CLUSTERED 
    (
        [RowID] ASC
    )
    WITH 
    (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF,
    ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) 
) 
GO
ALTER TABLE [dbo].[DBUpdateTest] 
ADD CONSTRAINT [DF_DBUpdateTest_DefValue1]      
DEFAULT ((200)) FOR [DefValue1]
GO
ALTER TABLE [dbo].[DBUpdateTest] 
ADD CONSTRAINT [DF_DBUpdateTest_DefValue2null]  
DEFAULT ((30)) FOR [DefValue2null]
GO
ALTER TABLE [dbo].[DBUpdateTest] 
ADD  CONSTRAINT [DF_DBUpdateTest_DefSecond]  
DEFAULT (datepart(second,getdate())) FOR [DefSecond]
GO

EF6 отлично обрабатывает столбцы IDENTITY, TIMESTAMP и COMPUTED, что означает после INSERT или UPDATE (через context.SaveChanges()). EF считывает новые значения обратно в объект сущности для немедленного использования.

Однако этого не происходит для столбцов с DEFAULT CONSTRAINT. И из того, что я могу сказать, это связано с тем, что, когда EF генерирует TSQL для выполнения INSERT, он предоставляет общее значение по умолчанию для типов с нулевым или непустым значением, точно так же, как если бы этот столбец не имел на нем DEFAULT CONSTRAINT. Таким образом, кажется очевидным, что EF полностью игнорирует возможность DEFAULT CONSTRAINT.

Вот мой EF-код для INSERT записи DBUpdateTest (и я только обновляю один столбец):

DBUpdateTest myVal = new DBUpdateTest();
myVal.UserValue = RND.Next(20, 90);
DB.DBUpdateTests.Add(myVal);
DB.SaveChanges();

Вот созданный EF SQL во время INSERT для DBUpdateTest (который покорно обновляет все возможные столбцы):

 exec sp_executesql 
 N'INSERT [dbo].[DBUpdateTest]([UserValue], [DefValue1], [DefValue2null],
          [DefSecond])
   VALUES (@0, @1, NULL, @2)
   SELECT [RowID], [CalcValue], [RowTimestamp]
   FROM [dbo].[DBUpdateTest]
   WHERE @@ROWCOUNT > 0 AND [RowID] = scope_identity()',
 N'@0 int,@1 int,@2 int',@0=86,@1=0,@2=54

Обратите внимание, что это очень ясно указывает, что будет значением по умолчанию для INT NOT NULL (0) и INT NULL (null), которое полностью преодолевает DEFAULT CONSTRAINT.

Это то, что происходит, когда выполняется команда EF INSERT, где она снабжает NULL для столбца с нулевым значением и ZERO для столбца INT

RowID   UserValue   DefValue1   DefValue2null   DefSecond   CalcValue
=========================================================================
211     100         200         NULL            0           NULL

Если, с другой стороны, я выполняю это утверждение:

insert into DBUpdateTest (UserValue) values (100)

Я получу такую ​​запись

RowID   UserValue   DefValue1   DefValue2null   DefSecond   CalcValue
=========================================================================
211     100         200         30              7           3787

Это работает как ожидалось по одной причине: команда TSQL INSERT не предоставляла значения для любых столбцов с определенным ПО УМОЛЧАНИЕМ.

То, что я пытаюсь сделать, заключается в том, чтобы заставить EF исключить столбцы DEFAULT CONSTRAINT из TSS INSERT, если я не устанавливаю для них значения в объекте модели.

Вещи, которые я уже пробовал

1. Признать ограничение по умолчанию? SO: Как заставить EF обрабатывать ограничение по умолчанию

В методе OnModelCreating() моего класса DbContext было рекомендовано, чтобы я мог сказать EF, что столбец с DEFAULT CONSTRAINT является полем COMPUTED, а это не так. Тем не менее, я хотел бы узнать, получит ли EF хотя бы считывание значения после INSERT (неважно, что это также, вероятно, не позволит мне назначить значение этому столбцу, что является более чем то, что я делаю не хочу):

        modelBuilder.Entity<DBUpdateTest>()
            .Property(e => e.DefValue1)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);

Это не работает, и на самом деле он вообще ничего не делает (ED: на самом деле это работает, см. пункт 2). EF по-прежнему генерирует один и тот же TSQL, предоставляя значения по умолчанию для столбцов и разбивая базу данных в процессе.

Есть ли флаг, который мне не хватает, элемент конфигурации, который я забываю установить, атрибут функции, который я могу использовать, некоторый код унаследованного класса, который я могу создать, чтобы заставить EF "правильно обрабатывать столбцы DEFAULT CONSTRAINT?"

2. Получить OnModelCreating() для выполнения? SO: OnModelCreating не вызывается

Janesh (внизу) показал мне, что EF будет удалять параметры из его сгенерированной команды TSQL INSERT, если столбец отмечен DatabaseGeneratedOption.Computed. Это просто не работало для меня, потому что, видимо, я использовал неправильную строку соединения (!!!).

Вот мой App.config, и вот раздел <connectionStrings>, где я показываю строку "bad" и "good" connect:

<connectionStrings>
  <add name="TEST_EF6Entities_NO_WORKY" providerName="System.Data.EntityClient" connectionString="metadata=res://*/TCXModel.csdl|res://*/TCXModel.ssdl|res://*/TCXModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=...ConnectStringHere...;App=EntityFramework&quot;"  />
  <add name="TEST_EF6Entities_IT_WORKS" providerName="System.Data.SqlClient" connectionString="data source=...ConnectStringHere...;App=EntityFramework;"  />
</connectionStrings>

Разница. В той, которая работает, используется ProviderName System.Data.SqlClient, то, которая не работает, использует System.Data.EntityClient. По-видимому, поставщик SqlClient позволяет вызвать метод OnModelCreating(), что позволяет использовать эффект DatabaseGeneratedOption.Computed.

========== НЕ РЕШЕНО ==========

Цель DEFAULT CONSTRAINTS в столбцах - позволить мне предоставлять (или не поставлять) значение и все же в конечном итоге иметь действительное значение на стороне базы данных. Мне не нужно знать, что SQL Server делает это, и я не должен знать, что такое значение по умолчанию или должно быть. Это происходит полностью вне моего контроля или знаний.

Точка, У меня есть опция, не поставляющая значение. Я могу предоставить его, или я не могу его предоставить, и я могу сделать это по-разному для каждого INSERT, если это необходимо.

Использование DatabaseGeneratedOption.Computed действительно не является допустимым вариантом для этого случая, потому что он заставляет выбрать: "вы можете ВСЕГДА предоставить значение (и, следовательно, НИКОГДА не использовать механизм по умолчанию базы данных), или вы НИКОГДА не можете обеспечить значение (и, следовательно, ВСЕГДА используйте механизм базы данных по умолчанию)".

Кроме того, этот параметр явно предназначен для использования только на фактических вычисленных столбцах, а не для столбцов с DEFAULT CONSTRAINT, потому что после применения свойство модели эффективно становится READ-ONLY для целей INSERT и UPDATE - поскольку это как реальная вычислительная колонка будет работать. Очевидно, это мешает моему выбору поставлять или не предоставлять значение для базы данных.

Итак, я все еще спрашиваю: как я могу заставить EF работать "правильно" с столбцами базы данных, которые определены с помощью DEFAULT CONSTRAINT?

4b9b3361

Ответ 1

Этот бит является ключом к вашему вопросу:

То, что я пытаюсь сделать, заключается в том, чтобы заставить EF NOT включать Столбцы DEFAULT CONSTRAINT в своем TSS INSERT, если я не эксгумативно установить для них значения в объекте.

Entity Framework не сделает этого для вас. Поля либо всегда вычисляются, либо всегда включаются в вставки и обновления. Но вы МОЖЕТЕ писать классы, чтобы вести себя так, как вы описываете. Вы должны установить поля (явно) в значения по умолчанию в конструкторе или использовать поля поддержки.

public class DBUpdateTest
/* public partial class DBUpdateTest*/ //version for database first
{
   private _DefValue1 = 200;
   private _DefValue2 = 30;

   public DbUpdateTest()
   {
      DefSecond = DateTime.Second;
   }

   public DefSecond { get; set; }

   public DefValue1
   {
      get { return _DefValue1; }
      set { _DefValue1 = value; }
   }

   public DefValue2
   {
      get { return _DefValue2; }
      set { _DefValue2 = value; }
   }
}

Если вы всегда вставляете эти классы, вам, вероятно, не нужно устанавливать значение по умолчанию в базе данных, но если вы вставляете sql из другого места, вам также придется добавить ограничение по умолчанию в базу данных

Ответ 2

Я категорически не согласен с тем, что DatabaseGeneratedOption.Computed не помогает прекратить отправку поля в вставке sql-команды. Я попробовал это с минимальным небольшим примером, чтобы проверить, и он работал.

Примечание. После того как вы применили DatabaseGeneratedOption.Computed к любому свойству, вы хотите указать любое значение из EF. то есть вы не можете указать какое-либо значение при вставке или обновлении записей.

Model

public class Person
{
    public int Id { get; set; }
    public int SomeId { get; set; }
    public string Name { get; set; }
}

Контекст

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

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>().HasKey(d => d.Id);
        modelBuilder.Entity<Person>()
            .Property(d => d.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        modelBuilder.Entity<Person>()
            .Property(d => d.SomeId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
    }
}

Миграция

public partial class Initial : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.People",
            c => new
                {
                    Id = c.Int(nullable: false, identity: true),
                    SomeId = c.Int(nullable: false, defaultValue:3), //I edited it mannually to assign default value 3.
                    Name = c.String(),
                })
            .PrimaryKey(t => t.Id);

    }

    public override void Down()
    {
        DropTable("dbo.People");
    }
}

Примечание. Я вручную изменил значение по умолчанию 3 на SomeId.

MainProgram

    static void Main(string[] args)
    {
        using (Context c = new Context())
        {
            Person p = new Person();
            p.Name = "Jenish";
            c.People.Add(p);
            c.Database.Log = Console.WriteLine;
            c.SaveChanges();
        }
    }

Я получил следующий запрос, зарегистрированный на моей консоли:

Opened connection at 04/15/2015 11:32:19 AM +05:30

Started transaction at 04/15/2015 11:32:19 AM +05:30

INSERT [dbo].[People]([Name])
VALUES (@0)
SELECT [Id], [SomeId]
FROM [dbo].[People]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()


-- @0: 'Jenish' (Type = String, Size = -1)

-- Executing at 04/15/2015 11:32:20 AM +05:30

-- Completed in 3 ms with result: SqlDataReader



Committed transaction at 04/15/2015 11:32:20 AM +05:30

Closed connection at 04/15/2015 11:32:20 AM +05:30

Уведомление SomeId не было передано команде Insert, вместо этого оно выбирается в команде выбора.

Ответ 3

Кто-то ответил на этот вопрос еще в 2013/2014, я верю:). Проект использовал EF5, хорошо, помните! Он сказал мне:

  • Щелкните правой кнопкой мыши на .edmx, выберите "Открыть с", затем "XML (текстовый редактор)" и найдите столбцы, которые вы задали как ограничение по умолчанию внутри раздела <!-- SSDL content --> файла.
  • Затем добавьте StoreGeneratedPattern="Computed" в конце этих полей.

Готово, тогда это сработало.

Затем я перешел на EF6, и он работает STILL, но БЕЗ добавления, сделанного ранее.

Теперь я сталкиваюсь с очень интересной проблемой, используя EF6, версия 6.1.3. В этот самый момент у меня есть 2 таблицы с Ограничением по умолчанию, чтобы установить булевское поле в 1, будь то запись ACTIVE или INACTIVE. Просто как это.

В одной таблице это работает, что означает: мне не нужно добавлять этот StoreGeneratedPattern="Computed" в столбец внутри .edmx. Но в другой таблице это НЕ; Я должен добавить этот StoreGeneratedPattern="Computed" в тот же столбец, что и таблица 1.