I'm trying to map a very basic parent-child relation with Fluent NHibernate.
However, when analyzing the SQL, only the parent-INSERT statement is created.
The situation is a simple class with a list of other classes. No relation back to the parent is needed. The children needs to be inserted/updated when the parent is inserted/updated.
var room = new Room();
room.Name = "Room1";
room.Courses.Add(new Course(){ Name = "Course1"});
room.Courses.Add(new Course(){ Name = "Course2"});
using (var session = sessionFactory.OpenStatelessSession())
{
using (var transaction = session.BeginTransaction())
{
session.Insert(room);
transaction.Commit();
}
}
The mapping looks like this.
public class RoomMapping : ClassMap<Room>
{
public RoomMapping()
{
Table("Rooms");
Id(x => x.Id)
.GeneratedBy.SeqHiLo("seq_rooms", "1000");
Map(x => x.Name);
HasMany(x => x.Courses)
.Cascade.All();
}
}
public class CourseMap : ClassMap<Course>
{
public CourseMap()
{
Table("Courses");
Id(x => x.Id)
.GeneratedBy.SeqHiLo("seq_courses", "1000");
Map(x => x.Name);
}
}
I already played with multiple options of the HasMany, however non with any success.
Sorry people. I just found it out.
I'm working in a Stateless session. So no relationships are managed ;)
Related
I have a many to many relationship between
Portfolio and PortfolioTags
A portfolio Item can have many PortfolioTags
I am looking at the best way of saving tags to a portfolio item. My Nhibnerate maps are like so:
public class PortfolioMap : ClassMap<Portfolio> {
public PortfolioMap() {
Table("Portfolio");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
Map(x => x.AliasTitle).Column("AliasTitle").Not.Nullable();
Map(x => x.MetaDescription).Column("MetaDescription").Not.Nullable();
Map(x => x.Title).Column("Title").Not.Nullable();
Map(x => x.Client).Column("Client").Not.Nullable();
Map(x => x.Summary).Column("Summary").Not.Nullable();
Map(x => x.Url).Column("Url");
Map(x => x.MainImage).Column("MainImage");
Map(x => x.TitleAlt).Column("TitleAlt");
Map(x => x.Description).Column("Description").Not.Nullable();
HasMany(x => x.PortfolioImage).KeyColumn("PortfolioId").Inverse();
HasMany(x => x.PortfolioTag).KeyColumn("PortfolioId").Cascade.All().Table("PortfolioTag").Inverse();
}
}
public class PortfoliotagMap : ClassMap<Portfoliotag> {
public PortfoliotagMap() {
Table("PortfolioTag");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
References(x => x.Portfolio).Column("PortfolioId");
References(x => x.Tag).Column("TagId");
}
}
public class TagMap : ClassMap<Tag>
{
public TagMap() {
Table("Tag");
LazyLoad();
Id(x => x.TagId).GeneratedBy.Identity().Column("TagId");
Map(x => x.TagVal).Column("Tag").Not.Nullable();
HasManyToMany(x => x.Portfolio).Table("PortfolioTag").ParentKeyColumn("TagId").ChildKeyColumn("PortfolioId").Inverse();
}
}
In my portfolio controller I am first trying to save my tags that do not exist. I tried using SaveOrUpdate on a tag repository. However as the ids are different multiple save of tags occurs at this point.
I thought about the following steps but it seems long winded:
1) getting all tags:
var tags = _tagRepository.GetAll();
2) Iterating over the tags from the item to save and seeing if they exist in the database. If so I would need to get the tag and associate with the portfolio item. If not i would need to save the tag one by one and then associate with the portfolio item.
var tags = _tagRepository.GetAll();
foreach (var tagInPortfolio in StringUtilities.SplitToList(model.Tags, new[] { ',' }))
{
// tag does not exist so save it
if (tags.Any(i => i.TagVal == tagInPortfolio))
{
_tagRepository.SaveOrUpdate(new Tag {TagVal = tagInPortfolio});
}
}
3) I then need to delete any relationships i.e. tags to portfolio items that dont exist.
4) Finally need to add the tag to to the portfolioTag. I would need to get all the tags again and then associate:
portfolio.PortfolioTag.Add(new Portfoliotag {Portfolio = portfolio, Tag = tag});
_portfolioRepository.UpdateCommit(portfolio);
This seems to long winded. Can anyone explain the most simplest way of doing this please.
I have looked at saveandcommit on tags but i get multiple inserts because of ids being different. Do I need to delete all existing tag relationships also as this seems to much logic for something simple.
Create now works with a commit -
public void CreateCommit(T entity)
{
using (ITransaction transaction = Session.BeginTransaction())
{
Session.Save(entity);
transaction.Commit();
}
}
However using the below and the above maps still meant duplicates where occurring in the tag table. So if one portfolio record added a tag like abc and another portfolio record added a tag abc i need the join table to reference the same record in the tag and not create another instance of abc. Do i need to do a lookup on the tag table to avoid this
public void UpdateCommit(T entity)
{
using (ITransaction transaction = Session.BeginTransaction())
{
Session.Update(entity);
transaction.Commit();
}
}
If I understood correctly, I think you misunderstood the many-to-many mapping. If you really have a relationship like this between the Portifolio and the Tag classes, you should not map the PortfolioTag table.
In a simple many-to-many relationship the table used to connect the other two main tables should have only the foreign keys from the two tables (that would also be a composite key of this intermediate table). In this case, the PortfolioTag table would have only two columns: PortfolioId and TagId, that would be not only foreign keys for the Portfolio and Tag tables, but also the primary key of this intermediate table.
In this case, your Portfolio class should have a list of Tags instead of a list of PortfolioTag, and the Tag class a list of Portfolios. And you should map the Portfolio and the Tag like this (with no need of mapping the intermediate table):
public class PortfolioMap : ClassMap<Portfolio> {
public PortfolioMap() {
Table("Portfolio");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
//
// Code intentionally omitted for clarity
//
HasManyToMany(x => x.Tags)
.Table("PortfolioTag")
.ParentKeyColumn("PortfolioId")
.ChildKeyColumn("TagId")
.LazyLoad()
.Cascade.SaveUpdate();
}
}
public class TagMap : ClassMap<Tag> {
public TagMap() {
Table("Tag");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
Map(x => x.TagVal).Column("Tag").Not.Nullable();
HasManyToMany(x => x.Portfolios)
.Table("PortfolioTag")
.ParentKeyColumn("TagId")
.ChildKeyColumn("PortfolioId")
.Inverse();
}
}
You will also have to save the Portfolio inside the context of a transaction for the intermediate table to be populated, like bellow:
using (var trans = Session.BeginTransaction()) {
try {
Session.SaveOrUpdate(obj);
trans.Commit();
} catch {
trans.Rollback();
throw;
}
}
//or like this (with TransactionScope)
using (var trans = new TransactionScope()) {
Session.SaveOrUpdate(obj);
trans.Complete();
}
With this approach, you would also be excused of the need to iterate through the Tags list to save each one. You would be able to just save the Portfolio and everything should be fine.
P.S.: I tested this code with FluentNHibernate v1.4.0.0 and NHibernate v3.3.1.4000.
I'm a new in nHibernate help me to save an object and his connections in the base.
I've got the base with two tables:
person (idPerson, firstName, secondName, idService
service (idService, name)
The table service has 3 positions (gold, silver, brilliant)
The Mapping:
public class PersonMap:ClassMap<Person>
{
public PersonMap()
{
Id(x => x.id);
Map(x => x.firstName);
Map(x => x.lastName);
Map(x => x.status);
References(x => x.serviceType).Column("idServiceType");
Table("Person");
}
}
public class ServiceMap : ClassMap<ServiceType>
{
public ServiceMap()
{
Id(x => x.id);
Map(x => x.serviceName);
Table("Service");
}
}
I use the next method in Repository for saving:
public void Saves(Person entity)
{
using (var session = hibernateHelp.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
ServiceRepository srp = new ServiceRepository();
NHibernateUtil.Initialize(entity.service);
session.Save(entity);
transaction.Commit();
}
}
}
I recive the date (firstName="My" lastName="Go" status=TRUE, serviceType="gold"), then I create a Person:
Person df=new Person{
firstName="My",
lastName="Go",
status=true,
serviceType=new ServiceType{serviceName="gold"}
};
When I send it to the Repository method Save(see above) the mapping is working and saving the new object in the table person and create the new note in the table service.
I need't to create the new note in the table service since it containts one. How to make the saver's method in order to save the link to the table server, but not create the new one???
I appreciate any links and suggestions.
Person newPerson = new Person
{
FirstName = "My",
LastName = "Go",
Status = true,
serviceType = session.Load<ServiceType>(idOfGold) // returns the service if loaded or a proxy representing it which is enough to save the reference
// or
serviceType = session.Query<ServiceType>().Where(st.name == "gold").Single()
};
session.Save(newPerson);
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).
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.
I compiled the fluent nhibernate 1.0 rc with nhibernate 2.1 and had several warnings after the errors were fixed.
Fluent nhibernate tells me to use a separate Subclass-map instead of JoinedSubclass.
Current Mapping:
public class ClientMap : ClassMap<Client>
{
public ClientMap()
{
LazyLoad();
Id(x => x.Id);
//some boring stuff in between
JoinedSubClass<Company>("Id", m =>
{
m.LazyLoad();
m.Map(x => x.Name);
m.Map(x => x.Form);
});
}
The classes are inherited (company : client).
I tried the new mapping as follows:
public class CompanyMap : SubclassMap<Company>
{
CompanyMap()
{
LazyLoad();
Map(x => x.Name);
Map(x => x.Form);
}
}
After this change I don't get any companies, I'm not sure about how hibernate correctly knows what to do. Before I sayd "look, I have this class and the subclass I give you straight away in your mapping" and now: "Here are two mappings, figure by yourself, thx" :)
Any advices how to get the new subclass-mapping corrected?
Update:
Now I figured out that this works for saving data but the fk ID is not written to the child. How do I map the FK? The field name is Client_id, as nhibernate expects fk field names..