LINQ select all items of all subcollections that contain a string - c#

I'm using jqueryui autocomplete to assist user in an item selection. I'm having trouble selecting the correct items from the objects' subcollections.
Object structure (simplified) is
public class TargetType
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<SubCategory> SubCategories { get; set; }
public TargetType()
{
SubCategories = new HashSet<SubCategory>();
}
}
public class SubCategory
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<SubTargetType> SubTargetTypes { get; set; }
public SubCategory()
{
SubTargetTypes = new HashSet<SubTargetType>();
}
}
Currently I'm doing this with nested foreach loops, but is there a better way?
Current code:
List<SubTargetResponse> result = new List<SubTargetResponse>();
foreach (SubCategory sc in myTargetType.SubCategories)
{
foreach (SubTargetType stt in sc.SubTargetTypes)
{
if (stt.Name.ToLower().Contains(type.ToLower()))
{
result.Add(new SubTargetResponse {
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name });
}
}
}

You can do using Linq like this
var result = myTargetType.SubCategories
.SelectMany(sc => sc.SubTargetTypes)
.Where(stt => stt.Name.ToLower().Contains(type.ToLower()))
.Select(stt => new SubTargetResponse {
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name });
The above query doesn't work. The following should work, but I'd not recommend that as that'd not be faster or more readable.
var result = myTargetType.SubCategories
.Select(sc => new Tuple<int, IEnumerable<SubTargetType>>
(sc.Id,
sc.SubTargetTypes.Where(stt => stt.Name.ToLower().Contains(type.ToLower()))))
.SelectMany(tpl => tpl.Item2.Select(stt => new SubTargetResponse {
Id = stt.Id,
CategoryId = tpl.Item1,
Name = stt.Name }));

Actually there are 2 different questions.
LINQ select all items of all subcollections that contain a string
Solutions:
(A) LINQ syntax:
var result =
(from sc in myTargetType.SubCategories
from stt in sc.SubTargetTypes.Where(t => t.Name.ToLower().Contains(type.ToLower()))
select new SubTargetResponse
{
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name
})
.ToList();
(B) Method syntax:
var result =
myTargetType.SubCategories.SelectMany(
sc => sc.SubTargetTypes.Where(stt => stt.Name.ToLower().Contains(type.ToLower())),
(sc, stt) => new SubTargetResponse
{
Id = stt.Id,
CategoryId = sc.Id,
Name = stt.Name
})
.ToList();
Currently I'm doing this with nested foreach loops, but is there a better way?
Well, it depends of what do you mean by "better". Compare your code with LINQ solutions and answer the question. I personally do not see LINQ being better in this case (except no curly braces and different indentation, but a lot of a hidden garbage), and what to say about the second LINQ version in this answer - if that's "better" than your code, I don't know where are we going.

Related

Get selected fields on a list using a LINQ query

I have an entity framework generated class like this.
public partial class TBLM_PRODUCT
{
public string PRODUCT_CODE { get; set; }
public string PRODUCT_DESC { get; set; }
public string PRODUCT_ISBN { get; set; }
public string PRODUCT_SUPPLIER { get; set; }
public string PRODUCT_PROGROUP { get; set; }
}
Normally I select items list like this using a LINQ query.
using ( AEntities RAEntity = new AEntities())
{
RAEntity.TBLM_PRODUCT.ToList<DataControllers.TBLM_PRODUCT>();
}
I want to select an item list with two fields like this like as in following query
select PRODUCT_CODE,PRODUCT_DESC from TBLM_PRODUCT where PRODUCT_PROGROUP='GG';
How can I achieve that?
using ( AEntities RAEntity = new AEntities())
{
var all = RAEntity.TBLM_PRODUCT.ToList<DataControllers.TBLM_PRODUCT>();
var yourList = all
.Where(x => x.PRODUCT_PROGROUP == "GG")
.Select(p => new { p.PRODUCT_CODE, p.PRODUCT_DESC })
.ToList();
}
Don't select all records first and then filtered your data.
If you use .ToList<DataControllers.TBLM_PRODUCT>() then it can select all records. So instead of this you can select your columns at the time of query fired to database.
If your TBLM_PRODUCT is of any collection type like IEnumerable<> or IQueryable<> then,
using ( AEntities RAEntity = new AEntities())
{
var result = RAEntity.TBLM_PRODUCT.Where(x => x.PRODUCT_PROGROUP == "GG").Select(x => new { x.PRODUCT_CODE, x.PRODUCT_DESC }).ToList();
}
using (AEntities RAEntity = new AEntities())
{
var list= RAEntity.TBLM_PRODUCT
.Where(p => p.PRODUCT_PROGROUP == "GG")
.Select(p => new TBLM_PRODUCT { PRODUCT_CODE = p.PRODUCT_CODE, PRODUCT_DESC = p.PRODUCT_DESC })
.ToList();
}

