MongoDb bulk operation get id - c#

I want to perform bulk operation via MongoDb. How to get array of Ids that will be returned after it?
Can i perform single-operation insert faster without using bulk ? Can you advise me some other approach ?
I'm using C# mongoDb driver 2.0 and MongoDb v. 3.0.2
update:
I found the following solution - save maximum ObjectId of mongo collection,
db.col.find().sort({_id:-1}).limit(1).pretty()
and do the same after insert
So we will get the range of inserted documents, does it make a sense?

You can insert items in bulk using the new driver with InsertManyAsync. If you want the Ids that the driver generated for these items you can simply get them out of the items themselves after they are inserted. For example:
Hamster[] hamsters = { new Hamster { Name = "Vaska" }, new Hamster { Name = "Petzka" } };
await collection.InsertManyAsync(hamsters);
var insertedIDs = hamsters.Select(_ => _.Id);

Related

Prevent possible duplicates on MongoDb Document Array

I have have a bunch of information that is saved inside an array of a document in MongoDb, the structure is something like this:
{
"Ticker":"TSLA34",
"History":[
{
"Price":26.36,
"UpdatedAt":"10/22/2015 10:12:00 AM"
},
{
"Price":26.37,
"UpdatedAt":"10/22/2015 10:13:00 AM"
}
]
}
Im saving the information inside the "History" array, and this information is time based, i need to insert minute by minute.
Sometimes the same minute came to me two or three times in a row. So i need to check if the current minute that i received is already inserted in this array before push into this array and save it in MongoDb, but only if the whole document does not exist, i need to create it and insert the information.
Im using MongoDb.Driver in .NET 6 and i build this method to insert the information only if the "UpdatedAt" field is not already saved into the "History" array
// var item = new Chart("TSLA34", new HistoryEntry(26.36, DateTime.UtcNow))
var filterByTicker = Builders<Chart>.Filter.Eq(g => g.Ticker, item.Ticker);
var filterByDate = Builders<Chart>.Filter.ElemMatch(g => g.History, Builders<HistoryEntry>.Filter.Eq(x => x.UpdatedAt, item.History.First().UpdatedAt));
var filter = filterByTicker & Builders<Chart>.Filter.Not(filterByDate);
var update = Builders<Chart>.Update.Push(asset => asset.History, item.History.First());
var updateOneModel = new UpdateOneModel<Chart>(filter, update) {IsUpsert = true};
I need to create the document if it isn't created, but if it is already created i need to verify if this information minute is inserted into the history.
I can do this doing a Find search and check it with Linq before do the Update process, but i was wondering if i can do this more efficiently with MongoDb.
Anyone has an idea?

Fetch row number MongoDB c#

I am fetching a single candidate exam result details after the
examination. which is stored in mongodb using c# driver. The
collection has TotalMarks field which is stored with marks obtained in
that exam.
Unfortunately the collection does not have the Rank Field because mark
calculation is not done in order
What I want to do is order the collection by totalmark and get the position(rank) of the candidate I am selecting.
public ExamCandidateResult ExaminationGetCandidateResultStatus( Guid examinationId, Guid candidateId)
{
var con = new MongoClient(DBConnection.ExamConnectionString);
var db = con.GetDatabase(ExamDB);
var collection = db.GetCollection<ExamCandidateResult>("Examination");
var filter = Builders<ExamCandidateResult>.Filter.Eq("ExaminationID", examinationId.ToString())
& Builders<ExamCandidateResult>.Filter.Eq("CandidateID", candidateId.ToString());
var data = collection.Find(filter).FirstOrDefault();
return data;
}
With this code I am fetching only the canidate details how can I fetch
the rank(row) with it ?
I don't think you can get the row number directly but You can use two queries, one to get the candidate and one to get the count of candidates who have more totalMarks than the desired candidate, and finally plus one count to get the rank of the candidate.

Add compound Index to MongoDB API in Cosmos

