tags:

views:

424

answers:

4

Is there any way in NHibernate that I can use the following Entities

public class Person
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual IList<Pet> Pets { get; set; }
}

public class Pet
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
}

And not have to create a "special" AddPet method on Person in order to have Child pets saved.

public void AddPet(Pet p)
{
    p.Person = this;
    Pets.Add(p);
}

_session.SaveOrUpdate(person);

Does not save the Pets because Pet has no Person reference.

If I update Pets to contain this reference.

public class Pet
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual Person Person { get; set; }
}

On new pets I still have to set Person this seems like overkill to me and also risky as People can still call

person.Pets.Add(new Pet())

The only other option I can think of is a Custom list that sets the parent reference when adding child entities.

A: 

http://stackoverflow.com/questions/552805/c-reflection-generics

Might be able to use this approach?

Wont work for children of children but its pretty good for Parent-Children.

protected static void SetChildReferences(E parent)
{
    foreach (var prop in typeof(E).GetProperties(BindingFlags.Public | BindingFlags.Instance))
    {
        if (!prop.CanRead) continue;

        Type listType = null;
        foreach (Type type in prop.PropertyType.GetInterfaces())
        {
            if (type.IsGenericType &&
                type.GetGenericTypeDefinition() == typeof(ICollection<>))
            {
                listType = type.GetGenericArguments()[0];
                break;
            }
        }

        List<PropertyInfo> propsToSet = new List<PropertyInfo>();
        foreach (PropertyInfo childProp in 
            (listType ?? prop.PropertyType).GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            if (childProp.PropertyType == typeof(E))
                propsToSet.Add(childProp);
        }

        if (propsToSet.Count == 0) continue;

        if (listType == null)
        {
            object child = prop.GetValue(parent, null);
            if (child == null) continue;
            UpdateProperties(propsToSet, child, parent);
        }
        else
        {
            ICollection collection = (ICollection)prop.GetValue(parent, null);
            foreach (object child in collection)
            {
                if (child == null) continue;
                UpdateProperties(propsToSet, child, parent);
            }
        }
    }
}

protected static void UpdateProperties(List<PropertyInfo> properties, object target, object value)
{
    foreach (PropertyInfo property in properties)
    {
        property.SetValue(target, value, null);
    }
}
bleevo
A: 

How do you know which pets does a person have in the database if you don't store the relation?

I would include the

public void AddPet(Pet p)

in the Person object and have a

IEnumerable<Pets> Pets { get; }

public property. The List property would be protected. This way person.Pets.Add(new Pet()) cannot happen and you're safe.

gcores
Yes I understand this is the way, my question was does the Pet being inside a collection of Pets inside a person infer that relationship anyway?
bleevo
No I doesn't, because it doesn't have a relation. I mean, if you reload a person, you couldn't reload the person's pets cos there is no way of knowing which pets would belong to that person. That's why you need a person property in the Pet. This is not a NHibernate issue, it's a DB issue.
gcores
A: 

This is not a NHibernate issue, it is a domain model issue. NHibernate is not responsible to build up your references and back references of you domain.

I agree with gcores, make the list protected or private. Find a way to link your objects together in a consistent way. if it is not as simple as implementing a add method, consider a separate service class or factories. (If it is even more complicated, try something like Spring.Net.)

By the way, NHibernate probably stored the pets, but didn't have the information to write the correct foreign key. So it stored orphaned pets which never appeared in a pets list of any person.

Stefan Steinegger
+1  A: 

I modified your example just a bit (in line with many of the suggestions here):

public class Person
{
    private IList<Pet> pets;

    protected Person()
    {}

    public Person(string name)
    {
        Name = name;
        pets = new List<Pet>();
    }

    public virtual Guid Id { get; set; }
    public virtual string Name { get; set; }
    public virtual IEnumerable<Pet> Pets
    {
        get { return pets; }
    }

    public virtual void AddPet(Pet pet)
    {
        pets.Add(pet);           
    }
}

public class Pet 
{
    protected Pet()
    {}

    public Pet(string name)
    {
        Name = name;
    }

    public virtual Guid Id { get; set; }
    public virtual string Name { get; set; }
}

public class PersonMap : ClassMap<Person>
{
    public PersonMap()
    {
        Id(x => x.Id).GeneratedBy.GuidComb();
        Map(x => x.Name);
        HasMany(x => x.Pets).Cascade.AllDeleteOrphan().Access.AsLowerCaseField();
    }
}

public class PetMap : ClassMap<Pet>
{
    public PetMap()
    {
        Id(x => x.Id).GeneratedBy.GuidComb();
        Map(x => x.Name);
    }
}

The following test:

    [Test]
    public void CanSaveAndRetrievePetAttachedToPerson()
    {
        Person person = new Person("Joe");
        person.AddPet(new Pet("Fido"));

        Session.Save(person);

        Person retrievedPerson = Session.Get<Person>(person.Id);
        Assert.AreEqual("Fido", retrievedPerson.Pets.First().Name);
    }

passes.

Note that this is using Fluent NHibernate for the mapping and the Session.

Rob Scott
After thinking about this more I agree with you and others, its a domain problem not a NHibernate problem.
bleevo