I'm trying to map CSV file into class object with C#. My problem is that i have 3 different files, but I want to fallow DRY principles. Can someone tell me how to change 'ParseLine' method to make it possible?
C# consol app.
This is how my FileReader looks like:
public class FileReader<T> : IFileReader<T> where T : Entity
{
private readonly ITransactionReader<T> _transactionReader;
public FileReader(ITransactionReader<T> transactionReader)
{
_transactionReader = transactionReader;
}
public List<T> GetInfoFromFile(string filePath)
{
var lines = File.ReadAllLines(filePath);
var genericLines = new List<T>();
foreach (var line in lines)
{
genericLines.Add(_transactionReader.ParseLine(line));
}
return genericLines;
}
}
public interface IFileReader<T> where T : Entity
{
List<T> GetInfoFromFile(string filePath);
}
This is how the object should look like.
public class TransactionReader : ITransactionReader<Transaction>
{
public Transaction ParseLine(string line)
{
var fields = line.Split(";");
var transaction = new Transaction()
{
Id = fields[0],
Month = int.Parse(fields[1]),
Day = int.Parse(fields[2]),
Year = int.Parse(fields[3]),
IncomeSpecification = fields[4],
TransactionAmount = int.Parse(fields[5])
};
return transaction;
}
}
public interface ITransactionReader<T>
{
T ParseLine(string line);
}
This is how I run it for test purposes.
class Program
{
private static readonly string filePath = "C:/Users/<my_name>/Desktop/C# Practice/ERP/ERP/CsvFiles/Transaction.csv";
static void Main(string[] args)
{
ITransactionReader<Transaction> transactionReader = new TransactionReader();
IFileReader<Transaction> fileReader = new FileReader<Transaction>(transactionReader);
List<Transaction> Test()
{
var obj = fileReader.GetInfoFromFile(filePath);
return obj;
}
var list = Test();
}
}
I'm looking to modify that line:
genericLines.Add(_transactionReader.ParseLine(line));
and method arguments to make it open for any CSV fil.
I don't mind to change that composition into something more effective.
Related
I have a Json file with following structure
{"status":"OK","masterlist":{"session":{"session_id":1621,"session_name":"Regular Session 2019"}
,"0":{"bill_id":001,"number":"2","change_hash":"xxxxxx","url":"xxxx","status_date":"2019-03-05","status":"1","last_action_date":"2019-03-05","last_action":"action","title":xxx,"description":xxxx"},
"2":{"bill_id":001,"number":"2","change_hash":"xxxxxx","url":"xxxx","status_date":"2019-03-05","status":"1","last_action_date":"2019-03-05","last_action":"action","title":xxx,"description":xxxx"},
"3":{"bill_id":001,"number":"2","change_hash":"xxxxxx","url":"xxxx","status_date":"2019-03-05","status":"1","last_action_date":"2019-03-05","last_action":"action","title":xxx,"description":xxxx"},
"4":{"bill_id":001,"number":"2","change_hash":"xxxxxx","url":"xxxx","status_date":"2019-03-05","status":"1","last_action_date":"2019-03-05","last_action":"action","title":xxx,"description":xxxx"},
I'm trying to put the contents to list of class like this:
public class LegiBill
{
public string bill_id;
public string number;
public string change_hash;
public string url;
public string status_date;
public string last_action_date;
public string last_action;
public string title;
public string description;
}
I'm using Newtonsoft.Jason with following code:
public static T ReadFromJsonStr<T>(string str) where T : new()
{
TextReader reader = null;
try
{
return JsonConvert.DeserializeObject<T>(str);
}
finally
{
if (reader != null)
reader.Close();
}
}
I don't have any problem with reading and parsing! I just don't know how to put the main contents which have labels like "0","1","2","3",.... to list of LegiBill like List<LegiBill>.
Thank you in advance.
If you cannot change json. You can do something like this inside ReadFromJsonStr method
public static List<LegiBill> ReadFromJsonStr(string str)
{
var parsedObject = JObject.Parse(str);
var popupJson = parsedObject["masterlist"].ToString();
var popupObj = JsonConvert.DeserializeObject<Dictionary<string, LegiBill>>(popupJson);
var filteredList = popupObj.Where(kvp => kvp.Key.Equals("session") == false).Select(x=>x.Value).ToList();
List<LegiBill> legiBills = new List<LegiBill>(filteredList);
foreach (var legiBill in filteredList)
{
if (legiBill != null)
{
legiBills.Add(legiBill);
}
}
return legiBills;
}
I' don't know much about C#.I'm trying to create a program that can calculate results for running competitions. I have created two methods: DoStuff(), with which I set up a Datagridview table columns and displays a text file splitted.And Method- Sorter(), with which I sort the runners into groups and display them in to the List boxes.
My problem.
Every time when i try to debug a program i get a following error:
'Cannot read from a closed TextReader.'
As you can see i set the StreamReader as public. But those two methods can not access it.
Is it possible in some way like this to use one StreamReader?!
Or i have to write my code in Form-Load and then it is the only way to access it?
My Code
public partial class Form1 : Form
{
public StreamReader sr = new StreamReader(#"C:\Users\Oscar\Desktop\result.txt");
public string line;
public List<string> V = new List<string>();
public List<string> V12 = new List<string>();
public List<string> V14 = new List<string>();
public List<string> V18 = new List<string>();
public List<string> V50 = new List<string>();
public List<string> VB = new List<string>();
public List<string> VC = new List<string>();
public List<string> S = new List<string>();
public List<string> S12 = new List<string>();
public List<string> S14 = new List<string>();
public List<string> S18 = new List<string>();
public List<string> S40 = new List<string>();
public Form1()
{
InitializeComponent();
}
public void ReadFile()
{
}
private void Form1_Load(object sender, EventArgs e)
{
SORTER();
Dostuff();
}
public void Dostuff()
{
if (!System.IO.File.Exists(#"C:\Users\Oscar\Desktop\result.txt"))
return;
dataGridView1.ColumnCount = 3;
dataGridView1.Columns[0].HeaderCell.Value = "Number";
dataGridView1.Columns[1].HeaderCell.Value = "name";
dataGridView1.Columns[2].HeaderCell.Value = "time";
using (sr)
while (sr.Peek() > -1 )
{
dataGridView1.Rows.Add(sr.ReadLine().Split(';'));
}
}
public void SORTER()
{
using (sr)
while ((line = sr.ReadLine()) != null)
{
if (line.Contains(";V;")) { V.Add(line); }
else if (line.Contains(";V12;")) { V12.Add(line); }
else if (line.Contains(";V14;")) { V14.Add(line); }
else if (line.Contains(";V18;")) { V18.Add(line); }
else if (line.Contains(";V50;")) { V50.Add(line); }
else if (line.Contains(";S12;")) { S12.Add(line); }
else if (line.Contains(";S14;")) { S14.Add(line); }
else if (line.Contains(";S18;")) { S18.Add(line); }
else if (line.Contains(";S40;")) { S40.Add(line); }
if (line.Contains(";S;")) { S.Add(line); }
}
listBox1.DataSource = V12;
listBox2.DataSource = V50;
listBox3.DataSource = V;
listBox4.DataSource = S;
listBox5.DataSource = S18;
listBox6.DataSource = S40;
}
}
Don't do this inside your Form definition:
public StreamReader sr = new StreamReader(#"C:\Users\Oscar\Desktop\result.txt");
That opens your file once when your form is created and leaves it open. Doing that can block or crash your application if you get an IO error, and often causes “file is in use” errors. You should open a file only when you actually need to use it, then close it immediately after. Wherever you do:
using (sr)
you should instead be doing this:
using (var sr = new StreamReader(#"C:\Users\Oscar\Desktop\result.txt"))
Actually, you should be doing this:
using (var sr = new StreamReader(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "result.txt")))
You have your StreamReader in a using block in your SORTER() method. This means after that block ends, the StreamReader will be disposed. When you try to reuse the same StreamReader in your DoStuff() method, you will get the error you described, as that StreamReader is no longer in a state to be read from.
Instead of having a global variable for your StreamReader, you could create these as local variables in each of your methods.
Some classes to start, I'm writing them all so you can reproduce my problem:
public class PermissionObject
{
public string permissionName;
public string permissionObject;
public bool permissionGranted;
public PermissionObject()
{
permissionName = "";
permissionObject = "";
permissionGranted = true;
}
public PermissionObject(string name, string obj, bool granted)
{
permissionName = name;
permissionObject = obj;
permissionGranted = granted;
}
}
public class Config
{
public string cmsDataPath = "";
public string cmsIP = "";
public List<UserClass> usersCMS = new List<UserClass>();
static public string pathToConfig = #"E:\testconpcms.xml";
public string cardServerAddress = "";
public void Save()
{
XmlSerializer serializer = new XmlSerializer(typeof(Config));
using (Stream fileStream = new FileStream(pathToConfig, FileMode.Create))
{
serializer.Serialize(fileStream, this);
}
}
public static Config Load()
{
if (File.Exists(pathToConfig))
{
XmlSerializer serializer = new XmlSerializer(typeof(Config));
try
{
using (Stream fileStream = new FileStream(pathToConfig, FileMode.Open))
{
return (Config)serializer.Deserialize(fileStream);
}
}
catch (Exception ex)
{
return new Config();
}
}
else
{
return null;
}
}
}
public class UserClass
{
public string Name;
public string Login;
public string Password;
public PCMS2 PermissionsList; // OR new PCMS1, as I will explain in a bit
public UserClass()
{
this.Name = "Admin";
this.Login = "61-64-6D-69-6E";
this.Password = "61-64-6D-69-6E";
this.PermissionsList = new PCMS2(); // OR new PCMS1, as I will explain in a bit
}
}
The problematic bit: consider two implementations of PCMS class, PCMS1 and PCMS2:
public class PCMS1
{
public PermissionObject p1, p2;
public PCMS1()
{
p1 = new PermissionObject("ImportConfigCMS", "tsmiImportCMSConfigFile", true);
p2 = new PermissionObject("ExportConfigCMS", "tsmiExportCMSConfigFile", true);
}
}
public class PCMS2
{
public List<PermissionObject> listOfPermissions = new List<PermissionObject>();
public PCMS2()
{
listOfPermissions.Add(new PermissionObject("ImportConfigCMS", "tsmiImportCMSConfigFile", true));
listOfPermissions.Add(new PermissionObject("ExportConfigCMS", "tsmiExportCMSConfigFile", true));
}
}
And finally main class:
public partial class Form1 : Form
{
private Config Con;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Con = Config.Load();
if (Con == null)
{
Con = new Config();
Con.cmsDataPath = #"E:\testconpcms.xml";
Con.Save();
}
if (Con.usersCMS.Count == 0)
{
UserClass adminDefault = new UserClass();
Con.usersCMS.Add(adminDefault);
Con.Save();
}
}
}
Now, using either PCMS1 or PCMS2, the config file generates properly - one user with 2 permissions.
However, when config file is present, calling Con = Config.Load() in the main class gives different results.
Using PCMS1, the Con object is as expected - 1 user with 2 permissions.
However, using PCMS2, the Con object is 1 user with 4 (four) permissions. It doubles that field (it's basically p1, p2, p1, p2). Put a BP to see Con after Load().
I guess the list (PCMS2) implementation is doing something wonky during load which I'm not aware of, but I can't seem to find the issue.
You creates your permission objects in constructor of PMCS2 you do it in the constructor of PMCS1 too, but there you do have two properties that will be overwritten by serializer.
In case of of PMCS2 your constructor adds two items to List and than serializer adds the items it has deserilized to the same list.
I don't know exactly your usecase but i would suggest to move init of the permissions to separated method:
public class PCMS1
{
public PermissionObject p1, p2;
public void Init()
{
p1 = new PermissionObject("ImportConfigCMS", "tsmiImportCMSConfigFile", true);
p2 = new PermissionObject("ExportConfigCMS", "tsmiExportCMSConfigFile", true);
}
}
public class PCMS2
{
public List<PermissionObject> listOfPermissions = new List<PermissionObject>();
public void Init()
{
listOfPermissions.Add(new PermissionObject("ImportConfigCMS", "tsmiImportCMSConfigFile", true));
listOfPermissions.Add(new PermissionObject("ExportConfigCMS", "tsmiExportCMSConfigFile", true));
}
}
after that you could call it, if you want to get initial settings:
if (Con.usersCMS.Count == 0)
{
UserClass adminDefault = new UserClass();
adminDefault.PermissionsList.Init();
Con.usersCMS.Add(adminDefault);
Con.Save();
}
I'm sure its very straightforward but I am struggling to figure out how to write an array to file using CSVHelper.
I have a class for example
public class Test
{
public Test()
{
data = new float[]{0,1,2,3,4};
}
public float[] data{get;set;}
}
i would like the data to be written with each array value in a separate cell. I have a custom converter below which is instead providing one cell with all the values in it.
What am I doing wrong?
public class DataArrayConverter<T> : ITypeConverter
{
public string ConvertToString(TypeConverterOptions options, object value)
{
var data = (T[])value;
var s = string.Join(",", data);
}
public object ConvertFromString(TypeConverterOptions options, string text)
{
throw new NotImplementedException();
}
public bool CanConvertFrom(Type type)
{
return type == typeof(string);
}
public bool CanConvertTo(Type type)
{
return type == typeof(string);
}
}
To further detail the answer from Josh Close, here what you need to do to write any IEnumerable (including arrays and generic lists) in a recent version (anything above 3.0) of CsvHelper!
Here the class under test:
public class Test
{
public int[] Data { get; set; }
public Test()
{
Data = new int[] { 0, 1, 2, 3, 4 };
}
}
And a method to show how this can be saved:
static void Main()
{
using (var writer = new StreamWriter("db.csv"))
using (var csv = new CsvWriter(writer))
{
var list = new List<Test>
{
new Test()
};
csv.Configuration.HasHeaderRecord = false;
csv.WriteRecords(list);
writer.Flush();
}
}
The important configuration here is csv.Configuration.HasHeaderRecord = false;. Only with this configuration you will be able to see the data in the csv file.
Further details can be found in the related unit test cases from CsvHelper.
In case you are looking for a solution to store properties of type IEnumerable with different amounts of elements, the following example might be of any help:
using CsvHelper;
using CsvHelper.Configuration;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace CsvHelperSpike
{
class Program
{
static void Main(string[] args)
{
using (var writer = new StreamWriter("db.csv"))
using (var csv = new CsvWriter(writer))
{
csv.Configuration.Delimiter = ";";
var list = new List<AnotherTest>
{
new AnotherTest("Before String") { Tags = new List<string> { "One", "Two", "Three" }, After="After String" },
new AnotherTest("This is still before") {After="after again", Tags=new List<string>{ "Six", "seven","eight", "nine"} }
};
csv.Configuration.RegisterClassMap<TestIndexMap>();
csv.WriteRecords(list);
writer.Flush();
}
using(var reader = new StreamReader("db.csv"))
using(var csv = new CsvReader(reader))
{
csv.Configuration.IncludePrivateMembers = true;
csv.Configuration.RegisterClassMap<TestIndexMap>();
var result = csv.GetRecords<AnotherTest>().ToList();
}
}
private class AnotherTest
{
public string Before { get; private set; }
public string After { get; set; }
public List<string> Tags { get; set; }
public AnotherTest() { }
public AnotherTest(string before)
{
this.Before = before;
}
}
private sealed class TestIndexMap : ClassMap<AnotherTest>
{
public TestIndexMap()
{
Map(m => m.Before).Index(0);
Map(m => m.After).Index(1);
Map(m => m.Tags).Index(2);
}
}
}
}
By using the ClassMap it is possible to enable HasHeaderRecord (the default) again. It is important to note here, that this solution will only work, if the collection with different amounts of elements is the last property. Otherwise the collection needs to have a fixed amount of elements and the ClassMap needs to be adapted accordingly.
This example also shows how to handle properties with a private set. For this to work it is important to use the csv.Configuration.IncludePrivateMembers = true; configuration and have a default constructor on your class.
Unfortunately, it doesn't work like that. Since you are returning , in the converter, it will quote the field, as that is a part of a single field.
Currently the only way to accomplish what you want is to write manually, which isn't too horrible.
foreach( var test in list )
{
foreach( var item in test.Data )
{
csvWriter.WriteField( item );
}
csvWriter.NextRecord();
}
Update
Version 3 has support for reading and writing IEnumerable properties.
I have a class which implements IDisposable like such
public class SomeClass : IDisposable
{
private IList<string> _someList = new List<string>();
public IList<string> SomeList
{
get { return _someList; }
}
public void Dispose()
{
_someList = null;
}
}
and a method that uses 'using' blocks to create two instances of this class and add it to a collection like such
private static void Main(string[] args)
{
IList<SomeClass> classes = new List<SomeClass>();
using (var sc = new SomeClass())
{
sc.SomeList.Add("a");
classes.Add(sc);
}
using (var sc = new SomeClass())
{
sc.SomeList.Add("b");
classes.Add(sc);
}
foreach (var a in classes)
{
Console.WriteLine(a.SomeList[0]);
}
Console.ReadLine();
}
Whats happening here is by the time i come to the foreach to iterate through my elements in "classes" the "SomeList" property of both my objects are null.
I do understand that since each using executes Dispose() and in the dispose, i AM null-ing the two lists, they will be null.
My question is how do i achieve this without having to stop null-ing "SomeList" inside my dispose().
thanks
--UPDATE 1
Something closer to real code
base.OnPreRender(e);
Page page = HttpContext.Current.CurrentHandler as Page;
HtmlHead head = (HtmlHead)page.Header;
if (Settings.AreSet)
{
using (var noIndex = new HtmlMeta())
{
noIndex.Name = "somename";
noIndex.Content = "somecontent";
head.Controls.AddAt(0, noIndex);
}
}
using (var machineName = new HtmlMeta())
{
machineName.Name = "somename2";
machineName.Content = "somecontent2";
head.Controls.AddAt(1, machineName);
}
UpdateHeader(head);
--UPDATE #2
A variant of the above class with a string property now, as opposed to a a list.
public class SomeClass2 : IDisposable
{
public string SomeString { get; set; }
public void Dispose()
{
SomeString = string.Empty;
}
}
It still does the same thing for the following method. My 'classes' contains lists of 'SomeClass2' which has 'SomeString' as empty (what i set in my dispose())
IList<SomeClass2> classes = new List<SomeClass2>();
using (var sc = new SomeClass2())
{
sc.SomeString = "a";
classes.Add(sc);
}
using (var sc = new SomeClass2())
{
sc.SomeString = "a";
classes.Add(sc);
}
foreach (var a in classes)
{
Console.WriteLine(a.SomeString);
}
Console.ReadLine();
You don't want Dispose to be called? Just don't use using statement.
Or move the entire logic inside using:
private static void Main(string[] args)
{
IList<SomeClass> classes = new List<SomeClass>();
using (var sc = new SomeClass())
{
sc.SomeList.Add("a");
classes.Add(sc);
using (var sc = new SomeClass())
{
sc.SomeList.Add("b");
classes.Add(sc);
foreach (var a in classes)
{
Console.WriteLine(a.SomeList[0]);
}
}
}
Console.ReadLine();
}
Btw: looks like using IDisposable in that case is pointless. GC would gather unused List<T> objects anyway.