I am using sorting in my application and using cosmos mongodb api as database. I have created wildcard index in the code to accommodate the sorting but somehow I am still getting the error as compound index is missing. Here is how I am creating the index.
var wcIndex = new IndexKeysDefinitionBuilder<T>().Wildcard();
var wcIndexModel = new CreateIndexModel<T>(wcIndex, options);
_collection = _database.GetCollection<T>(_collectionName);
_collection.Indexes.CreateOneAsync(wcIndexModel);
and the error am getting is
The index path corresponding to the specified order-by item is
excluded / The order by query does not have a corresponding composite
index that it can be served from.
Is there anyway I can add composite index for model to get the sorting done ?
I have added the below code to make it work for using sorting in mongo Cosmos API.
IndexKeysDefinition<T> keys = "{'$**': 1}";
var wildcardIndex = new CreateIndexModel<T>(keys);
_collection = _database.GetCollection<T>(_collectionName);
_collection.Indexes.CreateOne(wildcardIndex);

Is a MongoDB bulk upsert possible? C# Driver

I'd like to do a bulk upsert in Mongo. Basically I'm getting a list of objects from a vendor, but I don't know which ones I've gotten before (and need to be updated) vs which ones are new. One by one I could do an upsert, but UpdateMany doesn't work with upsert options.
So I've resorted to selecting the documents, updating in C#, and doing a bulk insert.
public async Task BulkUpsertData(List<MyObject> newUpsertDatas)
{
var usernames = newUpsertDatas.Select(p => p.Username);
var filter = Builders<MyObject>.Filter.In(p => p.Username, usernames);
//Find all records that are in the list of newUpsertDatas (these need to be updated)
var collection = Db.GetCollection<MyObject>("MyCollection");
var existingDatas = await collection.Find(filter).ToListAsync();
//loop through all of the new data,
foreach (var newUpsertData in newUpsertDatas)
{
//and find the matching existing data
var existingData = existingDatas.FirstOrDefault(p => p.Id == newUpsertData.Id);
//If there is existing data, preserve the date created (there are other fields I preserve)
if (existingData == null)
{
newUpsertData.DateCreated = DateTime.Now;
}
else
{
newUpsertData.Id = existingData.Id;
newUpsertData.DateCreated = existingData.DateCreated;
}
}
await collection.DeleteManyAsync(filter);
await collection.InsertManyAsync(newUpsertDatas);
}
Is there a more efficient way to do this?
EDIT:
I did some speed tests.
In preparation I inserted 100,000 records of a pretty simple object. Then I upserted 200,000 records into the collection.
Method 1 is as outlined in the question. SelectMany, update in code, DeleteMany, InsertMany. This took approximately 5 seconds.
Method 2 was making a list of UpdateOneModel with Upsert = true and then doing one BulkWriteAsync. This was super slow. I could see the count in the mongo collection increasing so I know it was working. But after about 5 minutes it had only climbed to 107,000 so I canceled it.
I'm still interested if anyone else has a potential solution
Given that you've said you could do a one-by-one upsert, you can achieve what you want with BulkWriteAsync. This allows you to create one or more instances of the abstract WriteModel, which in your case would be instances of UpdateOneModel.
In order to achieve this, you could do something like the following:
var listOfUpdateModels = new List<UpdateOneModel<T>>();
// ...
var updateOneModel = new UpdateOneModel<T>(
Builders<T>.Filter. /* etc. */,
Builders<T>.Update. /* etc. */)
{
IsUpsert = true;
};
listOfUpdateModels.Add(updateOneModel);
// ...
await mongoCollection.BulkWriteAsync(listOfUpdateModels);
The key to all of this is the IsUpsert property on UpdateOneModel.

C# - Updating a row using LINQ

