Sum up in a list of list properties - c#

I have below structure where I would like to sum up a list of properties
public class Space
{
public AirBalance AirBalance { get; set; } = new();
}
public class AirBalance
{
public bool AllowVariableExhaust { get; set; }
public SpaceVentilationAirflow VentilationAirflow { get; set; } = new();
}
public class SpaceVentilationAirflow
{
public List<A621VentilationAirflow> A621VentilationAirflows { get; set; } = new();
}
public class A621VentilationAirflow
{
public double BreathingZoneVentilation { get; set; }
}
I am trying to sum up all spaces of A621VentilationAirflow's BreathingZoneVentilation, and I have a value of 1115.05 for breathing zone ventilation. When I sum up using the below code, it always gives me the same value even If I have two spaces and a list of A621VentilationAirflow objects that exist.
Spaces?.Sum(a => a?.AirBalance?.VentilationAirflow?.A621VentilationAirflows.Sum(a => a.BreathingZoneVentilation))
Could anyone please let me know where I am doing wrong with the above code? Many thanks in advance

I feel like you need to change your code to:
Spaces?.Sum(a => a?.AirBalance?.VentilationAirflow?.A621VentilationAirflows.Sum(b => b.BreathingZoneVentilation))
Also, using projection (Select / SelectMany) you can get a more readable (imo) query:
var y = spaces
.Select(c => c.AirBalance)
.Select(c => c.VentilationAirflow)
.SelectMany(c => c.A621VentilationAirflows)
.Sum(c => c.BreathingZoneVentilation);
https://dotnetfiddle.net/RBX9aX

Related

How do I negotiate joins and groupings based on nested properties in LINQ?

So I've got a nested data structure like this:
public class ContractTerm
{
public int ContractId { get; set; }
public string SectionId { get; set; }
public string SubsectionId { get; set; }
public string TermId { get; set; }
public int TermOrder { get; set; }
public TermItem TermNavigation { get; set; }
}
public class TermItem
{
public string SectionId { get; set; }
public string SubsectionId { get; set; }
public string TermId { get; set; }
public string Text { get; set; }
public ICollection<ContractTerm> ContractNavigation { get; set; }
}
I've also got a class to map the section/subsection pairings in a more EF-friendly way (IRL this is an enum with attribute values and a helper, but this class abstracts away some work not necessary to reproduce the issue):
public class Section
{
public string Name { get; set; }
public string SectionId { get; set; }
public string SubsectionId { get; set; }
}
Both ContractTerm and TermItem have their own collections in a DbContext, and I'm trying to get a collection of all text entries assigned to specific Sections for a given ContractId. I have the following class to contain it:
public class TextsBySection
{
public string SectionName { get; set; }
public IEnumerable<string> Texts { get; set; }
}
I want to select a collection of TextsBySection, and have something like this:
public class ContractManager
{
//insert constructor initializing MyContext here
private MyContext Context { get; }
public IEnumerable<MyOutputClass> GetTerms(int contractId, IEnumerable<Section> sections)
{
Func<string, string, IEnumerable<string>> getBySection =
(section, subsection) => context.ContractTerms.Include(x => x.TermNavigation)
.Where(x => x.ContractId == contractId
&& x.SectionId == section
&& x.SubsectionId == subsection)
.Select(x => x.TermNavigation.Text);
var result = sections.Select(x => new MyOutputClass
{
SectionName = x.Name,
Texts = getBySection(x.SectionId, x.SubsectionId)
}).ToList();
return result;
}
}
This works fine and dandy, but it hits the database for every Section. I feel like there's got to be a way to use Join and/or GroupBy to make it only query once, but I can't quite see it. Something like this, perhaps:
var result = context.ContractTerms.Include(x => x.TermNavigation)
.Where(x => x.ContractId == contractId)
.Join(sections,
term => //something
section => //something
(term, section) => /*something*/)
If all this were in SQL, selecting the necessary data would be easy:
SELECT sections.name,
term_items.text
FROM contract_terms
JOIN term_items
ON term_items.section_id = contract_terms.section_id
AND term_items.subsection_id = contract_terms.subsection_id
AND term_items.term_id = contract_terms.term_id
JOIN sections --not a real table; just corresponds to sections argument in method
ON sections.section_id = contract_terms.section_id
AND sections.subsection_id = contract_terms.subsection_id
...and then I could group the results in .NET. But I don't understand how to make a single LINQ query that would do the same thing.
I changed my answer, well I would do something like this... maybe this may help you.
public static void Main(string[] args)
{
List<Section> sections = new List<Section>();
List<ContractTerm> contractTerms = new List<ContractTerm>();
List<TermItem> termItens = new List<TermItem>();
//considering lists have records
List<TextsBySection> result = (from contractTerm in contractTerms
join termItem in termItens
on new
{
contractTerm.SectionId,
contractTerm.SubsectionId,
contractTerm.TermId
}
equals new
{
termItem.SectionId,
termItem.SubsectionId,
termItem.TermId
}
join section in sections
on new
{
contractTerm.SectionId,
contractTerm.SubsectionId
} equals new
{
section.SectionId,
section.SubsectionId
}
select
new
{
sectionName = section.Name,
termItemText = termItem.Text
}).GroupBy(x => x.sectionName).Select(x => new TextsBySection()
{
SectionName = x.Key,
Texts = x.Select(i=> i.termItemText)
}).ToList();
}

