NHibernate: controlling Bidirectional ManyToMany. Adding from one entity and removing from the other entity - nhibernate

I have 2 tables with manyToMany relationship. user can have many groups and groups can have many users.
public class User
{
public virtual Guid Id { get; set; }
public virtual UInt64 UserId { get; set; }
public virtual IList<Group> Groups{ get; set; }
}
public class Group
{
public virtual Guid Id { get; set; }
public virtual Guid GroupGuid { get; set; }
}
I use Fluent NHibernate to AutoMap them as follows:
.Override<User>(obj => obj.HasManyToMany(x => x.Groups).Table("UserToGroup").Cascade.AllDeleteOrphan())
As a result Nhibernate produce 3 tables:
1. Users - Mapped object table
2. Groups Mapped Object table
3. UserToGroup - generated association table
The requirement is to have the following functionality on these tables:
1. AddUser(UInt64 UserId, IEnumerable groups)
2. RemoveGroup(GroupGuid groupGuild)
3. Dictionary> GetGroupToUsers();
AddUser method is work Ok, All table updated OK:
* Insert of new user in User table. OK
* Insert of new group in Group table. OK
* Insert of new relations recods in UserToGroup table. OK
I use the following code to add a User (including groups):
public void AddUser(User userData, IList<Guid> groupIds)
{
User user = new User();
user.UserId = userData.UserId;
IList<User> Users = new List<Users();
Users.Add(user);
IList<Group> groups = new List<Group>();
foreach (Guid groupId in groupIds)
{
Group grp = new Group();
grp.GroupGuid = groupId;
grp.Users = Users;
groups.Add(grp);
}
user.Groups = groups;
Session.Save(user);
}
I use the following code to Remove group:
public void RemoveGroup(GroupGuid groupGuild)
{
IList<Group> groupsToRemove = Session.QueryOver<Group>().Where(
row => row.GroupGuid == groupGuild).List();
foreach (Group group in groupsToRemove)
{
group.Users.Clear();
Session.Delete(group);
}
}
RemoveGroup method failed with the following exception:
threw exception:
NHibernate.Exceptions.GenericADOException: could not delete: [StorageObject.Group#11111111-1111-1111-1111-111111111111][SQL: DELETE FROM "Group" WHERE Id = ?] ---> System.Data.SqlServerCe.SqlCeException: The primary key value cannot be deleted because references to this key still exist. [ Foreign key constraint name = FK4B29424018FA0CD4 ]
I understand that Group table can't be delete since the reference UserToGroup table point to Group table.
1st question: how can I make hibernate to understand that I want to be able to controller this manyToMany relation from both side:
Add users with groups - from user side.
And delete groups - from group side
I tried to move Inverse to the User table but in this case when I add a User the association table is not populate.
What is the best way to define this kind of manyToMany relation?
Update:
Another way I tried is to have List of Users also in Groups in this configuration removeGroup is wokring but addUser in not working for specific scenario:
public class Group
{
public virtual Guid Id { get; set; }
public virtual Guid GroupGuid { get; set; }
public virtual IList<User> Users{ get; set; }
}
public class User
{
public virtual Guid Id { get; set; }
public virtual UInt64 UserId { get; set; }
public virtual IList<Group> Groups{ get; set; }
}
Now I have a bidirectional many to many.
My Fluent mapping is:
.Override<User>(obj => obj.HasManyToMany(x => x.Groups).ParentKeyColumn("UserId").ChildKeyColumn("GroupId").Table("UserToGroup").Cascade.SaveUpdate()
.Override<Group>(obj => obj.HasManyToMany(x => x.Users).ParentKeyColumn("GroupId").ChildKeyColumn("UserId").Table("UserToGroup")
If I use this configuration RemoveGroup works fine but AddUser is not working for the following case:
When adding 2 Users which contains the same Group the association table delete the first relation and holds just the last reference instead of having both relations.
Please advise.

Try to add an inverse collection in Group:
public class Group
{
public virtual Guid Id { get; set; }
public virtual Guid GroupGuid { get; set; }
public virtual IList<User> Users { get; set; }
}
In your mapping:
.Override<Group>(obj => obj.HasManyToMany(x => x.Users).Table("UserToGroup").Inverse().Cascade.SaveUpdate())
Your Remove method:
public void RemoveGroup(GroupGuid groupGuild)
{
IList<Group> groupsToRemove = Session.QueryOver<Group>().Where(
row => row.GroupGuid == groupGuild).List();
foreach (Group group in groupsToRemove)
{
group.Users.Clear(); // Removes references from all users pointing to that group
Session.Delete(group);
}
}
When you commit the transaction, the references should be automatically removed. I never tried this by myself, but it might work for you.

Related

Possible to dynamically join a related model in Entity Framework Core?

Say there's a project that involves a lot of tables, all of which reference a single table that holds a field called group ID. However, not all of the tables reference this single table directly.
For example, the db contains a garages table, and each garage is referenced by rows in a cars table, and each car is referenced by rows in a tire table. I want to organize the data into two groups, group1 and group2. Each garage has a group ID, however the cars and tires do not. Here is a diagram of the tables:
As you can see, assets in group 1 are highlighted red, and assets in group 2 are highlighted yellow. But not all rows highlighted necessarily have a group ID. You can imagine how this may go even longer, with hub caps having forgien keys for their respective tires, bolts with forgien keys to their respective hub caps, etc.
Is there a way to dynamically call an EF query that can get the group ID from the related garage, given EITHER a child car, tire, hub cap, and so on? Implementation might be something like this:
var tire = Context.Tires.where(t => t.ID == 3).FirstOrDefault<Tire>();
tire.findShortestJoinToEntity(entity => entity.GetProperty("GroupID") != null);
// Returns 2, the group of "Toyota Tires"
In more technical terms, it would need to recursively check for any forgien keys in the passed in model, then all the forgein keys of the referenced models, all their referenced models, etc. until there are either no more forgein keys or a field with the passed in name is found.
I think you can do it using reflection, it is not the ideal solution due to possible performance issues, but it does what you are looking for.
The key idea here is having all entity classes implementing a common interface IEntity so we can call a recursive method over any entity. Also assumes that we have both parentId (foreign key) and parent object in any entity, although you can tweak the method to use only the parentId, I have put the parent object only to take advantage of EF tracking mechanism linking parents and child entities
public interface IEntity
{
int Id { get; set; }
}
public class Group : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Garage : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual int GroupId { get; set; }
public virtual Group Group { get; set; }
}
public class Car : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public virtual int GarageId { get; set; }
public virtual Garage Garage { get; set; }
}
// ... other entity classes here
public static class EntityExtensions
{
public static int FindGroupId(this IEntity entity, Context context, string propertyName = null)
{
var groupId = 0;
if (entity != null)
{
var entityType = entity.GetType();
if (entity is Group)
{
// we found it
groupId = propertyName == null
? entity.Id
: (int)entityType.GetProperty(propertyName).GetValue(entity);
}
else
{
// look into properties for parent entities
foreach (var property in entityType.GetProperties())
{
if (property.GetGetMethod().IsVirtual
&& property.PropertyType.GetInterface(nameof(IEntity)) != null)
{
// it is parent entity
var parentEntity = property.GetValue(entity) as IEntity;
if (parentEntity == null)
{
// if parent entity is null, let's get it from db
var parentIdName = $"{property.PropertyType.Name}Id";
var parentId = (int)entityType.GetProperty(parentIdName).GetValue(entity);
parentEntity = context.Find(property.PropertyType, parentId) as IEntity;
}
groupId = FindGroupId(parentEntity, context, propertyName);
}
if (groupId > 0)
{
// we found it
break;
}
}
}
}
return groupId;
}
}

Get current user logged in in Custom Authentication MVC 4

I have created a simple login table without simplemembership because in my project i don't need to have a register, the users need to be created automatically in the database. (Made following this tutorial).
I have a model Login:
public class Login
{
public virtual int UserId { get; set; }
public virtual string Username { get; set; }
public virtual string Password { get; set; }
public virtual List<Enrollment> Enrollments { get; set; }
}
and Enrollments:
public class Enrollment
{
public virtual int EnrollmentId { get; set; }
public virtual string Name { get; set; }
public virtual List<Login> Users { get; set; }
}
And i have a table Enrollment_Login because of the many-to-many relationship.
I need to create a view where i show a list of enrollments that the user logged in is "registered".
I have this query:
var query= from name in db.Enrollments
where name.Logins.Any(c=>c.LoginId==??)
select name;
If this query is right, how can i get the current user logged in?
You can't get the LoginId (which I think is a foreign key of your Login entity in Enrollement entity)
When user is authenticated the the name of that user is stored in this.User.Identity.Name if you're in your action method.
The name in your case is the UserName property in Login entity.
So you must write the following code :
var currentLogin = db.Logins.Single(u => u.UserName = this.User.Identity.Name);
var query= from name in db.Enrollments
where name.Logins.Any(c=>c.LoginId==currentLogin.Id)
select name;
Or in one query:
var query= from enrollement in db.Enrollments
join login in db.Logins on enrollement.LoginId equals login.UserId
where login.UserName == this.User.Identity.Name
select enrollement;

Fluent Nhibernate Cascade.None() results in HasOne foreign key relationship not being persisted

I'm having issues with Nhibernate persisting a HasOne Relationship for one of my entities with Cascade.None() in effect. My domain model involves 4 classes listed below.
public class Project
{
public virtual int Id {get;set;}
public virtual IList<ProjectRole> Team { get; protected set; }
}
public class ProjectRole
{
public virtual int Id { get; set; }
public virtual User User { get; set; }
public virtual Role Role { get; set; }
}
public class Role
{
public virtual int Id { get; set; }
public virtual string Value { get; set; }
}
public class User
{
public virtual int Id { get; protected set; }
public virtual string LoginName { get; set; }
}
So basically we have projects, which have a list of ProjectRoles available from the Team property. Each ProjectRole links a User to the specific Role they play on that project.
I'm trying to setup the following cascade relationships for these entities.
project.HasMany<ProjectRoles>(p=> p.Team).Cascade.All()
projectRole.HasOne<Role>(r => r.Role).Cascade.None()
projectRole.HasOne<User>(r => r.User).Cascade.SaveUpdate()
I've used fluent nhibernate overrides to setup the cascades as above, but I'm finding that the line
projectRole.HasOne<Role>(r => r.Role).Cascade.None()
is resulting in the ProjectRole.Role property not being saved to the database. I've diagnosed this be looking at the SQL Generated by Nhibernate and I can see that the "Role_id" column in the ProjectRoles table is never set on update or insert.
I've also tried using
projectRole.HasOne<Role>(r => r.Role).Cascade.SaveUpdate()
but that fails as well. Unfortunately leaving it Cascade.All() is not an option as that results in the system deleting the Role objects when I try to delete a project role.
Any idea how to setup Cascade.None() for the ProjectRole-> Role relationship with out breaking persistence.
HasOne is for a one-to-one relationship which are rare. You want to use References to declare the one side of a one-to-many relationship. Making some assumptions about your domain model, the mapping should look like:
project.HasMany<ProjectRoles>(p=> p.Team).Inverse().Cascade.AllDeleteOrphan()
projectRole.References<Role>(r => r.Role);
projectRole.References<User>(r => r.User);
See also this question about the difference between HasOne and References.

Entity Framework Code First - using UserId key of Memberships/Users table as Foreignkey in a custom table

I am trying to create a SQL table to include additional user information.
I want it to be created by Migrations in VS2012 (package console => update-database).
I see that there are two tables using UserId column as key: Memberships and Users tables.
I am trying to define the following class and map it to SQL table via Migrator:
[Key]
[Column(Order = 0)]
[ForeignKey("User")]
public Guid **UserId** { get; set; }
[Key]
[Column(Order = 1)]
[ForeignKey("Category")]
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
**public virtual User User { get; set; }**
Although it is obvious in the Users SQL table that UserId column is the key, I get this error message:
\tSystem.Data.Entity.Edm.EdmEntityType: : EntityType 'User' has no key defined. Define the key for this EntityType.
\tSystem.Data.Entity.Edm.EdmEntitySet: EntityType: EntitySet 'Users' is based on type 'User' that has no keys defined.
What I am missing here? Might be that Microsoft.VisualBasic.ApplicationServices.User / System.Web.Security.MembershipUser classes weren't necessarily mapped to the tables this way and vise versa, and therefore the UserId property is not declared is Key dataannotation?
I am open for other solutions for this problem.
Big Thanks!
I am currently using asp.net mvc4 with Azure db.
When I want to use UserId in other tables from UserProfile. Notice in AccountModels there is class UserProfile:
[Table("UserProfile")]
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
}
Now, let's say your category is created by certain user, you can link category entry to user in following way.
[Table("Category")]
public class Category
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int CategoryId { get; set; }
public UserProfile CreatorUserId { get; set; }
}
Otherwise, you can always use UserProfile as model.