How can I select from an included entity in LINQ and get a flat list similar to a SQL Join would give?

I have two classes:
public class Topic
{
public Topic()
{
this.SubTopics = new HashSet<SubTopic>();
}
public int TopicId { get; set; }
public string Name { get; set; }
public virtual ICollection<SubTopic> SubTopics { get; set; }
}
public class SubTopic
public int SubTopicId { get; set; }
public int Number { get; set; }
public int TopicId { get; set; }
public string Name { get; set; }
public virtual Topic Topic { get; set; }
}
What I would like to do is to get a Data Transfer Object output from LINQ that will show me. I do want to see the TopicId repeated if there is more than one SubTopic inside that topic:
TopicId Name SubTopicId Name
1 Topic1 1 SubTopic1
1 Topic1 2 SubTopic2
1 Topic1 3 SubTopic3
2 Topic2 4 SubTopic4
I tried to code a Linq statement like this:
var r = context.Topics
.Select ( s => new {
id = s.TopicId,
name = s.Name,
sid = s.SubTopics.Select( st => st.SubTopicId),
sidname = s.SubTopics.Select ( st => st.Name)
}).
ToList();
But this does not really work as it returns sid and sidname as lists.
How will it be possible for me to get a flat output showing what I need?
You need SelectMany to expand a nested collection, along these lines
var r = context.Topics.SelectMany(t => t.SubTopics
.Select(st => new
{
TopicID = t.TopicId,
TopicName = t.Name,
SubTopicID = st.SubTopicId,
SubTopicName = st.Name
}));
try this :
var r = context.Topics
.Select ( s => new {
id = s.TopicId,
name = s.Name,
sid = s.SubTopics.Where(st=>st.TopicId==s.TopicId).Select( st => st.SubTopicId ),
sidname = s.SubTopics..Where(st=>st.TopicId==s.TopicId).Select ( st => st.Name)
}).
ToList();
Hope it will help
#Sweko provided an answer that satisfies the exact output that you requested. However, this can be even simpler if you just return the subtopic intact. It may run a bit quicker as well, since you don't need to create a new object for each element in the result.
Lastly, it looks like you wanted your result set ordered. For completeness, I've added those clauses as well.
var r = context.Topics
.SelectMany( topic => topic.SubTopics )
.OrderBy(sub => sub.TopicId)
.ThenBy(sub => sub.SubTopicId);

Linq "join" with a IList<T> getting "Error Unable to create a constant value.."

