The dapper tutorial gives this example to help a user with Multi Mapping (One to Many)
While this works I am curious why they have you store the orders in the dictionary but then in the end they use a linq.Distinct() and return from the list. It seems like it would be cleaner to just return the ordersDictionary.Values as the dictionary logic ensures no duplicates.
//Tutorial
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
Dictionary<int,Order> orderDictionary = new Dictionary<int, Order>();
List<Order> list = connection.Query<Order, OrderDetail, Order>(sql, (order, orderDetail) =>
{
if (!orderDictionary.TryGetValue(order.OrderID, out Order orderEntry))
{
orderEntry = order;
orderEntry.OrderDetails = new List<OrderDetail>();
orderDictionary.Add(orderEntry.OrderID, orderEntry);
}
orderEntry.OrderDetails.Add(orderDetail);
return orderEntry;
}, splitOn: "OrderID")
.Distinct()
.ToList();
return list;
}
//my suggestion
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
Dictionary<int,Order> orderDictionary = new Dictionary<int, Order>();
//change 1 no need to store into list here
connection.Query<Order, OrderDetail, Order>(sql, (order, orderDetail) =>
{
if (!orderDictionary.TryGetValue(order.OrderID, out Order orderEntry))
{
orderEntry = order;
orderEntry.OrderDetails = new List<OrderDetail>();
orderDictionary.Add(orderEntry.OrderID, orderEntry);
}
orderEntry.OrderDetails.Add(orderDetail);
return orderEntry;
}, splitOn: "OrderID"); //change 2 remove .Distinct().ToList()
return orderDictionary.Values.ToList(); //change 3 return dictionaryValues
}
I'm the author of this tutorial: https://dapper-tutorial.net/query#example-query-multi-mapping-one-to-many
why they have you store the orders in the dictionary
A row is returned for every OrderDetail. So you want to make sure to add the OrderDetail to the existing Order and not create a new one for every OrderDetail. The dictionary is used for performance to check if the Order has been already created or not.
it would be cleaner to just return the ordersDictionary.Values
How will your query return dictionary values?
Of course, if you are in a method such as yours, you can do
var list = orderDictionary.Values;
return list;
But how to make this Connection.Query return dictionary values? An order is returned for every row/OrderDetail, so the order will be returned multiple times.
Outside the Query, your dictionary solution works great and is even a better solution for performance, but if you want to make your Query return the distinct list of orders without using Distinct or some similar method, it's impossible.
EDIT: Answer comment
my suggestion return orderDictionary.Values.ToList(); //change 3 return dictionaryValues
Thank you for your great feedback, it's always appreciated ;)
It would be weird in a tutorial to use what the query returns when there is no relationship but use the dictionary for one to many relationships
// no relationship
var orders = conn.Query<Order>("", ...).Distinct();
// one to many relationship
conn.Query<Order, OrderDetail>("", ...);
var orders = orderDictionary.Values.ToList();
Your solution is better for performance the way you use it, there is no doubt about this. But this is how people usually use the Query method:
var orders = conn.Query("", ...).Distinct();
var activeOrders = orders.Where(x => x.IsActive).ToList();
var inactiveOrders = orders.Where(x => !x.IsActive).ToList();
They use what the Query method returns.
But again, there is nothing wrong with the way you do it, this is even better if you can do it.
Related
This is my first post. I am in a trouble in my laravel project
Here is my data table.
I have student Id like 1,2,3. every students have multiple results followed by courses.
I need to arrange them like that
I tried groupby and got this result
Is there any possible way to arrange them according to students.
Thank You
code: controller:
public function notification()
{
$auth_id = Auth::user()->id;
$teacher = Teacher::where('user_id', $auth_id)->first();
$teacher_id = ($teacher->id);
$batch = Batch::where('teacher_id', $teacher_id)->first();
$courses = AssignCourses::with('course')
->where('semester_id', $batch->semester_id)
->get();
$current_semester_results = Result::with(['student', 'course'])
->where('semester_id', $batch->semester_id)
->get()
->groupBy('student.id');
$batch_students = Student::with('result')
->where('semester_id', $batch->semester_id)
->get();
return view('users.teacher.my_batch.notification', compact(['current_semester_results', 'courses', 'batch_students']));
}
Just use the $batch_students and apply any aggregations on your PHP code, it is easier to do it.
$batch_students = Student::with('result')
->where('semester_id', $batch->semester_id)
->get();
$batch_students_grouped = $batch_students->groupBy('result.student_id');
Note: I could not test since I don't have the tables, so you might need to change the student_id nest/access index in the last line of code.
you can print out your $batch_students_grouped->all() and see how you should iterate your data and show it in frontend.
I'm trying to combine 3 separate SOQL queries into one, but still end up with 3 separate lists for ease of use and readability later.
List<Object__c> objectList = [SELECT Name, Id, Parent_Object__r.Name, Parent_Object__r.Id,
(SELECT Name, Id FROM Child_Objects__r)
FROM Object__c];
I know I can get a list of child objects thus:
List<Child_Object__c> childObjectList = new List<Child_Object__c>();
for(Object__c object : objectList){
childObjectList.addAll(object.Child_Objects__r);
}
How would I go about adding the Parent_Object__c records to their own list?
I'm assuming a map could be used to deal with duplicates, but how do I get this Parent_Object__c data into that map?
You are basically there.
All lookup fields are available in your example as object.Parent_Object__r. Use a Set to natively avoid duplicates. No deduping required on your part!
Set<Parent_Object__c> parentObjectSet = new Set<Parent_Object__c>();
List<Child_Object__c> childObjectList = new List<Child_Object__c>();
for(Object__c object : objectList){
childObjectList.addAll(object.Child_Objects__r);
parentObjectSet.add(object.Parent_Object__r);
}
Edit:
As per #eyescream (trust him!) you are indeed better off with a map to avoid duplicates.
So the above code would just be slightly different:
Map<Id, Parent_Object__c> parentObjectMap = new Map<Id, Parent_Object__c>();
List<Child_Object__c> childObjectList = new List<Child_Object__c>();
for(Object__c object : objectList){
childObjectList.addAll(object.Child_Objects__r);
if (object.Parent_Object__r != null) {
parentObjectMap.put(object.Parent_Object__r.Id, object.Parent_Object__r);
}
}
I have an interesting little problem. My controller is assigning values to the properties in my model using two tables. In one of the tables, I have some entries that I made a while ago, and also some that I've just added recently. The old entries are being assigned values correctly, but the new entries assign NULL even though they're in the same table and were created in the same fashion.
Controller
[HttpPost]
[Authorize]
public ActionResult VerifyReservationInfo(RoomDataView model)
{
string loginName = User.Identity.Name;
UserManager UM = new UserManager();
UserProfileView UPV = UM.GetUserProfile(UM.GetUserID(loginName));
RoomAndReservationModel RoomResModel = new RoomAndReservationModel();
List<RoomProfileView> RoomsSelectedList = new List<RoomProfileView>();
GetSelectedRooms(model, RoomsSelectedList);
RoomResModel.RoomResRmProfile = RoomsSelectedList;
RoomResModel.GuestId = UPV.SYSUserID;
RoomResModel.FirstName = UPV.FirstName;
RoomResModel.LastName = UPV.LastName;
RoomResModel.PhoneNumber = UPV.PhoneNumber;
return View(RoomResModel);
}
GetUserProfile from the manager
public UserProfileView GetUserProfile(int userID)
{
UserProfileView UPV = new UserProfileView();
ResortDBEntities db = new ResortDBEntities();
{
var user = db.SYSUsers.Find(userID);
if (user != null)
{
UPV.SYSUserID = user.SYSUserID;
UPV.LoginName = user.LoginName;
UPV.Password = user.PasswordEncryptedText;
var SUP = db.SYSUserProfiles.Find(userID);
if (SUP != null)
{
UPV.FirstName = SUP.FirstName;
UPV.LastName = SUP.LastName;
UPV.PhoneNumber = SUP.PhoneNumber;
UPV.Gender = SUP.Gender;
}
var SUR = db.SYSUserRoles.Find(userID);
if (SUR != null)
{
UPV.LOOKUPRoleID = SUR.LOOKUPRoleID;
UPV.RoleName = SUR.LOOKUPRole.RoleName;
UPV.IsRoleActive = SUR.IsActive;
}
}
}
return UPV;
}
The issue I see is that this database has a somewhat poor design, and that particular record fell into the trap of that poor design. Consider that you have two ID's on that table:
SYSUserProfileID
SYSUserID
That's usually an indication of a bad design (though I'm not sure you can change it), if you can, you should merge anything that uses SYSUserID to use SYSUserProfileID.
This is bad because that last row has two different ID's. When you use db.Find(someId) Entity Framework will look for the Primary Key (SYSUserProfileID in this case) which is 19 for that row. But by the sounds of it, you also need to find it by the SYSUserID which is 28 for that row.
Personally, I'd ditch SYSUserID if at all possible. Otherwise, you need to correct the code so that it looks for the right ID column at the right times (this will be a massive PITA in the future), or correct that record so that the SYSUserID and SYSUserProfileID match. Either of these should fix this problem, but changing that record may break other things.
I have this many to many association between KundeInfo and HovedKategori, which I have mapped in my MS SQL database like this:
I have implemented the methods KundeInfo.HovedKategoris:
public IEnumerable<KundeInfo> KundeInfos
{
get
{
using (var dc = new DataClassesBSMAKSDataContext())
{
dc.DeferredLoadingEnabled = false;
var kundeInfoHovedKategoris = dc.KundeInfoHovedKategoris.Where(x => x.HovedKategori_Id == Id);
var kundeInfos = dc.KundeInfos.Where(x => kundeInfoHovedKategoris.Any(y => y.KundeInfo_Id == x.Id));
return kundeInfos.ToList();
}
}
}
... and HovedKategori.KundeInfos:
public IEnumerable<HovedKategori> HovedKategoris
{
get
{
using (var dc = new DataClassesBSMAKSDataContext())
{
var kundeInfoHovedKategoris = dc.KundeInfoHovedKategoris.Where(x => x.KundeInfo_Id == Id);
var hovedKategoris = dc.HovedKategoris.Where(x => kundeInfoHovedKategoris.Any(y => y.HovedKategori_Id == x.Id));
return hovedKategoris.ToList();
}
}
}
This retrieves the associated KundeInfos from a specific HovedKategori and opposite. The problemhowever lies in the serialization. When I call ToList(), or serialize these objects to JSON, linq will try to first follow all references returned by HovedKategori.KundeInfos, if it were that method I were to call first, and then it would for each returned object, try to follow all references returned by KundeInfo.HovedKategoris and so on, until it would cast a stack overflow exception.
If I could somehow prevent linq from following certain properties with an [Ignore] attribute or something, it would work, but I haven't been able to find anything like that.
What can I do in this situation?
This is in part a design issue. What you should really ask yourself is if you need navigation properties in every possible direction. For example if you just add a Kategori ID instead of a navigation property you could still query your context (by using the ID) but do you really need to always get all the Kategori's with all underlying data?
also if you make your properties virtual you have lazy loading and will only get the information if you .Include it or explicitly reference it.
Ok - So I solved this problem by just making it into methods on the respective classes, which it should be in the first place, since it retrieved these entities from the database. So yes, partially design issue.
Virtual did not work, I considered using projection and an abstract class, but it would be such a haystack of inheritance, and class casts, that it would not even be worth considering.
public IEnumerable<KundeInfo> KundeInfos()
{
using (var dc = new DataClassesBSMAKSDataContext())
{
var kundeInfoHovedKategoris = dc.KundeInfoHovedKategoris.Where(x => x.HovedKategori_Id == Id);
var kundeInfos = dc.KundeInfos.Where(x => kundeInfoHovedKategoris.Any(y => y.KundeInfo_Id == x.Id));
return kundeInfos.ToList();
}
}
I need to provide a webservice which returns articles.
I want to include the user relationship in that result to avoid my clients to call another method to load the user object.
I use an Array Result because I want a collection of array (I think it's better to work with) so I wish I could eager load my user.
I tried:
* #ManyToOne(targetEntity="\My\Model\User\User", fetch="EAGER")
But it doesn't look to work.`
Edit, some code:
public function getPublishedArticles($page, $count, $useArrayResult = false) {
$qb = $this->createQueryBuilder('a');
$qb->where('a.status = :status')
->orderBy('a.published_date', 'DESC')
->addOrderBy('a.creation_date', 'DESC')
->setParameter('status', Article::STATUS_PUBLISHED )
->andWhere('a.published_date <= :date')
->setParameter('date', date('Y-m-d'));
}
$adapter = new PaginationAdapter($qb->getQuery());
$adapter->useArrayResult($useArrayResult);
$paginator = new \Zend_Paginator($adapter);
$paginator->setItemCountPerPage($itemCount)
->setCurrentPageNumber($page);
return $paginator;
}
And I call this method with the $useArrayResult flag sets to TRUE
When you're using DQL query you have add JOIN clause to join related entities:
$qb->createQueryBuilder('a')
->addSelect('u')
->join('a.user', 'u')
...
fetch="EAGER" and fetch="LAZY" are being used when you're fetching entities using EntityManager, ie:
$article = $em->find('Entity\Article', 123);