Fluent nhibernate map list of items - nhibernate

I have a class "Company" which has a list of "Operator"
public class Company
{
public IList<Opertator> Operators {get; set; }
public Int32 Id {get; set;}
}
public class Operator {
public Int32 Id {get; set; }
public Company Company {get; set; }
}
When I mapped as follows:
public class CompanyMapping : ClassMap<Company>
{
public ProductMapping() : base()
{
Id(x => x.Id, "CompanyId").GeneratedBy.Native();
HasMany(x => x.Operators);
}
}
public class OperatorMapping : ClassMap<Operator>
{
public OperatorMapping()
{
Id(x => x.Id);
Reference(x => x.Company);
}
}
I have a UI where user can add operators and remove operators by checking boxes.
in c# code, I query the stored company, and add to the list, or remove from the list.
then send SaveOrUpdate.
My problem is when I add one more operator to existing company, and save/Update, the NHibernate is deleting the whole list, and reinster them again.
I don't want to do that.
Can I have it, that NHibernate will detect the changed items (new items from the list, and insert them, and determine the deleted item and delete them)?
I tried change the mapping to have in the company mapping
HasMany(x => x.Operators).Inverse();
but it end up not deleting at all.
Any help?

Inverse says that the Operator is responsible for the association and you have to set it when adding an Operator to the Company
public class Company
{
public Int32 Id {get; set;}
public ICollection<Opertator> Operators {get; private set; }
public void Add(Opertator operator)
{
Operators.Add(operator);
operator.Company = this;
}
public Company()
{
Operators = new List<Operator>();
}
}
// and
HasMany(x => x.Operators).Inverse();
// use it like
company.Add(new Operator()); // instead of company.Operators.Add(new Operator());

Related

Nhibernate interceptors not picking up properties of base class

I'm converting from Fluent to Loquacious, and I've run in to an issue where my interceptors are not getting all the fields like I think they should. If I look at the OnSave function
public override Boolean OnSave(Object entity, Object id, Object[] state,
String[] propertyNames, IType[] types)
and take a look at the propertyNames the only items in there are the items that were explicitly mapped in the mapping file (in the example this would just be ID, Start, and End).
In my case though I have a base class which isn't mapped at all. Instead it's just contains properties that get filled out by the interceptors. This used to work in Fluent Nhibernate, but now that I've moved to Nhibernate 3.3 I can't get it to work anymore.
My classes/mapping look something like this
public class BaseAuditEntity
{
public virtual int ModifiedByUserID { get; set; }
public virtual DateTime LastModifiedTime { get; set; }
}
public class Foo : BaseAuditEntity
{
public virtual int ID { get; protected internal set; }
public virtual DateTime Start { get; protected internal set; }
public virtual DateTime End { get; protected internal set; }
}
public class FooMap: ClassMapping<Foo>
{
Id(x => x.ID, m => m.column("fooID"));
Property(x => x.Start, m => m.column("start"));
Property(x => x.End, m => m.column("end"));
}
Any ideas of how to get this work? I don't want to have to map this every class, and I didn't think I needed to map the BaseAuditEntity, at least with Fluent it wasn't needed.
you could make a base mapping class
public class BaseAuditEntityMapping<T> : ClassMapping<T> where T: BaseAuditEntity
{
ManyToOne(x => x.ModifiedByUser);
Property(x => x.LastModifiedTime);
}
public class FooMap: BaseAuditEntityMapping<Foo>

NHibernate bi-directional association

