I have a very simple test project where I am trying to figure out how to add children into the parent's collection.
The datamodel is quite basic:
Current result returns a duplicated entry.
Expected/desired
I expected the result to be just one entry with two children
GROUP1 -> { USER_1, USER_2 }
GROUP class
public class GROUP
{
public GROUP()
{
this.USERs = new HashSet<USER>();
}
public int Group_ID { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public ICollection<USER> USERs { get; set; }
}
USER class
public class USER
{
public int User_ID { get; set; }
public int Group_ID { get; set; }
public string Name { get; set; }
public Nullable<int> Age { get; set; }
public GROUP GROUP { get; set; }
}
Dapper method
public GROUP Get(int id)
{
string sqlGetGroupExtended = $"SELECT _group.Group_ID, _group.Name, _group.Location, _user.User_ID, _user.Name, _user.GROUP_ID, _user.Age FROM dbo.[GROUP] _group " +
"LEFT JOIN dbo.[USER] _user ON _group.Group_ID = _user.Group_ID " +
"WHERE _group.Group_ID = #groupid;";
GROUP result = null;
var lookup = new Dictionary<int, GROUP>();
using (var connection = new SqlConnection(Properties.Settings.Default.CodeTest_DB))
{
var extendedGroup = connection.Query<GROUP, USER, GROUP>(sqlGetGroupExtended, (parent, child) =>
{
if (!lookup.TryGetValue(parent.Group_ID, out GROUP found))
{
lookup.Add(parent.Group_ID, found = parent);
}
found.USERs.Add(child);
return found;
}, param: new { groupid = id }, splitOn: "Location");
// result = extendedGroup <--- WHAT TO DO HERE?
}
return result;
}
How can I achieve this?
References:
http://dapper-tutorial.net/dapper
https://github.com/StackExchange/Dapper/blob/master/Dapper.Tests/MultiMapTests.cs#L12
If you're using SQL Server 2016 or Azure SQL you can take advance of JSON to return a hierarchical object:
https://medium.com/dapper-net/one-to-many-mapping-with-dapper-55ae6a65cfd4
It's an article I wrote on the subject, along with source code, that shows how to elegantly solve the problem.
My bad, as the code shows here https://github.com/StackExchange/Dapper/blob/master/Dapper.Tests/MultiMapTests.cs#L12
I was missing the .Distinct()
var extendedGroup = connection.Query<GROUP, USER, GROUP>(sqlGetGroupExtended, (parent, child) =>
{
if (!lookup.TryGetValue(parent.Group_ID, out GROUP found))
{
lookup.Add(parent.Group_ID, found = parent);
}
found.USERs.Add(child);
return found;
}, param: new { groupid = id }, splitOn: "Location,User_ID").Distinct();
Related
I have a list of users which each user has some favorite products. so each user has a list of favorite products.
in a search panel i want to check some products and as a search result I want to return list of users that at least has one of those checked product. at least one. but not users that didn't check any of those products.
public class User
{
public long Id { get; set; }
public string Name { get; set; }
public List<UserProduct> UserProducts { get; set; }
public User()
{
}
public User(long id,string name,List<long> productIds)
{
Id = id;
Name = name;
UserProducts = productIds.Select(x => new UserProduct(id, x)).ToList();
}
}
public class Products
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public Products()
{
}
public Products(int productId,string name)
{
ProductId = productId;
ProductName = name;
}
}
public class UserProduct
{
public int Id { get; set; }
public long UserId { get; set; }
public long ProductId { get; set; }
public UserProduct()
{
}
public UserProduct(long userId,long productId)
{
ProductId = productId;
UserId = userId;
}
}
public class SearchDto
{
public List<long> SearchProductIds { get; set; }
}
public class Main
{
public void FillUsers()
{
List<Products> products=new List<Products>()
{
new Products(1,"a"),
new Products(2,"b"),
new Products(3,"c"),
new Products(4,"d"),
new Products(5,"e"),
new Products(6,"f"),
new Products(7,"g"),
};
List<User> users=new List<User>()
{
new User(1,"jack",new List<long>(){1} ),
new User(2,"Mary",new List<long>(){1,4} ),
new User(3,"Sam",new List<long>(){5} ),
new User(4,"Sara",new List<long>(){7,1,2} ),
};
SearchDto dto=new SearchDto()
{
SearchProductIds = new List<long> { 1,4}
};
//Here as search dto :I want to get Jack,Mary and Sara beacause they have 1 or 4 in their products
}
}
Lets say you have a list of IDs of the products that you selected(selectedProductIdList)
I am assuming that your
User Model looks like this
public string Username {get; set;}
public string FirstName {get; set;}
//....
//more User data
//....
public List<Product> FavouriteProduct {get; set;}
Your user search result would be
var userList = listOfUsers.Where(u => u.FavouriteProduct.Where(p => selectedProductIdList.Contains(p.Id)));
Assuming all the lists are in memory, in case of database modify code accordingly.
var result= (from p1 in SearchDtoList
join p2 in UserProductList
on p1 equals p2.ProductId
join u in UserList
on p2.UserId equals u.Id
select u).ToList();
If SearchPanelDto.SelectedProductsList returns list of Ids then
Var searchedProductsIds = SearchPanelDto.SelectedProductsList;
var userList = listOfUsers.Where(m =>
searchedProductsIds.Any(x=> m.IntrestetProducts.Select(t => t.ProductId).Any(z => x.Equals(z))));
else if SearchPanelDto.SelectedProductsList return list of object which contains Id
//Get selected productIds from search panel
Var searchedProductsIds = SearchPanelDto.SelectedProductsList.select(m=>m.ProductId).ToList();
var userList = listOfUsers.Where(m =>
searchedProductsIds.Any(x=> m.IntrestetProducts.Select(t => t.ProductId).Any(z => x.Equals(z))));
What is coming in to the webAPI is this JSON string and becomes deserilized in to this:
List<AddAssignMealView> mealtraiDeserializeObjects = JsonConvert.DeserializeObject<List<AddAssignMealView>>(mealTrainee);
mealtraiDeserializeObjects contains five index's one for each day of the week. Inside that specific index is a class that looks like what is below:
public class AddAssignMealView
{
public int TraineeID { get; set; }
public string DayOfTheWeek { get; set; }
public List<string> MealTypes { get; set; }
public List<int> MealID { get; set; }
public List<string> MealName { get; set; }
}
What I am trying to do is, be able to create a list of MealTrainee(Entity Framework):
public partial class MealTrainee
{
public int MealTraineeID { get; set; } //Ignore this one due to it being a post
public int MealID { get; set; }
public int TraineeID { get; set; }
public string DayOfTheWeek { get; set; }
public string MealType { get; set; }
public string MealName { get; set; }
public virtual Meal Meal { get; set; }
}
So I can be able to use addrange and add the list to the database. I understand how to use zip and combined two list types together if it is a single element. This is different due to it being a list of five elements and each of those five elements containing three lists. If someone could point me in the right direction, that would be helpful.
You can Zip 2 times to combine values from 3 lists into series of tuples. You can use SelectMany to flatten results. For example:
var result = mealtraiDeserializeObjects.SelectMany(c =>
c.MealID.Zip(c.MealName, (id,name) => new {id, name})
.Zip(c.MealTypes, (prev, type) => new {prev.id, prev.name, type})
.Select(r => new MealTrainee
{
TraineeID = c.TraineeID,
DayOfTheWeek = c.DayOfTheWeek,
MealID = r.id,
MealName = r.name,
MealType = r.type,
}));
This is the solution I found. I took it day by day in the sense that the first iteration would be Monday and then the count of meals(Ex:Meal for breakfast, Meal for Lunch) and put them in a count which would be "mealweek". Then I took mealweek and created a new mealtrainee for each count. Then after I made the meal out of the mealtrainee I put it in db.MealTrainees.AddRange and posted all the records.
[ResponseType(typeof(MealTrainee))]
public IHttpActionResult PostMealTrainee([FromBody] string mealTrainee)
{
List<MealTrainee> meals = new List<MealTrainee>();
using (DbContextTransaction dbContextTransaction = db.Database.BeginTransaction())
{
try
{
List<AddAssignMealView> mealtraiDeserializeObjects = JsonConvert.DeserializeObject<List<AddAssignMealView>>(mealTrainee);
foreach (var mealtraiDeserializeObject in mealtraiDeserializeObjects)
{
var mealWeek = mealtraiDeserializeObject.MealID.Select((m, i) => new
{
mealtraiDeserializeObject.TraineeID,
mealtraiDeserializeObject.DayOfTheWeek,
MealID = m,
MealTypes = mealtraiDeserializeObject.MealName[i],
MealName = mealtraiDeserializeObject.MealTypes[i]
}).ToList();
var meal = mealWeek.Select(x => new MealTrainee()
{
DayOfTheWeek = x.DayOfTheWeek,
MealID = x.MealID,
MealName = x.MealName,
MealType = x.MealTypes,
TraineeID = x.TraineeID
}).ToList();
db.MealTrainees.AddRange(meal);
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.SaveChanges();
dbContextTransaction.Commit();
return Ok(meals);
}
catch (Exception e)
{
dbContextTransaction.Rollback();
Logger.Log(e);
return BadRequest();
}
}
}
I'm trying to create a List of object Work using Dapper to do the mapping.
This is the code:
public class Work
{
public int id { get; set; }
public int id_section { get; set; }
public decimal price { get; set; }
public Model id_model { get; set; }
public Type id_type { get; set; }
}
class Model
{
public int id_model { get; set; }
public string Name { get; set; }
}
class Type
{
public int id_type { get; set; }
public string Name { get; set; }
}
public List<Work> GetListOfWork(int idList)
{
using (DatabaseConnection db = new DatabaseConnection()) //return a connection to MySQL
{
var par = new {Id = idList};
const string query = "SELECT id,id_section,price,id_model,id_type FROM table WHERE id_section = #Id";
return db.con.Query<Work, Model, Type, Work>(query,
(w, m, t) =>
{
w.id_model = m;
w.id_type = t;
return w;
}, par, splitOn: "id_model,id_type").ToList();
}
}
It doesn't give me any error but id_model and id_type in my the returned List are always empty (The object are created but all the fields are empty or null), other fields are mapped correctly.
Any clue ?
You need to add yourself the joins in the query string
Probably it is something like this
var par = new {Id = idList};
const string query = #"SELECT w.id,w.id_section,w.price,
m.id_model, m.Name, t.id_type, t.Name
FROM work w INNER JOIN model m on w.id_model = m.id_model
INNER JOIN type t on w.id_type = t.id_type
WHERE w.id_section = #Id";
return db.con.Query<Work, Model, Type, Work>(query,
(w, m, t) =>
{
w.id_model = m;
w.id_type = t;
return w;
}, par, splitOn: "id_model,id_type").ToList();
I maintain an API that, based on a request for a list of people, returns a different result set based on the request. For example, some API clients want to get a list of people and a list of their interactions, others want people and a list of their metadata. All this can be specified int he request to the API method that returns people.
This does not appear to work:
using (var dbcontext = new ExampleEntities())
{
var query = dbcontext.People.AsQueryable();
//determined in earlier application logic based on request
if(includeMetadata)
{
query = query.Include("metadata");
}
//determined in earlier application logic based on request
if(includeInteractions)
{
query = query.Include("interactions");
}
/* ...SNIP... */
}
What I don't want to do is this:
var query = dbcontext.People.Include("Metadata").Include("interactions");
which will mean every request to get a person will include ALL their related entities, even if the requesting API client does not need them.
I also don't want to code every possible combination of logic:
if(includeMetadata && includeInteractions)
{
var query = dbcontext.People.Include("Metadata").Include("interactions");
}
else if(includeMetadata)
{
var query = dbcontext.People.Include("Metadata");
}
else if(includeInteractions)
{
var query = dbcontext.People.Include("Interactions");
}
else
{
var query = dbcontext.People;
}
This will result in hard-to-maintain code, however, I realize I could code generate this if needed.
You can chain the IQueryable's
using (var dbcontext = new ExampleEntities())
{
var query = dbcontext.People.AsQueryable();
if(includeMetadata)
{
query = query.Include("metadata");
}
if(includeInteractions)
{
query = query.Include("interactions");
}
}
Your first example should work if you replace u with query
u = u.Include("metadata");
with
query = query.Include("metadata");
Works fine here... checking the sql statements with the EF 6 Log handler
[TestClass]
public void SomeTestClass
{
[TestMethod]
public void ShouldLoadOnlyRequiredCollections()
{
Database.SetInitializer(new DropCreateDatabaseAlways<HomesContext>());
var db = new HomesContext();
Assert.IsFalse(db.Homes.Any());
var home = db.Homes.Create();
db.Homes.Add(home);
home.Staff.Add(new Staff { Name = "wilma" });
home.Staff.Add(new Staff { Name = "betty" });
home.Residents.Add(new Resident { Name = "fred" });
home.Residents.Add(new Resident { Name = "barney" });
db.SaveChanges();
db = null;
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<HomesContext>());
var sb = new StringBuilder();
db = new HomesContext();
db.Database.Log = ((s) => { sb.Append(s + "\r"); });
Assert.IsTrue(db.Homes.Any());
string log;
log = sb.ToString();
Assert.IsTrue(sb.ToString().Contains("FROM [dbo].[Homes]"));
sb = new StringBuilder(); //ok get residents
var q = db.Homes.Include("Residents");
Assert.IsTrue(string.IsNullOrEmpty(sb.ToString()));
var lst = q.ToList();
log = sb.ToString();
Assert.IsTrue(sb.ToString().Contains("[dbo].[Homes]"));
Assert.IsTrue(sb.ToString().Contains("[dbo].[Residents]"));
Assert.IsTrue(!sb.ToString().Contains("[dbo].[Staff]"));
sb = new StringBuilder(); //get staff
q = db.Homes.Include("Staff");
Assert.IsTrue(string.IsNullOrEmpty(sb.ToString()));
lst = q.ToList();
log = sb.ToString();
Assert.IsTrue(log.Contains("[dbo].[Homes]"));
Assert.IsTrue(!log.Contains("[dbo].[Residents]"));
Assert.IsTrue(log.Contains("[dbo].[Staffs"));
sb = new StringBuilder(); //get residents and staff
q = db.Homes.Include("Staff");
q = q.Include("Residents");
lst = q.ToList();
log = sb.ToString();
Assert.IsTrue(log.Contains("[dbo].[Homes]"));
Assert.IsTrue(log.Contains("[dbo].[Residents]"));
Assert.IsTrue(log.Contains("[dbo].[Staffs]"));
}
}
public class HomesContext:DbContext
{
public DbSet<Home> Homes { get; set; }
}
public class Home
{
public Home()
{
Staff = new List<Staff>();
Residents = new List<Resident>();
}
public int HomeId { get; set; }
public string HomeName { get; set; }
public int MaxResidents { get; set; }
public int MaxStaff { get; set; }
public int CurrentResidents { get; set; }
[NotMapped]
public int CurrentStaff { get; set; }
public IList<Staff> Staff { get; set; }
public IList<Resident> Residents { get; set; }
}
public class Staff
{
public int StaffId { get; set; }
public string Name { get; set; }
public int HomeId { get; set; }
public Home Home { get; set; }
}
public class Resident
{
public int ResidentId { get; set; }
public string Name { get; set; }
public int HomeId { get; set; }
public Home Home { get; set; }
}
i am new to lambda expression so i try to solve one problem .but i can't. so can anyone suggest solution for this.
i have one class customer. inside i created another 3 class and create observable collection for 3 classes.i create one observable collection for this customer
ObservableCollection<Customer> customer2;
public class Customer
{
public string CusName { get; set; }
public int CusAge { get; set; }
public ObservableCollection<Bankdetails> bankdetails;
public ObservableCollection<order> orderlist;
public ObservableCollection<orderdetails> orderdetailslist;
public class Bankdetails
{
public string Bankaccno { get; set; }
public string bankname { get; set; }
public int bankid { get; set; }
}
public class order
{
public string ordername { get; set; }
public string orderid { get; set; }
}
public class orderdetails
{
public string orderid { get; set; }
public string itemname { get; set; }
public int itemqty { get; set; }
}
}
i write one linq query for getting values from customer2.anyhow its working .like this i tried to write one lambda query but i can't.
here i adding some values to observable collection.
customer2 = new ObservableCollection<Customer>
{
new Customer()
{
CusName="nixon",CusAge=24,
bankdetails=new ObservableCollection<Customer.Bankdetails>
{
new Customer.Bankdetails()
{
bankid=12,bankname="axis",Bankaccno="09876534"
}
},
orderlist=new ObservableCollection<Customer.order>
{
new Customer.order
{
orderid="Od123",ordername="Express"
}
},
orderdetailslist=new ObservableCollection<Customer.orderdetails>
{
new Customer.orderdetails
{
orderid="Od123",itemname="cpu",itemqty=5
}
}
}
};
this is my linq query
var customer1 = from cus in customer2
from bank in cus.bankdetails
from ord in cus.orderlist
from orddet in cus.orderdetailslist
where ord.orderid == orddet.orderid
select new
{
cus.CusAge,cus.CusName,
bank.Bankaccno,bank.bankid,bank.bankname,
ord.ordername,
orddet.itemname,orddet.itemqty
};
then what will be the lambda query.pls anyone suggest .
Matt's solution extended with the where from the question would be:
var xxx = customer2.SelectMany(cus =>
cus.bankdetails.SelectMany(bank =>
cus.orderlist.SelectMany(ord =>
cus.orderdetailslist.Where(orddet => orddet.orderid == ord.orderid)
.Select(orddet => new
{
cus.CusAge,
cus.CusName,
bank.Bankaccno,
bank.bankname,
orddet.itemname,
orddet.itemqty
}
)
)
)
);
var xxx = customer2.SelectMany(cus =>
cus.bankdetails.SelectMany(bank =>
cus.orderlist.SelectMany(ord =>
cus.orderdetailslist.Select(orddet => new
{
cus.CusAge,
cus.CusName,
bank.Bankaccno,
bank.bankname,
orddet.itemname,
orddet.itemqty
}
)
)
)
);