Is this an efficient way of mapping with Dapper? - c#

Im trying to map the list of Products and CategoryPictures properties inside the Category entity.
Then im mapping the Picture object inside the CategoryPictures.
Im not sure if this an effiecient way of using dapper as its my first time working with dapper. I used to work with entityframework.
Im using Dapper instead of EF as i want to improve my sql skills.
Should i use multiple results instead?
public async Task<IEnumerable<Category>> GetCategoriesListAsync()
{
using (var conn = SqlConnection())
{
string sql = #"select c.*, cp.*, pd.*, p.*
from Categories c
inner join CategoryPictures cp on cp.CategoryId = c.Id
inner join Products pd on pd.CategoryId = c.Id
inner join Pictures p on p.Id = cp.PictureId";
//string sqlmulti = $#"select * from {tableName};
// select * from Products";
//List<Category> cat = null;
//List<Product> prod = null;
//using (var lists = conn.QueryMultiple(sqlmulti))
//{
// cat = lists.Read<Category>().ToList();
// prod = lists.Read<Product>().ToList();
//}
var lookup = new List<Category>();
await conn.QueryAsync<Category, CategoryPicture, Product, Picture, Category>(sql,
(c, cp, pd, p) =>
{
if (lookup.FirstOrDefault(x => x.Id == c.Id) == null)
{
lookup.Add(c);
}
c.CategoryPictures.AsList().Add(cp);
c.Products.AsList().Add(pd);
foreach (var item in c.CategoryPictures)
{
item.Picture = p;
}
return null;
});
return lookup;
}
}

I've removed the link between Category and Product where the Product was child of Category.
The problem i was having with the previous code was that the Picture child of CategoryPictures was not populating properly. So i set the Picture object of CategoryPicture before populating CategoryPicture.
public async Task<IEnumerable<Category>> GetAllCategories()
{
using (var conn = SqlConnection())
{
string sql = #"select c.Id, c.Name, c.Description, c.Created, c.LastModified,
cp.Id, cp.CategoryId, cp.PictureId,
p.Id, p.Url
from Categories c
inner join CategoryPictures cp on cp.CategoryId = c.Id
inner join Pictures p on p.Id = cp.PictureId";
var lookup = new Dictionary<int, Category>();
await conn.QueryAsync<Category, CategoryPicture, Picture, Category>(sql,
(c, cp, p) =>
{
if (!lookup.TryGetValue(c.Id, out Category found))
{
lookup.Add(c.Id, found = c);
}
cp.Picture = p;
found.CategoryPictures.AsList().Add(cp);
return null;
});
return lookup.Values.ToList();
}
}

Related

GridView Only populating 1 result

I'm currently working to add Data to a GridView. The data comes from 2 tables that are on different databases. Currently I am able to populate the first entry, but it does not populate past that. here is the code:
void FillOrder(int inv)
{
var _ord = new OrdersContext();
var _pro = new ProductContext();
var qryOrder = (from o in _ord.OrderDetails
where o.InvNumberId == inv
select new
{
o.ProductID,
o.Quantity
}).ToList();
foreach (var order in qryOrder)
{
int prodID = order.ProductID;
int itemCount = qryOrder.Count;
var qryProducts = (from p in _pro.Products
where p.ProductID == prodID
select new
{
p.ProductID,
p.ProductName
}).ToList();
var results = (from t in qryOrder
join s in qryProducts
on t.ProductID equals prodID
select new
{
t.ProductID,
t.Quantity,
s.ProductName
}).ToList();
OrderItemList.DataSource = results;
OrderItemList.DataBind();
}
}
Can anyone help as to why it's only populating the first entry?
If the number of products involved is relatively small, (and since this query seems to be relate to one invoice, I would think that is true), then you can probably use something like the code below.
This is removing the loop, but the contains method will probably generate a SQL statement something like select ProductID, ProductName from products where productID in (,,,,,,) so may fail if the number of parameters is extremely large.
var _ord = new OrdersContext();
var _pro = new ProductContext();
var qryOrder = (from o in _ord.OrderDetails
where o.InvNumberId == inv
select new
{
o.ProductID,
o.Quantity
}).ToList();
// Get the productIDs
var productIDS = qryOrder.Select(o=>o.ProductID).Distinct().ToList();
// Get the details of the products used.
var qryProducts = (from p in _pro.Products
where productIDS.Contains(p.ProductID)
select new
{
p.ProductID,
p.ProductName
}).ToList();
// Combine the two in memory lists
var results = (from t in qryOrder
join s in qryProducts
on t.ProductID equals s.ProductID
select new
{
t.ProductID,
t.Quantity,
s.ProductName
}).ToList();
OrderItemList.DataSource = results;
OrderItemList.DataBind();

How to do a query from an object with a list of objects in Dapper C#

