Skip to content

Latest commit

 

History

History
773 lines (583 loc) · 30.6 KB

README_TR.md

File metadata and controls

773 lines (583 loc) · 30.6 KB

license Nuget Nuget-download

Multi Build And Test ( Net5.0, Core3.1, Core2.1 )

English Documentation

SapCo2 (SAP Connector Core)

SAPCO2 en basit ve hızlı yolla SAP üzerinden veri işlemeye yarayan bir kütüphane olarak tasarlamaya çalıştık. Mümkün olduğunca basitleştirerek hızlı bir şekilde kullanıma odaklandık.

Geliştirme aşamasında daha önceden geliştirilmiş olan SapNwRfc ve NwRfcNet projelerinden referans alınmıştır. SAPCO2' yi incelemeden önce bu iki projeye göz atmanızı tavsiye ederim. Özellikle Wrapper kısımları iki projenin ortak yanlarının birleştirilmesi ile ortaya çıkmıştır.

Kütüphanemizi .Net Core, Net 5 ve .Net Framework' lerini desteklemektedir.

Yeni mimari yapıdaki bir çok noktadaki katkılarından dolayı Mahmut KOLTUK' a teşekkür ederim.

Gereksinimler

Kütüphanemiz SAP NetWeaver RFC Library 7.50 SDK içerisindeki C++ ile geliştirilmiş .dll dosyalarına ihtiyaç duyar. İlgili SDK paketinin kurulumu ve daha fazlası için ✨ SAP'nin resmi sayfasına bakabilirsiniz.

İlgili SKD yı temin ettikten sonra (Sizden bir SAP lisanlı hesabı istiyor);

  • zip doyasınızı kendinize göre bir dizine çıkarıp işletim sisteminize göre PATH (Windows), LD_LIBRARY_PATH (Linux), DYLD_LIBRARY_PATH (MacOS) ortam değişkenine çıkardığınız dosyalar içerisindeki lib dizinini gösterin.
  • ve ya direkt lib klasörünün içeriğini output dizininize kopyalayın.

Windows işletm sisteminde SDK paketini kullanabilmeniz için Visual C++ 2013 yeniden dağıtılabilir paketinin 64 bit sürümünü yüklemeniz gerekir.

MacOSX ve DYLD_LIBRARY_PATH

Eğer uygulamanın alt bir işlemde çalışıyorsa DYD_LIBRARY_PATH yönlendirmesi alt işlemde çalışmaya bilir. Bunun nedeni olarakta Apple' ın OSX'e ait eklediği çalışma zamanı korumasıdır.

Bu sorunun en basit çözümü, SAP .dll dosyalarının uygulama dizinine yerleştirmeniz olur.

Dilerseniz SAPNwRFC projesinde anlatıldığı gibi NativeLibrary.SetDllImportResolver yönetimi kullanarak dll import işlemini değiştirebilrisiniz.

Kurulum

PackageManager

Install-Package SapCo2

veya DotNet

dotnet add package SapCo2

veya Paket Referansı

<PackageReference Include="SapCo2" Version="1.2.0.1" />

Note: Microsoft.Extensions.DependencyInjection ve Microsoft.Extensions.Options bağımlılıklarına sahiptir.

Kullanım

Dokümanda anlatılan tüm kullanımlara Samples dizini altındaki projeler ile ulaşabilirsiniz.

Kullanmak için Dependency injection üzerine .AddSapCo2 ile ekliyoruz. Ekleme işleminde connection bilgisine ihtiyaç duyuyor.

Bağlantı bilgisi

Bağlantı bilgisini string olarak atayabilmek için

var connectionString = "Name=ALIAS1;AppServerHost=HOST_NAME; SystemNumber=00; User=USER; Password=PASSWORD; Client=CLIENT_CODE; Language=EN; PoolSize=100; Trace=0;";

veya Appsettings.json içersinde çoklu bir şekilde

