Related
I've been trying to figure this out for the past few days, but I just can't seem to get it work.
So I have a txt file which has this format:
id;könyvcím;szerző;kiadó;kiadási év;
I am using a structs and a list such as this:
public static List<Books> BooksList = new List<Books>();
public struct Books
{
public int id;
public string title;
public string writer;
public string publisher;
public int published_year;
}
And I'm also putting all these into a List based on the struct like this:
StreamReader booksRead = new StreamReader("konyvek.txt", Encoding.UTF8);
booksRead.ReadLine();
while (!booksRead.EndOfStream)
{
string[] split = booksRead.ReadLine().Split(';');
Books inRead = new Books();
inRead.id = Convert.ToInt32(split[0]);
inRead.title = split[1];
inRead.writer = split[2];
inRead.publisher = split[3];
inRead.published_year = Convert.ToInt32(split[4]);
BooksList.Add(inRead);
}
booksRead.Close();
All I want is, for example, to find where the line with ID 2 is, and remove that line from my textfile. I've tried to get the index of the line I want, and remove it like that from my textfile, but it even fails to get the index, I tried using IndexOf, FindIndex and trying to go on a loop. I'm pretty sure my struct is not happy with me for using it like that because I get errors such as this when I run my code:
System.InvalidCastException: 'Unable to cast object of type 'Books' to
type 'System.IConvertible'.'
Here is the way I'm trying to get the index of the line I want to remove
Books item = new Books();
for (int i = 0; i < BooksList.Count; i++)
{
if (Convert.ToInt32(textBox_id_delete.Text) == item.id)
{
RemoveAt = item.id;
}
}
int index = BooksList.FindIndex(x => Convert.ToInt32(x) == RemoveAt);
MessageBox.Show(Convert.ToString(index));
I'm pretty sure I'm approaching this extremely wrong, and I'd accept any kind of help.
You are doing it completely wrong for a number of reasons.
First, how would you do that the way you are doing:
void Main()
{
var filename = #"c:\myFolder\mybooklist.txt";
// read into an enumerable
var books = File.ReadAllLines(filename)
.Select(x => x.Split(';'))
.Select(x => new Book {
Id = int.TryParse(x[0], out int bookId)?bookId:0,
Title = x[1],
Writer = x[2],
Publisher = x[3],
Published_year=int.TryParse(x[4], out int year)?year:0
});
// remove the one with id 2
// and save back
var otherBooks = books.Where(b => b.Id != 2);
File.WriteAllLines(filename, otherBooks.Select(b => $"{b.Id};{b.Title};{b.Writer};{b.Publisher};{b.Published_year}"));
}
public struct Book
{
public int Id;
public string Title;
public string Writer;
public string Publisher;
public int Published_year;
}
And now what is wrong with this.
A text file is not a database but you are trying to use a text file as a database.
With a text file, you are not actually doing any control here, if the ID is unique or not (there might be N books with the ID 2).
(Side matter) You are using C#, but looks like you are coming from another language and not using the naming conventions at all.
IMHO, instead you should simply use a database, an embedded one for example like LiteDb or Sqlite. If you care to see a sample with LiteDb or Sqlite, let me know.
EDIT: I am adding SQLite and LiteDb samples. In either case, you would need to add Sqlite.Data.Sqlite and LiteDB respectively from Nuget and add using statements.
In case of SQLite, please note that you could use Linq adding some drivers. I directly used the ADO.Net commands and didn't use a Book class for mapping.
LiteDB, being a NoSQL database written in C# for C#, can directly use objects and support Linq out of the box.
Samples show only the surface for both.
SQLite sample:
private static readonly string dataFile = #"d:\temp\books.s3db";
void Main()
{
CreateDb(dataFile);
SeedSampleData(dataFile);
// List the current data
Console.WriteLine("Current Data");
Console.WriteLine("".PadRight(100, '='));
ListData(dataFile);
Console.WriteLine("".PadRight(100, '='));
DeleteSampleRow(dataFile);
// List the current data
Console.WriteLine("After deleting");
Console.WriteLine("".PadRight(100, '='));
ListData(dataFile);
Console.WriteLine("".PadRight(100, '='));
}
void DeleteSampleRow(string dbName)
{
string deleteById = "delete from books where id = #id";
string deleteByTitle = "delete from books where Title = #title";
string deleteByWriter = "delete from books where Writer = #writer";
using (SQLiteConnection cn = new SQLiteConnection($"Data Source={dbName}"))
using (SQLiteCommand cmdById = new SQLiteCommand(deleteById, cn))
using (SQLiteCommand cmdByTitle = new SQLiteCommand(deleteByTitle, cn))
using (SQLiteCommand cmdByWriter = new SQLiteCommand(deleteByWriter, cn))
{
cmdById.Parameters.Add("#id", DbType.Int32).Value = 2; // delete the book with id = 2
cmdByTitle.Parameters.Add("#title", DbType.String).Value = $"Sample Title #5"; // delete all books having title "Sample Title #5"
cmdByWriter.Parameters.Add("#writer", DbType.String).Value = $"Sample Writer #3"; // delete all books written by "Sample Writer #3"
cn.Open();
cmdById.ExecuteNonQuery();
cmdByTitle.ExecuteNonQuery();
cmdByWriter.ExecuteNonQuery();
cn.Close();
}
}
void ListData(string dbName)
{
string selectCommand = "select * from books";
using (SQLiteConnection cn = new SQLiteConnection($"Data Source={dbName}"))
using (SQLiteCommand cmd = new SQLiteCommand(selectCommand, cn))
{
cn.Open();
var r = cmd.ExecuteReader();
while (r.Read())
{
Console.WriteLine($"{r["id"]},{r["title"]},{r["writer"]},{r["publisher"]},{r["published_year"]}");
}
cn.Close();
}
}
private void CreateDb(string dbName)
{
if (File.Exists(dbName)) // if it exists, delete and create afresh, just for sampling
{
File.Delete(dbName);
}
string createTable = #"Create Table books (
id int primary key not null,
title varchar(500) not null,
writer varchar(100) not null,
publisher varchar(100) not null,
published_year int not null
)";
using (SQLiteConnection cn = new SQLiteConnection($"Data Source={dbName}"))
using (SQLiteCommand cmd = new SQLiteCommand(createTable, cn))
{
cn.Open();
cmd.ExecuteNonQuery();
cn.Close();
}
}
private void SeedSampleData(string dbName)
{
string insertCommand = #"insert into books
(id, title, writer, publisher, published_year)
values
(#id, #title, #writer, #publisher, #year);";
using (SQLiteConnection cn = new SQLiteConnection($"Data Source={dbName}"))
using (SQLiteCommand cmd = new SQLiteCommand(insertCommand, cn))
{
cmd.Parameters.Add("#id", DbType.Int32);
cmd.Parameters.Add("#title", DbType.String);
cmd.Parameters.Add("#writer", DbType.String);
cmd.Parameters.Add("#publisher", DbType.String);
cmd.Parameters.Add("#year", DbType.Int32);
Random r = new Random();
cn.Open();
int id = 1;
using (SQLiteTransaction transaction = cn.BeginTransaction())
{
cmd.Parameters["#id"].Value = id++;
cmd.Parameters["#title"].Value = $"Around the World in Eighty Days";
cmd.Parameters["#writer"].Value = $"Jules Verne";
cmd.Parameters["#publisher"].Value = $"Le Temps, Pierre-Jules Hetzel";
cmd.Parameters["#year"].Value = 1873;
cmd.ExecuteNonQuery();
cmd.Parameters["#id"].Value = id++;
cmd.Parameters["#title"].Value = $"A Tale of Two Cities";
cmd.Parameters["#writer"].Value = $"Charles Dickens";
cmd.Parameters["#publisher"].Value = $"Chapman & Hall";
cmd.Parameters["#year"].Value = 1859;
cmd.ExecuteNonQuery();
// add dummy 10 more rows
for (int i = 0; i < 10; i++)
{
cmd.Parameters["#id"].Value = id++;
cmd.Parameters["#title"].Value = $"Sample Title #{i}";
cmd.Parameters["#writer"].Value = $"Sample Writer #{r.Next(1, 5)}";
cmd.Parameters["#publisher"].Value = $"Sample Publisher #{i}";
cmd.Parameters["#year"].Value = r.Next(1980, 2022);
cmd.ExecuteNonQuery();
}
transaction.Commit();
}
// databases generally use some indexes
new SQLiteCommand(#"Create Index if not exists ixId on books (id);", cn).ExecuteNonQuery();
new SQLiteCommand(#"Create Index if not exists ixTitle on books (title);", cn).ExecuteNonQuery();
new SQLiteCommand(#"Create Index if not exists ixWriter on books (writer);", cn).ExecuteNonQuery();
new SQLiteCommand(#"Create Index if not exists ixPublisher on books (publisher);", cn).ExecuteNonQuery();
cn.Close();
}
}
LiteDb sample:
private static readonly string dataFile = #"d:\temp\books.litedb";
void Main()
{
//CreateDb(dataFile); // this step is not needed with LiteDB
// instead we just simply delete the datafile if it exists
// for starting afresh
// if it exists, delete and create afresh, just for sampling
// so you can run this same sample over and over if you wish
if (File.Exists(dataFile))
{
File.Delete(dataFile);
}
SeedSampleData(dataFile);
// List the current data
Console.WriteLine("Current Data");
Console.WriteLine("".PadRight(100, '='));
ListData(dataFile);
Console.WriteLine("".PadRight(100, '='));
DeleteSampleRow(dataFile);
// List the current data
Console.WriteLine("After deleting");
Console.WriteLine("".PadRight(100, '='));
ListData(dataFile);
Console.WriteLine("".PadRight(100, '='));
}
void DeleteSampleRow(string dbName)
{
using (var db = new LiteDatabase(dbName))
{
var bookCollection = db.GetCollection<Book>("Books");
// by ID
bookCollection.Delete(2);
// by Title
bookCollection.DeleteMany(c => c.Title == "Sample Title #5");
// by Writer
bookCollection.DeleteMany(c => c.Writer == "Sample Writer #3");
}
}
void ListData(string dbName)
{
using (var db = new LiteDatabase(dbName))
{
var bookCollection = db.GetCollection<Book>("Books");
foreach (var book in bookCollection.FindAll())
{
Console.WriteLine($"{book.Id},{book.Title},{book.Writer},{book.Publisher},{book.Published_year}");
}
}
}
private void SeedSampleData(string dbName)
{
Random r = new Random();
var books = new List<Book> {
new Book {Title="Around the World in Eighty Days",Writer = "Jules Verne",Publisher = "Le Temps, Pierre-Jules Hetzel",Published_year= 1873},
new Book {Title="A Tale of Two Cities",Writer = "Charles Dickens",Publisher = "Chapman & Hall",Published_year= 1859},
};
// add dummy 10 more rows
books.AddRange(Enumerable.Range(0, 10).Select(i => new Book
{
Title = $"Sample Title #{i}",
Writer = $"Sample Writer #{r.Next(1, 5)}",
Publisher = $"Sample Publisher #{i}",
Published_year = r.Next(1980, 2022)
}));
using (var db = new LiteDatabase(dbName))
{
var bookCollection = db.GetCollection<Book>("Books");
bookCollection.InsertBulk(books);
// databases generally use some indexes
// create the same indexes that we created in SQLite sample
bookCollection.EnsureIndex(c => c.Id);
bookCollection.EnsureIndex(c => c.Title);
bookCollection.EnsureIndex(c => c.Writer);
bookCollection.EnsureIndex(c => c.Publisher);
}
}
public class Book
{
public int Id {get;set;}
public string Title {get;set;}
public string Writer {get;set;}
public string Publisher {get;set;}
public int Published_year {get;set;}
}
welcome to SO. I'm going to assume you've got a reason for keeping the data in a text file. As several answers have suggested if you need it in a text file the easiest thing to do is to simply create a new file with the lines you want.
One way to do that is to make use of a interator function to filter the lines. This lets you easily use the .NET File class to do the rest - creating the new file and removing the old if you want to. Often keeping the old file and archiving it can be useful too but anyway, here's a way to filter the lines.
static void Main(string[] _)
{
var filteredLines = FilterOnID(File.ReadAllLines("datafile.txt"), "2");
File.WriteAllLines("updated.datafile.txt", filteredLines);
// rename if necessary
File.Delete("datafile.txt");
File.Move("updated.datafile.txt", "datafile.txt");
}
static IEnumerable<string> FilterOnID(IEnumerable<string> lines, string id)
{
foreach (var line in lines)
{
var fields = line.Split(';');
if (fields.Length != 0 || !string.IsNullOrEmpty(fields[0]))
{
if (id == fields[0])
continue;
}
yield return line;
}
}
To test I added simple file like so:
1;field1;field2;field3
2;field1;field2;field3
3;field1;field2;field3
4;field1;field2;field3
5;field1;field2;field3
6;field1;field2;field3
And after running you get this:
1;field1;field2;field3
3;field1;field2;field3
4;field1;field2;field3
5;field1;field2;field3
6;field1;field2;field3
When you put books into a list from a file, you can search the book for remove from BooksList.
Delete it and save BooksList into a file.
var removeBook = BookList.FirstOrDefault(book => book.id == removeId);
if (removeBook != null)
{
BookList.Remove(removeBook);
}
var booksAsString = BookList.Select(book => $"{book.id};{book.title};{book.writer};{book.publisher};{book.published_year}");
File.WriteAllLines("konyvek.txt", booksAsString, Encoding.UTF8);
I'm trying to write a simple helper function that connects to Oracle database using Oracle.ManagedDataAccess.Core library, and returns the rows.
Returned object should survive when connection to database is closed. Something similar to the following. Not sure what the will be.
public <sometype> GetOracleResults(string connectionString, string cmdText, List<OracleParameter> oracleParameters)
{
<sometype> results = null;
try
{
using (OracleConnection oracleConnection = new OracleConnection(connectionString))
{
oracleConnection.Open();
using (OracleCommand oracleCommand = new OracleCommand(cmdText, oracleConnection))
{
foreach (var param in oracleParameters)
{
oracleCommand.Parameters.Add(param);
}
OracleDataReader oracleDataReader = oracleCommand.ExecuteReader();
if(oracleDataReader.HasRows)
{
results = new <sometype>();
while (oracleDataReader.Read())
{
//loop through the reader and add results
return results;
}
}
}
}
}
catch (Exception)
{
//todo
throw;
}
}
Does it have to be strongly typed? If not, a coworker showed me sticking the data in a List of Dictionary items, E.G.:
var columns = new List<string>();
for (int i = 0; i < myReader.FieldCount; i++)
{
columns.Add(myReader.GetName(i).Trim());
}
while (myReader.Read() && results.Count < 200)
{
Dictionary<string, string> row = new Dictionary<string, string>();
foreach (var column in columns)
{
row.Add(column, Convert.ToString(myReader[column], CultureInfo.InvariantCulture).Trim());
}
results.Add(row);
}
I suppose if you know the schema so that the generic type coming into the method had property names matching the column names, then you could use reflection to match up the column results with the C# object.
I am having difficulty with the following method:
public override List<Team> Search(Dictionary<string, string> prms,
int pageSize, int page, out int results)
{
List<Team> t = null;
//Team t = null;
var tresults = new List<Team>();
using (SqlConnection conn = DB.GetSqlConnection())
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = #"SearchForTeam";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
foreach (var key in prms.Keys)
{
cmd.Parameters.Add(key, prms[key]);
}
SqlDataReader reader
= cmd.ExecuteReader(CommandBehavior.CloseConnection);
while (reader.Read())
{
var temp = Load(reader);
if (t == null)
{
t = temp;
}
else
{
t.CityHistory.Add(temp.CityHistory[0]);
}
}
}
}
results = 0;
return t;
}
The error lies mainly with the if and else statement where the temp in the if block is claiming that it "cannot implicitly convert type DataLayer.Team to System.Collections.GenericList"
EDIT:
Here is my load method:
public Team Load(SqlDataReader reader)
{
var team = new Team()
{
TeamID = Int32.Parse(reader["TeamID"].ToString()),
TeamName = reader["TeamName"].ToString()
};
team.CityHistory.Add(
new TeamCity(
Int32.Parse(reader["TeamCitiesID"].ToString()),
team.TeamID,
Int32.Parse(reader["CityID"].ToString()),
reader["CityName"].ToString(),
Int32.Parse(reader["YearStart"].ToString()),
Int32.Parse(reader["YearEnd"].ToString())
)
);
return team;
}
t is defined as List<Team>, yet you later say t.CityHistory. CityHistory is clearly not a property of List<>. I'd guess it's a property of Team, but since you never show us that we can't say.
Show us the definition of Team and the method signature of Load() and perhaps we can give an answer.
UPDATE (from OP's update)
Now, I'm going to assume that you are getting multiple rows, one for each City, with the team info repeating. So, which you want is:
var temp = Load(reader);
// remove t definition above
var t = tresults.FirstOrDefault(team=> team.TeamId == temp.TeamId);
if (t == null)
{
t = temp;
tresults.Add(t);
}
else
t.CityHistory.Add(temp.CityHistory[0]);
(Updated again, based on Steve's comment)
You have to wrap temp into a List first to assign it to t:
if (t == null) {
t = new List<Team>() { temp }
} else {
t.add(temp);
}
VERY LONG COMMENT:
Consider the following refactoring of your method:
private IEnumerable<Team> LoadTeams()
{
using (SqlConnection conn = DB.GetSqlConnection())
{
using (SqlCommand cmd = conn.CreateCommand())
{
cmd.CommandText = #"SearchForTeam";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
foreach (var key in prms.Keys)
{
cmd.Parameters.Add(key, prms[key]);
}
SqlDataReader reader
= cmd.ExecuteReader(CommandBehavior.CloseConnection);
while (reader.Read())
{
yield return Load(reader);
}
}
}
}
public override List<Team> Search(Dictionary<string, string> prms,
int pageSize, int page, out int results)
{
List<Team> searchResult = new List<Team>;
//Team t = null;
var tresults = new List<Team>();
foreach(Team team in LoadTeams())
{
if (team .....)
searchResult.Add(team);
}
results = 0;
return searchResult;
}
Take into account this NEEDED initialization:
List<Team> searchResult = new List<Team>;
And ask yourself: What should the following excerpt do? :
if (team .....)
searchResult.Add(team);
P.S.: Also the line
results = 0;
should probably look like:
results = searchResult.Count;
I'm having to guess a little here, but I assume that your Load() method is returning a data type 'DataLayer.Team', which sounds like its one Team, and you're trying to assign it to 't', which is a list of teams.
Try:
t.Add(temp)
or
t.Add(temp as Team).
It doesn't help that you're using 'var' declarations all the time.
i am new to class/object.I am getting this error. I don't know why this is not navigating to the desired page. There is my code. Thanks for helping~~
WebService1.asmx.cs
public class yearList
{
public yearList(){}
public int year;
public List<int> list;
public List<int> List()
{
for (int i = -2; i < 3; i++)
{
list.Add(year+i);
}
return list;
}
}
[WebMethod]
public List<int> List()
{
yearList yl = new yearList();
string connStr2 = ConfigurationManager.ConnectionStrings["taisangrent_sql"].ConnectionString;
SqlConnection conn2 = new SqlConnection(connStr2);
string strSQL2 = "select YEAR(getdate()) as year";
SqlDataAdapter adapter2 = new SqlDataAdapter(strSQL2, conn2);
DataSet ds2 = new DataSet();
adapter2.Fill(ds2, "y_m");
yl.year = int.Parse(ds2.Tables["y_m"].Rows[0]["year"].ToString());
return yl.List();
}
try changing..
public List<int> list;
to
public List<int> list = new List<int>();
When you're calling list.Add(year);, the list object has now yet been newed up, so that's why you're getting the error.
I think it would be better to change the method name too...
public List<int> GetList() instead of public List<int> List();
EDIT: Good point by #Hassan
just to clarify, the DataAdapter.Fill() will Open/Close connections automatically.
One of the great features of ADO.NET is that the DataAdapter object's
Fill and Update methods can open and close a connection automatically.
The advantage of this is that it is not necessary to open the
connection explicitly because the DataAdapter opens it for you at the
moment right before it executes its SQL command against the database
and then closes it right afterwards.
When declaring as
public List<int> list;
your object is just creating, it wont assign the memory to it. So just decalre it like
public List<int> list = new List<int>();
And also try to give some other name to your function if its not a constructor as it already a class name.
I'm trying to format XML from a MySQL query to emulate what a client frontend is expecting for input. I have no control over what the client requires, so I have to match what I've gotten from Wireshark captures. I am not married to the idea of adding columns to the dataset to do this, and I can probably just do a search and replace for the additions to the XML, however, I have a large number of very similar, yet different queries & outputs to write, and I'd prefer to do something that scales well. Unfortunately it'll be throw away code because when I write the new front end client for this, we won't be tracking a lot of the data the current legacy system does like client IP address, or the supposedly unique "ActionID" both of which you'll see referenced below, nor will I have to do anything with XML, it'll all be MySQL driven queries.
My output should be in a form like this:
<PCBDatabaseReply>
<SearchResult>
<SBE_PCB_Data PCBID="53">
<Termination ActionID="97DF" User="UName:192.168.255.255" Date="2012-09-26T13:15:51" PCBID="53">
<Reason>Other</Reason>
</Termination>
</SBE_PCB_Data>
</SearchResult>
</PCBDatabaseReply>
The results from my query look like this:
EventType User Date PCBID Reason
Termination UName 2012-09-26T13:15:51 53 Other
My output XML currently looks like this:
<PCBDatabaseReply>
<Termination User="UName" Date="2012-09-26T13:15:51" PCBID="53">
<EventType>Termination</EventType>
<Reason>Other</Reason>
</Termination>
</PCBDatabaseReply>
Using this code:
string mysqlConnection = "server=server;\ndatabase=database;\npassword=password;\nUser ID=user;";
MySqlConnection connection = new MySqlConnection(mysqlConnection);
connection.Open();
string command = "SELECT eventtypes.EventType, events.User, DATE_FORMAT(events.DateTime,'%Y-%m-%dT%T') AS Date, pcbid.PCBID, getReasons.ItemValue AS Reason " +
"FROM events " +
"INNER JOIN pcbid ON events.PCBID = pcbid.PCBID " +
"INNER JOIN eventtypes " +
"ON events.EventType_ID = eventtypes.EventType_ID " +
"LEFT JOIN getReasons " +
"ON getReasons.Event_ID = events.Event_ID " +
"WHERE eventtypes.EventType = 'termination'";
//create fake "ActionID"
var random = new Random();
string ActionID = String.Format("{0}\"{1:X4}\"", "ActionID=", random.Next(0xffff));
MySqlDataAdapter adapter = new MySqlDataAdapter(command, connection);
DataSet dataSet = new DataSet();
adapter.Fill(dataSet);
//change upper level node name to what's expected in client-speak
dataSet.DataSetName = "PCBDatabaseReply";
//change first child node name to client-speak eventType
dataSet.Tables[0].TableName = dataSet.Tables[0].Rows[0][0].ToString();
StringWriter writer = new StringWriter();
var ds1 = dataSet.Tables[0];
DataColumn dcEventType = ds1.Columns[0];
DataColumn dcUser = ds1.Columns[1];
DataColumn dcDate = ds1.Columns[2];
DataColumn dcPCBID = ds1.Columns[3];
dcEventType.ColumnMapping = MappingType.Element;
dcUser.ColumnMapping = MappingType.Attribute;
dcDate.ColumnMapping = MappingType.Attribute;
dcPCBID.ColumnMapping = MappingType.Attribute;
dataSet.Tables[0].WriteXml(writer, true);
Console.WriteLine(writer.ToString());
I need to inject several things
At the top beneath <PCBDatabaseReply>:
<SearchResult>
<SBE_PCB_Data PCBID="53">
In the Termination tag: (from the fake ActionID in the code)
ActionID="0xnnnn" & append ":192.168.255.255" to the end of the user name
And then close with the appropriate tags:
</SBE_PCB_Data>
</SearchResult>
I have tried adding a dummy column for the "SBE_PCB_Data" tag, which didn't work.
DataColumn dcSBE_PCB_Data = new DataColumn("SBE_PCB_Data", System.Type.GetType("System.String"), "SBE_PCB_Data", MappingType.Element);
dcSBE_PCB_Data.DefaultValue = "SBE_PCB_Data";
//add to the dataset
dataSet.Tables[0].Columns.Add(dcSBE_PCB_Data);
//move it to the zeroth position
dcSBE_PCB_Data.SetOrdinal(0);
This just makes it show up as:
<SBE_PCB_Data>SBE_PCB_Data</SBE_PCB_Data>
I need it to wrap around the rest of the XML as an ancestor node.
How best to inject the XML I need into the results?
EDIT: refactored according to excellent example below
**EDIT: updated with final code
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Xml.Linq;
using MySql.Data.MySqlClient;
namespace TerminationResults
{
public class SearchResult
{
//all possible event detail tags (test items are excluded)
public string EventType { get; set; }
public string User { get; set; }
public string Date { get; set; }
public string PCBID { get; set; }
public string EAReason { get; set; }
public string ETReason { get; set; }
public string Notes { get; set; }
public string Reason { get; set; }
public string SBEJobNumber { get; set; }
public string SBEModelNumber { get; set; }
public string SBEPN { get; set; }
public string SBESerialNumber { get; set; }
//create fake IP address since we no longer track it
public string UserAndIP
{
get { return String.Format("{0}:192.168.255.255", User); }
set {}
}
//create fake actionID since the originals weren't inserted into the database because they weren't unique.
public string ActionId
{
get { return String.Format("{0:X4}", new Random().Next(0xffff)); }
set {}
}
}
internal class Program
{
private static void Main(string[] args)
{
var searchResults = GetSearchResults();
var xml = TransformList(searchResults);
Console.WriteLine(xml);
Console.ReadLine();
}
public static IEnumerable<SearchResult> GetSearchResults()
{
List<SearchResult> searchResults = new List<SearchResult>();
try
{
const string mysqlConnection = #"server=server;
database=database;
password=password;
User ID=username;";
MySqlConnection conn = new MySqlConnection(mysqlConnection);
conn.Open();
using (conn)
{
string cmd = #"SELECT eventtypes.EventType, events.User,
DATE_FORMAT(events.DateTime,'%Y-%m-%dT%T') AS Date,
pcbid.PCBID,
getEAReasons.ItemValue AS EAReason,
getETReasons.ItemValue AS ETReason,
getReasons.ItemValue AS Reason,
getNotes.ItemValue AS Notes,
getSBEJobNumbers.ItemValue AS SBEJobNumber,
getSBEModelNumbers.ItemValue AS SBEModelNumber,
getSBEPNs.ItemValue as SBEPN,
getSBESerialNumbers.ItemValue as SBESerialNumber
FROM events
INNER JOIN pcbid ON events.PCBID = pcbid.PCBID
INNER JOIN eventtypes
ON events.EventType_ID = eventtypes.EventType_ID
LEFT JOIN getEAReasons
ON getEAReasons.Event_ID = events.Event_ID
LEFT JOIN getETReasons
ON getETReasons.Event_ID = events.Event_ID
LEFT JOIN getReasons
ON getReasons.Event_ID = events.Event_ID
LEFT JOIN getNotes
ON getNotes.Event_ID = events.Event_ID
LEFT JOIN getSBEJobNumbers
ON getSBEJobNumbers.Event_ID = events.Event_ID
LEFT JOIN getSBEModelNumbers
ON getSBEModelNumbers.Event_ID = events.Event_ID
LEFT JOIN getSBEPNs
ON getSBEPNs.Event_ID = events.Event_ID
LEFT JOIN getSBESerialNumbers
ON getSBESerialNumbers.Event_ID = events.Event_ID
WHERE eventtypes.EventType = 'termination'";
try
{
using (MySqlDataAdapter adapter = new MySqlDataAdapter(cmd, conn))
{
DataSet dataSet = new DataSet();
adapter.Fill(dataSet);
DataTable ds = dataSet.Tables[0];
for (int row = 0; row < ds.Rows.Count; row++ )
{
SearchResult result = new SearchResult()
{
EventType = ds.Rows[row]["EventType"].ToString(),
User = ds.Rows[row]["User"].ToString(),
Date = ds.Rows[row]["Date"].ToString(),
PCBID = ds.Rows[row]["PCBID"].ToString(),
EAReason = ds.Rows[row]["EAReason"].ToString().Any() ? ds.Rows[row]["EAReason"].ToString() : null,
ETReason = ds.Rows[row]["ETReason"].ToString().Any() ? ds.Rows[row]["ETReason"].ToString() : null,
Notes = ds.Rows[row]["Notes"].ToString().Any() ? ds.Rows[row]["Notes"].ToString() : null,
Reason = ds.Rows[row]["Reason"].ToString().Any() ? ds.Rows[row]["Reason"].ToString() : null,
SBEJobNumber = ds.Rows[row]["SBEJobNumber"].ToString().Any() ? ds.Rows[row]["SBEJobNumber"].ToString() : null,
SBEModelNumber = ds.Rows[row]["SBEModelNumber"].ToString().Any() ? ds.Rows[row]["SBEModelNumber"].ToString() : null,
SBEPN = ds.Rows[row]["SBEPN"].ToString().Any() ? ds.Rows[row]["SBEPN"].ToString() : null,
SBESerialNumber = ds.Rows[row]["SBESerialNumber"].ToString().Any() ? ds.Rows[row]["SBESerialNumber"].ToString() : null
};
searchResults.Add(result);
}
}
}
catch (MySqlException ex)
{
Console.WriteLine(ex);
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
}
}
catch (MySqlException ex)
{
Console.WriteLine(ex);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return searchResults;
}
public static XElement TransformSearchResult (SearchResult result)
{
return new XElement("SBE_PCB_Data",
new XAttribute("PCBID", result.PCBID),
new XElement(result.EventType,
new XAttribute("ActionID", result.ActionId),
new XAttribute("User", result.UserAndIP),
new XAttribute("Date", result.Date),
new XAttribute("PCBID", result.PCBID),
result.EAReason == null ? null : new XElement("EAReason", result.EAReason),
result.ETReason == null ? null : new XElement("ETReason", result.ETReason),
result.Reason == null ? null : new XElement("Reason", result.Reason),
result.Notes == null ? null : new XElement("Note", result.Notes),
result.SBEJobNumber == null ? null : new XElement("SBEJobNumber", result.SBEJobNumber),
result.SBEModelNumber == null ? null : new XElement("SBEModelNumber", result.SBEModelNumber),
result.SBEPN == null ? null : new XElement("SBEPN", result.SBEPN),
result.SBESerialNumber == null ? null : new XElement("SBESerialNumber", result.SBESerialNumber)
)
);
}
public static XElement TransformList (IEnumerable<SearchResult> listOfResults)
{
return new XElement("PCBDatabaseReply",
new XElement("SearchResult",
from r in listOfResults
select TransformSearchResult(r)));
}
}
}
Had to do some tweaking to get this to run, but the concept is sound, and I like that it's extensible. It doesn't quite give the right output yet, but I can tweak that as well.
Ok, Let's refactor this.
Lets not try and do this directly from your dataset, you are trying to do to many things in your method here, it's messy hard to maintain and very hard to unit test.
The first thing we should do is create a SearchResult class that we can work with more easily, this is also a convenient place to put in our Business rules (Ip added to User and random ActionId) it also means that we can easily mock up data into this class without having to hit the database, we can then test our transform logic as a unit test, not an integration test (which are slower, and have more dependencies)
public class SearchResult
{
public string EventType {get ;set;}
public string User {get ; set;}
public DateTime Date {get;set;}
public int PCBID {get;set;}
public string Reason {get;set;}
public string UserAndIP
{
get
{
return String.Format("{0}:192.168.255.255",User);
}
}
public string ActionId
{
get
{
return String.Format("{0:X4}", new Random().Next(0xffff));
}
}
}
So lets rewrite the query to now populate a list of SearchResult's instead of a dataset
public IEnumerable<SearchResult> GetSearchResults()
{
using(var conn = GetYourConnection())
{
conn.open();
using(var cmd = conn.CreateCommand())
{
cmd.CommandText = GetYourQueryString();
using(var reader = cmd.ExecuteReader())
{
while(reader.Read())
{
var result = new SearchResult
{
.... populate from reader...
}
yield return result;
}
}
}
}
}
So now that we have a SearchResult class and a query method that gives us a list of them, lets transform that to your required XML.
Firstly, I'll make some assumtions that are not 100% clear from your question. (if these are not correct, it will be easy enough to modify)
I'll assume that we are creating a search result tag for each search
result returned from our query. And that these will be contained in
the PCBDatabaseReply tag.
The xml tag "Termination" is the value of the Event Type, so I'll
assume that tag should be the EventType value.
Lets use Linq to XML to create the XML from the list of SearchResults
Firstly We'll create a method that transforms individual SearchResults (the contents of the SearchResult tag)
public XElement TransformSearchResult(SearchResult result)
{
return new XElement("SearchResult",
new XElement("SBE_PCB_Data", new XAttribute("PCBID", result.PCBID)),
new XElement(result.EventType,
new XAttribute("ActionID", result.ActionId),
new XAttribute("User", result.UserAndIP),
new XAttribute("Date", result.Date),
new XAttribute("PCBID", result.PCBID)),
new XElement("Reason", result.Reason));
}
Secondly we'll create the method to transform the list
public XElement TransformList(IEnumerable<SearchResult> listOfResults)
{
return new XElement("PCBDatabaseReply",
from r in listOfResults
select TransformSearchResult(r));
}
Now our main calling method simply becomes...
var searchResults = GetSearchResults();
var xml = TransformList(searchResults);