Reading related data from two entities using LINQ - c#

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.

Related

LINQ statment looks unreasonable to me: what's my error?

There has to be a better way:
public IList<ApplicationUser> GetProspects()
{
var UsrNames = Roles.GetUsersInRole("prospect");
IList<ApplicationUser> Users = new List<ApplicationUser>();
foreach ( var u in UsrNames)
{
// In theory should return at most one element.
var UserListFromQuery = from usr in (new ApplicationDbContext()).Users where usr.UserName == u select usr;
Users.Add(UserListFromQuery.ElementAtOrDefault(0));
}
return Users;
}
Can you please show me the error of my ways?
This should do what you want:
using (var context = new ApplicationDbContext())
{
var result = Roles.GetUsersInRole("prospect")
.Select(name => context.Users.FirstOrDefault(user => user.UserName == name))
.Where(user => user != null)
.ToList();
}
I've modified your code to utilize a using statement for the context, so as to ensure it is disposed of properly even if there is an exception. Then I have a linq query which does the following:
Get the usernames
For every username, select the first user in Users with a matching username
Remove all nulls from the resulting enumeration. This is necessary because FirstOrDefault returns null if no matching user is found
Turn our final enumeration into a List
I guess you could join it, then group it, then cull the grouping. I'm not sure whether there is an overall advantage of the front-loading that you'd be doing by joining and grouping, so you might want to put a stopwatch to this (and to your original code) and figure that one out.
My suggestion is:
// force a prefetch, rather than potentially slamming the server again and again
var allUsers = (new ApplicationDbContext()).Users.ToList();
// use the prefetched list to join & filter on
var result = from entitled in UsrNames
join user in allUsers
on entitled equals user.UserName
group user by user.UserName into grp
select grp.First();
return result.ToList();
Couple of thoughts:
This is clearly a user-related table. So I'm guessing that you're not going to have 100k records or something along that scale. At most, maybe thousands. So safe to cache in local memory, especially if the data will not change many times throughout the day. If this is true, you might even want to preload the collection earlier on, and store into a singular instance of the data, to be reused later on. But this observation would only hold true if the data changes very infrequently.

Using a Linq query to select objects where any value of a property matches values from a list

So I am trying to filter the modules object list by userID of the currently logged in user. After that I am trying to take the moduleID of those selected object.
Using that I want to filter the reports list to only those that contain a moduleID that matches any of the the previously obtained list of moduleID.
I'm not that particularly knowledgeable on Linq and this is what I came up with:
var name = User.Identity.GetUserName();
//gets the currently logged in user:
ApplicationUser currentUser =
(ApplicationUser)db.Users.Single(x => x.UserName == name);
//gets moduleID's for modules owned by current user:
var modules = (from i in db.Modules
where i.User == currentUser
select i.ModuleID);
var Reports = from u in db.Reports
where u.moduleID == modules
select u;
I'm having problems with the last portion trying to incorporate the contains method into the statement.
Any help would be appreciated.
You could simplify the multiple queries to one if I'm inferring your table structure correctly:
var name = User.Identity.GetUserName();
var reports = from r in db.Reports
join m in db.Modules on r.moduleID equals m.ModuleID
where m.User.UserName == name
select r;
You can't compare a int to a IEnumerable<int>. However you can check if the moduleID is found within the modules like this :
where modules.Contains(u.moduleID)
You're on the right track.
var Reports = from u in db.Reports
where modules.Contains(u.moduleID)
select u;
Or using lambda:
var reports = db.Reports.Where(u => modules.Contains(u));
You're close. In Linq its a little backwards from standard SQL. In its case you want to see if the report's module id is contained in the list of modules, so it would look like this:
var Reports = from u in db.Reports
where modules.Contains(u.moduleID)
select u;
Use the navigation properties if you have a one to many relation you could do directly in your Where clause:
var name = User.Identity.GetUserName();
var Reports = db.Reports.Where(x => x.Module.User.Name == name);

Obtain more information from the user via Inner join Linq

I supposed in the process developed is such that it must show all the movies that are into film tablen and showing off, but this is how I have tried to do this:
it must find out which genres have in users tablen where after to show the users who like the first.
//As I said, I have a session at the top of the code.
int brugerid = Convert.ToInt16(Session["id"]);
var result = (from f in db.films
//it must find out which genres have in users tablen where after to show the users who like the first.
//brugere are users
//gener It is the genes users like.
join usersgenerId in brugere.Fk_generId on gener.generId equals usersgenerId.BrugereId
select new
{
image_navn = ((f.imgs.FirstOrDefault(i => i.feature == true)).navn == null ? "default.png" : (f.imgs.FirstOrDefault(i => i.feature == true)).navn),
image_feature = f.imgs.Where(A => A.feature == true),
film_navn = f.navn,
film_id = f.filmId,
film_tekst = f.tekst,
film_gener = f.gener.navn
}).ToList();
RepeaterFilmList.DataSource = result;
RepeaterFilmList.DataBind();
Table information
Brugere the name
id = BrugereId
Fk_generId belonging to the genes that user has selected.
and many other
Gener is the name
has generId as id
As mentioned in the comment, the question really is: show all movies that is in the same genre that the user preferred and then show everything else.
Although the following approach might not be db efficient (too lazy to create the db for this, so I am simulating everything in memory and using Linq to Object to solve the issue), it can certainly be resolved by the following steps:
Get the recommendation (matching the user's movie genre preference) like so:
var recommendation =
from f in films
from ug in userGenres
where ug.UserId == user.Id && ug.GenreId == f.GenreId
select f;
Now that we know what the user preferred, we can further filter this to just the preferred films' Id... and use that to get the rest of the unpreferred films (basically anything not matching the preferred film Ids):
var recommendedFilmIds = recommendation.Select(f => f.Id);
var everythingElse =
from f in films
where !recommendedFilmIds.Contains(f.Id)
select f;
Finally, join them together using Union and injecting the nessary fields for display purpose like Genre.Name, etc. like so:
var filmList = recommendation.Union(everythingElse).Select(f => new {
f.Id,
f.Title,
Genre = genres.Where(g => g.Id == f.GenreId).Select(g => g.Name).First()
});
And there you have it, the combined list will now contains both preferred films first (at top), followed by unpreferred films afterward.
The simulated tables are as follows: films which contains its own Id and genreId and userGenres which contains many to many relationship between user and genre and a particular user object which contains the user id.
An example of this can be found at: https://dotnetfiddle.net/Skuq3o
If you use EF, and you have a navigation property to genre table and you want to include those table as part of the query, use .Include(x => x.genre) or whatever you call your genre table after from f in films to avoid n+1 select if you wish to include the genre info in the final select clause.

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])
}
}

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

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();

Categories