Reporting Services - Getting 0 Values Even with Null Data - Referenced Assembly - c#

For an SSRS project 2008 3.5 Framework, I am trying to have all numeric fields return number even if no record is provided from the dataset. The following expression works if there is no record returned from the dataset:
=IIF(IsNothing(Fields!Location.Value),"0",Fields!Location.Value)
However the following call to an external assembly and a method that returns a double doesn't work if there is no record in the dataset:
=SSRSHelper.Helper.NEValue(Fields!Location.Value)
With the following C# method being called:
public static double NEValue(object val)
{
if (val != null)
{
string valStr = val.ToString();
double valDbl;
if (double.TryParse(valStr,out valDbl))
return valDbl;
}
return 0.0;
}
The method works when nothing is explicitly passed and when a valid value is passed.
Your help is greatly appreciated.
Correction: The inline expression doesn't work either. Regardless, the report requires 0 values to populate when the dataset is empty and I would prefer doing this in an external assembly so that it can be referenced by many reports.
Frank

If the data set is returning no records (rows), then you won't have any data rows displayed in your report. Depending on where you placed your code above, it probably isn't even being called. The detail data group is repeated as many times as there are records in your data set.
If you would like a row to be displayed when there are no records returned, then you need to have a row outside of the data group, such as the table header or footer, and set its' visibility to change based on the number of records in the dataset. Set visibility to something like=IIF(CountRows("Dataset1")=0,false,true)
Or am I missing something in your description?

Related

Oracle .NET appending M for ExecuteScalar int values. VS2019 reports only int in tooltip

I have an application that takes user created COUNT(*) query and does a cmd.ExecuteScalar() and notifies users based on the result.
This is utilizes both SQL and Oracle .NET adapters.
Using SQL adapters this code block runs as expected for returned int values:
var threshold = await command.ExecuteScalarAsync();
if (threshold is int?)
//Continue with int comparison logic
However, using Oracle adapters if(threshold is int?) returns false. If we look at the tooltips in VS2019 you would not know anything is wrong.
Clicking into the value you can see the actual issue is that Oracle returns an M on the value:
My issue is two-fold:
Why does .NET Oracle adapters append an M to the end of the values being returned from ExecuteScalar?
Why doesn't VS2019 show the true values in tooltips?
EDIT:
The following solves the coding issue:
var scalarResult = await cmd.ExecuteScalarAsync();
if (int.TryParse(scalarResult.ToString(), out int threshold))
Wish in the hover over tooltip it showed 0M or the datatype of the zero without having to "edit" the value to see its "true value".
The issue is exactly as #astentx stated in your question. Int is C#. You want to reference Oracle.DataAccess.dll (or ManagedDataAccess) and use the appropriate data type from OracleDbType. Since COUNT is only going to return an integer, INT32 or INT64 is probably the best option since there is no NUMBER type.
Even though the above should work, my preference is usually to pull numbers back into C# as strings to avoid the data conversion and then use MS libraries to convert them to numbers natively. This does require that you lock down your user queries to either have the user define the data type returned or you programmatically lock it as always being a certain type, such as number in this case.

How to calculate the (custom id) for a row which has not been inserted yet?

I am using the answer of this question How to automatically generate unique id in sql server to create a custom id for a table.It worked perfectly.Now I have a column which holds the values such as UID00000001 UID00000002 and so on. Suppose the last value in this column is UID00000003.Now I want to calculate the value for the row which hasn't been inserted yet via C# in one of my .aspx pages.In this case UID00000004. How can I achieve this value?
Any help would be appreciated.
Thank you.
If you are not required to generate these identifier at database level (e.g. some other processes insert records there), you can pre-generate them within your application. Something like above:
class Generator
{
public static int UniqueId = 0;
public static int GetNextId()
{
return Interlocked.Increment(ref UniqueId);
}
}
Then, your code can preallocate these identifiers and also format those strings. If multiple users access the same functionality, they will receive other identifiers. However, if one does not (successfully) performs a save operation, those identifiers will be lost.
You need to execute this query to get the next identity which will be generated for the table:
SELECT IDENT_CURRENT('table_name')+1;
For your case, it will have some other info concatenated with the next identity so the query will be like this:
SELECT 'UID' + RIGHT('00000000' + CAST(IDENT_CURRENT('table_name')+1 AS VARCHAR(8)), 8)
Of course you will need to write the C# code to send that query to the SQL Server.
Having said that, keep this in mind: When you get the value from that call and hold onto it, if during the time you are holding the value a record is inserted into that table, then the value is no longer the next value.
If you need the identiy value after a record is inserted in your application, please refer this answer.

