views:

698

answers:

3

I have a simple three table DB with many-to-many relation.

A(id, Name)
B(id, Name)
AB(AId, BId) references A and B

The corresponding classes:

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

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

public class AB
{
    public virtual A A { get; set; }
    public virtual B B { get; set; }
    public override bool Equals(object obj) { /* routine */ }
    public override int GetHashCode() { /* routine */ }
}

I have made mappings with Fluent NHibernate:

public class AMap : ClassMap<A>
{
    public AMap()
    {
        Id(x => x.Id).GeneratedBy.Identity();
        Map(x => x.Name);
    }
}

public class BMap : ClassMap<B> { /* The same as for A */ }

public class ABMap : ClassMap<AB>
{
    public ABMap()
    {
        CompositeId()
            .KeyReference(x => x.A, "AId")
            .KeyReference(x => x.B, "BId");
    }
}

So now I want to be able to do something like this

var a = new A { Name = "a1" };    
var b = new B { Name = "b1" };    
var ab = new AB { A = a, B = b };

//session.SaveOrUpdate(a);
//session.SaveOrUpdate(b);
session.SaveOrUpdate(ab);

But on SaveOrUpdate I do get TransientObjectException. So to pass over it I need to SaveOrUpdate A and B before saving the AB. But it seems that there should be the other way to persist these objects in a single SaveOrUpdate.

Is there any way to point in AB mapping to Cascade A and B on save operation?

UPDATE:

I removed the List of AB links in A class for clarity. Originally it was:

public class A
{
    public A()
    {
        LinkToB = new List<AB>();
    }

    public virtual int Id { get; set; }
    public virtual string Name { get; set }
    public virtual IList<AB> LinkToB { get; private set; }
}

public class AMap : ClassMap<A>
{
    public AMap()
    {
        Id(x => x.Id).GeneratedBy.Identity();
        Map(x => x.Name);

        HasMany(x => x.LinkToB)
            .KeyColumn("AId")
            .Inverse()
            .Cascade.All()
            .AsBag();
    }
}

// inside the transaction
var a = new A { Name = "a1" };
var b = new B { Name = "b1" };

a.LinkToB.Add(new AB { A = a, B = b });
// session.SaveOrUpdate(b);
session.SaveOrUpdate(a);

Thank you!

A: 

Just a side-note, but why did you create a class AB ? If the relationship between these 2 classes does not have any extra properties, then your class 'AB' is not necessary ...

To answer your question: you can define cascade options on a relationship in NHibernate.

In Fluent NHibernate, I guess you'll have to do it like this:

public class FooMap : ClassMap<Foo>
{
    public FooMap()
    {
       HasMany(x => x.Bars).Cascade.AllDeleteOrphan();
    }
}

(note that you have to apply it to your situation, but the collectionmapping returns a property Cascade, so you can define which cascading technique you wish to apply)

Frederik Gheysels
Thanks for quick reply. I removed this relation for simplicity. Where I should enter this Cascade rule?
mipi
I haven't really used Fluent yet, but when you use xml mapping files, the cascade attribute has to be set at the collection.
Frederik Gheysels
And for my situation there is no way to cascade the save operation or is there?
mipi
I used all these techniques but they solve the problem only partially. The cascading in your sample goes from Foo class (in my case A class). But what about B class? If I will add the B to the AB it won't cascade.
mipi
sample: `a.Links.Add(new AB { A = a; B = b });` b won't cascade
mipi
And I don't know where to put the rule about cascading the b...
mipi
+1  A: 

I did ask this question at nhibernate user group. And the answer was that there is no way to cascade any operation using composite-id (maybe in the future releases it will be possible).

So I made a workaround. I placed two References (many-to-one with cascading) instead of CompositeId and added Id to the AB table and AB entity. Now it's working and A and B entities are cascading.

mipi
A: 

I actually managed to get it working very easily, and the code looks too much NOT DRY!

Here is what my case looked like:

public class MappingSample : ClassMap<DomainClass>
{
    public MappingCvTheme()
    {
        CompositeId()
            .KeyProperty(x => x.SomeProperty)
            .KeyReference(x => x.SomeReferencedObjectProperty);

        //here comes the trick, repeat the reference code:
        References(x => x.SomeReferencedObjectProperty).Cascade.All();
    }
}

I hope it helps somebody looking for the same issue in the future.

Mohamed Meligy
One ugly side effect though that you'll have TWO columns created for SomeReferencedObjectProperty. One called "SomeReferencedObjectPropertyID", another called just "SomeReferencedObjectProperty". They'll always be in sync, but it still is ugly, and if you try to set the KeyReference and References to the same column name, INSERT will fail because NH will send 2 parameters although there is one column!
Mohamed Meligy
* in the prev comment I assument you are using NH to create your schema
Mohamed Meligy

related questions