В каждой таблице SQL нашего приложения есть столбец "CreatedDate" и "ModifiedDate". Мы используем первый подход БД. Когда я сохраняю данные, я хочу, чтобы эти два столбца были автоматически заполнены. Один из подходов - иметь значение по умолчанию как getdate()
в самой колонке таблицы SQL. Так что это будет частично решить проблему. Это означает, что он установит CreateDate и ModifiedDate, когда объект будет новым.
Однако, когда я редактирую/обновляю объект, я хочу, чтобы обновлялся только ModifiedDate
.
Есть много статей, которые делают это, используя первый подход Code. Но мы используем первый подход DB.
Какие у меня варианты?
Как установить дату создания и измененную дату для включения в первый подход DB
Ответ 1
Если вы хотите переопределить OnSave, вы должны переопределить все методы сохранения. В EF core 2.1 вы можете использовать лучшее решение с ChangeTracked и событиями. Например:
Вы можете создать интерфейс или базовый класс, как показано ниже:
public interface IUpdateable
{
DateTime ModificationDate{ get; set; }
}
public class SampleEntry : IUpdateable
{
public int Id { get; set; }
public DateTime ModificationDate { get; set; }
}
Затем при создании контекста добавьте событие в трекер изменения:
context.ChangeTracker.StateChanged += context.ChangeTracker_StateChanged;
И метод:
private void ChangeTracker_StateChanged(object sender, Microsoft.EntityFrameworkCore.ChangeTracking.EntityStateChangedEventArgs e)
{
if(e.Entry.Entity is IUpdateable && e.Entry.State == EntityState.Modified)
{
var entry = ((IUpdateable)e.Entry.Entity);
entry.ModyficationDate = DateTime.Now;
}
}
Это проще, и вам не нужно переопределять все методы;
Ответ 2
Для тех, кто использует асинхронную систему (SaveChangesAsync) и .net, лучше добавить код ниже в класс DbContext.
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
var AddedEntities = ChangeTracker.Entries().Where(E => E.State == EntityState.Added).ToList();
AddedEntities.ForEach(E =>
{
E.Property("CreationTime").CurrentValue = DateTime.Now;
});
var EditedEntities = ChangeTracker.Entries().Where(E => E.State == EntityState.Modified).ToList();
EditedEntities.ForEach(E =>
{
E.Property("ModifiedDate").CurrentValue = DateTime.Now;
});
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
а также вы можете определить класс или интерфейс, как показано ниже.
public class SaveConfig
{
public DateTime CreationTime { get; set; }
public DateTime? ModifiedDate { get; set; }
}
просто наследует объекты из объекта SaveConfig.
Ответ 3
Вы можете переопределить метод SaveChanges в DbContext и получить список добавленных и измененных объектов, и если в него добавлен Set CreatedDate, и если он изменен, установите ModifiedDate.
public override int SaveChanges()
{
var AddedEntities = ChangeTracker.Entries<Entity>().Where(E => E.State == EntityState.Added).ToList();
AddedEntities.ForEach(E =>
{
E.CreatedDate = DateTime.Now;
});
var ModifiedEntities = ChangeTracker.Entries<Entity>().Where(E => E.State == EntityState.Modified).ToList();
ModifiedEntities.ForEach(E =>
{
E.ModifiedDate = DateTime.Now;
});
return base.SaveChanges();
}
Вы также можете написать интерфейс, который имеет два свойства DateTime и сделать ваши объекты наследуемыми.
interface IEntityDate
{
DateTime AddedDate { set; get;}
DateTime ModifiedDate { set; get;}
}
class Entity : IEntityDate
{
public DateTime AddedDate { set; get;}
public DateTime ModifiedDate { set; get;}
}
Ответ 4
Мне нравится более общий подход, используемый следующим образом:
interface IEntityDate
{
DateTime CreatedDate { get; set; }
DateTime UpdatedDate { get; set; }
}
public abstract class EntityBase<T1>: IEntityDate
{
public T1 Id { get; set; }
public virtual DateTime CreatedDate { get; set; }
public virtual string CreatedBy { get; set; }
public virtual DateTime UpdatedDate { get; set; }
public virtual string UpdatedBy { get; set; }
}
public override int SaveChanges()
{
var now = DateTime.Now;
foreach (var changedEntity in ChangeTracker.Entries())
{
if (changedEntity.Entity is IEntityDate entity)
{
switch (changedEntity.State)
{
case EntityState.Added:
entity.CreatedDate = now;
entity.UpdatedDate = now;
break;
case EntityState.Modified:
Entry(entity).Property(x => x.CreatedDate).IsModified = false;
entity.UpdatedDate = now;
break;
}
}
}
return base.SaveChanges();
}
Обновление:
Для обработки CreatedBy
и UpdatedBy
я использую оболочку для DbContext следующим образом:
public interface IEntity
{
DateTime CreatedDate { get; set; }
string CreatedBy { get; set; }
DateTime UpdatedDate { get; set; }
string UpdatedBy { get; set; }
}
public interface ICurrentUser
{
string GetUsername();
}
public class ApplicationDbContextUserWrapper
{
public ApplicationDbContext Context;
public ApplicationDbContextUserWrapper(ApplicationDbContext context, ICurrentUser currentUser)
{
context.CurrentUser = currentUser;
this.Context = context;
}
}
public class ApplicationDbContext : DbContext
{
public ICurrentUser CurrentUser;
public override int SaveChanges()
{
var now = DateTime.Now;
foreach (var changedEntity in ChangeTracker.Entries())
{
if (changedEntity.Entity is IEntity entity)
{
switch (changedEntity.State)
{
case EntityState.Added:
entity.CreatedDate = now;
entity.UpdatedDate = now;
entity.CreatedBy = CurrentUser.GetUsername();
entity.UpdatedBy = CurrentUser.GetUsername();
break;
case EntityState.Modified:
Entry(entity).Property(x => x.CreatedBy).IsModified = false;
Entry(entity).Property(x => x.CreatedDate).IsModified = false;
entity.UpdatedDate = now;
entity.UpdatedBy = CurrentUser.GetUsername();
break;
}
}
}
return base.SaveChanges();
}
...
Ответ 5
Вот как я решил в EF Core 2.1:
У меня есть базовая модель, которая наследует интерфейс, подобный:
public interface IAuditableModel
{
DateTime Created { get; }
DateTime? Updated { get; set; }
}
public class BaseModel : IAuditableModel
{
public BaseModel()
{
}
public int Id { get; set; }
public DateTime Created { get; private set; }
public DateTime? Updated { get; set; }
}
Затем в моем DbContext:
public override int SaveChanges()
{
var added = ChangeTracker.Entries<IAuditableModel>().Where(E => E.State == EntityState.Added).ToList();
added.ForEach(E =>
{
E.Property(x => x.Created).CurrentValue = DateTime.UtcNow;
E.Property(x => x.Created).IsModified = true;
});
var modified = ChangeTracker.Entries<IAuditableModel>().Where(E => E.State == EntityState.Modified).ToList();
modified.ForEach(E =>
{
E.Property(x => x.Updated).CurrentValue = DateTime.UtcNow;
E.Property(x => x.Updated).IsModified = true;
E.Property(x => x.Created).CurrentValue = E.Property(x => x.Created).OriginalValue;
E.Property(x => x.Created).IsModified = false;
});
return base.SaveChanges();
}
Это позволяет избежать жесткого кодирования любых имен свойств и позволяет гибко применять конкретный интерфейс к выбранным вами моделям. В моем случае я хочу, чтобы это было на большинстве моделей, поэтому я применил к моей BaseModel, наследуемой всеми другими моделями.
Кроме того, я нашел на своих взглядах, которые отредактировали эти модели, созданная метка времени была уничтожена, так как я не загружал ее на страницу. В результате, когда модель была привязана к обратному сообщению, свойство стирало, установите мои созданные временные метки в 0 эффективно.
Ответ 6
Я сделал то же самое, что и @Arash, предложив публике переопределить "Task SaveChangesAsync" с той разницей, что при моей настройке я просто хочу обновить объекты, которые на самом деле имеют свойства CreateDate/ModifiedDate, и это то, что я придумал. Очень похоже на @Arash, но, возможно, это может помочь кому-то. Добавлено несколько констант EntityCreatedPropertyName и EntityModifiedPropertyName, определяющих имя свойств, чтобы мне не пришлось использовать повторяющиеся строки для тех, где я их использую.
public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default(CancellationToken))
{
#region Automatically set "CreatedDate" property on an a new entity
var AddedEntities = ChangeTracker.Entries().Where(E => E.State == EntityState.Added).ToList();
AddedEntities.ForEach(E =>
{
var prop = E.Metadata.FindProperty(EntityCreatedPropertyName);
if (prop != null)
{
E.Property(EntityCreatedPropertyName).CurrentValue = DateTime.Now;
}
});
#endregion
#region Automatically set "ModifiedDate" property on an a new entity
var EditedEntities = ChangeTracker.Entries().Where(E => E.State == EntityState.Modified).ToList();
EditedEntities.ForEach(E =>
{
var prop = E.Metadata.FindProperty(EntityModifiedPropertyName);
if (prop != null)
{
E.Property(EntityModifiedPropertyName).CurrentValue = DateTime.Now;
}
});
#endregion
return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}