Buddy ViewModel with LINQ Join - linq

SOS! What I am trying to achieve is for the logged in person, to see the users he or she is supporting (buddying). I am now trying to fully embrace ViewModels to coagulate views. I am using simplemembership MVC4 with Mysql. I have a UserProperties(all details of my users)linked to Userprofile and Everything else works. I usually use two databases one for membership and another for all other stuff.
models
UserProfile/UserProPerties - extended for all other properties
UserId
List item
UserName
UserProperty
FirstName
LastName
SchoolName
UserId
Buddyship
buddyId
buddiedByUserId
buddiedUserId
Viewmodel model
public class BuddyViewModel
{
public BuddyShip BuddyShip {get; set;}
public List<Buddyship> AllBudees {get; set;}
public UserProperty UserProperty { get; set; }
public PanelViewModel(Buddyship buddyship, List<Buddyship> allBudees)
{
Buddyship = buddyship;
AllBudees = allBudees;
}
}
BuddyViewModel Controller
// I believe this is where the magic should come from
public ActionResult Index(int? id)
{
//I get logged in user properties
var user = db.UserProperties.SingleOrDefault(x => x.UserName == User.Identity.Name);
Buddyship allBudees = db1.Buddyships.SingleOrDefault(u =>u.BuddiedByUserId == user.UserId);
var buds = from u in db.UserProperties
join m in db1.Buddyships on u.UserId equals m.BuddiedByUserId
where m.BuddiedByUserId == user.UserId
select new { u.FirstName, u.LastName, u.SchoolName, u.UserId };
var buddyviewmodel = new BuddyViewModel(buds //don't know what to put here);
return View(buddyviewmodel);
}
View
#model IEnumerable<BudTT.Models.BuddyViewModel>
#foreach (var item in Model.Buddyships)
{
<p>#Html.DisplayFor(model =>model.UserProperty.FirstName)</p>
<p>#Html.DisplayFor(model =>model.UserProperty.LastName)</p>
}
Thanks if you are able to help

Try to change your code as follows.
ViewModel:
public class BuddyViewModel
{
public BuddyShip BuddyShip {get; set;}
public List<Buddyship> AllBudees {get; set;}
public List<UserProperty> Users { get; set; } //** Add this property
}
Your action:
public ActionResult Index(int? id)
{
//I get logged in user properties
var user = db.UserProperties.SingleOrDefault(x => x.UserName == User.Identity.Name);
///**************Get Buddyships of current user*************************
List<Buddyship> allBudees = db1.Buddyships.Where(u =>u.BuddiedByUserId == user.UserId).ToList();
///**************Get Users supporting by user*************************
var buds =
(from u in db.UserProperties
join m in allBudees on u.UserId equals m.buddiedUserId
where m.BuddiedByUserId == user.UserId
select new UserProperty
{
FirstName = u.FirstName,
LastName = u.LastName,
SchoolName = u.SchoolName,
UserId = u.UserId
}).ToList();
var buddyviewmodel = new BuddyViewModel
{
Users = buds,
AllBudees = allBudees, //** if you really need this property in your View,
...
}
return View(buddyviewmodel);
}
Change View. In your action you are sending only one BuddyViewModel, which contains a list of UserProperties, but not a list of BuddyViewModels
#model BudTT.Models.BuddyViewModel
#foreach (var item in Model.Users)
{
<p>#item.FirstName</p>
<p>#item.LastName</p>
}

Related

EF Core - many queries sent to database for subquery

Using EF Core 2.2.2, I have a table in my database which is used to store notes for many other tables. In other words, it's sortof like a detail table in a master-detail relationship, but with multiple master tables. Consider this simplified EF Model:
public class Person
{
public Guid PersonID { get; set; }
public string Name { set; set; }
}
public class InvoiceItem
{
public Guid InvoiceItemID { get; set; }
public Guid InvoiceID { get; set; }
public string Description { get; set; }
}
public class Invoice
{
public Guid InvoiceID { get; set; }
public int InvoiceNumber { get; set; }
public List<Item> Items { get; set; }
}
public class Notes
{
public Guid NoteID { get; set; }
public Guid NoteParentID { get; set; }
public DateTime NoteDate { get; set; }
public string Note { get; set; }
}
In this case, Notes can store Person notes or Invoice notes (or InvoiceItem notes, though let's just say that the UI doesn't support that).
I have query methods set up like this:
public IQueryable<PersonDTO> GetPersonQuery()
{
return from p in Context.People
select new PersonDTO
{
PersonID = p.PersonID,
Name = p.Name
};
}
public List<PersonDTO> GetPeople()
{
return (from p in GetPersonQuery()
return p).ToList();
}
public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
return from p in Context.Invoices
select new InvoiceDTO
{
InvoiceID = p.InvoiceID,
InvoiceNumber = p.InvoiceNumber
};
}
public List<InvoiceDTO> GetInvoices()
{
return (from i in GetInvoiceQuery()
return i).ToList();
}
These all work as expected. Now, let's say I add InvoiceItems to the Invoice query, like this:
public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
return from p in Context.Invoices
select new InvoiceDTO
{
InvoiceID = p.InvoiceID,
InvoiceNumber = p.InvoiceNumber,
Items = (from ii in p.Items
select new ItemDTO
{
ItemID = ii.ItemID,
Description = ii.Description
}).ToList()
};
}
That also works great, and issues just a couple queries. However, the following:
public IQueryable<InvoiceDTO> GetInvoiceQuery()
{
return from p in Context.Invoices
select new InvoiceDTO
{
InvoiceID = p.InvoiceID,
InvoiceNumber = p.InvoiceNumber,
Items = (from ii in p.Items
select new ItemDTO
{
ItemID = ii.ItemID,
Description = ii.Description
}).ToList(),
Notes = (from n in Context.Notes
where i.InvoiceID = n.NoteParentID
select new NoteDTO
{
NoteID = n.NoteID,
Note = n.Note
}).ToList(),
};
}
sends a separate query to the Note table for each Invoice row in the Invoice table. So, if there are 1,000 invoices in the Invoice table, this is sending something like 1,001 queries to the database.
It appears that the Items subquery does not have the same issue because there is an explicit relationship between Invoices and Items, whereas there isn't a specific relationship between Invoices and Notes (because not all notes are related to invoices).
Is there a way to rewrite that final query, such that it will not send a separate note query for every invoice in the table?
The problem is indeed the correlated subquery versus collection navigation property. EF Core query translator still has issues processing such subqueries, which are in fact logical collection navigation properties and should have been processed in a similar fashion.
Interestingly, simulating collection navigation property with intermediate projection (let operator in LINQ query syntax) seems to fix the issue:
var query =
from i in Context.Invoices
let i_Notes = Context.Notes.Where(n => i.InvoiceID == n.NoteParentID) // <--
select new InvoiceDTO
{
InvoiceID = i.InvoiceID,
InvoiceNumber = i.InvoiceNumber,
Items = (from ii in i.Items
select new ItemDTO
{
ItemID = ii.ItemID,
Description = ii.Description
}).ToList(),
Notes = (from n in i_Notes // <--
select new NoteDTO
{
NoteID = n.NoteID,
Note = n.Note
}).ToList(),
};

