MongoDb Driver autogenerate _id for BsonDocument - c#

I'm trying to create several documents within a transaction, and one of the documents needs to be inserted as a BsonDocument as I have to be able to mutate a couple of values prior to insertion.
The two documents that I don't need to map .ToBsonDocument() are properly getting a new ID generated, but the main document is not.
Here is a basic class structure that I'm using.
public abstract class BaseDto
{
[JsonProperty("_id")] public string Id { get; init; }
[JsonProperty("_partitionKey")] public string PartitionKey { get; init; } = Constants.MasterDataPartitionKey;
}
public class ProductDto :BaseDto
{
public string Name { get; init; }
public string BrandId { get; init; }
public BrandDto Brand { get; init; }
public string ProducerId { get; init; }
public ProducerDto Producer { get; init; }
}
public class BrandDto :BaseDto
{
public string Name { get; init; }
}
public class ProducerDto :BaseDto
{
public string Name { get; init; }
}
And I'm configuring my mappings without the MongoDb specific attributes.
BsonClassMap.RegisterClassMap<BaseDto>(map =>
{
map.MapIdProperty(c => c.Id)
.SetElementName("_id")
.SetIdGenerator(CannectIdGenerator.Instance)
.SetSerializer(new StringSerializer(BsonType.String));
map.MapProperty(x => x.PartitionKey).SetElementName("_partitionKey");
});
BsonClassMap.RegisterClassMap<ProducerDto>(map => map.AutoMap());
BsonClassMap.RegisterClassMap<BrandDto>(map => map.AutoMap());
BsonClassMap.RegisterClassMap<ProductDto>(map => map.AutoMap());
The trouble I'm running into is that my Product isn't getting an auto-generated ID (the others are). Here's my transaction
[HttpPost]
public async Task<ActionResult<string>> Add(ProductDto product)
{
using var session = await _database.Client.StartSessionAsync();
try
{
session.StartTransaction();
// NOTICE THIS IS A `BsonDocument`
var products = _database.GetCollection<BsonDocument>(MasterCollection.Products.DisplayName);
var brands = _database.GetCollection<BrandDto>(MasterCollection.Brands.DisplayName);
var producers = _database.GetCollection<ProducerDto>(MasterCollection.Producers.DisplayName);
// THIS INSERT GETS A PROPER ID
await producers.InsertOneAsync(session, product.Producer);
// THIS INSERT GETS A PROPER ID
await brands.InsertOneAsync(session, product.Brand);
var insert = product.ToBsonDocument();
insert[nameof(ProductDto.Brand)] = insert[nameof(ProductDto.BrandId)] = product.Brand.Id;
insert[nameof(ProductDto.Producer)] = insert[nameof(ProductDto.ProducerId)] = product.Producer.Id;
// THIS INSERT DOES NOT GET AN ID, INSTEAD IT JUST STAYS `null`
await products.InsertOneAsync(session, insert);
await session.CommitTransactionAsync();
return Ok(insert["_id"]);
}
catch
{
await session.AbortTransactionAsync();
return Problem("Transaction Aborted");
}
}
What I'm trying to avoid is to have to manually initialize the ID field, I'd much prefer it to happen as a part of an insert.

Related

How to partial update mongodb document in Web Api Patch request

