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.
Related
Note has a many-to-many relationship to Subject
What is the best way to query it? I would like to write the following to get all the subjects on a give note:
const subjectRepo = connection.getRepository(Subject);
const response = await subjectRepo.find({
relations: ['notes'],
where: { note }
});
but that returns ALL of the subjects, not just the subjects on the note.
Reln defined as:
#ManyToMany(() => Subject, (subject: Subject) => subject.notes)
subjects: Subject[];
-- and --
#ManyToMany(() => Note, note => note.subjects)
#JoinTable()
notes: Note[];
The executed query is:
SELECT "Subject"."id" AS "Subject_id", "Subject"."name" AS "Subject_name", "Subject"."description" AS "Subject_description", "Subject"."createdDate" AS "Subject_createdDate", "Subject"."updatedDate" AS "Subject_updatedDate", "Subject"."notebookId" AS "Subject_notebookId", "Subject"."measurementsId" AS "Subject_measurementsId", "Subject_notes"."id" AS "Subject_notes_id", "Subject_notes"."content" AS "Subject_notes_content", "Subject_notes"."notedAt" AS "Subject_notes_notedAt", "Subject_notes"."createdDate" AS "Subject_notes_createdDate", "Subject_notes"."updatedDate" AS "Subject_notes_updatedDate", "Subject_notes"."notebookId" AS "Subject_notes_notebookId" FROM "subject" "Subject" LEFT JOIN "subject_notes_note" "Subject_Subject_notes" ON "Subject_Subject_notes"."subjectId"="Subject"."id" LEFT JOIN "note" "Subject_notes" ON "Subject_notes"."id"="Subject_Subject_notes"."noteId"
Note: you can do this:
return subjectRepo
.createQueryBuilder('subject')
.leftJoin('subject.notes', 'note')
.where('note.id = :id', { id: note.id })
.getMany();
But I am hoping for an approach with less strings, and explicit joining
The SQL you are trying to get TypeORM to generate is roughly as follows
SELECT *
FROM subject
JOIN subject_note AS jt on jt.subject_id = subject.id
WHERE jt.note_id = :id
1. This is not possible with repo.find
At the time of writing, there is no way to create a where clause on a joined table using repo.find(...). You can join (doc) but the where clause only affects the entity of the repository.
TypeORM also silently ignores invalid where clauses, so be careful about those.
2. Re-select the note entity
If you want all the subject of a given note, you will either need to use a query builder, like you noted or you will need to re-select the note object with it's relationships.
note = await noteRepo.find({
relations: ['subjects'],
where: { id: note.id }
});
const subjects = note.subjects
3. Use TypeORM lazy relations
If you want to avoid re-selection, you need to use TypeORM Lazy relations but this forces you to change the type in both entities to be a Promise
// note entity
#ManyToMany(() => Subject, (subject: Subject) => subject.notes)
subjects: Promise<Subject[]>;
// subject entity
#ManyToMany(() => Note, note => note.subjects)
#JoinTable()
notes: Promise<Note[]>;
With this lazy relations, you will need to await for the linked notes to load before each usage, but you will not need to provide an array of relations to the find method.
const note = await noteRepo.find({
where: { id: someId }
});
const subjects = await note.subjects
I have many tables which have foreign key relations. Such as Countries and Cities relationship. When I am deleting the country my application is breaking down because I have foreign key relationship. What I want is if user deletes the country, it should get deleted and set forrign keys to null. Following is my code in OnModelCreating Method:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Player>().HasOptional(r => r.Team)
.WithMany(a => a.Players)
.HasForeignKey(b => new { b.TeamId })
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
Problem here is I have 20 to 25 tables I don't want to do it manually. Is there any code which will automatically set cascade false for entire applciation?
Yes, you can remove the cascade delete for One-to-many and/or Many-to-many conventions in Code First. Just add either or both of these to your OnModelCreating:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
This will disable it, but you can turn cascade delete back on with .WillCascadeOnDelete(true) as needed. See here and here.
EDIT: If your question is about setting FK to null for a collection, you could do this:
Make sure you have a collection of cities on the country.
Expose CountryId as a FK on your City model and make it nullable.
Set them to null and delete the country.
var countryToDelete = context.Country.Include(c => c.Cities).FirstOrDefault(c => c.CountryId == countryIdToDelete;
countryToDelete.Cities.ForEach(c => c.CountryId = null);
context.Country.Remove(countryToDelete);
context.SaveChanges();
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.
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);