MongoDb Update one field using BulkWriteAsync - c#

I am trying to add one property in some of my documents (later I will be updating it).
My code looks like below:
public async Task<bool> InsertTagsToPeople(IEnumerable<People> toInsert)
{
var collection = _mongoClient.GetPeopleDetailsCollection();
try
{
var updates = new List<WriteModel<People>>();
foreach (var doc in toInsert)
{
var filter = Builders<People>.Filter.And(
Builders<People>.Filter.Eq(x => x.Address, doc.Address),
Builders<People>.Filter.Eq(x => x.Type, doc.Type));
var update = Builders<People>.Update.Set(x => x.Tags, doc.Tags);
updates.Add(new UpdateOneModel<People>(filter, update){IsUpsert = false});
}
var result = await collection.BulkWriteAsync(updates);
return true;
}
}
Problem with this code is that it is not setting Tags property. Nothing on Database is changing.
In result object I have properties like this:
When I change IsUpsert = true it throws error
Category : "DuplicateKey", Code : 11000, Message : "E11000 duplicate
key error collection:
I don`t understand why it is trying to update whole model instead just one property. But I suppose that IsUpsert do not matter here. Update.Set should do the work.

Related

How to convert SQLQuery to SortedList through EF6

I have an Entity Framework 6 class called Materials, which is reflected in my database as a table with the same name. Using a parent parameter, I need to return a sorted list of materials from a SQL Query, so that I can later check that edits the user makes do not affect the order. My SQL is a stored procedure that looks like this:
CREATE PROC [dbo].[GET_SortedMaterials](#FinishedGoodCode VARCHAR(50))
AS
SELECT
ROW_NUMBER() OVER (ORDER BY Component.Percentage_of_Parent DESC,Material.Material) AS _sortField
,Material.*
FROM
Components AS Component
INNER JOIN Materials AS Material ON Component.Child_Material = Material.Material
WHERE
Component.Parent_Code = #FinishedGoodCode
ORDER BY
Component.Percentage_of_Parent DESC
,Material.Material
As you can see, the orderby field is not included in the Material. For this reason, I felt I could not return just a set of Material objects and still keep the sorting - I have performed the ordering in SQL and added the _sortField (I think that field may be a bad idea).
My C# code to read the SQL looks like this:
public async Task<SortedList<int, Materials>> GET_SortedMaterials(IProgress<Report> progress, string finishedGoodCode)
{
try
{
var report = new Report { Message = "Retrieving Sorted Materials", NewLine = true, StatusCode = Enums.StatusCode.Working };
progress.Report(report);
using (var context = new DBContext())
{
var ingredientList = await context.Database.SqlQuery<(int _sortField,Materials mat)>("[app].[GET_Customers]").ToListAsync();
var sorted = new SortedList<int, Raw_Materials>();
foreach (var (_sortField, mat) in ingredientList.OrderBy(x=>x._sortField))
{
sorted.Add(_sortField, mat);
}
return sorted;
}
}
catch (Exception ex)
{ [EXCLUDED CODE]
}
}
When the code executes, I get the correct number of rows returned, but I do not get a Sorted list where the Key corresponds to the _sortField value and the Value to the Material value. I have tried various different versions of basically the same code and I cannot get the script to return a list of materials with information about their sorting, instead, the conversion to EF class fails entirely and I only get null values back:
Any advice about how to return a sorted list from SQL and maintain the sorting in C#, when the sort field is not in the return values would be very gratefully received.
use
var ingredientList = await context.Database.SqlQuery<Materials>("[app].[GET_Customers]").Select((mat, _sortField) => (_sortField, mat)).ToDictionary(x => x._sortField, x => x.mat);
or if you want async load use
var ingredientList = await context.Database.SqlQuery<Materials>("[app].[GET_Customers]").ToListAsync().Result.Select((mat, _sortField) => (_sortField, mat)).ToDictionary(x => x._sortField, x => x.mat);
full code
public async Task<SortedList<int, Materials>> GET_SortedMaterials(IProgress<Report> progress, string finishedGoodCode)
{
try
{
var report = new Report { Message = "Retrieving Sorted Materials", NewLine = true, StatusCode = Enums.StatusCode.Working };
progress.Report(report);
using (var context = new DBContext())
{
var ingredientList = await context.Database.SqlQuery<Materials>("[app].[GET_Customers]").ToListAsync().Result.Select((mat, _sortField) => (_sortField, mat)).ToDictionary(x => x._sortField, x => x.mat);
var sorted = new SortedList<int, Raw_Materials>();
foreach (var item in ingredientList.OrderBy(x => x.Key))
{
sorted.Add(item.Key, item.Value);
}
return sorted;
}
}
catch (Exception ex)
{
[EXCLUDED CODE]
}
}

JSON returntype sending NULL to JSON response for navigational properties (Asp.Net Core 3)

I am returning various values to JSON response like this
return Json(new
{
alldish.BasePrice,
alldish.Allergies,
alldish.Description,
alldish.DishName,
alldish.DishSizes
});
this works fine but when i try to send foreign key object inaddition to these properties through same procedure it gives me error instead of giving values
return Json(new
{
alldish.BasePrice,
alldish.Allergies,
alldish.Description,
alldish.DishName,
alldish.DishSizes,
alldish.DishExtraTypes
});
Actually I want to send same object to another action as a parameter so it can be opened on Edit Page
what are the other possible ways to achieve the same thing?
DishExtraTypes seems to be a collection navigation property. You may need to load the related data of your foreign key properties either eagerly, explicitly or lazily :
https://learn.microsoft.com/en-us/ef/core/querying/related-data
If you choose eager loading:
using (var context = new DishContext())
{
var dishes = context.Dishes
.Include(dish => dish.DishSizes)
.Include(dish => dish.DishExtraTypes)
.ThenInclude(dishExtraType => dishExtraType.DishExtras)
.ThenInclude(dishExtra => dishExtra.SizeToppingPrices)
.ToList();
}
If you choose lazy loading:
// In DbContext
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseLazyLoadingProxies()
.UseSqlServer(myConnectionString);
#L. Vallet was right on that I had to reference it which i did, i performed eager loading like
Actually here DishSize DishExtraTypes were navigational Properties of Dish and they had their own navigational properties as well so i had to get all of them and returned them to JSON response in the form of list As I wanted it in JSON so syntax was as follows
return Json(new
{
alldish.BasePrice,
alldish.Allergies,
alldish.Description,
alldish.DishCategoryId,
alldish.DishName,
alldish.DishSizes,
alldish.SubName,
alldish.Rating,
DishSize = alldish.DishSizes.Select(dishsize =>
new
{
BasePrice = dishsize.BasePrice,
Diameter = dishsize.Diameter,
Size = dishsize.Size
}),
DishExtraTypes = alldish.DishExtraTypes.Select(det => new
{
ChooseMultiple = det.ChooseMultiple,
Status = det.Status,
TypeName = det.TypeName,
DishExtras = det.DishExtras.Select(dex =>
new
{
ExtraName = dex.ExtraName,
ExtraPrice = dex.ExtraPrice,
Allergies = dex.Allergies,
IsAvailable = dex.IsAvailable,
ToppingPrices = dex.SizeToppingPrices.Select(stp =>
new
{
SizeName = stp.SizeName,
Price = stp.Price
})
})
}).ToList()
});

Insert collection into List from MongoDB

I try to get all data from collection into MongoDB server using C# driver.
The idea is connect to the server and get all collection than insert into list of class.
List<WatchTblCls> wts;
List<UserCls> users;
List<SymboleCls> syms;
public WatchTbl()
{
InitializeComponent();
wts = new List<WatchTblCls>();
users = new List<UserCls>();
syms = new List<SymboleCls>();
}
public async void getAllData()
{
client = new MongoClient("mongodb://servername:27017");
database = client.GetDatabase("WatchTblDB");
collectionWatchtbl = database.GetCollection<WatchTbl>("Watchtbl");
collectionUser = database.GetCollection<UserCls>("Users");
collectionSymbole = database.GetCollection<SymboleCls>("Users");
var filter = new BsonDocument();
using (var cursor = await collectionWatchtbl.FindAsync(filter))
{
while (await cursor.MoveNextAsync())
{
var batch = cursor.Current;
foreach (var document in batch)
{
wts.Add(new WatchTblCls(document["_id"], document["userId"], document["wid"], document["name"], document["Symboles"]));
}
}
}
}
I get this error under
wts.Add(new WatchTblCls(document["_id"], document["userId"], document["wid"], document["name"], document["Symboles"]));
Cannot apply indexing with [] to an expression of type 'WatchTbl'
I don't understand the reason behind using WatchTbl and WatchTblCls both together. Is WatchTblCls a model for the entity WatchTbl here? Im not sure.
In any case. If you go for aggregation and want to convert WatchTbl collection to WatchTblCls list, your desired solution might look like the following. I don't know the defiitions of the classes so I'm assuming:
var client = new MongoClient("mongodb://servername:27017");
var database = client.GetDatabase("WatchTblDB");
var collectionWatchtbl = database.GetCollection<WatchTbl>("Watchtbl");
var collectionUser = database.GetCollection<UserCls>("Users");
var collectionSymbole = database.GetCollection<SymboleCls>("Users");
var list = collectionWatchtbl.AsQueryable().Select(x => new WatchTblCls() {
id = x.id,
userId = x.userId,
.....
});
If you can use the same WatchTbl class and still want to load the full collection to a local List (which is definitely not a good idea):
List<WatchTbl> list = await collectionWatchtbl.Find(x => true).ToListAsync();

Find POCO with MongoDB .Net driver

MongoDB was harder than I remembered! I've tried various versions of if-exists-replace-else-insert with various functions and options. It should be easy, shouldn't it?
It's my personal opinion that the following should work.
var collection = storageClient.GetCollection<Observer>("observers");
await collection.Indexes.CreateOneAsync(Builders<Observer>.IndexKeys.Ascending(_ => _.MyId), new CreateIndexOptions { Unique = true });
foreach (var observer in _observers)
{
observer.Timestamp = DateTime.Now;
var res = await collection.FindAsync(o => o.MyId == observer.MyId);
if (res==null ||res.Current == null) {
await collection.InsertOneAsync(observer); //Part 1, fails 2nd time solved with res.MoveNextAsync()
}
else
{
observer.ID = res.Current.Single().ID;
var res2 = await collection.ReplaceOneAsync(o=>o.MyId==observer.MyId, observer);
var res3 = await collection.FindAsync(o => o.MyId == observer.MyId);
await res3.MoveNextAsync();
Debug.Assert(res3.Current.Single().Timestamp == observer.Timestamp); //Part 2, Assert fails.
}
}
Observer looks approximately like this:
public class Observer : IObserver
{
[BsonId]
public Guid ID { get; set; }
public int MyId { get; set; }
public DateTime Timestamp { get; set; }
}
The second time I run this with the exact same collection I unexpectedly get:
E11000 duplicate key error index: db.observers.$MyId_1 dup key: { : 14040 }
Edit:
Added original part two code: replacement.
Edit 2:
Now my code looks like this. Still fails.
var collection = storageClient.GetCollection<Observer>("gavagai_mentions");
await collection.Indexes.CreateOneAsync(Builders<Observer>.IndexKeys.Ascending(_ => _.MyID), new CreateIndexOptions { Unique = true });
foreach (var observer in _observers)
{
observer.Timestamp = DateTime.Now;
// Create a BsonDocument version of the POCO that we can manipulate
// and then remove the _id field so it can be used in a $set.
var bsonObserver = observer.ToBsonDocument();
bsonObserver.Remove("_id");
// Create an update object that sets all fields on an insert, and everthing
// but the immutable _id on an update.
var update = new BsonDocument("$set", bsonObserver);
update.Add(new BsonDocument("$setOnInsert", new BsonDocument("_id", observer.ID)));
// Enable the upsert option to create the doc if it's not found.
var options = new UpdateOptions { IsUpsert = true };
var res = await collection.UpdateOneAsync(o => o.MyID == observer.MyID,
update, options);
var res2 = await collection.FindAsync(o => o.MyID == observer.MyID);
await res2.MoveNextAsync();
Debug.Assert(res2.Current.Single().Timestamp == observer.Timestamp); //Assert fails, but only because MongoDB stores dates as UTC, or so I deduce. It works!!
}
You can do this atomically with UpdateOneAsync by using the IsUpsert option to create the doc if it doesn't already exist.
foreach (var observer in _observers)
{
// Create a BsonDocument version of the POCO that we can manipulate
// and then remove the _id field so it can be used in a $set.
var bsonObserver = observer.ToBsonDocument();
bsonObserver.Remove("_id");
// Create an update object that sets all fields on an insert, and everthing
// but the immutable _id on an update.
var update = new BsonDocument("$set", bsonObserver);
update.Add(new BsonDocument("$setOnInsert", new BsonDocument("_id", observer.ID)));
// Enable the upsert option to create the doc if it's not found.
var options = new UpdateOptions { IsUpsert = true };
var res = await collection.UpdateOneAsync(o => o.MyId == observer.MyId,
update, options);
}
Ok, it's just been a long time since I worked with cursors.
await res.MoveNextAsync();
helped.
This stuff is not hard to Google, but the fact of the matter is, I failed, so I'm going to leave the question up.
If you have an answer for part two https://stackoverflow.com/questions/32586064/replace-poco-with-mongodb-net-driver-2, then and would really like to post it here then I'll be happy two edit the questions.
As was pointed out in the comments the information is in fact readily available in the docs http://mongodb.github.io/mongo-csharp-driver/2.0/reference/driver/crud/reading/#finding-documents.

How to work around NotMapped properties in queries?

I have method that looks like this:
private static IEnumerable<OrganizationViewModel> GetOrganizations()
{
var db = new GroveDbContext();
var results = db.Organizations.Select(org => new OrganizationViewModel
{
Id = org.OrgID,
Name = org.OrgName,
SiteCount = org.Sites.Count(),
DbSecureFileCount = 0,
DbFileCount = 0
});
return results;
}
This is returns results pretty promptly.
However, you'll notice the OrganizationViewModel has to properties which are getting set with "0". There are properties in the Organization model which I added via a partial class and decorated with [NotMapped]: UnsecureFileCount and SecureFileCount.
If I change those 0s to something useful...
DbSecureFileCount = org.SecureFileCount,
DbFileCount = org.UnsecureFileCount
... I get the "Only initializers, entity members, and entity navigation properties are supported" exception. I find this a little confusing because I don't feel I'm asking the database about them, I'm only setting properties of the view model.
However, since EF isn't listening to my argument I tried a different approach:
private static IEnumerable<OrganizationViewModel> GetOrganizations()
{
var db = new GroveDbContext();
var results = new List<OrganizationViewModel>();
foreach (var org in db.Organizations)
{
results.Add(new OrganizationViewModel
{
Id = org.OrgID,
Name = org.OrgName,
DbSecureFileCount = org.SecureFileCount,
DbFileCount = org.UnsecureFileCount,
SiteCount = org.Sites.Count()
});
}
return results;
}
Technically this gives me the correct results without an exception but it takes forever. (By "forever" I mean more than 60 seconds whereas the first version delivers results in under a second.)
Is there a way to optimize the second approach? Or is there a way to get the first approach to work?
Another option would be to load the values back as an anonymous type and the loop through those to load your viewmodel (n+1 is most likely the reason for the slowness).
For example:
var results = db.Organizations.Select(org => new
{
Id = org.OrgID,
Name = org.OrgName,
DbSecureFileCount = org.SecureFileCount,
DbFileCount = org.UnsecureFileCount,
SiteCount = org.Sites.Count()
}).ToList();
var viewmodels = results.Select( x=> new OrganizationViewModel
{
Id = x.Id,
Name = x.Name,
DbSecureFileCount = x.DbSecureFileCount,
DbFileCount = x.DbFileCount,
SiteCount = x.SiteCount
});
Sorry about the formatting; I'm typing on a phone.
You are basically lazy loading each object at each iteration of the loop, causing n+1 queries.
What you should do is bring in the entire collection into memory, and use it from there.
Sample code:
var organizationList = db.Organizations.Load();
foreach (var org in organizationList.Local)
{
//Here you are free to do whatever you want
}

Categories

Resources