I'm trying to create a grid that contains all the information of a a certain ticket,that has alot of status(Rows in the table),i need to put in a grid,this is the methods i'm using:
Call the method to populate the grid:
private void AssemblyGrid()
{
//Set Grid
gridTStatus.DataSource = null;
gridTStatus.DataBind();
User loggedUser = (User)HttpContext.Current.Session[SESSIONNAME.LOGGEDUSER];
// Checa se o usuario tem permissão para utilizar essa funcionalidade
if ((loggedUser.Login.ToUpper() != ConfigurationManager.AppSettings[APPSETTINGS.SUPPORTUSERS].ToUpper()))
{
Response.Redirect("AccessDenied.aspx");
}
Clic objClic = GetClic();
Status returnStatus = new Status();
List<StatusClicComplete> lstClic = new List<StatusClicComplete>();
lstClic = ClicManager.SelectStatusClic(objClic,out returnStatus);
//Set Grid
gridTStatus.DataSource = lstClic;
gridTStatus.DataBind();
}
The method the retrieve the information from the datebase:
public List<StatusClicComplete> SelectStatusClicDB(Clic objClic , out Status returnStatus)
{
const string strStoredProcedure = "spSearchClicStatus";
List<StatusClicComplete> complete = new List<StatusClicComplete>();
try
{
Database database = DatabaseFactory.CreateDatabase(DATABASESETTINGS.CLICDB);
using (DbCommand dbCommand = database.GetStoredProcCommand(strStoredProcedure))
{
database.AddInParameter(dbCommand, "#iClic", DbType.Int32, objClic.ID);
using (IDataReader dataReader = database.ExecuteReader(dbCommand))
{
while (dataReader.Read())
{
complete.Add(new StatusClicComplete()
{
iClic = dataReader.GetInt32(Convert.ToInt32("iClic")),
iStatus = dataReader.GetInt32(Convert.ToInt32("iStatus")),
dtDateCreated = dataReader.GetDateTime(Convert.ToInt32("dtDateCreated")),
iEDV = dataReader.GetInt32(Convert.ToInt32("iEDV")),
sComments = dataReader.GetString(Convert.ToInt32("sComments"))
}
);
}
dataReader.Close();
returnStatus = StatusBuilder.BuildStatus("Success", string.Format("{0} - {1}", MethodBase.GetCurrentMethod().Name), true);
}
}
}
catch(Exception exception)
{
returnStatus = StatusBuilder.BuildStatus("Error", string.Format("{0} - {1}", MethodBase.GetCurrentMethod().Name, exception.Message), false);
}
//TODO NAV8CA - Escrever tratativa de objeto nulo
return complete;
}
The StatusClicClass ,that is the list type
namespace RB.LA.TER.CLIC.TRANSPORT
{
[Serializable]
public class StatusClicComplete
{
public int iClic { get; set; }
public int iStatus { get; set; }
public DateTime dtDateCreated { get; set; }
public int iEDV { get; set; }
public string sComments { get; set; }
}
}
And the Procedure:
PROCEDURE [dbo].[spSearchClicStatus]
#iClic int = NULL
AS
SELECT
dtDateCreated, iEDV, sComments, sDescription, SC.iStatus
FROM
T_STATUS_CLIC SC INNER JOIN T_STATUS S
ON SC.iSTATUS = S.iSTATUS
WHERE
SC.iClic = #iClic
ORDER BY
dtDateCreated
GO
I'm trying to run this code,but i'm getting a error (Input string was not in a correct format.) when it enter the complete while,someone know what i'm doing wrong?
The line dataReader.GetInt32(Convert.ToInt32("iStatus")) is wrong. It should be either
dataReader.GetInt32(4)
or
dataReader.GetInt32(dataReader.GetOrdinal("iStatus"));
In the first case, ordinal is directly specified. In the second case we calculate the ordinal using the column name. Also, you must ideally be checking for IsDBNull before using this method, otherwise you will get an InvalidCastException. So a more optimised method will be,
iClic = objClic.ID, // we already have that
iStatus = dataReader.IsDBNull(dataReader.GetOrdinal("iStatus")) ? dataReader.GetInt32(dataReader.GetOrdinal("iStatus")) : 0
P.S: Ordinal position is the position of the columns in the SELECT clause. Its a zero based index. In your select query iClic has 5th position, hence the ordinal position is given as 4.
Related
I have been binding short data to DataGridView in C# Winforms. However, I need to bind long string array with size 75 to DataGridView. My data list class consists of 6 individual variables with get and set and array of string which I have defined get and set properties. The individual variables are displayed but the array of strings is not displayed in DataGridView. In debug, I checked the data source of DataGridView and it seems ok. How can I display binded array in gridview.
Below is my source code to populate DataGridView named Logview
public void populateLogData(string path)
{
StreamReader sr = null;
BindingList<LogList> bindLogList;
BindingSource bLogsource = new BindingSource();
List<LogList> loglist = new List<LogList>();
try
{
Logview.DataSource = null;
Logview.Rows.Clear();
Logview.Columns.Clear();
Logview.AutoGenerateColumns = true;
if (File.Exists(path))
{
try
{
sr = new StreamReader(path);
StringBuilder readline = new StringBuilder(sr.ReadLine());
if (readline.ToString() != null && readline.ToString() != "")
{
readline = new StringBuilder(sr.ReadLine());
while (readline.ToString() != null && readline.ToString() != "")
{
string[] subdata = readline.ToString().Split(',');
LogList tloglist = new LogList(subdata[0], subdata[1], subdata[2], subdata[3], subdata[4], subdata[5], max_index);
for (int i = 6; i < subdata.Length; i++)
tloglist.setPartList(i-6, subdata[i]);
loglist.Add(new LogList(subdata, subdata.Length));
readline = new StringBuilder(sr.ReadLine());
}
}
bindLogList = new BindingList<LogList>(loglist);
bLogsource.DataSource = bindLogList;
Logview.AutoGenerateColumns = true;
Logview.DataSource = bindLogList;
Logview.Columns[0].Width = 140; // project name
Logview.Columns[1].Width = 140; // data/time
Logview.Columns[2].Width = 90;
Logview.Columns[3].Width = 90;
Logview.Columns[4].Width = 90;
Logview.Columns[5].Width = 90;
// max_index is set from another part of code
for(int i = 0; i <= max_index; i++)
{
int counter = 6 + i;
Logview.Columns.Add(headertext[i], headertext[i]);
Logview.Columns[counter].Width = 90;
Logview.Columns[counter].HeaderText = headertext[i];
}
}
catch (IOException io)
{
MessageBox.Show("Error: Cannot Open log file.");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
if (sr != null) sr.Close();
}
}
else
{
MessageBox.Show("Log file not found \n" + path);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
GC.Collect();
}
}
Below is LogList class
class LogList
{
const int max_size = 100;
private string[] holdList;
public string project { get; set; }
public string date_time { get; set; }
public string Qty { get; set; }
public string Pass { get; set; }
public string Fail { get; set; }
public string Result { get; set; }
public string[] partlist
{
get
{
return holdList;
}
set
{
holdList = value;
}
}
public LogList(string project, string date_time, string Qty, string Pass, string Fail, string Result, int partsize)
{
this.project = project;
this.date_time = date_time;
this.Qty = Qty;
this.Pass = Pass;
this.Fail = Fail;
this.Result = Result;
partlist = new string[partsize+1];
}
public void setPartList(int size, string getValue)
{
partlist[size] = getValue;
}
}
Project, date/time, Qty, Pass, Fail, Result is displayed. But partlist array is not displayed.
To supplement IVSoftware’s answer, below is an example using two grids in a master-detail scenario.
One issue I would have with your current approach, is that it uses an Array for the “parts list.” Currently this is a string array, and that isn’t going to work if we want to display it in a grid. Fortunately, there are a few easy ways we can get the data to display as we want.
One simple solution is to create a “wrapper” Class for the string. I will call this Class Part. I added a simple int ID property and the string PartName property. You could easily leave out the ID and have a simple string wrapper. This simple Class may look something like…
public class Part {
public int ID { get; set; }
public string PartName { get; set; }
}
This should allow the data to display correctly in the grid using just about any construct like an array, list etc.… So, we “could” change your current code to use an array of Part objects like…
Part[] Parts = new Parts[X];
And this would work, however, if we use an array and we know for sure that each LogItem may have a different number of parts in its PartsList, then we will have to manage the array sizes. So, a BindingList of Part objects will simplify this. The altered LogList (LogItem) Class is below…
public class LogItem {
public BindingList<Part> PartsList { get; set; }
public string Project { get; set; }
public string Date_Time { get; set; }
public string Qty { get; set; }
public string Pass { get; set; }
public string Fail { get; set; }
public string Result { get; set; }
public LogItem(string project, string date_Time, string qty, string pass, string fail, string result) {
Project = project;
Date_Time = date_Time;
Qty = qty;
Pass = pass;
Fail = fail;
Result = result;
PartsList = new BindingList<Part>();
}
}
So given the updated Classes, this should simplify things and we will use the same DataSource for both grids. This DataSource for the “master” grid will be a BindingList of LogItem objects. In the “detail” grid, we simply need to point it’s DataMember property to the PartsList property of the currently selected LogItem. And this would look something like…
dgvLogs.DataSource = LogsBL;
if (LogsBL.Count > 0) {
dgvParts.DataMember = "PartsList";
dgvParts.DataSource = LogsBL;
}
Below is the code to test the Classes above in a master-detail scenario with two grids. Create a new winform solution and drop two (2) DataGridViews on the form. The grid on the left is dgvLogs and the grid on the right is dgvParts.
public void populateLogData(string path) {
BindingList<LogItem> LogsBL = new BindingList<LogItem>();
string currentLine;
if (File.Exists(path)) {
try {
using (StreamReader sr = new StreamReader(path)) {
LogItem tempLogItem;
currentLine = sr.ReadLine(); // <- header row - ignoring
currentLine = sr.ReadLine();
while (currentLine != null) {
if (!string.IsNullOrEmpty(currentLine)) {
string[] splitArray = currentLine.Split(',');
if (splitArray.Length >= 6) {
tempLogItem = new LogItem(splitArray[0], splitArray[1], splitArray[2], splitArray[3], splitArray[4], splitArray[5]);
for (int i = 6; i < splitArray.Length; i++) {
tempLogItem.PartsList.Add(new Part { ID = i, PartName = splitArray[i] });
}
LogsBL.Add(tempLogItem);
}
else {
Debug.WriteLine("DataRead Error: Not enough items to make a LogItem: " + currentLine);
}
}
else {
Debug.WriteLine("DataRead Empty row");
}
currentLine = sr.ReadLine();
}
}
dgvLogs.DataSource = LogsBL;
if (LogsBL.Count > 0) {
dgvParts.DataMember = "PartsList";
dgvParts.DataSource = LogsBL;
}
}
catch (IOException io) {
MessageBox.Show("Error: Cannot Open log file.");
}
catch (Exception ex) {
MessageBox.Show(ex.Message + " Stacktrace- " + ex.StackTrace);
}
}
else {
MessageBox.Show("Log file not found \n" + path);
}
}
And some test data…
H1,h2,h3,h4,h5,h6,h7,h8
Model: LMG600N_IF_2blablas,2022-9-6,112,61,51,Fail,p1,p3,p4,p5,p6
1,2022-9-6,2112,621,251,Pass,px4,px5,px6,px1,px2,px3
data1,2022-9-7,3456,789,123,Fail,z3,z3,z4
Model: LMG600N_IF_2blablas,2022-9-6,112,61,51,Fail
Model: LMG600N_IF_2blablas,2022-9-6,112,61,51,Fail,p1,p3,p4,p5,p6,p7,p8,p99
BadData Model: LMG600N_IF_2blablas,2022-9-6,112,61
Moxxxdel: LMG600N_IF_2blablas,2022-9-6,11x2,6x1,5x1,Fail
Hope this helps and makes sense.
Your data list class consists of 6 individual variables with get and set, and an array of string. Your question is about the variables are displayed but the array of strings is not.
Here's what has worked for me (similar to the excellent suggestion by JohnG) for displaying the string array. What I'm doing here is taking a DataGridView and dropping in my main form without changing any settings (other than to Dock it). Given the default settings, the LogList class (shown here in a minimal reproducible example of 1 variable and 1 array of strings) is defined with a public string property named PartList and with this basic implementation:
class LogList
{
public LogList(string product, string[] partList)
{
Product = product;
_partList = partList;
}
public string Product { get; set; }
private string[] _partList;
public string PartList => string.Join(",", _partList);
}
To autoconfigure the DataGridView with Product and PartList columns, here is an example initializer method that sets the DataSource and adds the first three items as a test:
// Set data source property once. Clear it, Add to it, but no reason to nullify it.
BindingList<LogList> DataSource { get; } = new BindingList<LogList>();
private void InitDataGridView()
{
dataGridView1.DataSource = DataSource;
// Auto config columns by adding at least one Record.
DataSource.Add(
new LogList(
product: "LMG450",
// Four parts
partList: new string[]
{
"PCT2000",
"WCT100",
"ZEL-0812LN",
"EN61000-3-3/-11",
}
));
DataSource.Add(
new LogList(
product: "LMG600N",
// Three parts
partList: new string[]
{
"LTC2280",
"BMS6815",
"ZEL-0812LN",
}
));
DataSource.Add(
new LogList(
product: "Long Array",
// 75 parts
partList: Enumerable.Range(1, 75).Select(x => $"{ x }").ToArray()
));
// Use string indexer to access columns for formatting purposes.
dataGridView1
.Columns[nameof(LogList.Product)]
.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
dataGridView1
.Columns[nameof(LogList.PartList)]
.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
}
After running this code, the DGV looks like this:
With the mouse hovered over the item all 75 "parts" can be viewed.
One last thing - I notice you have some methods to assign a new partList[] of perhaps change an individual part at a specified index. (I didn't show them in the minimal sample but for sure you'll want things like that). You probably know this but make sure to call dataGridView1.Refresh after altering properties of an existing row/LogList object so that the view will reflect the changes.
I hope there's something here that offers a few ideas to achieve the outcome you want.
I was trying to store BinaryObjects in cache, specifically following this example. However, while the cache is getting populated, I am unable to SQL queries on the entries.
While I am getting an output of >>> BinaryObject.cs - Person: Cache Loaded. Size: 10, I am also getting warnings of the following:
[12:45:33,939][WARNING][main][GridQueryProcessor] Key-value pair is not inserted into any SQL table [cacheName=default, expValType=BinaryObject.Student, actualValType=Student]
[12:45:33,940][WARNING][main][GridQueryProcessor] ^-- Value type(s) are specified via CacheConfiguration.indexedTypes or CacheConfiguration.queryEntities
[12:45:33,947][WARNING][main][GridQueryProcessor] ^-- Make sure that same type(s) used when adding Object or BinaryObject to cache
[12:45:33,953][WARNING][main][GridQueryProcessor] ^-- Otherwise, entries will be stored in cache, but not appear as SQL Table rows
And this is definitely the reason why I don't see an output on my SQL query:
>>> All person names:
I have the following code:
Program.cs
namespace BinaryObject
{
class Program
{
static void Main(string[] args)
{
var cfg = new IgniteConfiguration
{
// TCPDiscoverySpi Configuration
};
using (IIgnite ignite = Ignition.Start(cfg))
{
// Establish database connection; query database
DBConnection dbc = new DBConnection();
OracleConnection con = dbc.EstablishConnection();
OracleCommand cmd = dbc.CreateCommand(con, "SELECT * FROM STUDENT WHERE ROWNUM <= 10");
OracleDataReader reader = dbc.DatabaseReader(cmd);
var cache0 = ignite.GetOrCreateCache<object, object>(new CacheConfiguration
{
Name = "default",
Backups = 1,
QueryEntities = new[]
{
new QueryEntity(typeof(int), typeof(Student))
}
});
var cache = cache0.WithKeepBinary<int, IBinaryObject>();
cache.Clear();
IBinary binary = cache.Ignite.GetBinary();
while (reader.Read())
{
Student.SetStudentObject(reader, binary, cache);
}
Console.WriteLine();
Console.WriteLine(">>> Binary Object - Student: Cache Loaded. Size: " + cache.GetSize() );
Console.WriteLine();
SqlQueryExample(cache);
}
}
private static void SqlQueryExample(ICache<int, IBinaryObject> cache)
{
var qry = cache.Query(new SqlFieldsQuery("select FirstName from Student"));
Console.WriteLine();
Console.WriteLine(">>> All student names:");
foreach (var row in qry)
Console.WriteLine(">>> " + row[0]);
}
}
}
Student.cs
namespace BinaryObject
{
class Student
{
[QuerySqlField(IsIndexed = true)]
public int StudentId { get; set; }
[QuerySqlField]
public string FirstName { get; set; }
[QuerySqlField]
public string LastName { get; set; }
[QuerySqlField]
public string Major { get; set; }
public void SetStudentObject(OracleDataReader reader)
{
cache[reader.IsDBNull(0) ? 0 : reader.GetInt32(0)] = binary.GetBuilder("Student")
.SetField("Student", reader.IsDBNull(0) ? 0 : reader.GetInt32(0))
.SetField("FirstName", reader.IsDBNull(5) ? "" : reader.GetString(5))
.SetField("LastName", reader.IsDBNull(2) ? "" : reader.GetString(2))
.SetField("Major", reader.IsDBNull(1) ? "" : reader.GetString(1))
.Build();
}
}
}
See the error message: expValType=BinaryObject.Student, actualValType=Student. You have to use the full type name when creating a binary object.
Replace binary.GetBuilder("Student") with binary.GetBuilder("BinaryObject.Student").
I wish to update data found in three related tables in the database. I'm actually sending all the needed data to the database, but can't succeed in updating them. I get the [DbEntityValidationException: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.] SQL Exception.
Below is what I actually have
ViewModel ChauffeurVM:
public class ChauffeurVM
{
public int ChauffeurId { get; set; }
public virtual PERSONNE Personne { get; set; }
public virtual PIECEIDENTITE PieceIdentite { get; set; }
public string NumeroPermis { get; set; }
}
Controller:
public ActionResult ModifierChauffeur(ChauffeurVM ChauffeurVM, HttpPostedFileBase postedPhoto, string Sexe)
{
CHAUFFEUR chauffeur = new CHAUFFEUR();
ChauffeurVM.Personne.Sexe = Sexe;
using (IDAL dal = new Dal())
{
ChauffeurVM.Personne.Photo = dal.UploadandGetImagePath(postedPhoto);
chauffeur.ChauffeurId = dal.UpdateChauffeur(ChauffeurVM);
return RedirectToAction("ListeChauffeur");
}
}
Dal method:
public int UpdateChauffeur(ChauffeurVM chauffeur)
{
CHAUFFEUR c = new CHAUFFEUR();
try
{
c = ChauffeurParId(chauffeur.ChauffeurId);
c.NumeroPermis = chauffeur.NumeroPermis;
bdd.Entry(c).State = EntityState.Modified;
bdd.SaveChanges();
}
catch
{
throw;
}
//Try to assign the value chauffeur.Personne.PersonneId to the pId
int pId = chauffeur.Personne.PersonneId;
c.Personne = new PERSONNE();
PERSONNE p = detailsChauffeurparPersonneId(chauffeur.Personne.PersonneId);
try
{
if (p != null)
{
p.Nom = chauffeur.Personne.Nom;
p.Prenom = chauffeur.Personne.Prenom;
p.Sexe = chauffeur.Personne.Sexe;
p.Telephone = chauffeur.Personne.Telephone;
p.Photo = chauffeur.Personne.Photo;
p.LieuNaissance = chauffeur.Personne.LieuNaissance;
p.DateNaissance = chauffeur.Personne.DateNaissance;
p.CodePostal = chauffeur.Personne.CodePostal;
p.Adresse = chauffeur.Personne.Adresse;
p.Email = chauffeur.Personne.Email;
p.AdresseBoulot = chauffeur.Personne.AdresseBoulot;
p.AdresseDomicile = chauffeur.Personne.AdresseDomicile;
p.PersonneId = chauffeur.Personne.PersonneId;
bdd.Entry(p).State = EntityState.Modified;
bdd.SaveChanges();
}
else
{
}
}
catch
{
throw;
}
try
{
PIECEIDENTITE pi = detailsPieceIdentiteparPersonneId(chauffeur.Personne.PersonneId);
pi.NumeroPiece = chauffeur.NumeroPiece;
pi.LieuDelivrance = chauffeur.LieuDelivrance;
pi.DateDelivrance = chauffeur.DateDelivrance;
pi.DateExpiration = chauffeur.DateExpiration;
pi.Autorite = chauffeur.Autorite;
bdd.Entry(pi).State = EntityState.Modified;
bdd.SaveChanges();
}
catch
{
throw;
}
return c.ChauffeurId;
}
I expect to update the data in the database. But I get the following exception : [DbEntityValidationException: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.]
When I add breakpoints, I succeed in seing all the data send from the form. I can't figure out which field has a null value.
Kindly help, please!
You have a DbEntityValidationException that should point you to the member causing this issue. You should catch the DbEntityValidationException as follows to get the validation messages. Look in the EntityValidationErrors list for further information.
try
{
}
catch(DbEntityValidationException ex)
{
var firstErrorMessage = ex.EntityValidationErrors.First().ValidationErrors.First()
.ErrorMessage;
}
I have a requirement where user will be uploading a csv file in the below format which will contain around 1.8 to 2 million records
SITE_ID,HOUSE,STREET,CITY,STATE,ZIP,APARTMENT
44,545395,PORT ROYAL,CORPUS CHRISTI,TX,78418,2
44,608646,TEXAS AVE,ODESSA,TX,79762,
44,487460,EVERHART RD,CORPUS CHRISTI,TX,78413,
44,275543,EDWARD GARY,SAN MARCOS,TX,78666,4
44,136811,MAGNOLIA AVE,SAN ANTONIO,TX,78212
What i have to do is, first validate the file and then save it in database iff its validated successfully and has no errors. The validations that i have to apply are different for each column. For example,
SITE_ID: it can only be an integer and it is required.
HOUSE: integer, required
STREET: alphanumeric, required
CITY: alphabets only, required
State: 2 alphabets only, required
zip: 5 digits only, required
APARTMENT: integer only, optional
I need a generic way of applying these validations to respective columns. What i have tried so far is that i converted the csv file to dataTable and i plan to try and validate each cell through regex but this doesn't seem like a generic or good solution to me. Can anyone help me in this regard and point me to the right direction?
Here is one efficient method :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.OleDb;
using System.Text.RegularExpressions;
using System.IO;
namespace ConsoleApplication23
{
class Program
{
const string FILENAME = #"c:\temp\test.csv";
static void Main(string[] args)
{
CSVReader csvReader = new CSVReader();
DataSet ds = csvReader.ReadCSVFile(FILENAME, true);
RegexCompare compare = new RegexCompare();
DataTable errors = compare.Get_Error_Rows(ds.Tables[0]);
}
}
class RegexCompare
{
public static Dictionary<string,RegexCompare> dict = new Dictionary<string,RegexCompare>() {
{ "SITE_ID", new RegexCompare() { columnName = "SITE_ID", pattern = #"[^\d]+", positveNegative = false, required = true}},
{ "HOUSE", new RegexCompare() { columnName = "HOUSE", pattern = #"[^\d]+", positveNegative = false, required = true}},
{ "STREET", new RegexCompare() { columnName = "STREET", pattern = #"[A-Za-z0-9 ]+", positveNegative = true, required = true}},
{ "CITY", new RegexCompare() { columnName = "CITY", pattern = #"[A-Za-z ]+", positveNegative = true, required = true}},
{ "STATE", new RegexCompare() { columnName = "STATE", pattern = #"[A-Za-z]{2}", positveNegative = true, required = true}},
{ "ZIP", new RegexCompare() { columnName = "ZIP", pattern = #"\d{5}", positveNegative = true, required = true}},
{ "APARTMENT", new RegexCompare() { columnName = "APARTMENT", pattern = #"\d*", positveNegative = true, required = false}},
};
string columnName { get; set;}
string pattern { get; set; }
Boolean positveNegative { get; set; }
Boolean required { get; set; }
public DataTable Get_Error_Rows(DataTable dt)
{
DataTable dtError = null;
foreach (DataRow row in dt.AsEnumerable())
{
Boolean error = false;
foreach (DataColumn col in dt.Columns)
{
RegexCompare regexCompare = dict[col.ColumnName];
object colValue = row.Field<object>(col.ColumnName);
if (regexCompare.required)
{
if (colValue == null)
{
error = true;
break;
}
}
else
{
if (colValue == null)
continue;
}
string colValueStr = colValue.ToString();
Match match = Regex.Match(colValueStr, regexCompare.pattern);
if (regexCompare.positveNegative)
{
if (!match.Success)
{
error = true;
break;
}
if (colValueStr.Length != match.Value.Length)
{
error = true;
break;
}
}
else
{
if (match.Success)
{
error = true;
break;
}
}
}
if(error)
{
if (dtError == null) dtError = dt.Clone();
dtError.Rows.Add(row.ItemArray);
}
}
return dtError;
}
}
public class CSVReader
{
public DataSet ReadCSVFile(string fullPath, bool headerRow)
{
string path = fullPath.Substring(0, fullPath.LastIndexOf("\\") + 1);
string filename = fullPath.Substring(fullPath.LastIndexOf("\\") + 1);
DataSet ds = new DataSet();
try
{
if (File.Exists(fullPath))
{
string ConStr = string.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}" + ";Extended Properties=\"Text;HDR={1};FMT=Delimited\\\"", path, headerRow ? "Yes" : "No");
string SQL = string.Format("SELECT * FROM {0}", filename);
OleDbDataAdapter adapter = new OleDbDataAdapter(SQL, ConStr);
adapter.Fill(ds, "TextFile");
ds.Tables[0].TableName = "Table1";
}
foreach (DataColumn col in ds.Tables["Table1"].Columns)
{
col.ColumnName = col.ColumnName.Replace(" ", "_");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return ds;
}
}
}
Here's a rather overengineered but really fun generic method, where you give attributes to your class to match them to CSV column headers:
First step is to parse your CSV. There are a variety of methods out there, but my favourite is the TextFieldParser that can be found in the Microsoft.VisualBasic.FileIO namespace. The advantage of using this is that it's 100% native; all you need to do is add Microsoft.VisualBasic to the references.
Having done that, you have the data as List<String[]>. Now, things get interesting. See, now we can create a custom attribute and add it to our class properties:
The attribute class:
[AttributeUsage(AttributeTargets.Property)]
public sealed class CsvColumnAttribute : System.Attribute
{
public String Name { get; private set; }
public Regex ValidationRegex { get; private set; }
public CsvColumnAttribute(String name) : this(name, null) { }
public CsvColumnAttribute(String name, String validationRegex)
{
this.Name = name;
this.ValidationRegex = new Regex(validationRegex ?? "^.*$");
}
}
The data class:
public class AddressInfo
{
[CsvColumnAttribute("SITE_ID", "^\\d+$")]
public Int32 SiteId { get; set; }
[CsvColumnAttribute("HOUSE", "^\\d+$")]
public Int32 House { get; set; }
[CsvColumnAttribute("STREET", "^[a-zA-Z0-9- ]+$")]
public String Street { get; set; }
[CsvColumnAttribute("CITY", "^[a-zA-Z0-9- ]+$")]
public String City { get; set; }
[CsvColumnAttribute("STATE", "^[a-zA-Z]{2}$")]
public String State { get; set; }
[CsvColumnAttribute("ZIP", "^\\d{1,5}$")]
public Int32 Zip { get; set; }
[CsvColumnAttribute("APARTMENT", "^\\d*$")]
public Int32? Apartment { get; set; }
}
As you see, what I did here was link every property to a CSV column name, and give it a regex to validate the contents. On non-required stuff, you can still do regexes, but ones that allow empty values, as shown in the Apartment one.
Now, to actually match the columns to the CSV headers, we need to get the properties of the AddressInfo class, check for each property whether it has a CsvColumnAttribute, and if it does, match its name to the column headers of the CSV file data. Once we have that, we got a list of PropertyInfo objects, which can be used to dynamically fill in the properties of new objects created for all rows.
This method is completely generic, allows giving the columns in any order in the CSV file, and parsing will work for any class once you assign the CsvColumnAttribute to the properties you want to fill in. It will automatically validate the data, and you can handle failures however you want. In this code, all I do is skip invalid lines, though.
public static List<T> ParseCsvInfo<T>(List<String[]> split) where T : new()
{
// No template row, or only a template row but no data. Abort.
if (split.Count < 2)
return new List<T>();
String[] templateRow = split[0];
// Create a dictionary of rows and their index in the file data.
Dictionary<String, Int32> columnIndexing = new Dictionary<String, Int32>();
for (Int32 i = 0; i < templateRow.Length; i++)
{
// ToUpperInvariant is optional, of course. You could have case sensitive headers.
String colHeader = templateRow[i].Trim().ToUpperInvariant();
if (!columnIndexing.ContainsKey(colHeader))
columnIndexing.Add(colHeader, i);
}
// Prepare the arrays of property parse info. We set the length
// so the highest found column index exists in it.
Int32 numCols = columnIndexing.Values.Max() + 1;
// Actual property to fill in
PropertyInfo[] properties = new PropertyInfo[numCols];
// Regex to validate the string before parsing
Regex[] propValidators = new Regex[numCols];
// Type converters for automatic parsing
TypeConverter[] propconverters = new TypeConverter[numCols];
// go over the properties of the given type, see which ones have a
// CsvColumnAttribute, and put these in the list at their CSV index.
foreach (PropertyInfo p in typeof(T).GetProperties())
{
object[] attrs = p.GetCustomAttributes(true);
foreach (Object attr in attrs)
{
CsvColumnAttribute csvAttr = attr as CsvColumnAttribute;
if (csvAttr == null)
continue;
Int32 index;
if (!columnIndexing.TryGetValue(csvAttr.Name.ToUpperInvariant(), out index))
{
// If no valid column is found, and the regex for this property
// does not allow an empty value, then all lines are invalid.
if (!csvAttr.ValidationRegex.IsMatch(String.Empty))
return new List<T>();
// No valid column found: ignore this property.
break;
}
properties[index] = p;
propValidators[index] = csvAttr.ValidationRegex;
// Automatic type converter. This function could be enhanced by giving a
// list of custom converters as extra argument and checking those first.
propconverters[index] = TypeDescriptor.GetConverter(p.PropertyType);
break; // Only handle one CsvColumnAttribute per property.
}
}
List<T> objList = new List<T>();
// start from 1 since the first line is the template with the column names
for (Int32 i = 1; i < split.Count; i++)
{
Boolean abortLine = false;
String[] line = split[i];
// make new object of the given type
T obj = new T();
for (Int32 col = 0; col < properties.Length; col++)
{
// It is possible a line is not long enough to contain all columns.
String curVal = col < line.Length ? line[col] : String.Empty;
PropertyInfo prop = properties[col];
// this can be null if the column was not found but wasn't required.
if (prop == null)
continue;
// check validity. Abort buildup of this object if not valid.
Boolean valid = propValidators[col].IsMatch(curVal);
if (!valid)
{
// Add logging here? We have the line and column index.
abortLine = true;
break;
}
// Automated parsing. Always use nullable types for nullable properties.
Object value = propconverters[col].ConvertFromString(curVal);
prop.SetValue(obj, value, null);
}
if (!abortLine)
objList.Add(obj);
}
return objList;
}
To use on your CSV file, simply do
// the function using VB's TextFieldParser
List<String[]> splitData = SplitFile(datafile, new UTF8Encoding(false), ',');
// The above function, applied to the AddressInfo class
List<AddressInfo> addresses = ParseCsvInfo<AddressInfo>(splitData);
And that's it. Automatic parsing and validation, all through some added attributes on the class properties.
Note, if splitting the data in advance would give too much of a performance hit for large data, that's not really a problem; the TextFieldParser works from a Stream wrapped in a TextReader, so instead of giving a List<String[]> you can just give a stream and do the csv parsing on the fly inside the ParseCsvInfo function, simply reading per CSV line directly from the TextFieldParser.
I didn't do that here because the original use case for csv reading for which I wrote the reader to List<String[]> included automatic encoding detection, which required reading the whole file anyway.
I would suggest to using a CSV-library to read the file.
For example you can use LumenWorksCsvReader: https://www.nuget.org/packages/LumenWorksCsvReader
Your approach with an regex validation is actually ok.
For example, you could create a "Validation Dictionary" and check every CSV Value against the regex-expression.
Then you can build a function that can validate a CSV-File with such a "Validation Dictionary".
See here:
string lsInput = #"SITE_ID,HOUSE,STREET,CITY,STATE,ZIP,APARTMENT
44,545395,PORT ROYAL,CORPUS CHRISTI,TX,78418,2
44,608646,TEXAS AVE,ODESSA,TX,79762,
44,487460,EVERHART RD,CORPUS CHRISTI,TX,78413,
44,275543,EDWARD GARY,SAN MARCOS,TX,78666,4
44,136811,MAGNOLIA AVE,SAN ANTONIO,TX,78212";
Dictionary<string, string> loValidations = new Dictionary<string, string>();
loValidations.Add("SITE_ID", #"^\d+$"); //it can only be an integer and it is required.
//....
bool lbValid = true;
using (CsvReader loCsvReader = new CsvReader(new StringReader(lsInput), true, ','))
{
while (loCsvReader.ReadNextRecord())
{
foreach (var loValidationEntry in loValidations)
{
if (!Regex.IsMatch(loCsvReader[loValidationEntry.Key], loValidationEntry.Value))
{
lbValid = false;
break;
}
}
if (!lbValid)
break;
}
}
Console.WriteLine($"Valid: {lbValid}");
Here is another way to accomplish your needs using Cinchoo ETL - an open source file helper library.
First define a POCO class with DataAnnonations validation attributes as below
public class Site
{
[Required(ErrorMessage = "SiteID can't be null")]
public int SiteID { get; set; }
[Required]
public int House { get; set; }
[Required]
public string Street { get; set; }
[Required]
[RegularExpression("^[a-zA-Z][a-zA-Z ]*$")]
public string City { get; set; }
[Required(ErrorMessage = "State is required")]
[RegularExpression("^[A-Z][A-Z]$", ErrorMessage = "Incorrect zip code.")]
public string State { get; set; }
[Required]
[RegularExpression("^[0-9][0-9]*$")]
public string Zip { get; set; }
public int Apartment { get; set; }
}
then use this class with ChoCSVReader to load and check the validity of the file using Validate()/IsValid() method as below
using (var p = new ChoCSVReader<Site>("*** YOUR CSV FILE PATH ***")
.WithFirstLineHeader(true)
)
{
Exception ex;
Console.WriteLine("IsValid: " + p.IsValid(out ex));
}
Hope it helps.
Disclaimer: I'm the author of this library.
I have stored the result of a stored procedure (in Entity Framework) in an IList and then bind my grid with this IList. When this result is null the grid hasn't got any columns but I need to show these columns in the grid. Is there any way to solve this problem?
This is my code:
IList list = new ArrayList();
try
{
var context = new SabzNegar01Entities1();
list = (from p in context.tbl_ReturnSalesFactor_D
let add = (p.MainNum * p.Fee)
let pureAdd = ((p.MainNum * p.Fee) - (p.MainNum * p.Discount)) + ((p.Tax + p.Charges) * p.MainNum)
let taxChange = (p.Tax + p.Charges) * p.MainNum
let discount = p.Discount * p.MainNum
where p.DocNo == inDocNo
select new { p.Row, p.StockCode, p.tbl_Stock.PDescription, p.Fee, p.MainNum, add, taxChange, discount, pureAdd }).ToList();
}
catch (Exception ex)
{
PMessageBox.Show(ex.Message, "Error in Reading ReturnSalesFactor_Details Data");
}
and binding:
radGridView_Product.DataSource = list ;
I would do this:
define a C# class that matches the data that you're getting back from the stored procedure (e.g. SalesInfo or whatever you want to call it)
then define your IList to be a List<SalesInfo> (please don't use the crappy old ArrayList anymore!)
when you call the stored procedure, but you get no values back, you just add a dummy SalesInfo entry to your list being returned, that e.g. has no data found as its description and everything else is empty/0.0
That way, your method will always return at least one element, and since that element is there, the gridview know it's columns and what to call them
Update:
I would first define a class to hold all those properties you want to display in your gridview:
// just a data container to hold the information - call it whatever you like!
// also: with the datatypes, I am just *GUESSING* because you didn't exactly tell us
// what those values are - adapt as needed !
public class SalesInfo
{
public int Row { get; set; }
public string StockCode { get; set; }
public string Description { get; set; }
public decimal Fee { get; set; }
public decimal MainNum { get; set; }
public decimal Add { get; set; }
public decimal TaxChange { get; set; }
public decimal Discount { get; set; }
public decimal PureAdd { get; set; }
}
Next, define a method that goes and gets that data from the stored procedure - if not data is returned, add a dummy entry instead:
// Define a method to return an IList of that data container class defined above
public IList<SalesInfo> GetSalesInfo()
{
// please, as of .NET 2.0 - use the List<T> and stop using ArrayList!
IList<SalesInfo> list = new List<SalesInfo>();
try
{
// put your context usage into using()..... blocks to ensure proper disposal
using (var context = new SabzNegar01Entities1())
{
// fetch the data, turn it into SalesInfo records
list = (from p in context.tbl_ReturnSalesFactor_D
where p.DocNo == inDocNo
select new SalesInfo
{
Row = p.Row,
StockCode = p.StockCode,
Description = p.tbl_Stock.PDescription,
Fee = p.Fee,
MainNum = p.MainNum,
Add = p.MainNum*p.Fee,
PureAdd = ((p.MainNum*p.Fee) - (p.MainNum*p.Discount)) + ((p.Tax + p.Charges)*p.MainNum),
Discount = p.Discount*p.MainNum,
TaxChange = (p.Tax + p.Charges)*p.MainNum
}).ToList();
}
// if you get back no data -> add a dummy entry
if (list.Count <= 0)
{
list.Add(new SalesInfo { Description = "(no data found)" });
}
}
catch (Exception ex)
{
PMessageBox.Show(ex.Message, "Error in Reading ReturnSalesFactor_Details Data");
}
// return the resulting list
return list;
}