生成されるプロパティに明示的な値を設定するSetting Explicit Values for Generated Properties

生成されるプロパティとは、エンティティが追加または更新されるときに、(EF またはデータベースのどちらかによって) 値が生成されるプロパティのことです。A generated property is a property whose value is generated (either by EF or the database) when the entity is added and/or updated. 詳細については、「生成される値」を参照してください。See Generated Properties for more information.

プロパティを生成するのではなく、生成されるプロパティに明示的な値を設定したいという場合があります。There may be situations where you want to set an explicit value for a generated property, rather than having one generated.

ヒント

この記事のサンプルは GitHub で確認できます。You can view this article's sample on GitHub.

モデルThe model

この記事で使用されるモデルには、単一の Employee エンティティが含まれます。The model used in this article contains a single Employee entity.

public class Employee
{
    public int EmployeeId { get; set; }
    public string Name { get; set; }
    public DateTime EmploymentStarted { get; set; }
    public int Salary { get; set; }
    public DateTime? LastPayRaise { get; set; }
}

追加中に明示的な値を保存するSaving an explicit value during add

Employee.EmploymentStarted プロパティは、(既定の設定を使用して) 新しいエンティティに対して、データベースで値が生成されるように構成されています。The Employee.EmploymentStarted property is configured to have values generated by the database for new entities (using a default value).

modelBuilder.Entity<Employee>()
    .Property(b => b.EmploymentStarted)
    .HasDefaultValueSql("CONVERT(date, GETDATE())");

次のコードでは、データベースに 2 名の従業員を挿入しています。The following code inserts two employees into the database.

  • 1 人目の従業員では、Employee.EmploymentStarted プロパティに割り当てられている値がないため、DateTime には CLR 既定値が設定されたままです。For the first, no value is assigned to Employee.EmploymentStarted property, so it remains set to the CLR default value for DateTime.
  • 2 人目の従業員では、1-Jan-2000 という明示的な値を設定しました。For the second, we have set an explicit value of 1-Jan-2000.
using (var context = new EmployeeContext())
{
    context.Employees.Add(new Employee { Name = "John Doe" });
    context.Employees.Add(new Employee { Name = "Jane Doe", EmploymentStarted = new DateTime(2000, 1, 1) });
    context.SaveChanges();

    foreach (var employee in context.Employees)
    {
        Console.WriteLine(employee.EmployeeId + ": " + employee.Name + ", " + employee.EmploymentStarted);
    }
}

1 人目の従業員に対してはデータベースが値を生成したこと、また、2 人目の従業員に対しては明示的な値が使用されたことが、出力に表示されます。Output shows that the database generated a value for the first employee and our explicit value was used for the second.

1: John Doe, 1/26/2017 12:00:00 AM
2: Jane Doe, 1/1/2000 12:00:00 AM

SQL Server の IDENTITY 列に対する明示的な値Explicit values into SQL Server IDENTITY columns

慣例として、Employee.EmployeeId プロパティは、ストア生成された IDENTITY 列です。By convention the Employee.EmployeeId property is a store generated IDENTITY column.

ほとんどの状況で、上述した方法がキー プロパティに対して有効です。For most situations, the approach shown above will work for key properties. しかし、SQL Server の IDENTITY 列に明示的な値を挿入するには、SaveChanges() を呼び出す前に IDENTITY_INSERT を手動で有効にする必要があります。However, to insert explicit values into a SQL Server IDENTITY column, you need to manually enable IDENTITY_INSERT before calling SaveChanges().

注意

SQL Server プロバイダー内で自動的にこれを行うための機能要求が、バックログに用意されています。We have a feature request on our backlog to do this automatically within the SQL Server provider.

using (var context = new EmployeeContext())
{
    context.Employees.Add(new Employee { EmployeeId = 100, Name = "John Doe" });
    context.Employees.Add(new Employee { EmployeeId = 101, Name = "Jane Doe" });

    context.Database.OpenConnection();
    try
    {
        context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT dbo.Employees ON");
        context.SaveChanges();
        context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT dbo.Employees OFF");
    }
    finally
    {
        context.Database.CloseConnection();
    }


    foreach (var employee in context.Employees)
    {
        Console.WriteLine(employee.EmployeeId + ": " + employee.Name);
    }
}