Possible to add a condition for linked table fields in LINQ

Can someone suggest me a solution to add condition for reference table items in linq.
I have a master table called TourPackage, which include
public class TourPackage{
public int TourID { get; set; }
public string TourName { get; set; }
public List<IncludedItems> IncludedItems { get; set; }
}
Every tour package contain some selected items reference like
public class IncludedItems {
public int TourID { get; set; }
public int IncludedID { get; set; }
public Included Included { get; set; }
}
All included item should have a reference to Included table for lookup reference
public class Included {
public int IncludedID { get; set; }
public string IncludedValue { get; set; }
}
now i have set of IncludedID like [1,2,3], Is it possible to filter TourPackage based on IncludedID.
Thanks in advance
You can use following code
I have sample array(i.e example) which contains ID's we check if current Id(i.e ele.Included.IncludedID) is present in the array of id's.
listex.Where(x => x.IncludedItems.Any(ele => example.Contains(ele.Included.IncludedID))).ToList();
sample:-
int[] example = new int[3];
example[0] = 123;
example[1] = 456;
example[2] = 789;
List<TourPackage> listex = new List<TourPackage>();
List<TourPackage> filterList = listex.Where(x => x.IncludedItems.Any(ele => example.Contains(ele.Included.IncludedID))).ToList();
Have you tried using something like:
var myIds = new List<int> {123,456};
var result = context.TourPackages
.Where(x => x.IncludedItems.Any(a => a.Included !=null && myIds.Contains(a.Included.IncludedId)))
.ToList();
You might have to include some relations manually depending if you're lazy loading is setup or not.
More info at https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx

LINQ and the dynamic List Type

Have class:
public class SignalRounding
{
public DateTime LastUpdated { get; set; }
public int Quantity { get; set; }
public string symbol { get; set; }
public double price { get; set; }
public double round { get; set; }
}
public class SignalRoundingView
{
public DateTime LastUpdated { get; set; }
public int Quantity { get; set; }
public string symbol { get; set; }
public double price { get; set; }
public double round { get; set; }
}
Create list
public static List<SignalRounding> SignalR;
SignalR = new List<SignalRounding>();
ListView.ItemsSource = SignalR;
ListView.Items.Refresh();
Add some information with certain parameters
try
{
var r = SignalR.Where(t => t.LastUpdated >= DateTime.Now.AddMinutes(-10))
.GroupBy(g => g.symbol)
.Select(s => new SignalRoundingView()
{
LastUpdated = s.Max(x => x.LastUpdated),
Quantity = s.Count(),
symbol = s.Key,
price = s.Single(l => l.LastUpdated == s.Max(x => x.LastUpdated)).price,
round = s.Single(l => l.LastUpdated == s.Max(x => x.LastUpdated)).round
})
.OrderByDescending(x => x.LastUpdated)
.ThenByDescending(x => x.Quantity).ToList();
Filter3List.ItemsSource = r;
Filter3List.Items.Refresh();
}
catch (Exception){throw;}
List<SignalRounding> SignalR is dynamic - sometimes there is an error.
With the following description: Collection was modified; the operation cannot be performed enumerating.
How fix it?
Make sure you don't allow one thread to iterate over the list (Linq query) while another thread is adding to it. You can use a lock to achieve this. There's a performance penalty so you need to decide whether this solution works well enough for you depending on how fast you add new objects to the list and how long the list gets.
private object myLock = new Object();
void AddToList(SignalR s)
{
lock(myLock) {
SignalR.Add(s);
}
}
void UpdateList() {
lock(myLock)
{
var r = SignalR.Where(...).ToList();
Filter3List.ItemsSource = r;
Filter3List.Items.Refresh();
}
}
You have multiple threads accessing your list which will throw exceptions. Instead of List<T>, use ConcurrentBag<T>:
public static ConcurrentBag<SignalRounding> SignalR;
Note: This is in the System.Collections.Concurrent namespace.

