How to save/pass MongoDB UpdateDefinition for logging and later use? - c#

I am stumped on how to save/pass MongoDB UpdateDefinition for logging and later use
I have created general functions for MongoDB in Azure use on a collection for get, insert, delete, update that work well.
The purpose is to be able to have a standard, pre-configured way to interact with the collection. For update especially, the goal is to be able to flexibly pass in an appropriate UpdateDefinition where that business logic is done elsewhere and passed in.
I can create/update/set/combine the UpdateDefinition itself, but when i try to log it by serializing it, it shows null:
JsonConvert.SerializeObject(updateDef)
When I try to log it, save it to another a class or pass it to another function it displays null:
public class Account
{
[BsonElement("AccountId")]
public int AccountId { get; set; }
[BsonElement("Email")]
public string Email { get; set; }
}
var updateBuilder = Builders<Account>.Update;
var updates = new List<UpdateDefinition<Account>>();
//just using one update here for brevity - purpose is there could be 1:many depending on fields updated
updates.Add(updateBuilder.Set(a => a.Email, email));
//Once all the logic and field update determinations are made
var updateDef = updateBuilder.Combine(updates);
//The updateDef does not serialize to string, it displays null when logging.
_logger.LogInformation("{0} - Update Definition: {1}", actionName, JsonConvert.SerializeObject(updateDef));
//Class Created for passing the Account Update Information for Use by update function
public class AccountUpdateInfo
{
[BsonElement("AccountId")]
public int AccountId { get; set; }
[BsonElement("Update")]
public UpdateDefinition<Account> UpdateDef { get; set; }
}
var acct = new AccountUpdateInfo();
acctInfo.UpdateDef = updateDef
//This also logs a null value for the Update Definition field when the class is serialized.
_logger.LogInformation("{0} - AccountUpdateInfo: {1}", actionName, JsonConvert.SerializeObject(acct));
Any thoughts or ideas on what is happening? I am stumped on why I cannot serialize for logging or pass the value in a class around like I would expect

give this a try:
var json = updateDef.Render(
BsonSerializer.SerializerRegistry.GetSerializer<Account>(),
BsonSerializer.SerializerRegistry)
.AsBsonDocument
.ToString();
and to turn a json string back to an update definition (using implicit operator), you can do:
UpdateDefinition<Account> updateDef = json;
this is off the top of my head and untested. the only thing i'm unsure of (without an IDE) is the .Document.ToString() part above.

Related

Deserialize into nested objects from Cypher Result in c#

