Как установить значения по умолчанию для свойства shadow - PullRequest
2 голосов
/ 14 февраля 2020

У меня есть следующая сущность:

public class Person
{
    public Guid Id { get; set; }

    public string Name { get; set; }
}

Это мой контекст БД

public class PersonDbContext : DbContext
{
    private static readonly ILoggerFactory
        Logger = LoggerFactory.Create(x => x.AddConsole());

    public DbSet<Person> Persons { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseLoggerFactory(Logger)
            .UseSqlServer(
                "Server=(localdb)\\mssqllocaldb;Database=PersonDb;Trusted_Connection=True;MultipleActiveResultSets=true");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder
            .Entity<Person>()
            .Property<DateTime>("Created")
            .HasDefaultValueSql("GETUTCDATE()")
            .ValueGeneratedOnAdd();

        modelBuilder
            .Entity<Person>()
            .Property<DateTime>("Updated")
            .HasDefaultValueSql("GETUTCDATE()")
            .ValueGeneratedOnAddOrUpdate();
    }
}

Как видно из переопределения OnModelCreating, я добавляю свойства тени Обновлено / Создан для Person сущности.

Я установил для этих свойств значения SQL значения по умолчанию

  • Created при добавлении значения
  • Updated когда значение добавлено или обновлено

Ниже приведен код клиента

var personId = Guid.Parse("CF5EE27D-C694-408A-9F7B-080FF6315843");

using (var dbContext = new PersonDbContext())
{
    var person = new Person
    {
        Id = personId,
        Name = "New Person"
    };

    dbContext.Add(person);

    await dbContext.SaveChangesAsync();
}

using (var dbContext = new PersonDbContext())
{
    var person = dbContext.Persons.Find(personId);

    var personName = person.Name;

    person.Name = $"{personName} {DateTime.UtcNow}";

    dbContext.SaveChanges();
}

Я могу подтвердить, что оба свойства установлены в UT C дата при вставке нового человека. Однако при обновлении свойство Updated не устанавливается.

