Linq query returns duplicate results - c#

The following query returns duplicate results, in the second select query.
Country has 0..1 to * relationship with leagues.
Leagues have 1 to * relationship with userLeagues.
return from ul in userLeagues
select new Map.Country
{
id = ul.Country.CountryId,
name = ul.Country.Common_Name,
leagues = userLeagues.Where(x => x.CountryId.Value == ul.CountryId.Value)
.Select(x => new Map.League
{
id = x.LeagueID,
name = x.leagueNameEN,
})
};
I tried using Distinct with no luck.
It seems that either i have to use distinct or groupby countryId
The output is such as
[
{
"id": 1,
"name": "Europe",
"leagues": [
{
"id": 2,
"name": "Champions League",
},
{
"id": 3,
"name": "Europa league",
}
]
},
{
"id": 1,
"name": "Europe",
"leagues": [
{
"id": 2,
"name": "Champions League",
},
{
"id": 3,
"name": "Europa league",
}
]
}
]

You need to group it by CountryId and Common_Name to get expected results:
var result = from ul in userLeagues
group ul by new { ul.Country.CountryId, ul.Country.Common_Name } into g
select new Map.Country
{
id = g.Key.CountryId,
name = g.Key.Common_Name,
leagues = g.Select(x => new Map.League
{
id = x.LeagueID,
name = x.leagueNameEN,
})
};