Is it possible to pass a report dataset as a method parameter to a custom assembly in SSRS 2008 or above?

TL;DR In a SSRS 2008 report which uses a custom assembly to do some extra calculations can I pass an entire report dataset as a method parameter?
Full story
I have an SSRS report with 3 datasets, each returned from an SQL query.
(In case it makes a difference to my question they're currently shared datasets although I'm sure local would work too)
The largest and primary dataset is a list of tasks which may or may not have been completed. I have information in here such as the ID, status, create date/time, target resolution hours etc of each task.
This dataset is displayed in a tablix and is the focus of the report.
The remaining two datasets are not displayed and are for reference. One is a simple one column query which returns a list of holiday dates for the UK. The other is a small table which contains our exact business hours.
At the moment I'm able to loop through the rows in the tablix of tasks and pass multiple values from the current row to a method. This is useful if I want to do some calculations based on data found only in the current row. For example I could take the create date/time and the response target hours and the assembly would return a target date/time for the current task. Cool so far.
I want to do a more complicated version of this where I not only pass in the row data but the 2 other datasets to get my return value. This is because in reality the due date calculation is much more complex and must take into account changing business hours and holidays from the other 2 datasets.
Can I pass a dataset as a method parameter to an assembly? Something like:
=Code.MyClass.MyMethod(val1, val2, dataset1, dataset2);.
I've been unable to find much definitive information on this. Nearly all tutorials demonstrate what I'm already doing by processing single rows. I'm sure I had an MSDN article that hinted this was not possible but I've lost it (helpful I know). There's a post on the Microsoft forums where a moderator says it's not possible. The general lack of information and tutorials suggests it's not possible or I'm doing this in the wrong way.
Any suggestions?
(I have alternate solutions such as having the assembly fetch the other datasets or just writing something outside SSRS but I'm not pursuing those until I knnow whether it can be done this way).
An older topic on the MSDN forums Iterate through rows of dataset in report's custom code offers a more definitive answer as well as a potential solution to this problem.
Passing the DataSet as an object or collection is not a possibility because:
A dataset in Reporting Services is not the same type of object as an ADO.Net dataset. A report dataset is an internal object managed by the SSRS runtime (it's actually derived from a DataReader object) and not an XML structure containing datatables, etc. and cannot be passed into the report's custom code.
The only way to effectively loop through the rows of a report dataset is to call a custom function or referenced method in a report data region expression. Using this technique, it may be possible to pass all of the the row and field information into a code structure, array or collection.
The hint given in the above statement suggests passing row and field information into a code structure. A contributor to the linked MSDN topic, Migeul Catalao developed a workaround using such an approach.
A real-world scenario of it's usage with example code demonstrating Migeul Catalao's solution can be found here.
Granted, it is still more of a row-by-row approach, so I would strongly suggest moving outside of SSRS and pursue alternative solutions.
Although I've accepted the other answer due to it being clear and helpful I didn't use that solution in the end (I was too stupid to understand it) and went for something else that works.
Disclaimer: This is a horrible hack. It works absolutely great in my scenario so I though I'd share in case it was useful to somebody else. There are many pitfalls here which could most likely be worked around given time.
I ended up following the advice in the comment given by Steven White and looking into LookupSet. This function allows you to query a dataset to return matching rows and a single column of data.
It looks like this:
LookupSet(Fields!ComparisonField.Value, // The value to search for, e.g '001'.
Fields!MatchField.Value, // The column to match on in the target dataset.
Fields!MyColumn.Value, // The column that will be returned.
"MyDataSet") // The dataset to search.
This returns a string array representing the returned values.
So far so good, but I needed ALL columns and rows. This is where the dirty hack appears in the form of string concatenation:
LookupSet(0, // Dummy ID 0.
0, // Matches the dummy ID 0 so all rows are returned.
Fields!Column1.Value + "[^]" // I concatenate all of the values into
+ Fields!Column2.Value + "[^]" // one string with the separator [^]
+ Fields!.Column3.Value, // so I can split them later.
"MyDataSet") // The dataset to query
I can now pass this to my custom assembly:
=MyAssemblyNamespace.Class.Method(LookupSet(0,0,Fields!Column1.Value..., "MyDataSet"), other, parameters, here)
Now in my C# method I have a generic object which after some reflection is actually an array of strings.
Cast to something useful:
var stringList = ((IEnumerable)MyDataSetObject).Cast<string>().ToList();
Split it:
foreach (var item in stringList)
{
var columns = item.Split(new[] { "[^]" }, StringSplitOptions.None);
// columns is a string[] which holds each column value for the current row
// So columns[0] is the value for column 1 in this row
// In my case I pushed the values to a DataTable row each time and built a datatable
// which when finished represented my dataset in full with all rows and columns.
}
I hope this makes sense to anyone trying to achieve a similar result.

strongly typed dataset and custom executescalr queries returning values

I have a strongly typed dataset, i must return some scalar values : the sum of values of column, the count of records with a specified column value and so on.
I' have added some custom queries to the dataset of the kind : select sum(mycolumn) as itsname from thetable [where anothercolumn = :myparameter] (last part is optional and i'm using oracle).
I've found that some queries return generics (i.e decimal? ) while other return object. I haven't found a rule for it, some get parameters others don't.
Does anybody knows why i get this different behaviour ?
Now i'm handling every query as if it where returning object, but i'd like to know if i'm doing somenthig wrong or what's the reason of this annoying behaviour
The type of value that is computed in database query will depend upon the database (it will decide that based upon type of operands and operation). For example, if the database computes the result as say currency then corresponding .NET value will be Decimal and so on. You can use database casting/type conversion operation in the query itself to ensure particular return type for the expression.

Associative Array/Hash/Hashtable using Connector/NET

I am working with asp.NET and c#, using the MySQL's connector/NET plug-in thingy to connect to a MySQL db (no surprises there).
And that works fine, can connect, run queries etc etc all fine and dandy, but is it possible to return a Hashtable or similar of the results? Save running a describe on the same table to get the column names and use those values to create the Hash each time.
The MySQL C/C++ connector which I assume to be wrapped around C# (versus re-implemented in C#) returns a two-demential array containing the results. This is only the column and row data, not the column name. The API also returns a field (column name) value through mysql_fetch_field_direct() -- a separate function call after obtaining the query results. This too is a two-demential array. The connector itself doesn't contain API for merging the two separate results (column names + column/row data) into a hash table.
Instead of making a second query to obtain the column names, all you need to do is call mysql_fetch_field_direct() for each column as you progress through assigning values. This gives you the field name along with the data contained in that column/row. At this point it's up to the developer as to how to arrange that data such as storing it in a hash table, etc.
I use a helper function as a wrapper around query execution that stores each row in a binary tree with the column name being the key and returns a linked list of trees for me to do with what I need.
in .net you get only datatables and datasets, a datatable is made out of datarows, those are very very similar to hashtables and in most cases you can use those to achieve the tasks, but if you need hashtable you can use this code
public static Hashtable convertDataRowToHashTable(DataRow dr)
{
if (dr == null)
{
return null;
}
Hashtable ret = new Hashtable(dr.Table.Columns.Count);
for (int iColNr = 0; iColNr < dr.Table.Columns.Count; iColNr++)
{
ret[dr.Table.Columns[iColNr].ColumnName] = dr[iColNr];
}
return ret;
}
other direction (hast table to datrow) is not that easy, as datarow does not have a public constructor (by design) and you have to call newRow = myDataTable.NewRow(); to get a new instance of a row, and than you can work with row almost as with hashtable
newRow["column1"]="some value";
but if you need a new column in hashtable you will have to add column to datatable and not to data row myTable.Columns.Add("name", "type");
hope this helps

Categories

Resources