NHibernate table per type persist child from existing parent - nhibernate

I have 2 classes, Member.cs and Customer.cs and using table-per-type inheritance mapping described here.
This question poses the same problem, but with no answer.
Customer.cs
public class Customer
{
}
Member.cs
public class Member : Customer
{
public Member(Customer customer)
{
CreateFromCustomer(customer);
}
private void CreateFromCustomer(Customer customer)
{
// Here I assume I'll assign the Id so NHibernate wouldn't have to create a new Customer and know what Customer to be referred
Id = customer.Id;
}
}
CustomerMap.cs
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id)
.GeneratedBy.GuidComb();
}
}
MemberMap.cs
public class MemberMap : SubclassMap<Member>
{
public MemberMap()
{
KeyColumn("Id");
}
}
I tried several test case :
Test1.cs
[Test]
public void CanAddCustomer()
{
var customerRepo = /* blablabla */;
using (var tx = NHibernateSessionManager.GetSession().BeginTransaction())
{
var customer = new Customer()
customerRepo.RegisterCustomer(customer);
tx.Commit();
}
using (var tx = NHibernateSessionManager.GetSession().BeginTransaction())
{
/* Get the persisted customer */
var customer = customerRepo.GetCustomerByWhatever();
var member = customerRepo.RegisterMember(new Member(customer));
tx.Commit();
}
}
I'm expecting to have :
1 customer and 1 member which is a child of that customer
Instead I have :
2 customers (1 that is what was correctly created and 1 with all null columns) and 1 member that Id referred to all null columns Customer.
Is it the expected behavior?
I understand if we were wanted to create a child object from a transient parent object, this is a correct behavior.
But what if we were to create a child object that refers to an existing parent object?
The link I provided doesn't cover any persistence example, neither does googling.

Short Answer
No, it is not possible to "upgrade" an already persisted object to its subclass. Nhibernate simply doesn't support this. That's why you see 2 customers and one member entry. This is actually the expected behavior because Nhibernate simply creates a copy with a new ID of the object instead of creating the reference to Member...
So basically you could do either
Copy the data of Customer into Member, delete customer and save Member
Use a different object structure without subclasses where Member is a different table with it's own ID and a reference to Customer
Use native sql to insert the row into Member...
Some example:
Your classes could look like this
public class Customer
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
public class Member : Customer
{
public virtual string MemberSpecificProperty { get; set; }
}
Basically, Member could have additional properties, but will have the same properties as Customer of cause, too.
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id)
.GeneratedBy.GuidComb();
Map(x => x.Name);
}
}
and for the sub class, you have to map additional properties only!
public class MemberMap : SubclassMap<Member>
{
public MemberMap()
{
Map(x => x.MemberSpecificProperty);
}
}
testing it
{
session.Save(new Customer()
{
Name ="Customer A"
});
session.Save(new Member()
{
Name = "Customer B",
MemberSpecificProperty = "something else"
});
session.Flush();
}
This will create 2 entries in the customer table and one row into Member table. So this is as expected, because we created one customer and one member...
Now the "upgrade" from Customer A to Member:
using (var session = NHibernateSessionFactory.Current.OpenSession())
{
session.Save(new Customer()
{
Name ="Customer A"
});
session.Flush();
}
using (var session = NHibernateSessionFactory.Current.OpenSession())
{
var customer = session.Query<Customer>().FirstOrDefault();
//var member = customer as Member;
var member = new Member()
{
Name = customer.Name,
MemberSpecificProperty = "something else"
};
session.Delete(customer);
session.Save(member);
session.Flush();
}

Related

NHibernate Delete Many-to-Many Parent and Child

