1

Ces deux dernières semaines ont été ma première expérience avec Castle ActiveRecord, et avec le pattern ActiveRecord en général. Je travaille sur un grand système qui l'utilise fréquemment, et j'ai trouvé d'étranges problèmes de transaction SQL (comme celui ci-dessous) pendant que je travaille dessus. Je vais vous donner une version simplifiée de celui qui m'a totalement déconcerté:Castle ActiveRecord - Toujours mettre à jour les enfants, pourquoi?

LE CONTEXTE:

J'ai une classe ActiveRecord, nous allons l'appeler l'utilisateur.

Disons que cet utilisateur a beaucoup d'objets "Pet".

[ActiveRecord] 
public class User: PersistentBase<User> 
{ 
//... 
     [PrimaryKey] 
    public long Id 
    { 
     get; 
     set; 
    } 

    /// <summary> 
    /// Date and time the object was first persisted to the database 
    /// </summary> 
    [Property, ValidateNonEmpty] 
    public DateTime CreationDate 
    { 
     get; 
     set; 
    } 

    /// <summary> 
    /// Date and time the object was last persisted to the database 
    /// </summary> 
    [Property, ValidateNonEmpty] 
    public DateTime ModificationDate 
    { 
     get; 
     set; 
    } 
    /// <summary> 
    /// Property used for optimistic concurrency 
    /// </summary> 
    [Version] 
    public int LockCount { get; set; } 

[HasMany(typeof(Pet), Cascade = ManyRelationCascadeEnum.SaveUpdate, Lazy = false, OrderBy = "Id")] 
     public IList<Pet> Pets { get; private set; } 

//... 

    protected override bool BeforeSave(IDictionary state) 
    { 
     bool retval = base.BeforeSave(state); 
     DateTime now = DateTime.Now; 
     state["CreationDate"] = now; 
     state["ModificationDate"] = now; 
     return retval; 
    } 

    /// <summary> 
    /// Called when a dirty object is going to be updated in the db. Use this 
    /// hook to update ModificationDate. 
    /// </summary> 
    /// <param name="id"></param> 
    /// <param name="previousState"></param> 
    /// <param name="currentState"></param> 
    /// <param name="types"></param> 
    /// <returns></returns> 
    protected override bool OnFlushDirty(object id, IDictionary previousState, IDictionary currentState, IType[] types) 
    { 
     bool retval = base.OnFlushDirty(id, previousState, currentState, types); 
     currentState["ModificationDate"] = DateTime.Now; 
     return retval; 
    } 

} 

[ActiveRecord] 
public class Pet : PersistentBase<Pet> 
{ 

    [PrimaryKey] 
    public long Id 
    { 
     get; 
     set; 
    } 

    /// <summary> 
    /// Date and time the object was first persisted to the database 
    /// </summary> 
    [Property, ValidateNonEmpty] 
    public DateTime CreationDate 
    { 
     get; 
     set; 
    } 

    /// <summary> 
    /// Date and time the object was last persisted to the database 
    /// </summary> 
    [Property, ValidateNonEmpty] 
    public DateTime ModificationDate 
    { 
     get; 
     set; 
    } 
    /// <summary> 
    /// Property used for optimistic concurrency 
    /// </summary> 
    [Version] 
    public int LockCount { get; set; }  

//... 

[BelongsTo("OwnerId")] 
public User User { get; set; } 

//... 

    protected override bool BeforeSave(IDictionary state) 
    { 
     bool retval = base.BeforeSave(state); 
     DateTime now = DateTime.Now; 
     state["CreationDate"] = now; 
     state["ModificationDate"] = now; 
     return retval; 
    } 

    /// <summary> 
    /// Called when a dirty object is going to be updated in the db. Use this 
    /// hook to update ModificationDate. 
    /// </summary> 
    /// <param name="id"></param> 
    /// <param name="previousState"></param> 
    /// <param name="currentState"></param> 
    /// <param name="types"></param> 
    /// <returns></returns> 
    protected override bool OnFlushDirty(object id, IDictionary previousState, IDictionary currentState, IType[] types) 
    { 
     bool retval = base.OnFlushDirty(id, previousState, currentState, types); 
     currentState["ModificationDate"] = DateTime.Now; 
     return retval; 
    } 

} 

Maintenant, les deux ont des champs automatiques d'identification (pris en charge par SQL Server 2005).

PROBLÈME:

Si j'aller de l'avant et ajouter un nouvel animal de compagnie à un utilisateur qui a déjà animaux existants et enregistrer l'utilisateur, je vois si je lance le Générateur de profils SQL que chacun d'animaux de compagnie a eu UPDATE a appelé sur eux ... mais pas un seul a été changé du tout. J'ai jeté des points d'arrêt partout, et j'ai constaté que, lorsque je sauve l'utilisateur, chacun des familiers a appelé "OnFlushDirty" (encore une fois, bien qu'ils n'aient jamais changé). Un processus externe qui examine (et parfois modifie) ces utilisateurs et animaux domestiques finit par causer de graves problèmes de transaction, qui pourraient être complètement évités si le scénario ci-dessus insérait SEULEMENT l'animal de compagnie qui avait été ajouté (et non les animaux de compagnie). n'ont pas été changés).