Get Count from entity framework

Quite new to EF, basically i want to convert this SQL query:
SELECT
PSKU.ProductSKUID,
PSKU.ProductSKUName,
W.WarehouseID,
W.WarehouseName,
SA.SystemAreaName,
COUNT(SLI.ProductSKUID) AS QTY
FROM dbo.StockLineItem AS SLI INNER JOIN
dbo.ProductSKU AS PSKU ON PSKU.ProductSKUID = SLI.ProductSKUID INNER JOIN
dbo.Warehouse AS W ON W.WarehouseID = SLI.WarehouseID INNER JOIN
dbo.SystemArea AS SA ON SA.SystemAreaID = SLI.SystemAreaID
WHERE (SA.SystemAreaID = 1)
AND W.WarehouseID = #WarehouseID
GROUP BY PSKU.ProductSKUID, PSKU.ProductSKUName, W.WarehouseName, SA.SystemAreaName, W.WarehouseID
To an effective EF statement. This is what i Have so far, my Model class and the method:
[Serializable]
public class StockReturnMethod
{
public int ProductSKUID { get; set; }
public int WarehouseID { get; set; }
public int LotID { get; set; }
public string LotName { get; set; }
public int AreaID { get; set; }
public string AreaName { get; set; }
public int BinID { get; set; }
public string BinName { get; set; }
}
public class DALStockMovement
{
scmEntitiesPrimaryCon entities = new scmEntitiesPrimaryCon();
public List<AvailibleStock> AvailibleStockQty(int warehouseID)
{
var rows = (from PLA in entities.ProductLocationAssignments
from W in entities.Warehouses
from SLI in entities.StockLineItems
from SA in entities.SystemAreas
from PSKU in entities.ProductSKUs
where W.WarehouseID == warehouseID
select new AvailibleStock() { WarehouseID = W.WarehouseID, ProductSKUID = PSKU.ProductSKUID, ProductSKUName = PSKU.ProductSKUName, WarehouseName = W.WarehouseName, Status = SA.SystemAreaName, QtyUnassigned = SLI.ProductSKUID }).ToList();
return rows;
}
Any Advice to get this to an Effective EF Statement would be appreciated
I actually used this tool called Linqer, since I had the SQL
I just popped it into that tool and it generated the Linq for me.
Here is what came out:
var SKUStock = (from sli in entities.StockLineItems
where
sli.SystemArea.SystemAreaID == 1 &&
sli.WarehouseID == warehouseID
group new { sli.ProductSKU, sli.Warehouse, sli.SystemArea, sli } by new
{
ProductSKUID = (System.Int32?)sli.ProductSKU.ProductSKUID,
sli.ProductSKU.ProductSKUName,
sli.Warehouse.WarehouseName,
sli.SystemArea.SystemAreaName,
WarehouseID = (System.Int32?)sli.Warehouse.WarehouseID
} into g
select new AvailibleStock()
{
ProductSKUID = (int)(System.Int32?)g.Key.ProductSKUID,
ProductSKUName = g.Key.ProductSKUName,
WarehouseID = (int)(System.Int32?)g.Key.WarehouseID,
WarehouseName = g.Key.WarehouseName,
Status = g.Key.SystemAreaName,
QtyUnassigned = (int)(Int64?)g.Count(p => p.sli.ProductSKUID != null)
}).ToList();
return SKUStock;
It returns exactly what i need :).

