Linq query to find matches in nested lists - linq

Using the classes below I need to select items from AllMeta where there is a match in Meta.
The match criteria is that at least 1 group in Meta.Groups.Name matches that of AllMeta.Values.Groups.Name.
AllMeta: is a dictionary<string, WikiMeta>
Meta: is a WikiMeta
public class WikiMeta
{
public string ContentTitle { get; set; }
public string PageTitle { get; set; }
public string PageMetaDescription { get; set; }
public List<WikiArticle> Articles = new List<WikiArticle>();
public List<WikiGroup> Groups = new List<WikiGroup>();
}
public class WikiGroup
{
public string Name { get; set; }
}
Any help appreciated.

A nested Any is the direct approach:
var result = AllMeta
.Where(kv =>
Meta.Any(m =>
m.Groups.Any(mg =>
kv.Value.Groups.Any(vmg =>
vmg.Name == mg.Name))));
Apologies Tim, slight error in specification. Meta is a single
WikiMeta but with one or more Groups.
var result = AllMeta
.Where(kv => Meta.Groups.Any(mg => kv.Value.Groups.Any(vmg => vmg.Name == mg.Name)));

Related

Asp.Net Core / Entity Framework Core - System.Linq.Enumerable+SelectEnumerableIterator`2[MyClass,System.String]

I am trying to return a json array of my 3rd level depth related data, the issue here is that I get the result with the right property name but with a non clear value content, I failed to find a similar case to solve it. From the returned value message it looks like I am returning a queryable instead of the final result and I need to iterate over it, I've tried several ways to achive that but failed to find the right one.
The json result:
[
{
"registeredYear": "System.Linq.Enumerable+SelectEnumerableIterator`2[MyPath.Groups.GroupYear,System.String]"
}
]
The api endpoint
public async Task<ActionResult<IEnumerable<UserGroup>>> GetUserGroupYears(string email, string groupName)
{
var groupYears = await _repo.GetUserGroupYears(email, groupName);
var mappedEntities = _mapper.Map<GroupYearsListDto[]>(groupYears);
return Ok(mappedEntities);
}
The Repository method
public async Task<IEnumerable<UserGroup>> GetUserGroupYears(string email, string groupName)
{
var userGroupYears = _context.UserGroups
.Include(uo => uo.Group.GroupYears)
.ThenInclude( oy => oy.Year)
.Where(uo => uo.Group.Name == groupName && uo.Email == email );
return await userGoupYears.ToArrayAsync();
}
The used classes:
public class UserGroup
{
public string Email { get; set; }
public string UserId { get; set; }
public virtual User User { get; set; }
public string GroupId { get; set; }
public virtual Group Group { get; set; }
}
public class Group
{
public string Name { get; set; }
public virtual ICollection<UserGroup> Users { get; set; }
public virtual ICollection<GroupYear> GroupYears { get; }
}
public class GroupYear {
public string GroupId { get; set; }
public virtual Group Group { get; set; }
public string YearId { get; set; }
public virtual Year Year { get; set; }
public string RegisteredYear { get; set; }
}
The data transfer object and the mapping:
public class GroupYearsListDto
{
public string RegisteredYear { get; set; }
}
public CoreMappingProfiles()
{
CreateMap<UserGroup, GroupYearsListDto>()
.ForMember(
dest => dest.RegisteredYear,
opt => opt.MapFrom(src => src.Group.GroupYears.Select(x => x.RegisteredYear))
);
}
Update: Attaching a debugger shows that the repository method is returning an IQueryable including the correct values and the controller method makes something wrong when mapping. So I think the following line is responsible of that wrong result:
var mappedEntities = _mapper.Map<GroupYearsListDto[]>(GroupYears);
You are getting this JSON result:
[
{
"registeredYear": "System.Linq.Enumerable+SelectEnumerableIterator`2[MyPath.Groups.GroupYear,System.String]"
}
]
Because you are mapping an IEnumerable<string> to a string, as I mentioned in my comment. So essentially you are getting the same as:
CreateMap<UserGroup, GroupYearsListDto>()
.ForMember(
dest => dest.RegisteredYear,
opt => opt.MapFrom(src =>
{
IEnumerable<string> registeredYears = src.Group.GroupYears.Select(x => x.RegisteredYear);
return registeredYears.ToString();
})
);
And registeredYears.ToString() is "System.Linq.Enumerable+SelectEnumerableIterator`2[MyPath.Groups.GroupYear,System.String]".
I imagine you will either:
Only have one - so do something like: src.Group.GroupYears.Select(x => x.RegisteredYear).Single()
Have multiples - so do something like: string.Join(", ", src.Group.GroupYears.Select(x => x.RegisteredYear))
You have many options, but you need to actually return a string to that property or else you will just get the ToString() version of IEnumerable<string>.
UPDATE:
Based on your comments below, you can try this:
Repository:
public IQueryable<GroupYear> GetGroupYears(string email, string groupName)
{
return _context
.UserGroups
.Where(x => x.Group.Name == groupName && x.Email == email)
.SelectMany(x => x.Group.GroupYears);
}
Controller:
public async Task<ActionResult<GroupYearsListDto[]>> GetGroupYears(string email, string groupName)
{
var groupYears = _repo.GetGroupYears(email, groupName);
var projection = _mapper.ProjectTo<GroupYearsListDto>(groupYears)
var mappedEntities = await projection.ToArrayAsync();
return Ok(mappedEntities);
}
Profile:
CreateMap<GroupYears, GroupYearsListDto>();

