Using LINQ to find Excel columns that don't exist in array? - arrays

I have a solution that works for what I want, but I'm hoping to get some slick LINQ types to help me improve what I have, and learn something new in the process.
The code below is used verify that certain column names exist on a spreadsheet. I was torn between using column index values or column names to find them. They both have good and bad points, but decided to go with column names. They'll always exist, and sometimes in different order, though I'm working on this.
Details:
GetData() method returns a DataTable from the Excel spreadsheet. I cycle through all the required field names from my array, looking to see if it matches with something in the column collection on the spreadsheet. If not, then I append the missing column name to an output parameter from the method. I need both the boolean value and the missing fields variable, and I wasn't sure of a better way than using the output parameter. I then remove the last comma from the appended string for the display on the UI. If the StringBuilder object isn't null (I could have used the missingFieldCounter too) then I know there's at least one missing field, bool will be false. Otherwise, I just return output param as empty, and method as true.
So, Is there a more slick, all-in-one way to check if fields are missing, and somehow report on them?
private bool ValidateFile(out string errorFields)
{
data = GetData();
List<string> requiredNames = new [] { "Site AB#", "Site#", "Site Name", "Address", "City", "St", "Zip" }.ToList();
StringBuilder missingFields = null;
var missingFieldCounter = 0;
foreach (var name in requiredNames)
{
var foundColumn = from DataColumn c in data.Columns
where c.ColumnName == name
select c;
if (!foundColumn.Any())
{
if (missingFields == null)
missingFields = new StringBuilder();
missingFieldCounter++;
missingFields.Append(name + ",");
}
}
if (missingFields != null)
{
errorFields = missingFields.ToString().Substring(0, (missingFields.ToString().Length - 1));
return false;
}
errorFields = string.Empty;
return true;
}

Here is the linq solution that makes the same.
I call the ToArray() function to activate the linq statement
(from col in requiredNames.Except(
from dataCol in data
select dataCol.ColumnName
)
select missingFields.Append(col + ", ")
).ToArray();
errorFields = missingFields.ToString();
Console.WriteLine(errorFields);

Related

Need to optimize the code for mapping codes to description

