Linq join collection on collection - c#

The Series object contains a property called Skus and it is IEnumerable
If this sku is in the allowed list of skus then I need that series.
In my example below, I'm joining on s.SeriesId which is not correct.
I believe it needs to be the collection s.Skus
I only want to return a series that has the contained sku in the collection.
IEnumerable<Data.Models.Series> series = await _seriesRepository.GetSeriesAsync(Properties.Settings.Default.Channel, page, limit);
string[] skusInSeries = series?.SelectMany(x => x.Skus).Distinct().ToArray();
IEnumerable<string> itemNumbers = GetAllowedSkus(Customer, Shipto, EnvironmentCode, AcceptLanguage, skusInSeries, Warehouse);
var selected = from s in series
join i in itemNumbers
on s.SeriesId equals i //s.Skus IEnumerable<string>
select s;

var selected = from s in series
where itemNumbers.Any(i => s.Skus.Contains(i))
select s;
Or the other way:
var selected = from s in series
where s.Skus.Any(sku => itemNumbers.Contains(sku))
select s;
I am guessing there are more Skus than itemNumbers typically and the the first choice is better. It may also be better to change itemNumbers to a list that can be passed to the database:
var itemNumbers = GetAllowedSkus(Customer, Shipto, EnvironmentCode, AcceptLanguage, skusInSeries, Warehouse).ToList();
var selected = from s in series
where itemNumbers.Any(i => s.Skus.Contains(i))
select s;
If a SQL (or other) database isn't involved, you would convert itemNumbers to a HashSet for efficient lookup:
var itemNumbers = new HashSet<string>(GetAllowedSkus(Customer, Shipto, EnvironmentCode, AcceptLanguage, skusInSeries, Warehouse));
var selected = from s in series
where s.Skus.Any(sku => itemNumbers.Contains(sku))
select s;

Related

foreach first list and see if it exists in the 2nd list and if it does then get other values from 2nd list