{
  "SapCo2": {
    "DefaultServer": "ALIAS1",
    "Connections": [
      {
        "Alias": "ALIAS1",
        "ConnectionPooling": {
          "Enabled": false,
          "PoolSize": 8,
          "IdleTimeout": "00:00:30",
          "IdleDetectionInterval": "00:00:01"
        },
        "ConnectionString": "Name=ALIAS1;User=USER;Password=PASSWORD;Client=CLIENT_CODE;SystemId:xxx;Language=EN;AppServerHost=HOST_NAME;SystemNumber=00;MaxPoolSize:100;PoolSize=50;IdleTimeout:600;Trace=0;",
        "ConnectionOptions": {}
      },
      {
        "Alias": "ALIAS2",
        "ConnectionPooling": {
          "Enabled": false,
          "PoolSize": 8,
          "IdleTimeout": "00:00:30",
          "IdleDetectionInterval": "00:00:01"
        },
        "ConnectionString": "",
        "ConnectionOptions": {
          "Name": "ALIAS2",
          "User": "USER",
          "Password": "PASSWORD",
          "Client": "CLIENT_CODE",
          "SystemId": "xxx",
          "Language": "TR",
          "AppServerHost": "HOST_NAME",
          "SystemNumber": "00",
          "MaxPoolSize": "100",
          "PoolSize": "50",
          "IdleTimeout": "600",
          "Trace": "0"
        }
      }
    ]
  }
}

appsetting.json içerisinde;

  • ConnectionString veya ConnectionOptions ile connection bilgisini atayabilirsiniz.
  • ALIAS1 veya ALIAS2 şeklinde çoklu şekilde connection tanımlası yapabilirsiniz.
  • Varsayılan bağlantı için DefaultServer kısmına bağlantı tanımlarındaki Alias ı belirtmeniz yeterlidir.

Dependency Injection

Startup.cs veya Program.cs veya vb.

IConfiguration configuration = new ConfigurationBuilder()
    .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
    .AddJsonFile("appsettings.json", optional: true)
    .AddEnvironmentVariables()
    .Build();

Dependencyinjection ile IServceProvider içerisinde .AddSapCo2 fonksiyonuna IConfiguration bilgisi atayarak kullanabiliriz.

Appsettings ile IConfiguration üzerinden

var serviceCollection = new ServiceCollection();
serviceCollection.AddOptions<IConfiguration>();
serviceCollection.Configure<IOptions<IConfiguration>>(configuration);
serviceCollection.AddSapCo2(s=>s.ReadFromConfiguration(configuration));
var serviceProvider = serviceCollection.BuildServiceProvider();

Manuel olarak

var serviceCollection = new ServiceCollection()
    .AddSapCo2(s =>
            {
                s.DefaultServer = "LIVE";
                s.RfcServers = new List<RfcServer>()
                {
                    new RfcServer()
                    {
                        Alias = "LIVE",
                        ConnectionString =
                            "Name=LIVE;User=USER;Password=PASSWORD;Client=CLIENT_CODE;SystemId:xxx;Language=EN;AppServerHost=HOST_NAME;SystemNumber=00;MaxPoolSize:100;PoolSize=50;IdleTimeout:600;Trace=0;",
                        ConnectionPooling = new RfcConnectionPoolingOption() {Enabled = true, PoolSize = 10}
                    }
                };
            });
var serviceProvider = serviceCollection.BuildServiceProvider();

IRFCClient Kullanımı

SAPCO2 3 şekilde kullanılabiliyor. Table, Bapi ve RFC.

Bunlara ek olarak RFC ve BAPI için Meta Data bilgisini alacağımız bir özellikte eklendi.

RFC

Bunların en temeli RFC (Remote Functon Call)'dır. Diğer aşamalarda aslında bunun özelleşmiştrilmiş ve kolaylaştırılmış şeklidir. Sadece RFC yi kullanarak tüm işlemleri gerçekleştirebilirsiniz.

Fonksiyon çağrısı öncesi input ve output nesnelerinizi oluşturup parametre olarak geçtiğiniz taktirde yeterli olacaktır. Örnek olarak RFC için CS_BOM_EXPL_MAT_V2_RFC reçete fonksiyonunu kullandım. Bununla ilgili koşul olarak vermek istediğim parametreler için BomInputParameter sınıfını geri dönüşte almak istediğim değelerler içinde BomOutputParameter sınıfını oluşturuyorum. Bu sınıfları SAP SE37 ile ilgili import ve export, Table nesnelerinden kullanmak istediğim alanları seçerek oluşturuyorum.

