I am creating a custom transformation in C# to be used in SSIS. I have already been able creating and adding the custom component and receive and alter data from a db source but I need more data to register in a log table. This data can only be passed with variables but I can't find a good explanation of how to add a readonlyvariable to my component.
I have tried to use IDTSVariable100 and VariableDispenser but I can't make sense of how to.
public override void ProvideComponentProperties()
{
base.ProvideComponentProperties();
base.RemoveAllInputsOutputsAndCustomProperties();
VariableDispenser varDispenser = this.VariableDispenser();
IDTSVariable100 vr = this.VariableDispenser.GetVariables();
IDTSInput100 input = this.ComponentMetaData.InputCollection.New();
input.Name = "Input_B";
IDTSOutput100 output=this.ComponentMetaData.OutputCollection.New();
output.Name = "Output_B";
// the output is synchronous with the input
output.SynchronousInputID = input.ID;
}
Basically i want to define readonlyvariables that I can alter the value before my custom component runs like the original "script component" has.
Well i researched a bit more and stumbled on a answer:
It seems that to access the SSIS public variables we have to get them with code on the ProcessInput Method:
var dimSrcId ="";
IDTSVariables100 variables = null;
this.VariableDispenser.LockForRead("User::dimSrcId");
this.VariableDispenser.GetVariables(out variables);
dimSrcId = variables["User::dimSrcId"].Value.ToString();
variables.Unlock();
By using the VariableDispenser.LockForRead() we're capable of searching for our variables and access there value.
Related
I am trying to create a process that will run daily that will import records from another database as Inventory Items. In order to do this, I need to create an extension of the InventoryItemMaint graph (to give me my custom action), as well as an extension of the InventoryItem DAC (to give me a custom field). I have tried to follow the guidelines laid out specifically in the T-300 manual to do this.
Here is the code for my InventoryItemMaint extension:
namespace PX.Objects.IN
{
public class InventoryItemMaint_Extension:PXGraphExtension<InventoryItemMaint>
{
public PXAction<PX.Objects.IN.InventoryItem> DailyOnixImport;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Daily Onix Import")]
protected void dailyOnixImport()
{
var invItemMaintExtInstance = Base.GetExtension<InventoryItemMaint_Extension>();
string todaysDate = DateTime.Today.ToString("MM/dd/yyyy");
foreach (STOnixItem currentOnixItem in PXSelect<STOnixItem,
Where<STOnixItem.addedDate, Equal<Required<STOnixItem.addedDate>>>>
.Select(this.Base, todaysDate))
{
InventoryItem currentInventoryItem = invItemMaintExtInstance.Base.Item.Current;
PXCache inventoryItemCache = invItemMaintExtInstance.Base.Item.Cache;
InventoryItemExt inventoryItemExtension = inventoryItemCache.GetExtension<InventoryItemExt>(currentInventoryItem);
inventoryItemCache.Clear();
currentInventoryItem.InventoryCD = currentOnixItem.ISBN13;
currentInventoryItem.Descr = currentOnixItem.Title;
currentInventoryItem.ItemClassID = currentOnixItem.ItemClass;
currentInventoryItem.RecPrice = decimal.Parse(currentOnixItem.MSRP);
currentInventoryItem.BasePrice = decimal.Parse(currentOnixItem.DefaultPrice);
currentInventoryItem.BaseItemWeight = decimal.Parse(currentOnixItem.Weight);
currentInventoryItem.WeightUOM = "POUND";
currentInventoryItem.ImageUrl = currentOnixItem.ImageLink;
//Assigning to the custom DAC Extension
inventoryItemExtension.UsrFromOnixFile = currentOnixItem.FromFile;
inventoryItemCache.Update(currentInventoryItem);
Base.Actions.PressSave();
}
}
}
}
I am currently getting an error that reads:
Error: Another process has updated the 'InventoryItem' record. Your
changes will be lost.
And here is the error trace text:
9/20/2018 3:26:05 PM Error: Error: Another process has added the
'InventoryItem' record. Your changes will be lost.
at PX.Data.PXCache1.PersistInserted(Object row) at
PX.Data.PXCache1.Persist(PXDBOperation operation) at
PX.Data.PXGraph.Persist(Type cacheType, PXDBOperation operation)
at PX.Data.PXGraph.Persist() at
PX.Objects.IN.InventoryItemMaint.Persist() at
PX.Data.PXSave1.d__2.MoveNext() at
PX.Data.PXAction1.d__31.MoveNext() at
PX.Data.PXAction1.d__31.MoveNext() at
PX.Data.PXActionCollection.PressSave(PXAction caller) at
PX.Objects.IN.InventoryItemMaint_Extension.dailyOnixImport() at
PX.Data.PXAction1.<>c__DisplayClass3_0.<.ctor>b__0(PXAdapter adapter)
at PX.Data.PXAction1.a(PXAdapter A_0) at
PX.Data.PXAction1.d__31.MoveNext() at
PX.Data.PXAction`1.d__31.MoveNext() at
PX.Web.UI.PXBaseDataSource.tryExecutePendingCommand(String viewName,
String[] sortcolumns, Boolean[] descendings, Object[] searches,
Object[] parameters, PXFilterRow[] filters, DataSourceSelectArguments
arguments, Boolean& closeWindowRequired, Int32& adapterStartRow,
Int32& adapterTotalRows) at
PX.Web.UI.PXBaseDataSource.ExecuteSelect(String viewName,
DataSourceSelectArguments arguments, PXDSSelectArguments pxarguments)
I have done a lot of searching around StackOverflow and other places, but haven't found any answers that seem to address my issue exactly. Tweaks I've made have resulted in other errors like variations on what I'm getting now (another process added vs another process updated) and MoveNext errors.
If anyone is able to help me out, I would be very appreciative.
guys,
I faced the same exception in the EmployeeMaint when we tried to update one of the complex fields in the DAC.
baseAction() throws this exception even though no code runs before saving.
I followed your suggestions #Hugues Beauséjour and found out that reason may be:
Means, the cache is dirty.
So, to resolve my issue I just needed to clear the cache for the object that throws the exception.
IN my case the exception was:
Update employee class error - Another process has added the 'VendorPaymentMethodDetail' record. Your changes will be lost
So I needed to clear the cache for VendorPaymentMethodDetail:
Base.Caches<VendorPaymentMethodDetail>().Clear();
Be careful to clear the cache in the case you need to read from the cache during your code after you clear the cache. In that case, you need to copy objects from the cache before cleaning and used this copy afterwards.
Hope it will be helpful to someone.
There seem to be a logical flaw in the code. You are updating the same current object in a loop. This serve no purpose as it will always overwrite with the last item returned by the loop. Invoking the Save action in a loop can also lead to errors if you're not careful.
As I mentioned in the comment, clearing the cache seems wrong. You want to keep the current data there. When you call clear you're invalidating the main document of the graph, that will lead to errors.
Changing fields closely tied to the key like InventoryCD can lead to the document being cleared and invalidated. If you have to modify key fields considering inserting new record instead of updating the current one.
There are other changes I would recommend.
Code:
// Consider replacing the default namespace to avoid conflicts
namespace MyNamespace
{
public class InventoryItemMaint_Extension:PXGraphExtension<InventoryItemMaint>
{
public PXAction<PX.Objects.IN.InventoryItem> DailyOnixImport;
// '(CommitChanges = true)' is not necessary
[PXButton]
[PXUIField(DisplayName = "Daily Onix Import")]
protected void dailyOnixImport()
{
InventoryItemMaint_Extension invItemMaintExtInstance = Base.GetExtension<InventoryItemMaint_Extension>();
string todaysDate = DateTime.Today.ToString("MM/dd/yyyy");
// You need to rethink that 'foreach' logic
STOnixItem currentOnixItem in PXSelect<STOnixItem,
Where<STOnixItem.addedDate, Equal<Required<STOnixItem.addedDate>>>>.Select(Base, todaysDate);
// You can access Base directly, no need to fetch it from the extension
InventoryItem currentInventoryItem = Base.Item.Current;
// Consider using more null check
if (currentOnixItem != null && currentInventoryItem != null)
{
// Consider using similar names for similar variables
InventoryItemExt currentInventoryItemExt = currentInventoryItem.GetExtension<InventoryItemExt>();
// Avoid setting key related fields like InventoryCD when updating
currentInventoryItem.Descr = currentOnixItem.Title;
currentInventoryItem.ItemClassID = currentOnixItem.ItemClass;
currentInventoryItem.RecPrice = decimal.Parse(currentOnixItem.MSRP);
currentInventoryItem.BasePrice = decimal.Parse(currentOnixItem.DefaultPrice);
currentInventoryItem.BaseItemWeight = decimal.Parse(currentOnixItem.Weight);
currentInventoryItem.WeightUOM = "POUND";
currentInventoryItem.ImageUrl = currentOnixItem.ImageLink;
currentInventoryItemExt.UsrFromOnixFile = currentOnixItem.FromFile;
// You fetched the item from the DataView
// you can update it in the DataView too.
Base.Item.Update(currentInventoryItem);
// Is it really needed to save here?
// This coupled with cache clearing and the loop updating
// the same record triggers the error in your question.
Base.Actions.PressSave();
}
}
}
}
I found another reason that can cause the same exception:
That may happen when you select from the same table in different caches, use one for select and another for insert. for instance:
we have a view:
public PXSelect<MPEmployeeWorkSchedule, Where<MPEmployeeWorkSchedule.employeeID, Equal<Current<MPEmployeeTermination.employeeID>>>> EmployeeWorkSchedule;
and in event we have code's segment:
we use the same DAC as in the view above and then we insert into the view:
EmployeeWorkSchedule.Cache.Insert(workSchedule);
Afterwards Persist() throws exception.
Solution was again to create another view instead of query and clear the cache:
This may be helpful, I hope.
Want to share one more way of finding the same error message. In my case the problem was in structure of database.
In order to fix the error message, I've did those steps:
dropped my table
generated sql script from existing table
deleted not needed columns
copy/pasted existing columns, with replacing names
As outcome, I've got error message to disappear.
I am automating my work with SAP GUI script at the moment and whilst trying to recreate the recorded macro I am having an issue at one particular point which I don't know how to translate.
session.findById("wnd[0]/shellcont/shell/shellcont[1]/shell").setCurrentCell 1,"MAKTX2"
session.findById("wnd[0]/shellcont/shell/shellcont[1]/shell").doubleClickCurrentCell
session.findById("wnd[1]/tbar[0]/btn[0]").press
I have read through the SAP GUI Scripting API pdf and am struggling to see how I action the .setCurrentCell 1,"MAKTX2" part. I am accessing the container cell with the following:
GuiContainerShell materials = (GuiContainerShell)session.FindById("wnd[0]/shellcont/shell/shellcont[1]/shell");
How do I make "materials" double click "MAKTX2"?
Edit: Full SAP GUI script:
SapROTWr.CSapROTWrapper sapROTWrapper = new SapROTWr.CSapROTWrapper();
object SapGuilRot = sapROTWrapper.GetROTEntry("SAPGUI");
object engine = SapGuilRot.GetType().InvokeMember("GetScriptingEngine", System.Reflection.BindingFlags.InvokeMethod, null, SapGuilRot, null);
GuiApplication GuiApp = (GuiApplication)engine;
GuiConnection connection = (GuiConnection)GuiApp.Connections.ElementAt(0);
GuiSession session = (GuiSession)connection.Children.ElementAt(0);
GuiFrameWindow frame = (GuiFrameWindow)session.FindById("wnd[0]");
GuiTextField jobsite = (GuiTextField)session.FindById("wnd[0]/usr/subSA_0100_1:SAPMZCX_CSDSLSBM5001_OFS_OTS:2410/subSA_2410_1:SAPMZCX_CSDSLSBM5001_OFS_OTS:2510/ctxtKUWEV-KUNNR");
jobsite.Text = "I033";
frame.SendVKey(0);
GuiLabel aggregates = (GuiLabel)session.FindById("wnd[1]/usr/lbl[12,3]");
aggregates.SetFocus();
GuiFrameWindow frame2 = (GuiFrameWindow)session.FindById("wnd[1]");
frame2.SendVKey(1);
GuiContainerShell materials = (GuiContainerShell)session.FindById("wnd[0]/shellcont/shell/shellcont[1]/shell");
To be honest I can't help you with C#, but perhaps the SAP interface is generic enough anyway. Thing is, session.findById("wnd[0]/shellcont/shell/shellcont[1]/shell") gives you a reference to an object of type GuiShell or GuiContainerShell or whatever it's called. On this reference, you can call the methods defined for this type. So in the same way, when you do
session.findById("wnd[0]/shellcont/shell/shellcont[1]/shell").setCurrentCell 1,"MAKTX2"
You're just getting the reference first, and then applying the method setCurrentCell on it, all on the same line.
When you did in C#
GuiContainerShell materials = (GuiContainerShell)session.FindById("wnd[0]/shellcont/shell/shellcont[1]/shell");
you gave this reference a name materials, and provided that line works correctly, I guess you can just say now:
materials.setCurrentCell(1, "MAKTX2")
materials.doubleClickCurrentCell
I am working with the C# Event Log API in Windows (essentially everything in System.Diagnostics.Eventing.Reader).
I have an EventMetadata object and pull its Description property to retrieve the message template for an event.
Generally, these templates look similar to the following:
Network interface reports message %1
These variables are easily replaceable with actual data whenever I receive an event.
(EventLogRecord.Properties match up to the placeholders)
Here is where my problem comes in. The EventLogRecord.Properties sometimes contain different kinds of placeholders. These always begin in %% and I cannot find a way of resolving them.
As an example:
// This method is triggered when a new event comes in
async public static void ListenerEvent(object s, EventRecordWrittenEventArgs args) {
var evt = (EventLogRecord)args.EventRecord;
// This method retrieves the template from a ProviderMetadata object
// And replaces all %n with {n}
// So that we can string.Format on it
var tmp = TemplateCache.TemplateFor(evt);
// Need this since the indices start with 1, not 0
var props = new List<object> {string.Empty};
props.AddRange(evt.Properties.Select(prop => prop.Value));
// Now the message should be human-readable
var msg = string.Format(tmp, props);
}
Using the above example template, the Properties might be ["%%16411"] and now I end up with the following message
Network interface reports message %%16411
I figure my question now is, how do I replace this %%16411?
I have looked into ProviderMetadata and the rest of its properties but none seem to match up.
Any help figuring out how to resolve these placeholders (or even what they are/where they come from) is appreciated.
An event that shows this behaviour is 5152, as found here: http://community.spiceworks.com/windows_event/show/452-microsoft-windows-security-auditing-5152
Thank you.
I am building an application, which has a form where the user can configure all his settings. When the application is loaded, the previously configured settings should reflect to the GUI (The UI should be consistent to the saved settings).
What I am currently doing is creating the settings on the project properties and I have a LoadSettings() method, which gets the values and outputs them to each component on the UI.
The thing is that this is getting VERY messy, and I don't like it at all.
So, that got me wondering, what are the correct approaches to achieve what I want, but yet getting high quality code? Any patterns for that?
private void LoadConfigs()
{
checkBoxStartOnStartup.Checked = ExistKeyValue(#"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", "Wallbase Downloader");
checkBoxCheckWallbaseOnline.Checked = Settings.Default.CheckWallbaseOnlineStartup;
comboBoxResolution.SelectedIndex = comboBoxResolution.FindStringExact(Settings.Default.Resolution == string.Empty
? GetScreenResolution()
: Settings.Default.Resolution);
comboBoxCondition.SelectedIndex = Settings.Default.ConditionIndex;
textBoxWallpaperFolders.Text = Settings.Default.WallpaperFolder;
numericChangeInterval.Text = Convert.ToString(Settings.Default.ChangeIntervalValue);
comboBoxChangeInterval.SelectedIndex = Settings.Default.ChangeIntervalIndex;
numericCheckInterval.Text = Convert.ToString(Settings.Default.CheckIntervalValue);
comboBoxCheckInterval.SelectedIndex = Settings.Default.CheckIntervalIndex;
numericWallpapersToLookFor.Text = Settings.Default.WallpapersToLookFor.ToString();
}
Well, WinForms are not the cleanest framework around...
What you could do is to load all settings when your application starts up and store them in some storage that is available to all forms, e.g. in a static property in a helper settings class.
You can then access that static property from each form when it loads and make all necessary changes to the form based on the settings.
You can use a Hashtable and use English strings for key to make your code really readable. Then serialize it to file on exit and deserialize it back when application loads. Save the serialized file to some common location so that you do not lose it.
How can I access variables inside my C# code which I've used in Data Flow -> Script Component - > My c# Script with my SSIS package?
I have tried with which is also not working
IDTSVariables100 varCollection = null;
this.VariableDispenser.LockForRead("User::FilePath");
string XlsFile;
XlsFile = varCollection["User::FilePath"].Value.ToString();
Accessing package variables in a Script Component (of a Data Flow Task) is not the same as accessing package variables in a Script Task. For a Script Component, you first need to open the Script Transformation Editor (right-click on the component and select "Edit..."). In the Custom Properties section of the Script tab, you can enter (or select) the properties you want to make available to the script, either on a read-only or read-write basis:
Then, within the script itself, the variables will be available as strongly-typed properties of the Variables object:
// Modify as necessary
public override void PreExecute()
{
base.PreExecute();
string thePath = Variables.FilePath;
// Do something ...
}
public override void PostExecute()
{
base.PostExecute();
string theNewValue = "";
// Do something to figure out the new value...
Variables.FilePath = theNewValue;
}
public override void Input0_ProcessInputRow(Input0Buffer Row)
{
string thePath = Variables.FilePath;
// Do whatever needs doing here ...
}
One important caveat: if you need to write to a package variable, you can only do so in the PostExecute() method.
Regarding the code snippet:
IDTSVariables100 varCollection = null;
this.VariableDispenser.LockForRead("User::FilePath");
string XlsFile;
XlsFile = varCollection["User::FilePath"].Value.ToString();
varCollection is initialized to null and never set to a valid value. Thus, any attempt to dereference it will fail.
First List the Variable that you want to use them in Script task at ReadOnlyVariables in the Script task editor and Edit the Script
To use your ReadOnlyVariables in script code
String codeVariable = Dts.Variables["User::VariableNameinSSIS"].Value.ToString();
this line of code will treat the ssis package variable as a string.
I had the same problem as the OP except I remembered to declare the ReadOnlyVariables.
After some playing around, I discovered it was the name of my variable that was the issue. "File_Path" in SSIS somehow got converted to "FilePath". C# does not play nicely with underscores in variable names.
So to access the variable, I type
string fp = Variables.FilePath;
In the PreExecute() method of the Script Component.
On the front properties page of the variable script, amend the ReadOnlyVariables (or ReadWriteVariables) property and select the variables you are interested in. This will enable the selected variables within the script task
Within code you will now have access to read the variable as
string myString = Variables.MyVariableName.ToString();
Strongly typed var don't seem to be available, I have to do the following in order to get access to them:
String MyVar = Dts.Variables["MyVarName"].Value.ToString();
This should work:
IDTSVariables100 vars = null;
VariableDispenser.LockForRead("System::TaskName");
VariableDispenser.GetVariables(vars);
string TaskName = vars["System::TaskName"].Value.ToString();
vars.Unlock();
Your initial code lacks call of the GetVariables() method.