views:

45

answers:

1

Hello everybody,

would you mind help me to better understand what to do with the relationship between my entities and NHibernate?

I have some difficulties to understand what operations I need to do by hand, and what operations NHibernate will do for me (or not).

I have these 2 entities:

public class Position : BaseEntity<int, Position>
{
    private IList<Player> allPlayers = new List<Player>();

    public Position()
    {

    }

    public Position(string name, int number)
        : this()
    {
        Name = name;
        Number = number;
    }

    public string Name { get; set; }
    public int Number { get; set; }
}

and

public class Player : BaseEntity<int, Player>
{
    public Player()
    {
        Visible = true;
    }

    public Player(string firstName, string lastName, int defaultNumber, Sex sex = Sex.Male, Position defaultPosition = null)
        : this()
    {
        FirstName = firstName;
        LastName = lastName;
        DefaultNumber = defaultNumber;
        Sex = sex;
        DefaultPosition = defaultPosition;
    }

    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Position DefaultPosition { get; set; }
}

Here are the fluent mappings:

public class PositionMap : ClassMap<Position>
{
    public PositionMap()
    {
        Id(pp => pp.Id)
            .GeneratedBy.Increment();

        Map(pp => pp.Name)
            .Not.Nullable();

        Map(pp => pp.Number)
            .Not.Nullable();

        HasMany<Player>(Reveal.Member<Position>("allPlayers"))
            .Access.CamelCaseField();
    }
}

public class PlayerMap : ClassMap<Player>
{
    public PlayerMap()
    {
        Table("Players");

        Id(p => p.Id)
            .GeneratedBy.Increment();

        Map(p => p.FirstName)
            .Not.Nullable()
            .UniqueKey("Players_Unique_FirstName_LastName");

        Map(p => p.LastName)
            .Not.Nullable()
            .UniqueKey("Players_Unique_FirstName_LastName");

        References(p => p.DefaultPosition);
    }
}

As you can see, one player has one position, but may have no position (so DefaultPosition is nullable).

Here are my questions:

  1. When I associate a position to a player's DefaultPosition, I have to do this through helper methods on Position? (like AddPlayer, DeletePlayer...)

I'd like, when I delete a position, that all the players who have this position, have instead a null DefaultPosition.

  1. Do I have to clear manually the field allPlayers of the position, and manually set null to all associated player's DefaultPosition, or can NHibernate take care of this for me?

  2. Why does NHibernate only does a DELETE FROM Positions WHERE Id... and doesn't update the field DefaultPOsition of the concerned players? I've tried to add a Cascade.Delete on the HasMany in PositionMap, but that doesn't change anything. Do I have to run a custom query which does that?

Thanks in advance

+2  A: 

Q1 If you add a property for Players on Position, then you don't need those helper methods.

public class Position : BaseEntity<int, Position>
{
    private IList<Player> allPlayers = new List<Player>();

    //read only
    public IList<Player> Players { get { return allPlayers;} }

    //... rest of class omitted
}

then call like:

var position = new Position();
position.Players.Add(new Player());

Q2, Q3 you could have a helper method on position to simplify.

something like:

public class Position : BaseEntity<int, Position>
{
    public void RemoveAll()
    {
       // null out the position on players
       foreach(var player in allPlayers)
       {
           player.Position = null; // SETS PositionId FIELD IN PLAYER TABLE TO NULL
       }
       allPlayers.Clear();
    }

    // ... rest of class omitted
}

with a call looking like:

using(var session = SessionFactory.GetCurrentSession())
{
   using(var tx = session.BeginTransaction())
   {
      position.RemoveAll();
      position.Delete();
      tx.Commit();
   }
}

Since the players will persist beyond the position but the position gets removed, you wouldn't want to use cascade. Cascade is for deletes, not really updating of id's. For example, delete an order and cascade all of it's line items.

Tim Hoolihan
Then, how should I update the players in the database with NULL Position ids?Update them separately within the session? Or with HQL?
Mike
the sample code does that. Look in the RemoveAll function above...
Tim Hoolihan
The RemoveAll function clears the relationships of my entities. But when I save the deletion of the position, NHibernate doesn't automatically set NULL to the field PositionId of the table Players, for each player which has this position.So is my question. Is there a way to force NHibernate to do that automatically, or should I add a custom query to perform this update after the position was deleted?
Mike
I guess I don't know how else to say it? The code above does what you are asking, why would you need a custom query? The players are nhibernate objects, so when their position is set to null and the transaction is commited, their positionId in the db will be null at that point. And the position.Delete takes care of the deletion. So at the end of this code, you will have deleted the position and the positionid of all players will be null, which is what you are asking for. As for nhibernate doing that automatically,it won't. As the post says, Cascade is for delete, not nulling out.
Tim Hoolihan
i added a comment for emphasis where the PositionID is getting set to null in the example
Tim Hoolihan
You don't have to guess, it's my bad... I forgot to uncomment a line in my code :/And now indeed, NHibernate updates the players with NULL for their Position.Anyway, I have some more questions :)I have set Inverse on the HasMany(pp => pp.Players) in PositionMap.But if I add 2 players to the Position, and then Delete the position, NHibernate does 2 requests to update the players, instead of one global update like: UPDATE Players SET PositionId = NULL WHERE PositionId = @p0;Is it the normal behavior? Or is there a way to force NHibernate to do one update only?
Mike
you could do a sql query with something like var query = session.CreateQuery("Update Player set PositionId = null where PositionId = :PositionId"); query.SetParameter("PositionId", Position.Id); query.ExecuteUpdate(); tx.Commit(); Or you could look at a stored proc that does that and deletes the position. See http://ayende.com/Blog/archive/2006/09/18/UsingNHibernateWithStoredProcedures.aspx
Tim Hoolihan