Closed tangkhaiphuong-colpal closed 2 years ago
i agree, supporting result like this would be helpful:
I'm not planning to implement this myself, but I'd consider accepting a PR that adds this feature.
Here is a prototype of what it might look like: https://github.com/huysentruitw/SapNwRfc/compare/master...campersau:dynamic
It only reads the values from RFC when they are accessed with no internal caching which requires that all the data is read before the function gets disposed.
Structure fields and function parameters can be accessed:
result.USER_NAME
result["USER_NAME"]
result[7]
It is also possible to iterate over all fields / parameters: foreach (var kv in result) Console.WriteLine(kv.Key + ": " + kv.Value)
They can also be casted to a IReadOnlyDictionary<string, object>
which gives access to Keys
/ Values
/ Count
/ ContainsKey
/ TryGetValue
.
using (var fn = connection.CreateFunction("USER_NAME_GET"))
{
var result = fn.Invoke<dynamic>();
var username = result.USER_NAME;
var sylangu = result["SYLANGU"];
var sysid = result[6];
foreach (var kv in result)
{
var key = kv.Key;
var value = kv.Value;
}
var dict = (IReadOnlyDictionary<string, object>)result;
}
Table rows can be accessed with (u)int indexer like table[2]
.
There is also a special table.Count
/ table["Count"]
member which returns the row count.
It is also possible to iterate over all rows: foreach (var row in table) Console.WriteLine(row.PROP)
They can also be casted to a IReadOnlyList<object>
.
using (var fn = connection.CreateFunction("GET_TABLE"))
{
var result = fn.Invoke<dynamic>();
var rowCount = result.TABLE.Count;
var row6 = result.TABLE[5];
foreach (var row in result.TABLE)
{
var prop = row.PROP;
}
var list = (IReadOnlyList<object>)result.TABLE;
}
If you want to access the data at a later point you could materialize it manually with a little helper method before the function gets disposed:
dynamic response;
using (var fn = connection.CreateFunction("GET_TABLE"))
{
var result = fn.Invoke<dynamic>();
response = Materialize(result);
}
var prop = response.TABLE[4]["PROP"];
object Materialize(object o)
{
if (o is IReadOnlyDictionary<string, object> dict)
{
var newDict = (IDictionary<string, object>)new ExpandoObject();
foreach (var kv in dict)
{
newDict.Add(kv.Key, Materialize(kv.Value));
}
return newDict;
}
else if (o is IReadOnlyList<object> list)
{
var newList = new List<object>(list.Count);
foreach (var value in list)
{
newList.Add(Materialize(value));
}
return newList;
}
return o;
}
Would this be helpful for your szenarios?
Still needs some tests :)
Anonymous types are already supported as arguments e.g. you can do:
using (var fn = connection.CreateFunction("MY_SAP_FUNC"))
{
fn.Invoke(new
{
PARAM = "Hello",
OBJECT = new
{
PROP = "WORLD"
}
});
}
Any updates on this?
And is there a way to call a SAP function with dynamic parameters (e.g. an ExpandoObject)? Anonymous arguments as mentioned by @campersau are nice, but sometimes I don't know the property names until runtime.
Due to a recent project I extended this library to work with a set of recursive parameters for input and output. Meaning you don't need to know and create the parameter types at compilation time and can have a more generic interface. The structure for input and output parameters (incl. tables) is automatically generated when you create the type SapParameterFunction. You can then query it and set or get the values. Due to its recursive nature and some C# language restrictions it may be a little rough on the edges but it works just fine. Is this of any interest for the project and this issue?
private static bool CallBapiTransactionCommit(SapConnection connection)
{
var function = new SapParameterFunction(connection, "BAPI_TRANSACTION_COMMIT");
var waitParameterName = "WAIT";
if (function.Definition.ImportParameters.TryGetValue(waitParameterName, out var waitParameterDefinition) && waitParameterDefinition.RfcType == SapRfcType.RFCTYPE_CHAR)
{
function.ImportParameters.SetParameterValue(waitParameterName, "X");
}
var result = function.Call();
if (!result.Successful)
return false;
if (!(result.ExportParameters.GetParameter("RETURN") is SapParameterStructure returnStructure))
return true;
return returnStructure.Value.Find(x => x.Name == "TYPE")?.Value != "E";
}
interface ISapParameterDefinition
{
string Name { get; set; }
object Value { get; set; }
int? BufferLength { get; set; }
SapRfcType RfcType { get; set; }
SapRfcDirection Direction { get; set; }
uint NucLength { get; set; }
uint NucOffset { get; set; }
uint Decimals { get; set; }
uint UcLength { get; set; }
uint UcOffset { get; set; }
string Description { get; set; }
string DefaultValue { get; set; }
bool IsOptional { get; set; }
ISapParameter Create();
}
interface ISapParameterDefinition<TValue, TCreate> : ISapParameterDefinition
{
new TValue Value { get; set; }
new TCreate Create();
}
interface ISapParameterDefinitionTable : ISapParameterDefinition<ISapParameterDefinitionStructure, ISapParameterTable>
{
}
interface ISapParameterDefinitionStructure : ISapParameterDefinition<List<ISapParameterDefinition>, ISapParameterStructure>
{
}
interface ISapParameterDefinitionField<TValue, TCreate> : ISapParameterDefinition<TValue, TCreate>
{
}
interface ISapParameter
{
string Name { get; set; }
object Value { get; set; }
object DefaultValue { get; }
bool HasValue { get; }
ISapParameterDefinition Definition { get; set; }
ISapParameter Copy(bool clearFieldValues);
void ClearValue();
bool TrySetValue(object value);
}
interface ISapParameter<TValue, TCopy> : ISapParameter
{
new TValue Value { get; set; }
new TValue DefaultValue { get; }
new TCopy Copy(bool clearFieldValues);
}
interface ISapParameterTable : ISapParameter<List<ISapParameterStructure>, ISapParameterTable>
{
ISapParameterDefinitionStructure RowDefinition { get; set; }
List<ISapParameterStructure> CopyRows(bool clearFieldValues);
void AddRow(ISapParameterStructure row);
void AddRows(IEnumerable<ISapParameterStructure> rows);
ISapParameterStructure CreateAndAddRow();
}
interface ISapParameterStructure : ISapParameter<List<ISapParameter>, ISapParameterStructure>
{
List<ISapParameter> CopyFields(bool clearFieldValues);
void AddField(ISapParameter field);
void AddFields(IEnumerable<ISapParameter> fields);
void AddFieldIfInDefinition(ISapParameter field);
void AddFieldsIfInDefinition(IEnumerable<ISapParameter> fields);
void SetFieldValue(ISapParameter field);
void SetFieldValues(IEnumerable<ISapParameter> fields);
}
interface ISapParameterField<TValue, TCopy> : ISapParameter<TValue, TCopy>
{
}
class SapParameterFieldString {}
class SapParameterFieldBytes {}
class SapParameterFieldInt {}
class SapParameterFieldNullableInt {}
class SapParameterFieldLong {}
class SapParameterFieldNullableLong {}
class SapParameterFieldDouble {}
class SapParameterFieldNullableDouble {}
class SapParameterFieldDecimal {}
class SapParameterFieldNullableDecimal {}
class SapParameterFieldBytes {}
class SapParameterFieldChars {}
class SapParameterFieldDateTime {}
class SapParameterFieldNullableDateTime {}
class SapParameterFieldTimeSpan {}
class SapParameterFieldNullableTimeSpan {}
I also have the same problem, hoping to use dynamic parameters instead of classes. Can you share more detailed methods? thanks @SwissEngineer
I wan to use this library binding to V8 Script Engine (java-script) with Mircrosoft ClearScript. So how to do that?