Oluşturduğum sınıflarımı RfcEntityPropertAttribute attribute ile işaretleyerek SAP tarafındaki karşılığını isim olarak belirtiyoruz. İstersek ne olduğu ile ilgili açıklama da belirtebiliyoruz.

SAP tarafında olmayan ama sınıfımda yer alan propertileri RfcEntityIgnorePropertyAttribute attribute ile hariç bırakıyorum.

BomInputParameter Class
public sealed class BomInputParameter: IRfcInput
{
    [RfcEntityProperty("AUMGB")]
    public string Aumgb { get; set; }

    [RfcEntityProperty("CAPID")]
    public string Capid { get; set; }

    [RfcEntityProperty("DATUV")]
    public DateTime Datuv { get; set; }

    [RfcEntityProperty("EMENG")]
    public string Emeng { get; set; }

    [RfcEntityProperty("MKTLS")]
    public string Mktls { get; set; }

    [RfcEntityProperty("MEHRS")]
    public string Mehrs { get; set; }

    [RfcEntityProperty("STPST")]
    public string Stpst { get; set; }

    [RfcEntityProperty("SVWVO")]
    public string Svwvo { get; set; }

    [RfcEntityProperty("WERKS")]
    public string Werks { get; set; }

    [RfcEntityProperty("VRSVO")]
    public string Vrsvo { get; set; }

    [RfcEntityProperty("STLAN")]
    public string Stlan { get; set; }

    [RfcEntityProperty("STLAL")]
    public string Stlal { get; set; }

    [RfcEntityProperty("MTNRV")]
    public string Mtnrv { get; set; }
}

Koşul olarak plantcode ile üretim yeri kodunu, materialCode ile de reçetesini almak istediğim malzemeyi değişken olarak tanımlıyorum.

var inputParameter = new BomInputParameter {
    Aumgb = "0",
    Capid = "PP01",
    Datuv = DateTime.Now,
    Emeng = "1",
    Mktls = "x",
    Mehrs = "x",
    Stpst = "0",
    Svwvo = "x",
    Werks = plantCode,
    Vrsvo = "x",
    Stlal = "1",
    Stlan = "1",
    Mtnrv = materialCode
};

Geri dönüş değeleri için BomOutputParameter sınıfımızda structor ve tabloya referans veriyoruz. Tabloları [] array olarak gösteriyoruz.

public sealed class BomOutputParameter: IRfcOutput
{
    [RfcEntityProperty("STB")]
    public Stb[] StbData { get; set; }

    [RfcEntityProperty("TOPMAT")]
    public Topmat Topmat { get; set; }
}
Table Stb Class
public sealed class Stb
{
    [RfcEntityProperty("STUFE", Description = "Seviye")]
    public int Level { get; set; }

    [RfcEntityProperty("OJTXB", Description = "Nesne kısa metni (bileşen grubu)")]
    public string Name { get; set; }

    [RfcEntityProperty("OJTXP", Description = "Nesne kısa metni (kalem)")]
    public string SubItemName { get; set; }

    [RfcEntityProperty("MMEIN", Description = "Temel ölçü birimi")]
    public string Unit { get; set; }

    [RfcEntityProperty("MNGLG", Description = "Temel ölçü biriminde hesaplanan bileşen miktarı")]
    public decimal UnitAmount { get; set; }

    [RfcEntityProperty("MNGKO", Description = "Bileşen ölçü biriminde hesaplanan bileşen miktarı")]
    public decimal Amount { get; set; }

    [RfcEntityProperty("IDNRK", Description = "Ürün ağacı bileşenleri")]
    public string SubItemCode { get; set; }

    [RfcEntityProperty("MENGE", Description = "Bileşen miktarı")]
    public decimal ComponentAmount { get; set; }

    [RfcEntityProperty("STLTY", Description = "Ürün ağacı tipi")]
    public string TreeType { get; set; }

    [RfcEntityProperty("STLKN", Description = "Ürün ağacı kalemi düğüm numarası")]
    public decimal TreeNodeNumber { get; set; }

