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?
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;
}
Related
I'm attempting to use a CTE with Dapper and multi-mapping to get paged results. I'm hitting an inconvenience with duplicate columns; the CTE is preventing me from having to Name columns for example.
I would like to map the following query onto the following objects, not the mismatch between the column names and properties.
Query:
WITH TempSites AS(
SELECT
[S].[SiteID],
[S].[Name] AS [SiteName],
[S].[Description],
[L].[LocationID],
[L].[Name] AS [LocationName],
[L].[Description] AS [LocationDescription],
[L].[SiteID] AS [LocationSiteID],
[L].[ReportingID]
FROM (
SELECT * FROM [dbo].[Sites] [1_S]
WHERE [1_S].[StatusID] = 0
ORDER BY [1_S].[Name]
OFFSET 10 * (1 - 1) ROWS
FETCH NEXT 10 ROWS ONLY
) S
LEFT JOIN [dbo].[Locations] [L] ON [S].[SiteID] = [L].[SiteID]
),
MaxItems AS (SELECT COUNT(SiteID) AS MaxItems FROM Sites)
SELECT *
FROM TempSites, MaxItems
Objects:
public class Site
{
public int SiteID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<Location> Locations { get; internal set; }
}
public class Location
{
public int LocationID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Guid ReportingID { get; set; }
public int SiteID { get; set; }
}
For some reason I have it in my head that a naming convention exists which will handle this scenario for me but I can't find mention of it in the docs.
There are more than one issues, let cover them one by one.
CTE duplicate column names:
CTE does not allow duplicate column names, so you have to resolve them using aliases, preferably using some naming convention like in your query attempt.
For some reason I have it in my head that a naming convention exists which will handle this scenario for me but I can't find mention of it in the docs.
You probably had in mind setting the DefaultTypeMap.MatchNamesWithUnderscores property to true, but as code documentation of the property states:
Should column names like User_Id be allowed to match properties/fields like UserId?
apparently this is not the solution. But the issue can easily be solved by introducing a custom naming convention, for instance "{prefix}{propertyName}" (where by default prefix is "{className}_") and implementing it via Dapper's CustomPropertyTypeMap. Here is a helper method which does that:
public static class CustomNameMap
{
public static void SetFor<T>(string prefix = null)
{
if (prefix == null) prefix = typeof(T).Name + "_";
var typeMap = new CustomPropertyTypeMap(typeof(T), (type, name) =>
{
if (name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
name = name.Substring(prefix.Length);
return type.GetProperty(name);
});
SqlMapper.SetTypeMap(typeof(T), typeMap);
}
}
Now all you need is to call it (one time):
CustomNameMap.SetFor<Location>();
apply the naming convention to your query:
WITH TempSites AS(
SELECT
[S].[SiteID],
[S].[Name],
[S].[Description],
[L].[LocationID],
[L].[Name] AS [Location_Name],
[L].[Description] AS [Location_Description],
[L].[SiteID] AS [Location_SiteID],
[L].[ReportingID]
FROM (
SELECT * FROM [dbo].[Sites] [1_S]
WHERE [1_S].[StatusID] = 0
ORDER BY [1_S].[Name]
OFFSET 10 * (1 - 1) ROWS
FETCH NEXT 10 ROWS ONLY
) S
LEFT JOIN [dbo].[Locations] [L] ON [S].[SiteID] = [L].[SiteID]
),
MaxItems AS (SELECT COUNT(SiteID) AS MaxItems FROM Sites)
SELECT *
FROM TempSites, MaxItems
and you are done with that part. Of course you can use shorter prefix like "Loc_" if you like.
Mapping the query result to the provided classes:
In this particular case you need to use the Query method overload that allows you to pass Func<TFirst, TSecond, TReturn> map delegate and unitilize the splitOn parameter to specify LocationID as a split column. However that's not enough. Dapper's Multi Mapping feature allows you to split a single row to a several single objects (like LINQ Join) while you need a Site with Location list (like LINQ GroupJoin).
It can be achieved by using the Query method to project into a temporary anonymous type and then use regular LINQ to produce the desired output like this:
var sites = cn.Query(sql, (Site site, Location loc) => new { site, loc }, splitOn: "LocationID")
.GroupBy(e => e.site.SiteID)
.Select(g =>
{
var site = g.First().site;
site.Locations = g.Select(e => e.loc).Where(loc => loc != null).ToList();
return site;
})
.ToList();
where cn is opened SqlConnection and sql is a string holding the above query.
You can map a column name with another attribute using the ColumnAttributeTypeMapper.
See my first comment on the Gist for further details.
You can do the mapping like
public class Site
{
public int SiteID { get; set; }
[Column("SiteName")]
public string Name { get; set; }
public string Description { get; set; }
public List<Location> Locations { get; internal set; }
}
public class Location
{
public int LocationID { get; set; }
[Column("LocationName")]
public string Name { get; set; }
[Column("LocationDescription")]
public string Description { get; set; }
public Guid ReportingID { get; set; }
[Column("LocationSiteID")]
public int SiteID { get; set; }
}
Mapping can be done using either of the following 3 methods
Method 1
Manually set the custom TypeMapper for your Model once as:
Dapper.SqlMapper.SetTypeMap(typeof(Site), new ColumnAttributeTypeMapper<Site>());
Dapper.SqlMapper.SetTypeMap(typeof(Location), new ColumnAttributeTypeMapper<Location>());
Method 2
For class libraries of .NET Framework >= v4.0, you can use PreApplicationStartMethod to register your classes for custom type mapping.
using System.Web;
using Dapper;
[assembly: PreApplicationStartMethod(typeof(YourNamespace.Initiator), "RegisterModels")]
namespace YourNamespace
{
public class Initiator
{
private static void RegisterModels()
{
SqlMapper.SetTypeMap(typeof(Site), new ColumnAttributeTypeMapper<Site>());
SqlMapper.SetTypeMap(typeof(Location), new ColumnAttributeTypeMapper<Location>());
// ...
}
}
}
Method 3
Or you can find the classes to which ColumnAttribute is applied through reflection and set type mappings. This could be a little slower, but it does all the mappings in your assembly automatically for you. Just call RegisterTypeMaps() once your assembly is loaded.
public static void RegisterTypeMaps()
{
var mappedTypes = Assembly.GetAssembly(typeof (Initiator)).GetTypes().Where(
f =>
f.GetProperties().Any(
p =>
p.GetCustomAttributes(false).Any(
a => a.GetType().Name == ColumnAttributeTypeMapper<dynamic>.ColumnAttributeName)));
var mapper = typeof(ColumnAttributeTypeMapper<>);
foreach (var mappedType in mappedTypes)
{
var genericType = mapper.MakeGenericType(new[] { mappedType });
SqlMapper.SetTypeMap(mappedType, Activator.CreateInstance(genericType) as SqlMapper.ITypeMap);
}
}
The below code should work fine for you to load a list of sites with associated locations
var conString="your database connection string here";
using (var conn = new SqlConnection(conString))
{
conn.Open();
string qry = "SELECT S.SiteId, S.Name, S.Description, L.LocationId, L.Name,L.Description,
L.ReportingId
from Site S INNER JOIN
Location L ON S.SiteId=L.SiteId";
var sites = conn.Query<Site, Location, Site>
(qry, (site, loc) => { site.Locations = loc; return site; });
var siteCount = sites.Count();
foreach (Site site in sites)
{
//do something
}
conn.Close();
}
Need a help with RavenDB.
In my web page I want to have such list:
item1 category1
item2 category2
...
and another one:
category1, number of items
category2, number of items
...
My data structures:
public class Item
{
public string Id { get; set; }
public string Name { get; set; }
public string CategoryId { get; set; }
}
public class Category
{
public string Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
}
Index for the first list:
public class Item_WithCategory : AbstractIndexCreationTask<Item>
{
public class Result
{
public string Name { get; set; }
public string CategoryName { get; set; }
}
public Item_WithCategory()
{
Map = items => from item in items
select new
{
Name = item.Name,
CategoryName = LoadDocument<Category>(item.CategoryId).Name
};
}
}
Is this data structure suitable for my case, or will it be better to have Category instead of CategoryId in item structure?
Should I use my index or is there a better solution to take category name?
If my index is good, how to write a correct query? My current try:
Item_WithCategory.Result[] all;
using (var session = DocumentStoreHolder.Store.OpenSession())
{
all = session.Query<Item_WithCategory.Result, Item_WithCategory>().ToArray();
}
but it throws exception stating that return type is item, not result. How to fix it?
You have a couple of options here. You could store both the CategoryId and the CategoryName on the Item entity. This will of course lead to duplicated data (if you still need to store the Category entity), but "storage is cheap" is a popular term these days.The downside of this is that you need to update each Item document of a given category if the category name changes to keep things consistent. A benefit is that you need to do less work to get your desired result.
If you store Category Name on the item as well you don't need a special index to handle the first list, just query on the Items and return what you need. For the second list you need to create a Map/Reduce index on the Item entity that groups on the category.
However, if you need to use the data structure you've given, there are a couple of ways of solving this. First, it's really not recommended to use a LoadDocument inside of an index definition, especially not in a select statement. This might affect indexing performance in a negative way.
Instead, just index the properties you need to query on (or use an auto index) and then use a Result Transformer to fetch information from related documents:
public class ItemCategoryTransformer : AbstractTransformerCreationTask<Item>
{
public ItemCategoryTransformer()
{
TransformResults = results => from item in results
let category = LoadDocument<Category>(item.CategoryId)
select new ItemCategoryViewModel
{
Name = item.Name,
CategoryName = category.Name
};
}
}
public class ItemCategoryViewModel
{
public string Name { get; set; }
public string CategoryName { get; set; }
}
You can use this Transformer with a Query on the Item entity:
using (var session = documentStore.OpenSession())
{
var items = session.Query<Item>()
.TransformWith<ItemCategoryTransformer, ItemCategoryViewModel>()
.ToList();
}
As for the second list, still using your data structure, you have to use a couple of things. First, a Map/Reduce index over the Items, grouped by CategoryId:
public class Category_Items_Count : AbstractIndexCreationTask<Item, Category_Items_Count.Result>
{
public class Result
{
public string CategoryId { get; set; }
public int Count { get; set; }
}
public Category_Items_Count()
{
Map = items => from item in items
select new Result
{
CategoryId = item.CategoryId,
Count = 1
};
Reduce = results => from result in results
group result by result.CategoryId
into c
select new Result
{
CategoryId = c.Key,
Count = c.Sum(x => x.Count)
};
}
}
But as you only have the CategoryId on the Item entity, you have to use a similar transformer as in the first list:
public class CategoryItemsCountTransformer : AbstractTransformerCreationTask<Category_Items_Count.Result>
{
public CategoryItemsCountTransformer()
{
TransformResults = results => from result in results
let category = LoadDocument<Category>(result.CategoryId)
select new CategoryItemsCountViewModel
{
CategoryName = category.Name,
NumberOfItems = result.Count
};
}
}
public class CategoryItemsCountViewModel
{
public string CategoryName { get; set; }
public int NumberOfItems { get; set; }
}
And lastly, query for it like this:
using (var session = documentStore.OpenSession())
{
var items = session.Query<Category_Items_Count.Result, Category_Items_Count>()
.TransformWith<CategoryItemsCountTransformer, CategoryItemsCountViewModel>()
.ToList();
}
As you can see, there are quite a difference in work needed depending on what data structure you're using. If you stored the Category Name on the Item entity directly you wouldn't need any Result Transformers to achieve the results you're after (you would only need a Map/Reduce index).
However, Result Transformers are executed server side and are only executed on request, instead of using LoadDocument inside of an index which is executed every time indexing occurs. Also, and maybe why LoadDocuments inside of index definitions isn't recommended, every change to a document that's referenced with a LoadDocument in an index will cause that index to have to be rewritten. This might lead to a lot of work for the index engine.
Lastly, to answer your last question about why you get an exception when querying: As the actual return type of your index is the document that that's being indexed (in this case Item). To use something else you need to project your result to something else. This can be done by using ".As()" in the query:
Item_WithCategory.Result[] all;
using (var session = DocumentStoreHolder.Store.OpenSession())
{
all = session.Query<Item_WithCategory.Result, Item_WithCategory>()
.As<Item_WithCategory.Result>()
.ToArray();
}
Long post, but hope it helps!
Is it possible to select a single object and populate a containing IEnumerable property with a single Lambda expression?
Something like this:
var someViewModel = _repository.Table.Where(x => x.Id == someId)
.Select(new ListViewModel(){
GroupId = x.Group.Id,
GroupTitle = x.Group.Title
List = ?? // Select new SubViewModel and add it to IEnumerable<SubViewModel>
})
The result I'm after is a new object (ListViewModel in this case) that contains 3 properties. "List" being a collection of newly selected objects.
Is this possible? Am I coming at this from the wrong angle?
Thanks!
Update:
Let me try again :) Keep in mind that my naming is fictional here. Given the following two classes I would like to construct a DB query using a Lambda expression which creates a single "ListViewModel" that contains a collection of "SubViewModel". Does this help clarify?
public class SubViewModel
{
public int Id { get; set; }
public string Title { get; set; }
}
public class ListViewModel
{
public int GroupId { get; set; }
public string GroupTitle { get; set; }
public IEnumerable<SubViewModel> List { get; set; }
}
I'm not sure if I understand your question correctly but here is what I am thinking, you need to create a new IEnumberable and add the item to that collection.
var someViewModel = _repository.Table.Where(x => x.Id == someId)
.Select(new ListViewModel()
{
GroupId = x.Group.Id,
GroupTitle = x.Group.Title
List = new List<SubViewModel> { new SubViewModel(x) }
});
I have the following entity collections in RavenDB:
public class EntityA
{
public string Id { get; set; }
public string Name { get; set; }
public string[] Tags { get; set; }
}
public class EntityB
{
public string Id { get; set; }
public string Name { get; set; }
public string[] Tags { get; set; }
}
The only thing shared is the Tags collection: a tag of EntityA may exist in EntityB, so that they may intersect.
How can I retrieve every EntityA that has intersecting tags with EntityB where the Name property of EntityB is equal to a given value?
Well, this is a difficult one. To do it right, you would need two levels of reducing - one by the tag which would expand out your results, and another by the id to collapse it back. Raven doesn't have an easy way to do this.
You can fake it out though using a Transform. The only problem is that you will have skipped items in your result set, so make sure you know how to deal with those.
public class TestIndex : AbstractMultiMapIndexCreationTask<TestIndex.Result>
{
public class Result
{
public string[] Ids { get; set; }
public string Name { get; set; }
public string Tag { get; set; }
}
public TestIndex()
{
AddMap<EntityA>(entities => from a in entities
from tag in a.Tags.DefaultIfEmpty("_")
select new
{
Ids = new[] { a.Id },
Name = (string) null,
Tag = tag
});
AddMap<EntityB>(entities => from b in entities
from tag in b.Tags
select new
{
Ids = new string[0],
b.Name,
Tag = tag
});
Reduce = results => from result in results
group result by result.Tag
into g
select new
{
Ids = g.SelectMany(x => x.Ids),
g.First(x => x.Name != null).Name,
Tag = g.Key
};
TransformResults = (database, results) =>
results.SelectMany(x => x.Ids)
.Distinct()
.Select(x => database.Load<EntityA>(x));
}
}
See also the full unit test here.
There is another approach, but I haven't tested it yet. That would be to use the Indexed Properties Bundle to do the first pass, and then map those results for the second pass. I am experimenting with this in general, and if it works, I will update this answer with the results.
I want to execute a single Query (or Stored Proc with multiple resultsets). I know how to do Multi-mapping using Dapper, but I can't sort how to map the two collections onto the same parent. Basically, given this Object definition...
class ParentObject
{
string Name { get; set; }
ICollection<ChildObjectOne> ChildSetOne {get;set;}
ICollection<ChildObjectTwo> ChildSetTwo { get; set; }
}
class ChildObjectOne
{
string Name { get; set; }
}
class ChildObjectTwo
{
int id { get; set; }
string LocationName { get; set; }
}
I want to be able to run a Dapper query that somehow yields:
IQueryable<ParentObject> result = cnn.Query(
// Some really awesome dapper syntax goes here
);
Not sure if you DON'T want to use MultiMapping but here's how it would work for your case. As far as I know and read on SO, is not not possible to map a deep nested object graph with a simple Query.
static void Main(string[] args)
{
var sqlParent = "SELECT parentId as Id FROM ParentTable WHERE parentId=1;";
var sqlChildOneSet = "SELECT Name FROM ChildOneTable;"; // Add an appropriate WHERE
var sqlChildTwoSet = "SELECT Id, LocationName FROM ChildTwoTable;"; // Add an appropriate WHERE
var conn = GetConnection() // whatever you're getting connections with
using (conn)
{
conn.Open();
using (var multi = conn.QueryMultiple(sqlParent + sqlChildOneSet + sqlChildTwoSet))
{
var parent = multi.Read<ParentObject>().First();
parent.ChildSetOne = multi.Read<ChildOne>().ToList();
parent.ChildSetTwo = multi.Read<ChildTwo>().ToList();
}
}
}
Similar questions for nested objects and dapper :
https://stackoverflow.com/search?q=nested+objects+%2B+dapper
It is possible to materialize an object with one-to-many relationships using the IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map); method in this case. However you need to make a few changes to the entities in order to have enough information to do so.
Here are a few SO threads with similar questions.
How do I map lists of nested objects with Dapper
Extension function to make it cleaner
Dapper.Net by example - Mapping Relationships
public class ParentObject
{
public ParentObject()
{
ChildSetOne = new List<ChildObjectOne>();
ChildSetTwo = new List<ChildObjectTwo>();
}
// 1) Although its possible to do this without this Id property, For sanity it is advisable.
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ChildObjectOne> ChildSetOne {get; private set;}
public ICollection<ChildObjectTwo> ChildSetTwo { get; private set; }
}
public class ChildObjectOne
{
// 2a) Need a ParentId
public int ParentId { get; set; }
public string Name { get; set; }
}
public class ChildObjectTwo
{
// 2b) This ParentId is not required but again for sanity it is advisable to include it.
public int ParentId { get; set; }
public int id { get; set; }
public string LocationName { get; set; }
}
public class Repository
{
public IEnumerable<ParentObject> Get()
{
string sql =
#"SELECT
p.Id,
p.Name,
o.Name,
o.ParentId,
t.Id,
t.LocationName,
t.ParentId
FROM
Parent p
LEFT JOIN ChildOne o on o.ParentId = p.Id
LEFT JOIN ChildTwo t on t.ParentId = p.Id
WHERE
p.Name LIKE '%Something%'";
var lookup = new Dictionary<int, ParentObject>();
using (var connection = CreateConnection())
{
connection.Query<ParentObject, ChildObjectOne, ChildObjectTwo, ParentObject>(
sql, (parent, childOne, childTwo) =>
{
ParentObject activeParent;
if (!lookup.TryGetValue(childOne.ParentId, out activeParent))
{
activeParent = parent;
lookup.add(activeParent.Id, activeParent);
}
//TODO: if you need to check for duplicates or null do so here
activeParent.ChildSetOne.Add(childOne);
//TODO: if you need to check for duplicates or null do so here
activeParent.ChildSetTwo.Add(childTwo);
});
}
return lookup.Values;
}
}