Company Change Approaches on the Server

It seems so and here we are doing Service Connect and dropping single-line .csv’s for “Simple Stuff”. Like go to Company 004 and Update 1 Column. Sigh :slight_smile: good to know… I tried Db.Validate() and it works.

It makes sense, when you run it in LINQPad you technically run as NO User. I didnt know if Epicor was wrapping in some Security around Db Context like it does with Cross-Company BAQs.

Yeah Db is just the EF Context it has no security around companies as far as I can tell. it is straight up EF with some sprinklings of Epicor… that’s why they don’t let you anywhere near that in SaaS MT (I even tried via reflection cough… but those clever clever epicor boys block access to GetType() and all other Reflection functions specifically. :cry:

2 Likes

Correct on all the above. DBContext is evil and powerful.

It’s why I encourage REST / OData type semantics for doing things. DBContext is too powerful to leave lying around in ignorant hands :slight_smile:

3 Likes

So back to the TemporarySession code, that is only handy if you are calling “Business Objects” if you are directly going to the Db Context then no need to switch session?

Bingo! though you should only “Read” in that context and “write” to UD fields/tables… but anything beyond that makes me a sad panda
image

1 Like

Agreed, you shouldn’t be inserting Part’s and ShipHead Rows via Db Context - because the BO usually triggers other BPMs which you don’t trigger by using Db Context and it may cause corrupt db… typically okay to update a Description or a UD Field or Insert UD100 Rows.

In that case us a TemporarySession or Service Connect and do so via the proper BO Calls.

2 Likes

Don’t forget service renderer. That allows you encapsulation and optional firing of bpm based on your needs

1 Like

You mean by enabling/disabling triggers? or simply calling EngWkBenchService.Update(ref EngWorkBenchDataSet);

@Bart_Elia Back to the TemporarySession:

CallContext.Current.TemporarySessionCreator.SetUserID("manager").SetCompanyID("00300000").Create()

This code above works if the CallContextClient User has access to 00300000. Also Impersonation must be allowed to impersonate manager. However, when a user does not have access, despite having Impersonation rights it will complain that xxx does not have access. I assume the only way to execute a “BO” as a different user, in my case a Service Account User is to create a new Session via Ice.Lib.SessionMod? (Cant use TemporarySession). [ Just Testing the Mechanics of it ]

SetUserID just Impersonates, but does not actually log-in as that user.


One thing to note is that TemporarySessionCreator executes fine when running your BPM as “ASYNC” and letting the Task agent via user Print execute it.

CallContext.Current.TemporarySessionCreator.SetUserID("manager").SetCompanyID("00300000").Create()

So only question remains is, how to do it as synchronous?


Perhaps I don’t use SetCompanyID:

        public TemporarySessionFluentFactory SetCompanyID(string companyID)
        {
            if (SysUserCompCache.GetSysUserComp(this._parentSession.UserID, companyID) == null)
            {
                throw new EpicorServerException(Strings.InvalidCompanyForUser(companyID, this._parentSession.UserID));
            }
            this.Settings.CompanyID = companyID;
            return this;
        }

Settings is public, perhaps just set it myself avoiding the exception.

Awww same Error… :smiley:

var s = CallContext.Current.TemporarySessionCreator;
s.Settings.CompanyID = "00300000";

using (s.Create()) { }

2018-09-07_1600

I’ll keep trying a few more things such as using:

        public Epicor.Hosting.Session StartTemporarySession(SessionSettings settings)
        {
            Epicor.Hosting.Session item = new Epicor.Hosting.Session(Current.Session.Settings, settings);
            this.sessions.Push(item);
            return item;
        }

By the way Bart i see the Push and Pop - showing as you stated before that it just pushes it on the Stack.

1 Like

I mean when you call ServiceRenderer you can bypass Method Directives by ignoring the facades:

public interface IEpicorServiceRenderer
{
TService GetService(IceDataContext context, bool ignoreFacade = false)

Sometimes you need to, sometimes not. Depends on the use case.

2 Likes

Yea if you don’t have access to the company, you are not getting in period. You need either impersonation or some kind of process to act as a proxy to get in.

What’s the use case?

So I could use SessionMod and instantiate a brand new session object? Just the usual use-case, I need to simply as a User do something very simple in a “Main Company” without giving the User full access to our Master Company… Something that can be done via Service Connect, but if I run the BPM as “async” it can also be done in a BPM just fine. I was just wondering If I could do it in a “synchronous” BPM and provide the user “instant feedback” of success or failure. I can read “Main Company” data via DbContext which is cool. I can also update minor fields such as certain boolean fields just fine via DbContext. But when it comes to actually running several BO Methods through then I need a session.

Again having a “Master Company” with Master Data and not everything can feed up or down via Multi-Company process, this is where it would come in-handy (use case).

I think I have enough info to go with. Async will work, Service Connect is an option, DbContext in many cases is an option.

@Bart_Elia By the way these methods work perfect in a Customization and they are fast, little to no session initialization (loading): which is also an option

Prototype Code, Needs a little Refactoring… but gist:
Runs BAQ via BO or Adapter as a SA_GLOBAL User… License is requested and also released properly.

public System.Data.DataSet GetBAQDataSetDataTagFromMainCompany_BO(string partNum, string partRev)
{
	System.Data.DataSet results = null;

	try
	{

		using (var newSession = new Ice.Core.Session("SA_GLOBAL", "ChrisConnPie!"))
		{
			removeEventhandlers(newSession);

			// Declare and Initialize Variables
			string BAQName = "SMI-zGetDataTagByPartRev";
			Ice.BO.QueryExecutionDataSet ds = new Ice.BO.QueryExecutionDataSet();

			// Add Parameter Rows
			ds.ExecutionParameter.AddExecutionParameterRow("PartNum", partNum, "nvarchar", false, Guid.Empty, "A");
			ds.ExecutionParameter.AddExecutionParameterRow("RevisionNum", partRev, "nvarchar", false, Guid.Empty, "A");

			// Use Business Object Directly
			Ice.Proxy.BO.DynamicQueryImpl dynamicQuery = Ice.Lib.Framework.WCFServiceSupport.CreateImpl<Ice.Proxy.BO.DynamicQueryImpl>(newSession, Epicor.ServiceModel.Channels.ImplBase<Ice.Contracts.DynamicQuerySvcContract>.UriPath);
			results = dynamicQuery.ExecuteByID(BAQName, ds);

			// var mod =(Ice.Proxy.Lib.SessionModImpl)sessionx.Variable("sessionMod");
			// mod.Logout();

		}

	}
	catch (System.Exception ex)
	{
		ExceptionBox.Show(ex);
	}

	return results;
}

Adapter Version:

public System.Data.DataSet GetBAQDataSetDataTagFromMainCompany_ADAPTER(string partNum, string partRev)
{
	using (var newSession = new Ice.Core.Session("SA_GLOBAL", "ChrisConnPie!"))
	{

		ILauncher iLnch = new ILauncher( newSession );

		using (var newTransaction = new EpiTransaction(iLnch))
		using (var adapterDynamicQuery = new DynamicQueryAdapter(newTransaction))
		{
			adapterDynamicQuery.BOConnect();

			// Declare and Initialize Variables
			string BAQName = "SMI-zGetDataTagByPartRev";
			Ice.BO.QueryExecutionDataSet ds = new Ice.BO.QueryExecutionDataSet();

			// Add Parameter Rows
			ds.ExecutionParameter.AddExecutionParameterRow("PartNum", partNum, "nvarchar", false, Guid.Empty, "A");
			ds.ExecutionParameter.AddExecutionParameterRow("RevisionNum", partRev, "nvarchar", false, Guid.Empty, "A");

			// Call Adapter method
			adapterDynamicQuery.ExecuteByID(BAQName, ds);

			System.Data.DataSet results = adapterDynamicQuery.QueryResults;
			return results;
		}

		// Cleanup Adapter Reference
		//adapterDynamicQuery.Dispose();
	}


	return null;
}

This is what I figured I could do on the Client Side… It works and has been working for a few months now! Flawless

Save this Snippet! Took Days to get right! Runs and honors Session Licensing timeouts etc… Also instead of managing another Service Account, I used the “Agent User” a method discovered in the MRP Engine. Works like a charm!

I did a study on the Sessions in Admin Console, I did a full SPIKE took 7 days to Test / Document Behaviours, it all ran super! It ran fast! I could even Create a Session between each BAQ Call… But I decided to Create one when the Form Loads and leave it.

// Session Impersonation Vars
Ice.Core.Session newSession;

// InitCustomCode
this.newSession = this.GetNewSessionAsSysAgent();

// DestroyCustomCode
// Begin Custom Code Disposal
Ice.Proxy.Lib.SessionModImpl sMod = GetInstanceField(typeof(Ice.Core.Session), newSession, "sessionMod") as Ice.Proxy.Lib.SessionModImpl;
sMod.Logout();
this.newSession = null; // Do NOT Dispose it, it will kill Epicor

// We need a Session Link to 003 which Local Users
// may not have access to - lets Auth as Agent
// could be refactored a bit
public Ice.Core.Session GetNewSessionAsSysAgent()
{
	string username = string.Empty;
	string password = string.Empty;

	object searchReslts = ProcessCaller.PerformSearch(oTrans.EpiBaseForm, "SysAgentAdapter", "Rows", string.Empty);

	if (searchReslts is DataSet) {
		DataSet searchDS = (DataSet) searchReslts;

		if (searchDS.Tables[0].Rows.Count > 0) {
			username = searchDS.Tables["SysAgent"].Rows[0]["SysUserID"].ToString();
			password = searchDS.Tables["SysAgent"].Rows[0]["SysPassWord"].ToString();
		}
	}

	if (username != string.Empty && password != string.Empty)
	{
		try {
			return new Ice.Core.Session(username, Epicor.Security.Cryptography.Encryptor.DecryptToString(password));
		}
		catch (Exception ex)
		{
			EpiMessageBox.Show("Unable to Authenticate with Company MAIN.\n" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
		}
	}

	return null;
}

internal static object GetInstanceField(Type type, object instance, string fieldName)
{
	BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
		| BindingFlags.Static;
	FieldInfo field = type.GetField(fieldName, bindFlags);
	return field.GetValue(instance);
}

public System.Data.DataSet GetBAQDataSetDataTagFromMainCompany(string partNum, string partRev)
{
	if (newSession == null)
	{
		MessageBox.Show("Unable to Authenticate with Company MAIN, Please See System Administrator");
		return null;
	}

	using (this.oTrans.PushDisposableStatusText("Initializing Global Query...", true))
	{
		ILauncher iLnch = new ILauncher( newSession );
		//removeEventhandlers(newSession);  // Done Globally

		using (var newTransaction = new EpiTransaction(iLnch))
		using (var adapterDynamicQuery = new DynamicQueryAdapter(newTransaction))
		{
			adapterDynamicQuery.BOConnect();

			// Declare and Initialize Variables
			string BAQName = "SMI-zGetDataTagByPartRev";
			Ice.BO.QueryExecutionDataSet ds = new Ice.BO.QueryExecutionDataSet();

			// Add Parameter Rows
			ds.ExecutionParameter.AddExecutionParameterRow("PartNum", partNum, "nvarchar", false, Guid.Empty, "A");
			ds.ExecutionParameter.AddExecutionParameterRow("RevisionNum", partRev, "nvarchar", false, Guid.Empty, "A");
			 
			// Call Adapter method
			adapterDynamicQuery.ExecuteByID(BAQName, ds);

			System.Data.DataSet results = adapterDynamicQuery.QueryResults;
			return results;
		}

	}

	return null;
}

Thanks @Rich and @Bart_Elia and @josecgomez and @Chris_Conn for poking at this a while back.

PS: Chris looks like sMod.Logout() was the only save way, removing the Session Event Listeners ruined other Screens / Avalara Tax etc… Good find.

DISCLAIMER: I do not do this on every form, in every case. We utilize Service Connect heavily! There are those 1 or 2 Cases where this is used, as we move to APIs, we didnt want to invest in more SC Workflows as we are phasing it out slowly.

4 Likes

I am digging about to see if the answer is hidden away in a comment (also recently created a post titled “Change company inside Epicor Function”)

Also looked at the 'Lets Get Funcy" post as well.

Is there any way to create a temporary session, as covered in this post, inside an Epicor Function?
EPICWEB does not seem to have a technical documentation on Epicor Functions. All I see is a section on this about REST API doc.