I have a list which has following result set .
public class PrExport
{
public string pId { get; set; }
public string prName { get; set; }
public string hId { get; set; }
public string hName { get; set; }
public string np { get; set; }
public string ftId { get; set; }
public string paName { get; set; }
}
List<PrExport> prTable = new List<PrExport>();
prTable =some 3rdparty service returns the result set as below;
I want the result set set as below
This is the code I have used.
var res = from c in prTable
group c by new { c.pId, c.prName, c.hId, c.hName, c.np, c.ftId } into gr
select new PrExport()
{
pId = gr.Key?.pId,
prName = gr.Key?.prName,
hId = gr.Key?.hId,
hName = gr.Key?.hName,
np = gr.Key?.np,
ftId = gr.Key?.ftId,
paName = string.Join(",", gr.Select(c => c?.paName))
};
return prTable = res.ToList();
I'm getting the result set as below .Here I'm not getting comma seperated paName which I wanted.What am I missing here?
As others have pointed out your code seems to be fine.
I don’t know how you produced the pictures of the tables but if that is a CSV file opened in e.g. Excel, it will not allow a comma , inside one of the values unless you use semi colon ; as delimiter in your CSV file.
check this out it may help you.
https://stackoverflow.com/questions/48723477/linq-one-to-many-relations-as-comma-separated-values
Related
This is a general coding question of how I can share the same list of data between the stages of reading, modifying and writing.
I'm a novice and new to C# too, so I'm struggling. I was expecting to be able to: read the CSV into the variable records, modify one or more of the records and then write out the modified data as a new CSV, but there is a scope issue with the using function.
The code below won't compile because records is out of scope in both the foreach loop that aims to modify some data and again when I'm trying to write the modified file.
I have tried various things to make records a more global variable but they have all failed and I am out of my depth.
I'm not even sure that this is the best way to approach the problem, so any advice would be appreciated.
private void Btn_Merge_Click(object sender, EventArgs e)
{
// Read the CSV into 'records'
StreamReader reader = new StreamReader(textBox_Shopify.Text);
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
var records = csv.GetRecords<Contact>();
}
// We now need to find the record for a specific person and change it
foreach (Contact customer in records)
{
if (customer.Email == "john.dow#business.com") // Hard-coded while testing
{
string Tags = customer.Tags; // Get the current tags
// If the Lead Marking tag is not already there, add it
if (!Tags.Contains("Send me Lead Marketing"))
{
// If there are tags already there, append a semi-colon separator
if (customer.Tags.Length > 0)
{
customer.Tags += ";";
}
customer.Tags += "Send me Lead Marketing";
MessageBox.Show(customer.Email + " Tags: " + customer.Tags); //Just while I'm testing
}
}
// If the customer is not in the list, add them as a new record
// To do...
}
// We can now write out the modified file
using (var writer = new StreamWriter(#"C:\temp\Output.csv"))
using (var outputCSV = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
outputCSV.WriteRecords(records);
}
}
public class Contact
{
[Name("First Name")] // This 'attribute' allows the class property First_Name to be matched to the header "First Name"
public string First_Name { get; set; }
[Name("Last Name")]
public string Last_Name { get; set; }
public string Email { get; set; }
[Name("Accepts Email Marketing")]
public string Accepts_Email_Marketing { get; set; }
public string Company { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string Province { get; set; }
[Name("Province Code")]
public string Province_Code { get; set; }
public string Country { get; set; }
[Name("Country Code")]
public string Country_Code { get; set; }
public string Zip { get; set; }
public string Phone { get; set; }
[Name("Accepts SMS Marketing")]
public string Accepts_SMS_Marketing { get; set; }
[Name("Total Spent")]
public string Total_Spent { get; set; }
[Name("Total Orders")]
public string Total_Orders { get; set; }
public string Tags { get; set; }
public string Note { get; set; }
[Name("Tax Exempt")]
public string Tax_Exempt { get; set; }
}
#Mureinik is halfway there. Since CsvHelper will only yield records as you iterate them, you will also need to call ToList() or in some other way iterate the records within the using statement.
IEnumerable<Contact> records;
// Read the CSV into 'records'
StreamReader reader = new StreamReader(textBox_Shopify.Text);
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
records = csv.GetRecords<Contact>().ToList();
}
You could define records in the scope of the entire method:
private void Btn_Merge_Click(object sender, EventArgs e)
{
IEnumerable<Contact> records;
// result of the code...
Although the thing I want to do seems be really trivial I can not find a way to achieve what I want. I know there exist multiple questions how to put class properties into the list together and separate it by a comma like that on SO, but none of them seemed to be relevant to my case.
I have a class Form defined as follows:
public class Form
{
public string CustomerName { get; set; }
public string CustomerAdress { get; set; }
public string CustomerNumber { get; set; }
public string OfficeAdress { get; set; }
public string Date { get; set; }
public Boolean FunctionalTest { get; set; }
public string Signature { get; set; }
public Form()
{
}
}
In the MainPage.xaml.cs, I create a List<Form> with the Form class properties and subsequently I would like to create a string with all of those class properties separated by a comma. For that case I use basic Join method with Select which converts any kinds of objects to string.
I do that by createCSV method inside MainPage.xaml.cs :
void createCSV()
{
var records = new List<Form>
{
new Form {CustomerName = customerName.Text,
CustomerAdress = customerAdress.Text,
CustomerNumber = customerNumber.Text,
OfficeAdress = officeAdress.Text,
Date = date.Date.ToString("MM/dd/yyyy"),
FunctionalTest = testPicker.ToString()=="YES" ? true : false,
Signature = signature.Text
}
};
string results = String.Join(",", (object)records.Select(o => o.ToString()));
}
The problem is instead of the desirable outcome which is:"Mark Brown,123 High Level Street,01578454521,43 Falmouth Road,12/15/2020,false,Brown"
I am getting: "System.Linq.Enumerable+SelectListIterator'2[MyApp.Form,System.String]"
PS. As you have noticed I am newbie in C#. Instead of non constructive criticism of the code, please for a valuable reply which would help me to understand what am I doing wrong.
Thanks in advance
In the Form class, You can override the ToString() method and use System.Reflection to get your comma string.
Form.cs
public class Form
{
public string CustomerName { get; set; }
public string CustomerAdress { get; set; }
public string CustomerNumber { get; set; }
public string OfficeAdress { get; set; }
public string Date { get; set; }
public bool FunctionalTest { get; set; }
public string Signature { get; set; }
public override string ToString()
{
string modelString = string.Empty;
PropertyInfo[] properties = typeof(Form).GetProperties();
foreach (PropertyInfo property in properties)
{
var value = property.GetValue(this); // you should add a null check here before doing value.ToString as it will break on null
modelString += value.ToString() + ",";
}
return modelString;
}
}
Code
List<string> CSVDataList = new List<string>();
List<Form> FormList = new List<Form>();
...
foreach (var data in FormList)
{
CSVDataList.Add(data.ToString());
}
Now you have a list of string CSVDataList with each Form object's data in comma style
P.S.
for DateTime
var value = property.GetValue(this);
if(value is DateTime date)
{
modelString += date.ToString("dd.MM.yyyy") + ",";
}
I'm trying to get expressions for the first level properties of a given class, through an array of strings, each one related to the property name to get the expression:
public List<MemberExpression> CreateMembers(string propertyPaths)
{
List<MemberExpression> test = new List<MemberExpression>();
var propertiesPath = propertyPaths.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var propertyPath in propertiesPath)
{
var mainParameter = Expression.Parameter(typeof(Process), "e");
var property = Expression.PropertyOrField(mainParameter, typeof(Process), propertyPath);
test.Add(property);
property = null;
mainParameter = null;
}
return test;
}
I'm trying to get the public virtual properties from this class:
public class Process
{
public int? CompanyId { get; set; }
public int? RecommendationId { get; set; }
public int? DiagnosisNodeId { get; set; }
[ForeignKey("DiagnosisId")]
public virtual Diagnosis Diagnosis { get; set; }
[ForeignKey("DiagnosisNodeId")]
public virtual DiagnosisNode DiagnosisNode { get; set; }
[ForeignKey("RecommendationId")]
public virtual Recommendation Recommendation { get; set; }
}
but after the first iteration the Expression.Property always throws an exception that the given property doesn't exist in the class Process.
Can someone help me with this?
an example of the propertyPaths is: "Diagnosis, DiagnosisNode"
The problem is the space after the comma. There is no property called " DiagnosisNode" (with a leading space). Either use .Trim() on the strings coming back from Split, or don't include the space in the first place.
I have a list created from a stored procedure using EF6.0
I have also created 3 classes
public class Resas
{
public string todo{ get; set; }
public string prop { get; set; }
public string Code { get; set; }
public string statusCode { get; set; }
public string checkin { get; set; }
public string checkout { get; set; }
public List<profiles> profiles { get; set; }
}
public class profiles
{
public string action { get; set; }
public string id { get; set; }
public string profileType { get; set; }
public string title { get; set; }
public string firstName { get; set; }
public string middleName { get; set; }
public string lastName { get; set; }
public List<emailAddresses> emailAdresses { get; set; }
}
public class emailAddresses
{
public string emailAddress { get; set; }
public string emailAddress2 { get; set; }
}
I am doing a for-loop in the list and I need to get certain columns and put it in the array (I will put two, to keep it simple)
myEntities db = new myEntities();
List<rev_Result> revList = new List<rev_Result>();
revList.Clear();
revList = db.rev().ToList();
for (int i = 0; i < revList.Count(); i++)
{
Resas resas = new Resas();
profiles[] profiles = new profiles[1];
resas.todo = revList[i].todo;
resas.profiles[0].lastName = revList[i].lastName;
}
I am not familiar with C# as you can see from the psedo-code above.
I cannot figure out how to feed the Resas with data and then its Profile with data and then move to the next Resas entry.
Any help appreciated.
That's fairly simple using Linq:
Resas resas = new Resas();
resas.profiles = revList
.Select(x => new profiles() { action = x.todo, lastName = x.lastName })
.ToList();
What's happening here is: You loop through every entry in revList and get your wanted data structure (that's what Select is doing). x refers to the current entry in the loop, while the stuff to the right side of the arrow is you 'output': a new instance of your profiles class with the members assigned accordingly. The result of all of this is then converted to a list (before ToList(), think of it as a recipe to create the list) and assigned to resas.profiles.
By the way, a word on conventions: Usually, in C#, you would give your classes a name that starts with a capital letter. Also, your profiles class seems to contain data of exactly one profile, so a better name might be Profile. This also makes your data structure more clear, since List<profiles> seems to be a list of lists of profiles - but that's not what it actually is, is it?
Furthermore, Members generally start with a capital letter as well, so instead of action, lastName, you'd have: Action and LastName.
You can try with Linq. This is the code that should solve your issue, but Resas class doesn't have action property:
List<Resas> ls = revList.Select(x => new Resas() {
action = x.todo,
profiles = new List<profiles>() {
new profiles { lastName = x.lastName }
}
).ToList();
If you need to use action property of inprofiles` class:
List<Resas> ls = revList.Select(x => new Resas() {
profiles = new List<profiles>() {
new profiles {
action = x.todo,
lastName = x.lastName
}
}
).ToList();
This is my first question on SO, please let me know if I am doing anything wrong!
I am trying to parse an XML similar to this:
<LiveUpdate>
<CityID>F0A21EA2</CityID>
<CityName>CityTown</CityName>
<UserName>john</UserName>
<ApplicationDetails>
<ApplicationDetail
Application="AC"
Licensed="true"
Version="2015.2"
Patch="0001"
/>
<ApplicationDetail
Application="AP"
Licensed="true"
Version="2015.2"
Patch="0002"
/>
</ApplicationDetails>
</LiveUpdate>
I have classes that look like this:
public class Client
{
public string cityID { get; set; }
public string cityName { get; set; }
public string userName { get; set; }
public List<Apps> appList { get; set; }
}
public class Apps
{
public string app { get; set; }
public string licensed { get; set; }
public string version { get; set; }
public string patch { get; set; }
}
I need to be able to have a client class with a list of all the application details to be iterated over.
So far the best I've come up with is:
XDocument xml = XDocument.Load(#"C:\blah\Desktop\1.xml");
var liveUpdate = xml.Root;
var clients = (from e in liveUpdate.Elements()
select new Client()
{
cityID = e.Element("CityID").Value,
cityName = e.Element("CityName").Value,
userName = e.Element("UserName").Value,
appList = e.Elements("ApplicationDetails")
.Select(a => new Apps()
{
app = a.Element("Application").Value,
licensed = a.Element("Licensed").Value,
version = a.Element("Version").Value,
patch = a.Element("Patch").Value
}).ToList()
});
However, I'm currently running into an error that says Object reference not set to an instance of an object.
I've seen some similar examples on here, but not that deal with data before the multiple children.
I'm fairly new to XML and Linq so any help here would be greatly appreciated!
Your XML only contains one LiveUpdate tag, so rather than iterating over all of the elements inside of it, you just want to look at the Root element.
In ApplicationDetails, Application, Licensed and such are attributes, not elements. Use .Attribute() to access them.
ApplicationDetails is a single tag, and inside it you have ApplicationDetail tags.
There is no DateTime element in your LiveUpdate tag.
This works:
var liveUpdate = xml.Root;
var e = liveUpdate;
var clients = new Client()
{
cityID = e.Element("CityID").Value,
cityName = e.Element("CityName").Value,
userName = e.Element("UserName").Value,
//dateTime = e.Element("DateTime").Value,
appList = e.Element("ApplicationDetails").Elements("ApplicationDetail")
.Select(a => new Apps()
{
app = a.Attribute("Application").Value,
licensed = a.Attribute("Licensed").Value,
version = a.Attribute("Version").Value,
patch = a.Attribute("Patch").Value
}).ToList()
};
Since you have already defined a class into which you wish to deserialize, you can use XmlSerializer to deserialize it for you.
First, let's rename some of your property names to more closely match the XML and c# naming conventions:
[XmlRoot("LiveUpdate")]
public class Client
{
public string CityID { get; set; }
public string CityName { get; set; }
public string UserName { get; set; }
[XmlArray("ApplicationDetails")]
[XmlArrayItem("ApplicationDetail")]
public List<Apps> AppList { get; set; }
}
public class Apps
{
[XmlAttribute]
public string Application { get; set; }
[XmlAttribute]
public bool Licensed { get; set; }
[XmlAttribute]
public string Version { get; set; }
[XmlAttribute]
public string Patch { get; set; }
}
Then add the following extension methods:
public static class XmlSerializationHelper
{
public static T LoadFromXML<T>(this string xmlString)
{
using (StringReader reader = new StringReader(xmlString))
{
object result = new XmlSerializer(typeof(T)).Deserialize(reader);
if (result is T)
{
return (T)result;
}
}
return default(T);
}
public static T LoadFromFile<T>(string filename)
{
using (var fs = new FileStream(filename, FileMode.Open))
{
object result = new XmlSerializer(typeof(T)).Deserialize(fs);
if (result is T)
{
return (T)result;
}
}
return default(T);
}
}
Now you can deserialize from your XML file as follows:
string fileName = #"C:\blah\Desktop\1.xml";
var client = XmlSerializationHelper.LoadFromFile<Client>(fileName);
I manually updated your Client class to map correctly to the provided XML, but if you wanted to do it automatically, see here: Generate C# class from XML.