mongodb C# error on first Push - c#

I've a Parent Entity that has a property of list of Child Entities. Initially on Insert I don't have values for the child entities.
But when I try to Update(by calling push) the document with Child Entities it fails.
This works when I insert a dummy child entity value to the Initial Add .
This is because the embedded document refers to null .
public class ParentDocument : Entity
{
public string prop1 { get; set; }
public List<EmbeddedDocument> EmbeddedDocuments { get; set; }
}
public class EmbeddedDocument
{
public string prop2{ get; set; }
}
The parent is saved First
_collection.InsertOne(new ParentDocument(){prop1 ="value"});
and later when I Update the document
var builder = Builders<ParentDocument>.Update;
var updateDefintion = builder.Push(x => x.EmbeddedDocuments ,new EmbeddedDocument() { prop2= "value2" });
_collection.UpdateManyAsync(x=>x.Id==ParentDocumentId, updateDefinition)
error occurs "A write operation resulted in an error mongodb"
But this push works if I have already inserted Embedded Document(s) in the List on first insert.
I think that is because of that the EmbeddedDocuments property is Inserted as null the push doesn't work.
I also tried passing empty List to intial Insert,but not helped.
One Idea would be to check if the count of List of Embedded documents is zero and call
Builder.set(x=>x.EmbeddedDocuments ,new List<EmbeddedDocument>(){ item1 });
But this will cost a query , which I don't want to.
Is there any other solution?
Thanks in Advance

To Hazard a guess, it's because the "array" field in the database is null after the insert. You either need to make the initial value in the database an empty array, or you need to make it not-present. You can either:
use the [BsonIgnoreIfDefault] attribute on your list field to not store nulls,
Initialize your list field to an empty list to store an empty array
This can be reproduced in the shell very easily:
> db.so.insert({x:1, y: null})
> db.so.update({x:1}, {$push: { y: "funny" }})
This will error. However, if you remove y from the insertion or change it to an empty array, the update will succeed.

Related

How to update entire document except ID with mongodb c# driver

At the moment i'm updating the entire document, but if the ID changes i get error message:
MongoDB.Driver.MongoWriteException: 'A write operation resulted in an error.
After applying the update, the (immutable) field '_id' was found to have been altered to _id: BinData(3, B3FD0EE0FF161845BE96BE40A7DDE84B)'
So i want it to ignore the ID field when updating a document.
Here's what i'm doing now:
public async Task<bool> UpdateMatch(Guid id, Match match)
{
ReplaceOneResult actionResult
= await _context.Match.ReplaceOneAsync(m => m.Id.Equals(id),
match,
new UpdateOptions { IsUpsert = true });
return actionResult.IsAcknowledged && actionResult.ModifiedCount > 0;
}
Thanks in advance for your help!
Add 'BsonIgnoreIfDefault' attribute to the InternalId property.
public class Document
{
[BsonId]
[BsonIgnoreIfDefault]
public ObjectId InternalId { get; set; }
// rest of document
}
The problem which is happening is that MongoDB will add the _id field to the replacement document if it is not specified in either the filter or replacement documents if ReplaceOneAsync is used. If _id is present in both, the values must be equal. However, if I understand your code properly you are trying to find a document by ID and replace it. There is a collection method called FindOneAndReplace() or FindOneAndReplaceAsync() that I would have used if I were you. You might want to check out the MongoDB documentation for this:
https://docs.mongodb.com/manual/reference/method/db.collection.replaceOne/
https://docs.mongodb.com/manual/reference/method/db.collection.findOneAndReplace/
Hope this helps you!

How to use AutonumberAttribute.getNextNumber()?