I've been wanting to deserialize automatically from a Cypher Result into their corresponding objects via neo4jclient.
These are the classes I want it to deserialize into
public class QuestionHub
{
public string Id { get; set; }
public string Title { get; set; }
public ICollection<Question> Questions {get;set;}
}
public class Question
{
public string Id { get; set; }
public string Title { get; set; }
public ICollection<Answer> Answers { get;set; }
}
public class Answer
{
public string Id { get; set; }
public string Value { get; set; }
}
I know for a fact that this code will put the corresponding QuestionHubs into a list of QuestionHubs. This is exactly what I want, but the problem is that those property that navigate to other classes, are not included.
var hubQuery = graphClient.Cypher
.Match("(qh:QuestionHub)-[:PART_OF]-(q:Question)-[:ANSWER]-(a:Answer)")
.ReturnDistinct<QuestionHub>("qh");
This is the
result
As you can see, the questions are not included.
Whenever I do this
var hubQuery = graphClient.Cypher
.Match("(qh:QuestionHub)-[:PART_OF]-(q:Question)-[:ANSWER]-(a:Answer)")
.ReturnDistinct<QuestionHub>("*");
I get the error
System.ArgumentException: 'Neo4j returned a valid response, however Neo4jClient was unable to deserialize into the object structure you supplied.
First, try and review the exception below to work out what broke.
If it's not obvious, you can ask for help at http://stackoverflow.com/questions/tagged/neo4jclient
Include the full text of this exception, including this message, the stack trace, and all of the inner exception details.
Include the full type definition of Neo4JTests.QuestionHub.
Include this raw JSON, with any sensitive values replaced with non-sensitive equivalents:
{ "columns":["a","q","qh"], "data":[[ {"data":{ "Value":"answer9aaf5134-9e73-4681-ba2f-e8224242ff19","Id":"9aaf5134-9e73-4681-ba2f-e8224242ff19" }},{"data":{ "Title":"questiond287a365-364a-4de0-b9f2-574893c1eaaa","Id":"d287a365-364a-4de0-b9f2-574893c1eaaa" }},{"data":{ "Title":"questionHub222a2fbe-6644-491a-b0a1-66df59f05f11","Id":"222a2fbe-6644-491a-b0a1-66df59f05f11" }} ]] } Arg_ParamName_Nam'
Inner Exception
InvalidOperationException: The deserializer is running in single column mode, but the response included multiple columns which indicates a projection instead. If using the fluent Cypher interface, use the overload of Return that takes a lambda or object instead of single string. (The overload with a single string is for an identity, not raw query text: we can't map the columns back out if you just supply raw query text.)
This error is probably because the cypher result gives multiple columns instead of one.
This is what I want to get
If I do this
var hubQuery = graphClient.Cypher
.Match("(u:User)-[:CREATOR_HUB]-(qh:QuestionHub)-[:PART_OF]-(q:Question)-[:ANSWER]-(a:Answer)")
.With("u, qh, q, COLLECT({Id: a.Id, Value: a.Value}) as answers")
.With("u, qh, COLLECT({Id: q.Id, Title: q.Title, Answers:answers}) as questions")
.With("{Creator: {Id:u.Id, FirstName: u.FirstName, LastName: u.LastName}," +
"Id: qh.Id, Title: qh.Title, Questions: questions} as result")
.ReturnDistinct<string>("result");
var hubQueryRes = await hubQuery .ResultsAsync;
List<QuestionHub> hubList = new List<QuestionHub>();
foreach (var hub in hubQueryRes )
{
hubList .Add(JsonConvert.DeserializeObject<QuestionHub>(hub));
}
I get what I want, but I needed to write all those .With
I want a way to automatically do that without all the .With writing.
I'm looking for a way to automatically deserialize the Cypher result into their corresponding objects with nested objects included.
Is there a way to do this?
Thanks in advance!
You're 100% correct that the reason it didn't create your QuestionHub instance is because the return was entirely in the wrong format for the client to cope with.
Unfortunately - your with workaround is about the only way - As you're using it to return the output of Cypher into a format that the Json Deserializer can handle.
The best I can see to do would be this:
var query = gc.Cypher
.Match("(qh:QuestionHub)-[:PART_OF]-(q:Question)-[:ANSWER]-(a:Answer)")
.With("qh, q{.*, Answers: COLLECT(a)} AS qAndA")
.With("qh{.*, Questions: COLLECT(qAndA)} AS result")
.Return(result => result.As<QuestionHub>());
Bear in mind, you would also need your ICollection to be either List or IEnumerable to deserialize properly - deserializing into ICollection isn't supported.

Customising ApplicationUser asp.net-identity

I'm trying to add a list of objects the the standard ApplicationUser object in MVC.
I've been looking at dozens of questions all about this but they all seem to be adding a single object, rather than a list.
What I'm trying to do is record all historic passwords a user has used, so I've added a table called AspNetUserPreviousPassword.
In my code I've added this line to the ApplicationUserclass:
public virtual DbSet<AspNetUserPreviousPassword> PreviousUserPasswords { get; set; }
and within the AspNetUserPreviousPassword object I've added the following:
public string ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
I've created the following extensions class:
public static class IdentityExtensions which has appeared in the User.Identity intellisense - so far so good.
{
public static string GetPreviousUserPasswords(this IIdentity identity)
{
var claim = ((ClaimsIdentity)identity).FindFirst("PreviousUserPasswords");
// Test for null to avoid issues during local testing
return (claim != null) ? claim.Value : string.Empty;
}
}
When I went to edit the GenerateUserIdentityAsync function, to insert the custom claims, I began to think my approach was incorrect as you can only add strings, e.g.
userIdentity.AddClaim(new Claim("PreviousUserPasswords", "This must be a string"));
Despite only being able to add a string here, I wanted to test my previous passwords were being read from the database so I added this code to test:
string previousPasswords = "";
await this.PreviousUserPasswords.ForEachAsync(p => previousPasswords += p.PasswordHash);
this.PreviousUserPasswords is always NULL.
My questions:
1) Why is this.PreviousUserPasswords always NULL?
2) Is my approach even correct - can I add a list of objects to ApplicationUser or should I be doing this another way?
Here is the article I used to get preventing the use of previous "X" passwords in my implementation of Asp.Net Identity. I tweaked it to suit my needs but this got me everything I needed to get on the right track. Give it a read.
http://www.c-sharpcorner.com/UploadFile/4b0136/how-to-customize-password-policy-in-Asp-Net-identity/