mvc 3 dropdownlistfor - selected value is always null

I have dropdowlist like this:
public class RegionLine
{
public Nullable<int> regionId { get; set; }
[Display(Name = "Województwo: ")]
public string regionName { get; set; }
}
and controller:
public PartialViewResult getPersonalData()
{
var d = rm.GetAllRegionsMapper();
ViewBag.Regions = new SelectList(rm.GetAllRegionsMapper().ToList(), "regionId", "regionName", "-- select item --");
var user = um.GetUserByLoginMapper(User.Identity.Name);
return PartialView("getPersonalData", user);
}
[HttpPost]
public PartialViewResult UpdatePersonalData(UserLine user)
{
var usr = um.GetUserByLoginMapper(User.Identity.Name);
ViewBag.Regions = new SelectList(rm.GetAllRegionsMapper().ToList(), "regionId", "regionName", "-- select item --");
if (ModelState.IsValid)
{
int status = uda.UpdateEmployeesPersonalData(user.UserId, user.PersonalData.Name, user.PersonalData.LastName,
user.Address.City, user.Address.Street, user.Address.Region.regionId, user.Address.PostCode,
user.PersonalData.KeyWord);
return PartialView("getLabelsPersonalData", user);
}
return PartialView("getPersonalData", usr);
}
the part of view with my dropdownlist:
<tr>
<td>#Html.LabelFor(a => a.Address.Region.regionName)</td>
<td>#Html.DropDownListFor(a => a.Address.Region.regionId, (SelectList)ViewBag.Regions)</td>
</tr>
and when i select some items, on httppost regionId is always null. Please help.
Its quite possible that rm.GetAllRegionsMapper().ToList() returns you a list with all regionId == null.
Also, why have you defined regionId as nullable? It always will be either key you selected from DropDownList or 0 if non was selected, no reason to have it as null ever.
You very much likely confusing between regionId that is key in the drop down list and the regionId that you trying to fetch after selection is made and posted back. Call the latter one selectedRegionId to avoid confusion.
Hope these few ideas will lead you to the right direction and help you localize your actual problem.

MVC3 Editing in the Index View

