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();
}
Related
I've been using Dapper to access data from an older database with a specific structure and am having trouble with some issues. Many of them have some sort of answer but I can't seem to combine the solutions, here we go:
The object is to be created out of 3 different tables (inner join)
The objects are to be created in a nested object (a main object with lists of sub objects)
The required tables have combined primary keys (no separate unique keys)
The objects have identical property names (I'm not sure that this is an issue, haven't come far enough)
public class MainObject {
public long Id1 { get; set; }
public long Id2 { get; set; }
public string Id3 { get; set; }
public List<SubObject1> Subobject1 { get; set; }
public List<SubObject2> Subobject2 { get; set; }
public string OtherProps { get; set; }
}
public class SubObject1 {
public long Id1 { get; set; }
public long Id2 { get; set; }
public string Id3 { get; set; }
}
public class SubObject1 {
public long Id1 { get; set; }
public long Id2 { get; set; }
public string Id3 { get; set; }
}
I've been trying to combine issues 1 and 2 as described in this StackOverflow answer. After that I've been trying to add issue 3 as described in this StackOverflow answer, but haven't been able to make it work. An error I get frequently is System.ArgumentException: When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id, so I'm not sure I even understand the entire concept.
My query (which is able to return multiple rows) is structured as:
SELECT MainObject.*, SubObject1.*, SubObject2.*
FROM MainObject
INNER JOIN SubObject1 ON MainObject.Id1 = SubObject1.Id1
AND MainObject.Id2 = SubObject1.Id2 AND MainObject.Id3 = SubObject1.Id3
INNER JOIN SubObject2 ON MainObject.Id1 = SubObject2.Id1
AND MainObject.Id2 = SubObject2.Id2 AND MainObject.Id3 = SubObject2.Id3
WHERE MainObject.OtherProps = 'SomeValue'
Preferable the output would be of type List<MainObject>.
I'm open to all remarks and hints
E: The reason we chose Dapper is because we're reluctant to use Entity Framework and our current mapping has performance issues. At the moment we query for List, which loops and queries for every SubObject1 and SubObject2 separately (thus executing a lot of queries).
Good morning mate
Look at your simple query I suggest that you use the Linq, it would be easier and have the resource you want ...
The dapper is used to query more complex and heavy ones where you select the fields eg: "SELEC a.id as a_id, B.id as b_id" and then manually map.
I suggest in this case to use Linq.
Linq example:
var blogs1 = context.Blogs
.Include(b => b.Posts.Select(p => p.Comments))
.Include(b => b.Users)
.Include(b => b.Users.City)
.ToList();
After trying to combine the different solutions for a while I stumbled on an article that took a different approach. Instead of trying to map the objects from a single query, it split up the joins in a QueryMultiple statement. I then used Linq to map the multiple objects as one.
public List<MainObject> GetMainObjects(string conn)
{
List<MainObject> mos = new List<MainObject>();
using (IDbConnection connection = new SqlConnection(conn))
{
SqlMapper.GridReader results = connection.QueryMultiple(
$"SELECT * FROM MainObject; " +
$"SELECT * FROM SubObject1; " +
$"SELECT * FROM SubObject2;");
mos = results.Read<MainObject>().ToList();
IEnumerable<SubObject1> so1s = results.Read<SubObject1>();
mos.ForEach(mo => mo.SubObject1 = new List<SubObject1>());
mos.ForEach(mo => mo.SubObject1.AddRange(
so1s.Where(so1 => mo.Id1 == so1.Id1 && mo.Id2 == so1.Id2 && mo.Id3.Equals(so1.Id3))
));
IEnumerable<SubObject2> so2s = results.Read<SubObject2>();
mos.ForEach(mo => mo.SubObject2 = new List<SubObject2>());
mos.ForEach(mo => mo.SubObject2.AddRange(
so2s.Where(so2 => mo.Id1 == so2.Id1 && mo.Id2 == so2.Id2 && mo.Id3.Equals(so2.Id3))
));
}
return mos;
}
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;
}
I really love Dapper's simplicity and possibilities. I would like to use Dapper to solve common challenges I face on a day-to-day basis. These are described below.
Here is my simple model.
public class OrderItem {
public long Id { get; set; }
public Item Item { get; set; }
public Vendor Vendor { get; set; }
public Money PurchasePrice { get; set; }
public Money SellingPrice { get; set; }
}
public class Item
{
public long Id { get; set; }
public string Title { get; set; }
public Category Category { get; set; }
}
public class Category
{
public long Id { get; set; }
public string Title { get; set; }
public long? CategoryId { get; set; }
}
public class Vendor
{
public long Id { get; set; }
public string Title { get; set; }
public Money Balance { get; set; }
public string SyncValue { get; set; }
}
public struct Money
{
public string Currency { get; set; }
public double Amount { get; set; }
}
Two challenges have been stumping me.
Question 1:
Should I always create a DTO with mapping logic between DTO-Entity in cases when I have a single property difference or simple enum/struct mapping?
For example: There is my Vendor entity, that has Balance property as a struct (otherwise it could be Enum). I haven't found anything better than that solution:
public async Task<Vendor> Load(long id) {
const string query = #"
select * from [dbo].[Vendor] where [Id] = #id
";
var row = (await this._db.QueryAsync<LoadVendorRow>(query, new {id})).FirstOrDefault();
if (row == null) {
return null;
}
return row.Map();
}
In this method I have 2 overhead code:
1. I have to create LoadVendorRow as DTO object;
2. I have to write my own mapping between LoadVendorRow and Vendor:
public static class VendorMapper {
public static Vendor Map(this LoadVendorRow row) {
return new Vendor {
Id = row.Id,
Title = row.Title,
Balance = new Money() {Amount = row.Balance, Currency = "RUR"},
SyncValue = row.SyncValue
};
}
}
Perhaps you might suggest that I have to store amount & currency together and retrieve it like _db.QueryAsync<Vendor, Money, Vendor>(...)- Perhaps, you are right. In that case, what should I do if I need to store/retrive Enum (OrderStatus property)?
var order = new Order
{
Id = row.Id,
ExternalOrderId = row.ExternalOrderId,
CustomerFullName = row.CustomerFullName,
CustomerAddress = row.CustomerAddress,
CustomerPhone = row.CustomerPhone,
Note = row.Note,
CreatedAtUtc = row.CreatedAtUtc,
DeliveryPrice = row.DeliveryPrice.ToMoney(),
OrderStatus = EnumExtensions.ParseEnum<OrderStatus>(row.OrderStatus)
};
Could I make this work without my own implementations and save time?
Question 2:
What should I do if I'd like to restore data to entities which are slightly more complex than simple single level DTO? OrderItem is beautiful example. This is the technique I am using to retrieve it right now:
public async Task<IList<OrderItem>> Load(long orderId) {
const string query = #"
select [oi].*,
[i].*,
[v].*,
[c].*
from [dbo].[OrderItem] [oi]
join [dbo].[Item] [i]
on [oi].[ItemId] = [i].[Id]
join [dbo].[Category] [c]
on [i].[CategoryId] = [c].[Id]
join [dbo].[Vendor] [v]
on [oi].[VendorId] = [v].[Id]
where [oi].[OrderId] = #orderId
";
var rows = (await this._db.QueryAsync<LoadOrderItemRow, LoadItemRow, LoadVendorRow, LoadCategoryRow, OrderItem>(query, this.Map, new { orderId }));
return rows.ToList();
}
As you can see, my question 1 problem forces me write custom mappers and DTO for every entity in the hierarchy. That's my mapper:
private OrderItem Map(LoadOrderItemRow row, LoadItemRow item, LoadVendorRow vendor, LoadCategoryRow category) {
return new OrderItem {
Id = row.Id,
Item = item.Map(category),
Vendor = vendor.Map(),
PurchasePrice = row.PurchasePrice.ToMoney(),
SellingPrice = row.SellingPrice.ToMoney()
};
}
There are lots of mappers that I'd like to eliminate to prevent unnecessary work.
Is there a clean way to retrive & map Order
entity with relative properties like Vendor, Item, Category etc)
You are not showing your Order entity but I'll take your OrderItem as an example and show you that you don't need a mapping tool for the specific problem (as quoted). You can retrieve the OrderItems along with the Item and Vendor info of each by doing the following:
var sql = #"
select oi.*, i.*, v.*
from OrderItem
inner join Item i on i.Id = oi.ItemId
left join Vendor v on v.Id = oi.VendorId
left join Category c on c.Id = i.CategoryId";
var items = connection.Query<OrderItem, Item, Vendor, Category, OrderItem>(sql,
(oi,i,v,c)=>
{
oi.Item=i;oi.Item.Category=c;oi.Vendor=v;
oi.Vendor.Balance = new Money { Amount = v.Amount, Currency = v.Currency};
return oi;
});
NOTE: The use of left join and adjust it accordingly based on your table structure.
I'm not sure I understand your question a 100%. And the fact that no one has attempted to answer it yet, leads me to believe that I'm not alone when I say it might be a little confusing.
You mention that you love Dapper's functionality, but I don't see you using it in your examples. Is it that you want to develop an alternative to Dapper? Or that you don't know how to use Dapper in your code?
In any case, here's a link to Dapper's code base for your review:
https://github.com/StackExchange/dapper-dot-net
Hoping that you'd be able to clarify your questions, I'm looking forward to your reply.
I have a DTO object like this:
public class TreeViewDTO
{
public string Value { get; set; }
public string Text { get; set; }
public bool HasChildren { get; set; }
}
and my entity mapped with Nhibernate is:
public class Entity
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Entity Parent { get; set; }
/* other properties */
}
I would like to know, how can I get a List of my DTOs and fill the HasChildren property using a count method or a subquery to know if there are childrens?
I have tried this, but does not work:
return Session.QueryOver<Entity>
.Select(entity => new TreeViewViewModel() {
Value = entity.Id.ToString(),
Text = entity.Name,
HasChildren = (Session.QueryOver<Entity>().Where(x => x.ParentId == entity.Id).RowCount() > 0)})
.ToList();
I got an exception with this: NotSupportedException and the messages says: x => (x.Parent.Id == [100001].Id) and it is not supported.
How could I create a query to fill this property?
PS: I would like to have a query to select only the Id, Name and Count... because my entity can have 30 fields or more...
Thank you.
Using the NHibernate Linq provider then you can do this:-
public class dto
{
public long Value { get; set; }
public int Count { get; set; }
public bool HasCount { get { return Count > 0; } }
}
Note: my DTO has a read-only property that looks at the actual count, the query is then:-
var a = Db.Session.Query<Support>().Select(
s => new dto {
Value = s.Id,
Count = s.CommentList.Count
}
).ToList();
This generates the following sQL
select support0_.Id as col_0_0_,
(select cast(count(*) as SIGNED)
from supportcomment commentlis1_
where support0_.Id = commentlis1_.SupportId) as col_1_0_
from support support0_
I have never seen a working example of this using QueryOver. I have had had a stab at it but couldn't get it working..
Didn't you consider the option of using something else rather than NHibernate for this job?
In my opinion, lightweight library like Dapper can be a brilliant solution for this use case. You'll end up with a resonably simple sql query instead of jiggling with Nhibernate.
Edit:
dapper code will be as simple as this:
public IDbConnection ConnectionCreate()
{
IDbConnection dbConnection = new SQLiteConnection("Data Source=:memory:;pooling = true;");
dbConnection.Open();
return dbConnection;
}
public void Select()
{
using (IDbConnection dbConnection = ConnectionCreate())
{
var query = #"SELECT e1.id as Value, e1.name as Text, CASE WHEN EXISTS
(SELECT TOP 1 1 FROM Entity e2 WHERE e2.parent = e1.id)
THEN 1 ELSE 0 END as HasChildren
FROM Entity e1";
var productDto = dbConnection.Query<TreeViewDTO>(query);
}
}
I'm creating a product listing for an online store. It's pretty standard stuff, a page of product thumbnails with brief details, price and a link through to full details.
I'm using a repository pattern, so I have a central data repository which gives me back tables from a SQL server. I've cut a lot of the code out for the sake of brevity, but just so you get the idea:
public class SqlProductsRepository : IProductsRepository
{
private Table<Product> productsTable;
public SqlProductsRepository(string connectionString)
{
var context = new DataContext(connectionString);
productsTable = context.GetTable<Product>();
// More tables set up here
}
public IQueryable<Product> Products
{
get { return productsTable; }
}
// More properties here
}
I have the following objects mapped to tables:
[Table(Name = "Products")]
public class Product
{
[Column(IsPrimaryKey = true)]
public string ProductCode { get; set; }
[Column]
public string Name { get; set; }
[Column]
public decimal Price { get; set; }
public List<ShopImage> Images = new List<ShopImage>();
}
[Table(Name = "Images_Products")]
public class Image_Product
{
[Column]
public int ImageID { get; set; }
[Column]
public string ProductCode { get; set; }
[Column]
public int DisplayOrder { get; set; }
}
[Table(Name = "Images")]
public class Image
{
[Column(Name = "ImageID")]
public int ImageID { get; set; }
[Column]
public bool Caption { get; set; }
}
If I perform the following query:
// 'db' is the repository (member variable of the controller class)
IQueryable<Product> products = from p in db.Products
join ip in db.Image_Product on p.ProductCode equals ip.ProductCode
where ip.DisplayOrder == 0
select p;
I get a nice IQueryable full of Product objects. However, what I want to do is populate each object's Images list property with a single Image object, with its ID set from the joined Image_Product table.
So I end up with a list of Products, each with one Image in its Images property, which has the ID of the image for that product in the database where DisplayOrder is 0.
I tried this projection, which I thought made sense:
IQueryable<Product> products = from p in db.Products
join ip in db.Image_Product on p.ProductCode equals ip.ProductCode
where ip.DisplayOrder == 0
select new Product {
ProductCode = p.ProductCode,
Price = p.Price,
Images = new List<Image> {
new Image { ImageID = ip.ImageID }
}
};
Which compiles, but throws a runtime error: Explicit construction of entity type 'xxx.Product' in query is not allowed.
Yet elsewhere in the project I do this:
var pages = from i in db.TemplatePageNavigationItems
orderby i.DisplayOrder
select new NavigationItem {
ID = i.PageID,
ParentID = i.ParentID,
Text = i.Name,
Title = i.Name,
Url = (i.Folder == null) ? "" : i.Folder
};
And get no complaints! I assume it's something to do with the first query returning an IQueryable<Product> but I'm not sure why.
Two questions really: why is this not allowed in the first situation, and what should I be doing in order to get my desired result?
As the error says, you can't construct explicit entity types (Product is just that) in your query which should return IQueryable<Product>. Your pages query will return IEnumerable<NavigationItem> and NavigationItem does not seem to be an entity type defined in the database.
You could try returning IEnumerable<Product> in your first query or define a separate type and return IEnumerable of that instead, if you need to project explicit, custom tailored instances of an object.