Suppose I have the following tables:
tb_1: |user_id|user_name|email|age|
tb_2: |item_id|item_name|value|
tb_3: |user_id|item_id|
And I have the models below:
Item:
public class Item {
public string Name {get; set;}
public int Value {get; set;}
}
User:
public class User {
public Guid UserId {get; set;}
public List<Item> Itens {get; set;}
}
I am using the following Query to do the search:
using(var connection = ...)
{
var query1 = "SELECT ... FROM tb_1";
var query2 = "SELECT ... FROM tb_2 JOIN tb_3 ON ... WHERE tb_3.user_id = #UserId";
var users = await connection.QueryAsync<User>(query1);
foreach(var user in users)
{
user.Itens = await connection.QueryAsync<Item>(query2, user.UserId);
}
return users;
}
Is it possible to remove the foreach and use only one query?
PS: The tables are N to N.
I was able to solve the problem. I researched and I found a solution in Dapper documentation from the "one to many" query.
string sql = "SELECT TOP 10 * FROM Orders AS A INNER JOIN OrderDetails AS B ON A.OrderID = B.OrderID;";
using (var connection = new SqlCeConnection("Data Source=SqlCe_W3Schools.sdf"))
{
var orderDictionary = new Dictionary<int, Order>();
var list = connection.Query<Order, OrderDetail, Order>(
sql,
(order, orderDetail) =>
{
Order orderEntry;
if (!orderDictionary.TryGetValue(order.OrderID, out orderEntry))
{
orderEntry = order;
orderEntry.OrderDetails = new List<OrderDetail>();
orderDictionary.Add(orderEntry.OrderID, orderEntry);
}
orderEntry.OrderDetails.Add(orderDetail);
return orderEntry;
},
splitOn: "OrderID")
.Distinct()
.ToList();
Console.WriteLine(list.Count);
FiddleHelper.WriteTable(list);
FiddleHelper.WriteTable(list.First().OrderDetails);
}
Reference: Query Multi-Mapping (One to Many)
I would say "yes, but: rewrite the query to use a join", i.e.
var query = #"
SELECT ... FROM tb_1
SELECT ... FROM tb_2 JOIN tb_3 ON ...
INNER JOIN (SELECT UserId FROM tb_1) x -- your original query here
on tb_3.user_id = x.UserId -- but as a sub-query
";
then use QueryMultiple. This returns two grids, so you'll need to read twice - then group and partition at the call-site.
using (var multi = connection.QueryMultiple(sql, ...))
{
var users = multi.Read<User>().AsList();
var allItems = multi.Read<Item>().AsList();
var usersById = users.ToDictionary(x => x.Id);
foreach(var item in allItems) {
usersById[item.UserId].Items.Add(item);
}
return users;
}

Dapper multipmap

I have the following in my db with a junction/xref table
I would like to map it to the following object
public class Coin : CoinBase
{
public IEnumerable<CoinAnnouncement> Announcements { get; set; }
public IEnumerable<CoinCategory> Categories { get; set; }
}
I would like to include annoucements in one go as well if possible (not showing in the db image)
Here is my dapper call
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
return await conn.QueryAsync<Coin, CoinCategory, Coin>(
#"SELECT c.CoinId, c.Userid, c.IconPath, c.LastPriceBtc, c.LastUpdatedUtc, c.Name, c.Rank,
c.Symbol, c.LogoPath, c.Description, c.SubReddit, c.TwitterScreenName, c.Website, c.Discord,
c.FacebookPage, c.Telegram
FROM Coins c
INNER JOIN CoinCategoriesCategories coinCat ON coinCat.CoinId = c.CoinId
INNER JOIN CoinCategories cat ON cat.CategoryId = coinCat.CategoryID",
(coin, coinCat) => {
coin.Categories = coinCat; //problem figuring out what this line would look like
return coin;
});
}
I essentially want to just ignore the xref/junction table and directly map the categories to the coin object
Solved it based on https://www.tritac.com/blog/dappernet-by-example
var lookup = new Dictionary<int, Coin>();
using (var conn = new SqlConnection(connectionString))
{
conn.Open();
var result = await conn.QueryAsync<Coin, CoinCategoryCategory, CoinCategory, Coin>(
#"SELECT c.*, coinCat.*, cat.*
FROM Coins c
INNER JOIN CoinCategoriesCategories coinCat ON coinCat.CoinId = c.CoinId
INNER JOIN CoinCategories cat ON cat.CategoryId = coinCat.CategoryID",
(c, cat, coinCat) =>
{
Coin coin;
if (!lookup.TryGetValue(c.CoinId, out coin))
{
lookup.Add(c.CoinId, coin = c);
}
if (coin.Categories == null)
coin.Categories = new List<CoinCategory>();
coin.Categories.Add(new CoinCategory { CategoryId = coinCat.CategoryId, Description = coinCat.Description });
return coin;
}, splitOn: "CategoryId, CoinId");
return lookup.Values;
}

