Any kind of help is welcome. Even if you can say (based upon your experience) that using an ORM for such a huge hierarchy is insane :).
Backgroud
My model layer has a pretty huge class hierarchy i.e. there are around 200 classes. The good/bad thing with hierarchy is that all of them have the same base class. The maximum distance between the base and leaf classes is 7 and the maximum number classes at any level in hierarchy is 80.
I am using nHibernate to save/load data from persistent storage.
Problem
The queries generated by nHibernate are pretty in efficient. e.g if I want to select ids of objects based upon some filter on a property in the base class, NHibernate will try to join all the tables in hierarchy/Union them depending which mapping strategy do I choose i.e. table per sub class or table per class hierarchy.
I understand that nHibernate does not which type of object until it can scan all the relevant tables. But what if I am only interested in the base class data at the moment. How to force nHibernate to load only the base class objects.
To illustrate my problem, here is a simplified version
public class Vehicle
{
public virtual Guid Identifier { get; set; }
public virtual int WheelsCount { get; set; }
public virtual Make Make { get; set; }
public virtual Model Model { get; set; }
}
public class Bike : Vehicle
{
public Bike()
{
WheelsCount = 2;
}
public virtual bool IsDirtBike { get; set; }
}
public class Car : Vehicle
{
public Car()
{
WheelsCount = 4;
}
public virtual bool IsFourWheelDrive { get; set; }
public virtual string Title { get; set; }
public virtual string Description { get; set; }
}
public class Make
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Model> Models { get; set; }
}
public class Model
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Make Make { get; set; }
}
And the mappings are as follows
public class VehicleMap : ClassMap<Vehicle>
{
public VehicleMap()
{
Id(x => x.Identifier).GeneratedBy.Guid();
Map(x => x.WheelsCount);
References(x => x.Make).Column("MakeId");
References(x => x.Model).Column("ModelId");
Table("Vehicle");
Polymorphism.Explicit();
UseUnionSubclassForInheritanceMapping();
}
}
public class BikeMap : SubclassMap<Bike>
{
public BikeMap()
{
Map(x => x.IsDirtBike);
Table("Bike");
// Abstract();
}
}
public class CarMap : SubclassMap<Car>
{
public CarMap()
{
Map(x => x.Title);
Map(x => x.Description);
Map(x => x.IsFourWheelDrive);
Table("Car");
// Abstract();
}
}
public class MakeMap : ClassMap<Make>
{
public MakeMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.Models)
.KeyColumn("MakeId");
Table("Make");
}
}
public class ModelMap : ClassMap<Model>
{
public ModelMap()
{
Id(x => x.Id);
Map(x => x.Name);
References(x => x.Make)
.Column("MakeId");
Table("Model");
}
}
Now if run the following query to load four wheeled vehicles, NHibernate will join vehicles, car and bike table. Whereas all I need right now is only the data stored in Vehicle table
List<Vehicle> vehicles = session.Query < Vehicle > ().Where(v => v.WheelsCount > 2).ToList();
Does anyone know how can I force nHibernate just load the data the is currently needed i.e. if it can return only vehicle objects instead of Car/Bike? With just a couple of tables in you schema you can overlook these queries by nHibernate but it really hurts when you have 200 tables :(.
P.S. In case there is a fault with model, please ignore that. This is not the real model. The actual model as stated earlier is much bigger. This model is there to illustrate the problem.
NHibernate has to join the tables to decide which type to return. otherwise polymorphism would be broken. Also it would be much harder to handle egde cases like abstract base class and so on.
Project only the properties you need and you are good to go
var vehicledatas = session.Query<Vehicle>()
.Where(v => v.WheelsCount > 2)
.Select(v => new { v.Id, v.WheelCount, v.Price })
.ToList();
If you need absolutly want only the base class then map it seperate for this use case
public class AlternateVehicleMap : VehicleMap
{
public AlternateVehicleMap()
{
EntityName("IAbsolutlyWantOnlyTheBaseClass");
Readonly(); // to make sure noone messes up
}
}
List<Vehicle> vehicles = session.CreateCriteria<Vehicle>("IAbsolutlyWantOnlyTheBaseClass").Add(Expression.Gt("WheelsCount", 2).List<Vehicle>();
Related
I am working to map an existing database using Fluent NHibernate and have encountered a problem when it comes to complex many-to-many relationships (additional columns).
I know that many-to-many relationships with additional columns have to be mapped as HasMany rather than HasManyToMany as they are not pure many-to-many relationships. The linking table has to be mapped as a class within itself, which I have done in the example below.
When loading this data from an existing database it loads fine. The project I am working on takes this data and inserts it into an empty database, which is where the problem occurs. I think that when inserting into the new database the CompositeId is trying to insert NULL values for ItemID and ItemGroupID which is not allowed in the database. Changing the database structure is not a viable option at this point, is there a way around this issue?
Thanks, example code below.
Entity Classes
public class Item
{
public virtual long ItemID { get; set; }
public virtual string Name { get; set; }
}
public class ItemGroup
{
public virtual long ItemGroupID { get; set; }
public virtual string Name { get; set; }
public virtual IList<ItemInGroup> ItemsInGroup { get; set; }
}
public class ItemInGroup
{
public virtual Item Item { get; set; }
public virtual ItemGroup ItemGroup { get; set; }
public virtual int? DisplayOrder { get; set; }
}
Mapping Classes
public class ItemMap : ClassMap<Item>
{
public ItemMap()
{
Table("Items");
Id(x => x.ItemID).GeneratedBy.Identity();
Map(x => x.Name);
}
}
public class ItemGroupMap : ClassMap<ItemGroup>
{
public ItemGroupMap()
{
Table("ItemGroups");
Id(x => x.ItemGroupID).GeneratedBy.Identity();
Map(x => x.Name);
HasMany(x => x.ItemsInGroup).KeyColumn("ItemGroupID").Cascade.All();
}
}
public class ItemInGroupMap : ClassMap<ItemInGroup>
{
public ItemInGroupMap()
{
Table("ItemsInGroups");
CompositeId().KeyReference(x => x.Item, "ItemID")
.KeyReference(x => x.ItemGroup, "ItemGroupID");
Map(x => x.DisplayOrder);
}
}
assuming DisplayOrder is the only extra column in the link table why not use the List index of IList as order?
public class ItemGroup
{
public virtual long ItemGroupID { get; set; }
public virtual string Name { get; set; }
public virtual IList<Item> Items { get; private set; }
}
public class ItemGroupMap : ClassMap<ItemGroup>
{
public ItemGroupMap()
{
Table("ItemGroups");
Id(x => x.ItemGroupID).GeneratedBy.Identity();
Map(x => x.Name);
HasManyToMany(x => x.ItemsInGroup)
.Table("ItemsInGroups")
.ParentKeyColumn("ItemGroupID")
.ChildKeyColumn("ItemID")
.AsList("DisplayOrder")
.Cascade.All();
}
}
Using POCO and Fluent NHibernate, I would like to have the ability to take an object like User and be able to give it various properties. I would like these objects to be able to focus on their properties, not common properties. Currently, I have an AuditableEntity that I can derive from to give my entity the ability to have Audited properties like CreatedDateTime and ModifiedDateTime, but I would also like to have the ability to take out the need to implement my Id for every object, Id's should be able to be part of some other base object, so I could say this object has a Guid Id and this one has an int id and this one has no Id. Currently, my User object looks like this:
User.cs
namespace ZeroBase.Domain.Entities
{
public class User : AuditableEntity<User>
{
public virtual Guid Id { get; set; }
public virtual string Username { get; set; }
public virtual string Password { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string EmailAddress { get; set; }
public virtual IEnumerable<Comment> Comments { get; set; }
}
}
and my UserMap looks like this:
UserMap.cs
namespace ZeroBase.Infrastructure.Data
{
public class UserMap : AuditMap<User>
{
public UserMap()
{
Id(x => x.Id)
.Column("Id")
.GeneratedBy.Guid();
Map(x => x.Username);
Map(x => x.Password);
Map(x => x.FirstName);
Map(x => x.LastName);
Map(x => x.EmailAddress);
HasMany(x => x.Comments);
Table("Users");
}
}
}
Is there anyway to handle the Id creation in some sort of a generic base class and still have a separate class that objects can inherit from to become auditable?
Are you using AuditableEntity for classes without an id? Entity implies that the class has an identity. Based on this, I would say that you can add a Id property to AuditableEntity.
If you are using it for classes without an identity, like value objects, I would create another base class. Would something like this make sense?
class Entity {
Guid Id { get; set; }
public Entity() {
Id = Guid.NewGuid();
}
}
interface IAuditable<T> {
DateTime CreatedAt {get; set; }
// ...
}
class AuditableEntity<T> : Entity, IAuditable<T> {
DateTime CreatedAt { get; set; }
}
class AuditableValueObject<T> : IAuditable<T> {
DateTime CreatedAt { get; set; }
}
// Class that has both an identity and is auditable
class User : AuditableEntity<User> {
// ..
}
// Class without an identity but auditable
class Money : AuditableValueObject<Money> {
// ..
}
// Class with an identity but not auditable
class Customer : Entity {
// ..
}
Note: Entity, value objects etc. are terms of Domain Driven Design (DDD).
I attempt to make a more useful sample of the FluentNHibernate tutorial, but I'm confused about what type of dependencies the objects have when requested from the repository. Basically I want the objects to be:
Bi-directional; so I can traverse up/down object hierarchy
Decoupled from NHibernate, repository, sessions (all the things I don't understand too well yet)
No-lazy load (Since I don't need it, and it helps me with (2), I think)
It's a bit hard for me to understand if, how and why the example I work on actually satisfy those points or not. But when I request a List and make a break in the code, I see that the child collection lists are of a type:
NHibernate.Collection.Generic.PersistentGenericBag<Store>
..which is a bit too complex for my head..
So, my concrete question is:
What changes are required for complete decoupling?, i.e. to retrieve object hierarchies of simple Lists? (What main concepts, in what classes etc. that must be changed)
I believe this is what I need, since; I write a single-physical-tier application, for single user, with no requirement for "undo-logic". I just want things as loose as possible, as I feel it gives much more reliable code when I use kind of a "push/pull" approach.
Product class:
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; }
public Product()
{
StoresStockedIn = new List<Store>();
}
}
Store class:
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);
}
}
Employee class:
public class Employee
{
public int Id { get; private set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Store Store { get; set; }
}
Mapping:
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");
}
}
public class StoreMap : ClassMap<Store>
{
public StoreMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.Staff) // 1:m
.Inverse() // other end of relation is responsible for saving
.Cascade.All(); // Tells NH to cascade events
HasManyToMany(x => x.Products).Cascade.All()
.Table("StoreProduct"); // Set m:m join table
// ..only required for bi-directional m:m
}
}
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Id(x => x.Id); // By default an int Id is generated as identity
Map(x => x.FirstName);
Map(x => x.LastName);
References(x => x.Store).Cascade.None().Not.LazyLoad();
}
}
Repository:
public ICollection<Product> GetAll()
{
using (ISession session = FNH_Manager.OpenSession())
{
var products = session.CreateCriteria(typeof(Product)).List<Product>();
return products;
}
}
I don't quite understand why you would want to decouple any further.
The public interface you are exposing is already in the form of an IList. You could expose that IList as an IEnumerable. That would make it a readonly collection. (you'd have to map it with FluentNHibernate to a field instead of a property)
The PersistentGenericBag is there for change tracking.
For example, how would NHibernate know when an entity was added, or removed from the list?
edit: Turning of lazy-loading is considered premature optimization. It's better to leave it out of the mappings and enable it in your repositories if you have to.
I am using IQueryable<> to build up batching queries.
I have used views successfully to fetch information so the IQueryable<> can find it, but in this instance I can't work out how to map a view, as it depends on properties rather than the entity's ID.
Say I have this entity and mapping:
public class Calculation
{
public virtual int Id { get; set; }
public virtual Organisation Organisation { get; set; }
public virtual Charge Charge { get; set; }
public virtual TransactionTotal TransactionTotal { get; set; }
}
public class CalculationMap : ClassMap<Calculation>
{
public CalculationMap()
{
Id(x => x.Id).GeneratedBy.Identity();
References(x => x.Organisation).Not.Nullable().UniqueKey("OC");
References(x => x.Charge).Not.Nullable().UniqueKey("OC");
}
This is the class I need to get in there: I'm using a view to give me the total amount per Organisation and Charge:
public class TransactionTotal
{
public virtual int Id { get; set; }
public virtual Organisation Organisation { get; set; }
public virtual Charge Charge { get; set; }
public virtual decimal Amount { get; set; }
}
public class TransactionTotalMap : ClassMap<TransactionTotal>
{
public TransactionTotalMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Table("TransactionTotalsView");
References(x => x.Charge).Not.Nullable();
References(x => x.Organisation).Not.Nullable();
Map(x => x.Amount).Precision(15).Scale(2).Not.Nullable();
}
}
Other places I have used views I have successfully used mappings like HasOne(x => x.TransactionTotal); but in this instance I need to tell Nhibernate to use the Organisation and Charge properties as the key.
Is this even possible? If so, how do I map the TransactionTotal class to the Calculation class?
Edit: I have used CompositeId in TransactionTotalMap as suggested by #David:
CompositeId().KeyProperty(x => x.Organisation.Id).KeyProperty(x => x.Charge.Id);
I'm still stuck on what to put in the CalculationMap though.
use the CompositeId() method in your mapping
I have following classes in my domain model:
public class Player
{
public virtual string ID { get; set; }
public virtual string Name { get; set; }
public virtual List<Match> Matches { get; set; }
}
public class Match
{
public virtual int ID { get; set; }
public virtual Player Player1 { get; set; }
public virtual Player Player2 { get; set; }
}
As you can see a Player can play multiple matches, and every match has two players.
How can I map these classes correctly using Fluent mapping?
The players on the match mapping would be References (many-to-one) referencing different playerIds in the match table and the matches on player would be hasMany (one-to-many):
public sealed class PlayerMap : ClassMap<Player>
{
public PlayerMap()
{
Id(x => x.ID).GeneratedBy.Native();
Map(x => x.Name);
HasMany(x => x.Matches).Cascade.SaveUpdate();
}
}
public sealed class MatchMap : ClassMap<Match>
{
public MatchMap()
{
Id(x => x.ID).GeneratedBy.Native();
References(x => x.Player1, "player1_id").NotFound.Ignore().Cascade.None();
References(x => x.Player2, "player2_id").NotFound.Ignore().Cascade.None();
}
}
You need a many-to-many (this case being a many-to-2). A possible fluent mapping is
public PlayerMap()
{
[...]
HasManyToMany(x => x.Matches).Cascade.AllDeleteOrphan();
}
otherwise nhibernate wouldn't know which column to use (match.player1_id or match.player2_id)