    [RfcEntityProperty("STPOZ", Description = "Dahili Sayaç")]
    public decimal InternalCounter { get; set; }

    [RfcEntityProperty("STLNR", Description = "Üst Seviye")]
    public int UpperTreeId { get; set; }

    [RfcEntityProperty("XTLNR", Description = "Alt Seviye")]
    public int SubTreeId { get; set; }

    [RfcEntityProperty("MATMK", Description = "Mal Grubu")]
    public string MaterialGroup { get; set; }
}
Struct Topmat Class
public sealed class Topmat
{
    [RfcEntityProperty("MATNR", Description = "Malzeme Tanımı")]
    public string Code { get; set; }

    [RfcEntityProperty("MAKTX", Description = "Tanım")]
    public string Definition { get; set; }
}

Bir tane IClient nesnesini dependency injectiondan alıp ExecuteRfcAsync fonksiyonunu çağırıyoruz.

using IRfcClient client = _serviceProvider.GetRequiredService<IRfcClient>();
BomOutputParameter bomResult = await client.ExecuteRfcAsync<BomInputParameter, BomOutputParameter>("CS_BOM_EXPL_MAT_V2_RFC", inputParameter);

sonuç olarak bomResult içerisinde BomOutputParameter türünden istediğimiz çıktıyı görebiliriz.

Burada bağlantı AppSettings içerisinde oluşturduğumuz DefaultAlias üzerinden yapılmaktadır. Bunu farklı bir alias üzerinden yapmak için Execute fonksiyonundan önce UseServer fonksiyonunu çağırmamız gerekmektedir.

using IRfcClient client = _serviceProvider.GetRequiredService<IRfcClient>();
client.UseServer("ALIAS_NAME");
BomOutputParameter bomResult = await client.ExecuteRfcAsync<BomInputParameter, BomOutputParameter>("CS_BOM_EXPL_MAT_V2_RFC", inputParameter);
Fonksiyonun tamamı
public BomOutputParameter GetBillOfMaterial(string materialCode, string plantCode)
{
    var inputParameter = new BomInputParameter
    {
        Aumgb = "0",
        Capid = "PP01",
        Datuv = DateTime.Now,
        Emeng = "1",
        Mktls = "x",
        Mehrs = "x",
        Stpst = "0",
        Svwvo = "x",
        Werks = plantCode,
        Vrsvo = "x",
        Stlal = "1",
        Stlan = "1",
        Mtnrv = materialCode
    };
    using IRfcClient client = _serviceProvider.GetRequiredService<IRfcClient>();
    BomOutputParameter bomResult = await client.ExecuteRfcAsync<BomInputParameter, BomOutputParameter>("CS_BOM_EXPL_MAT_V2_RFC", inputParameter);
    return bomResult;
}

Bapi

BAPI sistem tarafında oluşturulmuş ve güncelleme, silme, oluşturma gibi işlemlerde kullanılan ve her işlem sonucu için RETURN isimli bir geri dönüş değeri barındıran özel bir RFC türüdür.

Bapi çağırılarınızda işinizi kolaylaştırmak için çıktı nesnelerinizi IBapiOutput ara yüzünden veya BapiOutputBase abstrak sınıfından türetmeniz gerekmektedir. Bu işlem size çıktılarınız içinde otomatik RETURN nesnesini ekler. İşlem sonrasında bu nesnenin dönen değerlerinden MessageType değerinin Abort veya Error olması durumunda hata fırlatılır. Geri kalan işlemler RFC çağırısı ile aynıdır.

public sealed class RfcBapiOutputParameter
{
    [RfcEntityProperty("CODE")]
    public string Code { get; set; }

    [RfcEntityProperty("TYPE")]
    public string MessageType { get; set; }

    [RfcEntityProperty("MESSAGE")]
    public string Message { get; set; }

    [RfcEntityProperty("LOG_NO")]
    public string LogNo { get; set; }

    [RfcEntityProperty("LOG_MSG_NO")]
    public string LogMessageNumber { get; set; }

    [RfcEntityProperty("MESSAGE_V1")]
    public string MessageV1 { get; set; }

