I have this entity:
public class Permission
{
public virtual int Id{get;set;}
public virtual string Entity { get; set; }
public virtual bool ReadAction { get; set; }
public virtual bool UpdateAction { get; set; }
public virtual bool CreateAction { get; set; }
public virtual bool DeleteAction { get; set; }
public virtual Role Role { get; set; }
public virtual string RoleName
{
get { return Enum.GetName(typeof(Role),this.Role}
}
}
public class PermissionMap : ClassMap<Permission>
{
public PermissionMap()
{
Id(x => x.Id, "id").GeneratedBy.Identity().UnsavedValue(0);
Map(x => x.Entity);
Map(x => x.DeleteAction).CustomType<BooleanType>();
Map(x => x.ReadAction).CustomType<BooleanType>();
Map(x => x.CreateAction).CustomType<BooleanType>();
Map(x => x.UpdateAction).CustomType<BooleanType>();
Map(x => x.Role).CustomType<int>();
}
}
public enum Role
{
Administrator = 0,
SalesPerson = 1,
Marketing = 2
}
Every time I query the database it gets Updated.
I thought it is because of RoleName, but event when I have removed it its still get update.
The Permission get update for each row it has in the database.
Thanks
As discussed in comments, the point is, that we are all the time working with a session. It is so smart that even during read operation, it does keep track of all items by their ID.
If, any object is changed somehow (e.g. for a rendering or transfering reasons) and is still referenced by open session, it is managed as dirty one. When the session.Fulsh() is called (usually by default on transaction Commit() or even auto, depends on the FlushMode) any dirty object is persisted.
So, to avoid issuing the UPDATE statement the Entity mapping and its content must match. As at the end Shazam founded out, in this case the culprit was this mapping
// Map(x => x.Role).CustomType<int>();
Map(x => CustomType<Role>()
Related
I'm having a problem with NHibernate querying the database way too many times. I just realized it likely relates to the n+1 problem but I can't figure out how to change my mappings to solve the problem.
As you will see, my attempts involve specifying not to lazy load other objects, but it doesn't seem to do the trick.
This is the query:
public IQueryable<Report> ReadAll(DateTime since)
{
return m_session.QueryOver<Report>()
.JoinQueryOver(r => r.Mail)
.Where(m => m.Received >= since)
.List()
.AsQueryable();
}
Thanks in advance for any response! If you need any more information about my objects or mappings, please let me know.
Simplified objects graph (some omitted):
public class Report : EntityBase
{
public virtual Product Product { get; set; }
public virtual StackTrace StackTrace { get; set; }
public virtual Mail Mail { get; set; }
public virtual IList<ClientUser> ReadBy { get; set; }
}
-
public class Product : EntityBase
{
public virtual string Name { get; set; }
public virtual Version Version { get; set; }
public virtual IList<Report> Reports { get; set; }
public virtual IList<StackTrace> StackTraces { get; set; }
}
-
public class StackTrace : EntityBase
{
public virtual IList<StackTraceEntry> Entries { get; set; }
public virtual IList<Report> Reports { get; set; }
public virtual Product Product { get; set; }
}
Mapping examples:
public class ReportMap : ClassMap<Report>
{
public ReportMap()
{
Table("Report");
References(x => x.User)
.Column("EndUserId")
.Not.LazyLoad();
References(x => x.Product)
.Column("ProductId")
.Not.LazyLoad();
References(x => x.StackTrace)
.Column("StackTraceId")
.Not.LazyLoad();
HasManyToMany(x => x.ReadBy)
.Cascade.SaveUpdate()
.Table("ClientUserRead")
.ParentKeyColumn("ReportId")
.ChildKeyColumn("ClientUserId")
.Not.LazyLoad().BatchSize(200);
}
}
-
public class StackTraceMap : ClassMap<StackTrace>
{
public StackTraceMap()
{
Table("StackTrace");
References(x => x.Product)
.Column("ProductId");
HasMany(x => x.Entries)
.KeyColumn("StackTraceId")
.Not.LazyLoad()
.Cascade
.All().BatchSize(500);
HasMany(x => x.Reports)
.KeyColumn("StackTraceId")
.Inverse().BatchSize(100);
}
}
The way to go is to use batch fetching. Read more about it here:
How to Eager Load Associations without duplication in NHibernate?
On every entity mapping apply BatchSize (for many-to-one relation - avoiding 1 + N)
public ReportMap()
{
Table(...)
BatchSize(25);
...
And on every collection (solves issue with one-to-many 1 + N)
HasMany(x => x.Reports)
.KeyColumn("StackTraceId")
.BatchSize(25)
...
You can specify fetch paths in the query. For example, this fetch path can tell the query to eagerly join product and main objects, as well as an imaginary association on the collection of clients:
public IQueryable<Report> ReadAll(DateTime since)
{
return m_session.QueryOver<Report>()
.JoinQueryOver(r => r.Mail)
.Where(m => m.Received >= since)
.Fetch(x => x.Product).Eager
.Fetch(x => x.Mail).Eager
.Fetch(x => x.ReadBy.First().SomeProp).Eager
.List()
.AsQueryable();
}
If you want that always to happen, try using .Fetch.Join() instead of .Not.LazyLoad() for the assocations. But I would not recommend that because it can cause simple queries to become huge. Batching or subqueries can help too.
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");
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");
A referral has many leads. The entities are however, related by an agent identifier. Within my referral entity I ended up having to add an integer property mapped to the agent_id column in order to make the mappings work correctly.
If I remove the AgentID property from the entity and perform the mapping on the "Agent" object like so:
HasMany(x => x.Leads)
.AsBag()
.KeyColumn("Agent_Id")
.PropertyRef("Agent");
I run into an error:
Object does not match target type.
Description: An unhandled exception occurred during the execution of
the current web request. Please review the stack trace for more
information about the error and where it originated in the code.
Exception Details: System.Reflection.TargetException: Object does not
match target type.
I guess, I'm asking if this is an acceptable solution? The additional AgentID property won't be used anywhere other than within the property reference. Is there another way to perform this mapping within having to change the domain model as it cannot be changed at this time.
The working mappings:
public class Referral
{
public virtual int Id { get; set; }
public virtual int AgentID { get; set; }
public virtual Agent Agent { get; set; }
public virtual int? PositionNumber { get; set; }
public virtual DateTime? LastReferralDate { get; set; }
public virtual Account Account { get; set; }
public virtual IEnumerable<Lead> Leads { get; set; }
}
public ReferralMap()
{
Table("Referral");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
Map(x => x.PositionNumber).Column("PositionNumber");
Map(x => x.LastReferralDate).Column("LastReferralDate");
Map(x => x.AgentID).Column("Agent_ID");
References(x => x.Agent).Column("Agent_ID");
References(x => x.Account).Column("Account_id");
HasMany(x => x.Leads)
.AsBag()
.KeyColumn("Agent_Id")
.PropertyRef("AgentID");
}
i guess it is because you have
public LeadMap()
{
Id(l => l.AgentId).Column("Agent_Id");
}
instead of
public LeadMap()
{
CompositeId().KeyReference(l => l.Agent, "Agent_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.