bgmulinari / B1SLayer

A lightweight SAP Business One Service Layer client for .NET
MIT License
122 stars 42 forks source link

For UserFieldsMD object, request can't be executed #53

Closed paullomeira closed 7 months ago

paullomeira commented 9 months ago

The UserFieldsMD GET for id can't be create because the key is composed by two fields

UserFieldsMD(TableName='@UDT01',FieldID=0)

The SLConnection.Request(string, object) doesn't treat multiple fields in key

Service Layer reference

UserFieldsMD This entity enables you to manipulate 'UserFieldsMD' and manage user-defined fields in user and system tables.

GET UserFieldsMD(id) Retrieve all or some selected properties from an instance of 'UserFieldsMD' with the given id.

Example

GET https://localhost:50000/b1s/v1/UserFieldsMD(TableName='@UDT01',FieldID=0)
GET https://localhost:50000/b1s/v1/UserFieldsMD(TableName='@UDT01',FieldID=0)?$select=Name,Type,Size
bgmulinari commented 9 months ago

Hi, @paullomeira.

The SLConnection.Request(string, object) method is there for convenience when dealing with object keys, however, in case of a composite primary key, the method SLConnection.Request(string) should be used instead.

Check the example below from the README:

// Performs a GET on /AlternateCatNum specifying the record through a composite primary key
// The result is deserialized in a dynamic object
var altCatNum = await serviceLayer
    .Request("AlternateCatNum(ItemCode='A00001',CardCode='C00001',Substitute='BP01')").GetAsync();
paullomeira commented 9 months ago

Thanks for the message, @bgmulinari

What do you think about creating a method that extends type checks to object id?

We can use something with Reflection, where it identifies the instantiated type in id and creates the string for primary key. Something like the methods below:

change SLConnection.Request


        public SLRequest Request(string resource, object id) =>
            new SLRequest(this, new FlurlRequest(ServiceLayerRoot.AppendPathSegment($"{resource}({WithComposedKey(id)})")));

new functions

        public string WithComposedKey(object id)
        {
            if (id is string) return $"'{id}'";
            if (IsNumeric(id.GetType())) return $"{id}";

            var type = id.GetType();
            var properties = type.GetProperties();

            var keyValuePairs = properties.Select(prop => $"{prop.Name}={(prop.PropertyType.IsAssignableFrom(typeof(string)) ? $"'{prop.GetValue(id)}'" : $"{ prop.GetValue(id)}")}");

            return string.Join(",", keyValuePairs);
        }
        private bool IsNumeric(Type type)
        {
            return type == typeof(byte) ||
                   type == typeof(sbyte) ||
                   type == typeof(short) ||
                   type == typeof(ushort) ||
                   type == typeof(int) ||
                   type == typeof(uint) ||
                   type == typeof(long) ||
                   type == typeof(ulong) ||
                   type == typeof(float) ||
                   type == typeof(double) ||
                   type == typeof(decimal);
        }

and the request creation:

var oUserFieldsMD = _b1SLayer.Request($"UserFieldsMD", new { TableName="@UTB", FieldID=0 }).GetAsync<UserFieldsMd>()

bgmulinari commented 9 months ago

Hi, @paullomeira.

First of all, I really appreciate suggestions on potential ways to improve B1SLayer.

I considered implementing something like this at some point, but I honestly thought it was a bit overkill for not much of a benefit in usability. For the user, they would be creating a new object just to specify the composite key, so would it really make it better or simpler than a common string interpolation? I thought it didn't so I ended up deciding against it.

// current
var oUserFieldsMD = _b1SLayer.Request($"UserFieldsMD(TableName='@UTB',FieldID=0)").GetAsync<UserFieldsMd>();

// your suggestion
var oUserFieldsMD = _b1SLayer.Request($"UserFieldsMD", new { TableName="@UTB", FieldID=0 }).GetAsync<UserFieldsMd>();

Also, I generally avoid using reflection due to performance concerns.

With that said, if this is something you find to be useful in your specific scenario, you are free to implement it as an extension method, for instance:

public static class B1SLayerExtensions
{
    public static SLRequest RequestWithCompositeKey(this SLConnection slConnection, string resource, object id)
    {
        // your logic here
    }
}

// usage
var oUserFieldsMD = _b1SLayer.RequestWithCompositeKey($"UserFieldsMD", new { TableName="@UTB", FieldID=0 }).GetAsync<UserFieldsMd>();