Linq - Join collection with child collection of other collection - linq

I have a collection of users, each with a child collection of memberships. And then I also have a club collection. The child collection of each user is populated with memberships containing a club ID only. What I want is for each membership to also contain the club entity matching the club ID.
My entities:
public class User
{
public string Name { get; set; }
public IEnumerable<Membership> Memberships { get; set; }
}
public class Membership
{
public Guid ClubId { get; set; }
public Club Club { get; set; }
}
public class Club
{
public Guid Id { get; set; }
public string Name { get; set; }
}
Populated with data:
var club1Id = new Guid("FFEB77B9-1616-463B-B36E-F95F7E255FDE");
var club2Id = new Guid("7A6ECD38-5AEB-418B-9DD5-FBD777CE190C");
var users = new List<User>
{
new User
{
Name = "Patrick Stewart",
Memberships = new List<Membership>
{
new Membership { ClubId = club1Id },
new Membership { ClubId = club2Id }
}
},
new User
{
Name = "Brent Spiner",
Memberships = new List<Membership>
{
new Membership { ClubId = club1Id }
}
},
new User
{
Name = "Wesley Crusher",
Memberships = null
}
};
var clubs = new List<Club>
{
new Club
{
Id = club1Id,
Name = "Officers Club"
},
new Club
{
Id = club2Id,
Name = "Captains Club"
}
};
And then I need a LINQ query that adds the matched Club objects to the Membership objects.
So that when I print out users like this:
foreach (var user in users)
{
Console.WriteLine($"User: {user.Name}");
if (user.Memberships != null)
{
foreach (var membership in user.Memberships)
{
Console.WriteLine($" Membership: {membership.Club?.Name}");
}
}
}
It will look like this:
User: Patrick Stewart
Membership: Officers Club
Membership: Captains Club
User: Brent Spiner
Membership: Officers Club
User: Wesley Crusher
I want to do it in one go with a LINQ expression, so that I don't have to involve for-loops.

I ended up solving it with this LINQ expression:
users = users.Select(user => new {
user,
Memberships = user.Memberships?
.GroupJoin(clubs, membership => membership.ClubId, club => club.Id, (membership, club) => new { membership, club})
.SelectMany(mc => mc.club.DefaultIfEmpty(), (mc, club) =>
{
mc.membership.Club = club;
return mc.membership;
})
})
.Select(x =>
{
x.user.Memberships = x.Memberships;
return x.user;
})
.ToList();

LINQ is not intended to be used for modifying data, rather querying data and/or creating new data from data.
Instead, use a nested foreach loop.
First, create a Dictionary to make finding Club objects easier:
var clubDict = clubs.ToDictionary(c => c.Id);
Then, use the clubDict to modify each Membership to include the matching Club entity:
foreach (var user in users.Where(u => u.Memberships != null))
foreach (var membership in user.Memberships)
membership.Club = clubDict[membership.ClubId];

Related

Linq query to select any in list against a list

Using EF Core code-first, and I want to find any record with a similar list of a foreign entities to the entity I already have.
public class ClownModel {
public int Id { get; set; }
public List<CarModel> Cars { get; set; }
}
public class CarModel {
public int Id { get; set; }
}
var MyClown = new ClownModel() { /*add properties*/ }
//or maybe an existing record selected from database, just some ClownModel instance
Basically, "Select all the ClownModels where they have any Cars.Id that are in my MyClown.Cars"
Assuming that ClownModel has unique CarModel Id's, you can use the following query:
Matches All Ids
var ids = MyClown.Cars.Select(c => c.Id).ToList();
var query =
from cm in ctx.ClownModel
where cm.Cars.Where(c => ids.Contains(c.Id)).Count() == ids.Count
select cm;
Matches Any Ids
var ids = MyClown.Cars.Select(c => c.Id).ToList();
var query =
from cm in ctx.ClownModel
where cm.Cars.Where(c => ids.Contains(c.Id)).Any()
select cm;

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

MVC populate dropdown from foreign key

I have been struggling with this for several days. I need to populate a dropdownlistfor with genres.
My MovieRepository to grab the genres:
public IQueryable<Movies> MoviesAndGenres
{
get { return db.Movies.Include(m => m.parentGenre); }
}
My movie model
public virtual Genres parentGenre { get; set; }
Genre Model:
public class Genres
{
public Genres()
{
this.movies = new HashSet<Movies>();
}
[Key]
public int genreId { get; set; }
[Required(ErrorMessage = "A genre name is required")]
[StringLength(25)]
public String genreName { get; set; }
public ICollection<Movies> movies { get; set; }
}
I am trying to pass in the genres with a select list, but I am getting a LINQ to Entities does not recognize the System.String To String() Method, and this method cannot be translated to a stored expression.
Movies Controller, addMovie action:
ViewBag.Genres = movieRepository.MoviesAndGenres.Select(m => new SelectListItem
{
Text = m.parentGenre.genreName,
Value = m.parentGenre.genreId.ToString()
}).ToList();
return View();
View:
#Html.DropDownListFor(m => m.parentGenre, (SelectList)ViewBag.Genres)
Any help would be greatly appreciated!
Update:
Repository:
public IQueryable<Genres> MoviesAndGenres
{
get { return db.Genres; }
}
Controller:
var x = movieRepository.MoviesAndGenres.Select(m => new
{
Text = m.genreName,
Value = m.genreId
});
ViewBag.Genres = new SelectList(x);
return View();
View:
#Html.DropDownListFor(m => m.parentGenre, (SelectList)ViewBag.Genres)
Since you're retrieving all of the records anyways, you can just do this.
ViewBag.Genres = movieRepository.MoviesAndGenres.AsEnumerable()
.Select(m => new SelectListItem
{
Text = m.parentGenre.genreName,
Value = m.parentGenre.genreId.ToString()
});
You would also need to change your view to:
#Html.DropDownListFor(m => m.parentGenre, new SelectList(ViewBag.Genres))
Actually, a better approach would probably be this, since then it only retrieves the specific columns you need:
var x = movieRepository.MoviesAndGenres.Select(m => new
{
Text = m.parentGenre.genreName,
Value = m.parentGenre.genreId
});
ViewBag.Genres = new SelectList(x)
Also, the ToList() is no longer required because it's already in a an immediate state.

