SelectListItem checkboxes and autopopulate with using Contains() - c#

I have a list of courses. If a user is assigned to a course, then I want that checkbox to be checked. How come the Contains() is not accepted?
ViewModel:
public class ViewUserViewModel
{
public List<Cours> Courses { get; set; }
public List<UserCours> UserCoursesList { get; set; }
public AspNetUser user { get; set; }
public IEnumerable<SelectListItem> CourseList { get; set; }
}
Controller:
[HttpGet]
public ActionResult ViewUser(string id)
{
ViewUserViewModel model = new ViewUserViewModel();
model.user = db.AspNetUsers.FirstOrDefault(U => U.Id == id);
//List all courses
List<Cours> allCourses = db.Courses.OrderBy(c => c.CourseName).ToList();
model.Courses = allCourses;
//List of courses the user is assigned to
//var selectedCourse1 = db.UserCourses.Where(uc => uc.UserId == id).ToList();
model.UserCoursesList = db.UserCourses.Where(uc => uc.UserId == id).ToList();
//checkbox list
model.CourseList = allCourses.ToList().Select(x => new SelectListItem()
{
//Selected = selectedCourse1.Contains(x.CourseID),
Selected = model.UserCoursesList.Contains(x.CourseID),
Text = x.CourseName,
Value = x.CourseID.ToString()
});
}
I'm thinking the Selected property will test if List has the value assigned, then it would return back true. Instead, it's a syntax error and has invalid arguments. How can I compare the CourseList to the UserCoursesList?

I think you should use LINQ Any method. You not posted you UserCours class definition, but I guess that it should have an ID, and might look like that:
public class Cours
{
public int Id { get; set; }
// Other properties
}
In this case your check for Selected property will be:
Selected = model.UserCoursesList.Any(uc => uc.CourseID == x.CourseID)
Some tips:
In this case it will be better to use some meaningful name instead of
x, for example course. It will improve readability of the code.
allCourses is already a list, you do not need to call ToList()
method again.

Updated code: thanks to Aleksandr
[HttpGet]
public ActionResult ViewUser(string id)
{
ViewUserViewModel model = new ViewUserViewModel();
//Which user
model.user = db.AspNetUsers.FirstOrDefault(User => User.Id == id);
//List all courses
model.Courses = db.Courses.OrderBy(Courses => Courses.CourseName).ToList();
//List of courses the user is assigned to
model.UserCoursesList = db.UserCourses.Where(UserCourses => UserCourses.UserId == id).ToList();
//checkbox list
model.CourseList = model.Courses.Select(Course => new SelectListItem()
{
Selected = model.UserCoursesList.Any(UserCourse => UserCourse.CourseId == Course.CourseID),
Text = Course.CourseName,
Value = Course.CourseID.ToString()
});
return View(model);
}

Related

C# Dropdown is returning different default values for different items

