2

I have Places, each place can have many tags. Each tag can be assigned to many places.

public class Place {
    public int Id { get; set; }
    public string PlaceName { get; set; }

    public IEnumerable<Tag> Tags { get; set; }
}

public class Tag {
    public int Id { get; set; }
    public string TagName { get; set; }
}

public class TagPlace {
    public int Id { get; set; }
    public PlaceId { get; set; }
    public TagId { get; set; }
}

The database has equivalent tables with foreign keys as appropriate.

I want to get a collection of Places, and I want each Place to have an appropriate colleciton of Tags. I guess using Linq might be required.

I've found various articles on this, but they aren't quite the same / deal with a list of ints rather than two collections of objects.

eg https://social.msdn.microsoft.com/Forums/en-US/fda19d75-b2ac-4fb1-801b-4402d4bd5255/how-to-do-in-linq-quotselect-from-employee-where-id-in-101112quot?forum=linqprojectgeneral

LINQ Where in collection clause

What's the best way of doing this?

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
niico
  • 11,206
  • 23
  • 78
  • 161
  • Possible duplicate of [Dapper Many-to-Many Query](https://stackoverflow.com/questions/29610051/dapper-many-to-many-query) – Camilo Terevinto Jun 10 '18 at 18:21
  • Note that your `Place` class is wrong though, it has a `IEnumerable` when it should have a `IEnumerable` – Camilo Terevinto Jun 10 '18 at 18:22
  • 1
    @CamiloTerevinto Not so, Each place has an associated collection of tags. TagPlace - in the database - is just a table allowing the many to many relationship. When I access a Place I want to also be able to access the tags associated with it. – niico Jun 10 '18 at 18:24
  • 1
    @CamiloTerevinto That question doesn't have an accepted answer. It's also not really clear what they're asking. – niico Jun 10 '18 at 18:25
  • People don't always accept answers, the idea was to give you a hint on how you can do it. Yeah, you can model your classes how they are in the database or how they make sense in the real world; I tend to do the first and use ViewModels/DTOs for the second – Camilo Terevinto Jun 10 '18 at 18:26

1 Answers1

3

The classical approach with Dapper is to use a Dictionary to store the main objects while the query enumerates the records

public  IEnumerable<Place> SelectPlaces()
{
    string query = @"SELECT p.id, p.PlaceName, t.id, t.tagname
                     FROM Place p INNER JOIN TagPlace tp ON tp.PlaceId = p.Id
                     INNER JOIN Tag t ON tp.TagId = t.Id";
    var result = default(IEnumerable<Place>);
    Dictionary<int, Place> lookup = new Dictionary<int, Place>();
    using (IDbConnection connection = GetOpenedConnection())
    {
         // Each record is passed to the delegate where p is an instance of
         // Place and t is an instance of Tag, delegate should return the Place instance.
         result = connection.Query<Place, Tag, Place(query, (p, t) =>
         {
              // Check if we have already stored the Place in the dictionary
              if (!lookup.TryGetValue(p.Id, out Place placeFound))
              {
                   // The dictionary doesnt have that Place 
                   // Add it to the dictionary and 
                   // set the variable where we will add the Tag
                   lookup.Add(p.Id, p);
                   placeFound = p;
                   // Probably it is better to initialize the IEnumerable
                   // directly in the class 
                   placeFound.Tags = new List<Tag>();
              }

              // Add the tag to the current Place.
              placeFound.Tags.Add(t);
              return placeFound;

          }, splitOn: "id");
          // SplitOn is where we tell Dapper how to split the record returned
          // in the two instances required, but here SplitOn 
          // is not really needed because "Id" is the default.

     }
     return result;
}
Steve
  • 213,761
  • 22
  • 232
  • 286
  • Could also have a dictionary for the tags to avoid creating duplicate tag objects. That may or may not be desirable here. Also you don't want the result of the `Query` call as that will contain references to the same place objects multiple times. Instead you want the result to be the `Values` of the dictionary. – juharr Jun 10 '18 at 20:36
  • You mean to add the same Tag's reference to different Places? Well that could be an option but yes, not sure if this could be a requirement. It largely depends on the amount of data retrieved I suppose. – Steve Jun 10 '18 at 20:45
  • For the return value, no Dapper will return a distinct collection of Place. It checks the return value from the lambda and doesn't add the same Place multiple times to the Query result – Steve Jun 10 '18 at 20:49
  • It's been awhile since I used Dapper, and I did not know it would do that. – juharr Jun 11 '18 at 00:02