Это сгенерированное t- sql:

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[@p1='?' (DbType = Guid), @p0='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      SET NOCOUNT ON;
      UPDATE [Persons] SET [Name] = @p0
      WHERE [Id] = @p1;
      SELECT [Updated]
      FROM [Persons]
      WHERE @@ROWCOUNT = 1 AND [Id] = @p1;

Чтение документации по генерируемому значению при добавлении или update Я вижу следующее предупреждение:

Однако, если вы укажете, что свойство DateTime генерируется при добавлении или обновлении, вы должны указать способ создания значений. Один из способов сделать это - настроить значение по умолчанию GETDATE () (см. Значения по умолчанию), чтобы генерировать значения для новых строк. Затем вы можете использовать триггер базы данных для генерации значений во время обновлений (например, в следующем примере триггера).

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

Действительно, если я изменю определение свойства Updated shadow на

modelBuilder
    .Entity<Person>()
    .Property<DateTime>("Updated")
    .HasDefaultValueSql("GETUTCDATE()")
    .ValueGeneratedOnAdd();

И переопределим SaveChanges на PersonDbContext

public override int SaveChanges()
{
    ChangeTracker.DetectChanges();

    foreach (var entry in ChangeTracker.Entries().Where(entity => entity.State == EntityState.Modified))
    {
        entry.Property("Updated").CurrentValue = DateTime.UtcNow;
    }

    return base.SaveChanges();
}

Это делает то, что ожидается.

Итак, вопрос в том, как правильно установить значения по умолчанию для свойств тени в EF Core.

Это упрощенный пример из моего более крупного проекта, поэтому использование HasData для сущностей в OnModelCreating переопределении не является хорошим вариантом (из-за множества сущностей).

Я использую EF Core 3.1 .1

<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.1"/>

Ответы [ 2 ]

0 голосов
/ 17 февраля 2020

Я нашел dicussion , где указано, что имя ValueGeneratedOnAddOrUpdate() недостаточно описательно.

@ rowanmiller Спасибо за быстрый ответ! Теперь я знаю, как это работает, но я думаю, что это немного сбивает с толку. Именование «ValueGeneratedOnAddOrUpdate» предполагает, что значение фактически генерируется при вставке и обновлении.

И в документах говорится: «Значение, генерируемое при добавлении» «Значение, генерируемое при добавлении, означает, что если вы не укажете значение, оно будет сгенерировано для вас. "

" Значение, сгенерированное при добавлении или обновлении "" Значение, сгенерированное при добавлении или обновлении, означает, что новое значение создается при каждом сохранении записи (вставка или обновление). "

Может быть, добавить в документы раздел, описывающий, как вы используете собственный генератор значений?

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

@ bcbeatty подробное объяснение включено в основной раздел, и есть окно с предупреждением в разделах Fluent и Annotation данных, которые указывают на подробное примечание.

Это просто позволяет EF знать, что значения генерируются для добавленных объектов, но не не гарантирует, что EF установит фактический механизм для генерации значений. См. Value, сгенерированный в секции add для получения более подробной информации.

Так что кажется, что по крайней мере для DateTime свойств тени подход в OP - это путь к go.

0 голосов
/ 14 февраля 2020

, если вы хотите использовать повторно используемые свойства тени, выполните следующие действия.

1 - создайте пустой интерфейс маркера. IAuditableEntity.cs

    /// <summary>
    /// It's a marker interface, in order to make our entities audit-able.
    /// Every entity you mark with this interface, will save audit info to the database.
    /// </summary>
    public interface IAuditableEntity
    { }

2- Создайте класс c, в котором будет записываться логика ваших свойств тени c. AuditableShadowProperties.cs

public static class AuditableShadowProperties {

    public static readonly Func<object, DateTimeOffset?> EfPropertyCreatedDateTime =
        entity => EF.Property<DateTimeOffset?> (entity, CreatedDateTime);

    public static readonly string CreatedDateTime = nameof (CreatedDateTime);

    public static readonly Func<object, DateTimeOffset?> EfPropertyModifiedDateTime =
        entity => EF.Property<DateTimeOffset?> (entity, ModifiedDateTime);

    public static readonly string ModifiedDateTime = nameof (ModifiedDateTime);

    public static void AddAuditableShadowProperties (this ModelBuilder modelBuilder) {
        foreach (var entityType in modelBuilder.Model
                .GetEntityTypes ()
                .Where (e => typeof (IAuditableEntity).IsAssignableFrom (e.ClrType))) {
            modelBuilder.Entity (entityType.ClrType)
                .Property<DateTimeOffset?> (CreatedDateTime);

            modelBuilder.Entity (entityType.ClrType)
                .Property<DateTimeOffset?> (ModifiedDateTime);

        }
    }

    public static void SetAuditableEntityPropertyValues (
        this ChangeTracker changeTracker) {
        var now = DateTimeOffset.UtcNow;

        var modifiedEntries = changeTracker.Entries<IAuditableEntity> ()
            .Where (x => x.State == EntityState.Modified);
        foreach (var modifiedEntry in modifiedEntries) {
            modifiedEntry.Property (ModifiedDateTime).CurrentValue = now;
        }

        var addedEntries = changeTracker.Entries<IAuditableEntity> ()
            .Where (x => x.State == EntityState.Added);
        foreach (var addedEntry in addedEntries) {
            addedEntry.Property (CreatedDateTime).CurrentValue = now;
        }
    }
}

3- Добавьте необходимые изменения в PersonDbContext, чтобы использовать IAuditableEntity.

 // first we add our shadow properties to the database with next migration
 protected override void OnModelCreating(ModelBuilder builder)
{
...
  builder.AddAuditableShadowProperties();
}

// override saveChanges methods to use our shadow properties.
        public override int SaveChanges()
        {
            ChangeTracker.DetectChanges();

            BeforeSaveTriggers();

            ChangeTracker.AutoDetectChangesEnabled =
                false; // for performance reasons, to avoid calling DetectChanges() again.
            var result = base.SaveChanges();
            ChangeTracker.AutoDetectChangesEnabled = true;
            return result;
        }

     public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
        {
            ChangeTracker.DetectChanges();

            BeforeSaveTriggers();

            ChangeTracker.AutoDetectChangesEnabled =
                false; // for performance reasons, to avoid calling DetectChanges() again.
            var result = base.SaveChangesAsync(cancellationToken);
            ChangeTracker.AutoDetectChangesEnabled = true;
            return result;
        }

        public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess,
            CancellationToken cancellationToken = new CancellationToken())
        {
            ChangeTracker.DetectChanges();

            BeforeSaveTriggers();

            ChangeTracker.AutoDetectChangesEnabled =
                false; // for performance reasons, to avoid calling DetectChanges() again.
            var result = base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
            ChangeTracker.AutoDetectChangesEnabled = true;
            return result;
        }

        #region "ExtraMethods"

        public T GetShadowPropertyValue<T>(object entity, string propertyName) where T : IConvertible
        {
            var value = this.Entry(entity).Property(propertyName).CurrentValue;
            return value != null ?
                (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture) :
                default(T);
        }

        public object GetShadowPropertyValue(object entity, string propertyName)
        {
            return this.Entry(entity).Property(propertyName).CurrentValue;
        }


        private void BeforeSaveTriggers()
        {
            ValidateEntities();
            SetShadowProperties();
        }

        private void ValidateEntities()
        {
            var errors = this.GetValidationErrors();
            if (!string.IsNullOrWhiteSpace(errors))
            {
                // we can't use constructor injection anymore, because we are using the `AddDbContextPool<>`
                var loggerFactory = this.GetService<ILoggerFactory>();
                loggerFactory.CheckArgumentIsNull(nameof(loggerFactory));
                var logger = loggerFactory.CreateLogger<AppDbContext>();
                logger.LogError(errors);
                throw new InvalidOperationException(errors);
            }
        }

        private void SetShadowProperties()
        {
            ChangeTracker.SetAuditableEntityPropertyValues();
        }
        #endregio

использование:

4 - теперь вы можете добавить IAuditableEntity интерфейс к любому объекту, для которого вы хотите иметь эти свойства тени, и все готово.

public class Person : IAuditableEntity
{
    public Guid Id { get; set; }

    public string Name { get; set; }
}

Я использую IAuditableEntity с множеством других свойств, таких как как BrowserName userIp ... но я удалил их в этом примере, чтобы сделать его максимально простым. Нелегко объяснить все в этом примере, но не стесняйтесь спрашивать, есть ли у вас какие-либо вопросы об этом подходе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...