Think about what you're doing: For each league in userLeagues, you're creating a Map.Country for the country that league belongs to. If three leagues are in France, that's three Frances. France is a wonderful country, but let's not go overboard.
Instead, you want to start with a distinct list of countries. For each one, create one Map.Country, and give that Map.Country a list of the leagues that should belong to it.
First, let's make Country implement IEquatable<Country> for Distinct purposes:
public class Country : IEquatable<Country>
{
public bool Equals(Country other)
{
return other.CountryID == CountryID;
}
Second, you want to start with a distinct list of countries, and then populate them with leagues.
var q =
from ctry in userLeagues.Select(ul => ul.Country).Distinct()
select new
{
id = ctry.CountryID,
name = ctry.Common_Name,
leagues = userLeagues.Where(x => x.Country == ctry)
.Select(x => new
{
id = x.LeagueID,
name = x.leagueNameEn
}).ToList()
};
I didn't recreate your Map.League and Map.Country classes, I just used anonymous objects, and I left it that way because this code definitely works just as it is. But filling in your class names is trivial.
If it's not practical to make Country implement IEquatable<T>, just write a quick equality comparer and use that:
public class CountryComparer : IEqualityComparer<Country>
{
public bool Equals(Country x, Country y)
{
return x.CountryID == y.CountryID;
}
public int GetHashCode(Country obj)
{
return obj.CountryID.GetHashCode();
}
}
...like so:
var cc = new CountryComparer();
var q =
from ctry in userLeagues.Select(ul => ul.Country).Distinct(cc)
select new
{
id = ctry.CountryID,
name = ctry.Common_Name,
leagues = userLeagues.Where(x => cc.Equals(x.Country, ctry))
.Select(x => new
{
id = x.LeagueID,
name = x.leagueNameEn
}).ToList()
};
This is logically equivalent to a GroupBy, which is probably a more respectable way to do it. But somebody else thought of that before I did, so he earned the glory.

I would say the you need to reverse your query. So instead of starting with userLeagues, start with country and include the child leagues.

Related

EF Core LINQ groups results of the query incorrectly

I have a entity like this:
public class Vehicle
{
public long Id { get; set; }
public string RegistrationNumber { get; set; }
public string Model { get; set; }
public string Code { get; set; }
//other properties
}
Which has a unique constraint on { RegistrationNumber, Model, Code, /*two other properties*/ }
I'm trying to query the database to get an object that's structured like this:
[
{
"name": "Model1",
"codes": [
{
"name": "AAA",
"registrationNumbers": ["2", "3"]
},
{
"name":"BBB",
"registrationNumbers": ["3", "4"]
}
]
},
{
"name": "Model2",
"codes": [
{
"name": "BBB",
"registrationNumbers": ["4", "5"]
}
]
}
]
I.e. the list of Models, each models has a list of Codes that can co-appear with it, each code has a list of Registration Numbers that can appear with that Model and that Code.
I'm doing a LINQ like this:
var vehicles = _context.Vehicles.Where(/*some additional filters*/)
return await vehicles.Select(v => v.Model).Distinct().Select(m => new ModelFilterDTO()
{
Name = m,
Codes = vehicles.Where(v => v.Model== m).Select(v => v.Code).Distinct().Select(c => new CodeFilterDTO()
{
Name = c,
RegistrationNumbers = vehicles.Where(v => v.Model == m && v.Code == c).Select(v => v.RegistrationNumber).Distinct()
})
}).ToListAsync();
Which gets translated into this SQL query:
SELECT [t].[Model], [t2].[Code], [t2].[RegistrationNumber], [t2].[Id]
FROM (
SELECT DISTINCT [v].[Model]
FROM [Vehicles] AS [v]
WHERE --additional filtering
) AS [t]
OUTER APPLY (
SELECT [t0].[Code], [t1].[RegistrationNumber], [t1].[Id]
FROM (
SELECT DISTINCT [v0].[Code]
FROM [Vehicles] AS [v0]
WHERE /* additional filtering */ AND ([v0].[Model] = [t].[Model])
) AS [t0]
LEFT JOIN (
SELECT DISTINCT [v1].[RegistrationNumber], [v1].[Id], [v1].[Code]
FROM [Vehicles] AS [v1]
WHERE /* additional filtering */ AND ([v1].[Model] = [t].[Model])
) AS [t1] ON [t0].[Code] = [t1].[Code]
) AS [t2]
ORDER BY [t2].[Id]
Running this query in the SQL Server gets me correct sets of values. But when I perform the LINQ, I get an object like this:
[
{
"name": "Model1",
"codes": [
{
"name": "AAA",
"registrationNumbers": [/* every single registration number that is present among the records that passed the filters*/]
}
]
}
]
What is the problem may be, and how to fix it?
Edit: After playing with it for a bit, I'm even more confused than I was
This LINQ:
var vehicles = _context.Vehicles.Where(/*some additional filters*/)
return await vehicles.Select(v => v.Model).Distinct().Select(m => new ModelFilterDTO()
{
Name = m
}).ToListAsync();
Gives the expected result:
[
{
"name": "Model1"
},
{
"name": "Model2"
},
...
]
Hovewer this LINQ:
var vehicles = _context.Vehicles.Where(/*some additional filters*/)
return await vehicles.Select(v => v.Model).Distinct().Select(m => new ModelFilterDTO()
{
Name = m,
Codes = vehicles.Select(v=>v.Code).Distinct().Select(c => new CodeFilterDTO()
{
Name = c
})
}).ToListAsync();
Gives result like this:
[
{
"name": "Model1",
"codes": [
{
"name": "AAA"
}
]
}
]
Open for yourself GroupBy operator. Using double grouping you can achieve desired result.
var rawData = await _context.Vehicles
.Where(/*some additional filters*/)
.Select(v => new
{
v.Model,
v.RegistrationNumber,
v.Code
})
.ToListAsync(); // materialize minimum data
// perform grouping on the client side
var result = rawData
.GroupBy(v => v.Model)
.Select(gm => new ModelFilterDTO
{
Name = gm.Key,
Codes = gm
.GroupBy(x => x.Code)
.Select(gc => new CodeFilterDTO
{
Name = gc.Key,
RegistrationNumbers = gc.Select(x => x.RegistrationNumber).ToList()
}).ToList()
})
.ToList();

C# Remove duplicated objects from list and increase first

I have an list of objects => class Example { int quantity; string name; string comment; } and I want to remove all duplicates and increase the quantity by the number of duplicates that have the same name and comment.
Example:
[
{quantity: 1, name: "Hello", comment: "Hello there"},
{quantity: 2, name: "Bye", comment: "Good bye"},
{quantity: 1, name: "Hi", comment: "Hi there"},
{quantity: 1, name: "Hello", comment: "Hello there"},
{quantity: 1, name: "Bye", comment: "Good bye"},
]
and the result it should be:
[
{quantity: 2, name: "Hello", comment: "Hello there"},
{quantity: 3, name: "Bye", comment: "Good bye"},
{quantity: 1, name: "Hi", comment: "Hi there"}
]
I want to remove all duplicates
You haven't defined when two example objects are "duplicates". I guess, that you mean to say, that if two Examples objects have the same values for properties Name and Comment, then they are duplicates.
Normally you can use one of the overloads of Enumerable.GroupBy to find Duplicates. Use the overload with a parameter resultSelector to define exactly what you want as result.
IEnumerable<Example> examples = ...
var result = examples.GroupBy(
// key: Name-Comment
example => new
{
Name = example.Name,
Comment = example.Comment,
}
// parameter resultSelector: for every Name/Comment combination and all
// Examples with this Name/Comment combination make one new example:
(nameCommentCombination, examplesWithThisNameCommentCombination) => new Example
{
Name = nameCommentCombination.Name,
Comment = nameCommentCombination.Comment,
// Quantity is the sum of all Quantities of all Examples with this
// Name/Comment combination
Quantity = examplesWithThisNameCommentCombination
.Select(example => example.Quantity)
.Sum(),
});
This will only work if you want exact string equality. Are "Hello" and "HELLO" equal? And how about "Déjà vu" and "Deja vu"? Do you want case insensitivity for Name and Comment? And how about diacritical characters?
If you want more than just plain string equality, consider to create an ExampleComparer class.
class ExampleComparer : EqualityComparer<Example>
{
... // TODO: implement
}
Usage would be:
IEnumerable<Example> examples = ...
IEqualityComparer<Example> comparer = ...
var result = examples.GroupBy(example => example, // key
// resultSelector:
(key, examplesWithThisKey) => new Example
{
Name = key.Name,
Comment = key.Comment,
Quantity = examplesWithThiskey.Sum(example => example.Quantity),
},
comparer);
Implement the ExampleComparer
class ExampleComparer : EqualityComparer<Example>
{
public static IEqualityComparer<Example> ByNameComment {get;} = new ExampleComparer;
private static IEqualityComparer<string> NameComparer => StringComparer.CurrentCultureIgnoreCase;
private static IEqualityComparer<string> CommentComparer => StringComparer.CurrentCultureIgnoreCase;
I chose for two separate string comparers, so if later you decide different comparison, for instance Name has to match exactly, then you only have to change it here.
public override bool Equals (Example x, Example y)
{
// almost every Equals method starts with the following three lines
if (x == null) return y == null; // true if both null
if (y == null) return false; // false, because x not null
if (Object.ReferenceEquals(x, y)) return true; // same object
// return true if both examples are considered equal:
return NameComparer.Equals(x.Name, y.Name)
&& CommentComparer.Equals(x.Comment, y.Comment);
}
public override int GetHashCode(Example x)
{
if (x == null) return 5447125; // just a number
return NameComparer.GetHashCode(x.Name)
^ CommentComparer.GetHashCode(x.Comment);
}
Note: this will also work if Name or Comment are null or empty!
I used operator ^ (XOR), because that gives a fairly good hash if there are only two fields to consider. If you think that the vast majority of Examples have unique names, consider to check only property Name:
return NameComparer.GetHashCode(x.Name);
Because method Equals uses the NameComparer and CommentComparer to check for equality, make sure you use the same Comparers to calculate the HashCode.
Here's what I'd do:
Example[] before = new Example[]
{
new Example { Quantity = 1, Name = "Hello", Comment = "Hello there" },
new Example { Quantity = 2, Name = "Bye", Comment = "Good bye" },
new Example { Quantity = 1, Name = "Hi", Comment = "Hi there" },
new Example { Quantity = 1, Name = "Hello", Comment = "Hello there" },
new Example { Quantity = 1, Name = "Bye", Comment = "Good bye" },
};
Example[] after =
before
.GroupBy(x => new { x.Name, x.Comment }, x => x.Quantity)
.Select(x => new Example { Quantity = x.Sum(), Name = x.Key.Name, Comment = x.Key.Comment })
.ToArray();
That gives:
Perhaps this version is a little clearer:
Example[] after =
before
.GroupBy(
x => new { x.Name, x.Comment },
(k, xs) => new Example
{
Quantity = xs.Sum(x => x.Quantity),
Name = k.Name,
Comment = k.Comment
})
.ToArray();
Or perhaps this:
Example[] after =
(
from x in before
group x.Quantity by new { x.Name, x.Comment } into xs
select new Example
{
Quantity = xs.Sum(x => x),
Name = xs.Key.Name,
Comment = xs.Key.Comment
}
).ToArray();
Here's a simple solution, which gives you the answer in the List tata but you can do .ToArray() if you wish.
public class Example
{
public int quantity;
public string name;
public string comment;
}
Example[] toto = new Example[]
{
new Example
{
quantity = 1,
name = "Hello",
comment = "Hello there"
},
new Example
{
quantity = 2,
name = "Bye",
comment = "Good bye"
},
new Example
{
quantity = 1,
name = "Hi",
comment = "Hi there"
},
new Example
{
quantity = 1,
name = "Hello",
comment = "Hello there"
},
new Example
{
quantity = 1,
name = "Bye",
comment = "Good bye"
}
};
List<Example> tata = new List<Example>();
foreach (Example exa in toto)
{
bool found = false;
foreach (Example exb in tata)
{
if (exb.name == exa.name && exb.comment == exa.comment)
{
exb.quantity += exa.quantity;
found = true;
break;
}
}
if (!found)
{
tata.Add(exa);
}
}
A good exercise would be to LINQ that!

Flatten a collection based on an internal property

Given the follow class structure:
int Id;
string[] Codes;
And the following data:
Foo { Id = 1, Codes = new[] { "01", "02" } }
Foo { Id = 2, Codes = new[] { "02", "03" } }
Foo { Id = 3, Codes = new[] { "04", "05" } }
I would like to end up with the following structure.
Code = "01", Id = 1
Code = "02", Id = 1
Code = "02", Id = 2
Code = "03", Id = 2
Code = "04", Id = 3
Code = "05", Id = 3
I've got the following query, but it's giving me a collection as the Id rather than the flat structure I am after.
collection.GroupBy(f => f.Codes.SelectMany(c => c), f => f.Id,
(code, id) => new { Code = code, Id = id })
.ToArray()
What am I missing?
SelectMany can return multiple elements for each item as a single list
items
.SelectMany(foo => foo.Codes.Select(code => new { Id = foo.Id, Code = code }));
The answer of Diego Torres is correct; I would only add to it that this query is particularly concise and readable in the comprehension form:
var q = from foo in foos
from code in foo.Codes
select new { Code = code, foo.Id };

Linq Group By Into with additional parameters

I have following sql result:
Table Result
Goal is to group this result by ProjectId and SequenceId. The later JSON Result should look like this:
[
{
"ProjectId": 1,
"ProjectName": "Testprojekt 1",
"Sequences": [
{
"SequenceId": 2,
"SequenceName": "ESN_Tauschen"
},
{
"SequenceId": 3,
"SequenceName": "Demontage"
}
]
},
{
"ProjectId": 2,
"ProjectName": "Testprojekt 2",
"Sequences": [
{
"SequenceId": 3,
"SequenceName": "Demontage"
}
]
}
]
My current linq expression gives me following result:
[
{
"ProjectId": 1,
"Sequences": [
2,
3
]
},
{
"ProjectId": 2,
"Sequences": [
3
]
}
]
var context = new ReworkPlace();
var result = from p in context.Projects
join rs in context.ReworkStations on p.ProjectId equals rs.ProjectId
join l in context.ReworkStationReworkConfigurationLinkSets on rs.ReworkStationId equals
l.ReworkStationId
join rc in context.ReworkConfigurations on l.ReworkConfigurationId equals rc.ReworkConfigurationId
join s in context.Sequences on rc.SequenceId equals s.SequenceId
group s.SequenceId by p.ProjectId into g
select new
{
ProjectId = g.Key,
Sequences = g.ToList()
};
return Json(result, JsonRequestBehavior.AllowGet);
I dont know how I have to adapt my linq expression to inlcude the not grouped properties like ProjectName, SequenceId and SequenceName into my json result.
Would be nice if somebody could help me.
To get the desired result without totally rewriting the query, replace the grouping part:
group s.SequenceId by p.ProjectId into g
select new
{
ProjectId = g.Key,
Sequences = g.ToList()
};
with something like this:
group new { p, s } by p.ProjectId into g
let p = g.FirstOrDefault().p
select new
{
ProjectId = g.Key,
ProjectName = p.Name,
Sequences =
(from e in g
group e.s by e.s.SequenceId into g2
let s = g2.FirstOrDefault()
select new
{
SequenceId = g2.Key,
SequenceName = s.Name
}).ToList()
};
The trick is to include between group and by the data that will be needed inside the grouping (in addition to the Key which is what you put after by).
To get additional fields, you either include them in the grouping key, or if they are one and the same for the defined grouping key, use the FirstOrDefault to get them from the first record in the grouping as in the above example.

Extensible relational division in LINQ

In this example class IcdPatient represents a many-to-many relationship between a Patient table (not shown in this example) and a lookup table Icd.
public class IcdPatient
{
public int PatientId { get; set; }
public int ConditionCode { get; set; }
public static List<IcdPatient> GetIcdPatientList()
{
return new List<IcdPatient>()
{
new IcdPatient { PatientId = 100, ConditionCode = 111 },
new IcdPatient { PatientId = 100, ConditionCode = 222 },
new IcdPatient { PatientId = 200, ConditionCode = 111 },
new IcdPatient { PatientId = 200, ConditionCode = 222 },
new IcdPatient { PatientId = 3, ConditionCode = 222 },
};
}
}
public class Icd
{
public int ConditionCode { get; set; }
public string ConditionName { get; set; }
public static List<Icd> GetIcdList()
{
return new List<Icd>()
{
new Icd() { ConditionCode =111, ConditionName ="Condition 1"},
new Icd() { ConditionCode =222, ConditionName ="Condition 2"},
};
}
}
I would like for the user to be able to enter as many conditions as they want, and get a LINQ object back that tells them how many PatientIds satisfy that query. I've come up with:
List<string> stringFilteredList = new List<string> { "Condition 1", "Condition 2" };
List<int> filteringList = new List<int> { 111,222 };
var manyToMany = IcdPatient.GetIcdPatientList();
var icdList = Icd.GetIcdList();
/*Working method without joining on the lookup table*/
var grouped = from m in manyToMany
group m by m.PatientId into g
where g.Count() == filteringList.Distinct().Count()
select new
{
PatientId = g.Key,
Count = g.Count()
};
/*End*/
foreach (var item in grouped)
{
Console.WriteLine(item.PatientId);
}
Let's say that IcdPatient has a composite primary key on both fields, so we know that each row is unique. If we find the distinct number of entries in filteringList and do a count on the number of times a PatientId shows up, that means we've found all the people who have all conditions. Because the codes can be esoteric, I would like to do something like
let the user table in the ConditionName in type Icd and perform the same operation. I've not used LINQ this way a lot and I've gathered:
List<int> filteringList = new List<int> { 111,222 };
List<string> stringFilteredList= new List<string>{"Condition 1","Condition 2" };
filteringList.Distinct();
var manyToMany = IcdPatient.GetIcdPatientList();
var icdList = Icd.GetIcdList();
/*Working method without joining on the lookup table*/
var grouped = from m in manyToMany
join i in icdList on
m.ConditionCode equals i.ConditionCode
//group m by m.PatientId into g
group new {m,i} by new { m.ConditionCode }into g
where g.Count() == filteringList.Distinct().Count()
select new
{
Condition = g.Key.ConditionCode
};
/*End*/
but can't get anything to work. This is essentially a join on top of my first query, but I'm not getting what I need to group on.
You don't need to group anything in this case, just use a join and a contains:
List<string> stringFilteredList= new List<string>{"Condition 1","Condition 2" };
var patients =
from icd in Icd.GetIcdList()
join patient in IcdPatient.GetIcdPatientList() on icd.ConditionCode equals patient.ConditionCode
where stringFilteredList.Contains(icd.ConditionName)
select patient.PatientId;
Let's say that IcdPatient has a composite primary key on both fields, so we know that each row is unique. If we find the distinct number of entries in filteringList and do a count on the number of times a PatientId shows up, that means we've found all the people who have all conditions. Because the codes can be esoteric, I would like to do something like let the user table in the ConditionName in type Icd and perform the same operation.
I believe you're asking:
Given a list of ConditionCodes, return a list of PatientIds where every patient has every condition in the list.
In that case, the easiest thing to do is group your IcdPatients table by Id, so that we can tell every condition that a patient has by looking once. Then we check that every ConditionCode we're looking for is in the group. In code, that looks like:
var result = IcdPatient.GetIcdPatientList()
// group up all the objects with the same PatientId
.GroupBy(patient => patient.PatientId)
// gather the information we care about into a single object of type {int, List<int>}
.Select(patients => new {Id = patients.Key,
Conditions = patients.Select(p => p.ConditionCode)})
// get rid of the patients without every condition
.Where(conditionsByPatient =>
conditionsByPatient.Conditions.All(condition => filteringList.Contains(condition)))
.Select(conditionsByPatient => conditionsByPatient.Id);
In query format, that looks like:
var groupedInfo = from patient in IcdPatient.GetIcdPatientList()
group patient by patient.PatientId
into patients
select new { Id = patients.Key,
Conditions = patients.Select(patient => patient.ConditionCode) };
var resultAlt = from g in groupedInfo
where g.Conditions.All(condition => filteringList.Contains(condition))
select g.Id;
Edit: If you'd also like to let your user specify the ConditionName rather than the ConditionId then simply convert from one to the other, storing the result in filteringList, like so:
var conditionNames = // some list of names from the user
var filteringList = Icd.GetIcdList().Where(icd => conditionNames.Contains(icd.ConditionName))
.Select(icd => icd.ConditionCode);

Categories

Resources