NHibernate : update an ISet collection - nhibernate

See the class and mapping below. I'd like in some case update the address (at this time, it's all the time one address).
I do this :
var customer = session.Get<Customer>(customerId);
customer.Address.Clear();
customer.Address.Add(address);
address is coming from a form, the id field is not = 0 (when 0, at creation, no problem)
but when I do this :
session.Save(customer);
session.Commit();
I receive an exception on the commit (14 is the id of CustomerAddress) :
a different object with the same identifier value was already associated with the session: 14, of entity: CustomerAddress
What is the way to update this address ?
Thanks,
Class and Mapping
public class Customer
{
public virtual int Id { get; set; }
public virtual string LastName { get; set; }
public virtual Iesi.Collections.Generic.ISet<CustomerAddress> Address { get; set; }
public Customer()
{
Address = new Iesi.Collections.Generic.HashedSet<CustomerAddress>();
}
}
public class CustomerAddress
{
public virtual int Id { get; set; }
public virtual string Street { get; set; }
public virtual Customer Customer { get; set; }
}
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.LastName)
.Length(50)
.Not.Nullable();
HasMany(x => x.Address)
.AsSet()
.Inverse()
.Cascade.AllDeleteOrphan()
.Not.LazyLoad();
}
}
public class CustomerAddressMap : ClassMap<CustomerAddress>
{
public CustomerAddressMap()
{
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Street).Length(50);
References(x => x.Customer);
}
}

If the address you are saving already has an id assigned to it you can simply do the following:
var customer = session.Get<Customer>(customerId);
Session.Merge(address); // This will copy your transient entity into
// ..an entity with the same id that is in the
// ..first level cache.
session.SaveOrUpdate(customer);
You are getting that error because and address with an ID of 14 is already associated with your NHibernate session. So when you create the new detached transient entity and try to save it with that id NHibernate throws an error. Generally this is a good thing as it is very rare to want to do what you are doing.
A far better pattern would be to have a ViewModel for the fields of an address that are changeable by a user, and then do the following:
var address = Session.Get<Address>(addressVM.Id);
Mapper.Map(addressVM, address); // This is some type of mapper to copy properties
// ..from one object to another. I like automapper
// ..for this
Session.SaveOrUpdate(address);

If you are updating an address, why are you clearing the collection and re-adding?
using (var tx = session.BeginTransaction())
{
var customer = session.Get<Customer>(customerId);
var address = customer.Address.Single(/*optional condition here*/);
//or, if you are not updating the Customer, this might be better
var address = session.Get<Address>(addressId);
address.Street = updatedStreetInfo;
tx.Commit();
}

Related

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

Simple mapping in fluent nhibernate

I have a class Client which has a attribute of dogs
public class ClientsMap : ClassMap<Clients>
{
public ClientsMap()
{
Id(x => x.ClientID);
HasMany(x => x.Dogs);
}
}
public class Client
{
public virtual IList<Dog> Dogs { get; set; }
public virtual int ClientID { get; set; }
}
and a class of dog that references client.
public class Dog
{
public virtual Clients Client { get; private set; }
public virtual int Id { get; set; }
}
public class DogMap : ClassMap<Dog>
{
public DogMap()
{
Table("Pooches");
Id(x => x.Id);
References(x => x.Client).Column("ClientId");
}
}
Because I am mapping on to an existing DB i cannot change the field names.
When I try and return the dogs collection I am getting an invalid column error on client_id with the SQL
SELECT
dogs0_.Clients_id as Clients3_1_,
dogs0_.Id as Id1_,
dogs0_.Id as Id1_0_,
dogs0_.ClientId as ClientId1_0_
FROM
pooches dogs0_
How can I make this use clientid over cliet_id. I thought I specified this in the dogs map.
You should also specify the column name on the one to many relationship.
HasMany(x => x.Dogs)
.KeyColumn("ClientId");

Fluent Nhibernate - search for an item based on value of a many to many relationship

