DictTable CallObject - axapta

I am using the following code to dynamically execute calls to a table method that may or may not be present.
However, it always returns Error executing code: myTableName table does not have method 'myUpdateMethod'.
Dicttable dictTable;
Common common;
ExecutePermission perm;
perm = new ExecutePermission();
dictTable= new DictTable(tableName2Id('myTableName'));
if (dictTable != null)
{
common = dictTable.makeRecord();
// Grants permission to execute the
// DictTable.callObject method. DictTable.callObject runs
// under code access security.
perm.assert();
dictTable.callObject('myUpdateMethod', common);
}
// Close the code access permission scope.
CodeAccessPermission::revertAssert();
These objects are in different models, but just for kicks I tried making a reference between the two models to see if it made a difference. It did not fix the issue.
Thanks

Changed the method being called from static to non-static.
Started working, then found the callStatic() equivalent.
Here is the code I ended up using for the non-static method, which has no params.
Dicttable dictTable;
Common common;
ExecutePermission perm;
perm = new ExecutePermission();
dictTable= new DictTable(tableName2Id('MyTableName'));
if (dictTable != null)
{
common = dictTable.makeRecord();
// Grants permissions
perm.assert();
if (dictTable.doesMethodExist('myMethodName'))
{
dictTable.callObject('myMethodName', common);
}
}
// Close the code access permission scope.
CodeAccessPermission::revertAssert();

Related

Changelog method based on project tracker template

Based on the project tracker I have integrated a changelog into my app that relates my UserSettings model to a UserHistory model. The latter contains the fields FieldName, CreatedBy, CreatedDate, OldValue, NewValue.
The relation between both models works fine. Whenever a record is modified, I can see the changes in a changelog table. I now want add an "undo"-button to the table that allows the admin to undo a change he clicks on. I have therefore created a method that is handled by the widget that holds the changelog record:
function undoChangesToUserRecord(changelog) {
if (!isAdmin()) {
return;
}
var fieldName = changelog.datasource.item.FieldName;
var record = changelog.datasource.item.UserSettings;
record[fieldName] = changelog.datasource.item.OldValue;
}
In theory method goes the connection between UserHistory and UserSettings up to the field and rewrites its value. But when I click on the button, I get a "Failed due to circular reference" error. What am I doing wrong?
I was able to repro the issue with this bit of code:
google.script.run.ServerFunction(app.currentPage.descendants.SomeWidget);
It is kinda expected behavior, because all App Maker objects are pretty much complex and Apps Script RPC has some limitations.
App Maker way to implement it would look like this:
// Server side script
function undoChangesToUserRecord(key) {
if (!isAdmin()) {
return;
}
var history = app.models.UserHistory.getRecord(key);
if (history !== null) {
var fieldName = history.FieldName;
var settings = history.UserSettings;
settings[fieldName] = history.OldValue;
}
}
// Client side script
function onUndoClick(button) {
var history = widget.datasource.item;
google.script.run
.undoChangesToUserRecord(history._key);
}

Multiple TrackingParticipants not working, have funny side effects?

