I m facing issue to access the bridge table to get the values using nHibernate and LINQ.
I have 4 table, ROLES , MODULES , PERMISSIONS and the RoleModulePermission(bridge).
Roles contains ROLEID(pk) , ROLENAME
Modules contains MODULEID(pk) , MODULENAME
Permission contains PERMISSIONID , PERMISSIONTYPE
RoleModulePermission contains ROLEID , MODULEID and PERMISSIONID
I want to get the Modules n Permission applied to the ROLES on the basis of ROLEID.
Role Mapping
Table("tblRoles");
Id(role => role.RoleID).GeneratedBy.Identity();
Map(role => role.RoleName).Not.Nullable();
Map(role => role.IsActive).Not.Nullable();
Map(role => role.Description).Not.Nullable();
HasManyToMany(x => x.Users)
.Table("tblUserInRoles")
.ParentKeyColumn("RoleID")
.ChildKeyColumn("UserID")
.Not.LazyLoad();
HasManyToMany(x => x.Modules)
.Table("tblRolesPermission")
.ParentKeyColumn("RoleID")
.ChildKeyColumn("ModuleID")
.Not.LazyLoad();
HasManyToMany(x => x.Permissions)
.Table("tblRolesPermission")
.ParentKeyColumn("RoleID")
.ChildKeyColumn("PermissionID")
.Not.LazyLoad();
Module Mapping
Table("tblAppModules");
Id(mod => mod.ModuleID).GeneratedBy.Identity();
Map(mod => mod.ModuleName).Nullable();
Map(mod => mod.CreationDate).Nullable();
HasManyToMany(x => x.Roles)
.Table("tblRolesPermission")
.ParentKeyColumn("ModuleID")
.ChildKeyColumn("RoleID")
.Not.LazyLoad();
Permission Mapping
Table("tblPermission");
Id(p => p.PermissionID).GeneratedBy.Identity();
Map(p => p.PermissionType).Not.Nullable();
HasManyToMany(p => p.PermitRole)
.Table("tblRolesPermission")
.ParentKeyColumn("PermissionID")
.ChildKeyColumn("RoleID")
.Not.LazyLoad();
It seems that i did wrong in mapping ?
please do not assume 'AllowAccess' in tblRolesPermission
How to achieve this ?
Thanks
Mapping for ternary associations in FluentNHibernate depends from specifics of your scenario and domain model.
Basically, if you are going to have the many-to-many relations everywhere (i.e. your Module can have multiple Permissions, Role can have multiple Permissions and Role can have multiple Modules), you'll need to have a separate entity for the relation, called i.e. RoleModulePermission. It will change your graph from triangle-like (3 classes) into a star-like (4 classes with common "root"). The new entity will have three many-to-one relations and Role, Module and Permission will have one-to-many relation to RoleModulePermission. Set up the cascades and you can try querying this model like any other:
session.Query<Permission>()
.Where(x => x.RoleModules.Any(rm => rm.Module == module));
If your model is more restricted, i.e. you have some constraints, you can try to map it simpler using AsTernaryAssociation or AsEntityMap, which is pretty much the same. Then in your Role, Module and Permission classes you should have relation of type IDictionary<TFrom, To>. This can mean i.e. that Permission X maps Role Y to Module Z, but it means that within Permission X Role Y have no other Modules. Something like this in POCO class:
public virtual IDictionary<Role, Module> RoleModules { get; set; }
and like this in mapping:
HasManyToMany(x => x.RoleModules).Table("tblRolesPermissions").AsEntityMap();
does support it, but. You have to specify one-to-many or many-to-many relations at each side of the relation (in POCO's for Roles, Modules and Permission) and map each relation like that:
m.HasMany(x => x.RolesPermissionsModules).AsTernaryAssociation()
Querying using LINQ should then look somehow like this:
session.Query<Permission>()
.Where(x => x.RoleModules[role] == module);
Related
I have implemented ASP.Net identity with some custom properties following this article -
http://typecastexception.com/post/2014/06/22/ASPNET-Identity-20-Customizing-Users-and-Roles.aspx
Everything works well, except. I want to get users under specific role (e.g. Get me all the users under Admin role).
I tried following ways to retrieve the users -
var userRole = _roleManager.Roles.SingleOrDefault(m => m.Name == role.Name);
var usersInRole = _userManager.Users.Where(m => m.Roles.Any(r => r.RoleId == userRole.Id));
var usersInRole2 = _userService.GetUsers().Where(u => u.Roles.Any(r => r.RoleId == userRole.Id));
Where _roleManager is of type ApplicationRoleManager : RoleManager<ApplicationRole>. _userManageris of type ApplicationUserManager : UserManager<ApplicationUser, string>.
I am unable to get Roles under user in _userManager and _userService
PS : _userService is service that extends IRepository which queries DbSet<ApplicationUser>.
I can see Roles being properly mapped in table ApplicationUserRoles and I get expected result when I do _userManager.IsInRole(user.Id, "Admin");.
What could've gone wrong with this?
Rahul.
If you are using Entity Framework, it sounds like you are being caught out by lazy loading (since the roles are being added to the database but not when requested from a queryable).
Try something like the following:
_userManager.Users.Include(x => x.Roles).Where(m => m.Roles.Any(r => r.RoleId == userRole.Id));
I figured out where the issue was -
Initially the table ApplicationUserRoles had only primary key definitions, not the foreign key mapping (many to many mapping)..
I added this in OnModelCreating
modelBuilder.Entity<ApplicationUserRole>().HasKey((ApplicationUserRole r) => new { UserId = r.UserId, RoleId = r.RoleId });
//added these definitions
modelBuilder.Entity<ApplicationUser>().HasMany(p => p.Roles).WithRequired().HasForeignKey(p => p.UserId);
modelBuilder.Entity<ApplicationRole>().HasMany(p => p.Users).WithRequired().HasForeignKey(p => p.RoleId);
This completed the relationship and now I can see the Users under Roles and vice versa.
This resulted issue while updating the database, however I just had to do some changes in migration -
The object 'PK_Dbo.ApplicationUserRole' is dependent on column
'UserId'. ALTER TABLE DROP COLUMN UserId failed because one or more
objects access this column.
All I did is, I went to the migration file and moved these lines above DropColumn
DropIndex("dbo.ApplicationUserRole", new[] { "ApplicationUser_Id" });
DropIndex("dbo.ApplicationUserRole", new[] { "ApplicationRole_Id" });
DropPrimaryKey("dbo.ApplicationUserRole");
This solved the update-database exceptions as well.
Rahul
Im new to entity framework. I was trying to map 2 columns of my table(tblEnrollment) to a model(Enrollment). Before that i had changed the name of StudentIdcolumn in tblStudent in the model Student.
EntityTypeConfiguration<Student> studentEntityConfig = modelBuilder.Entity<Student>();
studentEntityConfig.ToTable("tblStudent");
studentEntityConfig.Property(p => p.StuID).HasColumnName("StudentID");
studentEntityConfig.HasKey<int>(s => s.StuID);
EntityTypeConfiguration<Enrollment> enrollmentEntityConfig = modelBuilder.Entity<Enrollment>();
enrollmentEntityConfig.Map(map =>
{
map.Properties(m => new
{
m.EnrollmentId,
m.Grade
});
});
enrollmentEntityConfig.ToTable("tblEnrollment");
Now when I'm trying to call a method to GetAllStudents from db I get the error
"The property 'Grade' on type 'Enrollment' cannot be mapped because it has been explicitly excluded from the model".
However the props Grade and enrollment have no relation to that method. They are not even in the tblStudent.
A location belongs to one or more entities. An entity can have one or more locations.
I try to get all other locations that have the same entity like the current location.
I have the following model:
class Location < ActiveRecord::Base
has_many :location_assignments
has_many :entities, :through => :location_assignments
accepts_nested_attributes_for :location_assignments
end
class Entity < ActiveRecord::Base
has_many :location_assignments
has_many :locations, through: :location_assignments
accepts_nested_attributes_for :location_assignments
end
This is in SQL what I want
SELECT DISTINCT l.* FROM locations l, location_assignments la, entities e
WHERE l.id = la.location_id
AND la.entity_id = e.id
AND e.id in ( SELECT ee.id from entities ee, location_assignments laa
WHERE ee.id = laa.entity_id
AND laa.location_id = 1)
But I don't want to use SQL.
This is what I tried with Rails
Location.joins(:entities => :locations).where(:locations => {:id => location.id})
It gives me several times the current location. The amount of rows is the same like the SQL (without distinct to get the current location only ones).
Any thoughts?
One way is to mimic your SQL which uses a subquery and just use standard ActiveRecord querying without dropping down to AREL. Let's start with the subquery:
Entity.joins(:location_assignments).where(:location_assignments => {:location_id => location.id})
This returns a relation which contains all the entities which have the location represented by location.id associated with them, just like your SQL subquery.
Then the main query, with the subquery as xxxxx for now for readability:
Location.joins(:entities).where(:entities => {:id => xxxxx})
This is the equivalent of your main query. Plugging in the subquery which returns what is basically an array of entities (ok, a relation, but same effect in this case) prompts ActiveRecord to turn the WHERE condition into an IN rather than just an =. ActiveRecord is also smart enough to use the id of each entity. So, plug in the subquery:
Location.joins(:entities).where(:entities => {:id => Entity.joins(:location_assignments).where(:location_assignments => {:location_id => location.id})})
Note that like your query, this returns the original location you started with, as well as all the others which share the same entities.
I think this should be equivalent to your SQL, but see how it goes on your data!
If you want to make the query more efficient using a self join (which means that instead of having 2 joins and a subquery with another join, you can just have 2 joins) but without using SQL fragment strings, I think you might need to drop down to AREL something like this:
l = Arel::Table.new(:locations)
la = Arel::Table.new(:location_assignments)
starting_location_la = la.alias
l_joined_la = l.join(la).on(l[:id].eq(la[:location_id]))
filtered_and_projected = l_joined_la.join(starting_location_la).on(starting_location_la[:entity_id].eq(la[:entity_id])).where(starting_location_la[:location_id].eq(location.id)).project(location[Arel.star])
Location.find_by_sql(filtered_and_projected)
This just joins all locations to their location assignments and then joins again with the location assignments using entity id, but only with those belonging to your starting location object so it acts like a filter. This gives me the same results as the previous approach using standard ActiveRecord querying, but again, see how it goes on your data!
I've got a couple tables, for example:
Product {Id, Name, ManufacturerId, ...}
Manufacturer {Id, Name, ...}
I'd like to be able to include ManufacturerName on my Product object (instead of having to load the whole Manufacturer row when I only need the name). My ProductMap looks like...
Table("Product");
Id(x => x.Id, "Id");
Map(x => x.ProductName, "ProductName");
Map(x => x.ManufacturerId, "ManufacturerId");
References(x => x.Manufacturer, "ManufacturerId");
What do I need to add to populate the ManufacturerName property on my Product object? I believe I need to make some sort of Join() call, but I'm having trouble figuring out how to write it with all the relevant parameters. It needs to join the current table (Product) to the Manufacturer table, on Product.ManufacturerId = Manufacturer.Id, and grab the Manufacturer.Name column, populating the ManufacturerName property on the object.
I think you could use a formula to dynamically retrieve a manufacturer name. This is not an elegant solution and personally I would prefer using a separate sql view mapped to a new entity (e.g. ProductExtra, etc.) where it would query just necessary columns but anyways. Here we go:
Add the ManufacturerName property to the Product class
Add a mapping line for that new property to your ProductMap:
Table("Product");
Id(x => x.Id, "Id");
Map(x => x.ProductName, "ProductName");
Map(x => x.ManufacturerId, "ManufacturerId");
Map(x => x.ManufacturerName).Formula("(select m.ManufacturerName from Manufacturer m where m.Id = ManufacturerId)");
References(x => x.Manufacturer, "ManufacturerId");
Hope this helps.
NH Joins are tricky, and require things that your schema may not support. For instance, the joined table's primary key is matched to your current table's primary key. It works a lot like a OneToOne mapping, except NH won't create an explicit constraint to that effect. Since this isn't the case in your mapping (looks like a many-to-one reference), I doubt you could make an explicit join work.
Try mapping a "pass-through" property:
public class Product
{
...
public string ManufacturerName
{
get{return NHibernateUtil.IsInitialized(Manufacturer)
? Manufacturer.Name : manufacturerName;}
set{if(NHibernateUtil.IsInitialized(Manufacturer))
Manufacturer.Name = value
else
manufacturerName = value;}
}
}
...
//In your mapping:
Map(x => x.ManufacturerName, "ManufacturerName");
This will persist a normalized Manufacturer's name onto the Product table as a denormalized field. The field will also exist in the Manufacturer table. When you retrieve JUST the Product, you get the name from the Product table. After the Manufacturer is lazy-initialized for some other reason (or eager-loaded), you get the name from the Manufacturer table, which then means you can persist the Manufacturer record's name to the Product.
Ruby on Rails is very new to me. I am trying to retrieve set of columns from 3 different tables. I thought I could use SQL view to retrieve my results but could not find a way to use views in Rails. Here are my tables.
1) User table --> user name, password and email
2) UserDetails table --> foreign key: user_id, name, address1, city etc.
3) UserWorkDetails --> foreign key: user_id, work address1, work type, etc
These 3 tables have one to one relationships. So table 2 belongs to table 1 and table 3 also belongs to table 1. Table 1 has one userdetails and one userworkdetails.
I want to get user email, name, address1, city, work address1, work type using joins.
What is the best way to handle this?
The data is (are) in the models. Everything else is just an optimization. So address1 is at user.user_detail.address1, for instance.
if you have
class User
has_one :user_detail
has_one :user_work_detail
end
class UserDetail
belongs_to :user
end
class UserWorkDetail
belongs_to :user
end
With user_id columns in tables named user_details and user_work_details then everything else is done for you.
If you later need to optimize you can :include the owned models, but it's not necessary for everything to work.
To get what you want done quickly use the :include option to include both the other tables when you query the primary table, so:
some_user_details = User.find(some_id, :include => [:user_details, :user_work_details])
This will just load all of the fields from the tables at once so there's only one query executed, then you can do what you need with the objects as they will contain all of the user data.
I find that this is simple enough and sufficient, and with this you're not optimising too early before you know where the bottlenecks are.
However if you really want to just load the required fields use the :select option as well on the ActiveRecord::Base find method:
some_user_details = User.find(some_id, :include => [:user_details, :user_work_details], :select => "id, email, name, address1, city, work_address1")
Although my SQL is a bit rusty at the moment.
User.find(:first, :joins => [:user_work_details, :user_details], :conditions => {:id => id})
I'd say that trying to just select the fields you want is a premature optimization at this point, but you could do it in a complicated :select hash.
:include will do 3 selects, 1 on user, one on user_details, and one on user_work_details. It's great for selecting a collection of objects from multiple tables in minimum queries.
http://apidock.com/rails/ActiveRecord/Base/find/class