How to get value from Dictionary to new Object in LINQ query - c#

I have a Collection of Data and the Dictionary:
Collection handle Student Data
Dictionary Keeps Student Courses.
I want to get the Course Name from the Dictionary and put it into as CourseName.
private viod GenerateStudentDetails(Student studentData)
{
var courses = m_courses.GetCoursesDictionary(); // returns Dictionary<Guid,string>()
var studentDetails= from data in studentData
select new
{
FirstName = data.FirstName,
LastName = data.LastName,
Email = data.Email,
Mobile = data.Profile.Mobile,
City = data.Profile.City,
PostCode = data.Profile.PostCode,
CourseName = courses[data.Profile.CourseID ?? Guid.Empty]
};
}
"LINQ to Entities does not recognize the method 'System.String get_Item(System.Guid)' method, and this method cannot be translated into a store expression."

You could try something as the below:
private viod GenerateStudentDetails(Student studentData)
{
var courses = m_courses.GetCoursesDictionary(); // returns Dictionary<Guid,string>()
var studentDetails= (from data in studentData
select new
{
FirstName = data.FirstName,
LastName = data.LastName,
Email = data.Email,
Mobile = data.Profile.Mobile,
City = data.Profile.City,
PostCode = data.Profile.PostCode,
CourseID = data.Profile.CourseID
}).AsEnumerable()
.Select(item=>new
{
FirstName = item.FirstName,
LastName = item.LastName,
Email = item.Email,
Mobile = item.Profile.Mobile,
City = item.Profile.City,
PostCode = item.Profile.PostCode,
CourseName = courses[item.Profile.CourseID ?? Guid.Empty]
});
}
What's the problem?
The problem is that the last expression in the anonymous type you create,
CourseName = courses[data.Profile.CourseID ?? Guid.Empty]
cannot be in this place, because it can't be translated appropriately. So you have this option. You can declare a sequence of the data you want from the studentData and then make any conversion of call anything you want to the new anonymous type we create.

Just for thought
var eCourses = ((IEnumerable<KeyValuePair<Guid, string>>) courses);
var studentDetails = (from data in studentData
select new
{
data.FirstName,
data.LastName,
data.Email,
data.Profile.Mobile,
data.Profile.City,
data.Profile.PostCode,
CourseName = eCourses.FirstOrDefault(s => s.Key == data.Profile.CourseID).Value
});

Related

C# List Join List And Filter From Another List