Where condition inside lambda expression c#

I have a entity like
public class Program
{
public int ID { get; set; }
public bool IsActive { get; set; }
public string Title { get; set; }
}
and
public class EMetrics
{
public int ID { get; set; }
public bool IsActive { get; set; }
public string Title { get; set; }
public List<Program> Programs { get; set; }
}
I have repository method like,
IEnumerable<EMetrics> IEmetricsRepository.GetAllByProgram(params int[] programIds)
{
var metrics = EntitySet
.Where(x => programIds.Contains(x.Programs.Select(x => x.ID)))
.ToList();
return metrics;
}
[The above code throwing build error]
Here only where I am facing problem to get the EMetrics based on the program Ids array params.
I want list Emetrics which are associated with the program.
You're incorrectly accessing the same input parameter in your LINQ. It should be refactored by changing your inner Select to use a different parameter:
IEnumerable<EMetrics> IEmetricsRepository.GetAllByProgram(params int[] programIds)
{
var metrics = EntitySet
.Where(x => programIds.Contains(x.Programs.Select(y => y.ID)))
.ToList();
return metrics;
}
So you want to check if all elements of one collection are present in the other. In LINQ that can be done with combination of Except and Any:
var metrics = EntitySet
.Where(x => x.Programs.Select(p => p.ID).Except(programIds).Any())
.ToList();
Fyi - your current code is failing because Array.Contains expects a single item, an int in this case, while you are giving it a whole enumerable

LINQ multiple keyword search to PagedList

I'm a bit lost here and I've tried a few different ways to tackle it. So far I'm having a hard time writing out the LINQ to do what I want.
I want to take the user input string which can be multiple keywords split either by whitespace or ",".
This here works grabs the whole search term and compares it to the title in the Post or any tag I may have. I want the user to type in "HTML Preview" which would match a post called, "Preview the World" with the tags "HTML", "CSS", etc....
This query won't work...but I'm trying to modify it so that it does work.
public IPagedList<Post> SearchResultList(string searchTerm, int resultsPerPage, int page)
{
string[] terms = searchTerm.Split(null);
TNDbContext context = DataContext;
return context.Posts
.Include(a => a.Tags)
.Include(b => b.Comments)
.Where(c => (c.Title.Contains(searchTerm) || c.Tags.Any(d => d.Name.StartsWith(searchTerm))) || searchTerm == null)
.OrderByDescending(x => x.Views)
.ToPagedList(page, resultsPerPage);
}
I tried writing this instead of the other "Where" statement
.Where(x => (terms.All(y => x.Title.Contains(y))) || terms == null)
but it keeps throwing this error
Cannot compare elements of type 'System.String[]'. Only primitive types, enumeration types and entity types are supported.
FOR REFERENCE:
public class Post
{
public Post()
{
Tags = new HashSet<Tag>();
Comments = new HashSet<Comment>();
}
public int Id { get; set; }
public string Title { get; set; }
public string UrlTitle { get; set; }
public DateTime Date { get; set; }
public DateTime DateEdited { get; set; }
public string Body { get; set; }
public string Preview { get; set; }
public string PhotoPath { get; set; }
public int Views { get; set; }
//Navigational
public ICollection<Tag> Tags { get; set; }
public ICollection<Comment> Comments { get; set; }
}
public class Tag
{
public Tag()
{
Post = new HashSet<Post>();
}
public int Id { get; set; }
public string Name { get; set; }
public int TimesTagWasUsed { get; set; }
//Navigational
public ICollection<Post> Post { get; set; }
}
You need to start with a base query, and then keep adding where clauses to it for each search term. Try this:
TNDbContext context = DataContext;
//Create the base query:
var query = context.Posts
.Include(a => a.Tags)
.Include(b => b.Comments)
.OrderByDescending(x => x.Views);
//Refine this query by adding "where" filters for each search term:
if(!string.IsNullOrWhitespace(searchTerm))
{
string[] terms = searchTerm.Split(" ,".ToCharArray(),
StringSplitOptions.RemoveEmptyEntries);
foreach(var x in terms)
{
string term = x;
query = query.Where(post => (post.Title.Contains(term) ||
post.Tags.Any(tag => tag.Name.StartsWith(term))));
}
}
//Run the final query to get some results:
var result = query.ToPagedList(page, resultsPerPage);
return result;
You can nest queries with additional 'from' statements, so something like this should work:
var list = (from post in context.Posts.Include(a => a.Tags).Include(b => b.Comments)
from term in terms
where post.Title.Contains(term) || post.Tags.Any(d => d.Name.StartsWith(term))
select post).OrderByDescending(x => x.Views);

Categories