After doing the blogposts about One to many and many to one it is now time to do one one about many to many with Simple.Data.

Many to many

It’s time to change our design. It is clear that our data is not normalized like it should. I need an intermediate table between Person and BadHabit.

Here is the code to do that.

Private Sub CreateTables()
        Using cn = New SqlCeConnection(ConnectionString)
            If cn.State = ConnectionState.Closed Then
            End If
            For Each SqlScript In CreateTableScripts()
                Using cmd = New SqlCeCommand(SqlScript, cn)
                End Using
        End Using
    End Sub

    Private Function CreateTableScripts() As IList(Of String)
        Dim s As New List(Of String)
        s.Add("CREATE TABLE Address (Id int IDENTITY(1,1) PRIMARY KEY, Street nvarchar (40) NOT NULL, HouseNumber nvarchar (10))")
        s.Add("CREATE TABLE Person (Id int IDENTITY(1,1) PRIMARY KEY, LastName nvarchar (40) NOT NULL, FirstName nvarchar (40), AddressId int NOT NULL)")
        s.Add("ALTER TABLE Person ADD CONSTRAINT FK_Person_Address FOREIGN KEY (AddressId) REFERENCES Address(Id)")
        s.Add("CREATE TABLE BadHabit (Id int IDENTITY(1,1) PRIMARY KEY, BadHabit nvarchar (40) NOT NULL)")
        s.Add("CREATE TABLE BadHabitPerson (BadHabitId int NOT NULL, PersonId int NOT NULL)")
        s.Add("ALTER TABLE BadHabitPerson ADD PRIMARY KEY(BadHabitId, PersonId)")
        s.Add("ALTER TABLE BadHabitPerson ADD CONSTRAINT FK_BadHabitPerson_Person FOREIGN KEY (PersonId) REFERENCES Person(Id)")
        s.Add("ALTER TABLE BadHabitPerson ADD CONSTRAINT FK_BadHabitPerson_BadHabit FOREIGN KEY (BadHabitId) REFERENCES BadHabit(Id)")
        Return s
    End Function```
And we have to add some data to make this work.

Dim db = Simple.Data.Database.OpenConnection(ConnectionString)
        db.Address.Insert(New With {.Street = "street1", .HouseNumber = "1"})
        db.Person.Insert(New With {.LastName = "lastname1", .FirstName = "firstname1", .AddressId = 1})
        db.Person.Insert(New With {.LastName = "lastname1", .FirstName = "firstname2", .AddressId = 1})
        db.BadHabit.Insert(New With {.BadHabit = "Drinks"})
        db.BadHabit.Insert(New With {.BadHabit = "Smokes"})
        db.BadHabit.Insert(New With {.BadHabit = "Eats to much"})
        db.BadHabitPerson.Insert(New With {.BadHabitId = 1, .PersonId = 1})
        db.BadHabitPerson.Insert(New With {.BadHabitId = 2, .PersonId = 1})
        db.BadHabitPerson.Insert(New With {.BadHabitId = 3, .PersonId = 2})

You will now notice that our previous query no longer works.

this line

vbnet person.BadHabit Will now give you.

Public member ‘BadHabit’ on type ‘SimpleRecord’ not found.

This can easily be fixed by adding the intermediarie table in the mix.

vbnet Dim result = db.Persons.FindAllByLastName("lastname1").WithAddress For Each person In result Console.WriteLine("{0} {1} {2} {3} {4}", person.Id, person.LastName, person.FirstName, person.Address.Street, person.Address.HouseNumber) For Each BadHabit In person.BadHabitPerson.BadHabit Console.WriteLine(BadHabit.BadHabit) Next Next See how I added BadHabitPerson in between Person and BadHabit? That makes it work.

The sql is now.

```tsql Simple.Data.Ado: Text select [Person].[Id],[Person].[LastName],[Person].[FirstName],[Person].[AddressId],[Address].[Id] AS [with1AddressId],[Address].[Street] AS [with1AddressStreet],[Address].[HouseNumber] AS [with1Address__HouseNumber] from [Person] LEFT JOIN [Address] ON ([Address].[Id] = [Person].[AddressId]) WHERE [Person].[LastName] = @p1 @p1 (String) = lastname1

Simple.Data.Ado: Text select [BadHabit].[Id],[BadHabit].[BadHabit] from [BadHabit] JOIN [BadHabitPerson] ON ([BadHabit].[Id] = [BadHabitPerson].[BadHabitId]) WHERE [BadHabitPerson].[PersonId] = @p1 @p1 (Int32) = 1

Simple.Data.Ado: Text select [BadHabit].[Id],[BadHabit].[BadHabit] from [BadHabit] JOIN [BadHabitPerson] ON ([BadHabit].[Id] = [BadHabitPerson].[BadHabitId]) WHERE [BadHabitPerson].[PersonId] = @p1 @p1 (Int32) = 2``` Which I would like to have in one statement again.

I could however not get it in one statement. But I’m sure Mark will fix it one day ;-).


A bit less intuitive but it’s still pretty simple once you know how.

I have the complete code in a gist here.