Need some help constructing proper LINQ query with join.
I have the following setup:
public class Student
{
public string StudentName { get; set; }
public string StudentEmail { get; set; }
}
public class Enrolled
{
public Student Student { get; set; }
public string Subject { get; set; }
}
public class Dropped
{
public Student Student { get; set; }
public string Subject { get; set; }
}
// Populating the List.
// Setup the Student List
List<Student> lstStudents = new List<Student>();
var John = new Student()
{
StudentEmail = "john#stanford.edu",
StudentName = "John Wayne"
};
var Maya = new Student()
{
StudentEmail = "maya#stanford.edu",
StudentName = "Maya Agnes"
};
var Eric = new Student()
{
StudentEmail = "eric#stanford.edu",
StudentName = "Eric James"
};
var Ellen = new Student()
{
StudentEmail = "ellen#stanford.edu",
StudentName = "Ellen Page"
};
lstStudents.Add(John);
lstStudents.Add(Maya);
lstStudents.Add(Eric);
lstStudents.Add(Ellen);
// Setup the Enrolled List
List<Enrolled> lstEnrolled = new List<Enrolled>();
// John
var JohnMath = new Enrolled() { Student = John, Subject = "Math" };
var JohnScience = new Enrolled() { Student = John, Subject = "Science" };
var JohnEnglish = new Enrolled() { Student = John, Subject = "English" };
// Maya
var MayaMath = new Enrolled() { Student = Maya, Subject = "Math" };
// Eric
var EricMath = new Enrolled() { Student = Eric, Subject = "Math" };
var EricScience = new Enrolled() { Student = Eric, Subject = "Science" };
var EricSocial = new Enrolled() { Student = Eric, Subject = "Social" };
// Ellen
var EllenMath = new Enrolled() { Student = Ellen, Subject = "Math" };
var EllenScience = new Enrolled() { Student = Ellen, Subject = "Science" };
var EllenEnglish = new Enrolled() { Student = Ellen, Subject = "English" };
var EllenSocial = new Enrolled() { Student = Ellen, Subject = "Social" };
lstEnrolled.Add(JohnMath);
lstEnrolled.Add(JohnScience);
lstEnrolled.Add(JohnEnglish);
lstEnrolled.Add(MayaMath);
lstEnrolled.Add(EricMath);
lstEnrolled.Add(EricScience);
lstEnrolled.Add(EricSocial);
lstEnrolled.Add(EllenMath);
lstEnrolled.Add(EllenScience);
lstEnrolled.Add(EllenEnglish);
lstEnrolled.Add(EllenSocial);
// Setup the Dropped List
List<Dropped> lstDropped = new List<Dropped>();
// John dropped Math
var JohnDropMath = new Dropped() { Student = John, Subject = "Math" };
// Eric dropped Social
var EricDropSocial = new Dropped() { Student = Eric, Subject = "Social" };
// Ellen Dropped Math
var EllenDropMath = new Dropped() { Student = Ellen, Subject = "Math" };
lstDropped.Add(JohnDropMath);
lstDropped.Add(EricDropSocial);
lstDropped.Add(EllenDropMath);
What I am trying to achieve is get a list of all students, and the subjects they are enrolled in one line, like:
Student Subjects
------- ----------------------------------
John English, Science
Maya Math
Eric Math, Science
Ellen English, Science, Social
I have constructed the following so far:
var StudentsAndCurrentSubjects = (from st in lstStudents
join en in lstEnrolled
on st.StudentName equals en.Student.StudentName
select new
{
st.StudentName,
en.Subject
})
.ToList();
But it is giving me the result per person per subject.
I hit a wall on how to exclude the dropped items from the list.
I am thinking of traversing the dropped list, like:
foreach(d in lstDropped)
{
// Logic to Remove it from the StudentsAndCurrentSubjects
}
But I feel that it is inefficient (especially if I have lots of rows).
I also do not know how to join the subjects in one line.
Looking for some help here.
Thanks.
We just need to first remove dropped subjects from enrolled list. then group by student.
note that we have list for each student, that it has Student and Subject as its fields, we just select the name of first student from each (as they all the same) and join the subjects together.
var result = lstEnrolled.Where(e => !lstDropped.Any(d => d.Student == e.Student && d.Subject == e.Subject)) //omit dropped courses
.GroupBy(x => x.Student) // group results by students
.Select(x => new {
Name = x.First().Student.StudentName.Split(' ').First(),
Subjects = string.Join(", ", x.Select(e => e.Subject))
}).ToArray();
for printing:
foreach(var x in result)
Console.WriteLine($"{x.Name}\t{x.Subjects}");
LIVE DEMO
You are almost there. The join is correct, you just need to actually apply the grouping you want:
var StudentsAndCurrentSubjects = (from st in lstStudents
join en in lstEnrolled
on st.StudentName equals en.Student.StudentName
select new
{
st.StudentName,
en.Subject
})
.GroupBy(s => s.StudentName, d => d.Subject)
.Select(grp => new { StudentName = grp.Key, Subjects = grp.ToList() })
.ToList();
Then you can use the projection above to display it however you want. Something like this maybe:
foreach (var grouping in StudentsAndCurrentSubjects) {
var studentName = grouping.StudentName;
var subjects = string.Join(", ", grouping.Subjects);
Console.WriteLine($"{studentName}\t{subjects}");
}
Try a GroupJoin by using the into keyword.
var studentsAndCurrentSubjects = (
from student in lstStudents
join enrollment in lstEnrolled
on student equals enrollment.Student
into enrollments
join drop in lstDrop
on student equals drop.Student
into drops
let classes = enrollments.Select(e => e.Subject).Except(drops.Select(d => d.Subject))
select new
{
Student = student.StudentName,
Subjects = string.Join(", ", classes)
})
.ToList();
Or, you can subquery with let. This will have noticeably slower performance against larger lists than the other solution, but it should be fine in the database.
var studentsAndCurrentSubjects = (
from student in lstStudents
let enrollments = lstEnrolled.Where(x => student == x.Student).Select(x => x.Subject)
let drops = lstDrops.Where(x => student == drop.Student).Select(x => x.Subject)
let classes = enrollments.Except(drops)
select new
{
Student = student.StudentName,
Subjects = string.Join(", ", classes)
})
.ToList();

