web-dev-qa-db-fra.com

Comment définir la date de création et la date de modification sur les entités dans la première approche de base de données

Dans chaque table SQL de notre application, nous avons la colonne "CreatedDate" et "ModifiedDate". Nous utilisons la première approche DB. Lorsque j'enregistre les données, je veux que ces deux colonnes soient automatiquement remplies. Une approche consiste à avoir la valeur par défaut comme getdate() sur la colonne de table SQL elle-même. Donc ça va résoudre le problème en partie. Cela signifie qu'il définira la date de création et la date de modification lorsque l'entité est nouvelle. Cependant, lorsque je modifie/met à jour l'entité, je souhaite que seulement ModifiedDate soit mis à jour.
De nombreux articles le font en utilisant la première approche du Code. Mais nous utilisons la première approche DB.
Quelles sont mes options ici?

15
LP13

Si vous souhaitez remplacer OnSave, vous devez remplacer toutes les méthodes de sauvegarde.Dans EF core 2.1, vous pouvez utiliser une meilleure solution avec ChangeTracked et les événements. Par exemple:

Vous pouvez créer une interface ou une classe de base comme l'exemple ci-dessous:

public interface IUpdateable 
{ 
    DateTime ModificationDate{ get; set; }

}

public class SampleEntry : IUpdateable
{
    public int Id { get; set; }
    DateTime ModificationDate{ get; set; }

} 

Ensuite, lors de la création du contexte, ajoutez un événement à Change tracker:

context.ChangeTracker.StateChanged += context.ChangeTracker_StateChanged;

Et méthode:

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;

        }
    }

C'est plus facile et vous n'avez pas à remplacer toutes les méthodes;

11
kjppster

Pour ceux qui utilisent un système asynchrone (SaveChangesAsync) et un noyau .net, il est préférable d'ajouter le code ci-dessous à la classe 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);
    }

et vous pouvez également définir une classe ou une interface comme ci-dessous.

public class SaveConfig
{
    public DateTime CreationTime { get; set; }
    public DateTime? ModifiedDate { get; set; }
}

héritez simplement des entités de l'entité SaveConfig.

9
Arash

Vous pouvez remplacer la méthode SaveChanges dans DbContext et obtenir une liste des entités ajoutées et modifiées de votre entité et si elle est ajoutée, définissez CreatedDate et si elle est modifiée, définissez 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();
}

Vous pouvez également écrire une interface qui possède deux propriétés DateTime et faire en sorte que vos Entités en héritent.

interface IEntityDate
{
    DateTime AddedDate { set; get;}
    DateTime ModifiedDate { set; get;}
}

class Entity : IEntityDate
{
    public DateTime AddedDate { set; get;}
    public DateTime ModifiedDate { set; get;}
}
8
Kahbazi

J'aime une approche plus générique utilisée comme ceci:

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();
}

Mise à jour:

Pour gérer CreatedBy et UpdatedBy j'utilise un wrapper pour DbContext comme ceci:

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();
    }

    ...
7
Ogglas

Voici comment j'ai résolu dans EF Core 2.1:

J'ai un modèle de base qui hérite d'une interface similaire à:

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; }
}

Ensuite, dans mon 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();
    }

Cela vous permet d'éviter de coder en dur les noms de propriété et vous permet d'appliquer de manière flexible l'interface spécifique aux modèles de votre choix. Dans mon cas, je veux cela sur la plupart des modèles, j'ai donc appliqué à mon BaseModel dont tous les autres modèles héritent.

De plus, je trouvais sur mes vues qui éditaient ces modèles que l'horodatage créé était effacé car je ne le chargeais pas sur la page. Par conséquent, lorsque le modèle a été lié lors de la publication, cette propriété a été effacée, ce qui a mis mes horodatages créés à 0.

2
Andrew

J'ai fait une chose similaire à ce que @Arash a proposé avec le remplacement public "Task SaveChangesAsync" à la différence qu'avec ma configuration, je veux juste mettre à jour les entités qui ont en fait une propriété CreatedDate/ModifiedDate et c'est ce que j'ai trouvé. Très similaire à @Arash, mais peut-être que cela peut aider quelqu'un. Ajout de deux constantes EntityCreatedPropertyName et EntityModifiedPropertyName définissant le nom des propriétés de sorte que je n'aurais pas à utiliser des chaînes dupliquées pour celles où je les utilise.

    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);
    }
0
Sandman