I am trying to model a parent/child association where a Parent class (Person) owns many instances of a child class (OwnedThing) - I want the OwnedThing instances to be saved automatically when the Person class is saved, and I want the association to be bi-directional.
public class Person
{
public class MAP_Person : ClassMap<Person>
{
public MAP_Person()
{
this.Table("People");
this.Id(x => x.ID).GeneratedBy.GuidComb().Access.BackingField();
this.Map(x => x.FirstName);
this.HasMany(x => x.OwnedThings).Cascade.AllDeleteOrphan().KeyColumn("OwnerID").Inverse();
}
}
public virtual Guid ID { get; private set; }
public virtual string FirstName { get; set; }
public virtual IList<OwnedThing> OwnedThings { get; set; }
public Person()
{
OwnedThings = new List<OwnedThing>();
}
}
public class OwnedThing
{
public class MAP_OwnedThing : ClassMap<OwnedThing>
{
public MAP_OwnedThing()
{
this.Table("OwnedThings");
this.Id(x => x.ID).GeneratedBy.GuidComb().Access.BackingField();
this.Map(x => x.Name);
this.References(x => x.Owner).Column("OwnerID").Access.BackingField();
}
}
public virtual Guid ID { get; private set; }
public virtual Person Owner { get; private set; }
public virtual string Name { get; set; }
}
If I set Person.OwnedThings to Inverse then the OwnedThing instances are not saved when I save the Person. If I do not add Inverse then the save is successful but person.OwnedThings[0].Owner is always null after I retrieve it from the DB.
UPDATE
When saving the data NHibernate will set the single association end in the database because it is set via the many-end of the association, so when I retrieve the OwnedThing from the DB it does have the link back to the Person set. My null reference was from Envers which doesn't seem to do the same thing.
Am I understanding you correctly that your problem only occur on "history" entities read by nhibernate envers?
If so, it might be caused by this bug
https://nhibernate.jira.com/browse/NHE-64
The workaround for now is to use Merge instead of (SaveOr)Update.
OwnedThings[0].Owner is most likely null because you are not setting it when you do the add. When using bidirectional relationships you have to do something like the below:
Person person = new Person();
OwnedThing pwnedThing = new OwnedThing();
pwnedThing.Owner = person;
person.OwnedThings.Add(pwnedThing);
If you do not explicity set the pwnedThing.Owner and you query that same object in the same ISession that you created it on it will be null. Typically I have add or remove methods that do this "extra" work for me. Take the below example:
public class Order : Entity
{
private IList<OrderLine> orderLines;
public virtual IEnumerable<OrderLine> OrderLines { get { return orderLines.Select(x => x); } }
public virtual void AddLine(OrderLine orderLine)
{
orderLine.Order = this;
this.orderLines.Add(orderLine);
}
public virtual void RemoveLine(OrderLine orderLine)
{
this.orderLines.Remove(orderLine);
}
}
public class OrderMap : ClassMap<Order>
{
public OrderMap()
{
DynamicUpdate();
Table("ORDER_HEADER");
Id(x => x.Id, "ORDER_ID");
HasMany(x => x.OrderLines)
.Access.CamelCaseField()
.KeyColumn("ORDER_ID")
.Inverse()
.Cascade.AllDeleteOrphan();
}
}

fluent nHibernate: How to persist a property which is mapped with Formula?

I am dealing with a legacy database, and we have a field which doesn't make sense anymore, but I would rather not change the DB schema.
I'm trying to map an old DB text field into a class with a boolean (only need to know about one option that the DB text field has). I can get the boolean value out of the DB using Forumla, but I can seem to get it to save any updates back into the DB.
My class and current fluent mapping for it is:
public class Bulletin
{
public virtual int Id { get; set;}
public virtual bool RegularBulletin { get; set;}
}
public class BulletinMapping : ClassMap<Bulletin>
{
public BulletinMapping()
{
Table("Bulletins");
Id(x => x.Id, "ID").GeneratedBy.Identity();
Map(x => x.RegularBulletin)
.Formula("case when EmailType = 'BULLETIN_B' then 1 else null end")
.Nullable();
}
}
Does anyone have any ideas about how to persist the RegularBulletin field?
Thanks
Saan
I would use a workaround for this- create a backing field protected virtual string RegularBulletinString and use your boolean conversion formula on it.
public class Bulletin
{
public virtual int Id { get; set;}
protected virtual string RegularBulletinString { get; set;}
public virtual bool RegularBulletin
{
get
{
return RegularBulletinString == "BULLETIN_B";
}
set
{
RegularBulletinString = value? "BULLETIN_B" : null;
}
}
}
public class BulletinMapping : ClassMap<Bulletin>
{
public BulletinMapping()
{
Table("Bulletins");
Id(x => x.Id, "ID").GeneratedBy.Identity();
Map(x => x.RegularBulletinString)
.Column("EmailType")
.Nullable();
IgnoreProperty(x=> x.RegularBulletin);
}
}

add objects with child collections