How to sort in LINQ If Join other database

Sort in LINQ
I have 2 database CustomerEntities and BillEntities
I want to get CustomerName from CustomerEntities and sort it but it have no data and I want .ToList() just once time because it slow if used many .ToList()
using (var db1 = new CustomerEntities())
{ using (var db2 = new BillEntities())
{
var CustomerData = db1.Customer.Select(s=> new{s.CustomerCode,s.CustomerName}).ToList();
var BillData = (from t1 in db2.Bill
select new {
BillCode = t1.Billcode,
CustomerCode = t1.Customer,
CustomerName = ""; //have no data
});
}
if(sorting.status==true)
{
BillData= BillData.OrderBy(o=>o.CustomerName); //can't sort because CustomerName have no data
}
var data = BillData .Skip(sorting.start).Take(sorting.length).ToList(); // I want .ToList() just once time because it slow if used many .ToList()
foreach (var b in data)
{
var Customer = CustomerData.FirstOrDefault(f => f.CustomerCode==b.CustomerCode );
if(CustomerName>!=null)
{
r.CustomerName = Customer.CustomerName; //loop add data CustomerName
}
}
}
I have no idea to do it. Help me please
I'm not sure if I understand your code but what about this:
var BillData = (from t1 in db2.Bill
select new {
BillCode = t1.Billcode,
CustomerCode = t1.Customer,
CustomerName = db1.Customer.FirstOrDefault(c => c.CustormerCode == t1.Customer)?.CustomerName
});
Then you have objects in BillData that holds the CustomerName and you can order by that:
BillData.OrderBy(bd => bd.CustomerName);
If you just want to get CustomerName from your customer Db and sort it, this is what i would have used. I used orderByDescending but you can use OrderBy aswell.
public List<Customer> getLogsByCustomerName(string customername)
{
using (var dbentites = new CustomerEntities())
{
var result = (from res in dbentites.Customer.OrderByDescending(_ => _.CustomerName)
where res.CustomerName == customername
select res).ToList();
return result.ToList();
}
}

How to generate SQL COUNT(*) OVER (PARTITION BY {ColumnName}) in LINQ-to-SQL?

Is it possible to generate the following SQL query by using LINQ-to-SQL query expression or method chains which is defer-executable?
Data Structure
alt text http://www.freeimagehosting.net/uploads/e062a48837.jpg
Select Distinct ClassRoomTitle,
Count(*) Over(Partition By ClassRoomNo) As [No Sessions Per Room],
TeacherName,
Count(*) Over(Partition By ClassRoomNo, TeacherName) As [No Sessions Per Teacher] From ClassRoom
Expected Result
alt text http://www.freeimagehosting.net/uploads/47a79fea8b.jpg
Try this:
var vGroup = from p in ClassRoom
group p by new { p.ClassRoomNo, p.TeacherName }
into g
from i in g
select new
{
i.ClassRoomNo,
i.TeacherName,
i.ClassRoomTitle,
NoSessionsPerTeacher = g.Count()
};
var pGroup = from p in vGroup
group p by new { p.ClassRoomNo }
into g
from i in g
select new
{
i.ClassRoomTitle,
NoSessionsPerRoom = g.Count(),
i.TeacherName,
i.NoSessionsPerTeacher
};
var result = pGroup.OrderBy(p => p.ClassRoomNo).ThenBy(p => p.TeacherName);
I didn't test the above but you can check my original code in case I got something wrong in the rewrite:
var vGroup = from p in Products
group p by new { p.ProductId, p.VariantId }
into g
from i in g
select new
{
i.ProductId,
i.VariantId,
VariantCount = g.Count()
};
var pGroup = from p in vGroup
group p by new { p.ProductId }
into g
from i in g
select new
{
i.ProductId,
ProductCount = g.Count(),
i.VariantId,
i.VariantCount
};
var result = pGroup.OrderBy(p => p.ProductId).ThenBy(p => p.VariantId);
var classRooms = from c in context.ClassRooms
group c by new {c.ClassRoomNo} into room
select new {
Title = room.First().ClassRoomTitle,
NoSessions = room.Count(),
Teachers = from cr in room
group cr by new {cr.TeacherName} into t
select new {
Teacher = t.Key,
NoSessions = t.Count()
}
};
A bit more structured than the posted expected result, but I find that to be better.
You can always use SelectMany if you want to go back to unstructured:
var unstructured = classRooms
.SelectMany(c=> c.Teachers.Select( t=> new {
Title = c.Title,
SessionsPerRoom = c.NoSessions,
Teacher = t.Teacher,
SessionsPerTeacher = t.NoSessions
});

Categories

Resources