Mapping private attributes of domain entities with Dapper dot net

For primitive type properties with private setters, Dapper is smart enough to map them automatically, as long as their names match what you get from the database/queries.

Let' say your User class is the Aggregate Root

public class User : AggregateRoot
{
    public int Id { get; private set; }
    public string Name { get; private set; }

    ...
}

and you have GetById method from the repository to reconstruct the User

public class UserRepository : Repository<User>, IUserRepository
{
    private UserRepository(IDbConnection dbConnection) : base(dbConnection) {}

    ...

    public User GetById(int id)
    {
        const string sql = @"
            SELECT *
            FROM [User]
            WHERE ID = @userId;
        ";

        return base.DbConnection.QuerySingleOrDefault<User>(
            sql: sql,
            param: new { userId = id }
        );
    }
}

As long as the sql returns an Id and Name column, those will be automatically mapped to your User class matching properties, even when they have private setters. Nice and clean!

Problems with relationships

Everything becomes tricky when you have one-to-many objects you need to load up.

Let's say now the User class has a read-only car list that belongs to the User, and some methods you can use to add/remove cars:

public class User : AggregateRoot
{
    public int Id { get; private set; }
    public string Name { get; private set; }

    private readonly IList<Car> _cars = new List<Car>();
    public IEnumerable<Car> Cars => _cars;

    public void PurchaseCar(Car car)
    {
        _cars.Add(car);
 
        AddEvent(new CarPurchasedByUser { ... });
    }

    public void SellCar(Car car)
    {
        _cars.Remove(car);

        AddEvent(new CarSoldByUser { ... });
    }
}

public class Car : Entity
{
    public int Id { get; private set; }
    public string Brand { get; private set; }
}

Now how do you load up the car list when User class is constructed?

Some suggested to just run multiple queries and you construct the car list after you construct the user by calling the PurchaseCar and SellCar methods (or whatever methods available in the class) to add/remove cars:

public User GetById(int id)
{
    const string sql = @"
        SELECT *
        FROM [User]
        WHERE ID = @userId;

        SELECT *
        FROM [Car]
        WHERE UserID = @userId;
    ";

    using (var multi = base.DbConnection.QueryMultiple(sql, new { userId = id })
    {
        var user = multi.Read<User>()
            .FirstOrDefault();

        if (user != null)
        {
            var cars = multi.Read<Car>();

            foreach (var car in cars)
            {
                user.PurchaseCar(car);
            }
        }

        return user;
    }
}

But you really can't do so if you're practicing Domain-Driven Design as those methods usually would have additional events they will fire that might be subscribed by others to fire up other commands. You were just trying to initialize your User object.

Solve it with reflection

The only thing that worked for me is to use System.Reflection!

public User GetById(int id)
{
    const string sql = @"
        SELECT *
        FROM [User]
        WHERE ID = @userId;

        SELECT *
        FROM [Car]
        WHERE UserID = @userId;
    ";

    using (var multi = base.DbConnection.QueryMultiple(sql, new { userId = id })
    {
        var user = multi.Read<User>()
            .FirstOrDefault();

        if (user != null)
        {
            var cars = multi.Read<Car>();

            // Load user car list using Reflection
            var privateCarListField = user.GetType()
                .GetField("_cars", BindingFlags.NonPublic | BindingFlags.Instance);

            privateCarListField.SetValue(car, cars);
        }

        return user;
    }
}

One option is to create a separate set of persistence classes to work with Dapper; for example: UserRecord and CarRecord. The record classes will match db table and will be encapsulate within persistence module. Dapper queries will run against this classes and then you can have a separate persistence factory which will assemble the domain entities and return them back to the client.

Small example:

        var carRecord = DbConnection.Query<CarRecord>("select * from cars where id = @id", new {id = 1});
        var carEntity = CarFactory.Build(carRecord);

This creates a nice separation and provides flexibility.