I have this column:
PersonId CategoryId SubCategoryId
1 61 47
2 61 48
3 61 0 424
4 61 0 425
5 84 55
6 61 585
7 101 48
8 101 424
8 666 47
10 666 424
When i search only for categoryId its fine, and subCategoryId its fine, but when i search for both, like categoryId = 47 && subCategoryId = 424.
I cant make it work... in this case i only need person 61 and person 666.
i can do with a foreach, but not a good idea for performance.
u guys can help me?
if (ids.IdCategory != null && ids.IdSubCategory != null)
{
query = unitOfWork.PersonSkillsRepository.GetAll().Where(m => m.IdCategory == ids.IdCategory || m.IdSubCategory == ids.IdSubCategory);
}
else if (ids.IdCategory != null)
query = unitOfWork.PersonSkillsRepository.GetAll().Where(m => m.IdCategory == ids.IdCategory);
else
query = unitOfWork.PersonSkillsRepository.GetAll().Where(m => m.IdSubCategory == ids.IdSubCategory);
first case i get half what i want... but i want some way to filter, because in this way i get 101 too and i dont know how to see if people have both category and subcategory
and i cant see a way to do this without a foreach
You should group all person skills by person id, and then select only those groups, which contain both given category and sub category:
unitOfWork.PersonSkillsRepository.GetAll()
.GroupBy(p => p.PersonId)
.Where(g => g.Any(p => p.IdCategory == ids.IdCategory)
&& g.Any(p => p.IdSubCategory == ids.IdSubCategory))
.Select(g => g.Key)
For optimization, you can filter out skills which do not match any of given categories before grouping.
unitOfWork.PersonSkillsRepository.GetAll()
.Where(p => p.IdCategory == ids.IdCategory
|| p.IdSubCategory == ids.IdSubCategory)
.GroupBy(p => p.PersonId)
.Where(g => g.Any(p => p.IdCategory == ids.IdCategory)
&& g.Any(p => p.IdSubCategory == ids.IdSubCategory))
.Select(g => g.Key)
I just Showed the data of yours in Image
The issue is that you don't have any value against CategoryId=47 in column SubCategoryId thats why when you search both of them together it gives nothing.
Try it by adding any value in SubCategoryId against CategoryId=47...
entities.Where(z => z.categoryId == 47 || z.subCategoryId == 424)
.GroupBy(z => z.personId)
.Where(z => z.Count() == 2)
.SelectMany(z => z.ToList());
will likely get you the data that you need.
That query says 'filter the data and only return those where categoryId is 47 or subCategoryId = 424'. Then it groups them together and checks that there are two of them (i.e. one row for the categoryId and one for the subCategoryId).
EDIT:
After the question is more clarified the following code should work
if (ids.IdCategory != null && ids.IdSubCategory != null)
{
query = unitOfWork.PersonSkillsRepository.GetAll().Where(m => (m.IdCategory == ids.IdCategory || m.IdSubCategory == ids.IdSubCategory) && m.IdPerson != 101);
}
Firstly your data will return null for your search criteria. Following are some example of linq that might help this scenario.
You can do different kind of linq.
List<CollectionObject> ColObj = CollectionObject.GetListCollectionObj();
List<CollectionObject> LinqResult = ColObj.Where(x => x.CategoryID == 47 && x.SubcategoryID == null).ToList();
The following code works fine with the linq statements above.
class CollectionObject
{
public int PersonID { get; set; }
public int? CategoryID { get; set; }
public int? SubcategoryID { get; set; }
public static List<CollectionObject> GetListCollectionObj()
{
List<CollectionObject> LColObj = new List<CollectionObject>();
LColObj.Add(new CollectionObject() { PersonID = 61, CategoryID = 47, SubcategoryID = null });
LColObj.Add(new CollectionObject() { PersonID = 61, CategoryID = 48, SubcategoryID = null });
LColObj.Add(new CollectionObject() { PersonID = 61, CategoryID = 0, SubcategoryID = 424 });
LColObj.Add(new CollectionObject() { PersonID = 61, CategoryID = 0, SubcategoryID = 425 });
LColObj.Add(new CollectionObject() { PersonID = 101, CategoryID = 48, SubcategoryID = null });
LColObj.Add(new CollectionObject() { PersonID = 101, CategoryID = null, SubcategoryID = 424 });
LColObj.Add(new CollectionObject() { PersonID = 666, CategoryID = 47, SubcategoryID = null });
LColObj.Add(new CollectionObject() { PersonID = 666, CategoryID = null, SubcategoryID = 424 });
return LColObj;
}
}
Well, while I was working on mine, it seems 3 other people came up with the same thing, so I'll throw in mine --- with the critical addition the OP needs....
Func<IGrouping<int, CollectionObject>, bool> query =
gp=>
gp.Any(g=>g.CategoryID == 47) &&
gp.Any(g=>g.SubcategoryID == 424);
var q = (from p in ColObj
group p by p.PersonID)
.Where(query);
You'll need to replace query with your one-field versions on occasion.
The following would do, but you should be aware your way of storing data is quite odd.
(I mean, aren't all your subcategories related to a category ?)
// gets all the Ids of people of category 47
var personIds = entities
.Where(x => x.categoryId == 47)
.Select(x => x.PersonId)
.Distinct()
.ToList();
// gets all rows of people of category 47 and subcategory 424
var result = entities
.Where(x => personIds.Contains(x.PersonId) && x.subCategoryId == 424)
.ToList();
Linked dotnetfiddle.
Related
Consider the following records:
The outlined records are the same gameobject but they have different configuration names (the record where ConfigurationName is null is a copy from my original table).
string customName= "Config1";
string draftName = "Draft_" + id;
var list = db.JoinedGameObjects
.Where(x => x.TBUploads_ID == id)
.Where(x => x.ConfigurationName.Equals(customName) || x.ConfigurationName == null)
.Select(x => new GameObjectViewModel()
{
ID = x.ID,
GameObjectName = x.GameObjectName,
Category = x.Category,
Family = x.Family,
Type = x.Type,
MetaTypeJson = x.MetaTypeJson,
MaterialsJson = x.MaterialsJson,
MetadataVisibilityJson = x.MetadataVisibilityJson,
Visible = x.joined_Visible ?? x.Visible
}).ToList();
This code gets all records where ConfigurationName equals my customName variable or where ConfigurationName = null. However, this is not what I want.
I'll try to explain the desired outcome with an example.
I first want to check if there is a record with this ID with ConfigurationName equal to draftName (if true select this record), if not check if there is a record with this ID with ConfigurationName equal to customName (if true select this gameobject), if not take the record with this ID where ConfigurationName is null.
Can someone guide me in the right direction or provide a working example?
It seems like you already have a working concept, just add a draftName comparison to your Where condition.
It will apply conditions in order defined in Where function
.Where(x => x.ConfigurationName.Equals(draftName) || x.ConfigurationName.Equals(customName) || x.ConfigurationName == null)
Edit #1:
This should reflect what the OP wants.
For the sake of brevity, I used custom `Person` class and simplified the code example, so the concept should be easy to understand:
public class Person {
public int Id { get; set; }
public string Name { get; set; }
}
Then the rest:
var people = new List<Person>
{
new Person {Id= 1, Name = null},
new Person {Id= 1, Name = "Mike"},
new Person {Id= 1, Name = "John"},
new Person{Id = 2, Name= null},
new Person{Id = 2, Name= "Peter"},
};
//filter values
string filter1 = "Mike";
string filter2 = "John";
string filter3 = null;
var byPriority = people
.Where(p=> p.Name == filter1 || p.Name == filter2 || p.Name == filter3)
.OrderByDescending(p => p.Name == filter1)
.ThenByDescending(p => p.Name == filter2)
.ThenByDescending(p => p.Name == filter3);
var oneByIId = byPriority
.GroupBy(p => p.Id)
.Select(g => g.First())
.ToList();
The key here is to order the records by conditions priority. Then group the records and take the first one from each group
Edit #2
In some cases, LINQ to SQL may fail when comparing to `NULL`, in such a case, modify null comparing parts as this:
var byPriority = people
.Where(p=> p.Name == filter1 || p.Name == filter2 || object.Equals(p.Name,filter3))
.OrderByDescending(p => p.Name == filter1)
.ThenByDescending(p => p.Name == filter2)
.ThenByDescending(p => object.Equals(p.Name,filter3));
var oneByIId = byPriority
.GroupBy(p => p.Id)
.Select(g => g.First())
.ToList();
The C# / Entity Framework problem:
I have object
Account
{
public string AccountId { get; set; }
public string UserId { get; set; }
public string CurrencyId { get; set; }
}
then I need to return all accounts on "user A" which have same currencyId as accounts for "user B"
This is simple SQL query, but I stuck with EF. This is what I tried
public IQueryable<Account> Test(string userA, string userB)
{
var accountsA = GetAccounts().Where(x => x.UserId == userA);
var accountsB = GetAccounts().Where(x => x.UserId == userB);
return accountsA.Join(
accountsB,
acc1 => acc1.CurrencyId,
acc2 => acc2.CurrencyId,
(acc1, acc2) => acc1
);
}
this query works but return a lot of duplicate accounts for userA.
I tried
public IQueryable<Account> Test(string userA, string userB)
{
var accountsA = GetAccounts().Where(x => x.UserId == userA);
var accountsB = GetAccounts().Where(x => x.UserId == userB);
return accountsA.GroupJoin(
accountsB,
acc1 => acc1.CurrencyId,
acc2 => acc2.CurrencyId,
(acc1, acc2) => acc1
);
}
but it crash with
System.InvalidOperationException
HResult=0x80131509
Message=Processing of the LINQ expression 'DbSet<Account>
.Where(x => x.UserId == "userA").GroupJoin(
outer: DbSet<Account>
.Where(x => x.UserId == "userB"),
inner: acc1 => acc1.CurrencyId,
outerKeySelector: acc2 => acc2.CurrencyId,
innerKeySelector: (acc1, acc2) => acc1)' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
how can I do DISTINCT on EF queries?
public IQueryable<Account> Test(string userA, string userB)
{
var accountsA = GetAccounts().Where(x => x.UserId == userA);
var accountsB = GetAccounts().Where(x => x.UserId == userB);
return accountsA.Where(accountA =>
accountsB.Any(accountB => accountB.CurrencyId == accountA.CurrencyId)
);
}
So user B has zero or more Accounts, where every Account has a CurrencyId.
"I need to return all accounts on "user A" which have same currencyId as the currencyIds of user B"
Apparently we need the CurrencyIds` of user B:
int idUserB = ...
var currencyIdsOfUserB = GetAccounts()
.Where(account => account.Id == idUserB)
.Select(account => account.CurrencyId)
.Distinct(); // only if you expect duplicates
All accounts of user A that have at least one of these currencyIds:
int idUserA:
var result = GetAccounts.Where(account => account.Id == idUserB
&& currencyIdsOfUserB.Contains(account.CurrencyId);
Accounts
Id UserId CurrencyId
01 09 18
02 10 50
03 11 19
04 20 49
05 10 51
06 10 52
07 20 52
08 20 51
09 10 50
10 20 52
User [10] has Accounts 2, 5, 6, 9 with CurrencyIds 50, 51, 52, 50
User [20] has Accounts 4, 7, 8, 10 with CurrencyIds 49, 52, 51, 52
currenCyIdsOfUserB = {50, 51, 52}
Give me all Accounts with UserId equal to [10] and CurrencyId in {50, 51, 52}.
The result will be the Accounts with Id 7, 8, 10
Simple comme bonjour!
ok, I found it. it is
.Distinct()
so the answer will be
var a1 = accountContext.Account.Where(x => x.UserId == "userA");
var a2 = accountContext.Account.Where(x => x.UserId == "userB");
var result = a1.Join(
a2,
acc1 => acc1.CurrencyId,
acc2 => acc2.CurrencyId,
(acc1, acc2) => acc1
)
.Distinct();
I have a survey where the answers are grouped by a Session GUID and a timestamp of the answer:
public class Answer
{
[Key]
public int Id { get; set; }
public DateTime DateCreated { get; set; }
public string Session { get; set; }
public int Numeric { get; set; }
public string Text { get; set; }
}
If the answer is type Numeric (from 1 to 5 stars) the numeric is filled, if the answer is type Text (like a comment), the text is filled.
Example of records:
48 2016-07-15 11:20:14.823 12f68234-fee0-4a3a-88ef-f3977824ed51 5 NULL
49 2016-07-15 11:20:19.550 12f68234-fee0-4a3a-88ef-f3977824ed51 2 NULL
50 2016-07-15 11:20:19.553 12f68234-fee0-4a3a-88ef-f3977824ed51 4 NULL
51 2016-07-15 11:20:19.557 12f68234-fee0-4a3a-88ef-f3977824ed51 3 NULL
52 2016-07-15 11:20:19.560 12f68234-fee0-4a3a-88ef-f3977824ed51 0 gostei bastante!
53 2016-07-15 11:59:59.000 a143125e-0463-13f9-fc83-48d660c96156 4 NULL
54 2016-07-15 12:00:26.277 a143125e-0463-13f9-fc83-48d660c96156 4 NULL
55 2016-07-15 12:00:26.277 a143125e-0463-13f9-fc83-48d660c96156 3 NULL
56 2016-07-15 12:00:26.277 a143125e-0463-13f9-fc83-48d660c96156 4 NULL
57 2016-07-15 12:00:26.297 a143125e-0463-13f9-fc83-48d660c96156 0 Acho que há algumas coisas para melhorar
58 2016-07-15 17:56:00.503 610821d4-5c48-4222-8c49-c19f0dd9182c 5 NULL
59 2016-07-15 17:56:16.617 610821d4-5c48-4222-8c49-c19f0dd9182c 5 NULL
60 2016-07-15 17:56:16.620 610821d4-5c48-4222-8c49-c19f0dd9182c 5 NULL
61 2016-07-15 17:56:16.617 610821d4-5c48-4222-8c49-c19f0dd9182c 4 NULL
62 2016-07-15 17:56:16.637 610821d4-5c48-4222-8c49-c19f0dd9182c 0 Gostei bastante de todo o serviço
The problem is that I can't group by session and ordered by datecreated because they are both distinct records.
The code I have that is not working is:
var sessions = _dbContext.Answers
.Where(p => p.Location.Company.Id == id)
.Where(p => p.Question.Type == QuestionType.Text)
.Where(p => p.Text != "")
.OrderByDescending(p => p.DateCreated)
.Select(p => p.Session)
.Distinct()
.Take(count);
var dto = new List<GetLastCommentsByCountByCompanyIdDTO>();
foreach (var session in sessions)
{
dto.Add(new GetLastCommentsByCountByCompanyIdDTO
{
LocationName = _dbContext.Answers.Where(s => s.Session == session).Select(s => s.Location.Name).FirstOrDefault(),
DateCreated = _dbContext.Answers.Where(s => s.Session == session).Select(s => s.DateCreated).FirstOrDefault(),
Comment = _dbContext.Answers.Where(s => s.Session == session && s.Question.Type == QuestionType.Text).Select(s => s.Text).FirstOrDefault()
});
}
return dto.OrderByDescending(p => p.DateCreated);
Try this one:
var baseQuery = _dbContext.Answers.AsNoTracking()
.Where(p => p.Location.Company.Id == id
&& p.Question.Type == QuestionType.Text
&& p.Text != null
&& p.Text != "")
.GroupBy(g => new { Session = g.Session, Location = g.Location.Name })
.Select(x =>
new
{
Session = x.Key.Session,
LocationName = x.Key.Location,
LastAnswer = x.OrderByDescending(f => f.DateCreated).FirstOrDefault()
})
.Select(x => new GetLastCommentsByCountByCompanyIdDTO
{
LocationName = x.LocationName,
DateCreated = x.LastAnswer.DateCreated,
Comment = x.LastAnswer.Text
})
.OrderByDescending(x => x.DateCreated)
.Take(count);
var res = baseQuery.ToList();
This should give you the answers grouped by Session.
var answersGroupedBySession = _dbContext.Answers
.Where(p => p.Location.Company.Id == id
&& p.Question.Type ==QuestionType.Text
&& p=>!String.IsNullOrEmpty(p.Text))
.GroupBy(g => g.Session, items => items,
(key, value) =>
new {
Session = key,
Answers = value.OrderByDescending(f => f.DateCreated)
}).ToList();
The variable answersGroupedBySession will be a list of annonymous objects with 2 properties Session and Answers. For each session, answers will be grouped (and sorted by date) under the Answers property. If you do not prefer the annonymous object, you may create a dto for that.
public class GroupedAnswers
{
public string Session {set;get;}
public IEnumerable<Answer> Answers {set;get;}
}
and use that in the projection part.
.GroupBy(g => g.Session, items => items, (key, value) => new GroupedAnswers
{
Session = key,
Answers = value.OrderByDescending(f => f.DateCreated)
}).ToList();
i have this match conditiond:
var matchConditions = new long[][]
{
new long[] { 109, 145 }, //color Id
new long[] { 202 }, // province Id
new long[] { 303, 309, 317, 318 }, //options Id
};
each of them is id of a different group, for example first nested array is for color.
and i need to know which products meet color id 109 or 145, second one is for something else.
i mean i want to get all products that meet any item of all each group,
{color 109 or 145 - and - province 202 - and - option 303 or 309 or 317 or 318}
i tried:
matchConditions.ToList().ForEach(x => x.Any(j => adAdFields.Select(co => co.listFieldId).Contains(j)))
and
matchConditions.All(x => x.Any(j => adAdFields.Select(co => co.listFieldId).Contains(j)))
but none of them work
EDIT:
before now i had this query:
var adsWithRelevantadFields =
from adField in cwContext.tblAdFields
join ads in cwContext.tblAds on adField.adId equals ads.id
where (prvId == 0 && ads.tabId == tabId) ||
(prvId != 0 & ctsId.Value == 0 && ads.provinceId == prvId & ads.tabId == tabId) ||
(prvId != 0 & ctsId.Value > 0 && ads.provinceId == prvId & ads.cityId == ctsId.Value & ads.tabId == tabId)
where ads.endDate >= theToDay
where ads.conditionId == 1
where ads.payed == true
group adField by adField.adId into adAdFields
where searchIds.All(i => adAdFields.Select(co => co.listFieldId).Contains(i))
this work well, but now i need to search more options that grouped as i showed, so i need to add "&&" to end of query to search these new items, that is my question.
EDIT2
let's suppose i have some lists(color, who produce, options, where, ....) now when i want to add a new product, add proper attribute in these format(productId, attributeId), so for example i have (1,109 - 1,202 - 1, 303 ...) and (2,109 - 2,202 - 2,318...) ...
so as i grouped each product(ads in real), i need just check which products grop:
{color 109 or 145 - and - province 202 - and - option 303 or 309 or 317 or 318}
How about something like this?
adAdFields.Where(x => matchConditions[0].Contains(x.colorId)
&& matchConditions[1].Contains(x.provinceId)
&& matchConditions[2].Contains(x.optionsId))
.ToList();
I think you need to grain you query, how about to build this query - step by step, in that case you can improve readability of your code and check availibility of all conditions.
Look at this:
class Program
{
static void Main(string[] args)
{
var adAdFields = new List<AdAdField>
{
new AdAdField {colorId = 109, optionsId = 303, provinceId = 202},
new AdAdField {colorId = 145, optionsId = 309, provinceId = 2},
new AdAdField {colorId = 3, optionsId = 317, provinceId = 3},
new AdAdField {colorId = 4, optionsId = 318, provinceId = 4}
}.AsQueryable();
var matchConditions = new long[][]
{
new long[] { 109, 145 }, //color Id
new long[] { 202 }, // province Id
new long[] { 303, 309, 317, 318 }, //options Id
};
var result1 = adAdFields.Where(x => matchConditions[0].Contains(x.colorId)
&& matchConditions[1].Contains(x.provinceId)
&& matchConditions[2].Contains(x.optionsId)).ToList();
var query = adAdFields;
if (matchConditions[0].Length > 0)
query = query.Where(x => matchConditions[0].Contains(x.colorId));
if (matchConditions[1].Length > 0)
query = query.Where(x => matchConditions[1].Contains(x.provinceId));
if (matchConditions[2].Length > 0)
query = query.Where(x => matchConditions[2].Contains(x.optionsId));
//below will be other possible conditions....
var result2 = query.ToList();
//result2 and result1 ARE SAME!!!
}
}
public class AdAdField
{
public int colorId { get; set; }
public int provinceId { get; set; }
public int optionsId { get; set; }
}
IQueriable will add conditions to query, eventually in the end of query you will call .ToList(), and it forces orm to generate appropriate sql. Until this time you just building query.
You can use dynamic linq , to construct a linq query similar to the one in #Selman22 answer, with varied number of conditions. Basically, A linq query is composed of a tree with expressions in it.
Dynamic LINQ means you build the tree, well - dynamically, which gives you more power creating the queries.
You can use PredicateBuilder to do so, or use a ready made library such as dynamic-query-library.
See:
http://www.codeproject.com/Articles/231706/Dynamic-query-with-Linq
http://msdn.microsoft.com/en-us/library/bb882637.aspx
http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
I have a collection in this format
IList<ReportRow> MyCollection
Where ReportRow is a
Dictionary<string, object>
So MyCollection might look like this:
Id1 Id2 Value Status Entered
123 2 56 New 50:01.7
123 2 76 Old 50:23.0
123 2 12 New 50:23.0
127 3 54 Old 50:23.0
127 3 77 New 59:23.0
...
Is there a way in Linq that I can do this:
What I need to do is for each Id1+Id2 combination I need to output the Value where Status is New and where the status is Old. There might be multiple New values for an Id1+Id2 combination so it should pick up the latest New record, using the Entered column to sort.
The new record will contain all the records plus 1 extra column like so:
Id1 Id2 NewValue OldValue Entered
123 2 12 76 50:23.0
127 3 77 54 59:23.0
Any help with this would be great
This should do it (or be close as this is off the top of my head)
collection.OrderByDescending(r => r.Value.Entered)
.GroupBy(r => {r.Id1, r.Id2)
.Select(g => new {
Id1 = g.Key.Id1,
Id2 = g.Key.Id2,
NewValue = g.FirstOrDefault(v => v.Status == "New").Value ?? 0,
OldValue = g.FirstOrDefault(v => v.Status == "Old").Value ?? 0,
Entered = g.Last().Entered
})
If you don't care for the dictionary string key you could do
collection.SelectMany(d => d.Value)
.OrderByDescending(r => r.Value.Entered)
.GroupBy(rr => {rr.Value.Id1, rr.Value.Id2)
.Select(g => new {
Id1 = g.Key.Id1,
Id2 = g.Key.Id2,
NewValue = g.FirstOrDefault(v => v.Status == "New").Value ?? 0,
OldValue = g.FirstOrDefault(v => v.Status == "Old").Value ?? 0,
Entered = g.Last().Entered
})
from row in MyCollection
group row by new { row.Id1, row.Id2 } into g
let lastNew = g.OrderByDescending(r => r.Entered)
.FirstOrDefault(r => r.Status == "New")
let lastOld = g.OrderByDescending(r => r.Entered)
.FirstOrDefault(r => r.Status == "Old")
select new {
Id1 = g.Key.Id1,
Id2 = g.Key.Id2,
NewValue = (lastNew == null) ? null : (int?)lastNew.Value,
OldValue = (lastOld == null) ? null : (int?)lastOld.Value,
Entered = g.OrderByDescending(r => r.Entered).First().Entered
}