审计 NHibernate 中的多对多关系

发布于 2024-10-09 06:04:01 字数 1720 浏览 0 评论 0原文

我已经使用 IPreUpdateEventListenerIPreInsertEventListener 实现了监听器来审核应用程序中表的更改,并且除了我的多对多关系中没有附加数据之外,一切正常。连接表(即我没有用于连接表的 POCO)。

每个可审核对象都实现一个 IAuditable 接口,因此事件侦听器会检查 POCO 是否属于 IAuditable 类型,如果是,则记录对该对象的任何更改。查找表实现了一个IAuditableProperty接口,因此如果IAuditable POCO的属性指向查找表,则更改将记录在主POCO的日志中。

所以,问题是,我应该如何确定我正在使用多对多集合并将更改记录在我的审核表中?

编辑:我正在使用 NHibernate 2.1.2.4000

//first two checks for LastUpdated and LastUpdatedBy ommitted for brevity
else if (newState[i] is IAuditable)
{
    //Do nothing, these will record themselves separately
}
else if (!(newState[i] is IAuditableProperty) && (newState[i] is IList<object> || newState[i] is ISet))
{
    //Do nothing, this is a collection and individual items will update themselves if they are auditable
    //I believe this is where my many-to-many values are being lost
}
else if (!isUpdateEvent || !Equals(oldState[i], newState[i]))//Record only modified fields when updating
{
    changes.Append(preDatabaseEvent.Persister.PropertyNames[i])
        .Append(": ");
    if (newState[i] is IAuditableProperty)
    {
        //Record changes to values in lookup tables
        if (isUpdateEvent)
        {
            changes.Append(((IAuditableProperty)oldState[i]).AuditPropertyValue)
                 .Append(" => ");
        }
        changes.Append(((IAuditableProperty)newState[i]).AuditPropertyValue);
    }
    else
    {
        //Record changes for primitive values
        if(isUpdateEvent)
        {
            changes.Append(oldState[i])
                .Append(" => ");
        }
        changes.Append(newState[i]);
    }
    changes.AppendLine();
}

I have implemented listeners to audit changes to tables in my application using IPreUpdateEventListener and IPreInsertEventListener and everything works except for my many-to-many relationships that don't have additional data in the joining table (i.e. I don't have a POCO for the joining table).

Each auditable object implements an IAuditable interface, so the event listener checks to see if a POCO is of type IAuditable, and if it is it records any changes to the object. Look up tables implement an IAuditableProperty inteface, so if a property of the IAuditable POCO is pointing to a lookup table, the changes are recorded in the log for the main POCO.

So, the question is, how should I determine I'm working with a many-to-many collection and record the changes in my audit table?

Edit: I'm using NHibernate 2.1.2.4000

//first two checks for LastUpdated and LastUpdatedBy ommitted for brevity
else if (newState[i] is IAuditable)
{
    //Do nothing, these will record themselves separately
}
else if (!(newState[i] is IAuditableProperty) && (newState[i] is IList<object> || newState[i] is ISet))
{
    //Do nothing, this is a collection and individual items will update themselves if they are auditable
    //I believe this is where my many-to-many values are being lost
}
else if (!isUpdateEvent || !Equals(oldState[i], newState[i]))//Record only modified fields when updating
{
    changes.Append(preDatabaseEvent.Persister.PropertyNames[i])
        .Append(": ");
    if (newState[i] is IAuditableProperty)
    {
        //Record changes to values in lookup tables
        if (isUpdateEvent)
        {
            changes.Append(((IAuditableProperty)oldState[i]).AuditPropertyValue)
                 .Append(" => ");
        }
        changes.Append(((IAuditableProperty)newState[i]).AuditPropertyValue);
    }
    else
    {
        //Record changes for primitive values
        if(isUpdateEvent)
        {
            changes.Append(oldState[i])
                .Append(" => ");
        }
        changes.Append(newState[i]);
    }
    changes.AppendLine();
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

又怨 2024-10-16 06:04:01

这不会触发的原因是因为集合没有更改,即它们仍然是之前的 ICollection 实例,但是集合的内容已更改。

我自己也找过这个,事件监听器不处理这种情况。这可能已在 v3.0 中修复(但不要引用我的话)。有一些不理想的解决方法:

1)在对象上放置一个属性,该属性可以出于审计目的生成集合的字符串表示形式。

2)使集合中的项目实现接口,以便对它们进行单独审核。

编辑:还有第三个选项:

“而不是多对多,我有一个多对一的连接表,然后一对多从它进入属性表。我隐藏连接表 POCO 背后有多对多连接每一端的逻辑,但仍然必须实现该对象及其上的所有接口。”