指定された ID がデータベースに保存されたことが、出力に表示されます。Output shows that the supplied ids were saved to the database.

100: John Doe
101: Jane Doe

更新中に明示的な値を設定するSetting an explicit value during update

Employee.LastPayRaise プロパティは、更新中にデータベースで値が生成されるように構成されています。The Employee.LastPayRaise property is configured to have values generated by the database during updates.

modelBuilder.Entity<Employee>()
    .Property(b => b.LastPayRaise)
    .ValueGeneratedOnAddOrUpdate();

modelBuilder.Entity<Employee>()
    .Property(b => b.LastPayRaise)
    .Metadata.AfterSaveBehavior = PropertySaveBehavior.Ignore;

注意

既定では、更新中に生成されるように構成されたプロパティに明示的な値の保存を試行しようとすると、EF Core は例外をスローします。By default, EF Core will throw an exception if you try to save an explicit value for a property that is configured to be generated during update. これを回避するには、下位レベルのメタデータ API にドロップダウンして、AfterSaveBehavior を設定する必要があります (上記を参照)。To avoid this, you need to drop down to the lower level metadata API and set the AfterSaveBehavior (as shown above).

注意

EF Core 2.0 での変更: 前のリリースでは、保存後の動作は IsReadOnlyAfterSave フラグ経由で制御されていました。Changes in EF Core 2.0: In previous releases the after-save behavior was controlled through the IsReadOnlyAfterSave flag. このフラグは廃止され、AfterSaveBehavior に置き換えられました。This flag has been obsoleted and replaced by AfterSaveBehavior.

また、データベースには、UPDATE 操作中に LastPayRaise 列の値を生成するためのトリガーがあります。There is also a trigger in the database to generate values for the LastPayRaise column during UPDATE operations.

CREATE TRIGGER [dbo].[Employees_UPDATE] ON [dbo].[Employees]
    AFTER UPDATE
AS
BEGIN
    SET NOCOUNT ON;
                  
    IF ((SELECT TRIGGER_NESTLEVEL()) > 1) RETURN;
                  
    IF UPDATE(Salary) AND NOT Update(LastPayRaise)
    BEGIN
        DECLARE @Id INT
        DECLARE @OldSalary INT
        DECLARE @NewSalary INT
          
        SELECT @Id = INSERTED.EmployeeId, @NewSalary = Salary        
        FROM INSERTED
          
        SELECT @OldSalary = Salary        
        FROM deleted
          
        IF @NewSalary > @OldSalary
        BEGIN
            UPDATE dbo.Employees
            SET LastPayRaise = CONVERT(date, GETDATE())
            WHERE EmployeeId = @Id
        END
    END
END

次のコードでは、データベース内の 2 人の従業員の給与を引き上げています。The following code increases the salary of two employees in the database.

  • 1 人目の従業員では、Employee.LastPayRaise プロパティに割り当てられている値がないため、null に設定されたままです。For the first, no value is assigned to Employee.LastPayRaise property, so it remains set to null.
  • 2 人目の従業員では、1 週間前 (給与引き上げの日付へ戻る) の明示的な値を設定します。For the second, we have set an explicit value of one week ago (back dating the pay raise).
using (var context = new EmployeeContext())
{
    var john = context.Employees.Single(e => e.Name == "John Doe");
    john.Salary = 200;

    var jane = context.Employees.Single(e => e.Name == "Jane Doe");
    jane.Salary = 200;
    jane.LastPayRaise = DateTime.Today.AddDays(-7);

    context.SaveChanges();

    foreach (var employee in context.Employees)
    {
        Console.WriteLine(employee.EmployeeId + ": " + employee.Name + ", " + employee.LastPayRaise);
    }
}

1 人目の従業員に対してはデータベースが値を生成したこと、また、2 人目の従業員に対しては明示的な値が使用されたことが、出力に表示されます。Output shows that the database generated a value for the first employee and our explicit value was used for the second.

1: John Doe, 1/26/2017 12:00:00 AM
2: Jane Doe, 1/19/2017 12:00:00 AM