Most efficient way to convert a object to another (Model to ViewModel)

Suppose I have a model with 20 fields, and in my index page, I want to list all models that are stored in my database.
In index page, instead of listing all fields of the model, I only to list 3 fields.
So, I make two class:
class CompleteModel {
public int Id { get; set; }
public string Field01 { get; set; }
public string Field02 { get; set; }
public string Field03 { get; set; }
public string Field04 { get; set; }
public string Field05 { get; set; }
...
public string Field20 { get; set; }
}
now, in my Controller, I can use:
await _context.CompleteModel.ToListAsync();
but I feel that it does not seem to be the right way to do it, because I'm getting all fields and using only 3 fields.
So, I made this code:
class ViewModel {
public string Field02 { get; set; }
public string Field04 { get; set; }
public string Field08 { get; set; }
}
var result = _context.CompleteModel.Select(
x => new {
x.Field02,
x.Field04,
x.Field08
}).ToListAsync();
var listResults = new List<IndexViewModel>();
if (result != null)
{
listResults.AddRange(results.Select(x => new IndexViewModel
{
Field02 = x.Field02,
Field04 = x.Field04,
Field08 = x.Field08
}));
}
I think this is a lot of code to do this.
First, I selected all the fields that I want, then, copied everything to another object.
There's a "more directly" way to do the same thing?
Like:
_context.CompleteModel.Select(x => new IndexViewModel { Field02, Field04, Field08 });
You could use AutoMapper to reduce the boiler plate so you're not manually copying field values over.
If you include the AutoMapper NuGet package then you'd need to have the following in your startup somewhere to configure it for your classes:
Mapper.Initialize(cfg => cfg.CreateMap<CompleteModel, ViewModel>());
You could then do something like the following:
var results = await _context.CompleteModel.ToListAsync();
var viewModelResults = results.Select(Mapper.Map<ViewModel>).ToList();
There are a lot of configuration options for the package so do take a look at the documentation to see if it suits your needs and determine the best way to use it if it does.
In my view this is one of the weaknesses of over abstraction and layering. The VM contains the data that is valuable to your application within the context of use (screen, process etc). The data model contains all the data that could be stored that might be relevant. At some point you need to match the two.
Use EF Projection to fetch only the data you need from the database into projected data model classes (using the EF POCO layer to define the query, but not to store the resultant data).
Map the projected classes onto your VM, if there is a naieve mapping, using Automapper or similar. However unless you are just writing CRUD screens a simple field by field mapping is of little value; the data you fetch from your data store via EF is in its raw, probably relational form. The data required by your VM is probably not going to fit that form very neatly (again, unless you are doing a simple CRUD form), so you are going to need to add some value by coding the relationship between the data store and the View Model.
I think concentrating on the count of lines of code would lead to the wrong approach. I think you can look at that code and ask "is it adding any value". If you can delegate the task to Automapper, then great; but your VM isn't really pulling its weight other than adding some validation annotation if you can consistently delegate the task of data model to VM data copying.