how to set key column im many to many relation with fluent nhibernate

I am using flunet nhibernate and in a many to many relation I need to have key column for the table between these two entities
HasManyToMany(p => p.Signers)
//.Cascade.AllDeleteOrphan()
.Table("PersonnelDocumentSigner")
.Schema("personnel");
public partial class PersonnelDocument
{
private IList<Position> _signers;
virtual public IList<Position> Signers
{
get
{
if (_signers == null)
_signers = new List<Position>();
return _signers;
}
set
{
_signers = value;
}
}
}
the created table just consist of these two columns:PersonnelDocumentId,PositionId
but I need a column Id for this connector table "PersonnelDocumentSigner"
how exactly I can assign it?
link-tables like your PersonnelDocumentSigner normally dont need an Id column because PersonnelDocumentId and PositionId together are the Id/Primary key. If you still want to have additional Data in the linking Table you should Create a new Entity.
class PersonnelDocumentSigner
{
public virtual PersonnelDocument Document { get; set; }
public virtual Position Signer { get; set; }
public virtual int Id { get; set; }
// additional Properties
}
class PersonnelDocumentSignerMap : ClassMap<PersonnelDocumentSigner>
{
Table("PersonnelDocumentSigner");
CompositeId()
.KeyReference(pds => pds.Document)
.KeyReference(pds => pds.Signer);
Map(pds => pds.Id);
// additional Mapps
}