We are rying to use WF with multiple tracking participants which essentially listen to different queries - one for activity states, one for custom tracknig records which are a subclass of CustomTrackingRecord.
The problem is that we can use both TrackingParticipants indivisually, but not together - we never get our subclass from CustomTrackingRecord but A CustomTrackingRecord.
If I put bopth queries into one TrackingParticipant and then handle everythign in one, both work perfectly (which indicates teh error is not where we throw them).
The code in question for the combined one is:
public WorkflowServiceTrackingParticipant ()
{
this.TrackingProfile = new TrackingProfile()
{
ActivityDefinitionId = "*",
ImplementationVisibility = ImplementationVisibility.All,
Name = "WorkflowServiceTrackingProfile",
Queries = {
new CustomTrackingQuery() { Name = "*", ActivityName = "*" },
new ActivityStateQuery() {
States = {
ActivityStates.Canceled,
ActivityStates.Closed,
ActivityStates.Executing,
ActivityStates.Faulted
}
},
}
};
}
When using two TrackingParticipants we have two TrackingProfile (with different names) that each have one of the queries.
in the track method, when using both separate, the lines:
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
Console.WriteLine("*** ActivityTracking: " + record.GetType());
if (record is ActivityBasedTrackingRecord)
{
System.Diagnostics.Debugger.Break();
}
never result in the debugger hitting, when using only the one to track our CustomTrackingRecord subclass (ActivityBasedTrackingRecord) then it works.
Anyone else knows about this? For now we have combined both TrackingParticipants into one, but this has the bad side effect that we can not dynamically expand the logging possibilities, which we would love to. Is this a known issue with WWF somewhere?
Version used: 4.0 Sp1 Feature Update 1.
I guess I encounterad the exact same problem.
This problem occurs due to the restrictions of the extension mechanism. There can be only one instance per extension type per workflow instance (according to Microsoft's documentation). Interesting enough though, one can add multiple instances of the same type to one workflow's extensions which - in case of TrackingParticipant derivates - causes weird behavior, because only one of their tracking profiles is used for all participants of the respective type, but all their overrides of the Track method are getting invoked.
There is a (imho) ugly workaround to this: derive a new participant class from TrackingParticipant for each task (task1, task2, logging ...)
Regards,
Jacob
I think that this problem isn't caused by extension mechanism, since DerivedParticipant 1 and DerivedParticipant 2 are not the same type(WF internals just use polymorphism on the base class).
I was running on the same issue, my Derived1 was tracking records that weren't described in its profile.
Derived1.TrackingProfile.Name was "Foo" and Derived2.TrackingProfile.Name was null
I changed the name from null to "Bar" and it worked as expected.
Here is a WF internal reference code, describing how is the Profile selected
// System.Activities.Tracking.RuntimeTrackingProfile.RuntimeTrackingProfileCache
public RuntimeTrackingProfile GetRuntimeTrackingProfile(TrackingProfile profile, Activity rootElement)
{
RuntimeTrackingProfile runtimeTrackingProfile = null;
HybridCollection<RuntimeTrackingProfile> hybridCollection = null;
lock (this.cache)
{
if (!this.cache.TryGetValue(rootElement, out hybridCollection))
{
runtimeTrackingProfile = new RuntimeTrackingProfile(profile, rootElement);
hybridCollection = new HybridCollection<RuntimeTrackingProfile>();
hybridCollection.Add(runtimeTrackingProfile);
this.cache.Add(rootElement, hybridCollection);
}
else
{
ReadOnlyCollection<RuntimeTrackingProfile> readOnlyCollection = hybridCollection.AsReadOnly();
foreach (RuntimeTrackingProfile current in readOnlyCollection)
{
if (string.CompareOrdinal(profile.Name, current.associatedProfile.Name) == 0 && string.CompareOrdinal(profile.ActivityDefinitionId, current.associatedProfile.ActivityDefinitionId) == 0)
{
runtimeTrackingProfile = current;
break;
}
}
if (runtimeTrackingProfile == null)
{
runtimeTrackingProfile = new RuntimeTrackingProfile(profile, rootElement);
hybridCollection.Add(runtimeTrackingProfile);
}
}
}
return runtimeTrackingProfile;
}

Is there anything wrong with this database class's execute query function?

So I have this old code being used, that runs simple ExecuteNonQuery command for database calls. I'm using DbConnection, DbTransaction and other System.Data.Common commands.
I seem to get a lot of Null Reference errors whenever I use the function in certain parts of the project, though it seems fine in other parts. I think it has to do with opening connections manually or some problem with calling it, but I'm wondering if the function itself is badly designed originally (shouldn't there be a way to fix any problems in the way it is called?)
I feel when transactions are involved, these null reference errors come up more often, I think the error I get is null exception at "_command = _db.GetStoredProcCommand(storedProcedure);" inside the following function. But that stored procedure does exist, so it makes no sense.
public List<OutputParameter> execute(String storedProcedure, StoredProcedureParameter[] sqlParameters)
{
try
{
List<OutputParameter> outputParameters = new List<OutputParameter>();
_command = _db.GetStoredProcCommand(storedProcedure);
for (int x = 0; x < sqlParameters.GetLength(0); x++)
{
if (sqlParameters[x] != null)
{
StoredProcedureParameter sqlParameter = sqlParameters[x];
String param = sqlParameter.ParameterName;
DbType dbType = sqlParameter.DbType;
object value = sqlParameter.Value;
if (sqlParameter.IsOutputParam)
{
_db.AddOutParameter(_command, param, dbType, 32);
OutputParameter outputParameter = new OutputParameter(param);
outputParameters.Add(outputParameter);
}
else
_db.AddInParameter(_command, param, dbType, value);
}
}
if (_transaction == null)
_db.ExecuteNonQuery(_command);
else
_db.ExecuteNonQuery(_command, _transaction);
foreach (OutputParameter op in outputParameters)
{
op.ParameterValue = _db.GetParameterValue(_command, op.ParameterName);
}
return outputParameters;
}
catch (SqlException sqle)
{
throw new DataAccessException(sqle.ToString());
}
catch (Exception e)
{
throw new DataAccessException(e.ToString());
}
}
Your _command variable appears to be a field and as such a shared member.
As such your code is very susceptible to multithreading issues (if two functions call this class with different stored procedures, what happens?).
A Command should also be closed and disposed of properly, which is not happening in your code, not explicitly anyways.
If you are getting a null reference exception in the line _command = _db.GetStoredProcCommand(storedProcedure); then the only thing that can be null there is _db. The storedProcedure is just a parameter and _command could happily be null without a problem.
Since you aren't actually doing anything in the code to make sure that _db exists and is valid, open, etc. then this is most likely the problem.

Is it possible to find the function and/or line number that caused an error in ActionScript 3.0 without using debug mode?

