web-dev-qa-db-fra.com

Entity Framework / SQL 2008 - Comment mettre à jour automatiquement les derniers champs modifiés pour des entités?

Si j'ai l'entité suivante:

public class PocoWithDates
{
   public string PocoName { get; set; }
   public DateTime CreatedOn { get; set; }
   public DateTime LastModified { get; set; }
}

Qui correspond à une table SQL Server 2008 avec le même nom/attributs ...

Comment puis-je automatiquement:

  1. Définissez le champ Caredon/LastModified pour l'enregistrement à maintenant (lorsque vous effectuez un insert)
  2. Définissez le champ lastmodifié pour l'enregistrement à maintenant (lors de la mise à jour)

Quand je dis automatiquement, je veux dire que je veux pouvoir faire ceci:

poco.Name = "Changing the name";
repository.Save(); 

Pas ça:

poco.Name = "Changing the name";
poco.LastModified = DateTime.Now;
repository.Save();

Dans les coulisses, "quelque chose" devrait mettre à jour automatiquement les champs DateTime. Qu'est-ce que "quelque chose"?

J'utilise l'entité Framework 4.0 - Y a-t-il une façon que EF puisse le faire automatiquement pour moi? (un paramètre spécial dans l'EDMX peut-être?)

Sur le côté SQL Server, je peux utiliser DefaultValue, mais cela ne fonctionnera que pour insert (pas la mise à jour).

De même, je peux définir une valeur par défaut à l'aide d'un constructeur sur le POCO, mais à nouveau, cela ne fonctionnera que lors de l'instanciation de l'objet.

Et bien sûr, je pourrait Utilisez des déclencheurs, mais ce n'est pas idéal.

Parce que j'utilise l'entité de l'entité, je peux accrocher à l'événement sauvegarde événement et mettre à jour les champs de date ici, mais je dois que je dois devenir "conscient" du poco (pour le moment, mon référentiel est mis en œuvre avec des génériques). Je devrais faire une sorte de OO Dessery (comme faire de mon POCO implémenter une interface et appelle une méthode à ce sujet). Je ne suis pas adversé à cela, mais si je dois faire Cela préférerais définir manuellement les champs.

Je suis essentiellement à la recherche d'une solution SQL Server 2008 ou d'entité Framework 4.0. (ou une voie intelligente .Net)

Des idées?

ÉDITER

Merci à @marc_s pour sa réponse, mais je suis allé avec une solution meilleure pour mon scénario.

41
RPM1984

Comme j'ai une couche de service médiant entre mes contrôleurs (IM en utilisant ASP.NET MVC) et mon référentiel, j'ai décidé de définir automatiquement les champs ici.

En outre, mon poco n'a pas de relation/abstraction, ils sont complètement indépendants. Je voudrais garder cela de cette façon et ne pas marquer de propriétés virtuelles ou créer des classes de base.

J'ai donc créé une interface, IAutogeneraTateAnfields:

public interface IAutoGenerateDateFields
{
   DateTime LastModified { get;set; }
   DateTime CreatedOn { get;set; }
}

Pour tout POCO, je souhaite générer automatiquement ces champs, je mettez en œuvre cette inteface.

Utilisation de l'exemple dans ma question:

public class PocoWithDates : IAutoGenerateDateFields
{
   public string PocoName { get; set; }
   public DateTime CreatedOn { get; set; }
   public DateTime LastModified { get; set; }
}

Dans ma couche de service, je vérifie maintenant si l'objet concret implémente l'interface:

public void Add(SomePoco poco)
{
   var autoDateFieldsPoco = poco as IAutoGenerateDateFields; // returns null if it's not.

   if (autoDateFieldsPoco != null) // if it implements interface
   {
      autoDateFieldsPoco.LastModified = DateTime.Now;
      autoDateFieldsPoco.CreatedOn = DateTime.Now;
   }

   // ..go on about other persistence work.
}

Je vais probablement casser ce code dans l'Ajoutez une méthode d'assistance/extension plus tard.

Mais je pense que c'est une solution décente pour mon scénario, car je ne veux pas utiliser de virtules sur l'enregistrement (comme j'utilise l'unité de travail, le référentiel et le poco pur) et ne veulent pas utiliser de déclencheurs.

Si vous avez des pensées/suggestions, faites le moi savoir.

9
RPM1984

Je sais que je suis un peu tard à la fête, mais je viens de résoudre ceci pour un projet que je travaille et je pensais partager ma solution.

Premièrement, pour rendre la solution plus récupéable, j'ai créé une classe de base avec les propriétés de l'horodatage:

public class EntityBase
{
    public DateTime? CreatedDate { get; set; }
    public DateTime? LastModifiedDate { get; set; }
}

Ensuite, j'ai envahi la méthode Savechanges sur mon dBContext:

public class MyContext : DbContext
{
    public override int SaveChanges()
    {
        ObjectContext context = ((IObjectContextAdapter)this).ObjectContext;

        //Find all Entities that are Added/Modified that inherit from my EntityBase
        IEnumerable<ObjectStateEntry> objectStateEntries =
            from e in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified)
            where
                e.IsRelationship == false &&
                e.Entity != null &&
                typeof(EntityBase).IsAssignableFrom(e.Entity.GetType())
            select e;

        var currentTime = DateTime.Now;

        foreach (var entry in objectStateEntries)
        {
            var entityBase = entry.Entity as EntityBase;

            if (entry.State == EntityState.Added)
            {
                entityBase.CreatedDate = currentTime;
            }

            entityBase.LastModifiedDate = currentTime;
        }

        return base.SaveChanges();
    }
}
53
Nick