foreach first list by using ID and ID_SCH and see if it exists in the 2nd list and if it does then get other values from 2nd list.
string getRecords = "SELECT .....";
List <Records> firstList = ReadAll(getRecords, reader => {
return new Records(
reader.ReadByName(NameRecord.ID, string.Empty),
reader.ReadByName(NameRecord.ID_SCH, string.Empty)
);
});
string getAllRecords = "SELECT .....";
List <Records> secondList = ReadAll(getAllRecords, reader => {
return new Records(
reader.ReadByName(NameRecord.ID, string.Empty),
reader.ReadByName(NameRecord.ID_SCH, string.Empty),
reader.ReadByName(NameRecord.BSID, string.Empty),
reader.ReadByName(NameRecord.BSID_SCH, string.Empty),
);
});
// currently I am able to use id only. But I would like to include `id` and `id_sch` as well in the below statement and then get the value of `BSID` and `BSID_SCH`.
var aa= data.Select(l1 => l1.Id).Intersect(secondList .Select(l2 => l2.Id)).ToList();
Acceptance criteria
1.foreach test in the first list see if it exists in the 2nd list. some how I managed to use idto get the result but I would like to useid_sch` as well.
if it does, get the tests that are excluded from 2nd list like BSID and BSID_SCH
after getting the BSID and BSID_SCH value from acceptance criteria 2, need to check if these BSID and BSID_SCH value exist in firstlist
If it exists in the first list then how to get the value of id idsch from first list.
You can use tuples to combine the two values. In a first step we add the values of the first list into a HashSet<T>, so that we can test whether an item exists fast and easily.
var l1Exclude = data
.Select(l1 => (l1.Id, l1.id_sch))
.ToHashSet();
var l1Include = data
.Select(l1 => (l1.BSID, l1.BSID_SCH))
.ToHashSet();
Now, you can use this result to filter the second list with of all its properties
IEnumerable<Records> result = secondList
.Where(l2 => l1Include.Contains((l2.BSID, l2.BSID_SCH)) &&
!l1Exclude.Contains((l2.Id, l2.id_sch)));
But a fundamental question is, whether it would not be easier and faster to perform this logic in SQL directly yielding the expected result. Something like this
SELECT b.*
FROM
Table2 b
INNER JOIN Table1 a
ON b.BSID = a.BSID AND b.BSID_SCH = a.BSID_SCH
WHERE
NOT EXISTS (SELECT *
FROM Table1 aa
WHERE aa.Id = b.Id AND aa.IdSch = b.IdSch)

LINQ: select specific value in a datatable column

In table I have 4 Columns GroupName, Display, Value and ID
How can I just show a specific data in display. I only want to show some of the groupNames Data
for example I only want to show Groupname = company and display = Forbes
Here's my linq
sample = (from c in smsDashboardDBContext.CodeDefinitions
orderby c.Display ascending
select new CodeDefinitionDTO
{
GroupName = c.GroupName,
Display = c.Display,
Value = c.Value,
Id = c.Id
}).ToList();
You can add a where statement in the query.
where c.GroupName == "company" && c.Display == "Forbes"
I only want to show some of the groupNames Data for example I only want to show Groupname = company and display = Forbes
Before the ToList, use a Where to keep only those items that you want to show:
var company = ...
var forbes = ...
var result = smsDashboardDBContext.CodeDefinitions
.OrderBy(codeDefinition => codeDefintion.Display)
.Select(codeDefinition => new CodeDefinitionDTO
{
Id = codeDefinition.Id,
GroupName = codeDefinition.GroupName,
Display = codeDefinition.Display,
Value = codeDefinition.Value,
})
.Where(codeDefinition => codeDefition.GroupName == company
&& codeDefintion.Display == forbes);
In words:
Order all codeDefinitions that are in the table of CodeDefintions by ascending value of property codeDefintion.Display.
From every codeDefinition in this ordered sequence make one new CodeDefinitionDTO with the following properties filled: Id, GroupName, Display, Value
Frome every codeDefintion in this sequence of CodeDefinitionDTOs, keep only those codeDefinitions that have a value for property GroupName that equals company and a value for property Display that equals forbes.
There is room for improvement!
Suppose your table has one million elements, and after the Where, only five elements are left. Then you will have sorted almost one million elements for nothing. Consider to first do the Where, then the Order and finally a Select.
In LINQ, try to do aWhere as soon as possible: all following statements will have to work on less items
In LINQ, try to do a Select as late as possible, preferrably just before the ToList / FirstOrDefault / ... This way the Select has to be done for as few elements as possible
So first the Where, then the OrderBy, then the Select, and finally the ToList / FirstOrDefault, etc:
var result = smsDashboardDBContext.CodeDefinitions
.Where(codeDefinition => ...);
.OrderBy(codeDefinition => codeDefintion.Display)
.Select(codeDefinition => new CodeDefinitionDTO
{
...
});

How do I drill down to specific data in my LINQ query result?

I want to drill down into a particular item in my data and output the list of results to the output window. My query result looks like this
private IEnumerable<DataRow> _data;
var query = from data in this._data
group data by data.Field<string>("Form Name") into groups //same as Form ID
select new
{
formName = groups.Key,
items = from d in groups
group d by d.Field<string>("Item Name") into grps
let name = grps.Key
let documentIDGroups = grps.GroupBy(t => t.Field<string>("Document ID"))
let documentIDGroupsCount = documentIDGroups.Count()
let distinctDocumentValueCount = from data in documentIDGroups
select new
{
docID = data.Key,
distinctDocValueCount = data.Where(t => string.IsNullOrEmpty(t.Field<string>("Document Value").Trim()) == false).Select(t => t.Field<string>("Document Value")).Distinct().Count()
}
let sum = distinctDocumentValueCount.Sum(t => t.distinctDocValueCount)
let distinctItemsNames = from data in grps
select data.Field<string>("Item Name").Distinct().Count()
let count = distinctItemsNames.Count()
select new
{
itemName = name,
documentIDGroups,
documentIDGroupsCount,
averageChoices = Math.Round(((decimal)sum / documentIDGroupsCount), 2),
distinctDocumentValueCount,
sum
}
};
So on that query result I want to drill down into a particular form name, and from there get a particular Item Name and so on
so the first step is to get the grouping of items and I have
var items = from d in query where d.formName == "someName" select d.items;
but I don't know how to isolate the items by a particular string.
I want to do the following
var item = from d in items where d.itemName == "anItemName" select d;
But I don't know the syntax.
Use the .FirstOrDefault extension if you expect a single item to be returned from your query. SO:
var item = (from d in items where d.itemName == "anItemName" select d).FirstOrDefault();

Linq query change from List<string> to List<T> where clause

I have a linq query that works when it I had a list of a single value now that I change to having a List that has several properties I need to change the where clause
So this works:
List<string> etchList = new List<string>();
etchList.Add("24");
var etchVect = (from vio in AddPlas
where etchList.Any(v => vio.Key.Formatted.Equals(v))
let firstOrDefault = vio.Shapes.FirstOrDefault()
where firstOrDefault != null
select new
{
EtchVectors = firstOrDefault.Formatted
}).ToList();
However I have a new hard coded list (which will represent incoming data:
List<ExcelViolations> excelViolations = new List<ExcelViolations>();
excelViolations.Add(new ExcelViolations
{
VioID = 24,
RuleType = "SPACING",
VioType = "Line-Line",
XCoordinate = 6132,
YCoordinate = 10031.46
});
So the NEW Linq query looks like this, but is obviously will not work as
AddPlas is a List and so using this other list of excelviolations, I wish to have it do where on each one of the properties in the excelviolations list
var etchVect = (from vio in AddPlas
where excelViolations.Any(vioId => vio.Key.Formatted.Equals(vioId))
let firstOrDefault = vio.Shapes.FirstOrDefault()
select new
{
EtchVectors = firstOrDefault.Formatted
}).ToList();
Now, since this is a list within a list, I would like to do something like add in each of the properties
so for example:
where excelViolations.VioID.Any(vioId => vio.Key.Formatted.Equals(vioId))
However that is not possible, but you see that I'm trying to access the property of VioID that is in the excelViolations and match it to the Key which is in vio list
Just change this line
where excelViolations.Any(vioId => vio.Key.Formatted.Equals(vioId))
to
where excelViolations.Any(excelVio => vio.Key.Formatted.Equals(excelVio.VioID))
then i thought it will works

Data duplication in DataGrid. Problem with LINQ query

I have such a query but it gives me wrong output.
I have two data collections abcdata && xyzdata. each collection consists of an anonymous objects that have Group, Name properties. What I need to do is to get resulting collection with merged groups from abcdata and xyzdata respectively.
if(this.AbcDataGrid.ItemsSource != null && this.XyzDataGrid.ItemsSource != null)
{
var abcdata = ((IEnumerable<dynamic>)this.AbcDataGrid.ItemsSource).ToList().OrderByDescending(x => x.Id);
var xyzdata = ((IEnumerable<dynamic>)this.XyzDataGrid.ItemsSource).ToList().OrderByDescending(x => x.Id);
var result = from i1 in abcdata
from i2 in xyzdata
select new
{
Name = i1.Name,
Group = i1.Group.ToString() + i2.Group.ToString()
};
this.ResultGrid.ItemsSource = result.ToList();
}
While I was expecting to get DataGrid populated with list of new {Name, Group} objects I have very strange result:
I believe that what you are trying to do is join both data collections.
The linq query that you are doing is returning the correct results as what you are asking with it is: for every element of abcdata, and for every element of xyzdata, return the object you are constructing. So, if abcdata has 3 elements and xyzdata has 5 elements, the result will have 15 elements.
If you want: for each element of abcdata, select the elements of xyzdata that have the same name and concatenate the Groups, what you need is a Join.
Something like
var result = from i1 in abcdata
join i2 in xyzdata on i1.Name equals i2.Name
select new
{
Name = i1.Name,
Group = i1.Group.ToString() + i2.Group.ToString()
};

Categories