C# Customization Error After Upgrade

We upgraded our pilot system this weekend to 2022.1.1 / Base Version 11.2.100.0 / 4.2.100.0. With this upgrade we are now getting an error on one of our customizations:

“Error: CS0308 - line 86 (848) - The non-generic type ‘Epicor.ServiceModel.Channels.ImplBase’ cannot be used with type arguments”

Code:

dynamicQuery = WCFServiceSupport.CreateImpl<Ice.Proxy.BO.DynamicQueryImpl>(session, ImplBase<DynamicQuerySvcContract>.UriPath);

Does anyone know what the new syntax should be in this case?

@Aaron_Moreng has an example:

C# Execute PerformClick() on a Submit Button Not Firing - ERP 10 - Epicor User Help Forum (epiusers.help)

From the “What’s New” document:

Kinetic no longer uses the Windows Communication Foundation (WCF) framework to manage service calls between the server, clients, and web applications. The system uses ASP.NET Web API instead.

I’m surprised the WCFServiceSupport class exists at all.

1 Like

I could be wrong, but if you are invoking this from an epicor customization you ought to use the adapters and not invoke the assembly manually. Second, if this isn’t inside epicor and you’re calling epicor business objects, consider using the rest services or functions to orchestrate. Are we correct to assume this is an old customization in tbe epicor app or is it outside?

1 Like

Yea, this is a super old customization inside of the Part Tracker module.

Makes sense. The client side adapters should get you where you’re going, that link that @Mark_Wonsil posted will give you the syntax if you need

Surely those are just web API wrappers over the WCF services though, versus a total re-write?

@dgreenEA ,

Depending on your application, a BAQView may be even better.

2 Likes

I am working on a customization with the same issue after the upgrade. In my previous code I was using a line very similar to yours to pull a value from a BAQ that returns a single value.

Previously this worked:

private int getLastUNID()
{
using (var svc = WCFServiceSupport.CreateImpl<Ice.Proxy.BO.DynamicQueryImpl>((Ice.Core.Session)oTrans.Session,Epicor.ServiceModel.Channels.ImplBase<Ice.Contracts.DynamicQuerySvcContract>.UriPath))
		{
			var qeds = new QueryExecutionDataSet();
			DataSet baq = svc.ExecuteByID("LastCPIUNID", qeds);
			if (Convert.ToString(baq.Tables["Results"].Rows[0]["Calculated_LastUNID"]) == "")
				{			
				return 0;
				}
			else
			{
			return (int)baq.Tables["Results"].Rows[0]["Calculated_LastUNID"];
			}
		}
}

So I referred to this post and found your link. After trying the syntax on that page, I have the following:

private int getLastUNID()
{
		DynamicQueryAdapter qry = new DynamicQueryAdapter(oTrans); 
	
		qry.BOConnect();
		DataSet baq = qry.ExecuteByID("LastCPIUNID");	
	    DataSet ds = qry.QueryResults;
		qry.Dispose();	
			
			if (Convert.ToString(ds.Tables["Results"].Rows[0]["Calculated_LastUNID"]) == "")
				{			
				return 0;
				}
			else
				{
				return (int)ds.Tables["Results"].Rows[0]["Calculated_LastUNID"];
				}	
}

But now I am getting an error on the DataSet baq = … line

 Error: CS0029 - line 273 (859) - Cannot implicitly convert type 'void' to 'System.Data.DataSet'

What am I missing?

I just added a variable declaration where it wasn’t needed. I changed the line to:

		qry.ExecuteByID("LastCPIUNID");

and now it works great again!

2 Likes

You can try using: dynamicQuery = WCFServiceSupport.CreateImpl<Ice.Proxy.BO.DynamicQueryImpl>(session, Ice.Proxy.BO.DynamicQueryImpl.UriPath);

1 Like

The WCFServiceSupport seems to have a wrapper a new ServiceSupport class, so even if WCF is not used, this wrapper class should continue working.

3 Likes

Hi expert guys,

fantastic solution @NateS @Mark_Wonsil @Aaron_Moreng @josecgomez , i have managed to use your conversion and it is working great on many classes, just this one i could not find the replacement for these WCFServiceSupport sub commands, any idea ?