Je voudrais aussi mettre en avant une solution tardive. Celui-ci est uniquement applicable au .NET Framework 4 mais fait ce type de tâche triviale.

var vs = oceContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
foreach (var v in vs)
{
    dynamic dv = v.Entity;
    dv.DateLastEdit = DateTime.Now;
}
6
chinupson

voici la version modifiée de la réponse précédente. Le précédent, on n'a pas fonctionné pour les mises à jour pour moi.

public override int SaveChanges()
    {
        var objectStateEntries = ChangeTracker.Entries()
            .Where(e => e.Entity is TrackedEntityBase && (e.State == EntityState.Modified || e.State == EntityState.Added)).ToList();
        var currentTime = DateTime.UtcNow;
        foreach (var entry in objectStateEntries)
        {
            var entityBase = entry.Entity as TrackedEntityBase;
            if (entityBase == null) continue;
            if (entry.State == EntityState.Added)
            {
                entityBase.CreatedDate = currentTime;
            }
            entityBase.LastModifiedDate = currentTime;
        }

        return base.SaveChanges();
    }
6
serg.salo

j'ai dû ajouter une entité de base l'étendre de ma base de données première classe partielle (ce qui n'est pas idéal)

    public override int SaveChanges()
    {
        AddTimestamps();
        return base.SaveChanges();
    }

    public override async Task<int> SaveChangesAsync()
    {
        AddTimestamps();
        return await base.SaveChangesAsync();
    }

    private void AddTimestamps()
    {
        //var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseEntity && (x.State == EntityState.Added || x.State == EntityState.Modified));

        //ObjectiveContext context = ((IObjectContextAdapter)this).ObjectContext;

        var entities = ChangeTracker.Entries().Where(e => e.Entity is BaseEntity && (e.State == EntityState.Modified || e.State == EntityState.Added)).ToList();

        var currentUsername = !string.IsNullOrEmpty(System.Web.HttpContext.Current?.User?.Identity?.Name)
            ? HttpContext.Current.User.Identity.Name
            : "Anonymous";

        foreach (var entity in entities)
        {
            if (entity.State == EntityState.Added)
            {
                ((BaseEntity)entity.Entity).CREATEDON = DateTime.UtcNow;
                ((BaseEntity)entity.Entity).CREATEDBY = currentUsername;
            }

            ((BaseEntity)entity.Entity).MODIFIEDON = DateTime.UtcNow;
            ((BaseEntity)entity.Entity).MODIFIEDBY = currentUsername;
        }
    }
1
cagedwhale

Vous avez deux options:

  • avoir une classe de base pour toutes vos classes d'entité d'entreprise qui fait le

    poco.LastModified = DateTime.Now;
    

    dans une méthode virtuelle .Save() que tous les autres devraient appeler

  • utilisez un déclencheur dans la base de données

Je ne pense pas qu'il y ait une autre méthode raisonnablement sûre et facile pour y parvenir.

1
marc_s

Nous pouvons utiliser une classe partielle et remplacer la méthode SAVECHANGES pour y parvenir.

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;


namespace TestDatamodel
{
    public partial class DiagnosisPrescriptionManagementEntities
    {
        public override int SaveChanges()
        {
            ObjectContext context = ((IObjectContextAdapter)this).ObjectContext;

            foreach (ObjectStateEntry entry in
                     (context.ObjectStateManager
                       .GetObjectStateEntries(EntityState.Added | EntityState.Modified)))
            {                    
                if (!entry.IsRelationship)
                {
                    CurrentValueRecord entryValues = entry.CurrentValues;
                    if (entryValues.GetOrdinal("ModifiedBy") > 0)
                    {
                        HttpContext currentContext = HttpContext.Current;
                        string userId = "nazrul";
                        DateTime now = DateTime.Now;

                        if (currContext.User.Identity.IsAuthenticated)
                        {
                            if (currentContext .Session["userId"] != null)
                            {
                                userId = (string)currentContext .Session["userId"];
                            }
                            else
                            {                                    
                                userId = UserAuthentication.GetUserId(currentContext .User.Identity.UserCode);
                            }
                        }

                        if (entry.State == EntityState.Modified)
                        {
                           entryValues.SetString(entryValues.GetOrdinal("ModifiedBy"), userId);
                           entryValues.SetDateTime(entryValues.GetOrdinal("ModifiedDate"), now);
                        }

                        if (entry.State == EntityState.Added)
                        {
                            entryValues.SetString(entryValues.GetOrdinal("CreatedBy"), userId);
                            entryValues.SetDateTime(entryValues.GetOrdinal("CreatedDate"), now);
                        }
                    }
                }
            }

            return base.SaveChanges();
        }
    }
}
0
Md. Nazrul Islam