How do you get the Id field for a newly inserted item using the .NET client for Azure Mobile

I'm trying to use Azure Mobile Services to create a backend for an asynchronous multiplayer game. I'm using a sql database and a .NET backend on WAMS, calling the service from the .NET client (Xamarin.iOS specifically atm).
The class for the item being into the db:
public class Match {
public string Id { get; set; }
public int Challengers { get; set; }
string GameData { get; set; }
public List<string> Players { get; set; }
public string LastPlayer { get; set; }
public string Message { get; set; }
public string NextPlayer { get; set; }
public int PlayerGroup { get; set; }
}
I'm inserting it into the database using:
var matchtable = MobileService.GetTable <Match> ();
CurrentMatch = new Match {
Message = variant.ToString () + ", " + CurrentUser + " vs ??",
NextPlayer = CurrentUser,
Players = players,
PlayerGroup = playerGroup,
Challengers = 0,
Game = null,
LastPlayer = null
};
await matchtable.InsertAsync (CurrentMatch);
I'm then doing other things that will affect the match and need to update it again later, but I don't have an Id field for the CurrentMatch to be able to do the update. Everything I can find tells me that I should get the Id field back after the insert (either the method returning something or updating CurrentMatch itself with it), but it must all be talking about a javascript backend or different client or something. The InsertAsync method in the .NET client has no return value (well, technically returns Task) and the CurrentMatch doesn't get updated with the Id field from the call (also makes sense since it's not a ref or out parameter).
How on earth am I supposed to get the Id field for an object I just inserted into the database?
I'm assuming you are using the latest version of the Mobile Services client SDK, in which case you are calling this InsertAsync method here.
You're right that the parameter is not a ref or out parameter, but it can modify the fields of the object you passed in. In this case, it will modify the contents of the Match object.
My guess is that there is another code issue that's interfering. Or, if that code snippet is in a method, make sure it returns a Task and you await it before you check the contents of Id. A simple console log should help here.
If this doesn't solve the problem, then please include more context, otherwise the code you've written should behave as I've said.

Can I consume JSON without defining the fields before in C# code?

I am working on a REST API for a project using Visual Studio 2013 with C# and ASP.NET, and I need some guidance.
When the webpage performs a POST, I am passing along a number of fields as a JSON object. By defining a data transfer object in my C# code, I can easily read the values from the JSON, but only if I define all the fields (with the same name).
Here is my current (working) code:
public class AgencyPostDTO
{
public string AgencyName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZIP { get; set; }
}
// POST: api/Agency
public string Post(AgencyPostDTO Agency)
{
int success;
success = SQLUpdateAgency(Agency);
if (success < 1)
{
return "Failed";
}
else
{
return "Success";
}
}
So far no problems. I need to pass the data over to a second function, where I will perform some data processing (including converting the data into XML) and send the data/XML to MS SQL using a stored procedure:
public int SQLUpdateAgency(AgencyPostDTO Agency)
{
string xml = Agency.SerializeObject();
... code to call SQL stored procedure ommitted here
}
Now to my problem. I would prefer if I did not have to define the parameters of the data transfer object AgencyPostDTO in the code, and instead the code would just read the incoming JSON and pass it along to the next function, where I create the XML containing everything passed along.
As it works now, if the JSON contains for example an email address field, it will be dropped unless I define it in AgencyPostDTO.
So why do I want to do this? For future ease of maintenance. The users may come and say they want to add additional fields to the web form. I can then simply have our SQL expert add that column to the table, give me the name of it and I add an input field to the HTML form and make sure it is included in the JSON sent over. That way we never have to touch the already written, tested and working code. The new field is simply passed though the whole process.
Can this be done? If so, any suggestions on how?
If you used JSON.NET to handle the deserialisation of your objects then that has support for dynamic properties. Once you'd read your JSON string, you could convert it to a JArray or JObject and from there by using the .Children() call to get a list of all properties to convert it to any XML object you needed.
Have a look here:
Deserialize json object into dynamic object using Json.net

Categories