I have a set of tables (ConditionTemplate and KeyWord) that have a many to many relationship. In code I am trying to add a Keyword to a specific ConditionTemplate record. Unfortunately, when I think I'm adding a Keyword to a specific condition I'm getting an error as if it's adding a new Keyword without being associated to a condition.
An Image of my Model:
My Code:
Global Variables Creation:
EnterpriseEntities EE;
ConditionTemplate myConditionTemplate;
Load Global Variables:
EE = new EnterpriseEntities();
EE.Database.Connection.ConnectionString = Myapp.EnterpriseEntityConnectionString;
myConditionTemplate = EE.ConditionTemplates.Where(c => c.TemplateCode == "17D").FirstOrDefault();
The above code loads a single Condition with Many Keywords.
Available Keywords are in a listbox and the user pushed a button to select a keyword(s) to move to the condition. This is the code that handles that.
foreach (KeyWord SelectedKeyWord in ListBoxAvailableKeyWords.SelectedItems)
{
KeyWord NewKeyWord = new KeyWord
{
KeyWordID = SelectedKeyWord.KeyWordID,
ID = SelectedKeyWord.ID,
Word = SelectedKeyWord.Word
};
myConditionTemplate.KeyWords.Add(NewKeyWord);
}
Then the user pushes a button to save changes and I call
EE.SaveChanges
Then I get this error:
System.Data.UpdateException: An error occurred while updating the
entries. See the inner exception for details. --->
System.Data.SqlClient.SqlException: Violation of UNIQUE KEY constraint
'IX_KeyWord'. Cannot insert duplicate key in object 'dbo.KeyWord'. The
duplicate key value is (ADJUDICATION). The statement has been
terminated.
If I remove the code that sets the word property (Word = SelectedKeyWord.Word
) when I create the keyword object I get this error.
System.Data.Entity.Validation.DbEntityValidationException: Validation
failed for one or more entities. See 'EntityValidationErrors' property for more details.
Which tells me the word field is required.
In order to tell EF that the KeyWords you selected already exist in the database and to avoid the problem you must attach them to the context:
foreach (KeyWord SelectedKeyWord in ListBoxAvailableKeyWords.SelectedItems)
{
KeyWord NewKeyWord = new KeyWord
{
// You actually only need to set the primary key property here
ID = SelectedKeyWord.ID
};
EE.KeyWords.Attach(NewKeyWord);
myConditionTemplate.KeyWords.Add(NewKeyWord);
}
Edit
If the KeyWord entities are already attached to your context (because they have been loaded before with the same context for example) you can use instead:
foreach (KeyWord SelectedKeyWord in ListBoxAvailableKeyWords.SelectedItems)
{
KeyWord NewKeyWord = EE.KeyWords.Find(SelectedKeyWord.ID);
myConditionTemplate.KeyWords.Add(NewKeyWord);
}
Related
I have CoreData with simple relationships as you can see below. One entity Word with 4 attributes and a Chapter entity with a one to many relationship (each word figures in only one chapter and chapters contains several words). When I try to import a file with a list of words and associated chapter, the chapters which are not yet in the database are created (which is what I want) but the chapters that already exists are created a 2nd time (new same entry in coredata). Is there an option I can activate in xcdatamodel to check and avoid duplicate entries on the relation entity?
Code details ->
fileprivate func saveAllWords(_ items: [(name: String, definition: String, example: String, chapter: String)]?) {
for item in items! {
let newWord = Word(context: self.context)
newWord.name = item.name.trimmingCharacters(in: .whitespaces)
newWord.definition = item.definition.trimmingCharacters(in: .whitespaces)
newWord.example = item.example.trimmingCharacters(in: .whitespaces)
newWord.option = 10 // option tag indicating that it's a new entry from external fileI generate a classic word
//
let myNewChapter = Chapter(context: self.context)
myNewChapter.name = item.chapter
newWord.chapter = myNewChapter
}
……
// Save the data in Core Data
do {
try self.context.save()
}
catch {
}
Any recommendation how to implement this uniqueness constraint to solve my duplicate issue?
You have to create a constraint for the unique attribute. It looks like you want the name of the Chapter to be unique
In your xcdatamodeld select your attribute, then right constraint and add they attribute.
Last but not least you will have to add merge policy for your Context, mostly likely in your AppDelegate. There are different merge policy. You should check them out which fits your needs most
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
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.)
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"
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.
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.