I'm trying to make a utility to generate an insert script of SQL tables along with relational table.
I got all the values in C#.
Now I want to remove the one column name and its value from the script.most probably the identity column.
For example: the string I have (which keeps on changing with table name and varies)
INSERT INTO Core.Customers ([customerId], [customername], [customeradress],[ordernumber])
VALUES (123, N'Rahul', N'244 LIZ MORN', 2334)
NOW I know I have to remove CustomerId (sometimes need to be replaces with #somevariable).
Please give me an efficient way how to retrieve customerId value and deleting column name and value.
I was looking for a method to find column value by column Name.
What I am doing is below - I know it's inefficient and can cause problem but for now it is working smoothly.
public string GetColumnValueToForDesiredColumnName(string row, TableInfo tableinfo, string NameofColumnTOfindvalueFor)
{
Dictionary<string, string> ValueTypedictionary = new Dictionary<string, string>();
string value = null;
// this code is quite messy - I need some suggestion on this one
string[] plusseperatedinsert = row.Replace("INSERT " + "[" + tableinfo.Schema + "].[" + tableinfo.TableName + "]", string.Empty).Trim().Replace("VALUES", "+").Split('+');
string[] columnvalues = plusseperatedinsert[0].Replace("(", string.Empty).Replace(")", string.Empty).Replace("(", string.Empty).Replace("[", string.Empty).Replace("]", string.Empty).Trim().Split(',');
string[] valuesfield = plusseperatedinsert[1].Replace("(", string.Empty).Replace(")", string.Empty).Replace("(", string.Empty).Replace("[", string.Empty).Replace("]", string.Empty).Trim().Split(',');
for (int index = 0; index < columnvalues.Length; index++)
{
ValueTypedictionary.Add(columnvalues[index], valuesfield[index]);
}
ValueTypedictionary.TryGetValue(NameofColumnTOfindvalueFor, out value);
return value;
}
This returns 123 as value.
And then I am using
string.Replace("[customerId],", string.empty).Replace(123, string.empty);
Create a special clas InsertQuery which stores pairs of column names with column values and then if you always need to remove or change an Id of a table you will know that it is on the first index of a list/array/whatever you use to store these pairs.
Define a method for removing this column and you are good to go.
So here is the code. You will probably change it somehow, it is just a proof of concept.
public class InsertQuery
{
private class Column
{
public string Name { get; set; }
public string Value { get; set; }
}
private readonly List<Column> columns = new List<Column>();
private readonly string tableName;
public InsertQuery(string tableName)
{
this.tableName = tableName;
}
public void AddColumn(string name, string value)
{
columns.Add(new Column { Name = name, Value = value });
}
public string RemoveColumnByName(string columnName)
{
var column = columns.First(c => c.Name == columnName);
var value = column.Value;
columns.Remove(column);
return value;
}
public string RemoveIdColumn()
{
var column = columns.First();
var value = column.Value;
columns.RemoveAt(0);
return value;
}
public override string ToString()
{
var sb = new StringBuilder();
sb.Append("INSERT INTO ");
sb.Append(tableName);
sb.Append(" (");
// append first all column names and then their values
return sb.ToString();
}
}
Trust me. Building such a tool using just string operations is not quite a good idea. In time you will want to add more functionality and you will be stuck with code that can be hardly extended. Encapsulation is a way to go.
Related
I have a string that looks like that:
random text 12234
another random text
User infos:
User name : John
ID : 221223
Date : 23.02.2018
Job: job1
User name : Andrew
ID : 378292
Date : 12.08.2017
Job: job2
User name : Chris
ID : 930712
Date : 05.11.2016
Job : job3
some random text
And this class:
class User
{
public string UserName { get; set; }
public string ID { get; set; }
public string Date { get; set; }
public string Job { get; set; }
public User(string _UserName, string _ID, string _Date, string _Job)
{
UserName = _UserName
ID = _ID;
Date = _Date;
Job = _Job;
}
}
And I want to create a List of Users with informations from that string.
I have tried doing that:
List<User> Users = new List<User>();
string Data = (the data above)
string[] lines = Data.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
List<string> UserNames = new List<string>();
List<string> IDs = new List<string>();
List<string> Dates = new List<string>();
List<string> Jobs = new List<string>();
foreach (var line in lines)
{
if (line.StartsWith("User name : "))
{
UserNames.Add(Line.Remove(0, 12));
}
if (Line.StartsWith("ID : "))
{
IDs.Add(Line.Remove(0, 5));
}
if (Line.StartsWith("Date : "))
{
Dates.Add(Line.Remove(0, 7));
}
if (Line.StartsWith("Job : "))
{
Jobs.Add(Line.Remove(0, 6));
}
}
var AllData = UserNames.Zip(IDs, (u, i) => new { UserName = u, ID = i });
foreach (var data in AllData)
{
Users.Add(new User(data.UserName, data.ID, "date", "job"));
}
But I can only combine two lists using this code. Also, I have more than 4 values for each user (the string above was just a short example) .
Is there a better method? Thanks.
Since it seems to be always 4 lines of information you could go in steps of 4 with a loop through the splitted array lines. At each step you would split by colon : and collect the last item, which is the desired value:
EDIT: In this case I would suggets to look for the START of the data.
int startIndex = Data.IndexOf("User name");
EDIT 2:
also ends with another line of text
then you can use LastIndexOf to find the end of the important information:
int endIndex = Data.LastIndexOf("Job");
int lengthOfLastLine = Data.Substring(endIndex).IndexOf(Environment.NewLine);
endIndex += lengthOfLastLine;
and then simply take a SubString from the startindex on until the end
string [] lines = Data.Substring(startIndex, endIndex - startIndex)
.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
List<User> allUsers = new List<UserQuery.User>();
for (int i = 0; i < lines.Length; i += 4)
{
string name = lines[i].Split(':').Last().Trim();
string ID = lines[i + 1].Split(':').Last().Trim();
string Date = lines[i + 2].Split(':').Last().Trim();
string Job = lines[i + 3].Split(':').Last().Trim();
allUsers.Add(new User(name, ID, Date, Job));
}
Ahhh, and you should Trim the spaces away.
This solution should be readable. The hard coded step size of 4 is actually annoying in my solution
Disclaimer: This solution works only as long as the format does not change. If the order of the lines should change, it will return false results
Instead of checking each line to add each of them to a a list, you can create your list of User directly. There you go:
Split by double new line
Split by new line
Build each User
Code:
var users = data.Split(new[] {"\n\n" }, StringSplitOptions.None).Select(lines =>
{
var line = lines.Split(new[] { "\n" }, StringSplitOptions.None);
return new User(line[0].Substring(11), line[1].Substring(4), line[2].Substring(6), line[3].Substring(5));
});
Try it online!
As #Mong Zhu answer, remove everything before and after. A this point, this is another question I wont try to solve. Remove the noise before and after then parse your data.
For a robust, flexible and self-documenting solution that will allow you to easily add new fields, ignore all the extraneous text and also cater for variations in your file format (this seems to be the case with, for example, no space in "ID:" only in the 3rd record), I would use a Regex and some LINQ to return a collection of records as follows:
using System.Text.RegularExpressions;
public class Record
{
public string Name { get; set; }
public string ID { get; set; }
public string Date { get; set; }
public string Job { get; set; }
}
public List<Record> Test()
{
string s = #"User name : John
ID : 221223
Date : 23.02.2018
Job: job1
User name : Andrew
ID : 378292
Date : 12.08.2017
Job: job2
User name : Chris
ID: 930712
Date : 05.11.2016
Job: job3
";
Regex r = new Regex(#"User\sname\s:\s(?<name>\w+).*?ID\s:\s(?<id>\w+).*?Date\s:\s(?<date>[0-9.]+).*?Job:\s(?<job>\w\w+)",RegexOptions.Singleline);
r.Matches(s);
return (from Match m in r.Matches(s)
select new Record
{
Name = m.Groups["name"].Value,
ID = m.Groups["id"].Value,
Date = m.Groups["date"].Value,
Job = m.Groups["job"].Value
}).ToList();
}
The CSV format seems to be what you're looking for (since you want to add some header to this file the actual CSV stars on 6th line):
random text 12234
another random text
User infos:
UserName;ID;Date;Job
John;221223;23.02.2018;job1
Andrew;378292;12.08.2017;job2
Chris;930712;05.11.2016;job3
And then you could read this file and parse it:
var lines = File.ReadAllLines("pathToFile");
var dataStartIndex = lines.IndexOf("UserName;ID;Date;Job");
var Users = lines.Skip(dataStartIndex + 1).Select(s =>
{
var splittedStr = s.Split(';');
return new User(splittedStr[0], splittedStr[1], splittedStr[2], splittedStr[3]);
}).ToList();
If you're working with console entry just skip the header part and let user enter comma separated values for each user on a different string. Parse it in a same way:
var splittedStr = ReadLine().Split(';');
var userToAdd = new User(splittedStr[0], splittedStr[1], splittedStr[2] , splittedStr[3]);
Users.Add(userToAdd);
I'm trying to update a sheet in C# using ClosedXML, but it seems the sheet is not being updated.
public string FeedAndFetchValueFromThirdSheet(List<string> listValueColl, IXLWorksheet worksheetThird)
{
int posTemp = worksheetThird.RowsUsed().Count(); // Value here is 1
string value = "";
foreach (var obj in listValueColl)
{
posTemp++;
worksheetThird.Cell(posTemp, 1).InsertData(obj);
}
int posUpdated = worksheetThird.RowsUsed().Count(); //After updating the sheet the value still remain 1
value = "A"+ (posTemp - listValueColl.Count()) +":A" + posTemp;
return value;
}
ClosedXML's InsertData() method uses any IList<T> as input, not a string or similar object.
So, just use List<string> or string[] array as container for data, that you want to insert.
The updated method:
public string FeedAndFetchValueFromThirdSheet(List<string> listValueColl, IXLWorksheet worksheetThird)
{
int posTemp = worksheetThird.RowsUsed().Count(); // Value here is 1
string value = "";
foreach (var obj in listValueColl)
{
posTemp++;
// Use IList (simple array, list, etc.) as container for data,
// that you want to insert.
string[] rowDataToInsert = { obj };
// Insert created array (not a string).
worksheetThird.Cell(posTemp, 1).InsertData(rowDataToInsert);
}
int posUpdated = worksheetThird.RowsUsed().Count(); //After updating the sheet the value still remain 1
value = "A" + (posTemp - listValueColl.Count()) + ":A" + posTemp;
return value;
}
method1:
public void method1(DataTable ServerGroupIds)
{
obj.method2(ServerGroupIds);
}
method2 :
public static void method2(string[] servergroups)
{
obj.Message = userName + " has Un-Restricted the Project.";
}
Now I want to pass the DataTable values into the method2 String[] servergroups
How can I pass my DataTable values into an array of string values?
Sorry, Forget to mention I have 2 columns in my DataTable. !st column is ProjectId and Second Column is Server Group Id. Now I need only ServerGroup Id's in my array of string
Try this
public void method1(DataTable ServerGroupIds)
{
string [] serverGroups = ServerGroupIds.AsEnumerable().Select(t => t.Field<string>("ID")).ToArray<string>();
obj.method2(serverGroups );
}
Don't forget to include System.Linq
in t.Field<string>("ID"). Replace "ID" with the name of the column in the data table you want to put into the array
For a single row you can do this:
var rowAsString = string.Join(", ", ServerGroupIds.Rows[0].ItemArray);
Now add all the rows (by looping through your DataTable) rowAsString to a list:
List<string> list = new List<string>();
for (int i = 0; i < ServerGroupIds.Rows.Count; i++)
{
string rowAsString = string.Join(", ", ServerGroupIds.Rows[i].ItemArray);
list .Add(rowAsString );
}
string[] array = list.ToArray();
And pass to method2:
obj.method2(array);
I would like to create two SQL tables using C#, within a loop. Each table is different, and has its column names stored in an array. Each array of column names is actually obtained from the header of a csv file.
### fnames is an array of file paths (2 csv files)
foreach string f in fnames)
{
## snip
using (StreamReader rdr = new StreamReader(f))
{
string header = read.line(); ## This is the array of table columns
}
string tab = Path.GetFileNameWithoutExtension(f);
string query = #"create table "+ tab + ..."; #I am not sure how to write the column names and types dynamically
}
Imagine that:
The columns for table 1 are : Date (datetime), Value (int)
The columns for table 2 are : Date (datetime), ID (varchar(255)), Return (int)
Note that the two tables have different columns with different types.
Would you have any suggestion as to how to achieve this?
Thank you!
You should break the problem apart, first you need to get a list of objects that define your column headers, after you have that you can loop over that list and build the query.
class HeaderInfo
{
public HeaderInfo(string header)
{
throw new NotImplementedException("Parse out your header info here and populate the class")
}
public string Name {get; private set;}
public string TypeInfo {get; private set;}
}
private List<HeaderInfo> ParseHeader(string header)
{
var headerInfo = new List<HeaderInfo>();
string[] headerItems = //Split your header line in to indvidual items some how
foreach(headerItem in headerItems)
{
headerInfo.Add(new HeaderInfo(headerItem));
}
return headerInfo;
}
private string TableString(List<HeaderInfo> headerInfo)
{
StringBuilder sb = new StringBuilder();
foreach(var info in headerInfo)
{
sb.AppendFormat("{0} {1}, ", info.Name, info.TypeInfo);
}
sb.Remove(sb.Length -2, 2); //Remove the last ", "
return sb.ToString();
}
private void YourMethod(string[] fnames)
{
### fnames is an array of file paths (2 csv files)
foreach string f in fnames)
{
## snip
List<HeaderInfo> headerInfo;
using (StreamReader rdr = new StreamReader(f))
{
string headerLine = read.line(); ## This is the array of table columns
headerInfo = ParseHeader(headerLine);
}
string tab = Path.GetFileNameWithoutExtension(f);
string query = String.Format(#"create table [{0}] ({1})", tab, TableString(headerInfo));
}
}
The DataTable below:
ClassID ClassName StudentID StudentName
1 A 1000 student666
2 B 1100 student111
5 C 1500 student777
1 A 1200 student222
2 B 1080 student999
The dictionary key is composed of "ClassID ,ClassName " and value is composed of "StudentID,StudentName" .
Dictionary<string, string> d = new Dictionary<string, string>();
foreach (DataRow dr in table.Rows)
{
string key=dr["ClassID"].ToString() + dr["ClassName"].ToString();
if (!d.ContainsKey(key))
{
//Do something();......
}
else
{
//Do something();......
}
}
foreach (var s in d.Keys)
{
Response.Write(s+"|+"+d[s]+"<br>");
}
Is there a faster way?
assume that key is '1,A' ,Value should be ' 1000,student666' and '1200,student222'
Here goes then. Using Linq, you can group them then perform string concatenation if you want.
// Start by grouping
var groups = table.AsEnumerable()
.Select(r => new {
ClassID = r.Field<int>("ClassID"),
ClassName = r.Field<string>("ClassName"),
StudentID = r.Field<int>("StudentID"),
StudentName = r.Field<string>("StudentName")
}).GroupBy(e => new { e.ClassID, e.ClassName });
// Then create the strings. The groups will be an IGrouping<TGroup, T> of anonymous objects but
// intellisense will help you with that.
foreach(var line in groups.Select(g => String.Format("{0},{1}|+{2}<br/>",
g.Key.ClassID,
g.Key.ClassName,
String.Join(" and ", g.Select(e => String.Format("{0},{1}", e.StudentID, e.StudentName))))))
{
Response.Write(line);
}
Try this:
Dictionary<string, string> d = new Dictionary<string, string>();
foreach (DataRow dr in table.Rows)
{
string key=dr["ClassID"].ToString() + "-" + dr["ClassName"].ToString();
string value=dr["StudentID"].ToString() + "-" + dr["StudentName"].ToString();
if (!d.ContainsKey(key))
{
d.Add(key, value);
}
}
Reference
Dictionary.Add Method
OR ELSE Try Onkelborg's Answer
How to use compound key for dictionary?
The tricky thing here is the composite key (ClassID, ClassName). Once you identify that, it's easy to search this site for the solution.
I'd recommend using tuples as pointed out here: Composite Key Dictionary
The easiest way is to use a string value of ClassID|ClassName as key. For example, use string value "1|A" for key for the first row, and string value "2|B" for key for the second row, etc.
Here is something that can give you an idea:
using System;
using System.Data;
using System.Collections.Generic;
namespace SO17416111
{
class Class
{
public int Id { get; set; }
public string Name { get; set; }
}
// Note that definition of Class and Student only differ by name
// I'm assuming that Student can/will be expanded latter.
// Otherwise it's possible to use a single class definition
class Student
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main()
{
DataTable table = GetData();
Dictionary<Class, List<Student>> d = new Dictionary<Class, List<Student>>();
foreach (DataRow dr in table.Rows)
{
// If it's possible to get null data from the DB the appropriate null checks
// should also be performed here
// Also depending on actual data types in your DB the code should be adjusted as appropriate
Class key = new Class {Id = (int) dr["ClassID"], Name = (string) dr["ClassName"]};
Student value = new Student { Id = (int)dr["StudentID"], Name = (string)dr["StudentName"] };
if (!d.ContainsKey(key))
{
d.Add(key, new List<Student>());
}
d[key].Add(value);
}
foreach (var s in d.Keys)
{
foreach (var l in d[s])
{
Console.Write(s.Id + "-" + s.Name + "-" + l.Id + "-" + l.Name + "\n");
}
}
}
// You don't need this just use your datatable whereever you obtain it from
private static DataTable GetData()
{
DataTable table = new DataTable();
table.Columns.Add("ClassID", typeof (int));
table.Columns.Add("ClassName", typeof (string));
table.Columns.Add("StudentID", typeof (int));
table.Columns.Add("StudentName", typeof (string));
table.Rows.Add(1, "A", 1000, "student666");
table.Rows.Add(2, "B", 1100, "student111");
table.Rows.Add(5, "C", 1500, "student777");
table.Rows.Add(1, "A", 1200, "student222");
table.Rows.Add(2, "B", 1080, "student999");
return table;
}
}
}
Note, that this can be compiled and tested as a console application - I substituted your Response.Write with Console.Write. I'm also generating a test DataTable, you should be able to use one that is already present in your application. As far as Class/Student classes go, you have several options here: you can have two separate classes as I show, you can use the same class or you can even use a Tuple class. I suggest you use two separate classes, as it improves readability and maintainability.
Note if you just need to output them, you don't need a dictionary or anything to that effect:
// Add null checks and type conversions as appropriate
foreach (DataRow dr in table.Rows)
{
Response.Write(dr["ClassID"] + "-" + dr["ClassName"] + "-" + dr["StudentID"] + "-" + dr["StudentName"] + "<br>");
}