Entity Framework Code First and populating join tables

I been practicing with EF Code First, SQL Express, and ASP.Net MVC3.
When I run the website first the correct tables are generated by the FooInitializer and Student and Image are populated but for some reason the join table (StudentImages) is not being populated.
What could be the issue?
Tables: Student, Image, and StudentImages
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Image> Images { get; set; }
}
public class Image
{
public int Id { get; set; }
public string Filename { get; set; }
public string Extension { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
public class FooInitializer : DropCreateDatabaseIfModelChanges<DBContext>
{
protected override void Seed(DBContext context)
{
var students = new List<Student> {
new Student { Id = 1, Name = "John" },
new Student { Id = 2, Name = "Jane" }
};
students.ForEach(s => context.Students.Add(s));
context.SaveChanges();
var images = new List<Image> {
new Image { Id = 1, Filename = "IMG_4596.JPG", Extension = ".jpg" },
new Image { Id = 2, Filename = "IMG_4600.JPG", Extension = ".jpg" }
};
images.ForEach(i => context.Images.Add(i));
students[0].Images.Add(images[0]);
context.SaveChanges();
}
}
From what I can tell your Image class does not have a reference to the StudentID. Try adding:
public int StudentID { get; set; }
to the Image class maybe?
Also having an ICollection would mean that one image could have multiple students - is this correct? Maybe it should be a public virtual Student Student {...}
EDIT: Also I found this, with a many to many relationship (if thats what you need):
In your OnModelCreating() Method:
modelBuilder.Entity<Student>()
.HasMany(c => c.Images).WithMany(i => i.Students)
.Map(t => t.MapLeftKey("StudentId")
.MapRightKey("ImageID")
.ToTable("StudentImages"));
taken from this link that states:
A many-to-many relationship between the Instructor and Course
entities. The code specifies the table and column names for the join
table. Code First can configure the many-to-many relationship for you
without this code, but if you don't call it, you will get default
names such as InstructorInstructorID for the InstructorID column.
EDIT: Here is the code I used the other night, with my implementation of the code first MVC site:
var users = new List<User>
{
new User { UserID = new Guid(), Email = "me#me.com", LastOnline = DateTime.Now, Password = "pword", RegistrationDate = DateTime.Now, SecurityAnswer = "me", SecurityQuestion = "who?", Roles = new List<Role>() },
};
users.ForEach(s => context.Users.Add(s));
context.SaveChanges();
var roles = new List<Role>
{
new Role { RoleID = "Admin", Description = "Administration Users", Users = new List<User>() }
};
roles.ForEach(r => context.Roles.Add(r));
users[0].Roles.Add(roles[0]);
context.SaveChanges();
var userLicense = new List<UserLicense>
{
new UserLicense { AddDateTime = DateTime.Now, LicenseType = "Farmer", Manufacturer = "Motorola", Model = "Droid", PhoneIdentifier = "c0e4223a910f", UserID = users[0].UserID, User = new User() }
};
userLicense[0].User = users[0];
userLicense.ForEach(u => context.UserLicenses.Add(u));
context.SaveChanges();
userLicense[0].User = users[0];
context.SaveChanges();
Notice in each instantiated item, I am also instantiating a new referenced item within the parent object.
EDIT:
Ok try this:
var students = new List<Student> {
new Student { Id = 1, Name = "John", Images = new List<Image>() },
new Student { Id = 2, Name = "Jane", Images = new List<Image>() }
};
students.ForEach(s => context.Students.Add(s));
context.SaveChanges();
var images = new List<Image> {
new Image { Id = 1, Filename = "IMG_4596.JPG", Extension = ".jpg", Students = new List<Student>() },
new Image { Id = 2, Filename = "IMG_4600.JPG", Extension = ".jpg", Students = new List<Student>() }
};
images.ForEach(i => context.Images.Add(i));
students[0].Images.Add(images[0]);
students[1].Images.Add(images[1]);
context.SaveChanges();
Try adding this before saving changes for each student:
foreach (Image i in s1.Images)
context.ObjectStateManager.ChangeObjectState(i, System.Data.EntityState.Added);
Also try with System.Data.EntityState.Modified.
Hope this works...

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