Loading multiple related table data into MVC view. (Linq to SQL) - c#

I seem to be having a problem passing related data to my view. I'm creating a simple blog, which has a database architecture of three tables. These are Users, Posts and Comments. For my user profile page I wish to pass all the data from these tables into the user profile view. I used Linq to SQl to create my database model. Here is my attempt at passing in the data using Linq:
public ActionResult NewProfile()
{
var users = from u in db.UserModels
join p in db.PostModels on u.UserId equals p.UserId
where u.UserId == WebSecurity.CurrentUserId
select new Profile { UserModel = u, PostModel = p };
return View(users);
}
This is the code in the receiving view:
#model IEnumerable<MvcBlog.Models.Profile>
#{
ViewBag.Title = "NewProfile";
}
<h2>NewProfile</h2>
#foreach (var item in Model)
{
#item.UserModel.Username
}
Before I tell you what the result was, I think it's important to give you an idea about the data in my database. Currently, I only have three users. Only one of these users (gb201) has created any blog posts. The user gb201 has created a total of two blog posts.
When I run the program, and log in as user gb201, the data that is displayed on the NewProfile page is:
New Profile
gb201
gb201
The view is being passed the data from two tables, however, when I wish to query the user table, it is duplicating the data based on the number of posts for that user. So if a user only had one post, then it would only display once. For the two other users, who haven't posted anything, there is no information displayed on this page.
I'm assuming that my linq code which is querying the database is wrong, however, I can't seem to think what i'm doing wrong. Sorry for the long question, and any help would be grateful.

What you see is correct as you are building a new Profile per post.
If you simply want one user to be returned you should not join to Post and include the post data afterwards.
var users = db.UserModels.Where(u => u.UserId == WebSecurity.CurrentUserId )
.Include(u => u.Posts)
.ToList()
You can then read each user and then each post in a nested loop.
UPDATE: Now at pc
In your view
#model IEnumerable<MvcBlog.Data.UserModel>
#{
ViewBag.Title = "NewProfile";
}
<h2>NewProfile</h2>
#foreach (var item in Model)
{
#item.Username
//if you wanted the postes per user
foreach(var post in user.Posts)
{
#post.Title
}
}
It would be good practice to create a Model for your view, instead of using your data clas as I have.
UPDATE: To work with Linq to SQL
The above will build a viewmodel that you can use in your view.
var users = db.UserModels.Where(u => u.UserId == WebSecurity.CurrentUserId )
//.Include(u => u.Posts)
.Select(u => UserViewModel{ UserModel = u, Posts => u.Posts})
.ToList()

The answer of NinjaNye is right, but it needs to have the 'new' before the viewmodel call, like this:
var users = db.UserModels.Where(u => u.UserId == WebSecurity.CurrentUserId)
.Select(u => new UserViewModel{ UserModel = u, Posts => u.Posts })
.ToList();

Related

Using query results with joined parameters without anonymous type

Adapting from this C# MVC tutorial I'm using the following statement to return a specific user to the view
User user = db.Users.Find(id);
if (user == null){return HttpNotFound();}
return View(user);
The Users model has a foreign key to Auctions so the view can iterate over a list of auctions associated with this user.
Auctions has a field called buyerID which corresponds with User.ID so I want to display the buyers name by linking back to User.userName by way of the Auctions.buyerID.
I can do this in SQL statements and LINQ as well but I think there is a simpler way.
How can I use, User user = db.Users.Find(id); and join it to User from the foreign key of Auctions.buyerID?
This is kind of what I thought would work but I can't create a new parameter 'buyerName' when User doesn't have this.
IEnumerable<User> thisUser = from usr in db.Users
join auct in db.Auctions on usr.ID equals auct.UserID
join ur in db.Users on auct.buyerID equals ur.ID
where usr.ID == id
select new User
{
buyerName = ur.userName
};
From other posts like this one I read about setting up a new class but that doesn't seem to stick to the DRY & KISS principles since now I need to have two classes that vary by one parameter located in different locations.
Is there a simpler way to join the table and get the strong typing without creating a new class?
So the result should display the buyers name instead of the buyers ID#
Since I wanted to avoid using anonymous type and I didn't want to create a new class I found a work around by passing a dictionary of user names to the view.
Controller
User user = db.Users.Find(id);
var dict = db.Users.Select(t => new { t.ID, t.userName })
.ToDictionary(t => t.ID, t => t.userName);
ViewData["userDict"] = dict;
Now I just looked up the username based on the buyer ID
View
#{Dictionary<int, String> userList = ViewData["userDict"] as Dictionary<int, String>;}
#{
if (item.buyerID.HasValue)
{
#Html.DisplayFor(modelItem => userList[item.buyerID.Value])
}
}

C# class code with data from multiple tables

