I have a list of object which is something like this
[Code Type][condition Type][Question Type][Description]
[A1][C1][q1][dC1]
[A1][C1][q2][dC1]
[A1][C1][q3][dC1]
[B1][C2][q4][dC2]
[B1][C2][q5][dC2]
[B1][C2][q6][dC2]
[B1][C3][q7][dC3]
[B1][C3][q8][dC3]
[B1][C3][q9][dc3]
I want to map this with a class which has a subclass and it's subclass also has a subclass.
Structure is like this
public Class TypeModel
{
public string Type{get;set;}
public List<ConditionModel> Conditions {get;set;}
}
public Class ConditionModel
{
public string Type{get;set;}
public string Description {get;set;}
public List<QuestionModel> Questions {get;set;}
}
public Class QuestionModel
{
public string Type {get;set;}
}
I have written this LINQ query to populate the main Type Class so far but it is not working. I need help in creating this query.
var results = allTypes.GroupBy(type => type.CodeType)
.Select(grp => new TypeModel
{
Type = grp.Select(i => i.CodeType).First(),
Conditions = new List<ConditionModel>
{
grp.GroupBy(condition => condition.ConditionType)
.Select(conditionGrp => new ConditionModel {
Type = conditionGrp.Select(i => i.ConditionType).First(),
Description = conditionGrp.Select(i => i.Description).First(),
Questions = new List<QuestionModel>
{
conditionGrp.GroupBy(question => question.QuestionType)
.Select(questionGrp => new QuestionModel
{
Type = questionGrp.Select(i => i.QuestionType).First(),
})
}
})
}
});
What I am trying to achieve with this query? To get list of TypeModel.
If you'll notice the table first three rows will fetch me one typeModel and another 6 rows another typeModel but it will have two ConditonModels and each condition Model, 3 questionModel.
The following should work:
Group by CodeType first, then inside each group, group by ConditionType, Description and select appropriate results.
var results = allTypes.GroupBy(
type => type.CodeType, // CodeType key selector
(codeType, elements) => new TypeModel
{
Type = codeType,
Conditions = elements.GroupBy(
x => new { x.ConditionType, x.Description }, // ConditionType key selector
x => x.QuestionType, // QuestionType selector as elements of the ConditionType group
(condition, elements2) => new ConditionModel
{
Type = condition.ConditionType,
Description = condition.Description,
// Questions transformation
Questions = elements2.Select(q => new QuestionModel { Type = q }).ToList()
}).ToList()
});
In case you are confused by so much nested LINQ, there is nothing wrong in using some plain old loops in order to create your resulting data:
var results = new List<TypeModel>();
foreach (var item in allTypes.GroupBy(type => type.CodeType))
{
var conditionsList = new List<ConditionModel>();
foreach (var item2 in item.GroupBy(x => new { x.ConditionType, x.Description }))
{
conditionsList.Add(new ConditionModel
{
Type = item2.Key.ConditionType,
Description = item2.Key.Description,
Questions = item2.Select(x => new QuestionModel { Type = x.QuestionType }).ToList()
});
}
results.Add(new TypeModel
{
Type = item.Key,
Conditions = conditionsList
});
}
Related
I've three lists in an object and want to perform order by operation using LINQ
object containing lists
public class ApplicationCommunications
{
public ApplicationCommunications()
{
listNotification = new List<ApplicationNotifications>();
listEmail = new List<ApplicationEmail>();
listSMS = new List<ApplicationSMS>();
}
public List<ApplicationNotifications> listNotification { get; set; }
public List<ApplicationEmail> listEmail { get; set; }
public List<ApplicationSMS> listSMS { get; set; }
}
Getting data from db
ApplicationCommunications applicationCommunications = new ApplicationCommunications();
applicationCommunications.listNotification = GetApplicationNotification(applicationId).Select(c => new ApplicationNotifications
{
NotificationId = c.NotificationId,
Message = c.Message,
SendDate = c.SendDate.Value
}).ToList();
applicationCommunications.listEmail = GetApplicationEmails(applicationId).Select(t => new ApplicationEmail
{
EmailContent = t.Body,
EmailAddress = t.Email,
SendDate = t.SendDate.Value,
}).ToList();
applicationCommunications.listSMS = GetApplicationMessage(applicationId).Select(t => new ApplicationSMS
{
SMSContent = t.Body,
PhoneNumber = t.Phone,
SendDate = t.SendDate.Value,
}).ToList();
We've three lists each list of the object has "senddate" property now I want to make a new list from these three lists where we will have data in order. Is that possible?
How we can perform order by with send date? simply I want to display data in order.
Select method gives you Enumerable type of list. Enumerable can be ordered by OrderBy, so simply do this
applicationCommunications.listNotification = GetApplicationNotification(applicationId).Select(c => new ApplicationNotifications
{
NotificationId = c.NotificationId,
Message = c.Message,
NotificationSendDate = c.SendDate.Value
})
.OrderBy(an => an.NotificationSendDate)
.ThenBy(an => an.NotificationId)
.ToList();
EDIT:
You can read more here https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.orderby?view=net-5.0
pardon for the incomplete question. We've three lists each list of the
object has "senddate" property now I want to make a new list from
these three lists where we will have data in order. Is that possible?
As shown in the other answer you need OrderBy:
List<DateTime> orderedSendDates = applicationCommunications.listNotification
.Select(x => x.NotificationSendDate)
.Concat(applicationCommunications.listEmail.Select(x => x.EmailSendDate))
.Concat(applicationCommunications.listSMS.Select(x => x.SMSSendDate))
.OrderBy(dt => dt)
.ToList();
If you want unique DateTimes use Distinct before the OrderBy.
If you don't have these properties initialized when you want the list you could do:
List<DateTime> orderedSendDates =
GetApplicationNotification(applicationId).Select(x => x.SendDate)
.Concat(GetApplicationEmails(applicationId).Select(x => x.SendDate))
.Concat(GetApplicationMessage(applicationId).Select(x => x.SendDate))
.Where(sendDateOrNull => sendDateOrNull.HasValue)
.Select(sendDateOrNull => sendDateOrNull.Value)
.OrderBy(dt => dt)
.ToList();
If you want a big list containing the different types of elements, ordered by sendDate (but not just a list of dateTime), you may first create a common type for that :
public class SentElement {
public string ElementDescription {get ; set;}
public DateTime SendDate { get; set;}
}
Then map your different types to the common type using Select, filling the description the way you want for each type of element:
var listNotification = GetApplicationNotification(applicationId).Select(c => new SentElement
{
ElementDescription = c.NotificationId + c.Message,
SendDate= c.SendDate.Value
}).ToList();
var listEmail = GetApplicationEmails(applicationId).Select(t => new SentElement
{
ElementDescription = t.EmailContent + t.EmailAddress,
SendDate = t.SendDate.Value,
}).ToList();
var listSMS = GetApplicationMessage(applicationId).Select(t => new SentElement
{
ElementDescription = t.Body + t.Phone,
SendDate = t.SendDate.Value,
}).ToList();
And finally merging and ordering the result :
var mergedList = listNotification.Concat(listEmail).Concat(listSMS).OrderByDescending(t=> t.SendDate);
I have a requirement for filter aggregations using NEST. But since I don't know much about this, I have made the below:
class Program
{
static void Main(string[] args)
{
ISearchResponse<TestReportModel> searchResponse =
ConnectionToES.EsClient()
.Search<TestReportModel>
(s => s
.Index("feedbackdata")
.From(0)
.Size(50000)
.Query(q =>q.MatchAll())
);
var testRecords = searchResponse.Documents.ToList<TestReportModel>();
result = ComputeTrailGap(testRecords);
}
private static List<TestModel> ComputeTrailGap(List<TestReportModel> testRecords)
{
var objTestModel = new List<TestModel>();
var ptpDispositionCodes = new string[] { "PTP" };
var bptpDispositionCodes = new string[] { "BPTP","SBPTP" };
int gapResult = testRecords.Where(w => w.trailstatus == "Gap").Count();
var ptpResult = testRecords.Where(w => ptpDispositionCodes.Contains(w.lastdispositioncode)).ToList().Count();
var bptpResult = testRecords.Where(w => bptpDispositionCodes.Contains(w.lastdispositioncode)).ToList().Count();
objTestModel.Add(new TestModel { TrailStatus = "Gap", NoOfAccounts = gapResult });
objTestModel.Add(new TestModel { TrailStatus = "PTP", NoOfAccounts = ptpResult });
objTestModel.Add(new TestModel { TrailStatus = "BPTP", NoOfAccounts = bptpResult });
return objTestModel;
}
}
DTO
public class TestReportModel
{
public string trailstatus { get; set; }
public string lastdispositioncode { get; set; }
}
public class TestOutputAPIModel
{
public List<TestModel> TestModelDetail { get; set; }
}
public class TestModel
{
public string TrailStatus { get; set; }
public int NoOfAccounts { get; set; }
}
This program works but as can be figure out that we are only accessing the Elastic Search via NEST and the rest of the Aggregations /Filter are done using Lambda.
I would like to perform the entire operation (Aggregations /Filter etc) using NEST framework and put it in the TestModel using filter aggregation.
How can I construct the DSL query inside NEST?
Update
I have been able to make the below so far but the count is zero. What is wrong in my query construction?
var ptpDispositionCodes = new TermsQuery
{
IsVerbatim = true,
Field = "lastdispositioncode",
Terms = new string[] { "PTP" },
};
var bptpDispositionCodes = new TermsQuery
{
IsVerbatim = true,
Field = "lastdispositioncode",
Terms = new string[] { "BPTP" },
};
ISearchResponse<TestReportModel> searchResponse =
ConnectionToES.EsClient()
.Search<TestReportModel>
(s => s
.Index("feedbackdata")
.From(0)
.Size(50000)
.Query(q =>q.MatchAll())
.Aggregations(fa => fa
.Filter("ptp_aggs", f => f.Filter(fd => ptpDispositionCodes))
.Filter("bptp_aggs", f => f.Filter(fd => bptpDispositionCodes))
)
);
Result
I see that you are trying to perform a search on the type of TestReportModel. The overall structure of your approach seems good enough. However, there is a trouble with the queries that are being attached to your filter containers.
Your TestReportModel contains two properties trialStatus and lastdispositioncode. You are setting the Field property as description inside your terms query. This is the reason that you are seeing the counts as zero. The model on which you are performing the search on (in turn the index that you are performing the search on) does not have a property description and hence the difference. NEST or Elasticsearch, in this case does not throw any exception. Instead, it returns count zero. Field value should be modified to lastdispositioncode.
// Type on which the search is being performed.
// Response is of the type ISearchResponse<TestReportModel>
public class TestReportModel
{
public string trailstatus { get; set; }
public string lastdispositioncode { get; set; }
}
Modified terms queries are as follows
// Field is "lastdispositioncode" and not "description"
// You may amend the Terms field as applicable
var ptpDispositionCodes = new TermsQuery
{
IsVerbatim = true,
Field = "lastdispositioncode",
Terms = new string[] { "PTP" },
};
var bptpDispositionCodes = new TermsQuery
{
IsVerbatim = true,
Field = "lastdispositioncode",
Terms = new string[] { "BPTP", "SBPTP" },
};
Since it seems that the values to lastdispositioncode seem to take a single word value (PTP or BPTP from your examples), I believe, it is not going to matter if the field in the doc is analyzed or not. You can further obtain the counts from the ISearchResponse<T> type as shown below
var ptpDocCount = ((Nest.SingleBucketAggregate)response.Aggregations["ptp_aggs"]).DocCount;
var bptpDocCount = ((Nest.SingleBucketAggregate)response.Aggregations["bptp_aggs"]).DocCount;
Edit: Adding an approach for keyword search
QueryContainer qc1 = new QueryContainerDescriptor<TestReportModel>()
.Bool(b => b.Must(m => m.Terms(t => t.Field(f => f.lastdispositioncode.Suffix("keyword"))
.Terms(new string[]{"ptp"}))));
QueryContainer qc2 = new QueryContainerDescriptor<TestReportModel>()
.Bool(b => b.Must(m => m.Terms(t => t.Field(f => f.lastdispositioncode.Suffix("keyword"))
.Terms(new string[]{"bptp", "sbptp"}))));
Now these query containers can be hooked to your aggregation as shown below
.Aggregations(aggs => aggs
.Filter("f1", f => f.Filter(f => qc1))
.Filter("f2", f => f.Filter(f => qc2)))
The queries for aggregations that are generated by the NEST client in this case look like below
"f1": {
"filter": {
"bool": {
"must": [
{
"terms": {
"lastdispositioncode.keyword": [
"bptp"
]
}
}
]
}
}
}
Also, coming back to the case of search being case-insensitive, Elasticsearch deals with search in a case-insensitive fashion. However, it varies depending on analyzed vs non-analyzed fields. Analyzed fields are tokenized and text fields are by default tokenized. We use the suffix extension method of NEST on analyzed fields ideally and to get an exact match on the analyzed field. More about them here
I'm new with AutoMapper and i'm using 5.1.1 version.
I tried to map a list of model with a list of extended model. I get empty object in return.
My source model
public class spt_detail
{
public string Name { get; set; }
public string Age { get; set; }
}
My destination model
public class spt_detail_extended : spt_detail
{
public string Mm1 { get; set; }
public string Mm2 { get; set; }
}
AutoMapper code
spt_detail details = db.spt_detail.ToList();
Mapper.Initialize(n => n.CreateMap<List<spt_detail>, List<spt_detail_extended>>());
List<spt_creance_detail_enrichi> cenr = AutoMapper.Mapper.Map<List<spt_detail>, List<spt_detail_enrichi>>(details);
Issue : details contains 8 rows but cenr 0.
Someone can helps me ?
Mapping for spt_detail to spt_detail_extended.
You should create map rules only for models, like that:
Mapper.Initialize(cfg =>
{
cfg.CreateMap<spt_detail, spt_detail_extended>();
});
And after that, you hould use the construction:
List<spt_detail_extended> extendeds = Mapper.Map<List<spt_detail_extended>>(details);
If you want to map other models, just add or edit your configuration.
Don't map the list, instead map like so:
Mapper.Initialize(n => n.CreateMap<spt_detail, spt_detail_extended>());
You call to do the map would stay the same:
List<spt_detail_extended> cenr = AutoMapper.Mapper.Map<List<spt_detail>, List<spt_detail_extended>>(details);
You have missed two steps in here.
The first one is that you need to initialize a list of your business object instead of initializing only a single one.
You retrieve a list from your database (you use .Tolist())
here is a sample on how i initialized an object:
List <SptDetail> details = new List <SptDetail> {
new SptDetail {
Age = "10",
Name = "Marion"
},
new SptDetail {
Age = "11",
Name = "Elisabeth"
}
};
The second misstep is that you were mapping lits, i suggest work with your single business class objects instead like following:
Mapper.Initialize(n => n.CreateMap<SptDetail, SptDetailExtended>()
.ForMember(obj => obj.ExProp1, obj => obj.MapFrom(src => src.Name))
.ForMember(obj => obj.ExProp2, obj => obj.MapFrom(src => src.Age)));
And the key in the hole story is to use .ForMember to specify wich member goes where because the properties does not have same name.
here is a code sample that runs like a charm with:
internal class Program
{
public static List<SptDetailExtended> InitializeExtendedObjects()
{
var details = new List<SptDetail>
{
new SptDetail
{
Age = "10",
Name = "Marion"
},
new SptDetail
{
Age = "11",
Name = "Elisabeth"
}
};
//this is wrong db.spt_detail.ToList();
Mapper.Initialize(n => n.CreateMap<SptDetail, SptDetailExtended>()
/*you need to use ForMember*/ .ForMember(obj => obj.ExProp1, obj => obj.MapFrom(src => src.Name))
.ForMember(obj => obj.ExProp2, obj => obj.MapFrom(src => src.Age)));
//instead of this Mapper.Initialize(n => n.CreateMap<List<spt_detail>, List<spt_detail_extended>>());
//change your mapping like following too
var cenr = Mapper.Map<List<SptDetailExtended>>(details);
return cenr;
}
private static void Main(string[] args)
{
var result = InitializeExtendedObjects();
foreach (var sptDetailExtended in result)
{
Console.WriteLine(sptDetailExtended.ExProp1);
Console.WriteLine(sptDetailExtended.ExProp2);
}
Console.ReadLine();
}
}
Hope this helps!
I have this class
public class BlessingDTO
{
public List<string> BlessingCategoryName;
public List<string> Blessings;
}
I am Getting the response of the two lists this way:
public async Task<List<BlessingDTO>> GetBlessing(string UserType)
{
string blessing = "Blessing_" + UserType;
List<BlessingDTO> results = new List<BlessingDTO>();
using (DTS_OnlineContext context = new DTS_OnlineContext())
{
var items = await context.Messages.AsNoTracking().Where(x => x.MessageContext == blessing).GroupBy(x=>x.GroupKey).Select(b=>b.OrderBy(x=>x.Sort)).ToListAsync();
if (items.Count() > 0)
{//Notes.Select(x => x.Author).Distinct();
results = items.ToList().ConvertAll(x => new BlessingDTO()
{ BlessingCategoryName = x.ToList().Select(y => y.MessageName).Distinct().ToList(),
Blessings = x.ToList().Select(y => y.MessageText).ToList()
});
}
}
return results;
}
if I am changing the class, for my porpuse to be:
public class BlessingDTO
{
public List<string> BlessingCategoryName;
public List<bless> Blessings;
}
public class bless
{
public string text;
public int length;
}
how can I initialize the new class ?
Blessings = new bless
won't give the results. how can I save the data to bring them in the response
Let's focus in this part:
items
.ToList()
.ConvertAll(x =>
new BlessingDTO()
{
BlessingCategoryName = x.ToList().Select(y => y.MessageName).Distinct().ToList(),
Blessings = x.ToList().Select(y => y.MessageText).ToList()
}
);
where items is probably a List<List<Message>>, thus x being a List<Message>.
Now what is causing an error is the following: Blessings = x.ToList().Select(y => y.MessageText).ToList(). This creates a new list for the list of messages, then selects the MessageText from that list, which results in IEnumerable<string>. In the end a new list is created for these strings. This list of strings isn't assignable to List<bless>, thus will generate an error.
What you want is a result of List<bless>, so we need to convert the List<Message> list into a List<bless> somehow. We know how to do that, namely with a select: x.Select(message => new bless()).ToList(). All we have to do is fill in the properties of bless: x.Select(message => new bless { text = message.MessageText }).ToList(). The other property is up to you.
You can initialise the list like this:
public class BlessingDTO
{
public List<string> BlessingCategoryName;
public List<bless> Blessings = new List<bless>();
}
Although, I would recommend these fields are changes to properties, as that is more idiomatic in C#
public class BlessingDTO
{
public List<string> BlessingCategoryName {get;set;}
public List<bless> Blessings {get;set;} = new List<bless>();
}
I received some help here with the following LINQ query, but am still struggling with it. The result I'm trying to obtain is to display some attributes and their values from an xml file in a DataGridView control. I'm calling my method from a button click and am trying to pass back the list for display in the grid. Here is an example of the row:
<z:row CenterCode="JAX" CenterName="Jacksonville" Version="1.0" NextExport="66742" NextImport="29756" LastImportTime="2015-06-10T14:48:33" FtpProxyServer="" FtpUserName="" FtpPassword="" ResetImportID="False"/>
Here is the method:
public static List<string[]> MonitorCounts(string upperLimit)
{
// Load xml
XDocument xmldoc = XDocument.Load(#"c:\XML\Configuration.xml");
XNamespace z = "#RowsetSchema";
Int32 limit = Convert.ToInt32(upperLimit);
var elementQuery = xmldoc.Descendants(z + "row").Where(e => (long?)e.Attribute("NextExport") > limit | (long?)e.Attribute("NextImport") > limit);
var attributes = elementQuery.Select(e => e.Attributes().Select(a => new KeyValuePair<string, string>(a.Name.LocalName, (string)a)).ToList()).ToList();
return attributes;
}
My questions are how to select only specific attributes and values in attributes. If I do something like this:
var attributes = elementQuery.Select(e => e.Attributes("CenterName").Select(a => new KeyValuePair<string, string>(a.Name.LocalName, (string)a)).ToList()).ToList();
then this is returned:
[0] = {[CenterName, Jacksonville]}
I need to select this and 4 others. I'm also getting a convrsion error - Cannot implicitly convert type 'System.Collections.Generic.List<System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string,string>>>' to 'System.Collections.Generic.List<string[]>. Appreciate any pointers to help me along.
You can use an anonymous type:
var attributes =
elementQuery.Select(e => new
{
CenterName = (string)e.Attribute["CenterName"],
Version = (string)e.Attribute["Version"],
// more attributes
}).ToList();
You can't however return this from the method in a useful way. So if you really need both the attribute name and the attribute value as strings, try this approach instead:
var attributes =
elementQuery.Select(e => new []
{
Tuple.Create("CenterName", (string)e.Attribute["CenterName"]),
Tuple.Create("Version", (string)e.Attribute["Version"]),
// more attributes
}).SelectMany(x => x).ToList();
The return type of your method now has to be List<Tuple<string, string>>.
And finally, if you actually need a List<string[]> as the return type, use this code:
var attributes =
elementQuery.Select(e => new []
{
new [] { "CenterName", (string)e.Attribute["CenterName"] },
new [] { "Version", (string)e.Attribute["Version"] },
// more attributes
}).SelectMany(x => x).ToList();
I solved my own problem. Here is what I did:
Created a class for the attributes needed:
public class dataRow
{
public string CenterName { get; set; }
public string CenterCode { get; set; }
public string NextImport { get; set; }
public string NextExport { get; set; }
public string LastImportTime { get; set; }
}
Selected the results into it:
List<dataRow> dataRows = elementQuery.Select( e => new dataRow
{ CenterName = (string)e.Attribute("CenterName"),
CenterCode = (string)e.Attribute("CenterCode"),
NextImport = (string)e.Attribute("NextImport"),
NextExport = (string)e.Attribute("NextExport"),
LastImportTime = (string)e.Attribute("LastImportTime") }).ToList();
Changed my method to return the correct object:
public static List<dataRow> MonitorCounts(string upperLimit)
Set my grids datasource to the method return:
dataGridView1.DataSource = xmlProcessing.MonitorCounts(tbxUpperLimit.Text.ToString());
return dataRows;