Linq Comparing Two Lists and Generating a New One - c#

I am Working on a MVC4 project and I am in need to create a new list of objects by comparing two lists. db below refers to the DbContext object.
In the project that I work, I am in need of comparing two lists. The first has all the subjects offered in a semester. The other has the subjects selected by the student.
List<Subject> subjects = db.Subjects;
//...
List<Subject> semesterSubjects = student.Subjects;
Out of this two lists I need to create a list of items that state if or not each an every item is selected. It looks like this.
class SubjectSelection
{
public int ID { get; set; }
public String Title { get; set; }
public bool IsSelected { get; set; }
}
What I need now is to compare subjects & semesterSubjects and get a IEnumerable<SubjectSelection>
Could I use Linq to get such a result and how?

Best way is to do this job on the server side. Select ids from your semester subjects:
var selectedIDs = semesterSubjects.Select(s => s.ID).ToList();
And pass them to query:
var query = db.Subjects.Select(s => new SubjectSelection()
{
ID = s.ID,
Title = s.Title,
IsSelected = selectedIDs.Contains(s.ID)
});
If you are using same db context instance to retrieve both semester subjects list and subjects list, then you can simply use:
var query = subjects.Select(s => new SubjectSelection()
{
ID = s.ID,
Title = s.Title,
IsSelected = semesterSubjects.Contains(s)
});
Entity Framework implements identity map pattern, which means it returns same in-memory instance of subject object, which returned by different queries.

var allSubjectsWithSelection = subjects.Select(o=>new SubjectSelection(){ID = o.ID,Title = o.Title, IsSelected = semesterSubjects.Any(s=>s.ID == o.ID)}).ToList();

Related

How to store multiple rows in one variable? C# Entity Framework

public static List<TruckWithModel> GetAllTrucks()
{
using (DAD_BaldipContext ctx = new DAD_BaldipContext())
{
var x = ctx.TruckFeatureAssociation
.Include(t => t.Truck)
.Include(tf => tf.Feature)
.Include(tm => tm.Truck.TruckModel)
.Select(it => new TruckWithModel()
{
Colour = it.Truck.Colour,
Size = it.Truck.TruckModel.Size,
RentalPrice = it.Truck.DailyRentalPrice,
Status = it.Truck.Status,
Model = it.Truck.TruckModel.Model,
Rego = it.Truck.RegistrationNumber,
Features = it.Feature.Description
}) ;
return (List<TruckWithModel>)x.ToList();
}
}
This code retrieves the various attribute values from the relative tables TruckFeatureAssociation, TruckFeature, IndividualTruck and TruckModel.
The trouble I'm having is that the TruckFeatureAssociation has up to 5 entries for the same truck, this table is a junction table between IndividualTruck and TruckFeature where TruckFeature is a table of various features.
For each TruckFeatureAssociation a different object of TruckWithModel is created i.e. if there are 3 features associated each truck has three rows displayed in the datagrid where I call this function.
I want it so that all the features can be stored in one object.
So in the above output I would want, only one row, saying alarm systems, chrome wheels.
The issue here is that you are querying against Features, but the model reflects a Truck... Query a truck, get it's features, then let your view model (TruckWithModel) help format that data for the view..
For example:
[Serializable]
public class TruckWithModel
{
public string Colour { get; set; }
public string Size { get; set; }
public decimal RentalPrice { get; set; }
public string Status { get; set; }
public string Model { get; set; }
public List<string> Features { get; set; } = new List<string>();
public string FormattedFeatures
{
get { return string.Join(", ", Features); }
}
}
Now when you query the data:
var trucks = ctx.Trucks
.Select(t => new TruckWithModel()
{
Colour = t.Colour,
Size = t.TruckModel.Size,
RentalPrice = t.DailyRentalPrice,
Status = t.Status,
Model = t.TruckModel.Model,
Rego = t.RegistrationNumber,
Features = t.Features.Select(f => f.Description).ToList()
}).ToList();
This assumes that Truck has a Collection of Features where TruckFeatureAssociation is just a mapping entity. If your Truck's collection is based off TruckFeatureAssociation:
Features = t.Features.Select(f => f.Feature.Description).ToList()
Now in your view, where you want to display the features, bind to the FormattedFeatures property to get the comma-delimited list of Features for each Truck.
Note that when you use Projection through .Select() you do not need to use .Include() to eager load related entities. EF can work out what to load to satisfy the projection automatically.