Hello and thanks taking your time to help me.
When a user login hes User id is stored in a session, and when he enters my Survey page. I want to the page to display the Surveys that is avalible to him.
My Database Diagram:
I tried to Write the code so First it checks if there is any relations between the user and the surveys in the RelationShip table. I have made sure that part works with debugging because it returns 1 since there is 1 user and one survey + the relation with the correct information.
But it cant write the last part like this: lstItem = db.Survey.Where(x => x.ID == relation).ToList(); so it returns the Correct Survey to my repeater. Since there might be more Surveys avaliple to a user.
My class Code:
public class Surveys
{
public static List<Survey> getAll(int id)
{
List<Survey> lstItem = new List<Survey>();
using (KONE_Entities db = new KONE_Entities())
{
List<User_Survey_Relation> relation = new List<User_Survey_Relation>();
relation = db.User_Survey_Relation.Where(i => i.UserID == id).ToList();
if (relation != null)
{
lstItem = db.Survey.Where(x => x.ID == relation).ToList();
}
}
return lstItem;
}
}
My C# code that binds it to the repeater:
private void BindSurvey()
{
int id = Convert.ToInt32(Session["UserID"].ToString());
rpSurveys.DataSource = Surveys.getAll(id);
rpSurveys.DataBind();
}
So to Clarify what Im asking for/ need help with: My Code will not return the Survey or Surveys that has a relation with the user that is logged on to the site.
Thanks for your time and I hope you can help me.
Compare individual relation values and then add the result to lstItem .
foreach(User_Survey_Relation relatSingle in relation){
lstItem.addRange(db.Survey.Where(x => x.ID == relatSingle.SurveyID).ToList());
}
}
Sorry...dude ...compared the whole class,i have edited it...please check if surveyId has to be compared or Id
You can see from the comments what is wrong with your logic. In terms of how best to solve it, I believe your best option is to join your tables in a single query rather than running 2 queries. The SQL syntax would be:
select s.*
from Survey s
inner join User_Survey_Relation usr on s.ID = usr.SurveyID
where usr.UserID = id
Translating this to LINQ becomes (this is rough - I don't have VS to test):
lstItem = (from db.Survey in survey
join db.User_Survey_Relation in relation on survey.ID
equals relation.SurveyID
where relation.UserID = id
select survey).ToList();
Like I say, you may need to play around with this to iron out any wrinkles. Hopefully you get the idea though.

Most efficient way to query a database and then remove entries from returned items

I have a app that allows you to follow peoples blog updates. I have a page where the user can choose who to follow. the people who can be followed are stored in a db ( Table name - Person) and when the user selects someone to follow that is also stored in the db ( Table name - Following).
The problem I have is when the user revisits the page to follow another person, what is the best way to query the database and only display people that the user is not following.
I am using Entity framework.
I have the following working. I need a where statement. followBloggers is returning a list of bloggers that the user is following and Uow.People.GetPeople() is returning all bloggers.
var followedBloggers = Uow.FollowBlogger.GetLinks(companyId).ToList();
return Uow.People.GetPeople().Select(p => new { p });
You could use something like this:
var AlreadyFollowed = currentUser.followBloggers.Select( f => f.Id);
Uow.People.GetPeople().Where( p => !AlreadyFollowed.Contains(p.Id));
Try this (assuming that Following has a FK to People on PersonID):
var followedIDs = user.followBloggers.Select(follow => follow.PersonID);
return Uow.People.GetPeople().Where(p => !followedIDs.Contains(p.PersonID));

Reading related data from two entities using LINQ

I have a one to many relationship of Users to Certificates. In my view I am able to see all data from Certificates and related data from Users. However this view gives me a repeat of UserID and is not effective. Please see this question here first.
In this view I used this query
var certUser = var cert = db.Certificates.Include(c => c.Users);
var AllcertUser = from s in certUser select s;
return View(AllcertUser.ToList());
Since UserID is distinct from this controller with this LINQ code:
var Allusers = from s in db.Users
select s;
return View(Allusers.ToList());
I get distinct Users from the code above. When I try to include from Certificates class, this is where I am failing to make it work. I need to include the Certificates so that I can have values from that entity which are related. I hope I made myself clear.
This is part of what I need. When Details are clicked the UserID must be passed and their details shown. At the moment I have hard coded id 23. How to pass the user id to the details view so that I get the certificates details.
public ActionResult Details(int id)
{
cpdEntities db = new cpdEntities();
var UserCerts = db.Certificates.Where(x => x.UserID == 23).ToList();
return View(UserCerts);
}
If I understand you correctly, you want to join tables and return all certificates grouped by users:
var query = from u in db.Users
join c in db.Certificates on u.UserID equals c.UserID into g
select new { User = u, Certificates = g };
Or you can pre-load Certificates (if lazy-loading is disabled):
var query = db.Users.Include(u => u.Certificates);
The reason you see multiple UserIds is because you are querying the Certificate entity, which as you said yourself, has a m:1 relationship with Users. If your view is a master-child type form and you want to see only one row per user, do the query the other way round:
var users = db.Users.Include(u => u.Certificates);
In your view you can iterate through each user's Certificate collection as required. For example, depending on the design of your view, you might just want to display certificates for one selected User.

Creating a join using a link table and the Entity Framework

Right now I am struggling with ASP.Net and MVC3 to display a person.
I have a table with the person information, a table for type of person.
A person can be multiple types.
So I created a link table that links the personid and typeid.
I am struggling to find a way to p[ush both the user details and the different types they are part of.
So the ActionResult is taking a PersonID and I can display the person information just fine, but I also need to pass a list of the types they are part of.
Any help, examples or links to a tutorial would be great. Thanks in advance.
Here is what I have right now in my controller.
public ViewResult Details(long id)
{
champion champion = _db.champions.Single(c => c.id == id);
return View(champion);
}
I tried using a ViewBag object and a join statement but it got too complex and went beyond my knowledge of linq statements.
http://msdn.microsoft.com/en-us/library/bb738708.aspx
var champion = _db.champions.Include("TheOtherModel").SingleOrDefault(c => c.id == id);
return View(champion);
and you should've access to "TheOtherModel" by using
champion.TheOtherModel
or in your view:
#foreach (var item in Model.TheOtherModel){
item.Property1
....
}
(should contain a list).

Categories