Dapper One-to-Many Relationship

Dapper One-to-Many Relationship

In a relational database, two tables can be related through primary and foreign key constraints. A very common relationship is One-to-Many, where each record of one table is related to zero or more records of another table. In Dapper, we execute a SQL JOIN query with the Dapper Query Method to retrieve records from two tables that are in a One-to-Many relationship.

Let’s now understand records retrieval process in Dapper based on tables that are in One-to-Many relationship.

Dapper One-to-Many Relationship

We have 2 tables which are in One-to-Many relationship, these are:

  1. Employee
  2. Address

Each employee in the Employee table can have zero or many addresses in the Address table. Similarly, many addresses can belong to a particular employee.

The Employee table is:
CREATE TABLE [dbo].[Employee] (
    [Id]     INT          IDENTITY (1, 1) NOT NULL,
    [Name]   VARCHAR (50) NOT NULL,
    [Email]  VARCHAR (50) NOT NULL,
    [Salary] MONEY        NOT NULL,
    PRIMARY KEY CLUSTERED ([Id] ASC)
);
The Address table is:
CREATE TABLE [dbo].[Address] (
    [Id]         INT          IDENTITY (1, 1) NOT NULL,
    [EmployeeId] INT          NOT NULL,
    [HouseNo]    INT          NOT NULL,
    [Street]     VARCHAR (50) NOT NULL,
    [City]       VARCHAR (50) NOT NULL,
    CONSTRAINT [PK_Address] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_Address_Employee] FOREIGN KEY ([EmployeeId]) REFERENCES [dbo].[Employee] ([Id])
);

The One-to-Many relationship between the 2 tables is shown below:

Dapper-One-to-Many Relationship

Dapper Query method for One-to-Many Relationship

Through Dapper Query method we can read records from One-to-Many relationship based tables. We simply apply Inner Join sql query and then map the result to strongly typed objects.

Employee.cs and Address.cs

Create 2 classes Employee.cs and Address.cs. To these classes the result set of the Inner Join query will be mapped.

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public int Salary { get; set; }
    public List<Address> Address { get; set; }
}

public class Address
{
    public int Id { get; set; }
    public int EmployeeId { get; set; }
    public int HouseNo { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
}

Note that the Employee class has a List<Address> type property which will be filled with the multiple addresses an employee is having. Next, see the code of the Query method which executes an INNER JOIN query to fetch Employee and his/her related Address records.

//connection string
string myCS = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=DapperStart;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

string sql = "SELECT * FROM Employee AS A INNER JOIN Address AS B ON A.Id = B.EmployeeID;";

using (var connection = new SqlConnection(myCS))
{
    var dictionary = new Dictionary<int, Employee>();


    var list = connection.Query<Employee, Address, Employee>(
    sql,
    (employee, address) =>
    {
        Employee e;

        if (!dictionary.TryGetValue(employee.Id, out e))
        {
            e = employee;
            e.Address = new List<Address>();
            dictionary.Add(e.Id, e);
        }

        e.Address.Add(address);
        return e;
    },
    splitOn: "Id")
    .Distinct()
    .ToList();
}

Explanation: In this example, the input parameters of query method – Query() are Employee and Address. The return type is Employee. So Query method will take an Employee and a Address, process them, and return an Employee.

Next, see the Func delegate where the processing of the resultset is done:

(employee, address) =>
{
    Employee e;

    if (!dictionary.TryGetValue(employee.Id, out e))
    {
        e = employee;
        e.Address = new List<Address>();
        dictionary.Add(e.Id, e);
    }

    e.Address.Add(address);
    return e;
}

We used a dictionary for going through each record of the result set. Then we are adding the list of Address to the “Address” property of the Employee. In the end we are returning the Employee object.

Related to this tutorial- Dapper One-to-One Relationship

The splitOn argument tells to split the record set on the Id column which is the first column of the Address table.

Let’s now check the working of this code. We place a breakpoint and checked the value of the list variable. The first Employee record’s Address Property is filled with it’s two related Address records. See the below image.

Dapper One-to-Many

This means the code is working properly.

Download source codes:

Download

Conclusion

In this tutorial, we learned how to retrieve records from two tables that are in a One-to-Many relationship using the Dapper Query method. We executed a SQL JOIN query and mapped the result set to the Employee and Address classes. A dictionary made sure each employee was created only once, while all of the employee’s related addresses were added to its Addresses list. We also used the splitOn argument to tell Dapper where the Address columns begin in each row.

Remember that an INNER JOIN returns only the employees who have at least one address. To include employees with no addresses, use a LEFT JOIN and check the address for null inside the Func delegate.

Best Practices

Keep the following points in mind when working with One-to-Many relationships in Dapper:

