0

I have a scheduled process that updates a User entity. This User entity has a ManyToMany relationship with a Roles object.

 public class UserMappingOverride : IAutoMappingOverride<User>
 {
    public void Override(AutoMapping<User> mapping)
    {
        mapping.HasManyToMany(u => u.Roles)
            .Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.None)
            .AsSet()
            .Cascade.SaveUpdate();        
    }
 }

...where the Roles object is an ICollection of a UserRole entity.

UserRole looks like this:

public class UserRole  : EnumEntity<UserRole.Values>
{
    public static readonly UserRole MyRole = new UserRole(Values.MyRole);

    protected UserRole(){}

    protected UserRole(Values value)
        : base(value, value.GetDescription())
    {
    }

    public enum Values
    {
        MyRole = 0
    }
}

...and User (irrelevant parts omitted):

public class User : Entity
{
 private readonly ICollection<UserRole> roles = new HashSet<UserRole>();
 ...
  public virtual IEnumerable<UserRole> Roles
    {
        get
        {
            return roles.AsEnumerable();
        }
    }
}

As part of the scheduled process, according to some criteria, a UserRole may be added or removed on the User. i.e.

 var users = UserRepository.GetAll();

 foreach (var user in users)
 {

          var userRole =  new UserRole(0);
          user.roles.Add(userRole);

          var transaction = NHibernateSession.Current.BeginTransaction();

          try
          {

              if (transaction.IsActive)
              {
                  NHibernateSession.Current.SaveOrUpdate(user);
                  transaction.Commit();
              }

          }
          catch (Exception e)
          {

              if (transaction.IsActive)
              {
                  transaction.Rollback();
              }
          }
          finally 
          {
            NHibernateSession.Current.Close();
            NHibernateSession.Current.Dispose();
          }
  }

When the process runs, it does so succesfully up to a point, after which every subsequent iteration of the loop results in the following:

  NHibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: 0, of entity: MyProject.Core.Domain.UserRole
 at NHibernate.Engine.StatefulPersistenceContext.CheckUniqueness(EntityKey    key, Object obj)

So the question is - why would this error occur only after a certain point, and what can I do to resolve it? I have seen Merge mentioned on similar issues of this type - would that be applicable here?

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
user806982
  • 135
  • 4
  • 16
  • could you show your entities and the mapping of both collections (or all three in case there is UserRole entity) – Radim Köhler Oct 12 '15 at 10:11
  • @RadimKöhler added the UserRole entity and relevant parts of the User entity – user806982 Oct 12 '15 at 10:31
  • I do not understand UserRole (EnumEntity). Is it a pair object between User and Role? does it have property `User User { get; set;}` and `Role Role { get; set; }`? – Radim Köhler Oct 12 '15 at 11:17
  • @RadimKöhler There is a UserRoleToUser table which holds the actual Role enum against the User (i.e. the table has 2 columns: UserFk and UserRoleFk). We're using AutoMap, and I think this table is being populated through a Cascade (ConventionBuilder.HasManyToMany.Always(r => r.Cascade.All())) – user806982 Oct 12 '15 at 12:04
  • Well, that is my point... if there is many-to-many (and pair table with FK) we do not have inner object in play... **no `UserRole`**. Just `User` has `Roles` collection and `Role` collection can have `Users`... – Radim Köhler Oct 12 '15 at 12:05

1 Answers1

0

Just a hint maybe.

The point I see here, is that:

  • there is now Role object (just some enum)
  • therefore there is no many-to-many

If this is true, we would have objects like:

public class User : Entity
{
    ISet<UserRole> _roles = new HashSet<UserRole>();
    public virtual ISet<UserRole> Roles
    {
        get { return _roles }
    }
    ...
}

NOTE: I used IList<> to be able to maintain such collection, we can create a virtual readonly property on top of it, and make this protected

And the collection item

public class UserRole : Entity
{
    public virtual User User { get; set; }
    public RoleEnum Role { get; set; }
}

And it could be mapped with just HasMany (one-to-many)

mapping.HasMany(u => u.Roles)
    ...
    .AsSet()
    .Inverse()
    .Cascade.SaveUpdate(); 

And now it should work, we just have to assign both ends

userRole.User = user;
user.Roles.Add(userRole);
Radim Köhler
  • 122,561
  • 47
  • 239
  • 335