Search code examples
c#nhibernatefluent-nhibernatenhibernate-mapping

Fluent NHibernate code first One to Many


I'm beginning to learn NHibernate so am trying to create some simple relationships - I am trying to create the relationships between a team and the players & manager - so far I have 2 classes:

public class Person
{
    public virtual int PersonId { get; set; }
    public virtual Team Team { get; set; }
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
    public virtual PersonType PersonType { get; set; } = PersonType.Player;
}

public enum PersonType
{
    Player = 1,
    Manager = 2,
    Referee = 3
}

public class Team
{
    public virtual int TeamId { get; set; }
    public virtual int Ranking { get; set; }
    public virtual IList<Person> Players { get; set; } = new List<Person>();
    public virtual Person Manager { get; set; }
}

as well as the class maps for each:

public class PersonMap : ClassMap<Person>
{
    public PersonMap()
    {
        Id(x => x.PersonId);
        References(x => x.Team).Cascade.None();
        Map(x => x.FirstName);
        Map(x => x.LastName);
        Map(x => x.PersonType).CustomType<PersonType>(); // register the enum as a custom type, otherwise it will be mapped as a string
    }
}

public class TeamMap : ClassMap<Team>
{
    public TeamMap()
    {
        Id(x => x.TeamId);
        Map(x => x.Ranking);
        HasMany(x => x.Players).Inverse().Cascade.All(); // any changes that are made to the team are cascaded to the players
        References(x => x.Manager).Cascade.None();
    }
}

When I am running the following code in a Console application

class Program
{
    static void Main(string[] args)
    {
        // pull a connection string from the config file
        var connectionString = ConfigurationManager.ConnectionStrings["Bonkers"].ConnectionString;

        // build a configuration object
        var configuration = Fluently.Configure()
            .Database(MsSqlConfiguration.MsSql2012.ConnectionString(connectionString))
            .Mappings(m => m.FluentMappings
            .AddFromAssemblyOf<PersonMap>()
            .AddFromAssemblyOf<TeamMap>())
            .BuildConfiguration();

        // export the schema configuration
        var exporter = new SchemaExport(configuration);
        exporter.Execute(true, true, false);

        // create a session factory
        var sessionFactory = configuration.BuildSessionFactory();

        // open a new session and add a new customer
        using (var session = sessionFactory.OpenSession())
        {
            var manager = new Person { FirstName = "Chris", PersonType = PersonType.Manager };
            var players = new List<Person>()
            {
                manager,
                new Person { FirstName = "Mark", PersonType = PersonType.Player },
                new Person { FirstName = "Karl", PersonType = PersonType.Player }
            };

            players.ForEach(player => session.Save(player)); // save all the players (& manager)

            var team = new Team { Ranking = 1, Manager = manager, Players = players.Where(p => p.PersonType != PersonType.Manager).ToList() };

            players.ForEach(player => session.Save(player)); // save all the players (& manager)

            session.Save(team); // save the team (the players should now exist in there)
        }

        // using a session query the database and update the customer type
        using (var session = sessionFactory.OpenSession())
        {
            using (var transaction = session.BeginTransaction())
            {
                session.Query<Team>().ToList().ForEach(team => 
                {
                    WriteLine($"Manager: {team.Manager.FirstName} Ranking: {team.Ranking}");
                    WriteLine($"Team players:");
                    team.Players.ToList().ForEach(p => WriteLine($"- {p.FirstName} {p.PersonType}"));
                });

                WriteLine($"Players:");
                session.Query<Person>().ToList()
                    .ForEach(c => WriteLine($"- {c.FirstName} {c.LastName} {c.PersonId} {c.Team?.TeamId} {c.PersonType}"));
            }
        }

        ReadKey();
    }
}

The one-to-one relationship between the team and the manager appears to be correct from the console output, but the one-to-many relationship between the team and the players does not work - any thoughts where my mapping classes are wrong, or how I am inserting them?


Solution

  • In case we use Inverse() we must do inverse mapping even in code - i.e. set bi-directional relation on both sides.

    // this is wrong, we do not provide inverse relation
    var players = new List<Person>()
    {
        manager,
        new Person { FirstName = "Mark", PersonType = PersonType.Player },
        new Person { FirstName = "Karl", PersonType = PersonType.Player }
    };
    

    The above code does not provide Person with its Team;

    // this is wrong, we do not provide inverse relation
    
    var team = new Team { ... };
    var p1 = new Person { FirstName = "Mark", PersonType = PersonType.Player };
    var p2 = new Person { FirstName = "Karl", PersonType = PersonType.Player };
    // the inverse relation is a MUST
    p1.Team = team;
    p2.Team = team;
    // this will do the cascade
    team.Players.Add(p1);
    team.Players.Add(p2);
    
    // now only team could be saved, the rest will be done by cascades
    session.Save(team);