I have 3 classes. Company, Address and Offices. Following are the entities definition.
Company:
public class Company: Identity
{
public Company()
{
Offices = Offices ?? new List<Office>();
}
public virtual string Name { get; set; }
public virtual IList<Office> Offices { get; set; }
public virtual void AddOffice(Office office)
{
office.Company = this;
Offices.Add(office);
}
}
Address:
public class Address: Identity
{
public Address()
{
CompanyOffices = CompanyOffices ?? new List<Office>();
}
public virtual string FullAddress { get; set; }
public virtual IList<Office> CompanyOffices { get; set; }
}
Office:
public class Office: Identity
{
public Office()
{
Company = Company ?? new Company();
Address = Address ?? new Address();
}
public virtual Company Company { get; set; }
public virtual Address Address { get; set; }
public virtual bool IsHeadOffice { get; set; }
}
Now for these classes i have following Mapping.
Company Mapping:
public class CompanyMapping: IdentityMapping<Company>
{
public CompanyMapping()
{
Map(x => x.Name);
HasMany(x => x.Offices).KeyColumn("CompanyId").Inverse().Cascade.AllDeleteOrphan();
}
}
Address Mapping:
public class AddressMapping: IdentityMapping<Address>
{
public AddressMapping()
{
Map(x => x.FullAddress);
HasMany(x => x.CompanyOffices).KeyColumn("AddressId").Inverse().Cascade.All();
}
}
Office Mapping:
public class OfficeMapping: IdentityMapping<Office>
{
public OfficeMapping()
{
Map(x => x.IsHeadOffice);
References(x => x.Company).Column("CompanyId").Cascade.None();
References(x => x.Address).Column("AddressId").Cascade.All();
}
}
Test Code:
var sessionFactory = CreateSessionFactory();
using (var session = sessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var companyOne = new Company { Name = "Company One" };
var companyTwo = new Company { Name = "Company Two" };
var addressOne = new Address { FullAddress = "Address One" };
var addressTwo = new Address { FullAddress = "Address Two" };
var officeOne = new Office { Company = companyOne, Address = addressOne, IsHeadOffice = true };
var officeTwo = new Office { Company = companyTwo, Address = addressTwo, IsHeadOffice = false };
var officeThr = new Office { Company = companyOne, Address = addressTwo, IsHeadOffice = true };
companyOne.AddOffice(officeOne);
companyTwo.AddOffice(officeTwo);
companyOne.AddOffice(officeThr);
session.SaveOrUpdate(companyOne);
session.SaveOrUpdate(companyTwo);
transaction.Commit();
}
}
using (var session = sessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var companyOne = session.Get<Company>((long)1);
session.Delete(companyOne);
transaction.Commit();
}
}
Code Description:
Here a company is going to have multiple offices. Office is many to many relationship of company and address, but due to the fact that there could be many other columns (Office own data), I have changed the relationship from many to many to 2 one to many.
Question:
I want my application to delete any address that is left orphan when a company is deleted. But in this case when i delete my company, It will delete it's address as well. But it doesn't checks if the address is orphan. If there is another reference to address it will still be deleted. As per given above test code it should delete the "Company One" and "Address One" but not the "Address Two" as it is not orphan. But it deletes the "Address Two" as well.
Kindly can anyone let me know what's wrong with the above mapping?
I'm not sure if NHibernate does a global check for a DeleteOrphan, or only a Session check, a true global check would involve a DB query of course. But this actually isn't relevant here, the reason DeleteOrphan exists is for when you disassociate entities with parents (e.g. remove an item from the collection then call Update on the parent) but you are calling an operation on a top level entity which is cascading down directly.
What's really happening then is that you are calling Delete on a Company, as per the mapping on Offices, that is the All component of it, every child in the Offices collection thus has Delete called on it, because you have called Delete on the parent Company.
Since the mapping for Office also has a child Address, which is also mapped All, and Office just had Delete called on it, it will thus call Delete directly on it's Address child, since a direct Delete doesn't care about other associations or not (unless Session or the DB says so), the Address is simply deleted from the DB.
If you cannot change your entity structure here, then you will have to either stop the Delete reaching unorphaned Addresses (disassociate them manually first) or stop the Delete cascading to Address at all, then manage Address deletion totally manually.
I'm sure there are some better entity structures which can handle these relationships better if you have flexibility there though :P

How to change the collection name on an index

