Fluent NHibernate table-per-class-hierarchy need to use .Where()? - nhibernate

I have the following mapping for a set of contact classes based off an abstract Contact class implementation.
public class ContactMapping : ClassMap<Contact> {
public ContactMapping() {
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.CreatedDate).Not.Nullable();
Map(x => x.Value).Not.Nullable();
Map(x => x.Level).Not.Nullable();
Map(x => x.Comments);
DiscriminateSubClassesOnColumn("ContactType");
}
}
public class PhoneContactMapping : SubclassMap<PhoneContact> {
public PhoneContactMapping() {
Map(p => p.PhoneType);
DiscriminatorValue("PhoneContact");
}
}
public class EmailContactMapping : SubclassMap<EmailContact> {
public EmailContactMapping() {
DiscriminatorValue("EmailContact");
}
}
public class WebsiteContactMapping : SubclassMap<WebsiteContact> {
public WebsiteContactMapping() {
DiscriminatorValue("WebsiteContact");
}
}
I have an entity class that HasMany EmailContact(s), WebsiteContact(s), and PhoneContact(s).
public class ContactableEntityMapping: ClassMap<ContactableEntity> {
public ContactableEntityMapping() {
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.CreatedDate).Not.Nullable();
Map(x => x.Comment);
HasMany<EmailContact>(x => x.EmailContacts).AsBag().Not.LazyLoad().Where("ContactType='EmailContact'");
HasMany<PhoneContact>(x => x.PhoneContacts).AsBag().Not.LazyLoad().Where("ContactType='PhoneContact'");
HasMany<WebsiteContact>(x => x.WebsiteContacts).Not.LazyLoad().AsBag().Where("ContactType='WebsiteContact'");
HasManyToMany(x => x.Addresses).AsSet();
}
}
If i do not specify there .Where() clauses the class ends up coming back with all rows mapped to EmailContact (since it is first in the listing) if LazyLoading is used, and if lazy-loading is not used I receive an exception as it attempts to cast the classes to the wrong type.
Obviously this is because the SQL executed is not passing in the additional where clause unless I specify it in the mapping. Am I missing something in my mapping, or is the Where mess just something we need to live with?
Thanks for the help!

Related

nHibernate One to One property null when loaded

I have a One to One relation between a TimeRecord and the Location.
This implementation is exactly the same es described in documentation:
https://github.com/jagregory/fluent-nhibernate/wiki/Fluent-mapping
public class TimeRecordMap : ClassMap<TimeRecord>
{
public TimeRecordMap()
{
Id(x => x.Id);
Map(x => x.Description);
Map(x => x.StartTime);
Map(x => x.EndTime);
HasOne(x => x.Location).Cascade.All();
}
}
public class LocationMap : ClassMap<Location>
{
public LocationMap()
{
Id(x => x.Id);
Map(x => x.Longitude);
Map(x => x.Latitude);
Map(x => x.Adress);
References(x => x.TimeRecord).Unique();
}
}
Now I query my TimeRecords with the following method:
public IList<TimeRecord> GetTimeRecords(string userid)
{
var query = Session.Query<TimeRecord>().Where(tr => tr.User.Id == userid);
return query.ToList();
}
Unfortunalelty my Location object is always null even if there is a coresponding entry in Location table but when I query for the coresponding Location with the desired TimeRecordId it is returned correctly.
See code here (code is inside a loop -> trCurrent is the current object in list received from "GetTimeRecords")
Location location = _locationRepo.getLocationByTimeRecordId(trCurrent.Id);
//trCurrent.Location = location; <- don't want to do it that way
if (trCurrent.Location != null)<- always null
{
... do stuff here
}
Implementation of my LocationRepository method:
public Location getLocationByTimeRecordId(int timeId)
{
var query = Session.Query<Location>()
.Where(tr => tr.TimeRecord.Id == timeId && tr.IsDeleted == false);
List<Location> lstReturn = query.ToList();
if (lstReturn.Count() == 0)
{
return null;
}
else
{
return lstReturn.First();
}
}
Can someone tell me why my Location is not resolved corretly?
Cheers,
Stefan
People claim that
HasOne / one-to-one is usually reserved for a special case. Generally, you'd use a References / many-to-one relationship in most situations (see: I think you mean a many-to-one). If you really do want a one-to-one, then you can use the HasOne method.
If you really do want a one-to-one and use it, you should remember that entities are joined by their ids by default.
If you check generated SQL you'll see something like JOIN Location ON Location.Id = TimeRecord.Id.
In order to get SQL like JOIN Location ON Location.TimeRecordId = TimeRecord.Id you should specify the foreign key via PropertyRef() method. So your mapping could be the folloving:
public class TimeRecordMap : ClassMap<TimeRecord>
{
public TimeRecordMap()
{
Id(x => x.Id);
Map(x => x.Description);
Map(x => x.StartTime);
Map(x => x.EndTime);
HasOne(x => x.Location).Cascade.All().PropertyRef(it => it.TimeRecord);
}
}
public class LocationMap : ClassMap<Location>
{
public LocationMap()
{
Id(x => x.Id);
Map(x => x.Longitude);
Map(x => x.Latitude);
Map(x => x.Adress);
References(x => x.TimeRecord/*, "TimeRecordId"*/).Unique().Not.Nullable();
}
}
In order to make sure that any location has TimeRecord you can add .Not.Nullable() into your LocationMap class.