The reason that this won't fire is because the collections haven't changed, i.e. they're still the same instance of ICollection that was there before, however the content of the collections have changed.

I've looked for this myself, and the event listeners do not handle this situation. This may have been fixed for v3.0 (but don't quote me on that). There are a couple of non ideal workarounds:

1)Put a property on the object which makes a string representation of the collection for the purposes of auditing.

2)Make the items in the collection implement the interface so they are audited individually.

Edit: There is a third option:

"Rather than a many-to-many, I have a many-to-one going to the joining table, and then a one-to-many coming from it into the property table. I hide the joining table POCO behind the logic of each of the ends of the many-to-many joins, but still have to implement the object and all the interfaces on it."

诠释孤独 2024-10-16 06:04:01

事实证明,实际上有一种方法可以通过事件侦听器来执行此操作,而无需公开连接表。您只需使事件侦听器实现 IPostCollectionRecreateEventListener 或 IPreCollectionRecreateEventListener 即可。根据我的测试,只要刷新会话,这些事件就会因更改的集合而被触发。这是我的 PostRecreateCollection 方法的事件侦听器代码。

public void OnPostRecreateCollection(PostCollectionRecreateEvent @event)
        {
            var session = @event.Session.GetSession(EntityMode.Poco);
            var propertyBeingUpdated = @event.Session.PersistenceContext.GetCollectionEntry(@event.Collection).CurrentPersister.CollectionMetadata.Role;

            var newCollectionString = @event.Collection.ToString();
            var oldCollection = (@event.Collection.StoredSnapshot as IList<object>).Select(o => o.ToString()).ToList();
            var oldCollectionString = string.Join(", ",oldCollection.ToArray());

            if (newCollectionString == oldCollectionString || (string.IsNullOrEmpty(newCollectionString) && string.IsNullOrEmpty(oldCollectionString)))
                return;

            User currentUser = GetLoggedInUser(session);
            session.Save(new Audit
            {
                EntityName = @event.AffectedOwnerOrNull.GetType().Name,
                EntityId = (int)@event.AffectedOwnerIdOrNull,
                PropertyName = propertyBeingUpdated,
                AuditType = "Collection Modified",
                EventDate = DateTime.Now,
                NewValue = newCollectionString,
                OldValue = oldCollectionString,
                AuditedBy = Environment.UserName,
                User = currentUser
            });
        }

最棘手的部分是获取正在更新的集合的名称。您必须通过 PersistenceContext 串联起来才能获取集合的 Persister,这使您可以访问它的元数据。

因为这些事件或监听器都没有被记录下来,所以我不知道除了刷新之外,这个事件是否会在其他情况下抛出,所以它有可能创建错误的审计条目。我计划在该领域做一些进一步的研究。

It turns out that there actually is a way to do this via Event Listeners without having to expose the joining tables. You just have to make your event listener implement IPostCollectionRecreateEventListener or IPreCollectionRecreateEventListener. Based on my testing these events get fired for changed collections whenever the session is flushed. Here's my event listener code for the PostRecreateCollection method.

public void OnPostRecreateCollection(PostCollectionRecreateEvent @event)
        {
            var session = @event.Session.GetSession(EntityMode.Poco);
            var propertyBeingUpdated = @event.Session.PersistenceContext.GetCollectionEntry(@event.Collection).CurrentPersister.CollectionMetadata.Role;

            var newCollectionString = @event.Collection.ToString();
            var oldCollection = (@event.Collection.StoredSnapshot as IList<object>).Select(o => o.ToString()).ToList();
            var oldCollectionString = string.Join(", ",oldCollection.ToArray());

            if (newCollectionString == oldCollectionString || (string.IsNullOrEmpty(newCollectionString) && string.IsNullOrEmpty(oldCollectionString)))
                return;

            User currentUser = GetLoggedInUser(session);
            session.Save(new Audit
            {
                EntityName = @event.AffectedOwnerOrNull.GetType().Name,
                EntityId = (int)@event.AffectedOwnerIdOrNull,
                PropertyName = propertyBeingUpdated,
                AuditType = "Collection Modified",
                EventDate = DateTime.Now,
                NewValue = newCollectionString,
                OldValue = oldCollectionString,
                AuditedBy = Environment.UserName,
                User = currentUser
            });
        }

The most tricky part is getting the name of the collection being updated. You have to chain your way through the PersistenceContext to get the Persister for the collection which gives you access to it's meta data.

Because none of these events or listeners are documented I don't know if this event gets thrown in other situations besides flush, so there's a potential it could create false audit entries. I plan to do some further research in that area.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文