When I use the AutonumberAttribute.getNextNumber(), it gives me the next number of the sequence but it also make the next number to change.
IE if I call 2 time in a row:
nextNumber = AutoNumberAttribute.GetNextNumber(ARLetteringPiece.Cache, LetteringPiece, numbering, DateTime.Now);
first time i'll get "0000001"
second time i'll get "0000002"
I want to be able to know what the next number will be without modifying it's next value.
Is there a way to achieve this ?
Thanks a lot
Edit to answer the comments :
I have a custom table, my UI key is generated with Autonumbering, and I need to put this key in the lines of my other tables to "bind" them to my custom table. So I need to know what will be the autogenerated number.
It depends on the relationship between your DACs (tables).
You can solve this by using the PXDBChildIdentity in the fields of all the tables that need to store the new key.
For example, if your DAC's autonumber field is of type integer and is called MyDAC.MyAutonumberField.
You can add the attribute to all fields in your other DACs that need to store the value like this:
[PXDBInt()]
[PXDBChildIdentity(typeof(MyDAC.myAutonumberField))]
public virtual int? MyDACID { get; set; }
If the other DACs are "children" of your custom DAC you should use the PXParent attribute in all the child DACs on the field that references their parent like this:
[PXDBInt(IsKey = true)]
[PXDBDefault(typeof(MyDAC.myAutonumberField))]
[PXParent(typeof(Select<MyDAC,
Where<MyDAC.myAutonumberField,
Equal<Current<myAutonumberField>>>>))]
public virtual int? MyParentDacID { get; set; }
I managed to do it in another way : First I save my "header", then I update the lines with the value autogenerated for my header and then I save it again.
public static void createLettering(List<ARRegister> lines)
{
// We build a new LELettering piece
Lettrage graph = CreateInstance<Lettrage>();
LELettering piece = new LELettering();
piece.Status = ListStatus._OPEN;
piece.LetteringDateTime = DateTime.Now;
piece = graph.ARLetteringPiece.Insert(piece);
// We fill the checked lines with the autonumber of the piece
bool lineUpdated = false;
foreach (ARRegister line in lines)
{
if (line.Selected.Value)
{
if (!lineUpdated)
{
piece.BranchID = line.BranchID;
piece.AccountID = line.CustomerID;
piece = graph.ARLetteringPiece.Update(piece);
graph.Actions.PressSave();
}
line.GetExtension<ARRegisterLeExt>().LettrageCD = graph.ARLetteringPiece.Current.LetteringCD;
graph.ARlines.Update(line);
lineUpdated = true;
}
}
// If there are lines in our piece, we save it
// It saves our lettering piece and our modifications on the ARLines
if (lineUpdated)
{
graph.Actions.PressSave();
}
}

Use string which is the same as the database-column-name to recognize which property to use

(Just using the {{ }} to tell which data is variable)
This gets the data from the database and puts it into a property. The Table_Column and PropertyName are usually not the same.
[Column(Name = "{{Table_Column}}")]
public string {{PropertyName}} { get; set; }
In this class the data (which is contained by properties in the location of {{PropertyName}})is connected to the docElement. docElement contains the name of the element and the name of the Table_Column.
var documentElements = GetAllElements();
foreach (DocumentElement docElement in documentElements)
{
ConnectData<Document>(docElement.ElementName, (doc) => doc.{{PropertyName}});
}
I want to connect data to my Elements using my function ConnectData, which requires me to give an ElementName and the data I want to connect to the Element. The documentElements, which I loop through with a foreach, all have an ElementName and ElementType. That ElementType is the same as {{Table_Column}} of the database. With that I want to find which property I need to call to get the right data.
Thanks in advance!
Questions? Please leave a comment.

Parsing xml and selecting just specific positions from an array