NHibernate inserts twice to base class when saving subclass. Violates unique constraint

I am using fluent nhibernate with a legacy oracle db, and Devart Entity Developer to generate mapping and entity classes.
I have a base table Product, which has several subclasses, including Tour. When saving Tour, nhibernate issues 2 identical inserts to the Product table which violates PK unique constraint.
Product mapping is:
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
Schema("CT_PRODUCTS");
Table("PRODUCT");
OptimisticLock.None();
LazyLoad();
CompositeId()
.KeyProperty(x => x.Season, set =>
{
set.Type("CT.DomainKernel.Enums.Season,CT.DomainKernel");
set.ColumnName("SEASON");
set.Access.Property();
})
.KeyProperty(x => x.ProductCode, set =>
{
set.ColumnName("PROD_CODE");
set.Length(10);
set.Access.Property();
});
Map(x => x.Name)
.Column("NAME")
.Access.Property()
.Generated.Never()
.CustomSqlType("VARCHAR2")
.Length(200);
HasOne(x => x.Tour)
.Class<Tour>()
.Access.Property()
.Cascade.SaveUpdate()
.LazyLoad();
}
}
Tour mapping is:
public class TourMap : SubclassMap<Tour>
{
public TourMap()
{
Schema("CT_PRODUCTS");
Table("TOUR");
LazyLoad();
KeyColumn("SEASON");
KeyColumn("PROD_CODE");
Map(x => x.Duration)
.Column("DURATION")
.Access.Property()
.Generated.Never()
.CustomSqlType("NUMBER")
.Not.Nullable()
.Precision(3);
HasOne(x => x.Product)
.Class<Product>()
.Access.Property()
.Cascade.SaveUpdate()
.LazyLoad()
.Constrained();
}
}
Tour Entity class:
public partial class Tour2 : Product
{
public virtual Product Product
{
get
{
return this._Product;
}
set
{
this._Product = value;
}
}
}
Any Ideas as to what is going wrong?
The solution to this was to Remove the Property references from Tour to Product, and from Product to Tour, which when thought about, make no sense anyway.

Fluent NHibernate : Conventions / KeyColumn

