Can't set Orchard field values unless item already created - orchardcms

I seem to be having a problem with assigning values to fields of a content item with a custom content part and the values not persisting.
I have to create the content item (OrchardServices.ContentManager.Create) first before calling the following code which modifies a field value:
var fields = contentItem.As<MyPart>().Fields;
var imageField = fields.FirstOrDefault(o => o.Name.Equals("Image"));
if (imageField != null)
{
((MediaLibraryPickerField)imageField).Ids = new int[] { imageId };
}
The above code works perfectly when against an item that already exists, but the imageId value is lost if this is done before creating it.
Please note, this is not exclusive to MediaLibraryPickerFields.
I noticed that other people have reported this aswell:
https://orchard.codeplex.com/workitem/18412
Is it simply the case that an item must be created prior to amending it's value field?
This would be a shame, as I'm assigning this fields as part of a large import process and would inhibit performance to create it and then modify the item only to update it again.

As the comments on this issue explain, you do need to call Create. I'm not sure I understand why you think that is an issue however.

Related

Where is my error with my join in acumatica?

I want to get all the attributes from my "Actual Item Inventry" (From Stock Items Form) so i have:
PXResultset<CSAnswers> res = PXSelectJoin<CSAnswers,
InnerJoin<InventoryItem,
On<CSAnswers.refNoteID, Equal<Current<InventoryItem.noteID>>>
>
>.Select(new PXGraph());
But, this returns me 0 rows.
Where is my error?
UPDATED:
My loop is like this:
foreach (PXResult<CSAnswers> record in res)
{
CSAnswers answers = (CSAnswers)record;
string refnoteid = answers.RefNoteID.ToString();
string value = answers.Value;
}
... but i can not go inside foreach.
Sorry for the English.
You should use an initialized graph rather than just "new PXGraph()" for the select. This can be as simple as "this" or "Base" depending on where this code is located. There are times that it is ok to initialize a new graph instance, but also times that it is not ok. Not knowing the context of your code sample, let's assume that "this" and "Base" were insufficient, and you need to initialize a new graph. If you need to work within another graph instance, this is how your code would look.
InventoryItemMaint graph = PXGraph<InventoryItemMaint>.CreateInstance<InventoryItemMaint>();
PXResultset<CSAnswers> res = PXSelectJoin<CSAnswers,
InnerJoin<InventoryItem, On<CSAnswers.refNoteID, Equal<Current<InventoryItem.noteID>>>>>
.Select(graph);
foreach (PXResult<CSAnswers> record in res)
{
CSAnswers answers = (CSAnswers)record;
string refnoteid = answers.RefNoteID.ToString();
string value = answers.Value;
}
However, since you should be initializing graph within a graph or graph extension, you should be able to use:
.Select(this) // To use the current graph containing this logic
or
.Select(Base) // To use the base graph that is being extended if in a graph extension
Since you are referring to:
Current<InventoryItem.noteID>
...but are using "new PXGraph()" then there is no "InventoryItem" to be in the current data cache of the generic base object PXGraph. Hence the need to reference a fully defined graph.
Another syntax for specifying exactly what value you want to pass in is to use a parameter like this:
var myNoteIdVariable = ...
InventoryItemMaint graph = PXGraph<InventoryItemMaint>.CreateInstance<InventoryItemMaint>();
PXResultset<CSAnswers> res = PXSelectJoin<CSAnswers,
InnerJoin<InventoryItem, On<CSAnswers.refNoteID, Equal<Required<InventoryItem.noteID>>>>>
.Select(graph, myNoteIdVariable);
foreach (PXResult<CSAnswers> record in res)
{
CSAnswers answers = (CSAnswers)record;
string refnoteid = answers.RefNoteID.ToString();
string value = answers.Value;
}
Notice the "Required" and the extra value in the Select() section. A quick and easy way to check if you have a value for your parameter is to use PXTrace to write to the Trace that you can check after refreshing the screen and performing whatever action would execute your code:
PXTrace.WriteInformation(myNoteIdVariable.ToString());
...to see if there is a value in myNoteIdVariable to retrieve a result set. Place that outside of the foreach block or you will only get a value in the trace when you actually get records... which is not happening in your case.
If you want to get deep into what SQL statements are being generated and executed, look for Request Profiler in the menus and enable SQL logging while you run a test. Then come back to check the results. (Remember to disable the SQL logging when done or you can generate a lot of unnecessary data.)

