RavenDB: PatchRequest to update/merge property that may not exist in document - c#

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);

Related

Replace property values in a class from List<Dictionary> values

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);
}
}

How to send multi-select attribute values to Acumatica

I can't find an example of this here or in the Acumatica sample code. Sending single attribute values works fine, but I can't find a way to send multi-select ones. They are returned as a comma-separated list in a string value, but sending them that way doesn't work. Also, sending them as multiple instances of single values doesn't work.
Here's what I've tried. (In the actual code I'm sending some other single attributes in the list, as well, but those work fine.)
// this results in nothing being set for the attribute
string interestedIn = "Brochure, Contact, Collecting small stones";
List<Acumatica.AttributeDetail> attributes = new List<Acumatica.AttributeDetail>();
attributes.Add(
new Acumatica.AttributeDetail {
Attribute = new Acumatica.StringValue { Value = "Interested in" },
Value = new Acumatica.StringValue { Value = interestIn }
}
);
custAdd.Attributes = attributes.ToArray();
// this results in the last item in the list being set for the attribute
string interestedIn = "Brochure, Contact, Collecting small stones";
List<Acumatica.AttributeDetail> attributes = new List<Acumatica.AttributeDetail>();
string[] interests = Convert.ToString(interestedIn).Split(',');
foreach (string interest in interests) {
attributes.Add(
new Acumatica.AttributeDetail {
Attribute = new Acumatica.StringValue { Value = "Interested in" },
Value = new Acumatica.StringValue { Value = interest.Trim() }
}
);
};
custAdd.Attributes = attributes.ToArray();
From the source
MappedCustomer obj = bucket.Customer;
Core.API.Customer impl = obj.Local;
impl.Attributes = impl.Attributes ?? new List<AttributeValue>();
AttributeValue attribute = new AttributeValue();
attribute.AttributeID = new StringValue() { Value = attributeID };
attribute.ValueDescription = new StringValue() { Value = attributeValue?.ToString() };
impl.Attributes.Add(attribute);
Some subtle differences here. Also, I wonder if the .ToArray() call is necessary.

Unable to add validators into EF Produced Metadata

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);
}
}
}
}

Updating entire node with mutating cypher in Neo4jclient

I need to update all the properties of a given node, using mutating cypher. I want to move away from Node and NodeReference because I understand they are deprecated, so can't use IGraphClient.Update. I'm very new to mutating cypher. I'm writing in C#, using Neo4jclient as the interface to Neo4j.
I did the following code which updates the "Name" property of a "resunit" where property "UniqueId" equals 2. This works fine. However,
* my resunit object has many properties
* I don't know which properties have changed
* I'm trying to write code that will work with different types of objects (with different properties)
It was possible with IGraphClient.Update to pass in an entire object and it would take care of creating cypher that sets all properies.
Can I somehow pass in my object with mutating cypher as well?
The only alternative I can see is to reflect over the object to find all properties and generate .Set for each, which I'd like to avoid. Please tell me if I'm on the wrong track here.
string newName = "A welcoming home";
var query2 = agencyDataAccessor
.GetAgencyByKey(requestingUser.AgencyKey)
.Match("(agency)-[:HAS_RESUNIT_NODE]->(categoryResUnitNode)-[:THE_UNIT_NODE]->(resunit)")
.Where("resunit.UniqueId = {uniqueId}")
.WithParams(new { uniqueId = 2 })
.With("resunit")
.Set("resunit.Name = {residentialUnitName}")
.WithParams(new { residentialUnitName = newName });
query2.ExecuteWithoutResults();
It is indeed possible to pass an entire object! Below I have an object called Thing defined as such:
public class Thing
{
public int Id { get; set; }
public string Value { get; set; }
public DateTimeOffset Date { get; set; }
public int AnInt { get; set; }
}
Then the following code creates a new Thing and inserts it into the DB, then get's it back and updates it just by using one Set command:
Thing thing = new Thing{AnInt = 12, Date = new DateTimeOffset(DateTime.Now), Value = "Foo", Id = 1};
gc.Cypher
.Create("(n:Test {thingParam})")
.WithParam("thingParam", thing)
.ExecuteWithoutResults();
var thingRes = gc.Cypher.Match("(n:Test)").Where((Thing n) => n.Id == 1).Return(n => n.As<Thing>()).Results.Single();
Console.WriteLine("Found: {0},{1},{2},{3}", thingRes.Id, thingRes.Value, thingRes.AnInt, thingRes.Date);
thingRes.AnInt += 100;
thingRes.Value = "Bar";
thingRes.Date = thingRes.Date.AddMonths(1);
gc.Cypher
.Match("(n:Test)")
.Where((Thing n) => n.Id == 1)
.Set("n = {thingParam}")
.WithParam("thingParam", thingRes)
.ExecuteWithoutResults();
var thingRes2 = gc.Cypher.Match("(n:Test)").Where((Thing n) => n.Id == 1).Return(n => n.As<Thing>()).Results.Single();
Console.WriteLine("Found: {0},{1},{2},{3}", thingRes2.Id, thingRes2.Value, thingRes2.AnInt, thingRes2.Date);
Which gives:
Found: 1,Foo,12,2014-03-27 15:37:49 +00:00
Found: 1,Bar,112,2014-04-27 15:37:49 +00:00
All properties nicely updated!

