I have a model that looks like this:
public class ComponentAttributeDto
{
public virtual long ComponentAttributeId { get; set; }
public virtual ComponentAttributeDto ParentComponentAttributeDto { get; set; }
public virtual string ComponentAttributeName { get; set; }
public virtual string Value { get; set; }
public virtual DataType DataType { get; set; }
public virtual IList<ComponentAttributeDto> ChildComponentAttributes { get; set; }
}
With a mapping file of:
public class ComponentAttributeMapping : ClassMap<ComponentAttributeDto>
{
public ComponentAttributeMapping()
{
Table("ComponentAttributes");
Id(x => x.ComponentAttributeId)
.GeneratedBy.Identity();
References(x => x.ParentComponentAttributeDto)
.Column("ParentComponentAttributeId");
HasMany(x => x.ChildComponentAttributes)
.Fetch.Select()
.Inverse()
.Cascade.AllDeleteOrphan()
.KeyColumn("ParentComponentAttributeId");
Map(x => x.ComponentAttributeName)
.Length(50);
Map(x => x.Value)
.Length(1500);
Map(x => x.DataType)
.Length(20);
}
}
When loading this with a large dataset that goes about 4 levels deep the performance is terrible. When running profiler I noticed that is it executing a select statement for every single value in the table for the data I want to lookup. Is there a way I can improve the performance to do some type of join on the table or something else?
You could use batch-size to pre-fetch instances, which reduces the number of queries considerably.
mapping (not sure if it is supported by Fluent in the meanwhile):
HasMany(x => x.ChildComponentAttributes)
.Fetch.Select()
.SetAttribute("batch-size", "20")
.Inverse()
.Cascade.AllDeleteOrphan()
.KeyColumn("ParentComponentAttributeId");
If you had a Root property, you could make a query for the whole tree at once.
public class ComponentAttributeDto
{
public virtual ComponentAttributeDto ParentComponentAttributeDto { get; private set; }
public virtual ComponentAttributeDto Root
{
get
{
if (ParentComponentAttributeDto == null)
{
return this;
}
else
{
return ParentComponentAttributeDto.Root;
}
}
private set
{ /* just for NH to call it */ }
}
// ....
}
HasMany(x => x.Children).AsSet().SetAttribute("batch-size", "20")
the query
session.CreateQuery(
#"from ComponentAttributeDto
where Root = :root"
.SetEntity(root);
should actually result in only a single query. Not sure if NH doesn't actually perform queries for the lists (ChildComponentAttributes), but it is worth a try.
Do you need the entire data structure all at once? Usually when I run into this problem I just take away the mapping handling from nHibernate and deal with it myself. Create a method for the class called getChildren() and have it run the query when called. If you want to add a child record then add another method called addChild() and have it instantiate with it's own parent ID.
You could eagerly fetch the hierarchy when you query. You can do this by using the eager fetch option in your query:
Session.QueryOver<ComponentAttributeDto>
.Fetch(a => a.ChildComponentAttributes).Eager
Down to the level you want to fetch.
Related
Good day. I can't save parent with children in same query. But children have null reference for parent...
Parent in my DB is table "food_in_the_cart" and model for it is here:
public class FoodInTheCartModel
{
public virtual int ID { get; set; }
public virtual ClientModel Client { get; set; }
public virtual EstablishmentModel Stocked { get; set; }
public virtual ProductModel DesirableProduct { get; set; }
public virtual IList<CartAdditiveModel> Additives { get; set; } //children
public virtual void AddAdditivesToTheCart(CartAdditiveModel a)
{
a.Cart = this;
Additives.Add(a);
}
public FoodInTheCartModel()
{
this.Additives = new List<CartAdditiveModel>();
}
}
Mapping also:
public FoodInTheCartMap()
{
Table("food_in_the_cart");
Id(x => x.ID)
.Column("id")
.GeneratedBy.Native("food_in_the_cart_id_seq");
References(x => x.Client)
.Column("fk_id_client")
.Not.Nullable()
.Not.LazyLoad();
References(x => x.DesirableProduct)
.Column("fk_id_product")
.Not.Nullable()
.Not.LazyLoad();
References(x => x.Stocked)
.Column("fk_id_stock")
.Not.Nullable()
.Not.LazyLoad();
HasMany(x => x.Additives)
.Table("cart_additive")
.Cascade.SaveUpdate()
.Cascade.All()
.Inverse()
.Not.LazyLoad();
}
And child is cart_additive. Model of cart_additive is type of CartAdditive and has reference for model of food_in_the_cart, also mapping this reference is:
References(x => x.Cart)
.Column("fk_id_cart")
.Not.LazyLoad();
Type relationship between these two tables is one to many: food_in_the_cart has many cart_additive. Seems I tried everything for saving query for parent with children... But children still have null value for parent. How to make parent reference in child not null?
I removed Insert() from has many in FoodInTheCartMap and and added AsBug() there, also allowed null values for fk of food_in_the_cart in cart_additives as for reference called Cart in model for this table. All works this way. Yay.
I have a "Group" class and a "GroupSummaryLevel" class, codes are given below. There is a one-to-one relation between these entities in DB. I need the "GroupSummaryLevel" as a property in Groups class. It is supposed to be a very simple join like
(SELECT g.Id FROM GroupSummaryLevel g WHERE g.AcctGroup = GroupID)
Unfortunately, I wasn't able to figure this out how to do with NHibernate. The many answers I saw here is no help to me. I would appreaciate any inputs from the more experienced NHibernate users out there. Thanks in advance.
public class Group : DomainEntity
{
public virtual string GroupId { get; set; }
public virtual string GroupName { get; set; }
public virtual GroupSummaryLevel GroupSummaryLevel { get; set; }
}
public class GroupSummaryLevel : DomainEntity
{
public virtual int Id { get; set; }
public virtual string AcctGroup { get; set; }
public virtual GroupSummaryLevel Parent { get; set; }
public virtual IList<GroupSummaryLevel> Children { get; set; }
public GroupSummaryLevel()
{
Children = new List<GroupSummaryLevel>();
}
}
The mapping I have done did not work so far. My mapping codes are as below:
public GroupMap()
{
Table("Groups");
LazyLoad();
Id(x => x.GroupId).GeneratedBy.Assigned().Column("GroupID").CustomType<TrimmedString>();
Map(x => x.GroupName).Column("GroupName").CustomType<TrimmedString>().Not.Nullable();
HasOne(x => x.GroupSummaryLevel).Cascade.None().ForeignKey("AcctGroup");
}
public GroupSummaryLevelMap()
{
Table("GroupSummaryLevel");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
Map(x => x.AcctGroup).Column("AcctGroup").CustomType<TrimmedString>().Not.Nullable();
//References(x => x.Parent).Column("ParentId");
//HasMany(x => x.Children).Cascade.All().KeyColumn("ParentId");
}
Note: I also need to do a self-join for GroupSummaryLevel, and no success with that either. Any recommendations for that will also be appreciated :)
I would say, that your one-to-one is not driven by primary/foreign keys, but by property-ref. So the Group should map the summary by saying something like this:
...if you want to find related SummaryLevel, pass my <id> into column mapped as AcctGroup
public GroupMap()
{
...
HasOne(x => x.GroupSummaryLevel)
.Cascade.None()
//.ForeignKey("AcctGroup")
.PropertyRef(gsl => gsl.AcctGroup)
;
}
public GroupSummaryLevelMap()
{
...
//References(x => x.Parent).Column("ParentId");
//HasMany(x => x.Children).Cascade.All().KeyColumn("ParentId");
References(x => x.Parent, "AcctGroup");
}
NOTEs for completeness, as discussed in comments:
In this scenario, when the "child" has reference to parent - it really calls for one-to-many/.HasMany() mapping.
The down side is, that child is represented as a colleciton of children: IList<GroupSummaryLevel>. It is not as straighforward to use, but we can create some virtual property, returning the .FirstOrDefault(). The benefit we get - is lazy loading (not in place with one-to-one).
I was using Fluent NHibernate and I had these two entities:
public class Location
{
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
private Stack<Place> _places;
public virtual IList<Place> Places
{
get { return _places.ToList(); }
set { _places = new Stack<Place>(value); }
}
public virtual void AddPlace(Place place)
{
_places.Push(place);
place.LocationReference = this;
}
}
public class Place
{
public virtual int Id { get; protected set; }
public virtual Location LocationReference { get; set; }
}
mapped as follow:
class LocationMap : ClassMap<Location>
{
public LocationMap()
{
Id(x => x.Id);
Map(x => x.Name)
.Not.Nullable()
.Unique();
HasMany(x => x.Places)
.Cascade.All()
.Inverse();
}
}
class PlaceMap : ClassMap<Place>
{
public PlaceMap()
{
Id(x => x.Id);
References(x => x.LocationReference);
}
}
I was using SQLite as RDBMS and I was saving only the Location, relying on Cascade() for managing Places insertion.
The two entities were successfully inserted in the DB, but when I tried to read a Location back and to access its list of Places, the list was empty.
After removing Inverse(), the code seemed to properly work. But when I inspected the DB, I found two columns within the Place table, while I expected only one of them: Location_id (that was empty) and LocationReference_id (that was set).
After one whole day of desperate googling, I noticed that everyone was naming the reference property as the class itself. So, I renamed it from LocationReference to Location, I added back the Inverse() call and everything worked fine. Only the column Location_id was in the DB, of course.
Does anyone know why this happened? Thanks.
I am using NHibernate 3.2 with FluentNHibernate and Linq to NHibernate. I want use Linq to NHibernate to eager load all of the grandchildren of a collection without having to load the children. For example, say that I have the following classes:
public class Parent
{
public virtual int Id { get; set; }
public virtual IList<Child> Children { get; set; }
}
public class ParentMap : ClassMap<Parent>
{
Id(x => x.Id);
HasManyToMany(x => x.Children).ExtraLazyLoad();
}
public class Child
{
public virtual int Id { get; set; }
public virtual IList<Parent> Parents { get; set; }
public virtual IList<Grandchild> Grandchildren { get; set; }
public virtual ProhibitivelyLargeType ProhibitivelyLargeField { get; set; }
public virtual ProhibitivelyLargeType RarelyUsedLargeField { get; set; }
}
public class ChildMap : ClassMap<Child>
{
Id(x => x.Id);
HasManyToMany(x => x.Parents).ExtraLazyLoad();
HasManyToMany(x => x.Grandchildren).ExtraLazyLoad();
Map(x => x.ProhibitivelyLargeField);
Map(x => x.RarelyUsedField).LazyLoad();
}
public class Grandchild
{
public virtual int Id { get; set; }
public virtual IList<Child> Children { get; set; }
public virtual int Age { get; set; }
}
public class GrandchildMap : ClassMap<Grandchild>
{
Id(x => x.Id);
HasManyToMany(x => x.Children).ExtraLazyLoad();
Map(x => x.Age);
}
For each Parent, I want to find out the total combined age of all of that parent's grandchildren. I could do so using the following method:
Dictionary<Parent, int> grandchildAges = session.Query<Parent>()
.FetchMany(p => p.Children)
.ThenFetchMany(c => c.Grandchildren)
.AsEnumerable()
.ToDictionary(
p => p,
p => p.Children.SelectMany(c => c.Grandchildren).Sum(g => g.Age)
);
This method produces the correct results. However, it necessitates loading all of the Child objects. Child includes a field of type ProhibitivelyLargeType, which is not lazy loaded, so I would really prefer to not load anything about Child but its ID. If I don't use FetchMany/ThenFetchMany, however, then I have the N + 1 problem and it takes a trip to the database for each Child and Grandchild, which is also unacceptable.
Alternatively, I could make ProhibitivelyLargeField LazyLoad. However, most applications that use the Child class need to use ProhibitivelyLargeField, but they do not want to have to load RarelyUsedLargeField, which is already LazyLoad. As I understand it, loading one LazyLoad property causes all of them to be loaded, so this solution would bog down the normal use case.
Is there a way to get the just the information that I am looking for using Linq to NHibernate, or do I have to use the Criteria Query API?
Thanks!
edited to give an example of why making ProhibitivelyLargeField LazyLoad might be undesirable
the following is QueryOver. its only to show the idea of loading the results in two smaller steps. Maybe you can translate it into LINQ
// inititialize the dictionary
Grandchild grandchild = null;
Dictionary<Parent, int> dict = session.QueryOver<Parent>()
.JoinQueryOver(p => p.Childs)
.JoinAlias(c => c.GrandChilds, () => grandchild)
.Select(Projections.Group<Parent>(p => p.Id), Projections.Sum(() => grandchild.Age))
.AsEnumerable()
.Cast<object[]>()
.ToDictionary(
array => session.Load<Parent>(array[0]),
array => (int)array[1]
);
// initialize all Parent proxies
session.QueryOver<Patient>()
.WhereProperty(p => p.Id).In(dict.Keys.Select(p => p.Id))
.ToList();
I haven't used nhibernate, but I have used linq to entities, and from what I see you are doing tons of database queries. You should instead do a single line query which returns only the data you want:
from parent in session.Parents
let children = parent.Children
select new {parent = parent, children.SelectMany(c => c.Grandchildren).Sum(gc => gc.Age)}
Apologies if I got something wrong. Haven't done C# in a while and I'm on my phone.
If this approach doesn't work someone just tell me and I'll delete it.
If I use session-per-transaction and call:
session.SaveOrUpdate(entity) corrected:
session.SaveOrUpdateCopy(entity)
..and entity is a transient instance with identity-Id=0. Shall the above line automatically update the Id of the entity, and make the instance persistent? Or should it do so on transaction.Commit? Or do I have to somehow code that explicitly?
Obviously the Id of the database row (new, since transient) is autogenerated and saved as some number, but I'm talking about the actual parameter instance here. Which is the business logic instance.
EDIT - Follow-up, of related problem.
Mappings:
public class StoreMap : ClassMap<Store>
{
public StoreMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name);
HasMany(x => x.Staff) // 1:m
.Cascade.All();
HasManyToMany(x => x.Products) // m:m
.Cascade.All()
.Table("StoreProduct");
}
}
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.FirstName);
Map(x => x.LastName);
References(x => x.Store); // m:1
}
}
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name).Length(20);
Map(x => x.Price).CustomSqlType("decimal").Precision(9).Scale(2);
HasManyToMany(x => x.StoresStockedIn)
.Cascade.All()
.Inverse()
.Table("StoreProduct");
}
}
EDIT2
Class definitions:
public class Store
{
public int Id { get; private set; }
public string Name { get; set; }
public IList<Product> Products { get; set; }
public IList<Employee> Staff { get; set; }
public Store()
{
Products = new List<Product>();
Staff = new List<Employee>();
}
// AddProduct & AddEmployee is required. "NH needs you to set both sides before
// it will save correctly"
public void AddProduct(Product product)
{
product.StoresStockedIn.Add(this);
Products.Add(product);
}
public void AddEmployee(Employee employee)
{
employee.Store = this;
Staff.Add(employee);
}
}
public class Employee
{
public int Id { get; private set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Store Store { get; set; }
}
public class Product
{
public int Id { get; private set; }
public string Name { get; set; }
public decimal Price { get; set; }
public IList<Store> StoresStockedIn { get; private set; }
}
As far as your question is concerned, whenever you flush your session is when your entity is persisted to the database. When saving your (new) entity, NHibernate generates the ID for you using the generator you provided.
Keep in mind that an Identity generator is not recommended (see this post by Ayende).
When you use an Identity generator, your new entity is persisted to the database when you save, even if you don't flush to the database. The reason this happens is because NHibernate needs to provide you with an ID for the entity, which it can't do without doing a roundtrip to the database.
A better solution would be to use something like a Guid generator, or HiLo if you want 'normal' values. This way you can save your entity without actually having to do a database roundtrip, which allows you to do a lot more performance wise (batching comes to mind).
I'm not sure I understand your question. The actual saving to the database occurs when the session is flushed (e.g. by committing the transaction). Calling SaveOrUpdate() doesn't itself save the entity, it just informs the session that the entity is due to be saved when the session is flushed.
Assuming that the ID of the entity maps to an identity field in the database and that your mapping tells NHibernate that identity is set by the database, then the ID set by the database will be set as the entity's ID when it is saved.
Nhibernate will set the ID property of your entity just after SaveOrUpdate call.
I noticed I saved by calling:
session.SaveOrUpdateCopy(entity);
..which does NOT update the Id. But by changing to:
session.SaveOrUpdate(entity);
..the Id's of transient entity will be updated.
I probably misunderstood the documentation (?).. Section 9.4.2 says:
SaveOrUpdateCopy(Object o)... If the given instance is unsaved or does not exist in the database, NHibernate will save it and return it as a newly persistent instance.
Is it just me, or does it not sound like a transient object (unsaved), will be "returned as persistent" ? Doesn't that mean with updated Id? Would appreciate a clarification how to interpret this sentence correctly (?)