Can't modify/remove a field from an ActivityNode using sbt

I created an ActivityNode (an Entry) and I can add custom fields with the
setFields(List<Field> newListField)
fonction.
BUT
I am unable to modify these fields. (In this case I try to modify the value of the field named LIBENTITE)
FieldList list = myEntry.getTextFields();
List<Field> updatedList = new ArrayList<Field>();
//I add each old field in the new list, but I modify the field LIBENTITE
for(Field myField : list){
if(myField.getName().equals("LIBENTITE")){
((TextField)myField).setTextSummary("New value");
}
updatedList.add(myField);
}
myEntry.setFields(updatedList);
activityService.updateActivityNode(myEntry);
This code should replace the old list of fields with the new one, but I can't see any change in the custom field LIBENTITE of myEntry in IBM connections.
So I tried to create a new list of fields, not modifying my field but adding a new one :
for(Field myField:list){
if(!myField.getName().equals("LIBENTITE")){
updatedList.add(myField);
}
}
Field newTextField = new TextField("New Value");
newTextField .setFieldName("LIBENTITE");
updatedList.add(newTextField );
And this code is just adding the new field in myEntry. What I see is that the other custom fields did not change and I have now two custom fields named LIBENTITE, one with the old value and the second with the new value, in myEntry.
So I though that maybe if I clear the old list of Fields, and then I add the new one, it would work.
I tried the two fonctions
myEntry.clearFieldsMap();
and
myEntry.remove("LIBENTITE");
but none of them seems to work, I still can't remove a custom field from myEntry using SBT.
Any suggestions ?
I have two suggestions, as I had (or have) similar problems:
If you want to update an existing text field in an activity node, you have to call node.setField(fld) to update the field in the node object.
Code snippet from my working application, where I'm updating a text field containing a (computed) start time:
ActivityNode node = activityService.getActivityNode(id);
node.setTitle(formatTitle()); // add/update start and end time in title
boolean startFound = false;
// ...
FieldList textfields =node.getTextFields();
Iterator<Field> iterFields = textfields.iterator();
while (iterFields.hasNext()) {
TextField fld = (TextField) iterFields.next();
if (fld.getName().equals(Constants.FIELDNAME_STARTTIME)) {
fld.setTextSummary(this.getStartTimeString()); // NOTE: .setFieldValue does *not* work
node.setField(fld); // write updated field back. This seems to be the only way updating fields works
startFound=true;
}
}
If there is no field with that name, I create a new one (that's the reason I'm using the startFound boolean variable).
I think that the node.setField(fld) should do the trick. If not, there might be a way to sidestep the problem:
You have access to the underlying DOM object which was parsed in. You can use this to tweak the DOM object, which finally will be written back to Connections.
I had to use this as there seems to be another nasty bug in the SBT SDK: If you read in a text field which has no value, and write it back, an error will be thrown. Looks like the DOM object misses some required nodes, so you have to create them yourself to avoid the error.
Some code to demonstrate this:
// ....
} else if (null == fld.getTextSummary()) { // a text field without any contents. Which is BAD!
// there is a bug in the SBT API: if we read a field which has no value
// and try to write the node back (even without touching the field) a NullPointerException
// will be thrown. It seems that there is no value node set for the field. We
// can't set a value with fld.setTextSummary(), the error will still be thrown.
// therefore we have to remove the field, and - optionally - we set a defined "empty" value
// to avoid the problem.
// node.remove(fld.getName()); // remove the field -- this does *not* work! At least not for empty fields
// so we have to do it the hard way: we delete the node of the field in the cached dom structure
String fieldName = fld.getName();
DeferredElementNSImpl fldData = (DeferredElementNSImpl) fld.getDataHandler().getData();
fldData.getParentNode().removeChild(fldData); // remove the field from the cached dom structure, therefore delete it
// and create it again, but with a substitute value
Field newEmptyField = new TextField (Constants.FIELD_TEXTFIELD_EMPTY_VALUE); // create a field with a placeholder value
newEmptyField.setFieldName(fieldName);
node.setField(newEmptyField);
}
Hope that helps.
Just so that post does not stay unanswered I write the answer that was in a comment of the initial question :
"currently, there is no solution to this issue, the TextFields are read-only map. we have the issue recorded on github.com/OpenNTF/SocialSDK/issues/1657"