When I save a document that has a generic type DataView<Customer>, I'm manually setting the collection name to "customers". However, I'm having some trouble making an index using AbstractIndexCreationTask with a non-default collection name. Here's my index:
public class customers_Search
: AbstractIndexCreationTask<DataView<Customer>, customers_Search.Result>
{
public class Result
{
public string Query { get; set; }
}
public customers_Search()
{
Map = customers =>
from customer in customers
where customer.Data != null
select new
{
Query = AsDocument(customer.Data).Select(x => x.Value)
};
Index(x => x.Query, FieldIndexing.Analyzed);
}
}
When this gets deployed, it looks like this:
from customer in docs.DataViewOfCustomer
where customer.Data != null
select new {
Query = customer.Data.Select(x => x.Value)
}
This doesn't work obviously, and if I change DataViewOfCustomer to "customers" it works just fine.
I'd rather not have to use non-type-checked (string) indexes to deploy. Is there a way to set the collection name that from the AbstractIndexCreationTask class?
Update
Since my data class is generic, I made a generic index which fixes up the names.
public class DataViewQuery<TEntity>
: AbstractIndexCreationTask<DataView<TEntity>, DataViewQueryResult>
{
private readonly string _entityName;
private readonly string _indexName;
// this is to fix the collection name for the index name
public override string IndexName { get { return _indexName; } }
// this is to fix the collection name for the index query
public override void Execute(IDatabaseCommands databaseCommands, DocumentConvention documentConvention)
{
var conventions = documentConvention.Clone();
conventions.FindTypeTagName =
type =>
typeof(DataView<TEntity>) == type
? _entityName
: documentConvention.FindTypeTagName(type);
base.Execute(databaseCommands, conventions);
}
public DataViewQuery(string entityName)
{
_entityName = entityName;
_indexName = String.Format("{0}/{1}", entityName, "Query");
Map = items =>
from item in items
where item.Data != null
select new
{
Query = AsDocument(item.Data).Select(x => x.Value)
};
Index(x => x.Query, FieldIndexing.Analyzed);
}
}
public class DataViewQueryResult
{
public string Query { get; set; }
}
Then I can create a specific index which has all the configuration in it.
// sets the collection type (DataView<Customer>) for the index
public class CustomerQuery : DataViewQuery<Customer>
{
// sets the collection name for the index
public CustomerQuery() : base(EntityName.Customers) { }
}
You need to configure this in the conventions.
The property to configure is FindTypeTagName

nHibernate mapping by code - how to populate object tree with nested collections

I can't seem to find a good example of what I want to do, using nHibernate mapping by code:
I have an object "Message" that has a list of "Organisms" and each "Organism" has a list of "Drugs". Please forgive my pseudo example below:
public class Message
List<Organism> Organisms;
public class Organism
List<Drugs> Drugs;
public class Drug
//create our tree structure
var message=new Message();
var drug = new Drug();
var organism = new Organism();
organism.Drugs.Add(drug);
message.Organisms.Add(organism); //now we have a message with one organism child with one drug drug
Using Bags in my class mappings, I am able to correctly save this message object, and have it persist correctly. The problem is when calling Get with the message ID to bring back the message. I am getting "collection is not associated with any session"
As for my mappers, my message has a bag of organisms, which have a bag of drugs.
Does anyone have an example of doing this type of thing with nHibernate mapping by code? I am missing something in my mappers...
you need to do a little more if you want bi-directional mappings.
So I would define a message property on the Organism class. And map that as a reference.
Add Add/Remove properties on the parent class for your collection class so that you can add the element to the list and to also add the this reference to your child object.
public class Message
{
public Int32 Id { get; set; }
public IList<Organism> Organisms { get; protected set; }
public Message()
{
Organisms = new List<Organism>();
}
public void AddOrganism(Organism organism)
{
if (Organisms.Contains(organism))
return;
organism.Message = this;
Organisms.Add(organism);
}
public void RemoveOrganism(Organism organism)
{
if (!Organisms.Contains(organism))
return;
Organisms.Remove(organism);
}
}
public class Organism
{
public Int32 Id {get;set;}
public Message Message { get; set; }
}
With regards to your mappings you need to set the Message.Organisms to a Bag and on the Organism.Message to a ManyToOne. For the next level down, just repeat this.
public class MessageMap
{
public MessageMap()
{
Bag(x => x.Organisms, map =>
{
map.Key(k =>
{
k.Column(col => col.Name("MessageId"));
});
map.Cascade(Cascade.All | Cascade.DeleteOrphans);
},
action => action.OneToMany());
}
}
public class OrganismMap
{
public OrganismMap()
{
ManyToOne(x => x.Message, map =>
{
map.Column("MessageId");
map.NotNullable(false);
});
}
}

Mapping a many-to-many relationship as an IDictionary