Impact of changing existing c# class properties from long to List<long>

I have existing with four properties and getting change in one of the fields from long to List<long>.
There is an existing code which is using already long. If I change it to List<long> I am unable to modify the existing code. Please help me out.
before change:
public class Class1
{
public long prop1 { get; set; }
public long prop2 { get; set; }
public List<string> prop3 { get; set; }
public List<string> prop3 { get; set; }
}
Chnaged class with prop2
public class Class1
{
public long prop1 { get; set; }
public **List<long>** prop2 { get; set; }
public List<string> prop3 { get; set; }
public List<string> prop3 { get; set; }
}
var details = Data1
.Where(m => m.MemberKey == clientMemberId).FirstOrDefault();
var getdata =Data2
.Where(x => Data1.prop2.Contains(x.prop2))
.Select(x => new
{
prop1= x.prop1,
prop3 = x.prop3
})
.ToList();
Getting an error at x.prop2
You need to decide what you want this line to do:
.Where(x => Data1.prop2.Contains(x.prop2))
If you want Data1.prop2 to contain any of the elements from x.prop2:
.Where(x => Data1.prop2.ToHashSet().IsSupersetOf(x.prop2))
If you want Data1.prop2 to contain all of the elements from x.prop2:
.Where(x => Data1.prop2.ToHashSet().IsSubsetOf(x.prop2))
If you want Data1.prop2 to contain exactly the same elements from x.prop2:
.Where(x => Data1.prop2.ToHashSet().SetEquals(x.prop2))
And if you want Data1.prop2 to contain exactly the same elements from x.prop2 in exactly the same order:
.Where(x => Data1.prop2.SequenceEqual(x.prop2))

Suggestion field array

I have a document that contains an array of tags. I need to create a suggestion field corresponding to this tag field (to generate tag suggestions based on the values in the tag array). I am using NEST to interact with elastic search mostly. But I am not able to updated the suggestion property. The class used for the document contains following
Document structure:
public class SuggestField
{
public IEnumerable<string> Input { get; set; }
public string Output { get; set; }
public object Payload { get; set; }
public int? Weight { get; set; }
}
public class Source{
[ElasticProperty(Index = FieldIndexOption.NotAnalyzed)]
public string[] tags { get; set; }
public SuggestField[] tag_suggest { get; set; }
}
I add the mapping as follows:
var response = client.Map<Source>(m => m
.MapFromAttributes()
.Properties(p => p
.Completion(c => c
.Name(cp => cp.tag_suggest)
.Payloads()
)));
For updating tags, I use external scripts. I was hoping to change this same script to add changes to tag_suggest field also. But I tried the following but it is not working. Following is the script I tried:
if (ctx._source.tags.indexOf(newTag) < 0) {
ctx._source.tags[ctx._source.tags.length] = newTag;
ctx._source.tag_suggest[ctx._source.tag_suggest.length] = { input :newTag }
}
I would change type of tag_suggest property from SuggestField[] to SuggestField. You can store all tags in SuggestField.Input.
public class Source
{
[ElasticProperty(Index = FieldIndexOption.NotAnalyzed)]
public string[] tags { get; set; }
public SuggestField tag_suggest { get; set; }
}
Regarding your update script, after this change you can modify it to:
if (ctx._source.tags.indexOf(newTag) < 0) {
ctx._source.tags[ctx._source.tags.length] = newTag;
ctx._source.tag_suggest.input[ctx._source.tag_suggest.length] = newTag;
}
Hope it helps.

aggregate on nested type properties - using NEST

