NHibernate mapping to entity using common properties - c#

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

Related

Fluent NHibernate CompositeId trying to insert null values

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();
}
}

NHibernate: Load base class objects only

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>();

fluent nhibernate - Many to Many mapping with attribute

I have 2 master tables which are linked by a map table as below
User [UserId,Name]
Resource [ResourceId,Name]
UserResourceMap [UserId,ResourceId,AccessLevel]
How would the Resource and User ClassMap with AccessLevel as a resource attribute look?
My Domain classes look like this
public class User
{
public virtual int UserId { get;protected set; }
public virtual string Name { get;set; }
}
public class Resource
{
public virtual int ResourceId { get;protected set; }
public virtual string Name { get;set; }
public virtual string AccessLevel { get;set; }//Issue-populate this using fluent
}
How can I use fluent to map the accessLevel attribute in the below code.
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("User");
Id(x => x.Key);
Map(x=>x.Name);
}
}
public class ResourceMap : ClassMap<Resource>
{
public ResourceMap()
{
Table("Resource");
Id(x => x.Key);
Map(x=>x.Name);//Need some Map Here to make a hasManyToMany Map with attribute
}
}
Your domain model does not seem to match your database model - the Resource class has the property AccessLevel (i.e. one AccessLevel per Resource) but in the DB model AccessLevel is a column on the map table (i.e. one AccessLevel per User-Resource relation).
Assuming the DB model is the correct model one (fairly straightforward) way of mapping this would be to introduce a class like this.
public class UserResource {
public virtual int UserResourceId { get; protected set; }
public virtual User User { get; set; }
public virtual Resource { get; set; }
public virtual string AccessLevel { get; set; }
}
and map it in this way:
public class UserResourceMap : ClassMap<UserResource> {
public UserResourceMap() {
Table("UserResourceMap");
Id(x => x.UserResourceId);
References(x => x.User).UniqueKey("UniqueUserAndResource");
References(x => x.Resource).UniqueKey("UniqueUserAndResource");
Map(x => x.AccessLevel);
}
}
If you want bidirectional associations you could also add a Collection property on User and/or Resource and map these with HasMany(...).Inverse(). Of course, this kind of mapping would introduce a new UserResourceId column in the UserResourceMap table (using a composite key consisting of User and Resource would mitigate that).
Another solution would be to add an EntityMap association. If the association is owned by User it would be a Dictionary<Resource, string> property. Something like this might do the trick:
public class User {
public virtual int UserId { get; protected set; }
public virtual string Name { get; set; }
public virtual Dictionary<Resource, string> Resources { get; set; } // Resource -> AccessLevel
}
public class UserMap : ClassMap<User> {
public UserMap() {
Table("User");
Id(x => x.UserId);
Map(x => x.Name);
HasMany<Resource, string>(x => x.Resources).AsEntityMap().Element("AccessLevel");
}
}
As you've correctly identified in your database schema, this isn't a pure many-to-many relationship - it's two one-to-many relationships as the intermediate table has an attribute (the access level).
I therefore think your domain is missing an entity - there doesn't appear to be any relationship in your model between a user and the resources they can access.
How about something like this:
public class User
{
public virtual int Id { get;protected set; }
public virtual string Name { get;set; }
public virtual ICollection<UserResource> UserResources { get; set;}
}
public class UserResource
{
public virtual int Id { get; protected set; }
public virtual User User { get; set;}
public virtual Resource Resource { get; set;}
public virtual string AccessLevel { get; set;}
}
public class Resource
{
public virtual int Id { get;protected set; }
public virtual string Name { get;set; }
}
And mappings like:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.UserResource)
.AsSet()
.Inverse()
.Cascade.AllDeleteOrphan();
}
}
public class UserResourceMap : ClassMap<UserResource>
{
public UserResourceMap()
{
Table("UserResourceMap");
Id(x => x.Id);
References(x => x.User).Not.Nullable();
References(x => x.Resource).Not.Nullable();
Map(x => x.AccessLevel);
}
}
public class ResourceMap : ClassMap<Resource>
{
public ResourceMap()
{
Cache.ReadOnly();
Id(x => x.Id);
Map(x => x.Name);
}
}

FluentNHibernate, child collection dependencies

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.

Mapping with NHibernate?

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)

Categories