    [RfcEntityProperty("MESSAGE_V2")]
    public string MessageV2 { get; set; }

    [RfcEntityProperty("MESSAGE_V3")]
    public string MessageV3 { get; set; }

    [RfcEntityProperty("MESSAGE_V4")]
    public string MessageV4 { get; set; }
}

Örnek için BBP_VENDOR_GETLIST Bapisini kullanıyorum. Bu bapi içerisine aldığı Şirket koduna göre sistemdeki satıcıların kodu ve adı bilgisini dönüyor.

input parameter

public class VendorBapiInputParameter: IBapiInput
{
    [RfcEntityProperty("COMP_CODE")]
    public string CompanyCode { get; set; }
}

Output Parameter

public class VendorBapiOutputParameter:IBapiOutput
{
    [RfcEntityProperty("RETURN")]
    public RfcBapiOutputParameter BapiReturn { get; set; }

    [RfcEntityProperty("VENDOR")]
    public Vendor[] Vendors { get; set; }
}
Table Vendor Class
public class Vendor
    {
        [RfcEntityProperty("VENDOR_NO")]
        public string VendorNo { get; set; }

        [RfcEntityProperty("NAME")]
        public string Name { get; set; }
    }

IRfcBapiOutput arayüzünden türediğine ve [RfcEntityProperty("RETURN")] attribute'üne sahip olduğuna dikkat edin. Bapiyi bapi yapan arkadaş budur.

Sonrası RFC işlemi ile anı şekilde gerçekleşir. IRfcClient nesnes üzerilir ve ExecuteBapiAsync fonksiyonu çağrılarak çalıştırılır.

    using IRfcClient sapClient = _serviceProvider.GetRequiredService<IRfcClient>();
    return await sapClient.ExecuteBapiAsync<VendorBapiInputParameter, VendorBapiOutputParameter>("BBP_VENDOR_GETLIST", inputParameter);

Burada companyCode değerini üst birimden 200 olarak atıyorum.

Kodun tamamı
public VendorBapiOutputParameter GetVerdorsByCompanyCode(string companyCode)
{
    var inputParameter = new VendorBapiInputParameter
    {
        CompanyCode = companyCode
    };

    using IRfcClient sapClient = _serviceProvider.GetRequiredService<IRfcClient>();
    return await sapClient.ExecuteBapiAsync<VendorBapiInputParameter, VendorBapiOutputParameter>("BBP_VENDOR_GETLIST", inputParameter);
}

Table

Table nesnelerine direk erişimimiz olmadığı için sistem tarafından bizlere sunulan RFC_READ_TABLE fonksiyonunu kullanarak tablodan çekim işlemini gerçekleştiririz.

RFC_READ_TABLE ın standart belli kuralları vardır. O alanlara ilgili verileri atayarak tablodan veri çekim işlemini gerçekleştiririz. Buradaki tüm bu kütfetten siz kurtarıp daha basit bir kullanım için ISapTable ara yüzünden table fonksiyonumuzu üretiyoruz.

Buradaki temel RFC işleminden sonra RFC_READ_TABLE tarafından çıktı olarak sunulan DATA tablosundaki WA struct'ı sizin belirlediğiniz parçalama karakterine göre parçalanarak ilgili sınıf propertylerine atanır.

Bu işlem sırasındaki dönüşüm işlemleri RFC Map işleminden farklı olarak işlendiği için table olarak kullanacağınız nesneyi RfcEntity propertyleri ise RfcEntityProperty attribüteleri ile işaretlemeniz gerekmektedir. RrfcTable name alanı ile tabloyu işaret eder. Yine bilgi olarak bulunması için unsafe özelliği taşır. Bu genelde RFCnin SAP ın standartlarında olmayıp özel olarak üzerildiğini belirtir. Bizim sistemler için 'Z' li diye ifade edilen geliştirmeleri tanımlar. Aynı şekilde propertylerde dönüşüm için TablePropertySapType ve Length özelliklerini taşır. Bunun yanında tablo ile aynı şekilde bu özelliğin bir geliştirme olup olmadığını belirtmek için unsafe kullanılır.SubTypePropertyName ise ilk etapta çekilmeyecek ama bağlantılı tabloları gösterir. şuan için SetOption methodunu kendimiz yazarak bunları dolduruyoruz. Bu alanları virtual olarak tanımlıyoruz ki normal datamızda dikkate alınmasın.

