2010-11-17 13 views
1

J'ai un déclencheur à des fins d'audit qui nécessite la présence des colonnes d'audit dans chaque instruction de mise à jour. Toutefois, LINQ to SQL enverra uniquement les colonnes d'une UPDATE qui a été modifiée. Étant donné que parfois le même utilisateur peut éditer une colonne (et donc la valeur d'audit de "UpdatedBy" serait la même) LINQ to SQL rencontrera des erreurs de déclenchement lors de la tentative de mise à jour.Comment puis-je contraindre LINQ à SQL pour toujours inclure une colonne dans les instructions UPDATE?

J'ai perçai réflecteur et remarquez que SubmitChanges dans DataContext est d'utiliser ChangeTracker, mais je ne peux pas travailler s'il y a une bonne façon de convaincre LINQ to SQL que la colonne a changé et doit donc être inclus. Le pire des cas serait d'inclure tous les champs dans les instructions de mise à jour, indépendamment des changements. Cependant, la référence ChangeTracker est enterrée dans le DataContext et ne nous permet pas d'injecter notre propre ChangeTracker. Je ai joué avec l'idée de détacher et de rattacher l'entité, mais cela semble au mieux alambiqué. Si quelqu'un a une idée, je l'apprécierais. À votre santé.

+1

Vous voudrez peut-être regarder [cette question] (http://stackoverflow.com/questions/1560513/can-you-convince -a-datacontext-pour-traiter-une-colonne-comme-toujours-sale) –

+0

Merci, n'a pas tout à fait le bon mot, mais c'est le * exact * même problème. À titre de référence, je peux me débrouiller si j'utilise deux contextes de données, ou si je fais deux mises à jour (peut-être la seconde qui suit la table d'audit et modifie le dernier enregistrement).), mais je ne peux pas trouver un moyen d'utiliser un contexte de données car vous ne pouvez pas attacher une instance du même ID sur le contexte. La réflexion pourrait être la bonne. –

+0

Tentative de fermeture, car il s'agit d'une copie de votre question Marc. Pourriez-vous poster votre commentaire en réponse afin que je puisse l'accepter? Semble la meilleure solution jusqu'à présent. –

Répondre

1

solution la plus simple:

Vous devez désactiver la vérification de la concurrence (UpdateCheck.Never) sur toutes les colonnes (vous pouvez changer cliquez dans le concepteur, ou modifier votre modèle si vous utilisez des modèles T4). Alternativement, vous devrez copier chaque champ dans l'exemple ci-dessous (pourrait générer une méthode de modèle pour ce faire).

Dans tous les cas:

MyDataContext db = new MyDataContext(); 
MyDataContext db2 = new MyDataContext(); 
Car betsy = db.Cars.First(c => c.Name == "Betsy"); 
Car betsy2 = new Car(); 
betsy2.Id= betsy.Id; 
db2.Cars.Attach(betsy2); 

/* Could be GetName() or whatever, but allows same */ 
betsy2.UpdatedBy = betsy.UpdatedBy; 
betsy2.OtherField = "TestTestTest"; 
db2.SubmitChanges(); 

D'autres "solutions" ci-dessous. Malheureusement, tout cela implique une double mise à jour, et ne fonctionne pas vraiment si vous faites une suppression. Comme vous êtes en train d'auditer, vous sauvegardez probablement dans une autre table quelque part où vous voulez patcher votre déclencheur en mettant à jour la dernière entrée d'audit avec '|' quand vous obtenez votre deuxième non '|' ou quelque chose (si c'est toujours dans une transaction peut-être pas la fin du monde). C'est tout non-idéal cependant.

La solution désordre:

MyDataContext db = new MyDataContext(); 
Car car = db.Cars.First(c => c.Id == 1); 
car.Name = "Betsy"; 
car.UpdatedBy = String.Format("{0}|{1}", car.UpdatedBy, DateTime.Ticks); 
db.SubmitChanges(); 

car.UpdatedBy = car.UpdatedBy.Substring(0, car.UpdatedBy.LastIndexOf('|')); 
db.SubmitChanges(); 

La solution un peu mieux:

public partial class MyDataContext : DataContext 
{ 
    public override void SubmitChanges(ConflictMode failureMode) 
    { 
     ChangeSet cs = base.GetChangeSet(); 
     foreach (object e in cs.Updates.Union(cs.Inserts)) 
     { 
      PropertyInfo updatedBy = e.GetType() 
       .GetProperties() 
       .FirstOrDefault(p => p.Name == "UpdatedBy"); 

      if (updatedBy == null) 
      { 
       base.SubmitChanges(failureMode); 
       return; 
      } 

      string updatedByValue = updatedBy.GetValue(e, null); 
      string tempValue = String.Format("{0}|{1}", updatedByValue, DateTime.Ticks; 
      updatedBy.SetValue(e, tempValue); 
      base.SubmitChanges(failureMode); 

      updatedBy.SetValue(e, tempValue.Substring(0, tempValue.LastIndexOf('|'))); 
      base.SubmitChanges(failureMode); 
     } 
    } 
} 

La meilleure solution de ce type que j'ai trouvé (si vous utilisez les modèles T4 pour LINQ to SQL soit encore plus simple):

Soit créer un fichier de classe partielle implémentant une interface commune pour chaque type d'entité auditée, soit modifier le modèle afin que les entités auditées implémentent une interface commune, par exemple:

public interface IAuditable 
{ 
    string UpdatedBy { get; set; } 
} 

ensuite modifier vos SubmitChanges comme suit:

public partial class MyDataContext : DataContext 
{ 
    public override void SubmitChanges(ConflictMode failureMode) 
    { 
     ChangeSet cs = base.GetChangeSet(); 
     foreach (object e in cs.Updates.Union(cs.Inserts)) 
     { 
      if (typeof(IAuditable).IsAssignableFrom(e)) 
      { 
       string tempValue = String.Format("{0}|{1}", ((IAuditable)e).UpdatedBy, DateTime.Ticks); 
       ((IAuditable)e).UpdatedBy = tempValue; 
       base.SubmitChanges(failureMode); 

       ((IAuditable)e).UpdatedBy = tempValue.Substring(0, tempValue.LastIndexOf('|')); 
       base.SubmitChanges(failureMode); 
      } 
     } 
    } 
}