Cannot remove this column, because it is part of an expression - c#

I have a datatable and I am masking one column by adding new column name as MASKEDSSN.
It has been masked. But i need to Remove Old Column name SSN. While Removing it throws an error as
Cannot remove this column, because it is part of an expression:
MaskedSSN = 'XXX-XX-'+SUBSTRING(CONVERT(SSN, System.String),6,4).
Code:
DataTable employeeTable = new DataTable();
employeeTable.Rows.Add("123455789");
employeeTable.Rows.Add("123447789");
employeeTable.Rows.Add("823456719");
employeeTable.Columns.Add("SSN");
int index = employeeTable.Columns["SSN"].Ordinal;
employeeTable.Columns.Add("MaskedSSN", typeof(string));
employeeTable.Columns["MaskedSSN"].Expression = "'XXX-XX-
'+SUBSTRING(CONVERT(SSN, System.String),6,4)";
DataTable newsss = new DataTable();
newsss = employeeTable.Copy();
newsss.AcceptChanges();
newsss.Columns.RemoveAt(index);
newsss.Columns.Remove("SSN");

You are using Expression syntax to build MaskedSSN
employeeTable.Columns["MaskedSSN"].Expression = "'XXX-XX-
'+SUBSTRING(CONVERT(SSN, System.String),6,4)";
If you make masking manually with AddRow, you can delete old SSN Column.
Edit
Func<string, string> MaskingFnc = (string ssnParam) => string.Format("XXX-XX-{0}", ssnParam.Substring(4,6));
DataTable employeeTable = new DataTable();
employeeTable.Columns.Add("SSN")
employeeTable.Columns.Add("SSNMasked")
employeeTable.Rows.Add("123455789", MaskingFnc("123455789"));
employeeTable.Rows.Add("123447789", MaskingFnc("123447789"));
employeeTable.Rows.Add("823456719", MaskingFnc("823456719"));