I am trying to map a Person and Address class that have a many-to-many relationship. I want to map the Address collection as an IDictionary with the Address property Type as the key. The relationship is only mapped from the Person side.
public class Person
{
public IDictionary<int, Address> Addresses { get; set; }
}
public class Address
{
public int Type { get; set; }
}
The mapping I am using is:
HasManyToMany<Address>(x => x.Addresses).Table("PersonAddress")
.ParentKeyColumn("PersonId").ChildKeyColumn("AddressId")
.AsMap(x => x.Type);
The problem is that the SQL issued is:
SELECT addressesd0_.PersonId as PersonId1_,
addressesd0_.AddressId as AddressId1_,
addressesd0_.Type as Type1_,
address1_.AddressId as AddressId5_0_
-- etc.
FROM dbo.PersonAddress addressesd0_
left outer join dbo.Address address1_
on addressesd0_.AddressId = address1_.AddressId
WHERE addressesd0_.PersonId = 420893
It's attempting to select Type from the many-to-many join table, which doesn't exist. I've tried a number of variations of the mapping without success.
How can I map this?
It's not possible. Dictionaries need a key value in the relational table. The table structure you have is a simple many-to-many bag or set.
What I would do, is map it as a normal bag or set and provide dictionary like access in the entity:
public class Person
{
private IList<Address> addresses;
public IEnumerable<Address> Addresses { get { return addresses; } }
public Address GetAddressOfType(int addressType)
{
return addresses.FirstOrDefault(x => x.Type == addressType);
}
public void SetAddress(Address address)
{
var existing = GetAddressOfType(address.Type);
if (existing != null)
{
addresses.Remove(existing);
}
addresses.Add(address);
}
}
You need to use components:
HasMany<Address>(x => x.Addresses)
.AsMap<int>("FieldKey")
.Component(x => x.Map(c => c.Id));

dynamic-component fluent automapping

Does anyone know how can we automatically map dynamic components using Fluent Automapping in NHibernate?
I know that we can map normal classes as components, but couldn't figure out how to map dictionaries as dynamic-components using fluent automapping.
Thanks
We've used the following approach successfully (with FluentNH 1.2.0.712):
public class SomeClass
{
public int Id { get; set; }
public IDictionary Properties { get; set; }
}
public class SomeClassMapping : ClassMap<SomeClass>
{
public SomeClassMapping()
{
Id(x => x.Id);
// Maps the MyEnum members to separate int columns.
DynamicComponent(x => x.Properties,
c =>
{
foreach (var name in Enum.GetNames(typeof(MyEnum)))
c.Map<int>(name);
});
}
}
Here we've mapped all members of some Enum to separate columns where all of them are of type int. Right now I'm working on a scenario where we use different types for the dynamic columns which looks like this instead:
// ExtendedProperties contains custom objects with Name and Type members
foreach (var property in ExtendedProperties)
{
var prop = property;
part.Map(prop.Name).CustomType(prop.Type);
}
This also works very well.
What I'm still about to figure out is how to use References instead of Map for referencing other types that have their own mapping...
UPDATE:
The case with References is unfortunately more complicated, please refer to this Google Groups thread. In short:
// This won't work
foreach (var property in ExtendedProperties)
{
var prop = property;
part.Reference(dict => dict[part.Name]);
}
// This works but is not very dynamic
foreach (var property in ExtendedProperties)
{
var prop = property;
part.Reference<PropertyType>(dict => dict["MyProperty"]);
}
That's all for now.
I got struggle with exactly the same problem. With fluent nHibernate we cannot map this but on my own I somehow was able to solve this. My solution is to build lambda expression on the fly and the assign this into object. For instance, lets say that:
Let my copy part of the site that Oliver refer:
DynamicComponent(
x => x.Properties,
part =>
{
// Works
part.Map("Size").CustomType(typeof(string));
// Works
var keySize = "Size";
part.Map(keySize).CustomType(typeof(string));
// Does not work
part.Map(d => d[keySize]).CustomType(typeof(string));
// Works
part.References<Picture>(d => d["Picture"]);
// Does not work
var key = "Picture";
part.References<Picture>(d => d[key]);
});
And we have this problem that we need to hardcode "Picture" in mapping. But somehow after some research I created following solution:
var someExternalColumnNames = GetFromSomewhereDynamicColumns();
'x' is a DynamicComponent callback in fluent Nhibernate e.g. (DynamicColumns): DynamicComponent(a => a.DynamicColumns, x => (...content of method below...))
foreach(var x in someExternalColumnNames)
{
if (x.IsReferenceToPerson == true)
{
var param = Expression.Parameter(typeof(IDictionary), "paramFirst");
var key = Expression.Constant(x.Name);
var me = MemberExpression.Call(param, typeof(IDictionary).GetMethod("get_Item"), new[] { key });
var r = Expression.Lambda<Func<IDictionary, object>>(me, param);
m.References<Person>(r, x.Name);
}
else
{
m.Map(x.Name)
}
}
//
// Some class that we want to reference, just an example of Fluent Nhibernate mapping
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Table("Person");
Id(x => x.PersonId, "PersonId");
Map(x => x.Name);
}
}
public class Person
{
public virtual Guid PersonId { get; set; }
public virtual string Name { get; set; }
public Person()
{ }
}
Maybe it would be helpful