I want to partial update fileds of my document that changed in the patch request and in every request the field probably changed for example one time Balance will be charged another time AccountHolder and others.
I want to update each specific field in single method not create a method for the number of fields .
Note: there is nothing in IDto interface, I use it just for separating Dtos from other classes.
UpdateAccountDto.cs
public class UpdateAccountDto : IDto
{
public string Id { get; set; } = string.Empty;
public string AccountId { get; set; } = string.Empty;
public string AccountHolder { get; set; } = string.Empty;
public string AccountType { get; set; } = string.Empty;
public decimal Balance { get; set; }
}
Account.cs my entity
public class Account
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; } = string.Empty;
[BsonElement("account_id")]
public string AccountId { get; set; } = string.Empty;
[BsonElement("account_holder")]
public string AccountHolder { get; set; } = string.Empty;
[BsonElement("account_type")]
public string AccountType { get; set; } = string.Empty;
[BsonRepresentation(BsonType.Decimal128)]
[BsonElement("balance")]
public decimal Balance { get; set; }
}
My endpoint
[HttpPatch("UpdatePartialAccount")]
public async Task<ActionResult> UpdatePartialAccount([FromQuery]string id,[FromBody] JsonPatchDocument<UpdateAccountDto>? document)
{
if (document is null)
return BadRequest();
var updateAccountDto = document.ToDto();
document.ApplyTo(updateAccountDto, ModelState);
if (!ModelState.IsValid)
return BadRequest();
var entity = updateAccountDto.ToEntity<Account>();
entity.Id = id;
await _accountRepository.PartialUpdateAsync(entity);
return NoContent();
}
PartialUpdateAsync method
public async Task<UpdateResult> PartialUpdateAsync(Account account)
{
//var filter = Builders<Account>.Filter.Eq(a => a.Id, account.Id);
//var update = Builders<Account>.Update.Set()
//Partial update
}
Your question are actually not very clear, but I guess you may want to know the field settings corresponding to JsonPatchDocument and the operation of updating the Mongodb database.
Regarding the field setting of JsonPatchDocument, you can refer to the official document to use it. For example:
[HttpPatch("UpdatePartialAccount")]
public async Task<ActionResult> UpdatePartialAccount([FromQuery] string id, [FromBody] JsonPatchDocument<UpdateAccountDto>? document)
{
if (document is null)
return BadRequest();
var dto = new UpdateAccountDto() { AccountId = "DAccount1" };
document.ApplyTo(dto, ModelState);
//.....
}
Suppose you do a Replace operation on it:
[
{
"operationType": 2,
"path": "/accountHolder",
"op": "replace",
"value": "TestHolder"
}
]
At this point your dto will become:
Please confirm the content of dto and then match with Account to update the database(I'm not sure how you map to Account, but I used AutoMapper). For example:
public async Task UpdateAsync(Account account)
{
await _account.UpdateOneAsync(c => c.AccountId == account.AccountId, account.AccountHolder);
}
For more operations on updating the database, you can check this link.
Hope this can help you.

DynamoDB - How to implement Optimistic Locking using ServiceStack.Aws

Currently, I am using ServiceStack.Aws v5.9.0 to communicate with DynamoDB. I have used PutItem for both creating and updating an item without anticipating data loss in case of concurrency handling.
public class Customer
{
[HashKey]
public int CustomerId { get; set; }
[AutoIncrement]
public int SubId { get; set; }
public string CustomerType { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
...//and hundreds of fields here
}
public class CustomerDynamo
{
private readonly IPocoDynamo db;
//Constructor
public CustomerDynamo()
{
var dynamoClient = new AmazonDynamoDBClient(_region);
var entityType = typeof(Customer);
var tableName = entityType.Name;
entityType.AddAttributes(new AliasAttribute(name: tableName));
db = new PocoDynamo(dynamoClient) { ConsistentRead = true }.RegisterTable(tableType: entityType);
}
public Customer Update(Customer customer)
{
customer.ModifiedDate = DateTime.UtcNow;
db.PutItem(customer);
return customer;
}
}
The above Update method is called in every service/async task that needs to update the data of the customer.
Refer to this article of AWS I decided to implement the Optimistic Locking to save my life from the issue of concurrency requests.
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBContext.VersionSupport.html
Assume that the VersionNumber will be the key for Optimistic Locking. So I added the VersionNumber into the Customer model.
public class Customer
{
[HashKey]
public int CustomerId { get; set; }
[AutoIncrement]
public int SubId { get; set; }
public string CustomerType { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
...//and hundreds of fields here
[DynamoDBVersion]
public int? VersionNumber { get; set; }
}
The result is VersionNumber not updated while it should be automatically incremented. I think it is just because the PutItem will override the whole existing item. Is this correct?
I think I need to change from PutItem to UpdateItem in the Update method. The question is how can I generate the expression dynamically to be used with the UpdateItem?
Thanks in advance for any help!
Updates:
Thanks #mythz for the useful information about DynamoDBVersion attribute. Then I tried to remove the DynamoDBVersion and using the UpdateExpression of PocoDynamo as below
public Customer Update(Customer customer)
{
customer.ModifiedDate = DateTime.UtcNow;
var expression = db.UpdateExpression<Customer>(customer.CustomerId).Set(() => customer);
expression.ExpressionAttributeNames = new Dictionary<string, string>()
{
{ "#Version", "VersionNumber" }
};
expression.ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
{
{ ":incr", new AttributeValue { N = "1" } },
{ ":zero", new AttributeValue { N = "0" } }
};
expression.UpdateExpression = "SET #Version = if_not_exists(#Version, :zero) + :incr";
if (customer.VersionNumber.HasValue)
{
expression.Condition(c => c.VersionNumber == customer.VersionNumber);
}
var success = db.UpdateItem(expression);
}
But the changes are not saved except the VersionNumber
The [DynamoDBVersion] is an AWS Object Persistence Model attribute for usage with AWS's DynamoDBContext not for PocoDynamo. i.e. the only [DynamoDB*] attributes PocoDynamo utilizes are [DynamoDBHashKey] and [DynamoDBRangeKey] all other [DynamoDB*] attributes are intended for AWS's Object Persistence Model libraries.
When needed you can access AWS's IAmazonDynamoDB with:
var db = new PocoDynamo(awsDb);
var awsDb = db.DynamoDb;
Here are docs on PocoDynamo's UpdateItem APIs that may be relevant.

How to query LIST by passing parameter?

I am using Entity Framework 5.0 and I created my database from model. The below is the screenshot of the edmx diagram.
I am working towards to a below structure of data:
On given Client ID give me list of Theader which belongs to that ClientID and its TReports so I modeled my models as below:
public class TReportHeaderModel
{
public int ID { get; set; }
public int ClientID { get; set; }
public string THeaderTitle { get; set; }
public int RowNumber { get; set; }
public IList<TReportModel> TReports { get; set; }
}
public class TReportModel
{
public int ID { get; set; }
public string TReportName { get; set; }
public string URL { get; set; }
public int RowNumber { get; set; }
}
So when I query to get Theaders and its each report for given clientID:
I am listing the headers first for given clientID:
public IList<TReportHeaderModel> GetHeadersByClient(int ClientID)
{
using (var connection = new TReportEntitiesConnection())
{
var clientHeaders= (from st in connection.THeaders
where ClientID == st.ClientID
select new TReportHeaderModel
{
ID=st.ID,
THeaderTitle=st.THeaderTitle,
RowNumber=st.RowNumber
}).ToList();
return (clientHeaders);
}
}
And then to get the list of reports for each title and this is where I am stuck--->
public IList<TReportModel> GetChildReportsByHeader(int THeaderID)
{
using (var connection = new TReportEntitiesConnection())
{
// ....
}
}
Instead of separating it by get the headers by client first and then get the report by header id, is it possible to combine it in one method? sorry for the confusing explanation but I am new to LINQ Query so please understand.
The below is the ideal structure for the UI implemetation:
Client ID =2
Header 1
TReportName
URL
Header 2
TReportName
URL
is it possible to combine it in one method?
If I understand you correctly, this is what you're looking for:
using (var connection = new TReportEntitiesConnection())
{
var clientHeaders = (
from st in connection.THeaders
where ClientID == st.ClientID
select new TReportHeaderModel
{
ID=st.ID,
THeaderTitle = st.THeaderTitle,
RowNumber = st.RowNumber,
Reports = from r in st.TReports
select new TReportModel
{
ID = r.ID,
TReportName = r.TReportName,
URL = r.URL,
RowNumber = r.RowNumber,
}
}
).ToList();
}
return clientHeaders;
Note that for this to work, TReportHeaderModel.TReports should be IEnumerable<TReportModel>.
Normally I would suggest you separate the methods for getting your data and transforming your data into DTOs like this (And usually I have the connection defined at the class level, not at the method level because I will reuse the connection many times, and I prefer keeping my data accesses as lazy as possible):
TReportEntitiesConnection conn = new TReportEntitiesConnection();
Then I will create extension methods like so:
public static class MyExtensions
{
public IQueryable<THeader> ByClientId(this IQuerable<THeader> conn, int ClientID)
{
return conn
.Include(h=>h.Reports)
.Where(h=>h.ClientID==ClientID);
}
public TReportHeaderModel ToDto(this THeader t)
{
return new TReportHeaderModel
{
ID=t.ID,
ClientID=t.ClientID,
THeaderTitle=t.THeaderTitle,
RowNumber=t.RowNumber,
Reports=t.Reports.ToDto()
};
}
public TReportModel ToDto(this TReport r)
{
return new TReportModel
{
ID=r.ID,
TReportName=r.TReportName,
URL=r.URL,
RowNumber=r.RowNumber
};
}
public IEnumerable<TReportHeaderModel> ToDto(this IEnumerable<THeader> h)
{
return h.Select(x=>x.ToDto());
}
public IEnumerable<TReportModel> ToDto(this IEnumerable<TReport> r)
{
return r.Select(x=>x.ToDto());
}
}
Then you can use it like so:
var result=conn.THeaders.ByClientId(200).ToDto();
If you prefer not having your connection at the module level, that is easy too:
using(var connection = new TReportEntitiesConnection())
{
var result=connection.THeaders.ByClientId(200).ToDto();
}
(or use AutoMapper and skip all the manual Dto conversions)

MongoDB Unable to determine the serialization information for the expression error

My data is having following structure
public enum ParamType
{
Integer=1,
String=2,
Boolean=3,
Double=4
}
public class Gateway
{
public int _id { get; set; }
public string SerialNumber { get; set; }
public List<Device> Devices { get; set; }
}
public class Device
{
public string DeviceName { get; set; }
public List<Parameter> Parameters { get; set; }
}
public class Parameter
{
public string ParamName { get; set; }
public ParamType ParamType { get; set; }
public string Value { get; set; }
}
I filled 10 document objects of Gateway in a MongoDB database.
Now I want to query all those gateways which contains a device having Parameter with ParamName as "Target Temperature" and whose Value > 15.
I created following queries
var parameterQuery = Query.And(Query<Parameter>.EQ(p => p.ParamName, "Target Temperature"), Query<Parameter>.GT(p => int.Parse(p.Value), 15));
var deviceQuery = Query<Device>.ElemMatch(d => d.Parameters, builder => parameterQuery);
var finalQuery = Query<Gateway>.ElemMatch(g => g.Devices, builder => deviceQuery);
But when I run this, it is giving an exception
Unable to determine the serialization information for the expression: (Parameter p) => Int32.Parse(p.Value)
Please suggest where I am wrong.
As the error suggests, you can't use Int32.Parse inside your query. This lambda expression is used to get out the name of the property and it doesn't understand what Int32.Parse is.
If you are querying a string, you need to use a string value for comparison:
var parameterQuery = Query.And(Query<Parameter>.EQ(p => p.ParamName, "Target Temperature"), Query<Parameter>.GT(p => p.Value, "15"));
However, that's probably not what you want to do since you're using GT. To be treated as a number for this comparison you need the value to actually be an int in mongo, so you would need to change the type of your property:
public class Parameter
{
public string ParamName { get; set; }
public ParamType ParamType { get; set; }
public int Value { get; set; }
}
var parameterQuery = Query.And(Query<Parameter>.EQ(p => p.ParamName, "Target Temperature"), Query<Parameter>.GT(p => p.Value, 15));
Summary
I ran into this when I was modifying a list. It appears that Linq First/FirstOrDefault was not handled well by MongoDB for me. I changed to be an array index var update = Builders<Movie>.Update.Set(movie => movie.Movies[0].MovieName, "Star Wars: A New Hope"); Note: This is in Asp.Net 5 using MongoDB.Driver 2.2.0.
Full Example
public static void TypedUpdateExample() {
var client = new MongoClient("mongodb://localhost:27017");
var database = client.GetDatabase("test");
var collection = database.GetCollection<Movie>("samples");
//Create some sample data
var movies = new Movie {
Name = "TJ",
Movies = new List<MovieData>
{
new MovieData {
MovieName = "Star Wars: The force awakens"
}
}
};
collection.InsertOne(movies);
//create a filter to retreive the sample data
var filter = Builders<Movie>.Filter.Eq("_id", movies.Id);
//var update = Builders<Movie>.Update.Set("name", "A Different Name");
//TODO:LP:TSTUDE:Check for empty movies
var update = Builders<Movie>.Update.Set(movie => movie.Movies[0].MovieName, "Star Wars: A New Hope");
collection.UpdateOne(filter, update);
}
public class Movie {
[BsonId]
public ObjectId Id { get; set; }
public string Name { get; set; }
public List<MovieData> Movies { get; set; }
}
public class MovieData {
[BsonId]
public ObjectId Id { get; set; }
public string MovieName { get; set; }
}

MVC3 linq joins

As a novice am trying my hands on MVC3,razor, EF I have Three connected Tables that I want to produce a view from it. In a simpleton's brief the following are about the tables
PJUsers - ID, memUID(this unique Id from membership),FirstName,LastName
PJAwards - user nominates another user for an award, this links with awardtypesID as foreign key ( awardId,bool:awardok)
PJAwartypes - (awardtypeID, awardName)
The query in the controller is like this
var lists =
from tl in db.PJawards
join u in db.PJUsers on tl.nomineeId equals u.ID into tl_u
join i in db.PJUsers on tl.nominatorId equals i.MemUID into tl_i
where tl.awardOk
orderby tl.awardDated ascending
from u in tl_u.DefaultIfEmpty()
from i in tl_i.DefaultIfEmpty()
select new
{
Status = tl.awardOk,
nomineeFname = u.FirstName,
nomineeLname = u.LastName,
award = tl.PJawards.awardName,
Dated = tl.awardDated,
nominatorFname = i.FirstName,
nominatorLname = i.LastName,
nomineeCountry = u.Citizen,
nomineeResidence = u.Residence,
awardtypeId = tl.ID
};
somewhere i read that i have to construct a model class similar to the query in the controller
{
public class AwardUserInfo
{
public AwardUserInfo() { }
public bool Status { get; set; }
public string nomineeFname { get; set; }
public string nomineeLname { get; set; }
public string award { get; set; }
public string Dated { get; set; }
public string nominatorFname { get; set; }
public string nominatorLname { get; set; }
public string nomineeCountry { get; set; }
public string nomineeResidence { get; set; }
public int awardtypeId { get; set; }
}
}
Please I learn by examples so to be able to help me assume I don't know anything
somewhere i read that i have to construct a model class similar to the query in the controller
Try this.
I guess your ef-model is similar to
So You can create a ViewModel class
public class PJAwardsViewModel
{
public int Id { get; set; }
public string NominatorFName { get; set; }
public string NomineeFname { get; set; }
public string AwardName { get; set; }
public bool IsAwarded { get; set; }
}
It will be also good if You add some service class
public class PJAwardsService
{
public static List<PJAwards> GetAll()
{
using (var context = new YourDBEntities())
{
return context.PJAwards
.Include(x => x.PJUsers)
.Include(x => x.PJUsers1)
.Include(x => x.PJAwartypes).ToList();
}
}
}
(Don't forget to write using System.Data.Entity; )
Then You can add a ViewModelHelper class
public class PJAwardsViewModelHelper
{
public static PJAwardsViewModel PopulatePJAwardsViewModel(PJAwards pjaward)
{
return new PJAwardsViewModel
{
Id = pjaward.Id,
NominatorFName = pjaward.PJUsers.FirstName,
NomineeFname = pjaward.PJUsers1.FirstName,
AwardName = pjaward.PJAwartypes.AwardName,
IsAwarded = pjaward.IsAwarded
};
}
public static List<PJAwardsViewModel> PopulatePJAwardsViewModelList(List<PJAwards> pjawardsList)
{
return pjawardsList.Select(x => PopulatePJAwardsViewModel(x)).ToList();
}
}
At the end Your controller index method will look like this
public ActionResult Index()
{
var pjawards = PJAwardsViewModelHelper.PopulatePJAwardsViewModelList(PJAwardsService.GetAll().ToList());
return View(pjawards);
}
The only thing You should do is add a view (build the project before). Choose PJAwardsViewModel as a Model class and List as a scaffold template.
Enjoy it.
Here is a step by step guide by Steven Sanderson on how to use Asp.net MVC3, EF Code First with MVCScaffolding (powershell automation).
http://blog.stevensanderson.com/2011/01/13/scaffold-your-aspnet-mvc-3-project-with-the-mvcscaffolding-package/
It is a multipart blog post takes you through the exciting journey of MVC3.
All the best.

Categories