2009-06-17 11 views
9

Quelqu'un sait-il un moyen de faire quelque chose de similaire à Django's signals en utilisant LINQ to SQL? J'essaie d'enregistrer quand de nouvelles lignes sont insérées et quand certaines colonnes sont mises à jour, donc je veux juste pre_save et post_save signaux.Signaux dans Linq à Sql?

je peux le faire de sorte avec certains modèles en utilisant les définis comme partials OnFooIDChanging() et OnFooIDChanged() (où FooID est une clé primaire), mais cela ne fonctionne pas pour les modèles dont la clé primaire est une identité, ou définie par code.

Pour ceux-ci, je pourrais peut-être utiliser OnValidate(), mais ce ne serait pre_save, et il fait face à la base de données difficile, puisque OnValidate() est appelé à partir DBContext.SubmitChanges(), ce qui bien sûr ne permet pas une seconde SubmitChanges() d'être appelé à partir à l'intérieur, rendant post_save fondamentalement impossible autant que je peux voir.

Répondre

1

Ok, je suis allé complètement dans le trou du lapin sur celui-ci, mais je pense avoir une solution assez cool:

Tout d'abord, ajouter un gestionnaire d'événements à votre contexte de données qui permettra de recueillir tous le poste -Sauvegardez les signaux et masquer la méthode Dispose afin que nous puissions appeler l'événement juste avant de disposer. (Notez que j'utilise le mot-clé new au lieu de override. Cela fait appeler l'événement possible.)

partial class MyDataContext 
{ 
    internal delegate void PostSaveHandler(); 
    internal event PostSaveHandler PostSave; 

    // This method hides the underlying Dispose because we need to call PostSave. 
    public new void Dispose(bool disposing) 
    { 
     // Obviously necessary error handling omitted for brevity's sake 
     PostSave(); 
     base.Dispose(disposing); 
    } 
} 

Ensuite, écrire un T4 Template qui inspecte le fichier dbml qui génère LINQ to SQL pour vous.

<# 
var dbml = XDocument.Load(@"MyDataContext.dbml"); 
var name = XName.Get("Type", "http://schemas.microsoft.com/linqtosql/dbml/2007"); 
var tables = from t in dbml.Descendants(name) select t.Attribute("Name").Value; 
foreach(var table in tables) 
{ 
#> 
    ... 

Pour chaque table de la base de données (et donc chaque classe partielle), ajoutez le partiel aux méthodes suivantes.

public partial class Foo 
{ 
    internal void OnInsert(MyDataContext db) { 
     PreInsert(); 
     db.PostSave += delegate { PostInsert(); }; 
    } 
    internal void OnUpdate(MyDataContext db) { 
     PreUpdate(); 
     db.PostSave += delegate { PostUpdate(); }; 
    } 
    internal void OnDelete(MyDataContext db) { 
     PreDelete(); 
     db.PostSave += delegate { PostDelete(); }; 
    } 
    partial void PreInsert(); 
    partial void PostInsert(); 
    partial void PreUpdate(); 
    partial void PostUpdate(); 
    partial void PreDelete(); 
    partial void PostDelete(); 
} 

// repeat for all tables 

Ajoutez également un autre partial MyDataContext via T4. Cela ajoutera des définitions aux méthodes partielles que Linq to SQL vous donne (comme mentionné par Merritt). Cachez ces fichiers dans un endroit sûr, afin que personne ne tente de les manipuler.

Votre structure de signaux est configurée. Maintenant vous pouvez écrire vos signaux. Mettez ces soit en Foo.cs ou tous ensemble dans un fichier Signals.cs:

partial class Foo 
{ 
    partial void PostInsert() 
    { 
     EventLog.AddEvent(EventType.FooInserted, this); 
    } 
} 

C'est un peu complexe, donc si quelque chose n'a pas de sens, s'il vous plaît laisser un commentaire et je ferai de mon mieux pour y remédier.

1

J'ai une solution beaucoup plus facile que ce que je l'ai déjà posté qui ne fonctionne pas de toute façon: SubmitChanges override (ConflictMode failureMode):

partial class MyDataContext 
{ 
    // SubmitChanges() calls this method after inserting default value for param 
    public override void SubmitChanges(ConflictMode failureMode) 
    { 

      // Pre-Submit Changes 

      //Updates    
      for (int changeCounter = 0; changeCounter < this.GetChangeSet().Updates.Count; changeCounter++) 
      {     
       var modifiedEntity = this.GetChangeSet().Updates[changeCounter];     
       // Do something, for example: 
       // var tableXEntry = new TableX() { Prop1 = "foo" }; 
       // this.tableXEntries.InsertOnSubmit(tableXEntry); 
      }    

      //Inserts    
      for (int changeCounter = 0; changeCounter < this.GetChangeSet().Inserts.Count; changeCounter++)    
      {     
       object modifiedEntity = this.GetChangeSet().Inserts[changeCounter];     
       // Do Something 
      } 


      // Submit Changes 
      base.SubmitChanges(failureMode); 


      // Post Submit Changes 

      //Updates    
      for (int changeCounter = 0; changeCounter < this.GetChangeSet().Updates.Count; changeCounter++) 
      {     
       var modifiedEntity = this.GetChangeSet().Updates[changeCounter];     
       // Do something, for example: 
       // var tableXEntry = new TableX() { Prop1 = "foo" }; 
       // this.tableXEntries.InsertOnSubmit(tableXEntry); 
      }    

      //Inserts    
      for (int changeCounter = 0; changeCounter < this.GetChangeSet().Inserts.Count; changeCounter++)    
      {     
       object modifiedEntity = this.GetChangeSet().Inserts[changeCounter];     
       // Do Something 
      } 
} 

Avec Entity Framework, je fais quelque chose de semblable à ce que vous essayez à faire: après avoir sauvegardé une entité, j'insère une nouvelle entrée dans une table différente à des fins d'audit (c'est une copie de l'entité avant les changements). il existe un événement SaveChanges() sur le conteneur d'entités EF (comme le contexte de données) qui vous permet d'ajouter au contexte actuel avant que les modifications ne soient sauvegardées.