Add columns to dataset to be used as XML parent nodes - c#

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);

Related

How do I remove a line from the list based on the ID of that line?

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);

UWP - Insert data from the server (json) into the database with certain conditions

I have an app that checks the data on the server (json) and the database. I want when the id on json is not the same as the id on the database, it will insert all data with unequal id into the database.
Code:
var sqlpath = System.IO.Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "ebookstore.db");
this.DataContextChanged += (s, e1) => { UpdateViewModel = DataContext as ViewModels.UpdateViewModel; };
string idDb = #"SELECT id FROM books where parent_folder_id = 2 and title like '%guru%'";
var IDdb = objConnUpdate.Prepare(idDb);
IDdb.Step();
iddb = IDdb[0].ToString();
IDDB = Convert.ToInt32(iddb.ToString());
ConnectionProfile connections = NetworkInformation.GetInternetConnectionProfile();
{
try
{
Downloading.IsOpen = true;
string urlPath1 = "https://.../fetch/k13G";
var httpClient1 = new HttpClient(new HttpClientHandler());
httpClient1.DefaultRequestHeaders.TryAddWithoutValidation("KIAT-API-KEY", "...*");
var values1 = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("halaman", "1"),
new KeyValuePair<string, string>("limit", "20"),
};
var response1 = await httpClient1.PostAsync(urlPath1, new FormUrlEncodedContent(values1));
response1.EnsureSuccessStatusCode();
string jsonText1 = await response1.Content.ReadAsStringAsync();
JsonObject jsonObject1 = JsonObject.Parse(jsonText1);
JsonArray jsonData1 = jsonObject1["data"].GetArray();
foreach (JsonValue groupValue in jsonData1)
{
JsonObject groupObject = groupValue.GetObject();
string ID = groupObject["id"].GetString();
BukuUpdate file1 = new BukuUpdate();
file1.ID = ID;
int intID = Convert.ToInt32(file1.ID);
if (intID != IDDB)
{
string jumlahidDb = #"SELECT COUNT(id) FROM books where parent_folder_id = 2 and id > " + IDDB + " and title like '%guru%'";
var jumlahIdDB = objConnUpdate.Prepare(jumlahidDb);
jumlahIdDB.Step();
if (jumlahiddb < jumlahbuku)
{;
if (nama == "Kelas_01_SD_")
{
DownloadBukuK2013G(url);
string K2013GUpdate = #"INSERT INTO books (id,title,folder_id,identifier,parent_folder_id) SELECT " + intID + ",'" + namaFile + ".pdf',34,'" + namaFile +
".pdf',2 WHERE not exists (select id AND title AND folder_id AND identifier AND parent_folder_id FROM books WHERE id=" + intID + " and title='" + namaFile +
".pdf' AND folder_id=34 and identifier='" + namaFile + ".pdf' and parent_folder_id=2)";
var K2013GQuery = objConnUpdate.Prepare(K2013GUpdate);
K2013GQuery.Step();
}
BukuUpdate.cs:
public class BukuUpdate
{
public string ID { get; set; }
}
Database:
I did not succeed in implementing it. How to handle it?
But if I use the code in my previous post, it only shows the first id in the database
You getting the first id by this code line iddb = IDdb[0].ToString(); for using. Actually the id list is saved in idDb variable.
I want if the id on json is not the same as the id on the database, then the data will be added to the database. If the same, then do nothing.
For this, you may just get all the ids from the database, and compare with the new income record from Json one by one. From your code snippet I'm not sure what's the Nuget package you're using for Sqlite, but the newest official tutorial using Microsoft.Data.SQLite package. I also strongly recommended you to use Microsoft.Data.SQLite package by following the official tutorial. By this way, the insert sample code may like this:
public sealed partial class MainPage : Page
{
private void btngettest_Click(object sender, RoutedEventArgs e)
{
List<String> ids = sqlhelp.GetData();
string idfromjson = "2";
foreach (string id in ids)
{
if (id != idfromjson)
{
//do insert operation
}
else
{
//do nothing
}
}
}
}
public class sqlhelp
{
public static void InitializeDatabase()
{
...
}
public static List<String> GetData()
{
List<String> entries = new List<string>();
using (SqliteConnection db =
new SqliteConnection("Filename=sqliteSample.db"))
{
db.Open();
SqliteCommand selectCommand = new SqliteCommand
("SELECT id from MyTable", db);
SqliteDataReader query = selectCommand.ExecuteReader();
while (query.Read())
{
entries.Add(query.GetString(0));
}
db.Close();
}
return entries;
}
Since your code snippet contains a lot of your own logic that I didn't give changes on your original code snippet. Please kindly reference my simple code which may be more clearly to know.

Two CSV files to one output, join and add data on column(s)

I'm working in C# (.Net 4) and I am trying to do several things:
I have 2 files ("Offline.csv","online.csv"), and I'm having those files make one "master" file (called "Attendance.csv")
Both offline.csv and online.csv contain similar data---
My Offline.csv file has:
(ID),(TimeInMin),(DateWithoutSlashes yyymmdd)
01,10,20151201
01,05,20151202
02,11,20151201
03,11,20151202
My Online.csv file has
(ID),(TimeInMin),(DateWithoutSlashes yyymmdd)
01,70,20151201
02,20,20151202
03,22,20151202
After my program is ran, the Attendance.csv should look something like:
(Same headers)
01,80,20151201
01,05,20121502 (notice the date from offline.csv, which doesn't exist in the online.csv)
02,31,20151201
03,33,20151202
So what I'm trying to do is:
Compare the data from both the offline.csv and online.csv files. If data matches on the "ID" and "Date" columns, add the minutes together (column 2) and put them as a row in the Attendance.csv file
However, IF the offline.csv contains rows that the online.csv doesn't have, then put all those other records into the Attendance.csv on their own. Perform the same action with the online.csv, being mindful to not duplicate minutes that were already merged together from step #1
I don't know if that all makes sense, but I hope it does :X
I have been beating my head against the wall all day with this, and I don't know what else to look at.
With all that said, here is what I have so far:
I have created my own class, called "aoitime", it looks as follows:
public class aoitime
{
public string ID { get; set; }
public string online { get; set; }
public string offline { get; set; }
public string dtonline { get; set; }
public string dtoffline { get; set; }
public string date { get; set; }
}
I then use IEnumerable in a different function, looks similar to ...
IEnumerable<aoitime> together =
from online in onlinefile
let onlineFields = online.Split(',')
from id in offlinefile
let offlineFields = id.Split(',')
where (onlineFields[0] == offlineFields[0] && onlineFields[2] == offlineFields[2]) || (!offlineFields[1].Contains(""))
orderby onlineFields[0]
select new aoitime
{
ID = onlineFields[0],
online = onlineFields[1],
offline = offlineFields[1],
dtonline = onlineFields[2],
dtoffline = offlineFields[2],
date = onlineFields[2]
};
StreamWriter Attendance = new StreamWriter(destination);
Attendance.Write("SIS_NUMBER,MINUTES,DATE" + Environment.NewLine);
foreach (aoitime att in together)
{
int date = int.Parse(att.date);
int dateonline = int.Parse(att.dtonline);
int dateoffline = int.Parse(att.dtoffline);
int online = int.Parse(att.online);
int offline = int.Parse(att.offline);
int total = (online + offline);
Console.WriteLine("Writing total time now: "+online);
Attendance.Write(att.ID + "," + total + "," date + Environment.NewLine);
}
I then tried creating another IEnumerable class spawn that looks similar to the one above, but instead using "where offlineFields[2] != onlineFields[2]" but I get unpredictable results. I just don't know where else to look or what else to do.
Please be gentle, I'm very much new to programming in general (I promise this isn't for a classroom assignment :-)
thanks so much for any advice and reading this book!
You are almost there. I wrote this code, so hopefully you will be able to learn something from it.
First you only need one entity class for this. Note the ToString method. You will see how it's used later.
public class Attendance
{
public int Id { get; set; }
public int TimeInMinutes { get; set; }
public string Date { get; set; }
public override string ToString()
{
return string.Format("{0},{1},{2}", Id, TimeInMinutes, Date);
}
}
Now the code to parse your files and create the new file. Read my comments in the code.
var onlineEntries = File.ReadAllLines(#"c:\online.txt");//read online file
var validOnlineEntries = onlineEntries.Where(l => !l.Contains("(")); //remove first line
var onlineRecords = validOnlineEntries.Select(r => new Attendance()
{
Id = int.Parse(r.Split(new[] {","}, StringSplitOptions.None)[0]),
TimeInMinutes = int.Parse(r.Split(new[] {","}, StringSplitOptions.None)[1]),
Date = r.Split(new[] {","}, StringSplitOptions.None)[2],
}).ToList();//popultae Attendance class
var offlineEntries = File.ReadAllLines(#"c:\offline.txt"); //read online file
var validOfflineEntries = offlineEntries.Where(l => !l.Contains("(")); //remove first line
var offlineRecords = validOfflineEntries.Select(r => new Attendance()
{
Id = int.Parse(r.Split(new[] { "," }, StringSplitOptions.None)[0]),
TimeInMinutes = int.Parse(r.Split(new[] { "," }, StringSplitOptions.None)[1]),
Date = r.Split(new[] { "," }, StringSplitOptions.None)[2],
}).ToList();//popultae Attendance class
var commonRecords = (from n in onlineRecords
join f in offlineRecords on new {n.Date, n.Id } equals new {f.Date, f.Id} //if Date and Id are equal
select new { n.Id, TimeInMinutes = (n.TimeInMinutes + f.TimeInMinutes), n.Date }).OrderBy(x => x.Id).Distinct().ToList(); //add Online and Off line time
var newRecords = commonRecords.Select(r => new Attendance()
{
Id = r.Id,
TimeInMinutes = r.TimeInMinutes,
Date = r.Date,
}); //Poulate attendance again. So we can call toString method
onlineRecords.AddRange(offlineRecords); //merge online and offline
var recs = onlineRecords.Distinct().Where(r => !newRecords.Any(o => o.Date == r.Date && o.Id == r.Id)).ToList(); //remove already added items from merged online and offline collection
newRecords.AddRange(recs);//add filtered merged collection to new records
newRecords = newRecords.OrderBy(r => r.Id).ToList();//order new records by id
File.WriteAllLines(#"C:\newFile.txt", newRecords.Select(l => l.ToString()).ToList()); //write new file.
Just to add this as an answer, I am selecting #Kosala-w's suggestion as an answer. My code now looks very identical to what he posted, except I modified the ID to a string format because the integers used for the IDs are pretty lenghty.
I thank both people who answered this question, and I appreciate the SO community! Have a good day :-)
public class Attendance
{
public string Id { get; set; }
public int TimeInMinutes { get; set; }
public int Code { get; set; }
public string Date { get; set; }
public override string ToString()
{
return string.Format("{0},{1},{2}", Id, TimeInMinutes, Date);
}
}
I also have more rows that I have to handle in the Attendance sheet than I stated in my original question (I didn't worry about those because I wasn't concerned that I'd have a hard time getting what I needed.)
Anyway, the code below is what I used, again, thanks Kosala.
private void createAttendance()
{
try
{
txtStatus.ResetText();
txtStatus.Text += "Creating Attendance file. Please wait.";
string destination = (#"C:\asdf\Attendance.csv");
barStatus.Caption = "Processing Attendance file. Please wait.";
if (File.Exists(destination))
File.Delete(destination);
var validOnlineEntries = File.ReadAllLines(#"C:\asdf\online.csv");//read online file
//var validOnlineEntries = onlineEntries.Where(l => !l.Contains("(")); //remove first line
var onlineRecords = validOnlineEntries.Select(r => new Attendance()
{
Id = (r.Split(new[] { "," }, StringSplitOptions.None)[0] + ",202" + "," + txtYear.Text),
TimeInMinutes = int.Parse(r.Split(new[] { "," }, StringSplitOptions.None)[1]),
Date = r.Split(new[] { "," }, StringSplitOptions.None)[2],
}).ToList();//populate Attendance class
var validOfflineEntries = File.ReadAllLines(#"C:\asdf\offline.csv"); //read online file
//var validOfflineEntries = offlineEntries.Where(l => !l.Contains("(")); //remove first line
var offlineRecords = validOfflineEntries.Select(r => new Attendance()
{
Id = (r.Split(new[] { "," }, StringSplitOptions.None)[0] + ",202" + "," + txtYear.Text),
TimeInMinutes = int.Parse(r.Split(new[] { "," }, StringSplitOptions.None)[1]),
Date = r.Split(new[] { "," }, StringSplitOptions.None)[2],
}).ToList();//populate Attendance class
var commonRecords = (from n in onlineRecords
join f in offlineRecords on new { n.Date, n.Id } equals new { f.Date, f.Id } //if Date and Id are equal
select new { n.Id, TimeInMinutes = (n.TimeInMinutes + f.TimeInMinutes), n.Date }).OrderBy(x => x.Id).Distinct().ToList(); //add Online and Off line time
var newRecords = commonRecords.Select(r => new Attendance()
{
Id = r.Id,
TimeInMinutes = r.TimeInMinutes,
Date = r.Date,
}).ToList(); //Populate attendance again. So we can call toString method
onlineRecords.AddRange(offlineRecords); //merge online and offline
var recs = onlineRecords.Distinct().Where(r => !newRecords.Any(o => o.Date == r.Date && o.Id == r.Id)).ToList(); //remove already added items from merged online and offline collection
newRecords.AddRange(recs);//add filtered merged collection to new records
newRecords = newRecords.OrderBy(r => r.Id).ToList();//order new records by id
StreamWriter Attendance = new StreamWriter(destination);
//Attendance.Write("SIS_NUMBER,SCHOOL_CODE,SCHOOL_YEAR,ABSENCE_DATE,ABSENCE_REASON1,ABSENCE_REASON2,MINUTES_ATTEND,NOTE,ABS_FTE1,ABS_FTE2" + Environment.NewLine);
Attendance.Write("SIS_NUMBER,SCHOOL_CODE,SCHOOL_YEAR,MINUTES_ATTEND,ABSENCE_DATE,ABSENCE_REASON2,ABSENCE_REASON1,NOTE,ABS_FTE1,ABS_FTE2" + Environment.NewLine);
Attendance.Dispose();
File.AppendAllLines(destination, newRecords.Select(l => l.ToString()).ToList()); //write new file.
Convert_CSV_To_Excel();
}
catch(Exception ex)
{
barStatus.Caption = ("ERROR: "+ex.Message.ToString());
}
}
I plan to do some more fine tuning, but this sure got me in the right direction!
The first thing that I'd do is define a simpler class to hold your aoitimes. For example:
public class aoitime
{
public string ID { get; set; }
public int TimeInMinutes { get; set; }
public string DateWithoutSlashes { get; set; }
}
Then, you'll want to parse the string from the csv file into that class. I figure that that's an implementation detail that you can probably figure out on your own. If not, leave a comment and I can post more details.
Next, the tricky part is that you want not only a join, but you want the exceptions as well. The join logic is fairly simple:
var matches = from offline in offlineItems
join online in onlineItems
on
new {offline.ID, offline.DateWithoutSlashes} equals
new {online.ID, online.DateWithoutSlashes}
select new aoitime
{
ID = offline.ID,
TimeInMinutes = offline.TimeInMinutes + online.TimeInMinutes,
DateWithoutSlashes = offline.DateWithoutSlashes
};
(Notice there that you're using anonymous objects in the "ON" join condition). But the hard part is how to get the exceptions. LINQ is set up to do inner joins or equijoins, but I'm not sure about outer joins. At least I haven't seen it.
So one solution might be to use the LINQ join to get the matches and then another LINQ query to get those that don't match and then combine those two collections and write them out to a file.
Another solution might be to go back to basics and do the iteration logic yourself. LINQ is just elegant iteration logic and if it doesn't do what you need it to do, you might need to do it yourself.
For example, let's say that you have your collection of online and offline items and you want to iterate through them and do the comparison:
List<aoitime> offlineItems = <some method that produces this list>
List<aoitime> onlineItems = <some method that produces this list>
List<aoitime> attendanceItems = new List<aoitime>();
//For simplicity, assuming that you have the same number of elements in each list
for (int i = 0; i < offlineItems.Count; i++)
{
aoitime offline = offlineItems[i];
aoitime online = onlineItems[i];
if(offline.ID == online.ID && offline.DateWithoutSlashes = online.DateWithoutSlashes)
{
//Create your new object and add it to the attendance items collection.
}
else
{
//Process the exceptions and add them individually to the attendance items collection.
}
}
So you do the iteration and processing yourself and have control over the whole process. Does that make sense? If not, let me know in a comment and I can add more.

How to add a new item button to a dropdownlist using c# for Winforms API

I have a dropdown list and I populate it programmatically with a dataset pulled from a database.
What I like to do is add an option to act like a button " " button. when it is clicked a user is navigated to a different form.
something like this
(screenshot before clicking on the drop-down menu)
(screenshot to show the option like a button " "
This is the code that I am using to populate the menu
private void InventoryAdd_Load(object sender, EventArgs e)
{
InputDepartment.Items.Clear();
InputVendors.Items.Clear();
//populate the Departments menu
dbConnetion db = new dbConnetion();
string sql = " SELECT departmentName, departmentID "
+ " FROM departments "
+ " WHERE status = 'active' ";
InputDepartment.Items.Clear();
var deptSource = new List<ComboItem>();
deptSource.Add(new ComboItem { ID = -1, Text = "Select a Department" });
deptSource.Add(new ComboItem { ID = 0, Text = " < Add New> " });
foreach (var item in db.getData(sql, null, r => new ComboItem()
{
ID = Convert.ToInt32(r["departmentID"]),
Text = r["departmentName"].ToString()
})
)
{
deptSource.Add(item);
};
InputDepartment.DataSource = deptSource;
}
//This generic class will help setting an id and a text for a comboItem
class ComboItem
{
public int ID { get; set; }
public string Text { get; set; }
public string Val1 { get; set; }
public override string ToString()
{
return Text;
}
}
This is my getData Method
// query the data base
public IEnumerable<T> getData<T>(string query, List<MySqlParameter> pars, Func<IDataRecord, T> transform)
{
using (var conn = new MySqlConnection(conn_string))
using (var cmd = new MySqlCommand(query, conn))
{
if (pars != null)
{
foreach (MySqlParameter p in pars)
{
cmd.Parameters.Add(p);
}
}
conn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
yield return transform(rdr);
}
}
conn.Close();
}
}
Instead of
deptSource.Add(new ComboItem { ID = i.ID, Text = i.Text });
use
deptSource.Add(i);
as i already is a ComboItem. Also, if your getData returns IEnumerable, that might be costly, so use.
foreach (var i in db.getData(sql, null, r => new ComboItem()
{
ID = Convert.ToInt32(r["departmentID"]),
Text = r["departmentName"].ToString()
}).ToList()
or even simpler:
deptSource.AddRange( db.getData(sql, null, r => new ComboItem()
{
ID = Convert.ToInt32(r["departmentID"]),
Text = r["departmentName"].ToString()
}) );
Finally, you need to hook up an event to:
protected override void OnSelectedValueChanged( EventArgs e )
Then you can check if the new value is 0 and perform your new item addition elsewhere.
UPDATE
I don't believe the standard ComboBox supports adding a button. There are 3rd party tools, which do. WPF also does.

How do i send a data from one form to function of another class

I know how to pass data from one form to another form, but how can i pass data from one form to class. This is my data which i want to pass from form1 to class Question:
string GrpID = "somevalue";
string DurationID = "somevalue";`
I searched for it but i didn't got exact answer for this.
class Question
{
string GroupID="here i want this value of GroupID,how can i get this";
string DuraID="and here value of DurationID";
// so that i can use them here like this
public IEnumerable<Question> GetQuestions(string topicId, int marks)
{
string sql = "select QID,Question,Opt1,Opt2,Opt3,Opt4,AnsOp,Marks from Questions where TopicID IN(" +
topicId + ") and Marks=" + marks.ToString();
var cmd = new OleDbCommand(sql, new OleDbConnection(#"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=db1.mdb"));
var rs = cmd.ExecuteReader();
if (rs != null)
{
while (rs.Read())
{
yield return
new Question
{
Id = rs[0].ToString(),
Text = rs[1].ToString(),
Option1 = rs[2].ToString(),
Option2 = rs[3].ToString(),
Option3 = rs[4].ToString(),
Option4 = rs[5].ToString(),
AnswerOption = rs[6].ToString(),
Marks = marks
};
}
}
}
}
public void Foo()//In this function Can i pass that `string[] conf` here?
{
var totQsn = Convert.ToInt16(conf[0]); // isn't this just the sum of everything else?
var mark1qsn = Convert.ToInt16(conf[3]); //this variable contains number of question to be display of mark 1
var mark2qsn = Convert.ToInt16(conf[4]);
var mark3Qsn = Convert.ToInt16(conf[5]);
var mark4Qsn = Convert.ToInt16(conf[6]);
var mark1questionSet = GetQuestions(topicId, 1).ToList();
var mark2questionSet = GetQuestions(topicId, 2).ToList();
}
Add a method to the class:
class Question
{
public void SomeFunction(string grpId, string durationId)
{
...
}
}
Then you can call it from your form:
questionInstance.SomeFunction("somevalue","somevalue");

Categories

Resources