Hopefully the title of this question makes sense, if not, here is my elaboration.
With two entities, Brand and Affiliate and a many-to-may relationship between them i would like to be able to use a query to find the Affiliates where the BrandName is a variable value.
Here is the Affiliate class and Affiliate MapClass (simplified of course)
public class Affiliate
{
public virtual int Id { get; private set; }
public virtual DateTime DateReceived { get; set; }
public virtual IList<Brand> Brands { get; set; }
public Affiliate()
{
Brands = new List<Brand>();
}
}
public class AffiliateApplicationRecordMap : ClassMap<Affiliate>
{
public AffiliateApplicationRecordMap()
{
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.DateReceived, "TimeStampCreated");
HasManyToMany(x => x.Brands)
.Cascade.All()
.ParentKeyColumn("AffiliateID")
.ChildKeyColumn("BrandID")
.Table("AffiliateBrand");
}
}
There is a mapping table called AffiliateBrand which provides the many to many mapping.
Here is the Brand class and ClassMap
public class Brand
{
public virtual int ID { get; private set; }
public virtual string Name { get; set; }
public virtual IList<Affiliate> Affiliates{ get; set; }
public Brand()
{
Affiliates = new List<Affiliate>();
}
public virtual void AddAffiliateApplication(Affiliate affiliate)
{
affiliate.Brands.Add(this);
Brands.Add(affiliate);
}
}
public class BrandMap : ClassMap<Brand>
{
public BrandMap()
{
Id(x => x.ID).GeneratedBy.Identity();
Map(x => x.Name);
HasManyToMany(x => x.Affiliates)
.Cascade.All()
.Inverse()
.ParentKeyColumn("BrandID")
.ChildKeyColumn("PartnerID")
.Table("AffiliateBrand");
}
}
Now i'm tyring to write this query with NHibernate:
var result = session
.CreateCriteria(typeof(Partner))
.AddOrder(Order.Asc("DateReceived"))
.Add(Restrictions.Eq("Brands.Name", brandName))
.SetMaxResults(10)
.List<Partner>();
Now clearly this isn't working and i didn't really think it would. What i'm trying to do is get all Affiliates back where the Brand has a specific name. How do i write this query?
You need to add a join to your criteria using CreateAlias
var result = session
.CreateCriteria(typeof(Partner))
.AddOrder(Order.Asc("DateReceived"))
.CreateAlias("Brands", "brand")
.Add(Restrictions.Eq("brand.Name", brandName))
.SetMaxResults(10)
.List<Partner>();

Fluent NHibernate one to many not saving children

I am using Fluent NHibernate. This is a classic case of a one to many relationship. I have one Supply parent with many SupplyAmount children.
The Supply parent object is saving with correct info, but the amounts are not getting inserted into the db when I save the parent. What am I doing for the cascade not to work?
The entities are as follows:
public class Supply : BaseEntity
{
public Guid SupplyId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Comments { get; set; }
public virtual IList<SupplyAmount> Amounts { get; set; }
public Supply()
{
Amounts = new List<SupplyAmount>();
}
public virtual void AddAmount(SupplyAmount amount)
{
amount.Supply = this;
Amounts.Add(amount);
}
}
public class SupplyAmount : BaseEntity
{
public virtual Guid SupplymountId { get; set; }
public virtual Supply Supply { get; set; }
public virtual int Amount { get; set; }
}
And the mapping as follows:
public class SupplyMap : ClassMap<Supply>
{
public SupplyMap()
{
Id(x => x.SupplyId);
Map(x => x.LastName);
Map(x => x.FirstName);
Map(x => x.Comments);
HasMany<SupplyAmount>(x => x.Amounts)
.Inverse().Cascade.SaveUpdate()
.KeyColumn("SupplyAmountId")
.AsBag();
}
}
public class SupplyAmountMap : ClassMap<SupplyAmount>
{
public SupplyAmountMap()
{
Id(x => x.SupplyAmountId);
References(x => x.Supply, "SupplyId").Cascade.SaveUpdate();
Map(x => x.Amount);
}
}
And this is how I call it:
public SaveIt()
{
Supply sOrder = Supply();
sOrder.FirstName = "TestFirst";
sOrder.LastName = "TestLast";
sOrder.Comments = "TestComments";
for (int i = 0; i < 5; i++)
{
SupplyAmount amount = new SupplyAmount();
amount.Amount = 50;
amount.Supply = sOrder;
sOrder.AddAmount(amount);
}
// This call saves the Supply to the Supply table but none of the Amounts
// to the SupplyAmount table.
AddSupplyOrder(sOrder);
}
I know this is an old post but why not...
// This call saves the Supply to the Supply table but none of the Amounts
This comment in SaveIt() indicates you call the save on the Supply and not the amounts.
In this case you have your logic the wrong way around.
So to fix this:
SupplyMap -> The Inverse shouldn't be there for Amounts.
HasMany<SupplyAmount>(x => x.Amounts).Cascade.SaveUpdate();
SupplyAmountMap ->
remove References(x => x.Supply, "SupplyId").Cascade.SaveUpdate();
Replace it with
References<Supply>(x=>x.Supply);
You should now be right to call the save on your supply object only and it will cascade down to the amounts.
Session.Save(supply);
In your test after you have arrange the supply and supplyamount make sure you call a
Session.Flush()
after your save to force it in.
This isn't as important in code as you will usually run in transactions before recalling the supply object.
Cheers,
Choco
Also as a side note it usually not a good idea to be to verbose with fluentmappings. let the default stuff do it thing which is why I would recommend against the column naming hints.

