I have this query code here :
//Get all records based on ActivityID and TaskID.
public IList<Model.questionhint> GetRecords1(int listTask, int listActivity)
{
IList<Model.questionhint> lstRecords = context.questionhints.ToList();
return lstRecords.GroupBy(x => new { x.QuestionNo, x.ActivityID, x.TaskID }).Where(a => a.TaskID == listTask && a.ActivityID == listActivity).ToList();
}
The error lies in the .Where statement, it says does not contain definition for ActivityID and TaskID.
Full error :
'System.Linq.IGrouping' does not contain a definition for 'ActivityID' and no extension method 'ActivityID' accepting a first argument of type 'System.Linq.IGrouping' could be found (are you missing a using directive or an assembly reference?)
I am weak in query statements, basically I want to retrieve records from database where activity id = something and task id = something and group them by questionNo, activityId and Task ID .
The simplest fix here is: filter (where) before you group; this will also reduce the work that the grouping has to do:
return context.questionhints
.Where(a => a.TaskID == listTask && a.ActivityID == listActivity)
.GroupBy(x => new { x.QuestionNo, x.ActivityID, x.TaskID })
.ToList();
The reason it isn't working in your original code is, as already mentioned, that GroupBy returns a sequence of groups - each of which has a .Key (your anonymous type) and is itself an IEnumerable<T> sequence of the items in that group.
However! Your method claims to return IList<Model.questionhint>; your grouped data is not, and will never be, an IList<Model.questionhint> - it will be an IList<IGrouping<{some anonymous type, Model.questionhint>>. So: you cannot group like that if you are claiming that it is an IList<Model.questionhint> - and since the grouping is an anonymous type, you can't change the return type to match. You have two choices:
don't group
group by something declarable (a custom type, or a Tuple<...>), and change the return type to match
For example:
public IList<IGrouping<Tuple<int,int,int>,Model.questionhint>>
GetRecords1(int listTask, int listActivity)
{
return context.questionhints
.Where(a => a.TaskID == listTask && a.ActivityID == listActivity)
.GroupBy(x => Tuple.Create(x.QuestionNo, x.ActivityID, x.TaskID))
.ToList();
}
change it to Where(a => a.Key.TaskID == listTask && a.Key.ActivityID == listActivity)
You are dealing with an IGrouping, of which the Key property is the anonymous object new { x.QuestionNo, x.ActivityID, x.TaskID }
This solves the original error, but now we are trying to return IGroupings, which is not the correct return type. A cleaner way of doing it would be
var groups = lstRecords.GroupBy(x => new { x.QuestionNo, x.ActivityID, x.TaskID }).Where(a => a.Key.TaskID == listTask && a.Key.ActivityID == listActivity);
IList<Model.questionhint> questionHints = new List<Model.questionhint>();
foreach(var group in groups)
{
questionHints.AddRange(group);
}
return questionHints;
Note, this code is untested. You can do all this in one linq line (as im sure Mark will), however i tend to split it for readability
Alternative
If your goal is to get all the questionHints that match the criteria, what is wrong with a simple Where clause?
lstRecords.Where(a=>a.TaskID == listTask && a.ActivityID == listActivity).ToList();
References
http://msdn.microsoft.com/en-us/library/bb344977.aspx
Related
I have following Linq queries:
var leaders = _db.Context.Person.Where(p => p.PersonGroup.Any(pg => pg.IsActive && !pg.IsDeleted && pg.GroupType == 'leader'))
var staff = _db.Context.Person.Where(p => p.PersonGroup.Any(pg => pg.IsActive && !pg.IsDeleted && pg.GroupType == 'staff'))
How do I save
pg => pg.IsActive && !pg.IsDeleted part to a variable so my query can be simplified to something like
var staff = _db.Context.Person.Where(p => p.PersonGroup.Any(pg => pg.IsActiveAndNotDeleted && pg.GroupType == 'staff'))
Thanks
So you have a sequence of Persons, where every Person has a property PersonGroup. Apparently PersonGroup is a sequence of zero or more similar items.
We don't know what these items are. What we do know, is that each of these items have Boolean properties IsActive and IsDeleted and a property GroupType which gives an indication of the type of the item: is it a leader, or a staff, or maybe something else.
Be aware: GroupType does not say anything about PersonGroup, but about one item in the PersonGroup. You didn't specify that all items in one PersonGroup have the same GroupType. As far as I know, it can be that PersonGroup has two items, one has GroupType leader and one has GroupType staff.
Requirement: Give me all Persons that have at least one item in property PersonGroup that is Active AND not IsDeleted AND has a third condition.
In your example, the third condition is pg.GroupType == leader, or pg.GroupType == staff. But it could be any condition on the type of items that are in PersonGroup.
I don't know the type of items that are in PersonGroup. Let's say they are items of class Item. Please replace this with the actual type of the items that are in PersonGroup.
My advice would be to create an extension method that takes as input an IQueryable<Person> and the third condition, and returns as output the query for all Persons that have at least one Item in property PersonGroup that is Active, not Deleted and that match the third condition.
If you are not familiar with extension methods, read Extension methods demystified
public static IQueryable<Person> WhereAnyActiveGroup(
this IQueryable<Person> persons,
Expression<Func<Item,Boolean>> thirdCondition)
{
return persons.Where(person => person.PersonGroup
.Where(item => item.IsActive && !item.IsDeleted)
.Where(thirdCondition)
.Any());
}
TODO: invent a proper method name.
In words: from the input sequence of Persons, keep only those persons that have at least one Item in property PersonGroup that is Active and not Deleted and that matches the thirdCondition.
Usage:
using (var dbContext = new PersonelContext())
{
var leaders = dbContext.Persons
.WhereAnyActiveGroup(person => person.GroupType == 'leader'))
.ToList();
var staff = dbContext.Persons
.WhereAnyActiveGroup(person => person.GroupType == 'staff'))
.ToList();
}
You can even concatenate this with other LINQ methods:
var result = dbContext.Persons.Where(person => person.City == "New York")
.WheraAnyActiveGroup(person => person.GroupType == 'staff')
.GroupBy(person => person.Name)
.ToList();
I think the simplest route would be some variation on providing a method that returns the result of a where:
private IEnumerable<Person> GetActiveInRole(string role){
return _db.Context.Person.Where(p => p.PersonGroup.Any(pg => pg.IsActive && !pg.IsDeleted && pg.GroupType == role));
}
And then use that and build on it:
var staff = GetActiveInRole("staff");
var s = staff.Where(p => p.Name == "John");
I had to switch my List<> to Dictionary<int,List> now I am struggling how to compare two values.
I need to compare Ids and i am getting an underline here
(m => m.Id == lastOpenedArticle.Id);
that 'KeyValuePair<int, Article>' does not contain a definition for 'Id' and no accessible extension method 'Id' accepting a first argument of type 'KeyValuePair<int, Article>'
The article contains the definition
var openedArticle = allOptimizationData.LastOpenedArticles.FirstOrDefault(m => m.Id == lastOpenedArticle.Id);
public static async void SaveLastOpenedArticle(ArticleDetailData lastOpenedArticle)
{
await Task.Run(() =>
{
Directory.CreateDirectory(Path.Combine(LangUpStorage.OptimalizationDataFolder));
var allOptimizationData = DeserializeAllOptimizationData();
var openedArticle = allOptimizationData.LastOpenedArticles.FirstOrDefault(m => m.Id == lastOpenedArticle.Id);
}
Is there a way to achieve comparison like i had when i used List with the dictionary?
Sorry I have never used Dictionary before plus i am a beginner
Working on the assumption that LastOpenArticles is the Dictionary<int,Article> that you mentioned:
var openedArticle = allOptimizationData.LastOpenedArticles
.FirstOrDefault(m => m.Value.Id == lastOpenedArticle.Id);
Keyvalue pairs have the Value and the Key properties.
so either m.Key or m.Value.Id depending on how you are using the ids
https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.keyvaluepair-2?view=netcore-3.1
I'm trying to generalize a duplicate checker function, which depending on which type of object, checks the properties said class has (provided in a configuration) are equal to those in another list.
I have decided to create a Dictionary, which will accept a type string for the key (Book, Author, Shop, etc.) and an array of properties that need to be equal.
Example of Dictionary enties:
"Book", ["Title", "CoverImage", "NumberOfPages"]
"Author", ["Name", "Address", "SomethingElse"]
Then, I pass an object to the function and use Reflection to get the name of the type...
obj.GetType().Name;
... which I then use to fetch the right KVP from the Dictionary, meaning that if I pass a Book object, I get "Book". We then use that to get the configuration via ...
configDictionary["obj.GetType().Name"]
... which gives us the array of strings that are the properties that we need to check equality on.
I've gotten to the part where I need something along the lines of
list.Where(x => --> for each of the strings in the array - x.GetType.GetProperty(string) && --> same for next string && same for next string
... and then I need to top it off with an...
x.Id != obj.Id
To make sure we check for duplicates based on our logic (different id's and matches on all properties but has different Id's thus - a duplicate).
The end query should look like
Books:
someList.Where(x =>
x.Title == obj.Title
&& x.CoverImage == obj.CoverImage
&& x.NumberOfPages == obj.NumberOfPages
&& x.Id != obj.Id)
.FirstOrDefault();
Authors:
someList.Where(x => x.Name == obj.Name
&& x.Address == obj.Address
&& x.SomethingElse == obj.SomethingElse
&& x.Id != obj.Id)FirstOrDefault();
Try to avoid reflection because it can slow down your application. As an alternative you can create a dictionary and put all comparators into it:
var configDictionary = new Dictionary<string, List<Func<object, object, bool>>>
{
{
"Book",
new List<Func<object, object, bool>>
{
(b1, b2) => ((Book)b1).Title == ((Book)b2).Title,
(b1, b2) => ((Book)b1).CoverImage == ((Book)b2).CoverImage,
(b1, b2) => ((Book)b1).NumberOfPages == ((Book)b2).NumberOfPages,
(b1, b2) => ((Book)b1).Id != ((Book)b2).Id,
}
},
// same for Authors
};
Now you can use it in Where method:
var typeName = obj.GetType().Name; // here we using Reflection but once per collection, not per each item
var first = someList.Where(x => configDictionary[typeName].All(f => f(x, obj))).FirstOrDefault();
Also, because FirstOrDefault also has overload that accept predicate last line can be rewritten to:
var first = someList.FirstOrDefault(x => configDictionary[typeName].All(f => f(x, obj)));
A better solution will be creating custom attribute which will tag property. Then in class override default method Equals which will get all properties with this attribute and return equality.
I currently have two lists of Directory Info. Candidatelist & VersionList. VersionList being a sublist of candidate list. I'm trying to remove all the elements from candidate list that appear in version list. So if canadidate list has 177 elements and version list has 77 then we have 100 elements left in candidate list. To be more explicit of whats inside the list. Each element correspons to a directory folder name who has a name and a parent folder name. It is possible that the directory name has duplicates but diffeerent parents. I tried doing this but I'm not necesarily getting the correct result Take a look:
candidateList.RemoveAll(x => versionslist.Any(y => y.Name == x.Name) && versionslist.Any(y => y.Parent.Name == x.Parent.Name));
return candidateList;
Your current query is not constraining business requirements to a single version list item. It's making the two queries separately. It's saying:
Are there any versionList items where the Name matches the current candidateList item name
If yes:
Are there any versionList items where the Parent.Name matches the current candidateList item Parent.Name
If yes, remove the item from the candidate list. Instead, you should be querying for a versionList item that meets both requirements pieces at the same time.
candidateList.RemoveAll(x => versionslist.Any(y => y.Name == x.Name && y.Parent.Name == x.Parent.Name));
return candidateList;
This is now saying:
Are there any versionList items where the Name and Parent.Name matches the current candidateList item Name and Parent.Name, respectively.
You can use IEnumerable.Except() method with custom IEqualityComparer:
var differences = candidateList.Except(versionList, new DirectoryInfoComparer());
The EqualityComparer could look like this:
public class DirectoryInfoComparer : IEqualityComparer<DirectoryInfo>
{
bool IEqualityComparer<DirectoryInfo>.Equals(DirectoryInfo x, DirectoryInfo y)
{
return (x.Name == y.Name) && (x.Parent.Name == y.Parent.Name);
}
int IEqualityComparer<DirectoryInfo>.GetHashCode(DirectoryInfo obj)
{
if (Object.ReferenceEquals(obj, null))
return 0;
return obj.GetHashCode();
}
}
As you mentioned, you want to remove all the elements from version list that appear in canadidate list. I think then your syntax should be like:
VersionList.RemoveAll(x => candidateList.Any(y => y.Name == x.Name && y.Parent.Name == x.Parent.Name);
return VersionList;
if it is vice versa, then use
candidateList.RemoveAll(x => VersionList.Any(y => y.Name == x.Name && y.Parent.Name == x.Parent.Name);
return candidateList;
I was originally using a foreach loop and then for each element in the loop, I perform a LINQ query like so:
foreach (MyObject identifier in identifiers.Where(i => i.IsMarkedForDeletion == false))
{
if (this.MyEntities.Identifiers.Where(pi => identifier.Field1 == pi.Field1 && identifier.Field2 == pi.Field2 && identifier.Field3 == pi.Field3).Any())
{
return false;
}
}
return true;
Then I modified it like so:
if (identifiers.Any(i => !i.IsMarkedForDeletion && this.MyEntities.Identifiers.Where(pi => i.Field1 == pi.Field1 && i.Field2 == pi.Field2 && i.Field3 == pi.Field3).Any()))
{
return false;
}
return true;
My question is this still the wrong way to use LINQ? Basically, I want to eliminate the need for the foreach loop (which seems like I should be able to get rid of it) and also make the DB query faster by not performing separate DB queries for each element of a list. Instead, I want to perform one query for all elements. Thanks!
You can change your code in this way, and it will be converted to SQL statement as expected.
To prevent runtime errors during transformation, it will be better to save DBSet to the IQueryable variable; identifiers should be IQueryable too, so you should change your code into something like this (to be honest, Resharper converted your foreach in this short labda):
IQueryable<MyObject2> identifiers = MyEntities.Identifiers.Where(i => i.IsMarkedForDeletion == false);
IQueryable<MyObject2> ids = MyEntities.Identifiers.AsQueryable();
return identifiers.All(identifier => !ids.Any(pi => identifier.Field1 == pi.Field1 && identifier.Field2 == pi.Field2 && identifier.Field3 == pi.Field3));
If identifiers is in memory collection you can change code in this way (hope that fields are string):
IQueryable<MyObject2> ids = MyEntities.Identifiers.AsQueryable();
string[] values = identifiers.Where(i => i.IsMarkedForDeletion == false).Select(i => String.Concat(i.Field1, i.Field2, i.Field3)).ToArray();
return !ids.Any(i => values.Contains(i.Field1 + i.Field2 + i.Field3));
Unfortunately your modified version will be executed exactly the same way (i.e. multiple database queries) as in the original foreach approach because EF does not support database query with joins to in memory collection (except for primitive and enumeration type collections), so if you try the most logical way
bool result = this.MyEntities.Identifiers.Any(pi => identifiers.Any(i =>
!i.IsMarkedForDeletion &&
i.Field1 == pi.Field1 && i.Field2 == pi.Field2 && i.Field3 == pi.Field3));
you'll get
NotSupportedException: Unable to create a constant value of type 'YourType'. Only primitive types or enumeration types are supported in this context.
The only way to let EF execute a single database query is to manually build a LINQ query with Concat per each item from in memory collection, like this
IQueryable<Identifier> query = null;
foreach (var item in identifiers.Where(i => !i.IsMarkedForDeletion))
{
var i = item;
var subquery = this.MyEntities.Identifiers.Where(pi =>
pi.Field1 == i.Field1 && pi.Field2 == i.Field2 && pi.Field3 == i.Field3);
query = query != null ? query.Concat(subquery) : subquery;
}
bool result = query != null && query.Any();
See Logging and Intercepting Database Operations of how to monitor the EF actions.
I would use it as follows:
if (identifiers.Where(i => !i.IsMarkedForDeletion &&
this.MyEntities.Identifiers.Field1 == i.Field1 &&
this.MyEntities.Identifiers.Field2 == i.Field2 &&
this.MyEntities.Identifiers.Field3 == i.Field3).Any()))
{
return false;
}
return true;
I hope this helps. Even though it is more to type out, it is more understandable and readable then using multiple 'where' statements.