Implicit ViewModel generation from LINQ?

In my project I have a GenericImageViewModel which is used by many entities.
Example of getting the ASP User Entity:
var query = UserRepository.Get(Id).Select(a => new TRDIdenityViewModel
{
FirstName = a.UserProfile.FirstName,
LastName = a.UserProfile.LastName,
NickName = a.UserProfile.NickName,
ProfileImage = a.UserProfile.ProfileImage
});
The ProfileImage is the GenericImageViewModel and has an implicit operator as follows:
public static implicit operator TRDGenericImageViewModel(TRDImage image)
{
return new TRDGenericImageViewModel
{
Id = image.Id,
AspectRatio = image.Ratio,
Url = image.Url,
};
}
If I run the query Entity Framework throws an exception:
"Unable to cast the type 'TRDImage' to type 'TRDGenericImageViewModel'. LINQ to Entities only supports casting EDM primitive or enumeration types."
If I create the GenericImageViewModel manually for each ViewModel everything is running fine:
var query = UserRepository.Get(Id).Select(a => new TRDIdenityViewModel
{
FirstName = a.UserProfile.FirstName,
LastName = a.UserProfile.LastName,
NickName = a.UserProfile.NickName,
ProfileImage = new TRDGenericImageViewModel {
Id = a.UserProfile.ProfileImage.Id,
AspectRatio = a.UserProfile.ProfileImage.Ratio,
Url = a.UserProfile.ProfileImage.Url,
},
});
But in this case I have to copy and paste the TRDGenericImageViewModel generation in every ViewModel and thats not the way it should goes. If something changed I have to modify all related classes.
So is there a way to avoid this exception?
Using .AsEnumerable() prior lambda is not possible due to selects later in that query.
var query = UserRepository
.Get(Id)
.AsEnumerable()
.Select(a => new TRDIdenityViewModel
{
FirstName = a.UserProfile.FirstName,
LastName = a.UserProfile.LastName,
NickName = a.UserProfile.NickName,
ProfileImage = new TRDGenericImageViewModel
{
Id = a.UserProfile.ProfileImage.Id,
AspectRatio = a.UserProfile.ProfileImage.Ratio,
Url = a.UserProfile.ProfileImage.Url,
},
Statistics = new TRDUserStatisticsViewModel
{
PostCount = a.Posts.Count(),
CommentCount = a.Comments.Count(),
ImageCount = a.Images.Count(),
VideoCount = a.Videos.Count(),
VoteCount = a.PostVotes.Count(),
}
});
When calling .AsEnumerable() only the entities included by the Include statement are counted. But if the user has more than 1000 Posts and 10.000 Votes the query is a data nightmare.

LINQ to handle IEnumerable and remove unmatch items from sub entity