  • Use a dictionary to avoid duplicates. A JOIN query returns one row per child record, so the parent repeats on every row. Track parents in a Dictionary so each one is created only once, then return dictionary.Values.
  • Choose the right join. An INNER JOIN skips parents that have no child records. Use a LEFT JOIN if you also want those parents, and check the child object for null inside the Func delegate.
  • Select columns explicitly. Avoid SELECT *. Listing the columns keeps the column order predictable, which is important because splitOn depends on it.
  • Set splitOn correctly. The split column must be the first column of the child table’s columns in the result set. If the column names differ, pass a comma-separated list such as splitOn: “Id,Id” for multiple types.
  • Initialize the child list. Give the collection property a default value, such as new List<Address>(), so it is never null.
  • Match data types. Use decimal in C# for SQL MONEY columns, not int, so no values are lost.
  • Use parameters, never string concatenation. Pass values through an anonymous object to prevent SQL injection. For example: new { Id = 1 }.
  • Dispose connections. Wrap the connection in a using block so it is closed and released properly.
  • Consider QueryMultiple for large child sets. When the parent has many columns, a JOIN repeats the parent data on every row. Running two separate SELECT statements with QueryMultiple and joining them in memory avoids this overhead.

Frequently Asked Questions

What is a One-to-Many relationship?

It is a relationship in which one record of a table is linked to zero or more records of another table. For example, one employee can have many addresses. It is implemented with a foreign key in the child table that references the primary key of the parent table.

How does Dapper map a One-to-Many relationship?

Dapper uses the Query method with a multi-mapping Func delegate. You run a JOIN query, and Dapper maps each row to the parent and child types. In the delegate, you add the child to the parent’s collection.

Why do we need a dictionary in the code?

The JOIN query returns the same parent on several rows, one for each child. Without a dictionary, you would end up with duplicate parent objects, each holding a single child. The dictionary makes sure only one parent object exists per Id and collects all its children.

What does the splitOn argument do?

It tells Dapper where one object’s columns end and the next object’s columns begin in each row. By default, Dapper splits on a column named Id. If your child table’s first column has a different name, specify it with splitOn.

Why are some employees missing from my results?

You are probably using an INNER JOIN, which returns only the employees who have at least one address. Switch to a LEFT JOIN to include employees without addresses, and handle the null Address in your code.

Can I use Dapper to fetch more than two related tables?

Yes. Dapper supports multi-mapping with up to seven types in a single Query call. Add each type as a generic argument and list the split columns in splitOn, separated by commas.

When should I use QueryMultiple instead of a JOIN?

QueryMultiple executes several SELECT statements in one round trip and returns each result set separately. It is useful when the parent table has many columns or the child table has many rows, because the parent data is not repeated for every child row.

Is the Dapper approach faster than Entity Framework Core?

Dapper is generally faster because it is a lightweight micro-ORM with very little overhead. The trade-off is that you write the SQL and the mapping logic yourself, while EF Core generates them for you.

SHARE THIS ARTICLE

  • linkedin
  • reddit
yogihosting

ABOUT THE AUTHOR

I hope you enjoyed reading this tutorial. If it helped you then consider buying a cup of coffee for me. This will help me in writing more such good tutorials for the readers. Thank you. Buy Me A Coffee donate

Leave a Reply

Your email address will not be published. Required fields are marked *