I have a class
public class AmenityShowtime
{
public String AmenityKey { get; set; }
public String AmenityIcon { get; set; }
public String shTimes { get; set; }
}
Ultimately, I want to have a structure that is comprised of these nested classes:
public class AmenityShowtime
{
public String AmenityKey { get; set; }
public String AmenityIcon { get; set; }
public String shTimes { get; set; }
}
// Movie Class
public class theMovie
{
public String Movie_title { get; set; }
public String Rating { get; set; }
public String RunTime { get; set; }
public List<theAmenities> amens { get; set; }
}
public class theAmenities
{
public String AmenityName { get; set; }
public String AmenityIcon { get; set; }
public List<theTimes> times { get; set; }
}
public class theTimes
{
public String timepref { get; set; }
}
I needed to group by AmenityKey and shtimes ... I used the following code:
IEnumerable<IGrouping<string, string>> query = amShow.GroupBy(ams => ams.AmenityKey, ams => ams.shTimes);
List<theAmenities> thisMoviesList = new List<theAmenities>();
foreach (IGrouping<string, string> grp in query)
{
theAmenities thisMovieAmenities = new theAmenities();
thisMovieAmenities.AmenityName = grp.Key;
List<theTimes> thisMovieTimes = new List<theTimes>();
foreach (string stimes in grp)
{
theTimes thisShowtime = new theTimes();
thisShowtime.timepref = stimes;
thisMovieTimes.Add(thisShowtime);
}
thisMovieAmenities.times = thisMovieTimes;
thisMoviesList.Add(thisMovieAmenities);
}
works great, with one exception ... how do I get access to the field: AmenityIcon in the
foreach (IGrouping<string, string> grp in query)
{
theAmenities thisMovieAmenities = new theAmenities();
thisMovieAmenities.AmenityName = grp.Key;
I want to be able to do the following:
thisMovieAmenites.AmenityIcon = AmenityIcon
I must be missing something, thank you in advance
If you want to include the amenityIcon in the grouping, then one way is this:
var query = amShow.GroupBy(ams => new {ams.AmenityKey, ams.AmenityIcon},
ams => ams.shTimes);
Note that you do need the var keyword now, since this is an IGrouping<Anonymous-type,String>
(This will group based on key and icon, I am assuming that the icon is the same if the key is the same, so this is essentially the same as grouping only by key)
Now you can do
foreach (var grp in query)
{
theAmenities thisMovieAmenities = new theAmenities();
thisMovieAmenities.AmenityName = grp.Key.AmenityKey;
thisMovieAmenities.AmenityIcon = grp.Key.AmenityIcon;
...
If you want to avoid anonymous types, you could also create your class theAmenities right in the grouping:
IEnumerable<IGrouping<theAmenities,string>> query = amShow.GroupBy(
ams => new theAmenities(){AmenityKey = ams.AmenityKey, AmenityIcon = ams.AmenityIcon},
ams => ams.shTimes);
but that requires that your class theAmenities implements the IEquatable<T> interface, to allow the GroupBy operator to recognize all theAmenities objects with the same key as equal.
If you use an anonymous type instead, this will work automatically. But this approach has the advantage that you could let your IEquatable ignore the AmenityIcon, if indeed it is possible that there are multiple items with the same key but different icons
Related
I am trying to group a column and form the the rest of the columns as child, hierarchical data:
I am trying to group by Code and form the parent and child relationship from a flat list, below is the hierarchical data I am trying to form:
source list:
public class ItemAssignmentFlatList
{
public int Code { get; set; }
public string Type { get; set; }
public string Description { get; set; }
public int ItemCode{ get; set; }
public DateTime EffectiveDate{ get; set; }
public string Area{ get; set; }
public string TaxCode{ get; set; }
public string LocationId { get; set; }
}
Need to convert above flat list into below List of hierarchical data:
public class ItemInfo
{
public int Code { get; set; }
public string Type { get; set; }
public string Description { get; set; }
public List<TaxInfo> TaxPlan { get; set; }
}
public class TaxPlan
{
public int ItemCode{ get; set; }
public DateTime EffectiveDate{ get; set; }
public string Area{ get; set; }
public string TaxCode{ get; set; }
public string LocationId { get; set; }
}
Need hierarchical list with above flat data list with C# extension methods.
I have below code, but looking for clean code to reduce number of lines:
var items= results.GroupBy(x => new { x.Code, x.Type });
List<ItemInfo> result = new List<ItemInfo>();
foreach (var group in items)
{
var taxPlans = group.
Select(y => new TaxPlan
{
TaxArea = y.TaxArea,
ItemCode = y.ItemCode
});
var itemInfo= new ItemInfo
{
Code = group.FirstOrDefault().Code,
Type = group.FirstOrDefault().Type,
Description = group.FirstOrDefault().Description,
TaxPlan = taxPlans.ToList()
};
result.Add(itemInfo);
}
Something like this?:
var input = new List<ItemAssignmentFlatList>(){
new ItemAssignmentFlatList{
Code = 1,
Area = "a"
},
new ItemAssignmentFlatList{
Code = 1,
Area = "b"
},
new ItemAssignmentFlatList{
Code = 2,
Area = "c"
}
};
input
.GroupBy(
x => x.Code,
(int code, IEnumerable<ItemAssignmentFlatList> items) =>
{
var first = items.FirstOrDefault();
var key = new ItemInfo
{
Code = first.Code
//, ...
};
var plan = items.
Select(y => new TaxPlan
{
Area = y.Area
//, ...
});
return new
{
key = key,
items = plan
};
}
).Dump();
Whenever you have a sequence of similar object, and you want to make "Items with their SubItems", based on common properties in your source sequence, consider to use one of the overloads of Enumerable.GroupBy
Because you don't just want "Groups of source items" but you want to specify your output, consider to use the overload that has a parameter resultSelector.
parameter keySelector: what should all elements in a group have in common
parameter resultSelector: use the common thing, and all elements that have this common thing to make one output element.
.
IEnumerable<ItemAssignmentFlatList> flatItemAssignments = ...
IEnumerable<ItemInfo> items = flatItemAssignments
// make groups with same {Code, Type, Description}
.GroupBy(flatItemAssignment => new {Code, Type, Description},
// parameter resultSelector: take the common CodeTypeDescription,
// and all flatItemAssignments that have this common value
// to make one new ItemInfo
(codeTypeDescription, flatItemAssignmentsWithThisCodeTypeDescription) => new ItemInfo
{
Code = codeTypeDescription.Code,
Type = codeTypeDescription.Type,
Description = codeTypeDescription.Description,
TaxPlans = flatItemAssignmentsWithThisCodeTypeDescription
.Select(flatItemAssignment => new TaxPlan
{
ItemCode = flatItemAssignment.ItemCode,
EffectiveDate = flatItemAssignment.EffectiveDate,
Area = flatItemAssignment.Area,
...
})
.ToList(),
});
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();
}
I have a sequence of objects, whose type has many properties and I would like to join it with another sequence and set a value taking from the second sequence. But because there are many properties I do not prefer to create a new anonymous type by doing all those property assignments. Is there a better way to do this in a single linq query (having been trying un-successfully with let)
var x = from lt in legalTerms
join le in legalEntities on lt.LegalEntityCode equals le.Code
select new {a = lt.a, b = lt.b, c = le.c, d = lt.d .... z=lt.z} // don't like
I need a sequence like legalTerms where the items have that one property updated from legalEntities
Wish there was somehow a way to clone le and set that one property c = le.c without using reflection etc
Basically this is what I wonder is possible in a linq statement
foreach (var lt in legalTerms)
{
foreach (var le in legalEntities)
{
if (le.Code == lt.LegalEntityCode)
{
lt.LegalEntity = le.Name;
}
}
}
how about this
var map = new Func<LegalTerm, LegalEntity, LegalTerm>((term, entity) =>
{
term.LegalEntity = entity.Name;
return term;
});
var query = from lt in legalTerms
join le in legalEntities
on lt.Code equals le.LegalEntityCode
select map(lt, le);
knowing that you have some sort of structures similar to those bellow
public class LegalTerm
{
public string Code { get; set; }
public string LegalEntity { get; set; }
public string a { get; set; }
public string b { get; set; }
public string c { get; set; }
public string d { get; set; }
}
public class LegalEntity
{
public string LegalEntityCode { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Basic idea is similar to Merging Expression Trees to Reuse in Linq Queries.
In my situation, I have two models and DTOs:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public Extra Extra { get; set; }
}
public class Extra
{
public int Id { get; set; }
public string Text { get; set; }
}
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
public ExtraDto Extra { get; set; }
}
public class ExtraDto
{
public int Id { get; set; }
public string Text { get; set; }
}
and expressions:
Expression<Func<Extra, ExtraDto>> extraSelector = o => new ExtraDto
{
Id = o.Id,
Text = o.Text
};
Expression<Func<User, UserDto>> userSelector = o => new UserDto
{
Id = o.Id,
Name = o.Name
};
Now, I'd like to 'append' extraSelector into userSelector. The pseudo code is like:
var selectorExpression = userSelector.Append(user => user.Extra, extraSelector);
Context.Users.Select(selectorExpression).ToList();
The final expression would be like this:
Expression<Func<User, UserDto>> userSelector = o => new UserDto
{
Id = o.Id,
Name = o.Name,
Extra = new ExtraDto
{
Id = o.Extra.Id,
Text = o.Extra.Text
}
};
I've tried using ExpressionVisitor, but no luck.
Apart from the "merge" of the two selectors, you have to insert the "path" o => o.Extra into the extraSelector and create a new "bind expression" for the property Extra of UserDto.
In fact, i'm playing around with such scenarios within this project, where i've tried to abstract this kind of expression plumbing. Your "merge" would then look like that:
userSelector = extraSelector.Translate()
.Cross<User>(o => o.Extra)
.Apply(o => o.Extra, userSelector);
The Translate extension method is just a little helper to make use of type inference, Cross inserts o => o.Extra into the extraSelector, Apply creates the "bind expression" for the property Extra of UserDto and finally merges the result with userSelector.
I have a IQueryable<SomePOCO> (a LINQ-Entities query, if that matters):
public class SomePOCO
{
public string ParentName { get; set; }
public string Name { get; set; }
public string Url { get; set; }
}
And i'm trying to project to a single object (anonymous type would be best, since i only need method scope) which has 2 properties:
public string ParentName { get; set; }
public ICollection<SimplePoco> { get; set;
SimplePOCO is as follows:
public class SimplePOCO
{
public string Name { get; set; }
public string Url { get; set; }
}
The reason i'm doing this is that all of the "SomePOCO"s im retrieving have the same ParentName, so i just want that once, as opposed to bringing over the wire the same value N amount of times and doing a .First().
Hope that makes sense.
The end result is i should be able to do this:
var parentName = result.ParentName; // string
var pocos = result.SimplePOCOs; // ICollection<SimplePOCO>
I think i either need some kind of aggregation, like with GroupBy or SelectMany.
Any ideas?
I think you just need to do a group by Parent Name
var result = collection.GroupBy(i => i.ParentName)
.Select(g => new {
ParentName = g.Key,
SimplePocos = g.Select(i => new SimplePoco
{
Name = i.Name,
Url = i.Url
})
});
This is the first step.
var groups = myQ.GroupBy(p => p.ParentName);
You will need to have your middle data structure. I'll call it Grouping.
var myList = new List<Grouping>();
foreach (var group in groups) {
var newGrouping = new Grouping();
new Grouping.ParentName = group.Key;
newGrouping.SimplePocos = group.Select(p => new SimplePoco(p)).ToArray();
}
You should have a constructor of SimplePoco that will convert it for you.