Mapping private attributes of domain entities with Dapper dot net
Asked Answered
M

2

12

In most of my projects I use nHibernate + Fluent mapping and recently I've started to play around with Dapper to see if I can move read operations to it.

I follow DDD approach so my domain entities do not have any public setters. For example:

public class User
{
    private int _id;
    private string _name;
    private IList<Car> _carList;

    protected User(){} // Fluent Mapping

    public User(string id, string name)
    {
        // validation 
        // ...
        _id = id;
        _name = name;
    }

    public int Id{ get {return _id;} }
    public string Name { get {return _name;} }
    public IList<Car> CarList { get {return _carList;}}         
}

public class Car
{
    private int _id;
    private string _brand;

    protected Car(){} // Fluent Mapping

    public Car(string id, string brand)
    {
        // validation 
        // ...
        _id = id;
        _brand= brand;
    }

    public int Id{ get {return _id;} }
    public string Brand { get {return _brand;} }
}

With Fluent nHibernate I'm able to reveal members for mapping:

Id(Reveal.Member<User>("_id")).Column("id");
Map(Reveal.Member<User>("_name")).Column("name");

Is there a way to map my domain entities with Dapper? If so, how?

Mantic answered 8/3, 2013 at 17:33 Comment(1)
Note that the IList<Car> exposed by the User class, is a DDD smell just like a setter: you should expose an IEnumerable<Car> instead, since all operations on an aggregate's state should be handled via commands sent to it.Pelt
I
18

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.

Illimitable answered 8/3, 2013 at 22:8 Comment(6)
+1 I can confirm that this solution works very well for custom repositories.Pelt
I thought about this solution but since this makes me write a lot more code I was wondering if there is any alternative. Thanks for your comments.Mantic
@R2D2: I wonder, how did you implement? I know this quite a old post but am stuck in a similar situation and ended up on this post while searching.Zygo
@CoderAbsolute - by creating factories as in the answer given. However, it generates a lot of extra work. Currently I 'm doing more and more of CQRS where I use nHibernate for entities without public setters and Dapper.NET for DTOs in quering.Mantic
It's a good practice to have a separate set of persistence classes or models to represent your data storage, i.e., the entities from Entity Framework. But I would say those models are not, and shouldn't be your domain models (since OP tags DDD). Your domain model should reflect the terms your business uses and what your business is doing. That could be the same, or not the same as how you persist them. More over, I would say you should have strict access control on your domain models, i.e., having private setters, rather than having public, like what your persistence models haveKamin
... because you want others to call the actions inside your domain models rather than freely set the property values.Kamin
K
14

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(user, cars);
        }

        return user;
    }
}
Kamin answered 11/4, 2019 at 21:13 Comment(1)
This is the approach I took as I also don't like the idea of having to create a whole bunch of "record" classes which is what the accepted answer does - IMO this should be the accepted answer to this question.Horme

© 2022 - 2024 — McMap. All rights reserved.