Map enum value robustly

I have a form where I collect data from users. When this data is collected, I pass it to various partners, however each partner has their own rules for each piece of data, so this has to be converted. I can make this happen, but my worries are about the robustness. Here's some code:
First, I have an enum. This is mapped to dropdown a dropdown list - the description is the text value, and the int mapped to the value.
public enum EmploymentStatusType
{
[Description("INVALID!")]
None = 0,
[Description("Permanent full-time")]
FullTime = 1,
[Description("Permanent part-time")]
PartTime = 2,
[Description("Self employed")]
SelfEmployed = 3
}
When the form is submitted, the selected value is converted to its proper type and stored in another class - the property looks like this:
protected virtual EmploymentStatusType EmploymentStatus
{
get { return _application.EmploymentStatus; }
}
For the final bit of the jigsaw, I convert the value to the partners required string value:
Dictionary<EmploymentStatusType, string> _employmentStatusTypes;
Dictionary<EmploymentStatusType, string> EmploymentStatusTypes
{
get
{
if (_employmentStatusTypes.IsNull())
{
_employmentStatusTypes = new Dictionary<EmploymentStatusType, string>()
{
{ EmploymentStatusType.FullTime, "Full Time" },
{ EmploymentStatusType.PartTime, "Part Time" },
{ EmploymentStatusType.SelfEmployed, "Self Employed" }
};
}
return _employmentStatusTypes;
}
}
string PartnerEmploymentStatus
{
get { return _employmentStatusTypes.GetValue(EmploymentStatus); }
}
I call PartnerEmploymentStatus, which then returns the final output string.
Any ideas how this can be made more robust?
Then you need to refactor it into one translation area. Could be something like a visitor pattern implementation. Your choices are distribute the code (as you are doing now) or visitor which would centralize it. You need to build in a degree of fragility so your covering tests will show problems when you extend in order to force you to maintain the code properly. You are in a fairly common quandry which is really a code organisational one
I did encounter such a problem in one of my projects and I solved it by using a helper function and conventions for resource names.
The function is this one:
public static Dictionary<T, string> GetEnumNamesFromResources<T>(ResourceManager resourceManager, params T[] excludedItems)
{
Contract.Requires(resourceManager != null, "resourceManager is null.");
var dictionary =
resourceManager.GetResourceSet(culture: CultureInfo.CurrentUICulture, createIfNotExists: true, tryParents: true)
.Cast<DictionaryEntry>()
.Join(Enum.GetValues(typeof(T)).Cast<T>().Except(excludedItems),
de => de.Key.ToString(),
v => v.ToString(),
(de, v) => new
{
DictionaryEntry = de,
EnumValue = v
})
.OrderBy(x => x.EnumValue)
.ToDictionary(x => x.EnumValue, x => x.DictionaryEntry.Value.ToString());
return dictionary;
}
The convention is that in my resource file I will have properties that are the same as enum values (in your case None, PartTime etc). This is needed to perform the Join in the helper function which, you can adjust to match your needs.
So, whenever I want a (localized) string description of an enum value I just call:
var dictionary = EnumUtils.GetEnumNamesFromResources<EmploymentStatusType>(ResourceFile.ResourceManager);
var value = dictionary[EmploymentStatusType.Full];

Categories