I am in the process of improving a console app and at the moment I cant get it to update rows instead of just creating a new row with the newer information in it.
class Program
{
List<DriveInfo> driveList = DriveInfo.GetDrives().Where(x => x.IsReady).ToList<DriveInfo>(); //Get all the drive info
Server server = new Server(); //Create the server object
ServerDrive serverDrives = new ServerDrive();
public static void Main()
{
Program c = new Program();
c.RealDriveInfo();
c.WriteInToDB();
}
public void RealDriveInfo()
{
//Insert information of one server
server.ServerID = 0; //(PK) ID Auto-assigned by SQL
server.ServerName = string.Concat(System.Environment.MachineName);
//Inserts ServerDrives information.
for (int i = 0; i < driveList.Count; i++)
{
//All Information used in dbo.ServerDrives
serverDrives.DriveLetter = driveList[i].Name;
serverDrives.TotalSpace = driveList[i].TotalSize;
serverDrives.DriveLabel = driveList[i].VolumeLabel;
serverDrives.FreeSpace = driveList[i].TotalFreeSpace;
serverDrives.DriveType = driveList[i].DriveFormat;
server.ServerDrives.Add(serverDrives);
}
}
public void WriteInToDB()
{
//Add the information to an SQL Database using Linq.
DataClasses1DataContext db = new DataClasses1DataContext(#"sqlserver");
db.Servers.InsertOnSubmit(server);
db.SubmitChanges();
What I would like it to use to update the information would be the RealDriveInfo() Method so instead of creating new entries it updates the currently stored information by running the method then inserting the information from the method and if needed will enter a new entry instead of simply entering new entries every time it has newer information.
At the moment it is running the method, gathering the relevant data then entering it in as a new row in both tables.
Any help would be appreciated :)
It's creating a new db entry each time because you are making a new server object each time, then calling InsertOnSubmit() - which inserts (creates) a new record.
I'm not entirely sure what you are trying to do, but a db update would involve selecting an existing record, modifying it, then attaching it back to the data context and calling SubmitChanges().
This article on Updating Entities (Linq toSQL) might help.
The problem is that you are trying to achieve Update functionality with a tool that is designed to provide object-oriented quering. LINQ allows for updating exisitng records, but you have to use it in a proper way to achieve this.
The proper way is to fetch data you want to update from the DB, perform modifications and then flush it back to the DB. So, assuming there are table named Servers in your data context, here's an abstract example:
DataClasses1DataContext db = new DataClasses1DataContext(#"sqlserver");
var servers = db.Servers.Where(srv=>srv.ID>1000); //extracting all servers with ID > 100 using lambda expression
foreach (server in servers){
server.Memory *=2; //let's feed them up with memory
}
db.Servers.SubmitChanges();
Another way to achieve this is to create an entity, than attach it to the DataContext using Table.Attach method, but it's quite a slippery slope, so I wouldn't recommend you taking it unless you have your LINQ skills improved.
For a detailed description, see
SubmitChanges
Lambda Expressions
I understand what is being asked, and I do not have an easy answer.
Example, you have a form of values, several of the values are changed, maybe some calculated. Or the form can contain a new record.
You create a record of the values
myrecord = new MyRecord()
Then fill in myRecord. doing what ever validation/calculations you want before you even touch the database itself.
//GetID either returns an existing ID or it returns a zero if this is a new record.
myrecord.id = GetIDForRecordOrZeroIfANewRecord(uniqueName);
myrecord.value1 = txtValue1.text;
myrecord.value2 = (DateTime)dtDate.value;
and so on through the fields.
You now have a record, if id is zero you can add it as a new record. But if id is an existing record you seem to have no choice with Linq except to have a function that writes each value from myrecord, so you have to have a function that contains something like -
var thisRecord = from n in mydatacontext.MyTable
where n.id == myrecord.id
select n;
thisrecord.value1 = myrecord.value1;
thisrecord.value2 = myrecord.value2;
and so on through all fields.
I do it, but it seems long winded when I already have all of the information ready in myrecord. A simple function of
mydatacontext.MyTable.Update(myrecord);
Would be ideal. Simmilar in fact to what I do with stored SQL functions in other databases, it simplifies the transfer of a record that is an update rather than new.

Categories