Ok. I have read a lot of similar situations but all of them seems to be more related to one-to-many mappings.
long story short... Here is the code.
public class Roles
{
public virtual int RoleID { get; protected set; }
public virtual Guid RoleGUID { get; protected set; }
public virtual string RoleName { get; protected set; }
public virtual string RoleDescription { get; protected set; }
public virtual IList<User> Users { get; protected set; }
}
public class RolesMap:ClassMap<Roles>
{
public RolesMap()
{
Table("Web_Roles");
Id(x => x.RoleID);
Map(x => x.RoleDescription);
Map(x => x.RoleName);
Map(x => x.RoleGUID);
HasManyToMany(x => x.Users)
.Cascade.All()
.Inverse()
.Table("Web_UserRoles");
}
}
public class User
{
public virtual int UserID { get; protected set; }
public virtual string UserName { get; protected set; }
public virtual string Password { get; protected set; }
public virtual string Email { get; protected set; }
public virtual Guid UserGUID { get; protected set; }
public virtual IList<Roles> Roles { get; protected set; }
}
public class UserMap: ClassMap<User>
{
public UserMap()
{
Table("Web_User");
Id(x => x.UserID);
Map(x => x.UserName);
Map(x => x.Password);
Map(x => x.UserGUID);
Map(x => x.Email);
HasManyToMany(x => x.Roles)
.Cascade.All()
.Table("Web_UserRoles");
}
}
public class UserRoles
{
public virtual int RoleID { get; protected set; }
public virtual int UserID { get; protected set; }
}
So, that's the domain entities and their respectives mappings. Not biggie. Of course, no mapping for UserRoles since it's only a "mapping table".
When i get my User I get the following error.
could not initialize a collection: [FamilyDerm.AUTH.Domain.User.Roles#1][SQL: SELECT roles0_.UserID as UserID1_, roles0_.Roles_id as Roles4_1_, roles1_.RoleID as RoleID1_0_, roles1_.RoleDescription as RoleDesc2_1_0_, roles1_.RoleName as RoleName1_0_, roles1_.RoleGUID as RoleGUID1_0_ FROM Web_UserRoles roles0_ left outer join Web_Roles roles1_ on roles0_.Roles_id=roles1_.RoleID WHERE roles0_.UserID=?]
It doesn't take much to realize that the mapping is converting RoleID from my "mapping entity" UserRoles to Roles_id, which I don't understand why. I am following NHibernate exact "way" but i don't seem to get it straight.
If I replace in SQL Server Roles_id by RoleID, it works like a charm. Obviously I am having either a naming convention issue or a mapping issue itself.
As I write this, i see questions popping out to my right with similar concerns but none of them offer a solution to my problem.
Thank you.
Because you haven't explicitly declared the field names in the Web_UserRoles table nHibernate infers them by adding an _Id to the name of the child collection so
HasManyToMany(x => x.Users)
.Cascade.All()
.Inverse()
.Table("Web_UserRoles");
will infer a field named Users_Id in the Web_UserRoles table and likewise Roles_Id from your HasManyToMany Roles mapping.
Modifying your HasManyToMany definitions to explicitly define the Parent and Child Id columns from your ProductInProduct table should sort your issue:
HasManyToMany(x => x.Users)
.Cascade.All()
.Inverse()
.Table("Web_UserRoles")
.ParentKeyColumn("UserId")
.ChildKeyColumn("RoleId");
and
HasManyToMany(x => x.Roles)
.Cascade.All()
.Table("Web_UserRoles")
.ParentKeyColumn("RoleId")
.ChildKeyColumn("UserId");
Related
I have the following classes:
public class Track
{
public virtual int Id { get; set; }
public virtual Track MainMix { get; set; }
public virtual IEnumerable<Track> SubMixes { get; set; }
public virtual IList<FileVersion> Files { get; set; }
}
public class FileVersion
{
public virtual int Id { get; set; }
public virtual Track Track { get; set; }
}
And the following mappings:
public class TrackMap : ClassMap<Track>
{
public TrackMap()
{
Id(x=>x.Id);
References(x => x.MainMix);
HasMany(x => x.SubMixes)
.Inverse()
.Cascade.All()
.KeyColumn("MainMix_id");
HasMany(a => a.Files)
.Access.CamelCaseField(Prefix.Underscore)
.Cascade.All();
}
}
public class FileVersionMap : ClassMap<FileVersion>
{
public FileVersionMap()
{
Id(x => x.Id);
References(x => x.Track);
}
}
There is omitted code for the sake of simplicity. The Track table has a "MainMix_id" column that is a self referencing column for a parent/child relationship among Track records.
When I try to fetch a track from the database the NHProfiler tells me that Nhibernate tries to fetch the fileversions of that track with the following query:
SELECT files0_.MainMix_id as MainMix9_1_,
files0_.Id as Id1_,
files0_.Id as Id9_0_,
files0_.Track_id as Track8_9_0_
FROM [FileVersion] files0_
WHERE files0_.MainMix_id = 3 /* #p0 */
It seems like it has confused the parent id column of the Track table with its primary key column. When I remove References(x => x.MainMix) from the Track mapping the query is correct, but I don't have the parent track record returned.
Let me know if I can clarify this any more and thanks in advance for your help!
Does this make a difference?
TrackMap :
References(x => x.MainMix).Column("MainMix_id");
FileVersionMap :
References(x => x.Track).Column("Track_id");
Playing around with Fluent NHibernate's Getting Started project. I tried to customize the example a bit, for a few reasons, among them elimination of circular reference for json serialization.
What I have done is to strip the Store and StoreMap classes of it's references back to Employee and Product classes. It now looks like this:
Store/StoreMap
public class Store
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
}
public StoreMap()
{
Id(x => x.Id);
Map(x => x.Name);
}
Employee/EmployeeMap
public class Employee
{
public virtual int Id { get; private set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual Store Store { get; set; }
}
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Id(x => x.Id);
Map(x => x.FirstName);
Map(x => x.LastName);
References(x => x.Store).Cascade.All();
}
}
Product/ProductMap
public class Product
{
public virtual int Id { get; private set; }
public virtual string Name { get; set; }
public virtual double Price { get; set; }
public virtual IList<Store> StoresStockedIn { get; private set; }
public Product()
{
StoresStockedIn = new List<Store>();
}
public virtual void StockAt(Store store)
{
StoresStockedIn.Add(store);
}
}
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.Price);
HasManyToMany(x => x.StoresStockedIn)
.Cascade.All()
.Table("StoreProduct");
}
}
I've moved "Cascade" operations into the Product and Employee instead of Store. From the testing I've done, both HasMany and HasManyToMany associations seem to be working okay.
My question is if it's the right approach. Whether or not it will lead to something that I have not anticipated.
as far as I know, it's standard practice in nHibernate to have both ends of a relationship in your model classes (actually, it usually makes sense from the business point of view).
Although you don't have to do it (your example, I think, should work fine).
If you don't want to expose one (or both) ends you can always define them as private or protected.
p.s concerning json serialization: it's not recommended to serialize your model entities. this is for a few reasons, the top ones being:
1. the data you display to the user may be different from what you have in your entities (for example- you might want to present an employee with their name, Id, their store name and number of products they own. it would be hard to do that using your model classes).
2. it's quite possible that you'd have uninitialized collections in your objects (if you use lazy-loading). These do not get serialized.
For serialization, DTOs is the recommended approach.
I have two tables, one UserDetails and another UserRoles. I mapped them in the following way. With this insert operation went fine but when i try to get the list of roles from user object, an exception is throwing. What i identified is, the SQL generated by Hibernate has USERCODE column as User_id in whare clause. How to over come this problem. Please help me.
User Class:
public virtual string Code { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string MiddleName { get; set; }
.......
public virtual IList<UserRole> Roles { get; set; }
public virtual IList<UserResource> Resources { get; set; }
User Map:
Table("USER_DETAILS");
Schema("PROJECTDOCS");
Id(x => x.Code).Column("CODE").GeneratedBy.Assigned();
Map(x => x.FirstName).Column("FIRSTNAME").Not.Nullable().Length(100);
Map(x => x.LastName).Column("LASTNAME").Length(100).Not.Nullable();
Map(x => x.MiddleName).Column("MIDDLENAME").Length(100);
...
HasMany(x => x.Roles).Inverse().Cascade.All();
UserRole Class:
public virtual long UserRoleID { get; set; }
public virtual User UserCode { get; set; }
public virtual long RoleID { get; set; }
public virtual string CreatedBy { get; set; }
public virtual DateTime CreatedDate { get; set; }
public virtual string UpdatedBy { get; set; }
public virtual DateTime UpdatedDate { get; set; }
UserRole Map:
Table("USER_ROLES");
Schema("PROJECTDOCS");
Id(x => x.UserRoleID).Column("URID").GeneratedBy.Increment();
Map(x => x.RoleID).Column("ROLEID").Not.Nullable();
Map(x => x.CreatedBy).Column("CREATEDBY").Not.Nullable().Length(36);
Map(x => x.CreatedDate).Column("CREATEDDATE").Not.Nullable().Default(DateTime.Now.ToString());
Map(x => x.UpdatedBy).Column("UPDATEDBY").Not.Nullable().Length(36);
Map(x => x.UpdatedDate).Column("UPDATEDDATE").Not.Nullable().Default(DateTime.Now.ToString());
References(x => x.UserCode).Column("USERCODE");
The final SQL generated was:
[SQL: SELECT roles0_.User_id as User8_1_, roles0_.URID as URID1_, roles0_.URID as URID2_0_, roles0_.ROLEID as ROLEID2_0_, roles0_.CREATEDBY as CREATEDBY2_0_, roles0_.CREATEDDATE as CREATEDD4_2_0_, roles0_.UPDATEDBY as UPDATEDBY2_0_,
roles0_.UPDATEDDATE as UPDATEDD6_2_0_, roles0_.USERCODE as USERCODE2_0_ FROM PROJECTDOCS.USER_ROLES roles0_ WHERE roles0_.User_id=?]
The bolded column name is what going worng.
Thanks in advance,
Pradeep
It looks like you are missing the key column on your HasMany to UserRoles in your UserClass. You need to probably do this:
HasMany(x => x.Roles).KeyColumn("URID").Inverse().Cascade.All();
I have the following entities
public class Client
{
public virtual int Id{get;set;}
public virtual IList<Telephone> Telephones { get; private set; }
}
public class User
{
public virtual int Id{get;set;}
public virtual IList<Telephone> Telephones { get; private set; }
}
public class Telephone
{
public virtual int Id{get;set;}
public virtual string Number { get; set; }
public virtual string Extension { get; set; }
public virtual TelephoneType TelephoneType { get; set; }
}
Client as mapping like this
HasManyToMany<Telephone>(x => x.Telephones)
.Table("tblClientTel")
.ParentKeyColumn("ClientId")
.ChildKeyColumn("TelId")
.LazyLoad()
.Cascade.SaveUpdate();
User as mapping like this
HasManyToMany<Telephone>(x => x.Telephones)
.Table("tblUserTel")
.ParentKeyColumn("UserId")
.ChildKeyColumn("TelId")
.LazyLoad()
.Cascade.SaveUpdate();
And Telephone like this
public TelephoneMap()
{
Table("tblTel");
Id(x => x.Id, "Id");
LazyLoad();
References<TelephoneType>(x => x.TelephoneType, "TypeId")
.Not.Nullable()
.Cascade.None()
.Not.LazyLoad();
Map(x => x.Number, "Number")
.Not.Nullable()
.Length(15);
Map(x => x.Extension, "Extension")
.Nullable()
.Length(10);
}
How can i query for all the telephone entities of a list of client ?
I've tried this
ICriteria criteria = base.CreateCriteria<Client>(null);
return base.CreateCriteria
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Telephones"))
)
.Add(Expression.In("Id", clientIds))
.SetFetchMode("Telephones", FetchMode.Eager)
.List<Telephone>();
But it returns the client Id
I have tried various approaches to mapping the following structure, but I finally admit that after a day of not getting very far, I need some help.
So the question is, how would you guys go about mapping something like this. The schema is not fixed at this point.
public abstract class BaseObject
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual string Prefix { get; set; }
public virtual string Suffix { get; set; }
public virtual BaseObject Parent { get; set; }
}
public class Room : BaseObject
{
public virtual int AreaId { get; set; }
}
public class Item : BaseObject
{
public virtual string Owner { get; set; }
public virtual IList<ItemAttribute> Attributes { get; set; }
public virtual int ItemTypeId { get; set; }
}
public class Potion : Item
{
public virtual int AmountLeft { get; set; }
}
Your input is very much appreciated.
This allows you to have it all in one table... doing this from memory, so syntax might not be exact.
public class ItemMap : ClassMap<BaseObject>
{
...
WithTable("objects");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name);
Map(x => x.Description);
...
DiscriminateSubClassesOnColumn("Type")
.SubClass<Room>("Room", x =>
{
x.Map(r => r.AreaId);
})
.SubClass<Item>("Item", c =>
{
i.Map(x => x.Owner);
i.References(x => x.Account).LazyLoad();
HasManyToMany(x => x.Attributes)
.WithParentKeyColumn("ItemId")
.WithChildKeyColumn("AttributeId")
.WithTableName("ItemAttributes")
.LazyLoad();
});
.SubClass<Potion>("Potion", x =>
{
x.Map(p => p.AmountLeft);
})
I would probably have a table for each class - Room, Item, Potion and then do fairly standard mappings for each.
I'd like to note that in my own experiences, it is a bad idea to name your Id field in your business objects "Id"
Here's a sample with Item, assoming some data names for your table.
public class ItemMap : ClassMap<Item>
{
public ItemMap()
{
WithTable("Items");
Id(x => x.Id, "ItemId").GeneratedBy.Identity();
Map(x => x.Name);
Map(x => x.Description);
Map(x => x.Prefix);
Map(x => x.Suffix);
Map(x => x.Owner);
Map(x => x.ItemTypeId);
References<Item>(x => x.Parent, "ParentItemId");
HasManyToMany(x => x.Attributes)
.WithParentKeyColumn("ItemId")
.WithChildKeyColumn("AttributeId")
.WithTableName("ItemAttributes")
.LazyLoad();
}
}
This is more than likely not be perfect - as I'm not sure how the mapping will work with the abstract parent.