Expressions, or computed columns, relies on the source column to have the data in, because it is referencing the data, it doesn't COPY the data and then mask it.
So if your original column data changes, the new column will automatically have the same value.
When you understand this dependency, you will realize that you can't just remove the source column, because if you do, you will have a null reference exception happening. Think of it as excel spreadsheet. Column A has a value, Column B uses Column A's value to determine a percentage (or show it in a currency format). If you delete Column A, Columb B will immediately get an error (#REF! or something)
Your question on CodeProject has more detail, which would be appreciated on StackOverflow too: https://www.codeproject.com/Questions/1272445/Cannot-remove-this-column-because-it-is-part-of-an
In there, you show you try to copy the table, and then remove the column from it, while still having the expression, which results in an error.
Hopefully my explanation highlights the dependency between a column and a computed column/expression.
I would suggest formatting the data in the front-end (that's why we have front-end), or, if it's part of an API, format it in the SQL output.
Any input can also be masked so that data is inserted correctly BEFORE having to read it out anywhere.
You can mask manually using string.Format (note, below might not compile, I leave it to you to figure out)
string result = string.Format("XXX-XX-{0}", SSNColumnValue.Substring(4,6));
Hope this helps guide you in the right direction

Related

Can't call DataTable.Load() when IDataReader source has a VARCHAR(256) Identity column

I'm running into some trouble trying to load data into a DataTable using an IDataReader. To keep it really simple, I just call ExecuteReader() on the command, create a DataTable, call Load() on it and feed it the object implementing IDataReader:
...
if(dataReader.HasRows)
{
DataTable tempDT = new DataTable();
tempDT.Load(dataReader);
....
}
...
This works in the vast majority of cases. However, in (rare) circumstances, I get the following exception (column name is obviously variable - in this case, it's ID):
Error - MaxLength applies to string data type only. You cannot set Column `ID` property MaxLength to be a non-negative number
I investigated the source table I was trying to load, and I suspect that the problem stems from it having a VARCHAR(256) ID column, that is a Required, Unique, Key (the issue doesn't seem to occur when the PK is a regular old int). This type of situation is really uncommon in the source data, and while it definitely isn't ideal, I can't modify the schema of the source data.
I took a look at the SchemaTable in more detail, and I am at a loss:
ColumName - ID
ColumnSize - 256
ProviderType - NVarChar
DataType - {Name = "String" FullName = "System.String"}
IsIdentity - True
IsKey - True
IsAutoIncrement - True
IsUnique - True
It just doesn't make sense to me. The source table uses unique codes as the ID, and while it isn't the way I would've designed it, it's.. fine. But I don't understand how a String/Varchar can ever be an identity, auto-increment, etc.
Unfortunately, I'm at the mercy of this source data and can't mess with it, so I'm hoping someone here might have more insight into what exactly is going on. Can anyone conceive of a way for me to Load() my DataTable without applying all the constraints from the IDataReader source data? Is there an entirely alternative approach that would avoid this problem?
Thanks for reading, thanks in advance for any assistance. It's my first question so be gentle. If there's any more information that would help, please let me know!
EDIT: Some people asked for the full code for loading the DataTable. Appended here. Should add that the CacheCommand/etc. comes in from this 'InterSystems.Data.CacheClient' assm. Kinda hoping the problem can be approached more generically. In this case, the Query string is just a 'SELECT TOP 10 *' test.
using (CacheConnection cacheConnection = new CacheConnection())
{
cacheConnection.ConnectionString = connectionString;
cacheConnection.Open();
using (CacheCommand cacheCommand = new CacheCommand(Query, cacheConnection))
{
using (CacheDataReader cacheDataReader = cacheCommand.ExecuteReader())
{
if (cacheDataReader.HasRows)
{
DataTable tempDT = new DataTable();
tempDT.Load(cacheDataReader); // Exception thrown here.
cacheConnection.Close();
return tempDT;
}
else
{
cacheConnection.Close();
return null;
}
}
}
}
EDIT 2: In case it's not clear, I'm trying to extract the entirety of a (small) table from the Cache DB into a DataTable. I normally do this by calling dataTable.Load(cacheDataReader), which works fine 99% of the time, but breaks when the source table in the Cache DB has an identity column of type VARCHAR.
Calling Load() on my DataTable object (which is empty) causes it to infer the schema based on the result set from the imported IDataReader (in this case, CacheDataReader). The problem is that the schema in the CacheDataReader specifies the data in the list above^, and DataTable doesn't seem to allow the MaxLength property, even though the type is VARCHAR/String.
SELECT TOP 10 * FROM table
WHERE IsNumeric(ColumName) = 0
This will return only data where the Primary Key is of type Int

Adding string to int32 column in datatable

I am pretty sure this is not possible but I will ask for clarification.
I am using ExcelLibrary in C# to convert a dataset into an Excel document. I recently had the requirement to add a string to the end of the excel document in every file. I simply added two new rows to the datatable (one empty and the second row displayed the string in the first cell).
I got a bug report today because in one particular excel document the first column displays the ID (something unusual in the system) and I get the error:
Input string was not in a correct format.Couldn't store <**mystring**> in id Column. Expected type is Int32.
I am pretty sure I cannot add my string to this column and I need to add it to a different column which is a nvarchar, but does anyone have any suggestions to resolve this problem?
edit: the code for anyone who may require it
DataRow dr = ds.Tables[0].NewRow();
ds.Tables[0].Rows.Add(dr);
DataRow dr2 = ds.Tables[0].NewRow();
dr2[0] = System.Configuration.ConfigurationManager.AppSettings["excelString"];
ds.Tables[0].Rows.Add(dr2);
The dataset I am working with is directly from a query in the database and the first column is an INT

Cannot fill temporary table from gridview

During a gridview => database update function, I use a column-by-column conversion to string in order to pass data back to my database as a whole command string. I'm aware that this sounds convoluted, so here is an example:
Classes in use:
InvoiceHandler.cs
Default.aspx.cs
Since this is a rather large snippet, here is a pastebin:
Default.aspx.cs: http://pastebin.com/Y3fJZ36Z
InvoiceHandler.cs: http://pastebin.com/ZsdAnDxr
At the first point of conversion (invoiceTableEdited.Columns["Column1"].ColumnName = "#K_INV";) I get a NullReferenceException error, assumedly because the method call in Default.aspx.cs
handler.invoiceTableEdited = ViewState["invoiceTable"] as DataTable;
handler.invoiceTableEdited.Rows[row.RowIndex]["K_INVOICE"] = sK_INVOICE;
appears to be having trouble.
What must I do to resolve this?
I'll be honest, it's not very clear how you're trying to create the column collection for the table 'InvoiceTableEdited'. What it looks like you're doing in OnUpdate is you are assigning a string variable to a row in the table with a given index and a column that doesn't currently exist. Your basically saying put this string into a cell with a row number of 'x' and a column name of "column1". At this point "column1" doesn't exist.
I'd create the columns you need first in InvoiceHandler.cs like this (assumes they're string):
invoiceTableEdited.Columns.Add("MyColumn1", typeof(string));
invoiceTableEdited.Columns.Add("MyColumn2", typeof(string));
Obviously if you have loads of columns and you don't care about their names then just create a loop and add them that way. This will give them the naming convention "ColumnN", where 'N' is the number. You can then assign a name to them by referencing there name ("Column1" for example) or using their index.

Convert Sorted DataView to Typed DataTable

i have a strong typed DataTable named Account wich i sorted on Account.FullName:
DataView dvAccount = new DataView(dtAccount)
dvAccount.Sort = "FullName desc";
The fullname is a generated field from my DataSet after my query, based on first name, middle, last etc. This means that sorting by SQL query is not an option unfortunately.
First i tried to get the table like this:
dtAccount = dvAccount.Table()
But this gave me the original Table the Dataview was based on. So after reading online i found out that i should have used the DataView.ToTable() function instead of the DataView.Table() function:
dtAccount = dvAccount.ToTable() as dsAccount.AccountDataTable; // returns null
dtAccount = ((dsAccount.AccountDataTable) dvAccount.ToTable()); // gives Convertion to Typed Datatable Error
Now i get the problem that my Account Table is Strong typed. so searching online tells me that i could go with the DataTable.Merge() Function or DataTable.ImportRow() for each row but these are told to be a very heavy procedures because every row gets checked on the Type. what's the best practice solution to this situation?
I just had the same issue. I used this kind of solution.
dtAccount = New dsAccount.AccountDataTable;
dtAccount.Merge(dvAccount.ToTable());
This works fine for me.
Tell me if you have a better one.

Selecting distinct records from DataTable

I've checked this question here as well as a number of other links all proposing similar solutions, but when I go distinctTable = dt.DefaultView.ToTable(true, "FILENAME"); I get an error message saying:
A field or property with the name 'LOCATION' was not found on the
selected data source.
Now, I have four columns altogether: Location, Folder, Filename, and Status. The values for Folder and Filename I select from the database, but the values for location and status are determined thru C# code. I have no idea why it's saying that the Location column isn't found because it works fine when I omit the above line.
I don't think I can use SQL because some of the records are being generated by user input (i.e. they aren't coming from the database).
I've also tried
view = new DataView(dt);
distinctTable = view.ToTable(true, "LOCATION", "FOLDER", "FILENAME", "STATUS");
or just distinctTable = view.ToTable(true, "FILENAME"); for the latter statement but this doesn't seem to do anything - it doesn't throw an exception but it doesn't eliminate duplicate records either.
What am I doing wrong?
It reads as if there are two issues:
You get an error when you databind to (I assume) your derived table;
When you try something else, you don't get an error, but you don't get the desired distinct records, either.
The code you post at the top:
distinctTable = dt.DefaultView.ToTable(true, "FILENAME");
... is going to give you a data table with exactly one column: FILENAME. So when you bind to it, you'll get an error if what you bind to is looking for a LOCATION column as well.
The code you post at the bottom:
view = new DataView(dt);
distinctTable = view.ToTable(true, "LOCATION", "FOLDER", "FILENAME", "STATUS");
... doesn't throw an error because it has LOCATION (and other columns) as part of the table, so the control you bind to is able to find all the columns.
But you say it doesn't remove duplicates. I'm wondering, when you say that you want to remove duplicates, but show code where you're only specifying one column for output, whether what you want is not to filter out exact duplicates of the entire record, but filter out records that have the same FILENAME value but different values of the other columns.
You can't (as far as I know) do that with DataView.ToTable. But you can do it with LINQ:
DataTable distinctTable = dt.AsEnumerable()
.GroupBy(r=> r.Field<string>("FILENAME"))
.Select(g=>g.First())
.CopyToDataTable();
How about using LINQ?
var items = yourdatatable.AsEnumerable().Distinct();

Categories