input parameter yerine RFC_READ_TABLE ın OPTIONS tablosunun TEXT özelliğine metin olarak koşul ifadelerinin ABAP ile yazılır. Bunu ister düz metin şeklinde MATNR EQ '12344555' şeklinde metin listesi oluşturarak, istersenizde Linq ya benzeterek zincirleyebildiğimiz Abap Query ile oluşturabilirsiniz. Abap Quey ile ilgili detayları Proje Wiki kısmına ekleyeceğim. FIELDS alanları otomatik olarak oluşturdunuz çıktı sınıfındaki attrütelere göre atanmaktadır. ROWCOUNT,ROWSKIPS, ROWDATA gibi özellikler opsiyonel olup gerektiğinde ezilebilmektedir.

Fazla uzatmadan örneğe bakalım. Örnek için MARA tablosunu ve alt detaylarını (Malzeme tanımı MAKT ve Malzeme sınıfı ZMM24030) tablolarından çekeceğim.

NOT: Alt deyatlar kısmındaki Malzeme sınıfları tablosu bir geliştirme tablosu olduğu için SAP sisteminizde yer almıyorsa lütfen new MaterialQueryOptions { IncludeAll = true } ifadesini new MaterialQueryOptions { IncludeDefinition = true } ifadesi ile değiştirin. Bu sayede sadece MAKT tablosunu çekersiniz.

[RfcEntity("MARA", Description = "Material General Table")]
public class Material : ISapTable
{
   [RfcEntityProperty("MATNR", Description = "Material Code", SapDataType = RfcDataTypes.CHAR, Length = 18)]
   public string Code { get; set; }

   [RfcEntityProperty("MATNR", Description = "Material Definition", SapDataType = RfcDataTypes.CHAR, Length = 18, SubTypePropertyName = "Code")]
   public virtual MaterialDefinition Definition { get; set; }

   [RfcEntityProperty("ZZEXTWG", Description = "Material Category Code", SapDataType = RfcDataTypes.CHAR, Length = 25, Unsafe = true)]
   public string MaterialCategoryCode { get; set; }

   [RfcEntityProperty("ZZEXTWG", Description = "Ürün Sınıfı", SapDataType = RfcDataTypes.CHAR, Length = 25, SubTypePropertyName = "ZZEXTWG", Unsafe = true)]
   public virtual MaterialCategoryDefinition MaterialCategory { get; set; }
}

Sornasında tablomuzu çekeceğimiz koşullarımızı AbapQuey ile oluşturuyoruz. Koşulu MATNR malzeme tanımının materialCodePrefix ile başlayan ve silinmiş olarak işaretlenmeyen kalemler olsun olarak belirliyoruz.

List<string> whereClause = new AbapQuery().Set(QueryOperator.Equal("MATNR", materialCode))
               .And(QueryOperator.NotEqual("LVORM", true, RfcDataTypes.BOOLEAN_X)).GetQuery();

IRfcClient nesnemizi üretiyoruz ve GetTableDataAsync methodu ile çekmek istediğmiz koşul, kayıt sayısı ve güvenli olayan (Geliştirme ile gelen) alanları isteyip istemediğimizi belirtiyoruz.

using IRfcClient sapClient = _serviceProvider.GetRequiredService<IRfcClient>();
List<Material> materials = await sapClient.GetTableDataAsync<Material>(whereClause, rowCount: recordCount);
return await SetOptionsAsync(materials, new MaterialQueryOptions { IncludeAll = true });
Kodun tamamı
public async Task<List<Material>> GetMaterialsByPrefixAsync(string materialCodePrefix,
           MaterialQueryOptions options = null, bool getUnsafeFields = true, int rowCount = 0)
{
   options ??= new MaterialQueryOptions();
   List<string> whereClause = new AbapQuery().Set(QueryOperator.StartsWith("MATNR", materialCodePrefix))
       .And(QueryOperator.NotEqual("LVORM", true, RfcDataTypes.BOOLEAN_X)).GetQuery();

   using IRfcClient sapClient = _serviceProvider.GetRequiredService<IRfcClient>();
   List<Material> result = await sapClient.GetTableDataAsync<Material>(whereClause, rowCount: recordCount);

   return await SetOptionsAsync(result, options, getUnsafeFields).ConfigureAwait(false);
}
Alt tablo işlemleri