CRM PlugIn Pass Variable Flag to New Execution Pipeline

I have records that have an index attribute to maintain their position in relation to each other.
I have a plugin that performs a renumbering operation on these records when the index is changed or new one created. There are specific rules that apply to items that are at the first and last position in the list.
If a new (or existing changed) item is inserted into the middle (not technically the middle...just somewhere between start and end) of the list a renumbering kicks off to make room for the record.
This renumbering process fires in a new execution pipeline...We are updating record D. When I tell record E to change (to make room for D) that of course fires the plugin on update message.
This renumbering is fine until we reach the end of the list where the plugin then gets into a loop with the first business rule that maintains the first and last record differently.
So I am trying to think of ways to pass a flag to the execution context spawned by the renumbering process so the recursion skips the boundary edge business rules if IsRenumbering == true.
My thoughts / ideas:
I have thought of using the Depth check > 1 but that isn't a reliable value as I can't explicitly turn it on or off....it may happen to work but that is not engineering a solid solution that is hoping nothing goes bump. Further a colleague far more knowledgeable than I said that when a workflow calls a plugin the depth value is off and can't be trusted.
All my variables are scoped at the execute level so as to avoid variable pollution at the class level....However if I had a dictionary object, tuple, something at the class level and one value would be the thread id and the other the flag value then perhaps my subsequent execution context could check if the same owning thread id had any values entered.
Any thoughts or other ideas on how to pass context information to a new pipeline would be greatly appreciated.
Per Nicknow sugestion I tried sharedvariables but they seem to be going out of scope...:
First time firing post op:
if (base.Stage == EXrmPluginStepStage.PostOperation)
{
...snip...
foreach (var item in RenumberSet)
{
Context.ParentContext.SharedVariables[recordrenumbering] = "googly";
Entity renumrec = new Entity("abcd") { Id = item.Id };
#region We either add or subtract indexes based upon sortdir
...snip...
renumrec["abc_indexfield"] = TmpIdx + 1;
break;
.....snip.....
#endregion
OrganizationService.Update(renumrec);
}
}
Now we come into Pre-Op of the recursion process kicked off by the above post-op OrganizationService.Update(renumrec); and it seems based upon this check the sharedvariable didn't carry over...???
if (!Context.SharedVariables.Contains(recordrenumbering))
{
//Trace.Trace("Null Set");
//Context.SharedVariables[recordrenumbering] = IsRenumbering;
Context.SharedVariables[recordrenumbering] = "Null Set";
}
throw invalidpluginexception reveals:
Sanity Checks:
Depth : 2
Entity: ...
Message: Update
Stage: PreOperation [20]
User: 065507fe-86df-e311-95fe-00155d050605
Initiating User: 065507fe-86df-e311-95fe-00155d050605
ContextEntityName: ....
ContextParentEntityName: ....
....
IsRenumbering: Null Set
What are you looking for is IExecutionContext.SharedVariables. Whatever you add here is available throughout the entire transaction. Since you'll have child pipelines you'll want to look at the ParentContext for the value. This can all get a little tricky, so be sure to do a lot of testing - I've run into many issues with SharedVariables and looping operations in Dynamics CRM.
Here is some sample (very untested) code to get you started.
public static bool GetIsRenumbering(IPluginExecutionContext pluginContext)
{
var keyName = "IsRenumbering";
var ctx = pluginContext;
while (ctx != null)
{
if (ctx.SharedVariables.Contains(keyName))
{
return (bool)ctx.SharedVariables[keyName];
}
else ctx = ctx.ParentContext;
}
return false;
}
public static void SetIsRenumbering(IPluginExecutionContext pluginContext)
{
var keyName = "IsRenumbering";
var ctx = pluginContext;
ctx.SharedVariables.Add(keyName, true);
}
A very simple solution: add a bit field to the entity called "DisableIndexRecalculation." When your first plugin runs, make sure to set that field to true for all of your updates. In the same plugin, check to see if "DisableIndexRecalculation" is set to true: if so, set it to null (by removing it from the TargetEntity entirely) and stop executing the plugin. If it is null, do your index recalculation.
Because you are immediately removing the field from the TargetEntity if it is true the value will never be persisted to the database so there will be no performance penalty.