Below the code, a Customer can have several address. There is a one-to-many relation. I'd like in the Address table as FK a field named "Customer" and not "Customer_id"
I' tried to add :
.KeyColumn("Customer") > no change
I tried to use to change ForeignKeyConvention no change.
Any idea ?
public class CustomerMap : ClassMap<Customer>
{
protected CustomerMap()
{
Id(x => x.Id).Not.Nullable();
Map(x => x.FirstName).Not.Nullable().Length(25);
Map(x => x.LastName);
HasMany<Address>(x => x.Addresses)
.KeyColumn("Customer")
.AsSet()
.Inverse()
.Cascade.AllDeleteOrphan();
}
}
public class AddressMap : ClassMap<Address>
{
public AddressMap()
{
Id(x => x.Id);
Map(x => x.City).Not.Nullable().Length(100);
}
}
public class ForeignKeyReferenceConvention : IHasManyConvention
{
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Key.PropertyRef("EntityId");
}
}
public void DBCreation()
{
FluentConfiguration config = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString("...."))
.Mappings(m =>
m.AutoMappings
.Add(AutoMap.AssemblyOf<Customer>())
.Add(AutoMap.AssemblyOf<Address>()
.Conventions.Setup(c => c.Add<ForeignKeyReferenceConvention>())
)
);
config.ExposeConfiguration(
c => new SchemaExport(c).Execute(true, true, false))
.BuildConfiguration();
}
I've never used automapping myself, but isn't ClassMap only used with "default" fluent mapping (not automapping)? Do you want to use fluent mapping or auto mapping?
Why is your one-to-many inverse although you don't have a many-to-one side mapped?
Btw, what's the purpose of that convention? PropertyRef() shouldn't be used unless absolutely needed (NHibernate can't do some optimizations with that).

Fluent Nhibernate - mapping a collection of components (value objects)?

I am currently using component maps like this:
public class UserMapping
{
public UserMapping()
{
Id(c => c.Id).GeneratedBy.HiLo("100");
Map(c => c.UserName);
Component(c => c.Country, CountryComponentMapping.Map);
}
}
public sealed class CountryComponentMapping
{
public static void Map(ComponentPart<Country> part)
{
part.Map(x => x.CountryName)
part.Map(x => x.CountryAlpha2)
}
}
I like this becuase I only have to define the mapping for the component/value object in one place.
How would I go about using the same semantics for a collection of the component? (e.g. lets assume we wanted to change this to a collection of countries on the user entity)
You can map this as a Component Collection. Unfortunately there is no overload to HasMany().Component() in Fluent NHibernate that allows you to specify that you want to use a derived class of ComponentMap. You can use a modification of your technique above though.
public sealed class UserMap : ClassMap<User> {
public UserMap() {
Id(c => c.Id).GeneratedBy.HiLo("100");
Map(x => x.Name);
HasMany(x => x.Countries).Component(CountryComponentMapping.Map);
}
}
public sealed class CountryComponentMapping {
public static void Map(CompositeElementBuilder<Country> part) {
part.Map(x => x.CountryName);
part.Map(x => x.CountryAlpha2)
}
}

Fluent Nhibernate ClassMaps and Column Order

Our entities have a group of common properties. In order to reduce repetitive mapping, I created a base ClassMap that maps the identities and common properties. For each entity's ClassMap I just subclass the base and it works great. For a new project we are also letting NH generate the DB schema for us. The issue is, the order of the columns is such that the properties from the base ClassMap appear first, followed by anything mapped in the sub class. The requirement for this build is that the columns appear in a specific order.
To get around this I did the following.
public class BaseMap<T> : ClassMap<T> where T : Entity
{
public BaseMap()
{
Id(x => x.Id);
MapEntity();
Map(x => x.CommonProperty1);
Map(x => x.CommonProperty2);
Map(x => x.CommonProperty3);
}
protected virtual void MapEntity()
{
}
}
public class SomeEntityMap : BaseMap<SomeEntity>
{
public SomeEntity()
{
base.MapEntity();
}
protected override void MapEntity()
{
Map(x => x.SomeEntityProperty1);
Map(x => x.SomeEntityProperty2);
Map(x => x.SomeEntityProperty3);
}
}
This works, but feels like a hack. Aside from the hack factor, is there anything here that could be problematic?
If you made the base class and map method abstract it would feel less hacky...
public abstract class BaseMap<T> : ClassMap<T> where T : Entity
{
public BaseMap()
{
Id(x => x.Id);
MapEntity();
Map(x => x.CommonProperty1);
Map(x => x.CommonProperty2);
Map(x => x.CommonProperty3);
}
protected abstract void MapEntity();
}
public class SomeEntityMap : BaseMap<SomeEntity>
{
protected override void MapEntity()
{
Map(x => x.SomeEntityProperty1);
Map(x => x.SomeEntityProperty2);
Map(x => x.SomeEntityProperty3);
}
}
This would keep the common property columns at the end of the table. Be aware that foreign key columns will still get added after those. I don't think there is any way to have full control of the column order w/ fluent, unless you hand-modify the create schema scripts.
I just had to implement something similar myself.
Assuming that you have
public class SomeEntity : Entity
{
...
}
A less 'hack-like' way would be:
public abstract class BaseMap<T> : ClassMap<T> where T : Entity
{
public BaseMap()
{
Id(x => x.Id);
Map(x => x.CommonProperty1);
Map(x => x.CommonProperty2);
Map(x => x.CommonProperty3);
}
}
public class SomeEntityMap : BaseMap<SomeEntity>
{
public SomeEntity()
{
Map(x => x.SomeEntityProperty1);
Map(x => x.SomeEntityProperty2);
Map(x => x.SomeEntityProperty3);
}
}
Same result in the end, but your not using overridden methods to add mappings. It'll be taken care of automagically.