I have a Save Method that saves with a Linq query a manually re-orderd list (in a web form) that is passed as the parameter to my method, and I try to update the Order Property of the IEnumerable<VM_CategoryLabel> I retrieve from the database (EF) with the corresponding value in the list (maybe would that be clearer with my code below):
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
//Order = x.Sequence_Cat,
Order = (int)listTemplate.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
I used the "test" var to see if my "sub-query" gets the correct value, and it does, but when I use my Linq expression inside the Select (the commented Order line), I get the following error:
Unable to create a constant value of type 'Namespace.Models.VM_CategoryLabelExtra. "Only primitive types and enumeration types are supported in this context.
Here are my classes:
public class VM_CategoryLabel
{
public int Id { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
public class VM_CategoryLabelExtra
{
public int Id { get; set; }
public int IdCat { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
So I suppose that I should not query the list inside my query ? So how do I "match" the 2 lists of values ?
I also tried the following (after having replace in the Linq query: Order = x.Sequence_Cat)that is not working neither because the iteration variable is
read-only:
foreach (var item in requete)
{
item.Order = listTemplate.Where(x => x.Id == item.Id).Select(x => x.Order).FirstOrDefault();
}
try
{
context.SaveChanges();
I suggest using this.
It is the let clause.
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
let list = listTemplate
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
Order = list.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
edit: instead offrom you can just do let list = listTemplate
Should work now :)
example for let:
// The let keyword in query expressions comes in useful with subqueries: it lets
// you re-use the subquery in the projection:
from c in Customers
let highValuePurchases = c.Purchases.Where (p => p.Price > 1000)
where highValuePurchases.Any()
select new
{
c.Name,
highValuePurchases
}
If you do not know how Let working than please download LinqPad and see an example

IList<T> in multi map/reduce result?

I'm struggling with RavenDB's multi map/reduce concept and recently asked this question regarding how to properly write a multi map/reduce index.
I got the simple index in that question working but when I tried to make it a bit more complicated, I cannot make it work. What I want to do is to have the result of the index to contain a list of string, i.e:
class RootDocument {
public string Id { get; set; }
public string Foo { get; set; }
public string Bar { get; set; }
public IList<string> Items { get; set; }
}
public class ChildDocument {
public string Id { get; set; }
public string RootId { get; set; }
public int Value { get; set; }
}
class RootsByIdIndex: AbstractMultiMapIndexCreationTask<RootsByIdIndex.Result> {
public class Result {
public string Id { get; set; }
public string Foo { get; set; }
public string Bar { get; set; }
public IList<string> Items { get; set; }
public int Value { get; set; }
}
public RootsByIdIndex() {
AddMap<ChildDocument>(
children => from child in children
select new {
Id = child.RootId,
Foo = (string)null,
Bar = (string)null,
Items = default(IList<string>),
Value = child.Value
});
AddMap<RootDocument>(
roots => from root in roots
select new {
Id = root.Id,
Foo = root.Foo,
Bar = root.Bar,
Items = root.Items,
Value = 0
});
Reduce =
results => from result in results
group result by result.Id into g
select new {
Id = g.Key,
Foo = g.Select(x => x.Foo).Where(x => x != null).FirstOrDefault(),
Bar = g.Select(x => x.Bar).Where(x => x != null).FirstOrDefault(),
Items = g.Select(x => x.Items).Where(
x => x != default(IList<string>).FirstOrDefault(),
Value = g.Sum(x => x.Value)
};
}
}
Basically I tried to set the Items property to default(IList) when mapping the ChildDocuments and to value of the RootDocument's Items property. This doesn't work, however. It gives the error message
Error on request Could not understand query:
-- line 2 col 285: invalid Expr
-- line 2 col 324: Can't parse double .0.0
when uploading the index. How do I handle lists in multi map/reduce indexes?
Don't use List in your indexes, instead, use arrays.
AddMap<ChildDocument>(
children => from child in children
select new {
Id = child.RootId,
Foo = (string)null,
Bar = (string)null,
Items = new string[0],
Value = child.Value
});
AddMap<RootDocument>(
roots => from root in roots
select new {
Id = root.Id,
Foo = root.Foo,
Bar = root.Bar,
Items = root.Items,
Value = 0
});
Reduce =
results => from result in results
group result by result.Id into g
select new {
Id = g.Key,
Foo = g.Select(x => x.Foo).Where(x => x != null).FirstOrDefault(),
Bar = g.Select(x => x.Bar).Where(x => x != null).FirstOrDefault(),
Items = g.SelectMany(x=>x.Items),
Value = g.Sum(x => x.Value)
};
David, you need to understand that RavenDB stores its indexes with Lucene.NET. That means, you cannot have any complex .net type inside your index.
In your example, I suggest you use a simple string instead of IList<string>. You can then join your string-items:
AddMap<ChildDocument>(
children => from child in children
select new {
Id = child.RootId,
Foo = (string)null,
Bar = (string)null,
Items = (string)null,
Value = child.Value
});
AddMap<RootDocument>(
roots => from root in roots
select new {
Id = root.Id,
Foo = root.Foo,
Bar = root.Bar,
Items = string.Join(";", root.Items),
Value = 0
});

Help with C# LINQ Projection

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.

Categories