I have a Text field that has semicolon separated codes. These code has to be replaced with the description. I have separate map that have code and description. There is a trigger that replace the code with their description. the data will loaded using the dataloader in this field. I am afraid, it might not work for large amount of data since I had to use inner for loops. Is there any way I can achieve this without inner for loops?
public static void updateStatus(Map<Id,Account> oldMap,Map < Id, Account > newMap)
{
Map<String,String> DataMap = new Map<String,String>();
List<Data_Mapper__mdt> DataMapList = [select Salseforce_Value__c,External_Value__c from Data_Mapper__mdt where
active__c = true AND Field_API_Name__c= :CUSTOMFIELD_MASSTATUS AND
Object_API_Name__c= :OBJECT_ACCOUNT];
for(Data_Mapper__mdt dataMapRec: DataMapList){
DataMap.put(dataMapRec.External_Value__c,dataMapRec.Salseforce_Value__c);
}
for(Account objAcc : newMap.values())
{
if(objAcc.Status__c != ''){
String updatedDescription='';
List<String> delimitedList = objAcc.Status__c.split('; ');
for(String Code: delimitedList) {
updatedDescription = DataMap.get(Code);
}
objAcc.Status__c = updatedDescription;
}
It should be fine. You have a map-based access acting like a dictionary, you have a query outside of the loop. Write an unit test that populates close to 200 accounts (that's how the trigger will be called in every data loader iteration). There could be some concerns if you'd have thousands of values in that Status__c but there's not much that can be done to optimise it.
But I want to ask you 3 things.
The way you wrote it the updatedDescription will always contain the last decoded value. Are you sure you didn't want to write something like updatedDescription += DataMap.get(Code) + ';'; or maybe add them to a List<String> and then call String.join on it. It looks bit weird. If you truly want first or last element - I'd add break; or really just access the last element of the split (and then you're right, you're removing the inner loop). But written like that this looks... weird.
Have you thought about multiple runs. I mean if there's a workflow rule/flow/process builder - you might enter this code again. And because you're overwriting the field I think it'll completely screw you over.
Map<String, String> mapping = new Map<String, String>{
'one' => '1',
'two' => '2',
'three' => '3',
'2' => 'lol'
};
String text = 'one;two';
List<String> temp = new List<String>();
for(String key : text.split(';')){
temp.add(mapping.get(key));
}
text = String.join(temp, ';');
System.debug(text); // "1;2"
// Oh noo, a workflow caused my code to run again.
// Or user edited the account.
temp = new List<String>();
for(String key : text.split(';')){
temp.add(mapping.get(key));
}
text = String.join(temp, ';');
System.debug(text); // "lol", some data was lost
// And again
temp = new List<String>();
for(String key : text.split(';')){
temp.add(mapping.get(key));
}
text = String.join(temp, ';');
System.debug(text); // "", empty
Are you even sure you need this code. Salesforce is perfectly fine with having separate picklist labels (what's visible to the user) and api values (what's saved to database, referenced in Apex, validation rules...). Maybe you don't need this transformation at all. Maybe your company should look into Translation Workbench. Or even ditch this code completely and do some search-replace before invoking data loader, in some real ETL tool (or even MS Excel)

Google Apps Script: how to create an array of values for a given value by reading from a two column list?

I have a set of data in a Google spreadsheet in two columns. One column is a list of article titles and the other is the ID of a hotel that is in that article. Call it list1.
Example data
I would like returned a new list with article titles in one column, and an array of the hotel IDs in that article in the other column. Call it list2.
Example data
There are thousands of lines that this needs to be done for, and so my hope was to use Google Apps Script to help perform this task. My original thinking was to
Create column 1 of list2 which has the unique article titles (no script here, just the G-sheets =unique() formula.
Iterate through the titles in list2, looking for a match in first column of the list1
If there is a match:
retrieve its corresponding value in column 2
push it to an empty array in column two of list2
move onto next row in list1
if no longer a match, loop back to step 2.
I've written the following code. I am currently getting a type error (TypeError: Cannot read property '0' of undefined (line 13, file "Code")), however, I wanted to ask whether this is even a valid approach to the problem?
function getHotelIds() {
var outputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list2');
var lastRow = outputSheet.getLastRow();
var data = outputSheet.getRange(2,1,lastRow,2).getValues();
var workingSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list1');
var lastActiveRow = workingSheet.getLastRow();
var itemIDS = [];
for (var i=1; i<=data.length; i++) {
var currentArticle = data[i][0];
var lookupArticle = workingSheet[i][0];
if (currentArticle === lookupArticle) {
var tempValue = [workingSheet[i][1]];
itemIDS.push(tempValue);
}
}
}
Use a simple google sheets formula:
You can use a very simple formula to achieve your goal instead of using long and complicated scripts.
Use =unique(list1!A2:A) in cell A2 of list2 sheet to get the unique hotels.
and then use this formula to all the unique hotels by dragging it down in column B.
=JOIN(",",filter(list1!B:B,list1!A:A=A2))
You got the idea right, but the logic needed some tweaking. The "undefined" error is caused by the workingSheet[i][0]. WorkingSheet is a Sheet object, not an array of data. Also, is not necessary to get the data from list2 (output), it is rather the opposite. You have to get the data from the list1 (source) sheet instead, and iterate over it.
I added a new variable, oldHotel, which will be used to compare each line with the current hotel. If it's different, it means we have reached a different Hotel and the data should be written in list2.
function getHotelIds() {
var outputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list2');
var outLastRow = outputSheet.getLastRow();
var workingSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list1');
var lastActiveRow = workingSheet.getLastRow();
var sourceValues = workingSheet.getRange("A2:B" + lastActiveRow).getValues();
var itemIDS = [];
var oldHotel = sourceValues[0][0]; //first hotel of the list
for (var i = 0; i < sourceValues.length; i++) {
if (sourceValues[i][0] == oldHotel) {
itemIDS.push(sourceValues[i][1]);
/*When we reach the end of the list, the oldHotel variable will never be different. So the next if condition is needed. Otherwise it wouldn't write down the last Hotel.
*/
if (i == sourceValues.length - 1) {
outputSheet.getRange(outLastRow + 1, 1, 1, 2).setValues([
[sourceValues[i][0], itemIDS.toString()]
]);
}
} else {
outputSheet.getRange(outLastRow + 1, 1, 1, 2).setValues([
[sourceValues[i - 1][0], itemIDS.toString()]
]);
oldHotel = sourceValues[i][0]; //new Hotel will be compared
outLastRow = outputSheet.getLastRow(); //lastrow has updated
itemIDS = []; //clears the array to include the next codes
}
}
}
I also converted the itemIDS array to a String each time, so it's written down in a single cell without issues.
Make sure each column of the Sheet is set to "Plain text" from Format > Number > Plain Text
References
getRange
setValues
toString()

Passing a full-text search parameter using Dapper.net

Consider this simple query which use full text searching on the Keywords field:
DECLARE #searchTerm VARCHAR(500) = 'painted'
SELECT * FROM StockCatalogueItems
WHERE (CONTAINS(KeyWords, #searchTerm))
This works as expected, but I need to do the same using a Dapper.net parameterised query. When using stored procedures, I create the full text parameter like this: "\"painted*\""
But using the same approach this doesn't work using dapper. No results are returned. This is the line in the query where I use the parameter:
AND (CONTAINS(KeyWords, #search))
and it's passed to the query like so:
return _context.Database.Connection.Query<StockProfileMatrix>(basequery, new
{
search = searchTerm
}
I can only assume that dapper is sanitising the string somehow, removing quotes perhaps?
Any ideas?
This works for me. However the tech stack am working on is .net core RTM and "Dapper": "1.50.0-rc3",
_dbConnection.QueryAsync<Guid>(#"select br.Id from Brand br where CONTAINS(br.Text,#Name)",new {Name = $"\"*{name}*\""}))
For completeness, I'll answer the question. The query syntax is correct, but the way in which the full-text parameter is created was obviously not. I created an extension method that formats the parameter:
public static string ToFullText(this string str)
{
string searchTerm = null;
if (!string.IsNullOrEmpty(str))
{
string[] keywords = str.Trim().Split(null);
foreach (var keyword in keywords)
{
searchTerm += string.Format("\"{0}*\" AND ", keyword);
}
if (searchTerm != null)
searchTerm = searchTerm.Substring(0, searchTerm.LastIndexOf(" AND "));
}
return searchTerm;
}
Then I call the method when I pass the parameter in to the dapper query:
_context.Database.Connection.Query<dynamic>(query, new
{
search = filter.SearchTerm.ToFullText()
});

Dapper Results(Dapper Row) with Bracket Notation

According to the Dapper documentation, you can get a dynamic list back from dapper using below code :
var rows = connection.Query("select 1 A, 2 B union all select 3, 4");
((int)rows[0].A)
.IsEqualTo(1);
((int)rows[0].B)
.IsEqualTo(2);
((int)rows[1].A)
.IsEqualTo(3);
((int)rows[1].B)
.IsEqualTo(4);
What is however the use of dynamic if you have to know the field names and datatypes of the fields.
If I have :
var result = Db.Query("Select * from Data.Tables");
I want to be able to do the following :
Get a list of the field names and data types returned.
Iterate over it using the field names and get back data in the following ways :
result.Fields
["Id", "Description"]
result[0].values
[1, "This is the description"]
This would allow me to get
result[0].["Id"].Value
which will give results 1 and be of type e.g. Int 32
result[0].["Id"].Type --- what datattype is the value returned
result[0].["Description"]
which will give results "This is the description" and will be of type string.
I see there is a results[0].table which has a dapperrow object with an array of the fieldnames and there is also a result.values which is an object[2] with the values in it, but it can not be accessed. If I add a watch to the drilled down column name, I can get the id. The automatically created watch is :
(new System.Collections.Generic.Mscorlib_CollectionDebugView<Dapper.SqlMapper.DapperRow>(result as System.Collections.Generic.List<Dapper.SqlMapper.DapperRow>)).Items[0].table.FieldNames[0] "Id" string
So I should be able to get result[0].Items[0].table.FieldNames[0] and get "Id" back.
You can cast each row to an IDictionary<string, object>, which should provide access to the names and the values. We don't explicitly track the types currently - we simply don't have a need to. If that isn't enough, consider using the dapper method that returns an IDataReader - this will provide access to the raw data, while still allowing convenient call / parameterization syntax.
For example:
var rows = ...
foreach(IDictionary<string, object> row in rows) {
Console.WriteLine("row:");
foreach(var pair in row) {
Console.WriteLine(" {0} = {1}", pair.Key, pair.Value);
}
}

IBATIS - Insert dynamic HashMap from Spring to Oracle

Some background on the issue. From the front end, I have two select boxes with the multiple property. One box is for approved items, the other is for ignored items. I place these into a Map with the key being the company's UID, and the value either being "Y" or "N" depending on which box the UID is in. Inserting HashMap Values to a table using ibatis provided some assistance, but the answer involved manually putting the entries, where as I am dynamically creating the map, so not sure what the keys will be. Below is the code for the Java:
// Set up the map object for the back end
Map<Integer, String> posMap = new HashMap<Integer, String>();
// Get the approved mailers
String[] mailerList = request.getParameterValues("approved");
if (mailerList != null && mailerList.length > 0)
{
for(String mailer : mailerList)
{
posMap.put(Integer.parseInt(mailer), "Y");
}
}
// reset the mailerList
mailerList = null;
// get the ignored mailers
mailerList = request.getParameterValues("ignored");
if (mailerList != null && mailerList.length > 0)
{
for(String mailer : mailerList)
{
posSampleMap.put(Integer.parseInt(mailer), "N");
}
}
// only update POS if the map is not empty
if(!posMap.isEmpty())
{
updateMapper.updatePOSSampling(posMap);
}
Normally, I would have something like this in the mapper.xml file:
<update id="updatePOSSampling" parameterType="hashmap">
UPDATE <table_name>
SET sampling_enabled = ${myMapValue}
WHERE mailer_name = ${myMapKey}
</update>
In the link I provided, they were manually putting in the keys and values, and as such, the example IBATIS could refer to the key. Since I'm not sure what my key is, what would be the best way to generate this query? I've got a temporary workaround of sending a two dimension array, but I feel using a Map DO would be a better way. Thanks in advance for any assistance.

Resources