I have two classes: a Site class and a Phase class. The Site class defines a collection of Phases. Each class corresponds to a database table. The database (SQL Server 2000) has a one to many reference between the two tables such that a given Site can be associated with many Phases but a given Phase can only be associated with a single Site.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
public class Site
{
public virtual int Id {get; set;}
[Required]
[Editable(true)]
[StringLength(64)]
public virtual string Name { get; set; }
public virtual ICollection<Phase> Phases {get; set;}
}
public class Phase
{
public virtual int Id {get; set;}
[Required]
[Editable(true)]
[StringLength(64)]
public virtual string Name { get; set; }
[Editable(true)]
[StringLength(16)]
public virtual string Code { get; set; }
public virtual int SiteId {get; set;}
public virtual Site Site {get; set;}
}
I'm using FluentNHibernate to do my mapping. I want to map this in such away that I can create a new Site instance, assign a few Phase instances and make one call to get all instances into the database:
Site site = new Site() { Name = "SiteName" };
Phase phase = new Phase() { Name = "PhaseName", Code = "Code" };
Phase otherPhase = new Phase() { Name = "OtherPhaseName" };
site.Phases.Add(phase);
site.Phases.Add(otherPhase);
Session.SaveOrUpdate(site);
I have the following mappings in place, but they aren't doing the trick:
public class SiteMap : ClassMap<Site>
{
public SiteMap()
{
Id(p => p.Id).Column("ST_ID").GeneratedBy.Native();
Map(p => p.Name).Column("ST_Name");
HasMany<Phase>(x => x.Phases).KeyColumn("ST_ID").LazyLoad().Inverse().AsSet();
}
}
public class PhaseMap : ClassMap<Phase>
{
public PhaseMap()
{
Id(p => p.Id).Column("PH_ID").GeneratedBy.Native();
Map(p => p.Name).Column("PH_Name");
Map(p => p.Code).Column("PH_Code").Nullable();
Map(p => p.SiteId).Column("ST_ID");
References<Site>(x => x.Site).Column("ST_ID").LazyLoad(Laziness.Proxy).Not.Insert().Not.Update();
}
}
I'm new to NHibernate generally, so I recognize there may be other issues with the mappings shown here that I'm not aware of. Any assistance on how best to map these two classes would be appreciated. TIA.
References<Site>(x => x.Site).Column("ST_ID").LazyLoad(Laziness.Proxy).Not.Insert().Not.Update();
Should just be
References<Site>(x => x.Site).Column("ST_ID").LazyLoad(Laziness.Proxy);
Your Not Insert and Update specifications do not update this column when you're insert or update the table.
You also need to specify Cascade on your one to many end
HasMany<Phase>(x => x.Phases).KeyColumn("ST_ID").Cascade.AllDeleteOrphan().LazyLoad().Inverse().AsSet();

Fluent NHibernate: How to create one-to-many bidirectional mapping?

Basic question: How to I create a bidirectional one-to-many map in Fluent NHibernate?
Details:
I have a parent object with many children. In my case, it is meaningless for the child to not have a parent, so in the database, I would like the foreign key to the parent to have NOT NULL constraint. I am auto-generating my database from the Fluent NHibernate mapping.
I have a parent with many child objects like so:
public class Summary
{
public int id {get; protected set;}
public IList<Detail> Details {get; protected set;}
}
public class Detail
{
public int id {get; protected set;}
public string ItemName {get; set;}
/* public Summary Owner {get; protected set;} */ //I think this might be needed for bidirectional mapping?
}
Here is the mapping I started with:
public class SummaryMap : ClassMap<Summary>
{
public SummaryMap()
{
Id(x => x.ID);
HasMany<Detail>(x => x.Details);
}
}
public class DetailMap : ClassMap<Detail>
{
public DetailMap()
{
Id(x => x.ID);
Map(x => x.ItemName).CanNotBeNull();
}
}
In the Detail table, the Summary_id should be Not Null, because in my
case it is meaningless to have a Detail object not attached to the
summary object. However, just using the HasMany() map leaves the Summary_id foreign key nullable.
I found in the NHibernate docs (http://www.hibernate.org/hib_docs/nhibernate/html/collections.html) that "If the parent is required, use a bidirectional one-to-many association".
So how do I create the bidirectional one-to-many map in Fluent NHibernate?
To get a bidirectional association with a not-null foreign key column in the Details table you can add the suggested Owner property, a References(...).CanNotBeNull() mapping in the DetailsMap class, and make the Summary end inverse.
To avoid having two different foreign key columns for the two association directions, you can either specify the column names manually or name the properties in a way that gives the same column name for both directions. In this case you I suggest renaming the Details.Owner property to Details.Summary.
I made the Summary id generated by increment to avoid problems when inserting into the table since Summary currenty has no columns besides id.
Domain:
public class Detail
{
public int id { get; protected set; }
public string ItemName { get; set; }
// Renamed to use same column name as specified in the mapping of Summary.Details
public Summary Summary {get; set;}
}
public class Summary
{
public Summary()
{
Details = new List<Detail>();
}
public int id { get; protected set; }
public IList<Detail> Details { get; protected set; }
}
Mapping:
public class DetailMap : ClassMap<Detail>
{
public DetailMap()
{
Id(x => x.id)
.GeneratedBy.Native();
Map(x => x.ItemName)
.CanNotBeNull();
References<Summary>(x => x.Summary)
// If you don't want to rename the property in Summary,
// you can do this instead:
// .TheColumnNameIs("Summary_id")
.CanNotBeNull();
}
}
public class SummaryMap : ClassMap<Summary>
{
public SummaryMap()
{
Id(x => x.id)
.GeneratedBy.Increment();
HasMany<Detail>(x => x.Details)
.IsInverse()
.AsBag(); // Use bag instead of list to avoid index updating issues
}
}