Fluent NHibernate Relationship Mapping and Save Exception

I'm new to NHibernate and am attempting to use Fluent's AutoMapping capability so that I do not need to maintain separate XML files by hand. Unfortunately I'm running into a problem with referenced entities, specifically 'Exception occurred getter of Fluent_NHibernate_Demo.Domain.Name.Id' - System.Reflection.TargetException: Object does not match target type.
I appear to have an error in at least one of my mapping classes although they do generate the correct SQL (i.e. the created tables have the correct indexes).
The implementations for my domain models and mappings are:
Name.cs
public class Name
{
public virtual int Id { get; protected set; }
public virtual string First { get; set; }
public virtual string Middle { get; set; }
public virtual string Last { get; set; }
}
Person.cs
public class Person
{
public virtual int Id { get; protected set; }
public virtual Name Name { get; set; }
public virtual short Age { get; set; }
}
NameMap.cs
public NameMap()
{
Table("`Name`");
Id(x => x.Id).Column("`Id`").GeneratedBy.Identity();
Map(x => x.First).Column("`First`").Not.Nullable().Length(20);
Map(x => x.Middle).Column("`Middle`").Nullable().Length(20);
Map(x => x.Last).Column("`Last`").Not.Nullable().Length(20);
}
PersonMap.cs
public PersonMap()
{
Table("`Person`");
Id(x => x.Id).Column("`Id`").GeneratedBy.Identity();
References<Name>(x => x.Name.Id, "`NameId`").Not.Nullable();
// There's no exception if the following line is used instead of References
// although no constraint is created
// Map(x => x.Name.Id).Column("`NameId`").Not.Nullable();
Map(x => x.Age).Column("`Age`").Nullable();
}
Finally, the following code will produce the exception:
Name name = new Name { First = "John", Last = "Doe" };
session.Save(name);
Person person = new Person { Name = name, Age = 22 };
session.Save(person); // this line throws the exception
As mentioned, the created schema is correct but I'm unable to save using the above code. What is the correct way to create a foreign key constraint using Fluent NHibernate?
If you want to reference the name, by ID, then that's what you should do. NHibernate is smart enough to figure out what the actual FK field on Person should be and where it should point; that is, after all, the job an ORM is designed to perform.
Try this mapping:
public PersonMap()
{
Table("`Person`");
Id(x => x.Id).Column("`Id`").GeneratedBy.Identity();
References(x => x.Name, "`NameId`").Not.Nullable();
Map(x => x.Age).Column("`Age`").Nullable();
}
You've mapped the person and the name; as a result, NHibernate knows which property is the ID property of Name, and can create and traverse the foreign key on Person.