I encountered a problem in my work recently, the following is the source code,
this.NameList = new ObservableCollection<Name>();
List<Others> others1 = new List<Others>();
others1.Add(new Others() { Company = "Company1", School = "School1" });
others1.Add(new Others() { Company = "Company1", School = "School1" });
others1.Add(new Others() { Company = "Company1", School = "School1" });
List<Others> others2 = new List<Others>();
others2.Add(new Others() { Company = "Company2", School = "School2" });
others2.Add(new Others() { Company = "Company2", School = "School2" });
others2.Add(new Others() { Company = "Company2", School = "School2" });
List<Others> others3 = new List<Others>();
others3.Add(new Others() { Company = "Company3", School = "School3" });
others3.Add(new Others() { Company = "Company3", School = "School3" });
others3.Add(new Others() { Company = "Company3", School = "School3" });
this.NameList.Add(new Name() { FirstName = "Jacob", LastName = "Deng", Others = others1 });
this.NameList.Add(new Name() { FirstName = "David", LastName = "Xu", Others = others2 });
this.NameList.Add(new Name() { FirstName = "Helen", LastName = "Liu", Others = others3 });
please click here to see the output of above code
And then please read the following code,
List<Others> others1 = new List<Others>();
others1.Add(new Others() { Company = "Company1", School = "School1" });
others1.Add(new Others() { Company = "Company1", School = "School1" });
others1.Add(new Others() { Company = "Company1", School = "School1" });
List<Others> others2 = new List<Others>();
List<Others> others3 = new List<Others>();
this.NameList.Add(new Name() { FirstName = "Jacob", LastName = "Deng", Others = others1 });
this.NameList.Add(new Name() { FirstName = "David", LastName = "Xu", Others = others2 });
this.NameList.Add(new Name() { FirstName = "Helen", LastName = "Liu", Others = others3 });
Please click here for the output of the second snippet code
From above two snippets, you might notice that the second snippet doesn't contain any items in other2 and other3, you can easily understand from the preview output.
Now the question comes, how do we use LINQ to IEnumerable to handle such case, how to use LINQ to remove those items from Others entity? but we need to keep other2 and other3 not to be null (keep it count is 0). Besides LINQ, are there any other solutions?
I tried to use the following, but failed,
var others = ((MyVM)DataContext).NameList.Select(n => n.Others.Where(o => o.School == "School1")).ToList();
I just did some test, if we don't use LINQ, then we can use the following snippet code to fix my it.
ObservableCollection<Name> nameList = ((MyVM)DataContext).NameList;
foreach(Name n in nameList)
{
List<Others> removeList = new List<Others>();
for(int i=0;i<n.Others.Count;i++)
{
if(n.Others[i].School!="School1")
{
removeList.Add(n.Others[i]);
}
}
foreach(Others other in removeList)
{
n.Others.Remove(other);
}
}
But it looks very redundant, and we know that LINQ is very useful and i believed that it can be fixed with LINQ. Hope someone could help me to fix it with LINQ,thanks.
Appreciate if someone could help me. Thanks a lot.
It's better to use List<T>.RemoveAll in your case. See Using LINQ to remove elements from a List<T> for more details.
ObservableCollection<Name> nameList = ((MyVM)DataContext).NameList;
foreach(Name n in nameList)
{
n.Others.RemoveAll(s => s.School != "School1");
}
Linq is good at read-only iterating instead of updating/deleting. If you insist using Linq, you have to re-construct each item.
var newList = from nl in this.namelist
// this list can be empty but never null
let notSchool1 = (from s in nl.Others
where s.School != "School1"
select s).ToList()
select new Name
{
FirstName = nl.FirstName,
LastName = nl.LastName,
Others = noSchool1
};
this.NameList = new ObservableCollection<Name>(newList);
Above code may break other functionalities in your application, because these items are observed.
As I understand your question,
You want to remove others whose school value is not 'School1'. There are different way available for that but I recommend to filter the data from NameList which you want.
You can try below code to get others list with 'School1'
var result = NameList.Select(name => new Name()
{
FirstName = name.FirstName,
LastName = name.LastName,
Others = name.Others.Where(obj => obj.School == "School1").ToList()
}
).ToList();

Linq query rewriting