I need some help with this one....
I have this simple model:
public class Candidat
{
public string LoginEmail { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Prénom")]
public string FirstName { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Nom")]
public string LastName { get; set; }
}
I also have a controller like this:
[Authorize]
public ActionResult Index(Candidat model)
{
if (model.LoginEmail == null)
{
model = null;
using (var db = new rhDB())
{
MembershipUser user = Membership.GetUser();
if (user != null)
{
model = (from m in db.Candidates where m.LoginEmail == user.Email select m).SingleOrDefault();
}
if (model == null)
{
model = new Candidat();
model.LoginEmail = user.Email;
model.Email = user.Email;
}
}
}
return View("MyProfileCandidate", model);
}
As you can see, I check if the user as an existing record in the database. If not, I create a new instance of the model and set some default values... Then, I pass it to an EditView.
The problem is that my view show with the error validation messages... For all required fields...
Of course, this is because those fields are empty and required... It seems like the view think I am posting back an invalid model... Is there a way to hide those validation message ?
Try clearing the model state if you intend to modify some of the values on your model in the POST action:
[Authorize]
public ActionResult Index(Candidat model)
{
if (model.LoginEmail == null)
{
model = null;
using (var db = new rhDB())
{
MembershipUser user = Membership.GetUser();
if (user != null)
{
ModelState.Clear();
model = (from m in db.Candidates where m.LoginEmail == user.Email select m).SingleOrDefault();
}
if (model == null)
{
ModelState.Clear();
model = new Candidat();
model.LoginEmail = user.Email;
model.Email = user.Email;
}
}
}
return View("MyProfileCandidate", model);
}
The reason for this is that HTML helpers will use model state values that were initially posted instead of those in the model. You could also clear individual fields in the model state: ModelState.Remove("FirstName");.

Lambda statement to project results based on if

I have a user who is part of a company, this company can have multiple offices, so the user can be part of head/main office or part of another office, I am projecting from a lamdba expression but I cannot figure out how to do: if user is not headoffice throw out the office address.
The code below shows user, joined onto userhistory (2 table join - which is necessary so I can throw out some info that is held on that table related to this user), here is what I have done so far:
[HttpPost]
[AjaxOnly]
public PartialViewResult GetUsers(string term)
{
var _sf = _repo.Single<Company>(x => x.Type == x.IsActive &&
(x.Identifier.Contains(term) || x.Name.Contains(term)));
//get company with addresses...
var users = _repo.All<User>().Where(x => x.CompanyID == _sf.CompanyID);
var offices = _repo.All<Office>().Where(x => x.CompanyID == _sf.CompanyID);
var _users = _repo.All<UserHistory>()
.Join(users, x => x.UserID, y => y.UserID,
(s, u) => new
{
_s = s,
_user = u
}).Select(x => new QuoteData
{
Login = x._user.Login,
Name = string.Format("{0} {1}", x._user.FirstName, x._user.LastName),
Tel = x._s.Mobile,
//let me know where user is based, if head office get me the address too...
IsBasedInHeadOffice = x._user.IsBasedInHeadOffice
//here: if !IsBasedInHeadOffice => GetMeAddress
});
return PartialView("QuoteUsersUC", _users);
}
public class QuoteData
{
public string Login { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Tel { get; set; }
public bool IsBasedInHeadOffice { get; set; }
}
Could I have written this even better/ simpler?
You can do it like this:
.Select(x =>
{
var result = new QuoteData
{
Login = x._user.Login,
Name = string.Format("{0} {1}", x._user.FirstName,
x._user.LastName),
Tel = x._surveyor.Mobile,
IsBasedInHeadOffice = x._user.IsBasedInHeadOffice
};
if(!result.IsBasedInHeadOffice)
result.Address = GetMeAddress();
return result;
});
UPDATE:
Using LINQ2SQL or EF, this should be a lot simpler because of the so called navigation properties. Basically, they remove the need for manual joins in your C# code, if your database is properly set up with foreign keys.
For example, if your table USER_HISTORY would have a foreign key constraint on the column user_id to the table USER, your class User would have a property UserHistories of type IEnumerable<UserHistory> that contains all associated user histories and the class UserHistory would have a property User. The same is true for all other associations: User <-> Company and Company <-> Office
Using this, your code could easily be rewritten to this:
public PartialViewResult GetUsers(string term)
{
var sf = _repo.Single<Company>(x => x.Type == x.IsActive &&
(x.Identifier.Contains(term) || x.Name.Contains(term)));
var users =
sf.Users
.SelectMany(x => x.UserHistories
.Select(y =>
new QuoteData
{
Login = x.Login,
Name = string.Format("{0} {1}",
x.FirstName,
x.LastName),
Tel = y.Mobile,
IsBasedInHeadOffice = x.IsBasedInHeadOffice
Address = x.IsBasedInHeadOffice ?
sf.Office.Address :
string.Empty
}));
return PartialView("QuoteUsersUC", _users);
}

Resources