Tablo okuma olayımınız kullanımı temelinde bu kadar. alt tablolarıda getirebilmek için setoptions isimli bir fonksiyon oluşturup onunla aldığımız alt tabloları ana MARA tablomuza bağliyoruz. Bu işlem daha çok unsafe, SubProperty gibi alanların kullanımına örnek olması için oluşturuldu.

Alt Tablo sınıfları

Malzeme Tanımları (MAKT) tablo sınıfı

[RfcEntity("MAKT", Description = "Material Definition Table")]
public class MaterialDefinition : ISapTable
{
   [RfcEntityProperty("MATNR", Description = "Material Code", SapDataType = RfcDataTypes.CHAR, Length = 18)]
   public string Code { get; set; }

   [RfcEntityProperty("MAKTX", Description = "Material Short Definition", SapDataType = RfcDataTypes.CHAR, Length = 40)]
   public string Definition { get; set; }
}

Malzeme Sınıfı (ZMM24030) tablo sınıfı

[RfcEntity("ZMM24030", Description = "Material Category Definition Table", Unsafe = true)]
public class MaterialCategoryDefinition : ISapTable
{
   #region Properties

   [RfcEntityProperty("ZZEXTWG", Description = "Material Category Code", SapDataType = RfcDataTypes.CHAR, Length = 25)]
   public string Code { get; set; }

   [RfcEntityProperty("TANIM", Description = "Material Category Definition", SapDataType = RfcDataTypes.CHAR, Length = 50)]
   public string Definition { get; set; }

   [RfcEntityProperty("ZZPARCASAYI", Description = "Material Category Unit Part Count", SapDataType = RfcDataTypes.INTEGER, Length = 10)]
   public int PartCount { get; set; }

   #endregion

   #region Methods

   public override string ToString()
   {
       return Definition;
   }

   #endregion
}

Alt Tabloların yüklenmesi

private async Task<List<Material>> SetOptionsAsync(List<Material> materialList,
           MaterialQueryOptions queryOptions, bool getUnsafeFields = true)
{
    if (!materialList.Any())
        return materialList;

    var taskList = new List<Task>();
    var definitionList = new ConcurrentQueue<MaterialDefinition>();
    var materialCategoryDefinitionList = new ConcurrentQueue<MaterialCategoryDefinition>();

    List<Task> materialDefinitionTaskList = SetMaterialDefinitionOptionAsync(queryOptions, materialList, definitionList);
    if (materialDefinitionTaskList.Any())
        taskList.AddRange(materialDefinitionTaskList);

    List<Task> materialCategoryTaskList =
        SetMaterialCategoryOptionAsync(queryOptions, materialList, materialCategoryDefinitionList);
    if (materialCategoryTaskList.Any())
        taskList.AddRange(materialCategoryTaskList);

    if (!taskList.Any())
        return materialList;

    await Task.WhenAll(taskList).ConfigureAwait(false);

    ILookup<string, MaterialDefinition> materialDefinitionLookup = definitionList.ToLookup(x => x.Code);
    ILookup<string, MaterialCategoryDefinition> materialCategoryLookup =
        materialCategoryDefinitionList.ToLookup(x => x.Code);

    foreach (Material material in materialList)
    {
        material.Definition = materialDefinitionLookup.Contains(material.Code)
            ? materialDefinitionLookup[material.Code].FirstOrDefault()
            : null;
        material.MaterialCategory = materialCategoryLookup.Contains(material.MaterialCategoryCode)
            ? materialCategoryLookup[material.MaterialCategoryCode].FirstOrDefault()
            : null;
    }

    return materialList;
}

Malzeme Tanımı İşlemleri

