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;
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 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 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
});
}
I have this class:
public class allFields
{
public string EAN { get; set; }
public string title { get; set; }
public string qty { get; set; }
public string price { get; set; }
public DateTime date { get; set; }
}
And a function that return an anonymous type:
public IEnumerable<object> stockEtatQty()
{
List<allFields> afList = new List<allFields>();
var query = from x in ctx.book
where x.qty > 0
select x;
foreach (var item in query)
{
allFields af = new allFields();
af.EAN = item.EAN;
af.title = item.Titre;
af.qty = ""+item.Quantite;
afList.Add(af);
}
var q = from x in afList
select new { EAN=x.EAN, Title=x.title, Quantity=x.qty };
return q; //q is a IEnumerable<'a> where a is new {string EAN, string Title, string Quantity}
}
In my WinForm a use this function as below:
private void QuantityToolStripMenuItem_Click(object sender, EventArgs e)
{
ServiceStock sstock = new ServiceStock();
var q = sstock.stockEtatQty().ToList();// q is a list<object>
string str = "";
foreach (var item in q)
{
str += item + Environment.NewLine;
}
MessageBox.Show(str);
}
The result is:
{ EAN = 1, Title = CSharp Security, Quantity = 970 }
{ EAN = 2, Title = MISC, Quantity = 100 }
...
What I want?
I want not like the result above, but separate each field apart of the item in the loop foreach, e.g get item.EAN, item.Title and item.Quantity.
If there is no solution for my problem I would like to know an alternative,
Thanks for help.
The obvious solution is to create a custom type (let's call it BookInfo) and return a IEnumerable<BookInfo> instead of a IEnumerable<object> (and maybe override ToString if you want to put the formatting into this class itself).
Then you can easily format the output.
public class BookInfo
{
public string EAN {get;set;}
public string Title {get;set;}
public int Quantity {get;set;}
}
public IEnumerable<BookInfo> stockEtatQty()
{
...
var q = from x in afList
select new BookInfo { EAN=x.EAN, Title=x.title, Quantity=x.qty };
return q;
}
private void QuantityToolStripMenuItem_Click(object sender, EventArgs e)
{
ServiceStock sstock = new ServiceStock();
var q = sstock.stockEtatQty();
var message = string.Join(Environment.NewLine,
q.Select(item => String.Format("{0} {1} {2}", item.EAN, item.Title, item.Quantity)));
MessageBox.Show(message);
}
Since the static type information about the object of anonymous type is lost by the time that you exit stockEtatQty() method, you could cast the object to dynamic and access fields like this:
str = string.Join(Environment.NewLine, q.Cast<dynamic>().Select(item =>
string.Format("{0} {1} {2}", item.EAN, item.Title, item.Quantity)
));
The cast to dynamic tells the compiler that EAN, Title, and Quantity need to be resolved at runtime.
Note that I also replaced the foreach loop with a call to string.Join to improve performance: repeated string concatenation creates unnecessary partial string objects, which string.Join avoids. Another solution would be to use StringBuider instead of string concatenation +=.
stockEtatQty is in a project (Service) and QuantityToolStripMenuItem_Click is in another project (View)
Unfortunately, this means that you would not be able to use anonymous types: anonymous types are generated with internal visibility, limiting their use to the assembly in which they are produced. You can use a work-around based on ExpandoObject described in this answer:
var q = afList.Select(x => {
dynamic res = new ExpandoObject();
res.EAN=x.EAN;
res.Title=x.title;
res.Quantity=x.qty;
return res;
});
Create a new class that represents the new object structure and return that.
var q = from x in afList
select new SmallerType { EAN=x.EAN, Title=x.title, Quantity=x.qty };
WinForm Function
foreach (SmallerType item in q)
{
//
}
You can use collection of dynamic objects instead of simple objects as return type of your method:
public IEnumerable<dynamic> stockEtatQty()
Then you will not have IntelliSense but at runtime properties will be found:
foreach (var item in sstock.stockEtatQty())
str += String.Format("{0}", item.EAN) + Environment.NewLine;
But I suggest you to create custom class with EAN, Title and Quantity properties. Or just use your allFields instead of anonymous objects.
Consider also to use StringBuilder for string creation to avoid creating lot of in-memory strings:
var builder = new StringBuilder();
foreach (var item in sstock.stockEtatQty())
builder.AppendFormat("{0}{1}", item.EAN, Environment.NewLine);
MessageBox.Show(builder.ToString());
Basically I'm looking to select both string columns and put it all into a single array of strings. Right now I'm having to do two selects and combine the results. It isn't a huge deal, I just think it looks awkward. Any suggestions on how to accomplish the same goal with one linq statement? Here is a test case I'm using to mess around:
[TestFixture]
public class test {
public class Values {
public string Present { get; set; }
public string Previous { get; set; }
public bool Flag { get; set; }
}
[Test]
public void test1() {
var list = new List<Values> {
new Values { Present = "present1", Previous = "previous1", Flag = false },
new Values { Present = "present2", Previous = "previous2", Flag = false },
new Values { Present = "present3", Previous = "previous3", Flag = true },
new Values { Present = "present4", Previous = "previous4", Flag = true }
};
var r1 = list.Where(c => c.Flag).Select(c => c.Present);
var r2 = list.Where(c => c.Flag).Select(c => c.Previous);
var combined = r1.Concat(r2);
Assert.AreEqual(4, combined.Count());
}
}
An alternative solution with using SelectMany (it keeps the duplicates):
var combined = list.Where(c => c.Flag)
.SelectMany(c => new[] { c.Present, c.Previous });
Assert.AreEqual(4, combined.Count());
Does the final ordering of the strings in the list matter? If not, it seems like this would be the clearest way to accomplish this:
var strings = new List<String>();
foreach (var value in list.Where(c => c.Flag))
{
strings.Add(value.Present);
strings.Add(value.Previous);
}