0
votes

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?

1

1 Answers

1
votes

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