private List<Task> SetMaterialDefinitionOptionAsync(MaterialQueryOptions queryOptions, List<Material> materialList, ConcurrentQueue<MaterialDefinition> definitionList)
{
    var taskList = new List<Task>();
    if (!queryOptions.IncludeDefinition)
        return taskList;

    var list = materialList.Where(x => x.Code != null).Select(x => (object)x.Code).ToList();
    List<object>[] parts = list.Partition(PartitionCount);

    foreach (List<object> part in parts)
    {
        List<string> query = new AbapQuery()
            .Set(QueryOperator.In("MATNR", part))
            .GetQuery();

        taskList.Add(Task.Run(async () =>
        {
            using IRfcClient client = _serviceProvider.GetRequiredService<IRfcClient>();
            List<MaterialDefinition> definitions = await client.GetTableDataAsync<MaterialDefinition>(query);
            foreach (MaterialDefinition definition in definitions)
                definitionList.Enqueue(definition);
        }));
    }

    return taskList;
}

Malzeme Sınıfı İşlemleri

private List<Task> SetMaterialCategoryOptionAsync(MaterialQueryOptions queryOptions, List<Material> materialList, ConcurrentQueue<MaterialCategoryDefinition> materialCategoryDefinitionList)
{
    var taskList = new List<Task>();
    if (!queryOptions.IncludeMaterialCategory)
        return taskList;

    var materialCategoryList =
        materialList.Where(x => x.MaterialCategoryCode != null)
            .Select(x => (object)x.MaterialCategoryCode)
            .Distinct()
            .ToList();

    List<object>[] materialCategoryParts = materialCategoryList.Partition(PartitionCount);

    foreach (List<object> part in materialCategoryParts)
    {
        List<string> queryList = new AbapQuery()
            .Set(QueryOperator.In("ZZEXTWG", part))
            .GetQuery();

        taskList.Add(Task.Run(async () =>
        {
            using IRfcClient client = _serviceProvider.GetRequiredService<IRfcClient>();
            List<MaterialCategoryDefinition> definitions = await client.GetTableDataAsync<MaterialCategoryDefinition>(queryList);
            foreach (MaterialCategoryDefinition definition in definitions)
                materialCategoryDefinitionList.Enqueue(definition);
        }));
    }

    return taskList;
}

Meta Data

SapNco dan alıştığımız fonsiyonun meta data bilgilerini alabilmek için SapNetwareRFC içerisindeki bir Field ve Parameter bilgilerini birleştirerek sunacak bir method hazırladım. Method Sadece BAPI ve RFC için çalışıyor. Tabloları RFC_READ_TABLE üzerinden aldığımız için onun meta datasına malesef buradan erişemiyoruz.

ilerleyen zamanlarda DL003 gibi tablo tanımlarının tutulduğu tablolar üzerinden bu meta datayı almak içinde bir çalışmam olacak.

Diğer kullanımlar ile benzer şekilde IRfcClient nesnesini ürettikten sonra ReadFunctionMetaData fonksiyonunu çağırıyoruz.Bu method bize ParameterMetaData türünden bir liste dönmekte. Bu liste SE37 ile listedeğimiz Import,Export,Tables taki nesneleri tutuyor. Bu nesnelerin elemanlarına ise Field listesi altında alıyoruz.

public class ParameterMetaData
{
    public string Name { get; set; }
    public string Type { get; set; }
    public string Direction { get; set; }
    public int NumericLength { get; set; }
    public int UcLength { get; set; }
    public int Decimals { get; set; }
    public string Description { get; set; }
    public string DefaultValue { get; set; }
    public bool Optional { get; set; }

    public List<FieldMetaData> Fields { get; set; }

}
public class FieldMetaData
{
    public string Name { get; set; }
    public string Type { get; set; }
    public int NucLength { get; set; }
    public int NucOffset { get; set; }
    public int UcLength { get; set; }
    public int UcOffset { get; set; }
    public int Decimals { get; set; }
}

İçerisine meta datasına ulaşmak istediğiniz RFC veya BAPI nin ismini vermeniz yeterli.

using IRfcClient client = _serviceProvider.GetRequiredService<IRfcClient>();
List<ParameterMetaData> result = client.ReadFunctionMetaData(functionName);

Şimdilik Hepsi bu kadar.