How to assign a value to an Orchard ContentPickerField from code?

I am working on an Orchard site that needs to be able to use some customized forms to create new content items.
To handle this I'm using a controller to display a form and then trying to create the new content items on post back by populating the dynamic items and then sending them through the ContentManagerService's Create() function.
This is working ok until I got to the content picker field I have as part of my content item.
In my project I have a content type of Question Record that has a SubmittedBy field that is a Content Picker Field.
Here is what I can see in the immediate window while processing the post back:
> dynamic q = _questionService.NewQuestion("Why doesn't this work?");
{Custom.Website.Models.Question}
base {Orchard.ContentManagement.ContentPart}: {Custom.Website.Models.Question}
IsNew: true
OriginalQuestion: "Why doesn't this work?"
Summary: null
> q.QuestionRecord
{Orchard.ContentManagement.ContentPart}
base {System.Dynamic.DynamicObject}: {Orchard.ContentManagement.ContentPart}
ContentItem: {Orchard.ContentManagement.ContentItem}
Fields: Count = 5
Id: 0
PartDefinition: {Orchard.ContentManagement.MetaData.Models.ContentPartDefinition}
Settings: Count = 0
TypeDefinition: {Orchard.ContentManagement.MetaData.Models.ContentTypeDefinition}
TypePartDefinition: {Orchard.ContentManagement.MetaData.Models.ContentTypePartDefinition}
Zones: {Orchard.UI.ZoneCollection}
> q.QuestionRecord.SubmittedBy
{Orchard.ContentPicker.Fields.ContentPickerField}
base {Orchard.ContentManagement.ContentField}: {Orchard.ContentPicker.Fields.ContentPickerField}
ContentItems: null
Ids: {int[0]}
The ContentItems property is read-only and the Ids when assigning a new int[] to the Ids array I get a System.ObjectDisposedException with the message: Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed.
Are there any workarounds to get this value set in code or do I need to create my own property to store the related content item ids? It would be very helpful to have the admin interface of the ContentPickerField also available.
Thanks.
If you have a reference to the ContentPickerField, you can assign it a value using the Ids property.
In example (assuming your content type has a part called Question which has a field called SubmittedBy):
var submittedByField = ((dynamic)q.ContentItem).Question.SubmittedBy;
sbmittedByField.Ids = new[] { submittedById };
As Bertrand mentioned, the format to access a content field is: contentItem.PartName.FieldName.
If you attached a field to a type directly via the admin, the part name has the same name as the type name, hence contentItem.TypeName.FieldName (where TypeName is actually the name of the implicitly created part).

Assigning a value to the primary field in code fails

I have a custom entity named eld_timereg. This entity has an attribute named eld_name (which is the primary field). Records are created in a generic handler (after pushing a button in the ribbon). I'm using late binding.
var myService = ...;
var t = new Entity( "eld_timereg" );
t["field1"] = "abc";
.
.
.
t["eld_name"] = GenerateAnUniqueStringCode("ZXC");
// returns something like ZXC-16398-T1VC
return myService.Create(t);
The record is created without any error. Checking the entity in SSMS, the value is blank although it is a required field.
What is happening here?
Found the problem. You put me on the right track James. Thank you for that. The eld_name attribute was overwritten in the plug-in (I forgot). Here it should be filled with some information. This was going wrong and always returned a null.

Resources