Create DropDownList with OptGroup

I'm attempting to create a drop down list from one table in my database grouped by names from a different table in the same database.
What I have is a table that holds a bunch of units. These units belong to parent units. The tables are joined on the parent units id. So for the Units table, there's a property Parent, data type int. The ParentUnit table has an Id data type int, and a Name.
What I want to do is create a drop down list with all the Units group together by the Parent's Name. Unfortunately, this needs to happen inside the Controller and NOT the Model.
The other peace is, the drop down list is part of a workorder request form. So in the Workorder Controller, I need to create a SelectList with the optionGroup of Name of the Parent Unit.
How do I go about doing this?
This is what I'm thinking:
ViewBag.UnitId = new SelectList(db.Units.ToList(), "Id", "Name", ParentUnit.Name);
but I don't know how to implement it since I'm new to MVC.
When I tried ViewBag.UnitId = new SelectList(db.Units.ToList(), "Id", "Name", "Parents", 1);, the IDs of the Parents showed up in the optGroup, I'm guessing because the Id of the ParentUnit is stored in the Units table. How can I get the Name of the Parent to show up?
Based on my experience, First, it's better to change your model,
So you can have both tables (models) in one model with self-relation, for example, your model can be:
public class Model_Name
{
public int Id { get; set; }
public string Title { get; set; }
public int ParentId { get; set; }
public Model_Name model_name { get; set; }
public ICollection<Model_Name> Model_Names{ get; set; }
}
And then implement your view model layer (DTO) as below;
public class View_Model_Name
{
public int Id {get;set;}
public string Title {get;set;}
public int ParentId {get;set;}
public int ParentTitle {get;set;}
}
Then for the implementation option group, you need to get a Dictionary of data, same as below:
public Dictionary<long,List<View_Model_Name>> GetGroupAndChilds()
{
var result = _yourcontext.Model_Name
.Select(x => new View_Model_Name
{
ID = x.ID,
Title = x.Title,
ParentId = x.ParentId,
ParentTitle=x.Model_Names.Title,
}).AsEnumerable().GroupBy(x => x.ParentId).ToList();
return result.ToDictionary(k => k.Key, v => v.ToList());
}
Now you have a list of your data and you can use it in the presentation layer (Razor Page) as below:
public List<SelectListItem> showlist = new List<SelectListItem>();
public List<SelectListItem> GetList()
{
var allList= GetGroupAndChilds();
foreach (var (key, value) in allList)
{
var parentTitle = GetDetails(key).Title; //you can write a function to get title from Id
var group = new SelectListGroup() { Name = parentTitle };
foreach (var per in value)
{
var item = new SelectListItem(per.Title, per.ID.ToString())
{
Group = group
};
showlist.Add(item);
}
}
And finally, add the below tag to your cshtml (view) page;
<select asp-for="Your_Page_Model.YourItem" asp-items="Model.showlist"></select>
Finish

Merge items in list with the same key/property

This shouldn't be very hard to do but I'm struggling to find out the correct way to do this.
I have two lists that have the same properties. What I would like to do is merge these two lists together and combine/merge the individual items in the lists that have the same key/property.
Here is my ViewModel:
public class GradeViewModel {
public int GradeId { get; set; }
public string Name { get; set; }
public int Total { get; set; }
}
And here I am populating my lists:
var buyPostGrades = Mapper.Map<IEnumerable<BuyPostGrade>, IEnumerable<GradeViewModel>>(_reportService.GetBuyPostGrades());
var sellPostGrades = Mapper.Map<IEnumerable<SellPostGrade>, IEnumerable<GradeViewModel>>(_reportService.GetSellPostGrades());
Now what I would like to do is combine the two lists together into one and if there are any items between the lists that share the same GradeId then I want to merge them into just one item.
So, for example, if I had an item in my buyPostGrades list that looked like the following:
GradeId = 6,
Name = "TestGrade",
Total = 4
and then I had an item in my sellPostGrades list that looked like the following:
GradeId = 6,
Name = "TestGrade",
Total = 10
then I would like to merge those two items into one that would look like:
GradeId = 6,
Name = "TestGrade",
Total = 14
I'm sure there in some Linq that I can use to do this but I'm still new to it and am not sure which way would be best.
Assuming that identical grade ID implies identical name, you can merge two lists as follows:
var res buyPostGrades
.Concat(sellPostGrades)
.GroupBy(x => new {x.GradeId, x.Name})
.Select(g => new GradeViewModel {
GradeId = g.Key.GradeId
, Name = g.Key.Name
, Total = g.Sum(x => x.Total)
}).ToList();

Get items for list

I got the following documents:
public class TreeNode
{
string Id;
string Owner; //"users/1"
string TodoListId; //"todolists/1"
string ParentId; //"treenodes/1"
}
public class TodoList
{
string Id;
List<TodoItem> Items;
}
public class TodoListItem
{
bool IsCompleted;
}
How can I fetch all items for the current user which has not completed? Should I redesign any of the documents?
I want something like:
from all treenodes belonging to the current user
load all todolists
and return all active items within those lists
But within one server roundtrip
Update 2
Here is how I tried to do it with two queries (SelectMany is not supported):
var todoListIds = _dbSession.Query<UserTreeNode>()
.Where(x => x.UserId == user.Id)
.Select(x => x.TodolistId);
var nodes = _dbSession.Query<Todolist>()
.Where(x => x.Id.In(todoListIds))
.SelectMany(x => x.Items.Where(item => !item.IsCompleted));
You can't make RavenDB only return a sub-set of a single doc, so in your case you need to get the entire TodoList and then just filter on the client.
You can do this in a single network call using the Include feature, this should work:
var todoListIds = _dbSession.Query<UserTreeNode>()
.Include(x => x.TodoListId)
.Where(x => x.UserId == user.Id)
.Select(x => x.TodolistId);
foreach (var userListId in todoLisIds)
{
//This won't cause an extra network call, because the session will have already loaded it
var todoList = _dbSession.Load<TodoList>(userListId);
//In-memory filtering to get the outstanding items
var activeItems = todoList.Items.Where(x => x.IsCompleted).ToList();
}
I think what you have provided is not the real code but the following gives you uncompleted items from a todolist object.
list.Items.Where(q => q.IsCompleted == false);
I spent some time on it and I believe you need a different approach, (please note its something related to architecture and I can't be 100% sure about it, it might need some modification). It seems like you want to create a TODO List for the user. I think, its may be better if you could structure it in a way That
A User Can have one or more To Do List(s) (I have assumed one To Do List in my example)
One To Do List Item can have multiple instances of Actual work to Do
I would follow a structure similar to below:
public class User
{
public string ID { get; set; }//.... All User Attributes AND
}
public class TodoList
{
public string Id { get; set; }
public User owner { get; set; }
}
public class TodoListItem
{
public string ItemID { get; set; }
public TodoList parent { get; set; }
public string ItemDescription { get; set; }
public bool IsCompleted { get; set; }
}
Above I have a class for User which is currently representing your user. Then I have a class for ToDoList which is holding User class object (not the id of user) then I have ToDoListItem which is holding ToDoList object as parent.
If we look through database perspective than we have One to Many relationship between User and ToDoList and again one to many in ToDoList and ToDoListItem.
Now if you want to search user with incomplete work to do , just try the following linq query:
var query = from t in listTDL
where t.IsCompleted == false
select t.parent.owner;
You might need these lines to fill a test data structure:
User user = new User() { ID = "User1" };
TodoList td = new TodoList() { Id = "TD1", owner = user};
List<TodoListItem> listTDL = new List<TodoListItem>();
TodoListItem tdl = new TodoListItem() { ItemID = "TDL1", ItemDescription = "Frist Item", IsCompleted = false, parent=td };
listTDL.Add(tdl);
listTDL.Add(new TodoListItem() { ItemID = "TDL2", ItemDescription = "second Item", IsCompleted = true, parent = td });
listTDL.Add(new TodoListItem() { ItemID = "TDL3", ItemDescription = "third Item", IsCompleted = true, parent = td });
listTDL.Add(new TodoListItem() { ItemID = "TDL4", ItemDescription = "fourth Item", IsCompleted = false, parent = td });
List<User> userList = new List<User>();
userList.Add(user);
Here is how I would do:
var result = todoList.Where
(
x => nodeList.Any
(
y => y.Owner == "ownerId" && y.TodoListId == x.Id
)
).SelectMany(x => x.Items).Where(z => !z.IsCompleted);
P.s. I'am not familiar with RavenDB so showing just an idea

Easiest way to merge two List<T>s

I've got two List<Name>s:
public class Name
{
public string NameText {get;set;}
public Gender Gender { get; set; }
}
public class Gender
{
public decimal MaleFrequency { get; set; }
public decimal MaleCumulativeFrequency { get; set; }
public decimal FemaleCumulativeFrequency { get; set; }
public decimal FemaleFrequency { get; set; }
}
If the NameText property matches, I'd like to take the FemaleFrequency and FemaleCumulativeFrequency from the list of female Names and the MaleFrequency and MaleCumulativeFrequency values from the list of male Names and create one list of Names with all four properties populated.
What's the easiest way to go about this in C# using .Net 3.5?
Are you attempting to sum each of the values when you merge the lists? If so, try something like this:
List<Name> list1 = new List<Name>();
List<Name> list2 = new List<Name>();
List<Name> result = list1.Union(list2).GroupBy(x => x.NameText).Select(x => new
Name
{
NameText = x.Key,
Gender = new Gender
{
FemaleCumulativeFrequency = x.Sum(y => y.Gender.FemaleCumulativeFrequency),
FemaleFrequency = x.Sum(y => y.Gender.FemaleFrequency),
MaleCumulativeFrequency = x.Sum(y => y.Gender.MaleCumulativeFrequency),
MaleFrequency = x.Sum(y => y.Gender.MaleFrequency)
}
}).ToList();
What this does is the following:
Unions the lists, creating an IEnumerable<Name> that contains the contents of both lists.
Groups the lists by the NameText property, so if there are duplicate Names with the same NameText, they'll show up in the same group.
Selects a set of new Name objects, with each grouped Name's properties summed... you can also use Average if that makes more sense.
Converts the entire query to a List<Name> by calling the "ToList()" method.
Edit: Or, as you've said below, you simply want to merge the two lists... do this:
List<Name> allNames = femaleNames.Union(maleNames).ToList();
This looks a lot like the census name frequency data, right? Gender is a bit of a misnomer for the class you have it's more like "FrequencyData".
In effect you want a Dictionary so you can lookup any name and get the four values for it. You could simply take the males and do ToDictionary(...) on it and then iterate over the females, if the name exists in the dictionary, replace the female probabilities on it, if it doesn't exist, create a new dictionary entry.
My own approach to this same data was to create a Table in a database with all four values attached.
Here's some code for your scenario ...
Dictionary<string, Gender> result;
result = males.ToDictionary(x => x.NameText, x => x.Gender);
foreach (var female in females)
{
if (result.ContainsKey(female.NameText))
{
result[female.NameText].FemaleCumulativeFrequency = female.Gender.FemaleCumulativeFrequency;
result[female.NameText].FemaleFrequency = female.Gender.FemaleFrequency;
}
else
result.Add(female.NameText, female.Gender);
}
I think this could be what you want although I'm not sure if it handles the cumulative frequencies as you'd expect:
var mergedNameList = maleNames
.Concat(femaleNames)
.GroupBy(n => n.NameText)
.Select(nameGroup => new Name
{
NameText = nameGroup.Key,
Gender = new Gender
{
MaleFrequency = nameGroup.Sum(n => n.Gender.MaleFrequency),
MaleCumulativeFrequency = nameGroup.Sum(n => n.Gender.MaleCumulativeFrequency),
FemaleFrequency = nameGroup.Sum(n => n.Gender.FemaleFrequency),
FemaleCumulativeFrequency = nameGroup.Sum(n => n.Gender.FemaleCumulativeFrequency)
}
}.ToList();

Categories