This document describes strategies developers can use to support and extend the capabilities of T-Connect, and addresses both SQL and .Net code considerations.
T-Connect is comprised of three application components, a management database (TctepDb), and a set of EDI databases corresponding to each EDI transaction type (e.g. TConnectEDI834)
- Parsing Engine. The parsing engine consists of:
- Transaction entities corresponding to EDI message types
- A data access layer
- Structural validation libraries
- Processing Engine: The processing engine is in charge of executing tasks and tracking the results. It’s comprised of:
- Configuration tables for rules, services and routing
- Message tracking tables
- Management Portal: The Portal is the front end of T-Connect, used for viewing outbound and inbound EDI operations, configuring trading partners and routes, and managing EDI errors.
Prerequisites
The following software is required to develop across the spectrum of T-Connect components:
- Visual Studio 2017+
- SQL Server Management Studio
- Access to an instance of the T-Connect EDI Management Suite
- IIS 7+ (If deploying or edit Portal components)
Object Model and API
Developers work with the T-Connect EDI object model to create rules and edits, maps, data lookups, and custom validations. A T-Connect EDI object can be constructed by parsing an EDI file, outbounding an EDI file from a T-Connect database, or by manually constructing the loops and segments of a particular transaction.
Once a T-Connect EDI object has be instantiated, it is immediately available for validation, persistence to a T-Connect database, or serialization to the X12 format.
Parsing an EDI File into an Object Model
The T-Connect EDI object model uses a set of .Net libraries named Caladan. A standard parsing process references namespaces Parser, Standard Entities, and Validators.
using Caladan.LinqToEdi.Parser;
using Caladan.LinqToEdi.StandardEntities;
using Caladan.X12Validation.Validators;
There are three steps in parsing an EDI file:
- Initialize the transaction header – calling a method FromStreamReader in X12HeaderInfo will read the data in the ISA and GS loop into an object.
//Initialize Header Information from File
X12HeaderInfo info = X12HeaderInfo.FromStreamReader(new StreamReader(filepath), true);
- Resolve the transaction – in order to ensure the valid structure of an EDI file, the transaction has to be one supported by T-Connect. The resolver handles this part for the user.
//Check and Handle Supported EDI Transactions
X12TransactionSetResolver resolver = X12TransactionSetResolver.Create(info);
if (!resolver.TryResolveStType(out stType))
{
throw new X12ParsingException($"Could not determine message type for this file (ST01: {info.ST01}, GS08: {info.GS08})");
}
- Parse – reading the EDI file into a X12 object model
//Invoke Parsing Process
X12_LoopISA EDI = parser.Parse(new StreamReader(filepath), stType); #region Init EDI //Declare path to the EDI File string filepath = @"..\..\InputFiles\837P_withInvalids_5_1_2.edi"; /* * SET UP EDI */ //Instantiate Parser Object X12Parser parser = new X12Parser(); //Instantiate Type Type stType; //Initialize Header Information from File X12HeaderInfo info = X12HeaderInfo.FromStreamReader(new StreamReader(filepath), true); //Check and Handle Supported EDI Transactions X12TransactionSetResolver resolver = X12TransactionSetResolver.Create(info); if (!resolver.TryResolveStType(out stType)) { throw new X12ParsingException($"Could not determine message type for this file (ST01: {info.ST01}, GS08: {info.GS08})"); } //Invoke Parsing Process X12_LoopISA EDI = parser.Parse(new StreamReader(filepath), stType); #endregion
Exploring the EDI Object
The T-Connect EDI Object structure reflects the implementation guideline of a transaction. As shown above, the highest level of the structure is the X12_LoopISA, which contains all information from the file. The Parse method returns this object type.
//Invoke Parsing Process
X12_LoopISA EDI = parser.Parse(new StreamReader(filepath), stType);
The easiest way to peek inside the structure is through the Watch window. The expanded view of variable “EDI” shows properties in the object that holds data from the flat file. Containers like Header information, GS and ISA loops are nested within this level of structure.
Getting further into the structure, we can see a collection of ST loops within the GS loop. Content segments are nested within ST transaction loops:
The T-Connect EDI Object Model captures the benefit of object querying by utilizing the LINQ library. Instead of drilling down to the substructure one level at a time, users can query back a set of object. The set of ST loops can be return by just one instruction: EDI.DescendantLoops<X837P_LoopST>()
With this pattern, we are able to perform complex operation with a simple instruction such as
EDI.DescendantLoops<X837P_LoopST>().First().DescendantLoops<X837P_Loop2300>()
This expression returns all the 2300 loops from the first ST loop without having to access any loop in between.
Validation
The same of set of library applies in the validation process.
using Caladan.LinqToEdi.Parser;
using Caladan.LinqToEdi.StandardEntities;
using Caladan.X12Validation.Validators;
//Declare path to the EDI File
string filepath = @"..\..\InputFiles\837P_withInvalids_5_1_2.edi";
//Validate
Caladan.X12ValidatorService.X12ValidatingParser validatingParser = new Caladan.X12ValidatorService.X12ValidatingParser();
X12_LoopISA EDI = validatingParser.Parse(filepath);
X12ValidationResultCollection validationResults = validatingParser.Results;
Drilling into the X12ValidationResultCollection object, we notice the Results View that holds the collection X12ValidationResult object. Each represents an errors flagged by the validator and the details about the errors are contained within it.
EDI Enrichment
The object structure is not finalized or immutable. Contents may be modified at any stage. Below is the example of the simple enrichment of an EDI837P object. The value of element CLM01 in 2300 loop is being set with a new GUID.
//Declare path to the EDI File string filepath = @"..\..\InputFiles\837P_withInvalids_5_1_2.edi"; /* * SET UP EDI */ //Instantiate Parser Object X12Parser parser = new X12Parser(); //Instantiate Type Type stType; //Initialize Header Information from File X12HeaderInfo info = X12HeaderInfo.FromStreamReader(new StreamReader(filepath), true); //Check and Handle Supported EDI Transactions X12TransactionSetResolver resolver = X12TransactionSetResolver.Create(info); if (!resolver.TryResolveStType(out stType)) { throw new X12ParsingException($"Could not determine message type for this file (ST01: {info.ST01}, GS08: {info.GS08})"); } //Invoke Parsing Process X12_LoopISA EDI = parser.Parse(new StreamReader(filepath), stType); //Enrichment Process X837P_Loop2300 loop2300 = EDI.DescendantLoops().FirstOrDefault(); loop2300.CLM.CLM01 = System.Guid.NewGuid().ToString();
Peeking inside of the CLM01 in the Watch window, we can see the value is set to new generated GUID.
X12 Object Model Mapping
HIPAA transaction sets and mappings can be very complex. Within the T-Connect object model framework, a developer can perform efficient transformations to the EDI payload with the flexibility and power of Visual Studio code editing and debugging.
The object mapping process involves two data structures. Usually, either the source or destination is a simple entity object representing a flat file or result set. The other structure involved is the T-Connect EDI object, which can serve as a source or destination.
In most cases, the EDI object model gets data directly from parsing and exports data to other structures. Below is an example of a simple class that contains Submitter and Provider information. This class also has a method PrintInfo that will write all the information and return data as String.
public class RecordInfo { #region PROPERTIES //ST Number public int RecordNumber { get; set; } //Submitter public string SubmitterName { get; set; } public string SubmitterInfo { get; set; } //Provider public string ProviderName { get; set; } public string ProviderInfo { get; set; } #endregion #region MEMBERS public string PrintInfo() { return ""; } #endregion }
The following mapping example will demonstrate how to traverse through the EDI object model and retrieve data along the way.
//Declare Variables List recInfoList = new List(); RecordInfo recInfo; //Query all ST records into a Collection Object var st_Collection = EDI.DescendantLoops(); //Loop through St records foreach(var st in st_Collection) { //Instantiate New Record Info recInfo = new RecordInfo(); //Get ST Number recInfo.RecordNumber = Int32.Parse(st.ST.ST02); //SUBMITTER foreach(var subloop1000_nm1 in st.X837P_SubLoop1000_NM1) { var loop1000a = subloop1000_nm1.X837P_Loop1000A.FirstOrDefault(); if (loop1000a != null) { //Get Submitter Info recInfo.SubmitterName = loop1000a.NM1.NM103; recInfo.SubmitterInfo = loop1000a.PER.FirstOrDefault().PER02; } } //PROVIDER foreach(var loop2000a in st.X837P_Loop2000A) { //Get first 2000 loop var loop2000a_nm1 = loop2000a.X837P_SubLoop2000A_NM1.FirstOrDefault(); if(loop2000a_nm1 != null) { //Get the first 2010aa loop var loop2010aa = loop2000a_nm1.X837P_Loop2010AA.FirstOrDefault(); if(loop2010aa != null) { //Get Provider Info recInfo.ProviderName = loop2010aa.NM1.NM103; recInfo.ProviderInfo = String.Format("{0} {1} {2}", loop2010aa.N3.N301, loop2010aa.N4.N402, loop2010aa.N4.N403); } } } //Add Record to List recInfoList.Add(recInfo); } //Write out all the record info System.Text.StringBuilder sb = new System.Text.StringBuilder(); foreach(var record in recInfoList) { sb.AppendLine(record.PrintInfo()); } String myReport = sb.ToString();
- Initialize and EDI Object (See instruction above)
- Declare objects to store selected data from EDI Objec
//Declare Variables List<RecordInfo> recInfoList = new List<RecordInfo>();
RecordInfo recInfo;
- Query ST records into a collection
//Query all ST records into a Collection Object
var st_Collection = EDI.DescendantLoops<X837P_LoopST>();
The method DescendantLoops return all specified loop types under its parent. In this particular instruction, all St loops in the ISA loop (variable EDI) will be returned.
- Iterate through the collection of St records
//Loop through St records
foreach(var st in st_Collection)
{
//Instantiate New Record Info
recInfo = new RecordInfo();
//Get ST Number
recInfo.RecordNumber = Int32.Parse(st.ST.ST02);
//SUBMITTER
foreach(var subloop1000_nm1 in st.X837P_SubLoop1000_NM1)
{
var loop1000a = subloop1000_nm1.X837P_Loop1000A.FirstOrDefault();
if (loop1000a != null)
{
//Get Submitter Info
recInfo.SubmitterName = loop1000a.NM1.NM103 recInfo.SubmitterInfo = loop1000a.PER.FirstOrDefault().PER02;
}
}
//PROVIDER
foreach(var loop2000a in st.X837P_Loop2000A)
{
//Get first 2000 loop var loop2000a_nm1 = loop2000a.X837P_SubLoop2000A_NM1.FirstOrDefault();
if(loop2000a_nm1 != null)
{
//Get the first 2010aa loop
var loop2010aa = loop2000a_nm1.X837P_Loop2010AA.FirstOrDefault();
if(loop2010aa != null)
{
//Get Provider Info
recInfo.ProviderName = loop2010aa.NM1.NM103; recInfo.ProviderInfo = String.Format("{0} {1} {2}", loop2010aa.N3.N301, loop2010aa.N4.N402, loop2010aa.N4.N403);
}
}
}
//Add Record to List
recInfoList.Add(recInfo);
}
- Retrieve ST02 value
//Instantiate New Record Info
recInfo = new RecordInfo();
//Get ST Numbe
recInfo.RecordNumber = Int32.Parse(st.ST.ST02);
- Retrieve Submitter Information
//SUBMITTER
foreach(var subloop1000_nm1 in st.X837P_SubLoop1000_NM1)
{ var loop1000a = subloop1000_nm1.X837P_Loop1000A.FirstOrDefault();
if (loop1000a != null)
{
//Get Submitter Info
recInfo.SubmitterName = loop1000a.NM1.NM103; recInfo.SubmitterInfo = loop1000a.PER.FirstOrDefault().PER02;
}
}
Notice that this method drills through each child loop one level at a time until the target loop is found [St->SubLoop1000_NM1 ->Loop1000A->NM1 ]. Another way to retrieve the target loop is through querying.
st.DescendantLoops<X837P_Loop1000A>().Where(x => String.IsNullOrEmpty(x.NM1?.NM103)==false);
This expression returns every 1000A loop within the current St record under the condition that NM103 isn’t empty.
- Retrieve Provider Information
//PROVIDER
foreach(var loop2000a in st.X837P_Loop2000A)
{
//Get first 2000 loop var loop2000a_nm1 = lop2000a.X837P_SubLoop2000A_NM1.FirstOrDefault();
if(loop2000a_nm1 != null)
{
//Get the first 2010aa loop var loop2010aa = loop2000a_nm1.X837P_Loop2010AA.FirstOrDefault();
if(loop2010aa != null)
{
//Get Provider Info
recInfo.ProviderName = loop2010aa.NM1.NM103; recInfo.ProviderInfo = String.Format("{0} {1} {2}", loop2010aa.N3.N301, loop2010aa.N4.N402, loop2010aa.N4.N403);
}
}
}
Users may modify content retrieved from the source. This example concatenates multiple string values together to form a ProviderInfo value.
- Add to List
//Add Record to List
recInfoList.Add(recInfo);
- Write to string
//Write out all the record info System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach(var record in recInfoList)
{
sb.AppendLine(record.PrintInfo());
}
String myReport = sb.ToString();
Database Persistence API
The Persistence API allows objects to be persisted to the T-Connect EDI databases. Use this feature after enrichments, transformations, or applying custom rules. T-Connect stores the complete EDI payload with a few lines of code.
public void Persist_EDI837P_File_To_TConnectDatabase(string filepath) { //Declare Connection String string connectionStr = @"Server=.;Database=TConnectEDI837P;Trusted_Connection=True;"; //Instantiate Database Persistent API Object TConnect.API.Features.Database db = new TConnect.API.Features.Database(); //Invoke the save to database try { int ediFileID = db.Save(filepath, connectionStr); } catch (Exception ex) { throw ex; } }
Persisting Stream
The database API also works with data streams. In most cases, the EDI enrichment process is performed by modifying the object model rather than editing the text file directly. The modified version of the EDI can be persisted directly to the database as stream.
public void Persist_EDI837_Obj_To_TConnectDatabase(X12_LoopISA edi) { //Declare Connection String string connectionStr = @"Server=.;Database=TConnectEDI837P;Trusted_Connection=True;"; //Declare path to the EDI File string filepath = @"..\..\EDI837P_Sample.edi"; //Instantiate Database Persistent API Object TConnect.API.Features.Database db = new TConnect.API.Features.Database(); //Invoke the save to database try { int ediFileID; using (MemoryStream mem = new MemoryStream()) { //Write EDI Objet to Memory Stream edi.HeaderInfo.CR = false; edi.HeaderInfo.LF = false; edi.Save(mem, System.Text.Encoding.UTF8, false); mem.Position = 0; //Persist to Database ediFileID = db.Save(mem, connectionStr); } } catch (Exception ex) { throw ex; } }