I'm currently trying to implement an automated bug reporter for a Flex application, and would like to return error messages to a server along with the function/line number that caused the error. Essentially, I'm trying to get the getStackTrace() information without going into debug mode, because most users of the app aren't likely to have the debug version of flash player.
My current method is using the UncaughtErrorEvent handler to catch errors that occur within the app, but the error message only returns the type of error that has occurred, and not the location (which means it's useless). I have tried implementing getStackTrace() myself using a function name-grabber such as
private function getFunctionName (callee:Function, parent:Object):String {
for each ( var m:XML in describeType(parent)..method) {
if ( this[m.#name] == callee) return m.#name;
}
return "private function!";
}
but that will only work because of arguments.callee, and so won't go through multiple levels of function calls (it would never get above my error event listener).
So! Anyone have any ideas on how to get informative error messages through the global
error event handler?
EDIT: There seems to be some misunderstanding. I'm explicitly avoiding getStackTrace() because it returns 'null' when not in debug mode. Any solution that uses this function is what I'm specifically trying to avoid.
Just noticed the part about "I don't want to use debug." Well, that's not an option, as the non-debug version of Flash does not have any concept of a stack trace at all. Sucks, don't it?
Not relevant but still cool.
The rest is just for with the debug player.
This is part of my personal debug class (strangely enough, it is added to every single project I work on). It returns a String which represents the index in the stack passed -- class and method name. Once you have those, line number is trivial.
/**
* Returns the function name of whatever called this function (and whatever called that)...
*/
public static function getCaller( index:int = 0 ):String
{
try
{
throw new Error('pass');
}
catch (e:Error)
{
var arr:Array = String(e.getStackTrace()).split("\t");
var value:String = arr[3 + index];
// This pattern matches a standard function.
var re:RegExp = /^at (.*?)\/(.*?)\(\)/ ;
var owner:Array = re.exec(value);
try
{
var cref:Array = owner[1].split('::');
return cref[ 1 ] + "." + owner[2];
}
catch( e:Error )
{
try
{
re = /^at (.*?)\(\)/; // constructor.
owner = re.exec(value);
var tmp:Array = owner[1].split('::');
var cName:String = tmp.join('.');
return cName;
}
catch( error:Error )
{
}
}
}
return "No caller could be found.";
}
As a side note: this is not set up properly to handle an event model -- sometimes events present themselves as either not having callers or as some very weird alternate syntax.
You don't have to throw an error to get the stack trace.
var myError:Error = new Error();
var theStack:String = myError.getStackTrace();
good reference on the Error class
[EDIT]
Nope after reading my own reference getStackTrace() is only available in debug versions of the flash player.
So it looks like you are stuck with what you are doing now.

Problems with remote shared objects Fms Flex 4

I'm trying to develop an application where simultaneous users can interact and i need to have a persistent remote shared object with the list of users currently in session.
When a new user enter in the session he get the server's object with the list. That list was supose to have all the others users in session but is undefined.
I'm doing this first:
users_so = SharedObject.getRemote("users_so", nc.uri, true);
users_so.connect( nc );
users_so.addEventListener( SyncEvent.SYNC, usersSyncHandler );
then i set property to shared object
remoteUsers = new ArrayCollection();
remoteUsers.addItem(displayName);
users_so.setProperty("usersID", remoteUsers);
and finaly i put users in the list.
Thanks!
I would say, that you need to use sharedObject.setDirty("usersID");
SharedObject can't know, that you changed content of ArrayCollection, because the reference to it didn't change. You can use setDirty() to force synch.
Note: The SharedObject.setProperty()
method implements the setDirty()
method. In most cases, such as when
the value of a property is a primitive
type like String or Number, you would
use setProperty() instead of setDirty.
However, when the value of a property
is an object that contains its own
properties, use setDirty() to indicate
when a value within the object has
changed. In general, it is a good idea
to call setProperty() rather than
setDirty(), because setProperty()
updates a property value only when
that value has changed, whereas
setDirty() forces synchronization on
all subscribed clients.
I am using simple dynamic object for this. Client has read-only SharedObject and server decides when to add/remove client from this SharedObject.
m_so is SharedObject (remote), m_userList is Object (local)
if(m_so.data.userList != null) {
for (var key:String in m_so.data.userList) {
if(m_userList[key] == null) {
_addUser(m_so.data.userList[key]);
}
}
for(var clientId:String in m_userList) {
if(m_so.data.userList[clientId] == null) {
_removeUser(clientId);
}
}
}
application.onAppStart = function () {
userList = {};
so = SharedObject.get("roster", false);
so.setProperty("userList", userList);
}
application.onConnect = function (client /*Client*/, userId /*string*/) {
application.acceptConnection(client);
client.userId = userId;
userList[userId] = userId;
so.setProperty("userList", userList);
}
application.onDisconnect = function (client /*Client*/) {
var userId = client.userId;
delete userList[userId];
so.setProperty("userList", userList);
}
I found one solution that works better for me:
It consists in calling remote funcions on server and then broadcast to all clients. The clientes then apply the necessery changes making the solution a lot more stable.

Resources