I cant get at One-to-One relationship working with Fluent NHibernate. I have User and UserDetails tables and they 'share' a primary key. How do I map them?
This is my latest/best attempt, this fails with NHibernate.Id.IdentifierGenerationException: attempted to assign id from null one-to-one property: User
User
protected UserMap()
{
Table("user");
Id(x => x.Id)
.Column("user_key")
.GeneratedBy.GuidComb().UnsavedValue(Guid.Empty);
References(x => x.UserDetail)
.PropertyRef(x=>x.User)
.Column("user_key")
.Not.Insert().Not.Update().Cascade.All();
}
UserDetail
protected UserDetailMap()
{
Table("user_detail");
Id(x => x.Id).Column("user_key")
.GeneratedBy.Foreign("User")
.UnsavedValue(Guid.Empty);
References(x => x.User)
.Column("user_key")
.Not.Insert().Not.Update().Unique().Cascade.None();
}
Try this
protected UserDetailMap()
{
Table("user_detail");
Id(x => x.Id)
.Column("user_key")
.GeneratedBy.Foreign("User")
.UnsavedValue(Guid.Empty);
HasOne(x => x.User)
.Constrained();
}
Related
I have a problem where I have a many-to-many mappings in my table structure creating headaches when trying to edit a simple record.
Example layout of where I am having problems:
Facilities Many-to-One Locations
Facilities One-to-Many Users
Users Many-to-Many Locations
Users One-to-Many PreviousPasswords
If I make a change to a Facilities record (Change the name field) I get the following error upon save:
collection [Users.PreviousPasswords] was not processed by flush()
Mapping looks like:
public FacilitiesMap()
{
Table("Facilities");
Id(x => x.ID);
Map(x => x.Name);
HasMany(x => x.Users).KeyColumn("FacilitiesID").Cascade.AllDeleteOrphan().Inverse();
HasMany(x => x.Locations).KeyColumn("FacilitiesID").Cascade.AllDeleteOrphan().Inverse();
}
public UsersMap()
{
Table("Users");
Id(x => x.ID);
Map(x => x.FirstName);
Map(x => x.LastName);
References(x => x.Facilities, "FacilitiesID").ForeignKey("ID");
HasMany(x => x.PreviousPasswords).KeyColumn("UsersID").Cascade.AllDeleteOrphan().Inverse();
HasManyToMany<Locations>(x => x.Locations)
.Schema("Members")
.Table("UsersToLocations")
.ParentKeyColumn("UsersID")
.ChildKeyColumn("LocationsID")
.LazyLoad()
.Cascade.SaveUpdate().Inverse();
}
public LocationsMap()
{
Table("Locations");
Id(x => x.ID);
Map(x => x.Name);
References(x => x.Facilities, "FacilitiesID").ForeignKey("ID");
HasMany(x => x.Patients).KeyColumn("LocationsID").Cascade.AllDeleteOrphan().Inverse();
HasManyToMany<Users>(x => x.Users)
.Schema("Members")
.Table("UsersToLocations")
.ParentKeyColumn("LocationsID")
.ChildKeyColumn("UsersID")
.Cascade.SaveUpdate();
}
public PreviousPasswordsMap()
{
Table("PreviousPasswords");
Id(x => x.ID);
Map(x => x.Password);
Map(x => x.DateTime);
References(x => x.Users, "UsersID").ForeignKey("ID");
}
The only way I can do a successful update to the Facilities record is if I use the following function to get the record before changing and saving it:
public Facilities GetFacility(int id)
{
return FluentSessionManager.GetSession()
.CreateCriteria<Facilities>()
.Add(Expression.Eq("ID", id))
.SetFetchMode("Users", FetchMode.Eager)
.SetFetchMode("Locations", FetchMode.Eager)
.UniqueResult<Facilities>();
}
The problem with this method is that where there are 10,000 users it takes a long time to process this query. Or even worse, if we have 100 location as well, then it takes around 2 minutes to get the one Facilities record to edit.
I am sure there is some kind of issue in the Mapping. Not sure how to fix or even where to start. Any help with this would be greatly appreciated.
Thanks in advance.
Do you really need all the users for the facility? When you only add users you can use
HasMany(x => x.Users).ExtraLazyLoad();
and to improve the query when really all subcollections are needed
public Facilities GetFacility(int id)
{
var session = FluentSessionManager.GetSession();
// ignore the result, we only want to cache the results in the session
session.CreateCriteria<Facilities>()
.Add(Expression.Eq("ID", id))
.SetFetchMode("Users", FetchMode.Eager)
.Future<Facilities>();
return session.CreateCriteria<Facilities>()
.Add(Expression.Eq("ID", id))
.SetFetchMode("Locations", FetchMode.Eager)
.FutureValue<Facilities>().Value;
}
Can not update / insert data into the database
This is the tables I have:
Role (Id, Name, ...)
Privilege (Id, Name, ...)
RolePrivilege (Id, RoleId, PrivilegeId, IsAllowed)
I have map:
public class RoleWithPrivilegesEntityMap : ClassMap<RoleWithPrivilegesEntity>
{
public RoleWithPrivilegesEntityMap()
{
Table( "ROLE" );
Not.LazyLoad();
Id( x => x.Id, "ID" ).GeneratedBy.Identity();
Map( x => x.Name, "NAME" ).Not.Nullable().Length( 100 );
HasMany(x => x.RolePrivileges)
.Cascade.AllDeleteOrphan()
.Inverse()
.Fetch.Join().KeyColumn("RoleId");
}
}
public class PrivilegeEntityMap : ClassMap<PrivilegeEntity>
{
public PrivilegeEntityMap()
{
Table("PRIVILEGE");
Id(x => x.Id, "ID").GeneratedBy.Identity();
Map(x => x.Name, "NAME").Not.Nullable();
HasMany(x => x.RolePrivileges)
.Cascade.AllDeleteOrphan()
.Inverse()
.Fetch.Join().KeyColumn("PrivilegeId");
}
}
public class RolePrivilegeEntityMap : ClassMap<RolePrivilegeEntity>
{
public RolePrivilegeEntityMap()
{
Table("ROLEPRIVILEGE");
Id(x => x.Id, "R_ID").GeneratedBy.Identity();
Map(x => x.IsAllowed, "ISALLOWED").Not.Nullable();
References(x => x.Role)
.Not.Nullable()
.Column("R_ID");
References(x => x.Privilege)
.Not.Nullable()
.Column("P_ID");
}
}
I'm trying to update existing record
session.SaveOrUpdate(rolePrivilege)
database make insert into table RolePrivilege, but I need to at first delete record.
what am I doing wrong?
normally nothing will go to the database at all because you never call session.Flush() or transaction.Commit(). However with Id generation Identity you basicly force NHibernate to insert immediately hence you see the INSERT statement.
You can also get rid of the Id in RolePrivilegeEntity and form a compositeId over Role and Priviledge which would also enhance performance (no need to insert immediatly).
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.
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).
having a little trouble with a mapping for the following table setup currently:
Shop
[1] [1]
/ \
[n] [n]
Category-[m]---[n]-Article
The behaviour should be the following :
1 - when deleting a shop, all Articles and Categories Should be deleted
2 - when deleting a Category, related Articles should be unassigned but not deleted
3 - when deleting an Article, related Categories should be unassigned but not deleted
Here's the current mapping:
public class ShopMap: ClassMap<Shop>
{
public ShopMap()
{
this.Table("shop");
Id(x => x.Id).Column("id").GeneratedBy.Native();
Map(x => x.Name).Column("name");
HasMany(x => x.Categories).Cascade.AllDeleteOrphan;
HasMany(x => x.Articles).Cascade.AllDeleteOrphan;
}
}
public class CategoryMap: ClassMap<Category>
{
public CategoryMap()
{
this.Table("category");
Id(x => x.Id).Column("id").GeneratedBy.Native();
Map(x => x.Name).Column("name");
References(x => x.Shop);
HasManyToMany(x => x.Articles).Cascade.AllDeleteOrphan()
.Table("article_category")
.ChildKeyColumn("article_id")
.ParentKeyColumn("category_id")
.Inverse();
}
}
public class ArticleMap: ClassMap<Article>
{
public ArticleMap()
{
this.Table("article");
Id(x => x.Id).Column("id").GeneratedBy.Native();
Map(x => x.Name).Column("name");
References(x => x.Shop);
HasManyToMany(x => x.Categories).Cascade.All()
.Table("article_category")
.ParentKeyColumn("article_id")
.ChildKeyColumn("category_id");
}
}
When deleting a Category (Session.Delete()), NH tries to delete the related Articles as well. Changing the Cascade-Mode to SaveUpdate will fix this, but will leave the entries in the link table *article_category*. Summing up : Cascade.SaveUpdate is too lazy, Cascade.All is too eager.
I tried everything that came to my mind in the mappings, but couldn't find a correct way to map this (rather simple schema).
Any ideas on how to (fluently) map this are greatly appreciated!
Thanks in advance
Sebi
The entries are left in the link table because Category.Articles is defined as the inverse side of the relationship. You need to remove the Category from Article.Categories before deleting it in order for the link record to be removed.