LA QUESTION:

FAIS je quelque chose au-dessus de c'est un grand non-non en termes de faire en sorte qu'une telle situation ne se produise pas?

Nous vous remercions de l'aide que vous pouvez fournir!

* EDIT 1: OnFlushDirty a previousState._values ​​null *

EDIT: OH! J'ai presque oublié la partie la plus étrange de tous!

Lorsque OnFlushDirty est appelé à ces animaux de compagnie, la previousState existe et currentState ... ils ont tous deux (étant un dictionnaire) ont une _values ​​variable interne qui devrait avoir les valeurs des états précédents et actuels ...

... seulement currentStates a cette variable renseignée. La variable "_values" de previousState est définie sur "null". Notez que c'est sur tous les animaux de compagnie qui existaient auparavant. previousState devrait toujours être peuplé de quelque chose, non?

* EDIT 2: Après avoir remplacé Propriétés Auto ... *

I a remplacé la liste Automobile avec un membre privé traditionnel avec accesseurs de propriété. Cela ne semblait pas faire la différence. J'ai mis NHProfiler sur le système, et j'ai constaté que NHProfiler ne pouvait pas se connecter à mon application Web si je l'utilisais via IIS (j'utilise IIS7/Win7 avec Visual Studio 2008).Je me suis dit que j'essaierais de passer à l'utilisation de "ASP.NET Development Server" de Visual Studio pour voir si NHProfiler verrait alors l'application.

Deux choses se sont passées quand je l'ai fait:

1) NHProfiler vu mon application et a commencé à recueillir des données 2) Les multiples MISES À JOUR fait sur les enfants sont partis

Cependant, le changement de retour à IIS7/Win7, les mises à jour multiples continuent d'arriver. Cela signifie-t-il que c'est potentiellement une sorte de problème de configuration? Autant que je sache, rien dans ma configuration ne devrait changer autre que l'URL vers laquelle je navigue (http://localhost dans IIS, http://localhost:(some_random_port) avec le serveur de développement ASP.NET) lors de l'utilisation des différents types de serveurs. Alors pourquoi les deux situations ci-dessus changent-elles soudainement?

Répondre

1

IIRC château activerecord repose sur NHib.

Dans un tel cas, une liste a une sémantique spéciale dans Nhib, car elle a l'intention d'être une liste ordonnée. Ainsi, même si vous n'avez pas de propriété de position définie, elle met à jour les éléments de la liste ordonnée comme si elle avait cette colonne.

Je n'ai pas vérifié cela, mais IIRC, c'est votre problème.

+0

Oh, intéressant! Je vais essayer de remplacer cela par un mécanisme plus générique ... ICollection arrêterait-il le problème? Ou peut-être simplement IEnumerable? – EdgarVerona

+0

Consultez ce lien: https://www.hibernate.org/hib_docs/nhibernate/html_single/#collections-persistent et notez le paragraphe commençant par "Tous les types de collection sauf ISet et sac ont une colonne d'index" Il y a plus d'info à avoir, mais je crois que cela devrait vous aider à démarrer. –

+0

Ah, merci! Je l'apprécie profondément! Je lis maintenant les docs, je vais essayer et vous dire comment ça se passe! – EdgarVerona

1

C'est très simple. ActiveRecord ne peut pas gérer les autoconfigurations C# dans les collections (en raison du type d'interface). Utilisez un champ de support à la place et l'initialiser avec

private IList<Pet> pets = new List<Pet>(); 

-Markus

+0

Oh, je ne savais pas ça! Est-ce que ça se trouve dans la documentation quelque part? Je n'avais pas vu de mention à ce sujet, mais c'est vraiment bon à savoir Je vais essayer dès que j'aurai Connaissez-vous de bonnes ressources où je peux en apprendre plus sur ces "gotchas" dans ActiveRecord? – EdgarVerona

+0

Il doit y avoir de bonnes ressources quelque part sur ce ... Je viens de le faire Je ne cherche pas au bon endroit dans les documents du château, ou ils manquent de cette information ... et je ne trouve pas une bonne référence externe qui m'indiquerait des choses comme ça . = ( – EdgarVerona

+0

Oui, ce n'était pas ça, j'ai remplacé toutes les autoproperties de la collection dans le programme par des propriétés explicites, et ça n'a pas fait de différence ... ça soulève toujours l'évènement OnFlushDirty pour les enfants inchangés, avec previousState._value toujours null. – EdgarVerona

1

Il arrive que veille prolongée, vous devez utiliser la fusion() pour qu'Hibernate « charge » les données previuos de l'objet détaché, dans le château l'équivalent est la méthode SaveCopy().

Questions connexes