DateCreated или Modified Column - структура сущностей или использование триггеров на SQL Server

После того, как я прочитал один вопрос во вложенной ссылке, я понял, как установить столбцы DateCreated и DateModified в Entity Framework и использовать его в своем приложении. Тем не менее, в старом SQL-способе путь триггера более популярен, поскольку он более безопасен с точки зрения DBA.

Значит, любой совет, какой путь - лучшая практика? должен ли он быть установлен в инфраструктуре сущности для целостности приложения? или должен использовать триггер, поскольку он имеет больше смысла с точки зрения безопасности данных? Или есть способ создать триггер в структуре сущностей? Благодарю.

EF CodeFirst: созданные и измененные столбцы в стиле Rails

Кстати, хотя это не имеет большого значения, я создаю это приложение с помощью ASP.NET MVC С#.

Ответ 1

Мнение: Триггеры похожи на скрытое поведение, если вы не ищете их, вы обычно не поймете, что они есть. Мне также нравится поддерживать DB как "тупой", насколько это возможно при использовании EF, поскольку я использую EF, поэтому моей команде не требуется поддерживать код SQL.

Для моего решения (сочетание ASP.NET WebForms и MVC в С# с бизнес-логикой в ​​другом проекте, который также содержит DataContext):

Недавно у меня была аналогичная проблема, и хотя для моей ситуации она была более сложной (DatabaseFirst, поэтому требуется специальный файл TT), решение в основном одинаков.

Я создал интерфейс:

public interface ITrackableEntity
{
    DateTime CreatedDateTime { get; set; }
    int CreatedUserID { get; set; }
    DateTime ModifiedDateTime { get; set; }
    int ModifiedUserID { get; set; }
}

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

UPDATE: здесь мои изменения в файле TT, где я обновил метод EntityClassOpening():

public string EntityClassOpening(EntityType entity)
{
    var trackableEntityPropNames = new string[] { "CreatedUserID", "CreatedDateTime", "ModifiedUserID", "ModifiedDateTime" };
    var propNames = entity.Properties.Select(p => p.Name);
    var isTrackable = trackableEntityPropNames.All(s => propNames.Contains(s));
    var inherits = new List<string>();
    if (!String.IsNullOrEmpty(_typeMapper.GetTypeName(entity.BaseType)))
    {
        inherits.Add(_typeMapper.GetTypeName(entity.BaseType));
    }
    if (isTrackable)
    {
        inherits.Add("ITrackableEntity");
    }

    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1}partial class {2}{3}",
        Accessibility.ForType(entity),
        _code.SpaceAfter(_code.AbstractOption(entity)),
        _code.Escape(entity),
        _code.StringBefore(" : ", String.Join(", ", inherits)));
}

Осталось только добавить следующее к моему частичному классу DataContext:

    public override int SaveChanges()
    {
        // fix trackable entities
        var trackables = ChangeTracker.Entries<ITrackableEntity>();

        if (trackables != null)
        {
            // added
            foreach (var item in trackables.Where(t => t.State == EntityState.Added))
            {
                item.Entity.CreatedDateTime = System.DateTime.Now;
                item.Entity.CreatedUserID = _userID;
                item.Entity.ModifiedDateTime = System.DateTime.Now;
                item.Entity.ModifiedUserID = _userID;
            }
            // modified
            foreach (var item in trackables.Where(t => t.State == EntityState.Modified))
            {
                item.Entity.ModifiedDateTime = System.DateTime.Now;
                item.Entity.ModifiedUserID = _userID;
            }
        }

        return base.SaveChanges();
    }

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

Ответ 2

Что касается DateCreated, я бы просто добавил ограничение по умолчанию в этом столбце, установленном на SYSDATETIME(), которое вступает в силу при вставке новой строки в таблицу.

Для DateModified, лично, я бы, вероятно, использовал триггеры для этих таблиц.

По-моему, подход триггера:

  • упрощает; Мне не нужно беспокоиться и помнить каждый раз, когда я сохраняю объект для установки DateModified

  • делает его "более безопасным" тем, что он также применит DateModified, если кто-то найдет способ моего приложения для непосредственного изменения данных в базе данных (используя, например, Access или Excel или что-то в этом роде).

Ответ 4

Я согласен с marc_s - гораздо безопаснее иметь триггер в базе данных. В базах данных моей компании я требую, чтобы каждое поле имело поле Date_Modified, Date_Created, и у меня даже есть функция утилиты для автоматического создания необходимых триггеров.

При использовании с Entity Framework я обнаружил, что мне нужно использовать аннотацию [DatabaseGenerated] с моими классами POCO:

[Column(TypeName = "datetime2")]
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime? Date_Modified { get; set; }

[Column(TypeName = "datetime2")]
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime? Date_Created { get; set; }

Я пытался использовать сопоставление хранимых процедур для объекта, а EF создавал параметры @Date_Modified, @Date_Created на моих вставках/обновлениях sprocs, получавших ошибку

В процедуре или функции указано слишком много аргументов.

В большинстве примеров показано использование [NotMapped], которое позволит select/insert работать, но тогда эти поля не будут отображаться при загрузке этого объекта!

В качестве альтернативы вы можете просто убедиться, что любые sprocs содержат параметры @Date_Modified, @Date_Created, но это противоречит дизайну использования триггеров в первую очередь.