This has really stumped me...
I have two queries..
This one does NOT work, with the error,
"LINQ to Entities does not recognize the method GetAllCustomers()' method, and this method cannot be translated into a store expression."
results = theDocuments.Select(x => new DocumentModel()
{
document_id = x.document_id,
document_type = x.document_type,
franchisee_id = x.franchisee_id,
customer_id = x.customer_id,
address = x.address,
area_id = x.area_id,
***HERE***customer_name = outletService.GetAllCustomers().Where(xx => xx.id == x.customer_id).First().name
}).OrderBy(x => x.document_id);
Now rewriting it like this works:
results = from p in theDocuments
join cust in outletService.GetAllCustomers() on p.customer_id equals cust.id
select new DocumentModel()
{
customer_name = cust.name,
document_id = p.document_id,
document_type = p.document_type,
franchisee_id = p.franchisee_id,
customer_id = p.customer_id,
address = p.address
};
I understand that this cannot be converted to it's relevant SQL, however, I am not sure what the second query is doing differently in it's execution in order for it to work, does it execute the GetAllCustomers() before doing anything else? How is it working differently internally?
How could I rewrite the first query so that it executes correctly?
Thanks,
James.
**This is what I ended up with **
results = theDocuments.Join(
outletService.GetAllCustomers(), docs => docs.customer_id, customers => customers.id, (doc, cust) => new DocumentModel()
{
document_id = doc.document_id,
document_type = doc.document_type,
franchisee_id = doc.franchisee_id,
customer_id = doc.customer_id,
address = doc.address,
area_id = doc.area_id,
customer_name = cust.name
});
The first one try to translate your method into a SQL method, the second create a join in the query. In my opinion the correct way is using join :)
I was a problem. I found a solution. That might be help you.
dynamic user = new
{
Name = "",
Surname = ""
};
dynamic userModel = new
{
Name = "",
Surname = "",
FullName = ""
};
var query = db.User.AsNoTracking();
query = query.Select(x => new userModel
{
Name = x.Name,
Surname = x.SurName,
FullName = $"{x.Name}{x.SurName}",
}).ToList();
If you try to execute this query. You will take a crash. Linq doesnt support
FullName = $"{x.Name}{x.SurName}"
You have to change this line to
FullName = x.Name + " " + x.SurName

Check if a string contains a value within a generic list and replace it

I have a string with a message containing some fields I want to swap out to actual values
var message = "Hi [CustomerName]. Its [TODAY], nice to see you were born on the [DOB]!";
var mappingCodes = new List<string> {"[CUSTOMER_NAME]","[DOB]",[TODAY]};
var customEmails = new Dictionary<string, string>();
var today = DateTime.Now;
var customers = new List<Customer>()
{
new Customer()
{
FirstName = "Jo",
LastName = "Bloggs",
Email = "jo#bloggs.com",
DOB = "10/12/1960"
}
};
foreach (var customer in customers)
{
var emailMessage = "";
customEmails.Add(customer.Email,emailMessage);
}
What I'm trying to do is loop through each of the customers and take the message replacing any of the mappingCodes with actual codes.
e.g. [Today] Would be the today and CustomerName would be Customer.FirstName + Customer.LastName
There could be 1000's of customers so I need something robust. I'm just not sure how to first check the string contains any of the mappingCodes and then replace them with the desired values.
Any advice?
You could try something like this. String.Format is rather efficient. It also would allow you to format Date.Today, if you want.
var customers = new List<Customer>()
{
new Customer()
{
FirstName = "Jo",
LastName = "Bloggs",
Email = "jo#bloggs.com",
DOB = "10/12/1960"
}
};
foreach (var customer in customers)
{
var emailMessage = String.Format("Hi {0}. Its {1}, nice to see you were born on the {2}!", customer.FirstName, DateTime.Today, customer.DOB);
customEmails.Add(customer.Email,emailMessage);
}
You can use Regex.Replace(string, MatchEvaluator):
var customers = new[] {
new {
Name = "Fred Flintstone",
City = "Bedrock"
},
new {
Name = "George Jetson",
City = "Orbit City"
}
};
string template = "Hello, [NAME] from [CITY]!";
var re = new Regex(#"\[\w+\]"); // look for all "words" in square brackets
foreach (var customer in customers)
{
Trace.WriteLine(
re.Replace(template, m => {
// determine the replacement string
switch (m.Value) // m.Value is the substring that matches the RE.
{
// Handle getting and formatting the properties here
case "[NAME]":
return customer.Name;
case "[CITY]":
return customer.City;
default:
// The "mapping code" is not supported, I just return the
// original substring
return m.Value;
}
}));
}
Obviously the above is just the general approach, you'll have to modify it to support your mapping codes and data structures.

Categories