I'm trying to aggregate on counts of nested type, or sum of property of nested types, but unable to get NEST to include more than one nested document in the calculation.
var result = elasticClient.Search<ItemIncidents>(s => s
.Aggregations(a => a
.Terms("group by role", ts => ts
.Field(o => o.LabelName)
.d
.Aggregations(aa => aa
.Sum("sum incidents", sa => sa
.Field("incidents.index")))
)
)
);
The classes used are this:
public class ItemIncidents
{
public int Id{get;set;}
public string LabelName { get; set; }
// [ElasticProperty(Type = FieldType.Nested)]
public List<IncidentInstance> Incidents { get; set; }
}
public partial class IncidentInstance: {
public string Id { get; set; }
public int Index { get; set; }
public int Count { get; set; }
}
If I have more than one incidentinstance per ItemIncident, elastic is only counting the last one in the list in the aggregate count of index. If the value of Index = 3 for all the IncidentInstances, and there are five documents each with two incidentinstances, the result I'm getting is 15 (5*1*3), instead of 30 (5*2*3).
Is this an issue where I need to do some special attribute mapping on the Index field?
You can use nested aggregation for nested type.
So, for your documents:
public class ItemIncidents
{
public int Id { get; set; }
public string LabelName { get; set; }
[ElasticProperty(Type = FieldType.Nested)]
public List<IncidentInstance> Incidents { get; set; }
}
public class IncidentInstance
{
public string Id { get; set; }
public int Index { get; set; }
public int Count { get; set; }
}
use aggregation
var searchResponse = client.Search<ItemIncidents>(s => s.Aggregations(a => a
.Nested("indexCount", nested => nested
.Path(p => p.Incidents)
.Aggregations(aa => aa.Sum("sum", sum => sum.Field(f => f.Incidents.First().Index))))));

Linq query which has a non db object within

I have multiple View Model classes that get data from the database using Linq (DBContext classes have a similar structure but with more fields)
I am lacking knowledge to get data from one class object because that is contained within a wrapper class that does not have a corresponding db object.
Thank you in advance for any help or suggestions on this.
Here are the class structures:
public class TopLevelViewModel
{
public TopLevelID { get; set; }
public TopLevelTitle { get; set; }
public List<SecondLevelViewModel> SeconLevel { set; get; }
public TopLevelViewModel()
{
Inner = new List<SecondLevelViewModel>();
}
}
public class SecondLevelViewModel
{
public SecondLevelID { get; set; }
public SecondLevelTitle { get; set; }
public TopLevelID { get; set; }
public WrapperViewModel { get; set; }
}
public class InnerViewModel
{
public InnerID { get; set; }
public InnerTitle { get; set; }
public SecondLevelID { get; set; }
}
public class WrapperViewModel
{
public List<InnerViewModel> Inner { set; get; }
public WrapperViewModel()
{
Inner = new List<InnerViewModel>();
}
}
Linq query to get data for the above db objects:
List<TopLevelViewModel> data = null;
data = db.TopLevel.Where(t => t.TopLevelID == URLTopLevelID)
.Select(t => new TopLevelViewModel()
{
TopLevelID = t.TopLevelID,
TopLevelTitle = t.TopLevelTitle,
SeconLevel = db.SecondLevel.Where(s => s.TopLevelID == t.TopLevelID)
.Select(s => new SecondLevelViewModel()
{
SecondLevelID = s.SecondLevelID,
SeconLevelTitle = s.SeconLevelTitle,
WrapperViewModel = ???
}).ToList<SecondLevelViewModel>()
}).ToList < TopLevelViewModel();
Query to get Inner object data:
db.Inner.Where(i => i.SecondLevelID == s.SecondLevelID)
.Select(i => new InnerViewModel()
{
InnerID = i.InnerID,
InnerTitle = i.InnerTitle
}).ToList<InnerViewModel>()
Question: how do I incorporate the above inner query to be part of WrapperViewModel class and have that in the main Linq query as a single query?
WrapperViewModel does not have a DB object but it is kind of a wrapper class to InnerViewModel class.
I think I figured it out. Hope it will be useful to someone. If there are any ideas/suggestions please share!
The single query would be:
List<TopLevelViewModel> data = null ;
data = db.TopLevel.Where(t => t.TopLevelID == URLTopLevelID).Select(t => new TopLevelViewModel()
{
TopLevelID = t.TopLevelID,
TopLevelTitle = t.TopLevelTitle,
SeconLevel = db.SecondLevel.Where(s => s.TopLevelID == t.TopLevelID).Select(s => new SecondLevelViewModel()
{
SecondLevelID = s.SecondLevelID,
SeconLevelTitle = s.SeconLevelTitle,
WrapperViewModel = new WrapperViewModel { Inner =
db.Inner.Where(i => i.SecondLevelID == s.SecondLevelID).Select(i => new InnerViewModel()
{
InnerID = i.InnerID,
InnerTitle = i.InnerTitle
}).ToList<InnerViewModel>()
}).ToList<SecondLevelViewModel>()
}).ToList < TopLevelViewModel();

Resources