I have a database in Entity Framework that has a set of DTOs created from it that are then consumed by Breeze from the client.
We use the DataAnnotations on the server to validate the data that comes in from Breeze and I want to be able to replicate these validators on the client. Since Breeze implements these validators already and apparently supports adding validators into the metadata I thought I'd give a go at extending the Breeze Server Project.
I am already aware that EDMXWriter only supports a small set of DataAnnotations.
Basically all my project does is add post-generation the required validators into the json that is sent by Breeze.
Here is Part of a 'Table' that has the DataAnnotation of StringLength (That Breeze does support) on the Title Property.
{
"name":"Table",
"customannotation:ClrType":"...",
"key":{
"propertyRef":{
"name":"Id"
}
},
"property":[
{
"name":"Title",
"type":"Edm.String",
"fixedLength":"false",
"unicode":"true",
"validators":[
{
"validatorName":"stringLength",
"maxLength":"Max",
"minLength":1
}
]
}
]
}
I've formatted the output generation to match the requirements set by the scheme on the breeze website: http://www.breezejs.com/documentation/metadata-schema
But Breeze is not interpreting these validators that I am adding to the Metadata.
I noticed that the schema provided by Breeze Server for EF has a different design to the Schema set on the web link above. Does BreezeJS not interpret validators of EF provided Metadata? And if that is the case is there an easy way to enable this or will I have to write that into the client too.
I was aware that the Breeze team did say that they were planning on implementing better EF DataAnnotation support however I've seen nothing come of that. Perhaps this is implemented already and I've missed something? One can only hope it will be that easy.
Regards,
Oliver Baker
There are two Metadata formats that breeze understands. The first, which is the default for an EDM (Entity Framework) based model, is a json serialized version of the EDMX CSDL. This is a MS format which cannot easily be extended, and only supports the limited number of data annotations listed above.
The other alternative is breeze's native metadata format. This format is typically used by any Non Entity Framework based breeze servers. This is also the format used when applying the MetadataStore.exportMetadata and MetadataStore.importMetadata method calls. If your server provides metadata in this format then you can include whatever validations you want. The best way to investigate this format is to simply export the metadata for your current application and take a look. The result is simply the stringified native metadata json.
One approach that several breeze developers have taken is use a prebuild process that roundtrips the CSDL formatted metadata from an EF server thru a breeze client to translate it into native format and then simply storing this result on the server ( in your case with some added validators) and simply returning this prestored metadata to the client in production during the Metadata call.
In addition, you can also extend the breeze metadata format: See:
http://www.breezejs.com/documentation/custom-metadata
We have a number of developers who use such extended metadata for a variety of purposes, including the addition of validation metadata.
It seems the EFContextProvider has very limited validation annotation support, basically just:
required - if !isNullable
maxLength - if maxLength is specified
The output listed at http://www.breezejs.com/documentation/metadata-schema is of the metadata object in the client library, once processed.
http://www.breezejs.com/documentation/validation shows how to manually edit this information, and notes the following:
Many of these validators correlate to .NET data annotations . In a future release, the Breeze.NET EFContextProviderwill be able to include these validations in the metadata automatically for you. For now, you'll have to add them to the properties on the client side as we show next.
So if you extend the EFContextProvider with additional metadata, you'll have to manually process this and add it to the validators objects in the property info in the metadata store.
One approach that several breeze developers have taken is use a prebuild process that roundtrips the CSDL formatted metadata from an EF server thru a breeze client to translate it into native format and then simply storing this result on the server
Here is my solution using jint, up on github. Obviously computationally expensive, so the method calling this has a [Conditional["DEBUG"]] attribute
public static class MedSimDtoMetadata
{
const string breezeJsPath = #"C:\Users\OEM\Documents\Visual Studio 2015\Projects\SimManager\SM.Web\Scripts\breeze.min.js";
public static string GetBreezeMetadata(bool pretty = false)
{
var engine = new Engine().Execute("var setInterval;var setTimeout = setInterval = function(){}"); //if using an engine like V8.NET, would not be required - not part of DOM spec
engine.Execute(File.ReadAllText(breezeJsPath));
engine.Execute("breeze.NamingConvention.camelCase.setAsDefault();" + //mirror here what you are doing in the client side code
"var edmxMetadataStore = new breeze.MetadataStore();" +
"edmxMetadataStore.importMetadata(" + MedSimDtoRepository.GetEdmxMetadata() + ");" +
"edmxMetadataStore.exportMetadata();");
var exportedMeta = JObject.Parse(engine.GetCompletionValue().AsString());
AddValidators(exportedMeta);
return exportedMeta.ToString(pretty ? Formatting.Indented : Formatting.None);
}
//http://stackoverflow.com/questions/26570638/how-to-add-extend-breeze-entity-types-with-metadata-pulled-from-property-attribu
static void AddValidators(JObject metadata)
{
Assembly thisAssembly = typeof(ParticipantDto).Assembly; //any type in the assembly containing the Breeze entities.
var attrValDict = GetValDictionary();
var unaccountedVals = new HashSet<string>();
foreach (var breezeEntityType in metadata["structuralTypes"])
{
string shortEntityName = breezeEntityType["shortName"].ToString();
string typeName = breezeEntityType["namespace"].ToString() + '.' + shortEntityName;
Type entityType = thisAssembly.GetType(typeName, true);
Type metaTypeFromAttr = ((MetadataTypeAttribute)entityType.GetCustomAttributes(typeof(MetadataTypeAttribute), false).Single()).MetadataClassType;
foreach (var breezePropertyInfo in breezeEntityType["dataProperties"])
{
string propName = breezePropertyInfo["name"].ToString();
propName = char.ToUpper(propName[0]) + propName.Substring(1); //IF client using breeze.NamingConvention.camelCase & server using PascalCase
var propInfo = metaTypeFromAttr.GetProperty(propName);
if (propInfo == null)
{
Debug.WriteLine("No metadata property attributes available for " + breezePropertyInfo["dataType"] + " "+ shortEntityName +'.' + propName);
continue;
}
var validators = breezePropertyInfo["validators"].Select(bp => bp.ToObject<Dictionary<string, object>>()).ToDictionary(key => (string)key["name"]);
//usingMetaProps purely on property name - could also use the DTO object itself
//if metadataType not found, or in reality search the entity framework entity
//for properties with the same name (that is certainly how I am mapping)
foreach (Attribute attr in propInfo.GetCustomAttributes())
{
Type t = attr.GetType();
if (t.Namespace == "System.ComponentModel.DataAnnotations.Schema") {
continue;
}
Func<Attribute, Dictionary<string,object>> getVal;
if (attrValDict.TryGetValue(t, out getVal))
{
var validatorsFromAttr = getVal(attr);
if (validatorsFromAttr != null)
{
string jsValidatorName = (string)validatorsFromAttr["name"];
if (jsValidatorName == "stringLength")
{
validators.Remove("maxLength");
}
Dictionary<string, object> existingVals;
if (validators.TryGetValue(jsValidatorName, out existingVals))
{
existingVals.AddOrOverwrite(validatorsFromAttr);
}
else
{
validators.Add(jsValidatorName, validatorsFromAttr);
}
}
}
else
{
unaccountedVals.Add(t.FullName);
}
}
breezePropertyInfo["validators"] = JToken.FromObject(validators.Values);
}
}
foreach (var u in unaccountedVals)
{
Debug.WriteLine("unaccounted attribute:" + u);
}
}
static Dictionary<Type, Func<Attribute, Dictionary<string, object>>> GetValDictionary()
{
var ignore = new Func<Attribute, Dictionary<string, object>>(x => null);
return new Dictionary<Type, Func<Attribute, Dictionary<string, object>>>
{
[typeof(RequiredAttribute)] = x => new Dictionary<string, object>
{
["name"] = "required",
["allowEmptyStrings"] = ((RequiredAttribute)x).AllowEmptyStrings
//["message"] = ((RequiredAttribute)x).ErrorMessage
},
[typeof(EmailAddressAttribute)] = x => new Dictionary<string, object>
{
["name"] = "emailAddress",
},
[typeof(PhoneAttribute)] = x => new Dictionary<string, object>
{
["name"] = "phone",
},
[typeof(RegularExpressionAttribute)] = x => new Dictionary<string, object>
{
["name"] = "regularExpression",
["expression"] = ((RegularExpressionAttribute)x).Pattern
},
[typeof(StringLengthAttribute)] = x => {
var sl = (StringLengthAttribute)x;
return GetStrLenDictionary(sl.MaximumLength, sl.MinimumLength);
},
[typeof(MaxLengthAttribute)] = x => GetStrLenDictionary(((MaxLengthAttribute)x).Length),
[typeof(UrlAttribute)] = x => new Dictionary<string, object>
{
["name"] = "url",
},
[typeof(CreditCardAttribute)] = x=> new Dictionary<string, object>
{
["name"] = "creditCard",
},
[typeof(FixedLengthAttribute)] = x => //note this is one of my attributes to force fixed length
{
var len = ((FixedLengthAttribute)x).Length;
return GetStrLenDictionary(len, len);
},
[typeof(RangeAttribute)] = x => {
var ra = (RangeAttribute)x;
return new Dictionary<string, object>
{
["name"] = "range",
["min"] = ra.Minimum,
["max"] = ra.Maximum
};
},
[typeof(KeyAttribute)] = ignore
};
}
static Dictionary<string,object> GetStrLenDictionary(int maxLength, int minLength = 0)
{
if (minLength == 0)
{
return new Dictionary<string, object>
{
["name"] = "maxLength",
["maxLength"] = maxLength
};
}
return new Dictionary<string, object>
{
["name"] = "stringLength",
["minLength"] = minLength,
["maxLength"] = maxLength
};
}
static void AddOrOverwrite<K,V>(this Dictionary<K,V> oldValues, Dictionary<K,V> newValues)
{
foreach (KeyValuePair<K,V> kv in newValues)
{
if (oldValues.ContainsKey(kv.Key))
{
oldValues[kv.Key] = kv.Value;
}
else
{
oldValues.Add(kv.Key, kv.Value);
}
}
}
}
Related
Is there a more efficient way of converting dynamo db data into concrete types? For example, when I query the data everything is in:
List<Dictionary<string, AttributeValue>>
Is it possible to easily convert the type without having to loop through each item and doing this all manually?
For example I am doing:
return items.Select(item => new Connection
{
ConnectionId = Guid.Parse(item["connectionId"].S),
ClientId = item["clientId"].S,
ProviderId = item["providerId"].S,
Scopes = item["scopes"].SS.ToArray(),
CredentialsId = item["credentialsId"].S,
Evidences = ToEvidences(item["consentEvidences"].L)
})
.ToList();
This then returns a list of my type Connection however I am explicitly mapping each field. Is there an easier way or a helper library that can do the mapping?
I think you'll have luck with the higher-level .NET Document model. It presents more natural data types.
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DotNetSDKMidLevel.html
The easiest way I have found is to use the Document.FromAttributeMap function to convert it to a Document object and then convert it again to the .NET type using the DynamoDBContext.FromDocument method as shown below.
public async Task<IEnumerable<WeatherForecast>> GetAll(string cityName)
{
var queryRequest = new QueryRequest()
{
TableName = nameof(WeatherForecast),
KeyConditionExpression = "CityName = :cityName",
ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
{
{":cityName", new AttributeValue(cityName)},
}
};
var response = await _dynamoDbClient.QueryAsync(queryRequest);
return response.Items.Select(a =>
{
var doc = Document.FromAttributeMap(a);
return _dynamoDbContext.FromDocument<WeatherForecast>(doc);
});
}
I have a method that takes a List<Dictionary<string,object>> as a parameter. The plan is to use that parameter, but only update the values held in a particular class. Here is the (partially written) method
public async Task<Errors> UpdatePageForProject(Guid projectId, List<Dictionary<string, object>> data)
{
if (!IsValidUserIdForProject(projectId))
return new Errors { ErrorMessage = "Project does not exist", Success = false };
if (data.Count == 0)
return new Errors { ErrorMessage = "No data passed to change", Success = false };
var page = await _context.FlowPages.FirstOrDefaultAsync(t => t.ProjectId == projectId);
foreach (var d in data)
{
}
return new Errors { Success = true };
}
My original plan is to take each dictionary, check if the key and the property in page match and then alter the value (so I can pass in 1 dictionary or 8 dictionaries in the list and then alter page to save back to my entity database).
I'd rather not use reflection due to the speed hit (though C#9 is really fast, I'd still rather not use it), but I'm not sure how else this can be done. I did consider using AutoMapper to do this, but for now would rather not (it's a PoC, so it is possibly overkill)
If you want to do this without Reflection (which I agree is a good idea, not just for performance reasons) then you could use a "map" or lookup table with actions for each property.
var map = new Dictionary<string,Action<Page,object>>()
{
{ "Title", (p,o) => p.Title = (string)o },
{ "Header", (p,o) => p.Field1 = (string)o },
{ "DOB", (p,o) => p.DateOfBirth = (DateTime)o }
};
You can then iterate over your list of dictionaries and use the map to execute actions that update the page.
foreach (var dictionary in data)
{
foreach (entry in dictionary)
{
var action = map[entry.Key];
action(page, entry.Value);
}
}
I have a document with the following domain model:
class Entity
{
...
Dictionary<string, string> Settings { get; set; }
...
}
And there's a need to update specified collection. But not override - merge with incoming updates. As I need to process thousands of documents in that manner, I choosed the PatchCommand for better performance. Got following:
new PatchCommandData
{
Key = upd.EntityId,
Patches = new[]
{
// Foreach incoming Setting remove existing value (if any) and add the new one
new PatchRequest
{
Type = PatchCommandType.Modify,
Name = nameof(Entity.Settings),
Nested = upd.UpdatedSettings.Keys
.Select(x => new PatchRequest
{
Type = PatchCommandType.Unset,
Name = x
})
.ToArray()
.Union(upd.UpdatedSettings.Keys
.Select(x => new PatchRequest
{
Type = PatchCommandType.Set,
Name = x,
Value = upd.UpdatedSettings[x]
})
.ToList())
.ToArray(),
Value = RavenJToken.FromObject(upd.UpdatedSettings)
}
}
}
This way next update is performed:
Before: { Setting1 = "Value1", Setting2 = "Value2" }
Update request: { Setting2 = "NewValue2", Setting3 = "Value3" }
After: { Setting1 = "Value1", Setting2 = "NewValue2", Setting3 = "Value3" }
But.. There's always a "but". If there is a document without Settings property in db, provided patch will raise an error saying "Cannot modify value from Settings because it was not found".
I can't find any option to switch patch mode to Set vs. Modify on the fly. And there is no option to load all documents, apply the update on application's side and update thousands of documents.
The only reasonable option I can see is to create Dictionary instance for Settings property in the class constructor.
Folks, can you advice some other options?
P.S. RavenDB version is limited to 3.5
Done. You can find the final version below. Actually, the ScriptedPatchCommandData was used. It contains JavaScript body, that Raven will evaluate and execute against documents. Potentially, it isn't the best option from the performance point of view. But nothing critical as it turned out. Hope it may be helpful for someone.
const string fieldParameterName = "fieldName";
const string valueParameterName = "value";
var patch = updates
.Select(update => new ScriptedPatchCommandData
{
Key = update.EntityId,
Patch = new ScriptedPatchRequest
{
Script = $"this[{fieldParameterName}] = _.assign({{}}, this[{fieldParameterName}], JSON.parse({valueParameterName}));",
Values = new Dictionary<string, object>
{
{
fieldParameterName,
nameof(Entity.Settings)
},
{
valueParameterName,
JsonConvert.SerializeObject(update.UpdatedSettings)
}
}
}
})
.ToList();
return await DocumentStore.AsyncDatabaseCommands.BatchAsync(patch);
I am given an absolute URI that contains a query string. I'm looking to safely append a value to the query string, and change an existing parameter.
I would prefer not to tack on &foo=bar, or use regular expressions, URI escaping is tricky. Rather I want to use a built-in mechanism that I know will do this correctly and handle the escaping.
I've found a ton of answers that all use HttpUtility. However this being ASP.NET Core, there is no more System.Web assembly anymore, thus no more HttpUtility.
What is the appropriate way to do this in ASP.NET Core while targeting the core runtime?
If you are using ASP.NET Core 1 or 2, you can do this with Microsoft.AspNetCore.WebUtilities.QueryHelpers in the Microsoft.AspNetCore.WebUtilities package.
If you are using ASP.NET Core 3.0 or greater, WebUtilities is now part of the ASP.NET SDK and does not require a separate nuget package reference.
To parse it into a dictionary:
var uri = new Uri(context.RedirectUri);
var queryDictionary = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query);
Note that unlike ParseQueryString in System.Web, this returns a dictionary of type IDictionary<string, string[]> in ASP.NET Core 1.x, or IDictionary<string, StringValues> in ASP.NET Core 2.x or greater, so the value is a collection of strings. This is how the dictionary handles multiple query string parameters with the same name.
If you want to add a parameter on to the query string, you can use another method on QueryHelpers:
var parametersToAdd = new System.Collections.Generic.Dictionary<string, string> { { "resource", "foo" } };
var someUrl = "http://www.google.com";
var newUri = Microsoft.AspNetCore.WebUtilities.QueryHelpers.AddQueryString(someUrl, parametersToAdd);
Using .net core 2.2 you can get the query string using
var request = HttpContext.Request;
var query = request.Query;
foreach (var item in query){
Debug.WriteLine(item)
}
You will get a collection of key:value pairs - like this
[0] {[companyName, ]}
[1] {[shop, ]}
[2] {[breath, ]}
[3] {[hand, ]}
[4] {[eye, ]}
[5] {[firstAid, ]}
[6] {[eyeCleaner, ]}
The easiest and most intuitive way to take an absolute URI and manipulate it's query string using ASP.NET Core packages only, can be done in a few easy steps:
Install Packages
PM> Install-Package Microsoft.AspNetCore.WebUtilities
PM> Install-Package Microsoft.AspNetCore.Http.Extensions
Important Classes
Just to point them out, here are the two important classes we'll be using: QueryHelpers, StringValues, QueryBuilder.
The Code
// Raw URI including query string with multiple parameters
var rawurl = "https://bencull.com/some/path?key1=val1&key2=val2&key2=valdouble&key3=";
// Parse URI, and grab everything except the query string.
var uri = new Uri(rawurl);
var baseUri = uri.GetComponents(UriComponents.Scheme | UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.UriEscaped);
// Grab just the query string part
var query = QueryHelpers.ParseQuery(uri.Query);
// Convert the StringValues into a list of KeyValue Pairs to make it easier to manipulate
var items = query.SelectMany(x => x.Value, (col, value) => new KeyValuePair<string, string>(col.Key, value)).ToList();
// At this point you can remove items if you want
items.RemoveAll(x => x.Key == "key3"); // Remove all values for key
items.RemoveAll(x => x.Key == "key2" && x.Value == "val2"); // Remove specific value for key
// Use the QueryBuilder to add in new items in a safe way (handles multiples and empty values)
var qb = new QueryBuilder(items);
qb.Add("nonce", "testingnonce");
qb.Add("payerId", "pyr_");
// Reconstruct the original URL with new query string
var fullUri = baseUri + qb.ToQueryString();
To keep up to date with any changes, you can check out my blog post about this here: http://benjii.me/2017/04/parse-modify-query-strings-asp-net-core/
HttpRequest has a Query property which exposes the parsed query string via the IReadableStringCollection interface:
/// <summary>
/// Gets the query value collection parsed from owin.RequestQueryString.
/// </summary>
/// <returns>The query value collection parsed from owin.RequestQueryString.</returns>
public abstract IReadableStringCollection Query { get; }
This discussion on GitHub points to it as well.
This function return Dictionary<string, string> and does not use Microsoft.xxx for compatibility
Accepts parameter encoding in both sides
Accepts duplicate keys (return last value)
var rawurl = "https://emp.com/some/path?key1.name=a%20line%20with%3D&key2=val2&key2=valdouble&key3=&key%204=44#book1";
var uri = new Uri(rawurl);
Dictionary<string, string> queryString = ParseQueryString(uri.Query);
// queryString return:
// key1.name, a line with=
// key2, valdouble
// key3,
// key 4, 44
public Dictionary<string, string> ParseQueryString(string requestQueryString)
{
Dictionary<string, string> rc = new Dictionary<string, string>();
string[] ar1 = requestQueryString.Split(new char[] { '&', '?' });
foreach (string row in ar1)
{
if (string.IsNullOrEmpty(row)) continue;
int index = row.IndexOf('=');
if (index < 0) continue;
rc[Uri.UnescapeDataString(row.Substring(0, index))] = Uri.UnescapeDataString(row.Substring(index + 1)); // use Unescape only parts
}
return rc;
}
It's important to note that in the time since the top answer has been flagged as correct that Microsoft.AspNetCore.WebUtilities has had a major version update (from 1.x.x to 2.x.x).
That said, if you're building against netcoreapp1.1 you will need to run the following, which installs the latest supported version 1.1.2:
Install-Package Microsoft.AspNetCore.WebUtilities -Version 1.1.2
I use this as extention method, works with any number of params:
public static string AddOrReplaceQueryParameter(this HttpContext c, params string[] nameValues)
{
if (nameValues.Length%2!=0)
{
throw new Exception("nameValues: has more parameters then values or more values then parameters");
}
var qps = new Dictionary<string, StringValues>();
for (int i = 0; i < nameValues.Length; i+=2)
{
qps.Add(nameValues[i], nameValues[i + 1]);
}
return c.AddOrReplaceQueryParameters(qps);
}
public static string AddOrReplaceQueryParameters(this HttpContext c, Dictionary<string,StringValues> pvs)
{
var request = c.Request;
UriBuilder uriBuilder = new UriBuilder
{
Scheme = request.Scheme,
Host = request.Host.Host,
Port = request.Host.Port ?? 0,
Path = request.Path.ToString(),
Query = request.QueryString.ToString()
};
var queryParams = QueryHelpers.ParseQuery(uriBuilder.Query);
foreach (var (p,v) in pvs)
{
queryParams.Remove(p);
queryParams.Add(p, v);
}
uriBuilder.Query = "";
var allQPs = queryParams.ToDictionary(k => k.Key, k => k.Value.ToString());
var url = QueryHelpers.AddQueryString(uriBuilder.ToString(),allQPs);
return url;
}
Next and prev links for example in a view:
var next = Context.Request.HttpContext.AddOrReplaceQueryParameter("page",Model.PageIndex+1+"");
var prev = Context.Request.HttpContext.AddOrReplaceQueryParameter("page",Model.PageIndex-1+"");
I'm not sure at what point it was added but as early as .NET Core 3.1 HttpUtility.ParseQueryString is available and built into the standard .NET Microsoft.NETCore.App framework. I was able to access it from a class library with no additional NuGet packages or special dll references required.
I've tested it with .NET Core 3.1, .NET 6, and .NET 7.
The benefit of this approach is that you do not have to reference any web libraries which will add bloat to your project if you are building a library that may be used outside of the context of ASP.NET.
... of course, if you are needing this within an ASP.NET Core app, the Microsoft.AspNetCore.WebUtilities class suggested by others is perfectly valid.
EDIT: #KTCO pointed out in his comment on the OP back in 2017 that this class was available in .NET Core 2.0 as well.
The following code sample writes a simple object to a couchbase lite (version 2) database and reads all objects afterwards. This is what you can find in the official documentation here
This is quite a lot of manual typing since every property of every object must be transferred to the MutableObject.
class Program
{
static void Main(string[] args)
{
Couchbase.Lite.Support.NetDesktop.Activate();
const string DbName = "MyDb";
var db = new Database(DbName);
var item = new Item { Name = "test", Value = 5 };
// Serialization HERE
var doc = new MutableDocument();
doc.SetString("Name", item.Name);
doc.SetInt("Value", item.Value);
db.Save(doc);
using (var qry = QueryBuilder.Select(SelectResult.All())
.From(DataSource.Database(db)))
{
foreach (var result in qry.Execute())
{
var resultItem = new Item
{
// Deserialization HERE
Name = result[DbName].Dictionary.GetString("Name"),
Value = result[DbName].Dictionary.GetInt("Value")
};
Console.WriteLine(resultItem.Name);
}
}
Console.ReadKey();
}
class Item
{
public string Name { get; set; }
public int Value { get; set; }
}
}
From my research Couchbase lite uses JsonConvert internally, so there might be a way to simplify all that with the help of JsonConvert.
Anything like:
var json = JsonConvert.SerializeObject(item);
var doc = new MutableDocument(json); // No overload to provide raw JSON
or maybe
var data = JsonConvert.SerializeToDict(item); // JsonConvert does not provide this
var doc = new MutableDocument(data);
Is there or is this some kind of optimization and the preferred approach is by intend?
People ask about this quite often, but Couchbase Lite does not actually store JSON strings in the database. They are stored in a different format so this would not give the benefit that you think (the JSON would need to be reparsed and then broken down into the other format). I'd been pushing for a way to serialize classes directly instead of going through dictionary objects (which seems like the ultimate goal here) but our priority is on things that enterprise clients want and this doesn't seem to be one of them. Note that for it to make it in, it needs to be implemented in C# Java and Objective-C / Swift.
I don't know about JsonConvert but there seems to be a constructor that takes IDictionary<string, object> as argument. So I would try something like this (brain-compiled):
MutableDocument CreateDocument(object data)
{
if (data == null) return null;
var propertyValues = new Dictionary<string, object>();
foreach (var property in data.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
propertyValues[property.Name] = property.GetValue(data);
}
return new MutableDocument(propertyValues);
}
See if this works.