I have a get page that renders the edit page that looks like this
public ActionResult EditItemInstance(int id)
{
ItemInstance i = db.ItemInstances.Find(id);
var item = (from it in db.Items.Where(x => x.deleted == false)
select new
{
itemID = it.ID,
itemName = it.ItemID + ": " + it.Name
}).OrderBy(x => x.itemName).ToList();
ViewBag.ItemID = new SelectList(item, "itemID", "itemName", i.ItemID);
return View(i);
}
And in my view page I have a dropdown list that looks like this
#Html.DropDownList("ItemID", null, "-- Select --", htmlAttributes: new { #class = "form-control chosen-select" })
I want the default value to be the value of the current item I am editing. For most of the items this works correctly. But when I edit some items I get a default value of '-- Select -- '
Why is the default value working for some items but coming up as 'select' for others?
ViewBag and ViewData is not the best option to use. If you want to refactor this code soon - it will be hard to track changes you should make.
Use it only if there is no any other option. However I cannot think about case like that.
Imagine that your model contains property SelectedItem of type SelectListItem and Items of type SelectList
Then use DrodownListFor
#Html.DropDownListFor(m => m.SelectedItem, Model.Items)
Yea don't use ViewBag and ViewData to pass around data if you don't have to. Use ViewModel instead because it's strongly-typed and you don't have to cast it on your view, and you can declare additional properties to suit your needs.
Again, I am not sure the relationship between your ItemInstance and the list of Items coming back from the database, but from what you're trying to do, I am guessing there is a dropdown on the side of your edit page, and whatever the current item instance being edited would be selected on the dropdown?
public class EditItemInstanceViewModel
{
public IEnumerable<ItemOptionViewModel> AvailableItemOptions { get; set; }
public ItemInstanceViewModel ItemInstance { get; set; }
}
public class ItemOptionViewModel
{
public int ItemId { get; set; }
public string ItemName { get; set; }
}
public class ItemInstanceViewModel
{
public int ItemInstanceId { get; set; }
public string ItemInstanceName { get; set; }
// ... there might be more properties
}
Then in your controller, you can fill EditItemInstanceViewModel like this:
public ActionResult EditItemInstance(int id)
{
ItemInstance itemInstance = db.ItemInstances.Find(id);
if (itemInstance == null)
{
return HttpNotFound();
}
var availableItemOptions = (from it in db.Items.Where(x => x.deleted == false)
select new ItemOptionViewModel
{
ItemID = it.ID,
ItemName = it.ItemID + ": " + it.Name
})
.OrderBy(x => x.ItemName)
.ToList();
var vm = new EditItemInstanceViewModel
{
AvailableItemOptions = availableItemOptions,
ItemInstance = new ItemInstanceViewModel
{
ItemInstanceId = itemInstance.Id,
ItemInstanceName = itemInstance.Name
}
};
return View(vm);
}
Then on the view:
#model EditItemInstanceViewModel
#{
}
...
#Html.DropdownList("selected-item-id",
<!-- Enumerable items; Dropdown value field; Dropdown text field; Selected value; -->
new SelectList(Model.AvailableItemOptions, "ItemInstanceId", "ItemInstanceName", Model.ItemInstance.ItemInstanceId),
"-- Select --",
new { #class = "form-control chosen-select" })
...
Nice and clean!

Update a value with linq

I'm trying to update the Selectedproperty of an IEnumerable<SelectListItem> for a MVC-Combobox website using linq. However this is not working, as shown in the debbuging result: The Count() for the criteria returns an item, however the Count()for .Selected == truereturns 0.
public IEnumerable<SelectListItem> Categories { get; set; }
public CategoryModel Category
{
get { return category; }
set
{
category = value;
Categories.Where(x => x.Value == value.Id.ToString()).First().Selected = true;
}
//Debugging Results
//?Categories.Where(x => x.Value == value.Id.ToString()).Count()
//1
//?Categories.Count(x => x.Selected == true);
//0
}
Update:
I guess the problem is more bound to the IEnumerable<SelectListItem>, because the after changing Categories to an ObservableCollection it works fine (example below), even though LinQ is not designed for changing data....
System.Diagnostics.Debug.Print(Categories.Where(x => x.Id == value.Id).FirstOrDefault().Description);
Categories.Where(x => x.Id == value.Id).FirstOrDefault().Description = "Stackoverflow";
System.Diagnostics.Debug.Print(Categories.Where(x => x.Id == value.Id).FirstOrDefault().Description);
LINQ is to query your data-source not to modify it.
Your current approach has a drawback anyway, you would select one but you would not deselect the others. So you need a loop:
public CategoryModel Category
{
get { return category; }
set
{
category = value;
// consider to use a lock here to avoid multi threading issues
foreach(SelectListItem catItem in Categories)
catItem.Selected = catItem.Value == value.Id.ToString();
}
}
I would use a method SetSelectedCategory instead of a property if i'd modify a collection.
IEnumerable does not guarantee that changes get persisted across enumerations.
It all depends on the underlying implementation in the end (List, Array, Observable, etc).
Among the options that you have is to change your actual Categories to a writable collection (like List)...
But you might not be able to do that, or you might simply prefer to stay lean and keep using the IEnumerable.
In that case you could simply mutate the original collection and project it over the original
void Main()
{
Categories = Load();
var active = new Func<CategoryModel, int, CategoryModel>((category, match) =>
{
return new CategoryModel
{
Id = category.Id,
Name = category.Name,
Active = category.Id == match
};
});
Categories = Categories.Select(p => active(p, 2));
Categories.Dump();
}
public IEnumerable<CategoryModel> Categories { get; set; }
public IEnumerable<CategoryModel> Load()
{
yield return new CategoryModel { Id=1, Name = "one" };
yield return new CategoryModel { Id=2, Name = "two" };
yield return new CategoryModel { Id=3, Name = "three" };
}
public class CategoryModel
{
public int Id { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
}
Id|Name|Active
1 one False
2 two True
3 three False
this is also to emphasize that you can use linq for "transformations" using "projections"

LINQ to Entities, Where Any In

How to write 'Where Any In' in LINQ to Entity?
Here is my model :
class Chair
{
public int Id { get; set; }
public int TableId { get; set; }
public Table Table { get; set; }
}
class Table
{
public int Id { get; set; }
public ICollection<Chair> Chairs { get; set; }
public ICollection<Category> Categories { get; set; }
public Table()
{
Chairs = new List<Chair>();
Categories = new List<Category>();
}
}
class Category
{
public int Id { get; set; }
public ICollection<Table> Tables { get; set; }
}
I also got a simple list of Category :
List<Category> myCategories = new List<Category>(c,d,e);
I want to get only that Chairs that belongs to Table that got one of the Category from myCategories List. Thats what im trying to do :
var result =
ctx.Chairs.Where(x => x.Table.Categories.Any(y => myCategories.Any(z => z.Id == y.Id))).ToList();
I think its ok but what i get is error :
"Unable to create a constant value of type 'ConsoleApplication1.Category'. Only primitive types or enumeration types are supported in this context"
Try to compare with in-memory categories Ids collection, instead of categories collection.
var myCategoriesIds = myCategories.Select(c => c.Id).ToArray();
var result =
context.Chairs
.Where(
x => x.Table.Categories.Any(
y => myCategoriesIds.Contains(y.Id)))
.ToList();
this is because ctx.Chairs is a collection that is in database, you should retrieve that collection first in order to compare it with in-memory data:
var result = ctx
.Chairs
.AsEnumerable() // retrieve data
.Where(x =>
x.Table.Categories.Any(y =>
myCategories.Any(z => z.Id == y.Id)))
.ToList();
EDIT: that wouldn't be the correct thing to do if you have a lot of entities on database, what you can do is to split it into two queries:
var tables = ctx.Tables
.Where(x =>
x.Categories.Any(y =>
myCategories.Any(z => z.Id == y.Id)));
var result = ctx.Chairs
.Where(x =>
tables.Any(t=> t.Id == x.TableId))
.ToList();
You can select Ids from myCategories and use it last statement.
var CategoryIds = myCategories.Select(ct => ct.Id);
var result = ctx.Chairs.Where(x => x.Table.Categories.Any(y => CategoryIds.Any(z => z == y.Id))).ToList();

How to Specify SelectedValue for SelectList?

In my MVC Project, I have a Course model looks like this:
public class Course
{
[Key]
public int CourseId { get; set; }
[Required, MaxLength(50)]
public string Name { get; set; }
// bunch of other properties
}
I'm creating a SelectList for some Courses in my Action
var user = context.Users.Include("Courses")
.FirstOrDefault(x => x.Email == User.Identity.Name);
var courses = user.Courses.OrderBy(x => x.Name);
ViewBag.Courses = new SelectList(courses, "CourseId", "Name");`
I have a nullable courseId parameter in my Action like this:
public ActionResult Index(int? courseId)
If it's not null, I want to change the default selected value to that course.In order to do this I tried:
if (courseId != null)
{
var selectedCourse = courses
.FirstOrDefault(x => x.CourseId == courseId.Value);
if (selectedCourse != null)
{
ViewBag.Courses = new SelectList(courses, "CourseId", "Name", selectedCourse.CourseId);
}
}
But it doesn't work and selectedCourse is not null.Instead of specified Course I see the first course in my list selected every time.What am I missing ?
Edit: I'm creating DropdownList in my View like this:
#Html.Bootstrap().DropDownList("Courses", (SelectList)ViewBag.Courses)
You should send the selected value via your model, but, if you want to send it via viewbag, please try like so:
#Html.DropDownListFor(model => Model.CourseId,
new SelectList(ViewBag.Courses, "Value", "Text", #ViewBag.CourseId))
Please see my original answer to a similar question here for more info https://stackoverflow.com/a/16799915/1477388
If any answers solve your problem. Try it
put your selected value into a ViewBag.Seleted
$("#DropDownId option").each(function(){
if($(this).val() == "ViewBag.Selected"){
$(this).attr("selected","selected");
}
});

Linq "join" with a IList<T> getting "Error Unable to create a constant value.."

I have a Save Method that saves with a Linq query a manually re-orderd list (in a web form) that is passed as the parameter to my method, and I try to update the Order Property of the IEnumerable<VM_CategoryLabel> I retrieve from the database (EF) with the corresponding value in the list (maybe would that be clearer with my code below):
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
//Order = x.Sequence_Cat,
Order = (int)listTemplate.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
I used the "test" var to see if my "sub-query" gets the correct value, and it does, but when I use my Linq expression inside the Select (the commented Order line), I get the following error:
Unable to create a constant value of type 'Namespace.Models.VM_CategoryLabelExtra. "Only primitive types and enumeration types are supported in this context.
Here are my classes:
public class VM_CategoryLabel
{
public int Id { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
public class VM_CategoryLabelExtra
{
public int Id { get; set; }
public int IdCat { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
So I suppose that I should not query the list inside my query ? So how do I "match" the 2 lists of values ?
I also tried the following (after having replace in the Linq query: Order = x.Sequence_Cat)that is not working neither because the iteration variable is
read-only:
foreach (var item in requete)
{
item.Order = listTemplate.Where(x => x.Id == item.Id).Select(x => x.Order).FirstOrDefault();
}
try
{
context.SaveChanges();
I suggest using this.
It is the let clause.
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
let list = listTemplate
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
Order = list.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
edit: instead offrom you can just do let list = listTemplate
Should work now :)
example for let:
// The let keyword in query expressions comes in useful with subqueries: it lets
// you re-use the subquery in the projection:
from c in Customers
let highValuePurchases = c.Purchases.Where (p => p.Price > 1000)
where highValuePurchases.Any()
select new
{
c.Name,
highValuePurchases
}
If you do not know how Let working than please download LinqPad and see an example

Categories