DynamicQuery[0]
QueryWhereItem.Select
DynamicQuery.Rows.Count

	using(DynamicQueryImpl qd = WCFServiceSupport.CreateImpl<DynamicQueryImpl>((Session)oTrans.Session, ImplBase<DynamicQuerySvcContract>.UriPath))
	    {
			Guid newguid = Guid.NewGuid();
            DynamicQueryDataSet qds = qd.GetByID(baqID);
			string dcUID = "";
			string restring = "";
			if (qds.DynamicQuery.Rows.Count > 0)
				restring = qds.DynamicQuery[0]["AuthorID"].ToString().Trim();			

			if(paramsList.Count > 0){
				foreach(DictionaryEntry de in paramsList){
					string[] keyFileds = de.Key.ToString().Split('^');
					string filedName = "";
					string compOP = "=";
					filedName = keyFileds[1];
					compOP = keyFileds[2];

					DataRow[] drs = qds.QueryWhereItem.Select("FieldName = '" + filedName + "' and CompOp = '" + compOP + "'");
					if(drs.Length >0){
						drs[0].BeginEdit();
			            drs[0]["RValue"] = de.Value.ToString();
			            drs[0].EndEdit();
					}else{
						newguid = (Guid)qds.QuerySubQuery.Rows[0]["SubQueryID"];
						ParaSet(ref qds, newguid, baqID, restring,keyFileds[0].Trim(),filedName.Trim(),de.Value.ToString(),compOP,"nvarchar");
					}
				}
			}

			QueryExecutionDataSet qes = qd.GetQueryExecutionParameters(qds);
			DataSet ds = qd.Execute(qds,qes);

			if(ds.Tables["Errors"].Rows.Count>0)
			{
				EpiMessageBox.Show(ds.Tables["Errors"].Rows[0]["ErrorText"].ToString());
			}
			resultDt = ds.Tables[0];
		}

I’m not sure I understand the question entirely, but utilizing the dynamic query adapter will allow you to interact with the results of the baq as a dataset and from there you can perform your selection/filter/count operations like so:

//Call a Dynamic Query (BAQ) adapter and return results to a grid
//This is especially useful when returning data to a custom grid without needing to format the column headers in code. 
private void btnGet_Click(object sender, System.EventArgs args)
{
  //use dynamic query to populate grdBPM
  DynamicQueryAdapter adapterQuery = new DynamicQueryAdapter(oTrans);					  
  adapterQuery.BOConnect();
  adapterQuery.ExecuteByID("YourBAQIDHere");
  DataSet ds = adapterQuery.QueryResults;
  adapterQuery.Dispose();
  grdDisplayResults.DataSource = ds;
}

//Call a Dynamic Query adapter with parameters
//Multiple parameters can be added, just add a new parameter row 
private DataSet getInfoFromQuery(string partNum)
{
  DataSet dsLots = new DataSet();

  DynamicQueryAdapter qryAvailableLots = new DynamicQueryAdapter(oTrans);
  QueryExecutionDataSet parameters = new QueryExecutionDataSet();

  //set parameter: partNum
  DataRow paramRow = parameters.ExecutionParameter.NewRow();
  paramRow["ParameterID"] = "PartNum";
  paramRow["ParameterValue"] = partNum;
  paramRow["ValueType"] = "varchar(50)";
  paramRow["IsEmpty"] = "False";
  paramRow["RowMod"] = "";
  parameters.ExecutionParameter.Rows.Add(paramRow);

  qryAvailableLots.BOConnect(); 
  
  qryAvailableLots.ExecuteByID("YourParamQueryHere", parameters);
  dsLots = qryAvailableLots.QueryResults;
  qryAvailableLots.Dispose();		
  return dsLots;
}

The query results are where you interact with the data returned from the dynamic query.

2 Likes

If we switch over to the adapter method as suggested, when does the adapter get disposed? I’ve always preferred the WCFServiceSupport method because the scope seems clearer to me. For some reason I thought that BOConnect() takes additional overhead and if it is called within a function, it will be attaching additional adapters to oTrans each time it’s run?

You can either manually dispose the adapter via the .Dispose() method or wrap the entire thing in a using statement. There is very little overhead with adapters and this is the proper way to invoke the Epicor BO APIs in a client customization

@Aaron_Moreng Thanks for clarifying. It seems like I have a lot of rework ahead of me then if they are eliminating the proxies

1 Like

I’m also speaking from E10 experience, not Kinetic. I don’t know much about Kinetic client customizations, but you might check out functions for orchestration in lieu of adapters. Again, don’t want to misrepresent Kinetic if it is indeed fundamentally different

It’s not just Epicor. Microsoft has deprecated WCF in .NET 5+ and Kinetic server is .Net 6 so the future is REST for now (who knows, maybe gRPC in the near future for some things. :person_shrugging: ).

It is a much better practice to put your business logic server-side and not hit repositories directly from the client and functions are a great way to do this.

Unfortunately I’m still running 10.2.300 so functions are not available yet. When we upgrade to Kinetic we know the screens will be totally different. Hoping to move these WCF customizations into functions over time if possible