I'm parsing an xml document, that is like this:
<?xml version="1.0" encoding="UTF-8" ?>
....
<maj>true</maj>
<data>
<c>2</c>
<t>0</t>
<r>168</r>
<r>La rentrée Auchan</r>
<r>0</r>
<r>2012-08-21 00:00:00</r>
<r>2012-08-28 00:00:00</r>
<r>56</r>
<r>VL</r>
<c>2</c>
<t>1</t>
...
</data>
I want to get what there is inside the array "r", but only the first position 1 the position 5 and 6, and only where t=0
I've tried to work like this, I have a listbox that bind the data :
XDocument XMLtxt = JsonConvert.DeserializeXNode(e.Result);
listClients.ItemsSource =
from c in XMLtxt.Descendants()
select new JsonB()
{
t=c.Element("t").Value.Where(x=>(int) x==0),
r1=c.Element("r").Select(..
}
the jsonB :
public class JsonB
{
public int c { get; set; }
public int t { get; set; }
public string r1 { get; set; }
public int r5 { get; set; }
public string r6 { get; set; }
public object[] r { get; set; }
}
I really need help, thank you
The XML is a bit ill-structured, so you will have to fix this first with your code.. For starters, in a simple way you can sequentially read/parse the contents so that the structure of the record is clear, and then filter them:
prepare a list of JsonB objects - name it i.e. 'allRecords'
prepare a variable of type JsonB - name it i.e. 'lastRecord'
prepare a variable of type int - name it i.e. 'numberOfRs'
get the <data> tag
loop over all of its direct children, and for each child:
check the name of the child
if it is <c>:
create new JsobB and put it into lastRecord
reset the numberOfRs to zero
add the lastRecord to the allRecords list
read the c value and put it into lastRecord.c
if it is <t>:
read the value and put it into lastRecord.t
if it is <r>:
add 1 to the numberOfRs
if numberOfRs is 1, 5 or 6:
read the value and put it into lastRecord.r1 or r5 or r6
this way, you will have a list allRecords with pretty objects, and you can simply .Where(item => item.t ==0) on it.
However, you may notice it to be very 'wasteful' if many of such objects are to be ignored. Then, you could adjust the sequential parser to filter then on-the-fly and thus behave like this
prepare a list of JsonB objects - name it i.e. 'allRecords'
prepare a variable of type JsonB - name it i.e. 'lastRecord'
prepare a variable of type int - name it i.e. 'numberOfRs'
prepare a variable of type bool - name it i.e. 'isInteresting', preset it to false
get the <data> tag
loop over all of its direct children, and for each child:
check the name of the child
if it is <c>:
if isInteresting is true
add the lastRecord to the allRecords list
create new JsobB and put it into lastRecord
reset the numberOfRs to zero
add the lastRecord to the allRecords list reset the isInteresting to false
read the c value and put it into lastRecord.c
if it is <t>:
read the value and put it into lastRecord.t
if it was zero, set the isInteresting to true
if it is <r>:
add 1 to the numberOfRs
if numberOfRs is 1, 5 or 6:
read the value and put it into lastRecord.r1 or r5 or r6
if isInteresting is true
add the lastRecord to the allRecords list
This way, you will end up with a list that is already filtered and all not interesting items will be GC'ed in the meantime. Please note that the 'isinteresting - add to list' is done twice: once before creatig new JsonB, and then also in the final step when all children has been read. If you forget about the check-add after the loop - you may sometimes accidentially skip/ignore the last record.

Remove a property/column from a generic list

Due to some reason I cannot change the query so I have to do this in C#.
I have a class:
public class myClass
{
int id { get; set; }
string name { get; set; }
DateTime sDate { get; set; }
bool status { get; set; }
}
The data I am getting is fetched in this list. Now what I want is to remove those properties from a list that has null values. I may sound insane but you read it right. I thought of creating another list with only the selected properties, but any of the above properties can be null. So I have to devise a mechanism to filter my list based on this.
For more clarity consider the following example.
List<myClass> lstClass = some data source.
After getting the data the generic list(lstClass) looks like this.Consider the result set in a table:
Id Name Sdate status
1 a null null
2 b null null
3 c null false
Can i some how make my list look like this after removing the property sdate.
So the new list that I want to create should have only three properties.
Id Name status
1 a null
2 b null
3 c false
Any ideas? Can I do this using Linq?
PS: This has nothing to do with presentation. I don’t have a grid where I am not able to hide columns that Is not what I am looking for.
Assuming you have a generic list of myClass instances, you can create an anonymous type with only the needed properties:
List<myClass> list = ...;
var reducedList = list.Select(e => new {e.id, e.name, e.status}).ToList();
// note: call to ToList() is optional
foreach (var item in reducedList)
{
Console.WriteLine(item.id + " " + item.name + " " + item.status);
//note: item does not have a property "sDate"
}
I'm not sure you should solve your issue in the Data, but rather it's a presentation problem.
In which control do you want to display it ? Let's say you display it in DataGrid with AutoGenerateColumns=True, then you can 1) loop on columns/properties 2) for each column/property see if all property values for all rows are null and if so set column's visibility to Collapsed.
If you generate your columns by yourself it's even simpler : only add columns when content is not null for all rows.
If your DB content is dynamic, you might want to bind each row's visibility to a property that would state wether all rows are null or not for that property. Depending on how generic you want your code to be, the code might be very different, and in case you want to have generic solution, using Reflection to retrieve/get/set properties might be of some use.

Categories