LINQ contains in a list within a list - c#

I'm trying to use a contains in a list within a list but been stuck on this one:
var postFilter = PredicateBuilder.False<Company>();
// Loop through each word and see if it's in the company's facility
foreach (var term in splitSearch)
{
var sTerm = term.Trim();
postFilter = postFilter.Or(x =>
x.Facilities.Contains(y=>
y.Facility.Name.ToUpper().Contains(sTerm)) ||
x => x.Facilities.Contains(y =>
y.Facility.Description.ToUpper().Contains(sTerm)));
}
Postfilter is a list of companies, a company has a list of companyfacility items which is a 1:m relationship. A facility also has a 1:m relationship with this table. The x thus represents a companyfacility object. The y should represent a facility object.
(So a company can have many facilities, and a facility can belong to many companies. In between i use the companyfacility table for additional information of that companies facility - example, a specific company can have a lathe table that goes to diameter 300 where other companies would go to diameter 250, so it's important to have the table in between)
I want to return the companies which have the sTerm in their facility name or facility description, but this linq statement is invalid.
Thanks for the help!

Here is the LINQ and some example code:
void Main()
{
List<Company> postFilter = new List<UserQuery.Company>();
var sTerm = "XXX".Trim().ToUpper();
postFilter = postFilter.Where(x =>
x.Facilities.Any(f => f.Name.ToUpper().Contains(sTerm)
|| f.Description.ToUpper().Contains(sTerm))).ToList();
}
public class Company
{
public string CompanyName { get; set; }
public List<Facility> Facilities { get; set; }
}
public class Facility
{
public string Name { get; set; }
public string Description { get; set; }
}

Related

C# GroupBy Trouble

As of now, I am trying to create a list that groups based on certain criteria and then display that list in the view.
I have two database tables and one is an association table.
First Table
public partial class InitialTraining
{
public InitialTraining()
{
InitialTrainingAssociations = new HashSet<InitialTrainingAssociation>();
}
public int Id { get; set; }
[ForeignKey("MedicInfo")]
public int TfoId { get; set; }
[ForeignKey("InstructorInfo")]
public int? InstructorId { get; set; }
[ForeignKey("PilotInfo")]
public int? PilotId { get; set; }
public DateTime DateTakenInitial { get; set; }
public decimal FlightTime { get; set; }
public bool Active { get; set; }
[StringLength(2000)]
public string Narrative { get; set; }
[Required]
[StringLength(20)]
public string TrainingType { get; set; }
[ForeignKey("CodePhase")]
public int PhaseId { get; set; }
[ForeignKey("PhaseTrainingType")]
public int PhaseTrainingTypeId { get; set; }
public string EnteredBy { get; set; }
public DateTime? EnteredDate { get; set; }
public virtual MedicInfo MedicInfo { get; set; }
public virtual MedicInfo InstructorInfo { get; set; }
public virtual MedicInfo PilotInfo { get; set; }
public virtual Code_Phase CodePhase { get; set; }
public virtual Code_PhaseTrainingType PhaseTrainingType { get; set; }
public virtual ICollection<InitialTrainingAssociation> InitialTrainingAssociations { get; set; }
}
Second Table (Association Table)
public class InitialTrainingAssociation
{
public int Id { get; set; }
[ForeignKey("InitialTraining")]
public int InitialTrainingId { get; set; }
[ForeignKey("CodePerformanceAnchor")]
public int? PerformanceAnchorId { get; set; }
[ForeignKey("GradingSystem")]
public int? GradingSystemId { get; set; }
public virtual AviationMedicTraining.CodePerformanceAnchor CodePerformanceAnchor { get; set; }
public virtual InitialTraining InitialTraining { get; set; }
public virtual GradingSystem GradingSystem { get; set; }
}
Here is my GroupBy in C#.
// get list of initial training record ids for statistics
var lstInitialTrainings = db.InitialTrainings.Where(x => x.TfoId == medicId && x.Active).Select(x => x.Id).ToList();
// get list of initial training performance anchors associated with initial training records
var lstPerformanceAnchors = db.InitialTrainingAssociations
.Where(x => lstInitialTrainings.Contains(x.InitialTrainingId)).GroupBy(t => t.PerformanceAnchorId)
.Select(s => new MedicStatistic()
{
PerformanceAnchorName = db.CodePerformanceAnchor.FirstOrDefault(v => v.Id == s.Key).PerformanceAnchor,
AnchorCount = s.Count()
}).ToList();
My Goal
Obviously from my code I want to group by the performance anchor in the association table, but I need more information from the Initial Training table to include in my ViewModel MedicStatistic, but I am having trouble figuring out the best way to do it.
My overall goal is to be able to get the most recent time a performance anchor was completed from the Initial Training table.
Visual
Initial Training Table (not all fields were captured in snippet b/c they're not important for the purpose of this question)
Initial Training Association Table
What I expect
So, from the pictures provided above as you can see there are multiple 1's for performance anchor id's in the association table, but they each have different InitialTrainingId. So, this specific performance anchor has been done multiple times, but I need to get the most recent date from the Initial Training table. Also, I need to get the corresponding grade with the anchor from the Grading System table, based on the most recent date.
So, for the performance anchor that equals 1.. I would want the grade that corresponds to the InitialTrainingId of 17 because that record was the most recent time that the performance anchor of 1 was done.
If you have any questions please let me know.
You want the data grouped by CodePerformanceAnchor, so the most natural way to start the query is at its DbSet which immediately eliminates the necessity of grouping:
from pa in db.CodePerformanceAnchors
let mostRecentInitialTraining
= pa.InitialTrainingAssociations
.Select(ita => ita.InitialTraining)
.OrderByDescending(tr => tr.DateTakenInitial)
.FirstOrDefault()
select new
{
pa.PerformanceAnchor,
mostRecentInitialTraining.DateTakenInitial,
mostRecentInitialTraining. ...
...
AnchorCount = pa.InitialTrainingAssociations.Count()
}
As you see, only navigation properties are used and the query as a whole is pretty straightforward. I assume that the PerformanceAchor class also has an InitialTrainingAssociations collection.
I can't guarantee that EF will be able to execute it entirely server-side though, that's always tricky with more complex LINQ queries.
I'm going to ignore the virtual properties in your InitialTrainingAssociation class, since you didn't mention anything about them and it's not immediately apparent to me whether they actually contain data, or why they are virtual.
It seems like IQueryable.Join is the easiest way to combine the data you want.
In the following example, we will start with the entries from the InitialTrainings table. We will then Join with the InitialTrainingAssociations table, which will result in a collection of paired InitialTraining and InitialTrainingAssociation objects.
var initialTrainingResults =
// Start with the InitialTrainings data.
db.InitialTrainings
// Add association information.
.Join(
// The table we want to join with
db.InitialTrainingAssociations,
// Key selector for the outer type (the type of the collection
// initiating the join, in this case InitialTraining)
it => it.Id,
// Key selector for the inner type (the type of the collection
// being joined with, in this case InitialTrainingAssociation)
ita => ita.InitialTrainingId,
// Result selector. This defines how we store the joined data.
// We store the results in an anonymous type, so that we can
// use the intermediate data without having to declare a new class.
(InitialTraining, InitialTrainingAssociation) =>
new { InitialTraining, InitialTrainingAssociation }
)
From here, we can add data from the PerformanceAnchors and GradingSystems tables, by performing more Joins. Each time we perform a Join, we will add a new entity to our anonymous type. The result will be a collection of anonymous types representing data we retrieved from the database.
// Add performance anchor information.
.Join(
db.PerformanceAnchors,
x => x.InitialTrainingAssociation.PerformanceAnchorId,
pa => pa.Id,
(x, PerformanceAnchor) =>
new { x.InitialTrainingAssociation, x.InitialTraining, PerformanceAnchor }
)
// Add grading system information.
.Join(
db.GradingSystems,
x => x.InitialTrainingAssociation.GradingSystemId,
gs => gs.Id,
// No need for InitialTrainingAssociation anymore, so we don't
// include it in this final selector.
(x, GradingSystem) =>
new { x.InitialTraining, x.PerformanceAnchor, GradingSystem }
);
(This was a verbose example to show how you can join all the tables together. You can use less Joins if you don't need to access all the data at once, and you can filter down the InitialTrainings collection that we start with if you know you only need to access certain pieces of data.)
At this point, initialTrainingResults is an IEnumerable containing one entry for each association between the InitialTrainings, PerformanceAnchors, and GradingSystems tables. Essentially, what we've done is taken all the InitialTrainingAssociations and expanded their Ids into actual objects.
To get the most recent set of data for each performance anchor:
var performanceAnchors = initialTrainingResults
// Group by each unique Performance Anchor. Remember, the IEnumerable
// we are operating on contains our anonymous type of combined Training,
// Performance Anchor and Grading data.
.GroupBy(x => x.PerformanceAnchor.Id)
// Order each Performance Anchor group by the dates of its training,
// and take the first one from each group
.Select(g => g.OrderByDescending(x => x.InitialTraining.DateTakenInitial).First());
In the Select you can order the group result to get the most recent associated InitialTraining by DateTakenInitial, and from there get the desired data
//...omitted for brevity
.GroupBy(t => t.PerformanceAnchorId)
.Select(g => {
var mostRecent = g.OrderByDescending(_ => _.InitialTraining.DateTakenInitial).First();
// get the corresponding grade with the anchor from the Grading System table
var gradeid = mostRecent.GradingSystemId;
var gradingSystem = mostRecent.GradingSystem;
//get the most recent date from the Initial Training
var mostRecentDate = mostRecent.InitialTraining.DateTakenInitial
//..get the desired values and assign to view model
var model = new MedicStatistic {
//Already have access to CodePerformanceAnchor
PerformanceAnchorName = mostRecent.CodePerformanceAnchor.PerformanceAnchor
AnchorCount = g.Count(),
MostRecentlyCompleted = mostRecentDate,
};
return model;
});

LINQ grouping multiple fields and placing non-unique fields into list

I have a list of objects, TargetList populated from the database which I want to group together based on the AnalyteID, MethodID and InstrumentID fields, but the Unit fields will be stored in a list applicable to each grouped object.
Furthermore, it is only possible for one of the available units to have a target assigned to it. Therefore, during the grouping I need a check to see if a target is available and, if so, skip creation of the unit list.
The TargetList object contains the following attributes:
public int id { get; set; }
public int AnalyteID { get; set; }
public string AnalyteName { get; set; }
public int MethodID { get; set; }
public string MethodName { get; set; }
public int InstrumentID { get; set; }
public string InstrumentName { get; set; }
public int UnitID { get; set; }
public string UnitDescription { get; set; }
public decimal TargetMean { get; set; }
public List<Unit> Units { get; set; }
I have a method for multi-grouping using LINQ:
TargetList.GroupBy(x => new { x.AnalyteID, x.MethodID, x.InstrumentID })...
But unsure as to how to check for a target at a row before extracting all available units at current group if target doesn't exist.
I created a solution which groups all rows returned from the database based on the AnalyteID, MethodID and InstrumentID ('names' of each of these are included in the grouping aswell).
Additionally, all non-unique Unit attributes (UnitID and UnitDescription) are placed into a list only if the TargetMean is 0.
targetViewModel.TargetList
// Group by unique analyte/method/instrument
.GroupBy(x => new { x.AnalyteID, x.AnalyteName, x.MethodID, x.MethodName, x.InstrumentID, x.InstrumentName })
// Select all attributes and collect units together in a list
.Select(g => new TargetView
{
id = g.Max(i => i.id),
AnalyteID = g.Key.AnalyteID,
AnalyteName = g.Key.AnalyteName,
MethodID = g.Key.MethodID,
MethodName = g.Key.MethodName,
InstrumentID = g.Key.InstrumentID,
InstrumentName = g.Key.InstrumentName,
TargetMean = g.Max(i => i.TargetMean),
UnitID = g.Max(i => i.UnitID),
UnitDescription = g.Max(i => i.UnitDescription),
// only extract units when target mean is 0
Units = g.Where(y => y.TargetMean == 0)
.Select(c => new Unit { ID = c.UnitID, Description = c.UnitDescription }).ToList()
}).ToList();
Note: The Max method is used to extract any required non-key attributes, such as the TargetMean/id. This works fine because only one row will ever be returned if a TargetMean exists.
It does feel 'dirty' to use the Max method in order to obtain all other non-key attributes though so if anyone has any other suggestions, please feel free to drop an answer/comment as I am interested to see if there are any cleaner ways of achieving the same result.

Retrieve Related Data (Multi-Level)

I have three classes:
Country.
District.
Province
public class Country
{
public Country()
{
Districts = new HashSet<District>();
}
public string Name { get; set; }
public virtual ICollection<District> Districts { get; private set; }
}
public class Ditrict
{
public Ditrict()
{
Provinces = new HashSet<Province>();
}
public string Name { get; set; }
public virtual ICollection<Province> Provinces { get; private set; }
}
public class Province
{
public string Name { get; set; }
}
What I need to accomplish is how to include all Provinces, Districts (Even if there is no Provinces), Country (Even if there is no Districts) in one command using Ling.
The reason is that I am going to use DevExpress Grid, so the user can see the Country and add a new District, and can see the District and add a new Province.
My all attempts failed, because what I get is the Country that have Disctrict and District that have Province.
Btw, I am using:
VS 2015
EF Core
MS SQL 2016
[Solution]
After many attempts and searching, I discovered that I need to use .ThenInclude to the third level Provinces. Like:
Countries = dbContext
.Countries
.Include(c => c.Districts)
.ThenInclude(p => p.Provinces)
.ToList();
It would have been nice if you could send us what you have tried. Roughly speaking you can use Include to eager load dependent entities and collections. If you query the chain of entities starting from Country it will retrieve all the countries even if they don't have Districts:
using (var dbContext = new YourDbContext())
{
return dbContext.Countries
.Include(c => c.Districts.Select(d => d.Provinces))
.ToList();
}
I guess the reason you retrieve countries only if they have districts, etc, is because your query starts from the other end of the chain (Province) rather than starting from Country as above.

EF eagerly load filtered child objects / filter only childs

my EF Poco classes structure as below and what I try to achieve is to get all CategoryProducts Including Products and ProductName but only ProductNames having languageid=1
I dont want to filter Root objects. I need not all productnames are loaded but only productname with languageid=1
I dont know how to achieve this. for example, I tried query below
var products = db.CategoryProduct.Include("Product.ProductName").
Where(p=>p.Product.ProductName.Any(a=>a.LanguageId==1)).ToList();
But this one filters all categoryProducts which have ProductName with languageid=1. this is not what I want because all products have names in 5 different languages. I just dont want to load eagerly for each product 5 times but only 1 time for languageid=1
public partial class CategoryProduct
{
[Key]
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
public partial class Product
{
public virtual ICollection<ProductName> ProductName { get; set; }
}
public partial class ProductName
{
public int ProductId { get; set; }
public int LanguageId { get; set; }
public string Name { get; set; }
public virtual Product Product { get; set; }
}
I'm afraid that using eager loading you can't filter the related entities unless you project your query in an anonymous type or a DTO:
var products = db.CategoryProduct.Include(c=>c.Product.ProductName)
.Select(c=> new CategoryProductDTO()
{
//...
ProductNames= c.Product.ProductName.Where(a=>a.LanguageId==1)
})
.ToList();
If you don't want to project your query and you want to load specific related entities, then I suggest you to use Explicit Loading:
var catproduct = db.CategoryProduct.Include(c=>c.Product).FirstOrDefault();// This is just an example, select the category product that you need to load the related entities
context.Entry(catproduct.Product)
.Collection(b => b.ProductName)
.Query()
.Where(pn => pn.LanguageId==1)
.Load();
But IMHO the first variant is the way to go
this is not easily doable, but something like the following may do it:
from cp in db.CategoryProduct.Include(x => x.Product)
from pn in cp.Product.ProductName.Where(x => x.LanguageId == 1).DefaultIfEmpty()
select new {
Cat = cp,
Name1 = pn.Name
}
then you have you product in Cat.Product, and the name in Name1.
The basic idea is to set a LEFT JOIN on ProductName.

What C# Data Structure Should I Use?

This question pertains to C#, LINQ grouping and Collections.
I'm currently working on a grouping issue and I wanted to get some feedback from the community. I've encountered this particular problem enough times in the past that I'm thinking of writing my own data structure for it. Here are the details:
Suppose you have a grouping that consists of a manufacturer and products and the data structure is grouped by manufacturer. There are many manufacturers and many products. The manufacturers each have a unique name and id. The products may have similar names, but they do have unique ids. The proceeding list represents an example.
Ford 1, Fiesta 1945, Taurus 6413, Fusion 4716, F1 8749,
Toyota 2, Camry 1311, Prius 6415, Corolla 1117, Tacoma 9471
Chevrolet 3, Silverado 4746, Camero 6473, Volt 3334, Tahoe 9974
etc...
The data structure I would use to represent this would be
IEnumerable<Manufacturer, ManufacturerID, IEnumerable<Product, ProductID>>
but this doesn't exist. So my question I want to ask the community is what data structure would you recommend and why?
Update:
I would like to keep the types anonymous and avoid the dynamic keyword. So the data structure would be something like
IEnumerable SomeDataStructure<T, U, V>
The other requirement is that is can have duplicated items. Here's kind of what I'm thinking:
public class MyDataStructure<T, U, V>
{
// make this like a list, not a dictionary
}
Update:
I decided to go with a Tuple data structure. It's very powerful and easy to query against. The proceding code is how I ended up using it to create my manufacturer-vehicle relationships. The result is a nicely ordered data structure that has unique manufacturers ordered by name with their associated unique vehicles ordered by name.
public class ManufacturersVehicles
{
public int ManufacturerID { get; set; }
public string ManufacturerName { get; set; }
public int VehicleID { get; set; }
public string VehicleName { get; set; }
}
// "data" actually comes from the database. I'm just creating a list to use a mock structure to query against.
var data = new List<ManufacturersVehicles>
{
{ ManufacturerID = 1, Manufacturer = "Ford", VehicleID = 1945, VehicleName = "Fiesta" },
{ ManufacturerID = 1, Manufacturer = "Ford", VehicleID = 6413, VehicleName = "Taurus" },
{ ManufacturerID = 1, Manufacturer = "Ford", VehicleID = 4716, VehicleName = "Fusion" },
etc...
};
// Get a collection of unique manufacturers from the data collection and order it by the manufacturer's name.
var manufacturers = data.Select(x => new { ManufacturerID = x.ManufacturerID, ManufacturerName = x.ManufacturerName })
.Distinct()
.OrderBy(x => x.ManufacturerName)
.Select(x => Tuple.Create(x.ManufacturerID, x.ManufacturerName, new Dictionary<int, string>()))
.ToList();
// Add the manufacturer's vehicles to it's associated dictionary collection ordered by vehicle name.
foreach (var manufacturer in manufacturers)
{
// Get the collection of unique vehicles ordered by name.
var vehicles = _alertDetails.Where(x => x.ManufacturerID == manufacturer.Item1)
.Select(x => new { VehicleID = x.VehicleID, VehicleName = x.VehicleName })
.Distinct()
.OrderBy(x => x.VehicleName);
foreach (var vehicle in vehicles)
{
manufacturer.Item3.Add(vehicle.VehicleID, vehicle.VehicleName);
}
}
I would create a class named Manufacturer:
public class Manufacturer
{
public int ManufacturerId { get; set;}
public string Name { get; set; }
public IEnumerable<Product> Products { get; set;}
}
Then create a Product class:
public class Product
{
public int ProductId { get; set;}
public string Name { get; set;}
}
Then use LINQ projection with the Select extension method to create the Manufacturer object.
Like this?
public class Manufacturer : IEquatable<Manufacturer>
{
public string Name { get; private set; }
public int ID { get; private set; }
// ...
}
public class Product
{
public string Name { get; private set; }
public int ID { get; private set; }
// ...
}
// groups is of type IEnumerable<IGrouping<Manufacturer, Product>>
var groups = data.GroupBy(row => new Manufacturer(row), row => new Product(row));
EDIT: If you want to use anonymous types (as you mentioned now in your update) then GroupBy should work just as well if you construct anonymous objects, instead of declaring a Manufacturer and Product class as in my sample.
MyDataStructure sounds very similar to a Tuple. See here for the three-generic parameter variant. Tuple gives a strongly typed container for a number of specified other types.

Categories

Resources