Entity Framework - Activité d'audit

auditing entity entity-framework

Question

Ma base de données contient une colonne "LastModifiedUser" sur chaque table dans laquelle je souhaite collecter l'utilisateur connecté à partir d'une application qui effectue une modification. Je ne parle pas de l'utilisateur de la base de données, il s'agit donc essentiellement d'une chaîne de caractères pour chaque entité. Je voudrais trouver un moyen de le définir par défaut pour chaque entité afin que les autres développeurs ne soient pas obligés de penser à l'affecter à tout moment.

Donc, quelque chose comme ceci se produirait:

using (EntityContext ctx = new EntityContext())
{
    MyEntity foo = new MyEntity();

    // Trying to avoid having the following line every time
    // a new entity is created/added.
    foo.LastModifiedUser = Lookupuser(); 

    ctx.Foos.Addobject(foo);
    ctx.SaveChanges();
}

Réponse populaire

Il existe un moyen idéal d'accomplir cela dans EF 4.0 en utilisant ObjectStateManager.

Tout d'abord, vous devez créer une classe partielle pour votre ObjectContext et vous abonner à l' événement ObjectContext.SavingChanges . Le meilleur endroit pour s'abonner à cet événement est à l'intérieur de la méthode OnContextCreated. Cette méthode est appelée par le constructeur de l'objet de contexte et le constructeur surcharge, ce qui est une méthode partielle sans implémentation:

partial void OnContextCreated() {
    this.SavingChanges += Context_SavingChanges;
}


Maintenant, le code qui fera le travail:

void Context_SavingChanges(object sender, EventArgs e) {

    IEnumerable<ObjectStateEntry> objectStateEntries = 
        from ose 
        in this.ObjectStateManager.GetObjectStateEntries(EntityState.Added 
                                                         | EntityState.Modified)
        where ose.Entity != null
        select ose;

    foreach (ObjectStateEntry entry in objectStateEntries) {

        ReadOnlyCollection<FieldMetadata> fieldsMetaData = entry.CurrentValues
                .DataRecordInfo.FieldMetadata;

        FieldMetadata modifiedField = fieldsMetaData
            .Where(f => f.FieldType.Name == "LastModifiedUser").FirstOrDefault();

        if (modifiedField.FieldType != null) {

            string fieldTypeName = modifiedField.FieldType.TypeUsage.EdmType.Name;                    
            if (fieldTypeName == PrimitiveTypeKind.String.ToString()) {
                entry.CurrentValues.SetString(modifiedField.Ordinal, Lookupuser());
            }
        }
    }
}

Explication du code :
Ce code localise toutes les entrées ajoutées ou modifiées possédant une propriété LastModifiedUser , puis met à jour cette propriété avec la valeur provenant de votre méthode personnalisée Lookupuser () .

Dans le bloc foreach, la requête analyse en détail les valeurs actuelles de chaque entrée. Ensuite, à l'aide de la méthode Where , il examine les noms de chaque élément FieldMetaData pour cette entrée, en sélectionnant uniquement ceux dont le nom est LastModifiedUser . Ensuite, l'instruction if vérifie que la propriété LastModifiedUser est un champ String . alors il met à jour la valeur du champ.

Une autre façon de connecter cette méthode (au lieu de vous abonner à l'événement SavingChanges ) consiste à remplacer la méthode ObjectContext.SaveChanges .

À propos, le code ci-dessus appartient à Julie Lerman de son livre Framework d'entité de programmation .


EDIT pour l'auto-suivi de la mise en œuvre de POCO:

Si vous avez des POCO à suivi automatique, je modifierais d'abord le modèle T4 pour appeler la méthode OnContextCreated (). Si vous regardez votre fichier ObjectContext.tt, il existe une méthode Initialize () qui est appelée par tous les constructeurs. Il est donc un bon candidat pour appeler notre méthode OnContextCreated (). Il suffit donc de modifier le fichier ObjectContext.tt comme suit: ce:

private void Initialize()
{
    // Creating proxies requires the use of the ProxyDataContractResolver and
    // may allow lazy loading which can expand the loaded graph during serialization.
    ContextOptions.ProxyCreationEnabled = false;
    ObjectMaterialized += new ObjectMaterializedEventHandler(HandleObjectMaterialized);
    // We call our custom method here:
    OnContextCreated();
}

Et cela fera que notre OnContextCreated () sera appelé à la création du contexte.

Maintenant, si vous placez vos POCO derrière la limite de service, cela signifie que le ModifiedUserName doit venir avec le reste des données de votre consommateur de service WCF. Vous pouvez leur exposer cette propriété LastModifiedUser à mettre à jour ou si elle stocke dans une autre propriété et si vous souhaitez mettre à jour LastModifiedUser à partir de cette propriété, vous pouvez modifier le code 2nd comme suit:


foreach (ObjectStateEntry entry in objectStateEntries) {

    ReadOnlyCollection fieldsMetaData = entry.CurrentValues
            .DataRecordInfo.FieldMetadata;

    FieldMetadata sourceField = fieldsMetaData
            .Where(f => f.FieldType.Name == "YourPropertyName").FirstOrDefault();             

    FieldMetadata modifiedField = fieldsMetaData
        .Where(f => f.FieldType.Name == "LastModifiedUser").FirstOrDefault();

    if (modifiedField.FieldType != null) {

        string fieldTypeName = modifiedField.FieldType.TypeUsage.EdmType.Name;
        if (fieldTypeName == PrimitiveTypeKind.String.ToString()) {
            entry.CurrentValues.SetString(modifiedField.Ordinal,
                    entry.CurrentValues[sourceField.Ordinal].ToString());
        }
    }
}


J'espère que cela t'aides.



Related

Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow