Get value of a class property by JsonProperty - c#

For environment settings, we are storing JSON blobs in our DB, such as:
[
{
"env": "local",
"keya": "valueA_local"
"keyb": "valueB_local"
},
{
"env": "development",
"keya": "valueA_dev"
"keyb": "valueB_dev"
},
{
"env": "qa",
"keya": "valueA_qa"
"keyb": "valueB_qa"
}
]
And we have an enum:
public enum EnvironmentSettings
{
KeyA = 1,
KeyB = 2,
}
And we have a class:
[Serializable]
public class MyProjectEnvironmentSettings
{
[JsonProperty("env")]
public string Environment { get; set; }
[JsonProperty("keya")]
public string KeyA{ get; set; }
[JsonProperty("keyb")]
public string KeyB{ get; set; }
}
And finally, I need to create a method that returns a value for a given pair. I'd like this method to not have to be modified if a new setting is added.
public string GetEnvironmentSetting(EnvironmentSettings environmentSetting)
{
List<MyProjectEnvironmentSettings> environmentSettings = // get the entire list from the DB
string envName = // get environment
string keyToGet = environmentSetting.ToString().ToLower();
// This will get the group of settings specific to the environment
var envSettings = environmentSettings.Where(e => e.Environment.ToLower() == envName).FirstOrDefault();
// And last, I need to get the value for the specific setting that is being requested
string settingValue = envSettings.Where(e => e.???? == keyToGet ); // <---------- This is where I need help!
}
I'm unsure of how to query by the properties JsonProperty attribute.
Thanks!

You can use reflection.
var property = typeof(MyProjectEnvironmentSettings)
.GetProperties()
.FirstOrDefault(prop => prop
.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName == keyToGet);
string settingValue = property?.GetValue(envSettings);
You may need to add BindingFlags if you change the accessibility of your properties.
This way you won't have to change your GetEnvironmentSetting method, but you will still need to update your enum and your class's properties each time you add something.
Also, you will still have the burden of making sure the names match. You could also create a mapping between enum and properties (even directly to the PropertyInfo, so you could cache them), but that then creates a third thing that you have to maintain for each change.

Related

How to map two class properties based on Customer

I have an application in which I am getting Shopify orders via Shopify webhooks. Now I have an issue is when I have only one client I mapped Shopify to receive an order via the traditional way with object properties and save it into the database.
Now I got two more clients and all clients want to map class fields according to them.
Let's suppose I got a Shopify order object with the following details:
{
"id": 4655316533326,
"checkout_id": 29212941516878,
"user_id": 71894499406,
"note": "hello world",
"subtotal_price_set": {
"shop_money": {
"amount": "109.00",
"currency_code": "USD"
}
"total_tax": "0.00",
"total_tax_set": {
"shop_money": {
"amount": "0.00",
"currency_code": "USD"
},
"presentment_money": {
"amount": "0.00",
"currency_code": "USD"
}
}
}
I have a class name Order as well in my application which contains some of the following properties:
public class Order {
public string OrderNo { get; set; }
public string DebtorID { get; set; }
public virtual string DeliveryAddress1 { get; set; }
public virtual string DeliveryAddress2 { get; set; }
public virtual string RIRTNo { get; set; }
public List<OrderLine> Lines { get; set; }
public string OrderNote { get; set; }
Public Customer Customer { get; set; }
}
Now some customers want me to map shopify order Note property with mine Order.Customer.Note
and one want to map that Note proerty with Order.Note
same with OrderNo. One wants to map it directly with Order.OrderNo and others want to map it with ``RIRTNo```
How to handle this kind of situation?
I created one table on the database to keep this mapping information.
I can get the customer detail from the Shopify order and then I make the DB query to get map information but the problem is in the database the detail is in dictionary like structure.
Consider this below mapping info for x Customer:
the key is a Shopify object property
"Order": {
"note": "Customer.Note"
"Id": "OrderId"
}
the key is the Shopify property name and the value is my application object name but I can't figure out how to make them based on this info.
First, you can make a web form to admin this task
we need a table we will call it MappingInfo table.
The idea is for 1 field you have 2 or 3 options to map it with the other class
so you will insert into this table all your customer's mapping options
after that, you will use the Automapper like this
var config = new MapperConfiguration(GetMapperConfigurationExpression());
var _mapper = config.CreateMapper();
private MapperConfigurationExpression GetMapperConfigurationExpression(Int customerId)
{
MapperConfigurationExpression mapperConfiguraitonExpression = new MapperConfigurationExpression();
// Get Data From MappingInfo Table where MappingInfo.customerId == customerId "from the parameters" and MappingInfo.PropertyName == "AppId"
if (MappingInfo.OptionId==1)
mapperConfiguraitonExpression.CreateMap<srcMyClass, destMyClass>().ForPath(dest => dest.AppId.Code, opt => opt.MapFrom(src => src.InvoiseId))
if (MappingInfo.OptionId==2)
mapperConfiguraitonExpression.CreateMap<srcMyClass, destMyClass>().ForPath(dest => dest.AppId.Code, opt => opt.MapFrom(src => src.LogecalId))
}
Basically, this depends on the Customer's mind.
So if you don't have any way to mapping yet.
you can divide your customers into 3 or 4 groups or more.
then you can use a factory design pattern
or simply can use an "if" statement depending on a new field "CustomerGroupId" and this should come with the entity to your code
Then:
if (Customer.CustomerGroupId == 1)
{
// do mapping like Order.Note = shopify.Note
}
else if (Customer.CustomerGroupId == 1)
{
// do mapping like Order.Customer.Note = shopify.Note
}
A very simple option is to use the "mapping info" within the Order class, and then modify the OrderNote property to use the mapping for getting/setting the note.
For example, create a method that provides the mapping:
private string _map = "note"; // default value when SetMap is not used
public void SetMap(string map)
=> _map = map;
And then OrderNote becomes:
private string _note;
public string OrderNote {
get => GetNote();
set => SetNote(value);
}
private string GetNote()
{
if(_map == "note") return _note;
if(_map == "Customer.Note") return Customer.Note;
throw new ArgumentOutOfRangeException("Invalid _map");
}
private void SetNote(string value)
{
if(_map == "note") { _note = value; return; }
if(_map == "Customer.Note") { Customer.Note = value; return; }
throw new ArgumentOutOfRangeException("Invalid _map");
}
Use the same pattern for each type of mapping.

Create RavenDB index based on dynamic properties

In RavenDB 4.2, I want to create an Index/Map based on a dynamic object. Better put, on dynamic properties which are not known at compile-time.
Here is an example of the raw JSON I'm ingesting:
{
"id": "A",
"detections":
[
{
"steps": [
{
"object": {
"id": "A1",
"target": {
"domain_name": "foobar.com"
}
},
"object": {
"id": "A2",
"target": {
"ipv4": "127.0.0.1"
}
}
}
]
}
]
}
The above sample is ingested from a 3rd party and stored in a RavenDB collection. Roughly translated, the following model has the challenge:
public class Step
{
public string Id { get; set; }
public DateTime When {get; set;}
public dynamic Object { get; set; } // aware that it's not handy naming
}
The pickle in this is that the object.target.X property name is dynamic. They cannot be strong-typed and can be a lot of things, like: domain_name, ipv4, ipv6, dns, shoe_size, hair_colour etc. This is why the entire steps.object is ingested and stored as either System.Object or dynamic.
My objective is to basically do a SelectMany() on each object.target and extract the property name (key) and value. This would make my RavenDB Index something like this:
public class StepsIndex : AbstractIndexCreationTask<Models.Step, StepsIndex.Result>
{
public class Result
{
public DateTime When { get; set; }
public string TargetKey { get; set; }
public string TargetValue { get; set; }
// ... removed other properties for brevity
}
public StepsIndex()
{
Map = steps =>
from block in blocks
from detection in blocks.Detections
from step in detection.Steps
select new Result
{
// extract property name (key), like 'domain_name'
TargetKey = step.Object.target.GetType().GetProperties()[0].Name,
// extract property value, like 'foobar.com'
TargetValue = step.Object.target.GetType().GetProperty(s.Object.target.GetType().GetProperties()[0].Name).GetValue(s.Object.target, null)
};
}
}
Unfortunately this doesn't work due to step.Object being dynamic and resulting in the following error during compile-time:
Error [CS1963] An expression tree may not contain a dynamic operation
Second option I've tried is to cast it to JSON in the expression, which also fails because Raven's projection is not aware of Newtonsoft.Json during runtime:
// Error CS0103: The name 'JObject' does not exist in the current context
// Error CS0103: The name 'JsonConvert' does not exist in the current context
TargetKey = JObject.Parse(JsonConvert.SerializeObject(ass.Object))["target"][0].Value<string>(),
A third option I thought of was perhaps changing the dynamic Object to System.Object Object, but haven't found a neat way to extract the property key/values without knowning the property.
The question: how can I extract these dynamic property keys and values and Map them to a RavenDB index?
RavenDB allows to index dynamic fields:
See:
https://ravendb.net/docs/article-page/4.2/Csharp/indexes/using-dynamic-fields
https://github.com/ravendb/book/blob/v4.0/Ch10/Ch10.md#dynamic-data

Web API 2 - Implementing a PATCH

I currently have a Web API that implements a RESTFul API. The model for my API looks like this:
public class Member
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime Created { get; set; }
public DateTime BirthDate { get; set; }
public bool IsDeleted { get; set; }
}
I've implemented a PUT method for updating a row similar to this (for brevity, I've omitted some non-relevant stuff):
[Route("{id}")]
[HttpPut]
public async System.Threading.Tasks.Task<HttpResponseMessage> UpdateRow(int id,
[FromBody]Models.Member model)
{
// Do some error checking
// ...
// ...
var myDatabaseEntity = new BusinessLayer.Member(id);
myDatabaseEntity.FirstName = model.FirstName;
myDatabaseEntity.LastName = model.LastName;
myDatabaseEntity.Created = model.Created;
myDatabaseEntity.BirthDate = model.BirthDate;
myDatabaseEntity.IsDeleted = model.IsDeleted;
await myDatabaseEntity.SaveAsync();
}
Using PostMan, I can send the following JSON and everything works fine:
{
firstName: "Sara",
lastName: "Smith",
created: "2018/05/10",
birthDate: "1977/09/12",
isDeleted: false
}
If I send this as my body to http://localhost:8311/api/v1/Member/12 as a PUT request, the record in my data with ID of 12 gets updated to what you see in the JSON.
What I would like to do though is implement a PATCH verb where I can do partial updates. If Sara gets married, I would like to be able to send this JSON:
{
lastName: "Jones"
}
I would like to be able to send just that JSON and update JUST the LastName field and leave all the other fields alone.
I tried this:
[Route("{id}")]
[HttpPatch]
public async System.Threading.Tasks.Task<HttpResponseMessage> UpdateRow(int id,
[FromBody]Models.Member model)
{
}
My problem is that this returns all the fields in the model object (all of them are nulls except the LastName field), which makes sense since I am saying I want a Models.Member object. What I would like to know is if there is a way to detect which properties have actually been sent in the JSON request so I can update just those fields?
I hope this helps using Microsoft JsonPatchDocument:
.Net Core 2.1 Patch Action into a Controller:
[HttpPatch("{id}")]
public IActionResult Patch(int id, [FromBody]JsonPatchDocument<Node> value)
{
try
{
//nodes collection is an in memory list of nodes for this example
var result = nodes.FirstOrDefault(n => n.Id == id);
if (result == null)
{
return BadRequest();
}
value.ApplyTo(result, ModelState);//result gets the values from the patch request
return NoContent();
}
catch (Exception ex)
{
return StatusCode(StatusCodes.Status500InternalServerError, ex);
}
}
Node Model class:
[DataContract(Name ="Node")]
public class Node
{
[DataMember(Name = "id")]
public int Id { get; set; }
[DataMember(Name = "node_id")]
public int Node_id { get; set; }
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "full_name")]
public string Full_name { get; set; }
}
A valid Patch JSon to update just the "full_name" and the "node_id" properties will be an array of operations like:
[
{ "op": "replace", "path": "full_name", "value": "NewNameWithPatch"},
{ "op": "replace", "path": "node_id", "value": 10}
]
As you can see "op" is the operation you would like to perform, the most common one is "replace" which will just set the existing value of that property for the new one, but there are others:
[
{ "op": "test", "path": "property_name", "value": "value" },
{ "op": "remove", "path": "property_name" },
{ "op": "add", "path": "property_name", "value": [ "value1", "value2" ] },
{ "op": "replace", "path": "property_name", "value": 12 },
{ "op": "move", "from": "property_name", "path": "other_property_name" },
{ "op": "copy", "from": "property_name", "path": "other_property_name" }
]
Here is an extensions method I built based on the Patch ("replace") specification in C# using reflection that you can use to serialize any object to perform a Patch ("replace") operation, you can also pass the desired Encoding and it will return the HttpContent (StringContent) ready to be sent to httpClient.PatchAsync(endPoint, httpContent):
public static StringContent ToPatchJsonContent(this object node, Encoding enc = null)
{
List<PatchObject> patchObjectsCollection = new List<PatchObject>();
foreach (var prop in node.GetType().GetProperties())
{
var patch = new PatchObject{ Op = "replace", Path = prop.Name , Value = prop.GetValue(node) };
patchObjectsCollection.Add(patch);
}
MemoryStream payloadStream = new MemoryStream();
DataContractJsonSerializer serializer = new DataContractJsonSerializer(patchObjectsCollection.GetType());
serializer.WriteObject(payloadStream, patchObjectsCollection);
Encoding encoding = enc ?? Encoding.UTF8;
var content = new StringContent(Encoding.UTF8.GetString(payloadStream.ToArray()), encoding, "application/json");
return content;
}
}
Noticed that tt also uses this class I created to serialize the PatchObject using DataContractJsonSerializer:
[DataContract(Name = "PatchObject")]
class PatchObject
{
[DataMember(Name = "op")]
public string Op { get; set; }
[DataMember(Name = "path")]
public string Path { get; set; }
[DataMember(Name = "value")]
public object Value { get; set; }
}
A C# example of how to use the extension method and invoking the Patch request using HttpClient:
var nodeToPatch = new { Name = "TestPatch", Private = true };//You can use anonymous type
HttpContent content = nodeToPatch.ToPatchJsonContent();//Invoke the extension method to serialize the object
HttpClient httpClient = new HttpClient();
string endPoint = "https://localhost:44320/api/nodes/1";
var response = httpClient.PatchAsync(endPoint, content).Result;
Thanks
PATCH operations aren't usually defined using the same model as the POST or PUT operations exactly for that reason: How do you differentiate between a null, and a don't change. From the IETF:
With PATCH, however, the enclosed entity contains a set of
instructions describing how a resource currently residing on the
origin server should be modified to produce a new version.
You can look here for their PATCH suggestion, but sumarilly is:
[
{ "op": "test", "path": "/a/b/c", "value": "foo" },
{ "op": "remove", "path": "/a/b/c" },
{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
{ "op": "replace", "path": "/a/b/c", "value": 42 },
{ "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
{ "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]
#Tipx's answer re using PATCH is spot on, but as you've probably already found, actually achieving that in a statically typed language like C# is a non-trivial exercise.
In the case where you're using a PATCH to represent a set of partial updates for a single domain entity (e.g. to update the first name and last name only for a contact with many more properties) you need to do something along the lines of looping each instruction in the 'PATCH' request and then applying that instruction to an instance of your class.
Applying an individual instruction will then comprise of
Finding the property of the instance that matches the name in the
instruction, or handling property names you weren't expecting
For an update: Trying to parse the value submitted in the patch into the instance property and handling the error if e.g. the instance property is a bool but the patch instruction contains a date
Deciding what to do with Add instructions as you can't add new properties to a statically typed C# class. One approach is to say that Add means "set the value of the instance's property only if property's existing value is null"
For Web API 2 on the full .NET Framework the JSONPatch github project looks to make a stab at providing this code, although it doesn't look like there's been a lot of development on that repo recently and the readme does state:
This is still very much an early project, don't use it in production
yet unless you understand the source and don't mind fixing a few bugs
;)
Things are simpler on .NET Core as that has a set of functionality to support this in the Microsoft.AspNetCore.JsonPatch namespace.
The rather useful jsonpatch.com site also lists out a few more options for Patch in .NET:
Asp.Net Core JsonPatch (Microsoft official implementation)
Ramone (a framework for consuming REST services, includes a JSON Patch implementation)
JsonPatch (Adds JSON Patch support to ASP.NET Web API)
Starcounter (In-memory Application Engine, uses JSON Patch with OT for client-server sync)
Nancy.JsonPatch (Adds JSON Patch support to NancyFX)
Manatee.Json (JSON-everything, including JSON Patch)
I need to add this functionality to an existing Web API 2 project of ours, so I'll update this answer if I find anything else that's useful while doing that.
I wanted to achieve exactly the same thing, but used a different method to others described here. I've created a working repo using this if you want to check it out:
https://github.com/emab/patch-example
If you have the following two models:
Database model
public class WeatherDBModel
{
[Key]
public int Id { get; set; }
public string City { get; set; }
public string Country { get; set; }
public double Temperature { get; set; }
public double WindSpeed { get; set; }
public double Rain { get; set; }
public Weather(int id, string city, string country, double temperature, double windSpeed, double rain)
{
Id = id;
City = city;
Country = country;
Temperature = temperature;
WindSpeed = windSpeed;
Rain = rain;
}
}
Update model
Containing exact names of database model properties. Includes properties which can be updated
public class WeatherUpdateModel
{
public string? City { get; set; }
public string? Country { get; set; }
public double Temperature { get; set; }
public double WindSpeed { get; set; }
public double Rain { get; set; }
}
This update model is sent to the service layer along with the id of the object you'd like to update.
You can then implement the following method in your repository layer which maps any non-null values from the updateModel into an existing entity if it has been found:
public Weather Update(int id, WeatherUpdate updateObject)
{
// find existing entity
var existingEntity = _context.Weather.Find(id);
// handle not found
if (existingEntity == null)
{
throw new EntityNotFoundException(id);
}
// iterate through all of the properties of the update object
// in this example it includes all properties apart from `id`
foreach (PropertyInfo prop in updateObject.GetType().GetProperties())
{
// check if the property has been set in the updateObject
// if it is null we ignore it. If you want to allow null values to be set, you could add a flag to the update object to allow specific nulls
if (prop.GetValue(updateObject) != null)
{
// if it has been set update the existing entity value
existingEntity.GetType().GetProperty(prop.Name)?.SetValue(existingEntity, prop.GetValue(updateObject));
}
}
_context.SaveChanges();
return existingEntity;
}
Using this method you can change your models without worrying about the update logic, as long as you ensure that the UpdateModel is kept up-to-date with the database model.
If a property of your object was omitted in your JSON, ASP.NET won't "set" that property on the object, the property will have its default value. In order to know which properties were sent with the JSON object you need to have a way to detect which properties of the object were set.
In order to detect which properties have "actually been sent" with the JSON object, you can modify your Member class to contain a collection of property names that were "set". Then, for all properties that you want to be able to know if they were sent in the JSON object make that when the property is set the name of the property should be added to the collection of set properties.
public class Member
{
private string _firstName;
private string _lastName;
...
private bool _isDeleted;
public string FirstName
{
get => _firstName;
set
{
_firstName = value;
_setProperties.Add(nameof(FirstName));
}
}
public string LastName
{
get => _lastName;
set
{
_lastName = value;
_setProperties.Add(nameof(LastName));
}
}
...
public bool IsDeleted
{
get => _isDeleted;
set
{
_isDeleted= value;
_setProperties.Add(nameof(IsDeleted));
}
}
private readonly HashSet<string> _setProperties = new HashSet<string>();
public HashSet<string> GetTheSetProperties()
{
return new HashSet<string>(_setProperties);
}
}
In the UpdateRow method you can now check whether a property was sent in the JSON by checking if it is in the _setProperties collection. So if you want to see if the LastName was sent in the JSON just do
bool lastNameWasInJson = model.Contains(nameof(model.LastName));
Following up to Avid Learners approach. I found this easy to add to an existing PUT method.
Alternatively to avoid loading twice you could apply update operations and then before saving apply the patch, but I'd rather load twice and have simple code.
public ResultModel Patch(UpdateModel model)
{
var record = LoadAsUpdateModel(model.Id);
if (record == null) return null;
foreach(var propertyName in model.SetProperties())
{
var property = model.GetType().GetProperty(propertyName);
property.SetValue(record, property.GetValue(model));
}
return Update(record);
}

configuration.GetValue list returns null

I am trying to read a list from appsettings.json file using the GetValue<T> method:
var builder = new ConfigurationBuilder().SetBasePath(System.AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
IConfigurationRoot configuration = builder.Build();
var rr = configuration.GetValue<IList<ConnectionSettings>>("Connections");
public class ConnectionSettings
{
public string Name { get; set; }
public string Host { get; set; }
public string Account { get; set; }
public string Password { get; set; }
}
and my appsettings.json
{
"Connections": [
{
"Name": "",
"Host": "192.168.1.5",
"Account": "74687",
"Password": "asdsdadsq"
},
{
"Name": "",
"Host": "127.0.0.1",
"Account": "45654",
"Password": "asdasads"
}
]
}
Problem is that I always get null and I dont understand why.
According to the documentation for GetValue<>, it gets the value of a (single) key and converts it to the specified type. Unfortunately, it doesn't throw an error if the value can't be converted, which is the situation you're running into.
I believe that Get<> would be preferable in your situation.
var rr = configuration.GetSection("Connections").Get<IList<ConnectionSettings>>();
According to Get<>'s documentation, it:
Attempts to bind the configuration instance to a new instance of type T. If this configuration section has a value, that will be used. Otherwise binding by matching property names against configuration keys recursively.
This allows you to get the value directly or, if it can't find the property, it looks for nested objects that contain a matching property.
An alternative would be as #AthanasiosKataras says; use Bind<>. This is helpful when you might have a sparse configuration in which you want to overlay some values with either default or calculated values.
I have spotted the following issue on GitHub: GetValue<T> not working with lists
Long story short: It is by design.
So you can try this:
var result = new List<ConnectionSettings>();
var rr = configuration.GetSection("Connections").Bind(result);
Configuration.Get<T> is a better option when you have nested configuration using non-primitive structure like list or array.
{
"Email": {
"ToEmails": [
"test1#test.com",
"test2#test.com",
"test3#test.com"
]
}
List<string> emailTo = _config.GetSection("Email:ToEmails").Get<List<string>>()
foreach (string email in emailTo)
{
sendGridMessage.AddTo(new EmailAddress(email));
}
OR use Bind()
public static class ConfigurationRootExtentions
{
public static List<T> GetListValue<T>(this IConfigurationRoot configurationRoot, string section)
{
var result = new List<T>();
configurationRoot.GetSection(section).Bind(result);
return result;
}
}
Ref[1]: https://blog.bitscry.com/2017/11/14/reading-lists-from-appsettings-json/
Ref[2]: https://github.com/aspnet/Configuration/issues/451
If you develop a project from scratch i.e using the VS template then set the following properties for appsettings.json (Right-click on appsettings.json--> click on Properties ). After that update following property value.
Copy to output Directory: Copy always
Now, you can run the application and get value using GetValue method.
After that, You can reverse the same setting as pervious
Copy to output Directory: Copy if newer
It might be useful for you.
GetValue<type>("key") does not work for complex types.
Let's say you have the following appsettings.json structure:
{
"ArrayOfObjectsWithinObjects": [
{
"foo0": "bar",
"foo1": 1,
"foo2": false,
"anotherObject": {
"foo3.1": "3.1",
"foo3.2": "3.2"
}
},
{
"foo0": "bar",
"foo1": 1,
"foo2": false,
"anotherObject": {
"foo3.1": "3.1",
"foo3.2": "3.2"
}
}
...
]
}
and C# the appropriate classes
public class Foo
{
public string foo0 { get; set;}
public int foo1 { get; set;}
public bool foo2 { get; set;}
public AnotherObject anotherObject { get; set;}
}
public class AnotherObject
{
public string foo3.1 { get; set; }
public string foo3.2 { get; set; }
}
You should be able to:
//using static System.Console;
//using System.Linq;
//using Microsoft.Extensions.Configuration;
var _listOfFoos = _config.GetSection("ArrayOfObjects")
.GetChildren()
.ToList()
.Select( x => new Foo
{
x.GetValue<string>("")
x.GetValue<int>("")
x.GetValue<bool>("")
x.GetSection("anotherObject").Get<AnotherObject>()
});
WriteLine(_listOfFoos.anotherObject.foo3.1);
Note that foo3.1 is an invalid c# syntax and was used for didactic purposes.

Create index with multi field mapping syntax with NEST 2.x

I just can't seem to get the syntax correct for multi field mapping in NEST 2.0--if that's the correct terminology. Every example I've found for mapping seems to be <= the 1.x version of NEST. I'm new to Elasticsearch and NEST, and I've been reading their documentation, but the NEST documentation hasn't been completely updated for 2.x.
Basically, I don't need to index or store the entire type. Some fields I need for indexing only, some fields I'll need to index and retrieve, and some I don't need for indexing, just for retrieval.
MyType
{
// Index this & allow for retrieval.
int Id { get; set; }
// Index this & allow for retrieval.
// **Also**, in my searching & sorting, I need to sort on this **entire** field, not just individual tokens.
string CompanyName { get; set; }
// Don't index this for searching, but do store for display.
DateTime CreatedDate { get; set; }
// Index this for searching BUT NOT for retrieval/displaying.
string CompanyDescription { get; set; }
// Nest this.
List<MyChildType> Locations { get; set; }
}
MyChildType
{
// Index this & allow for retrieval.
string LocationName { get; set; }
// etc. other properties.
}
I've have been able to index the entire object and child as-is using the following as an example:
client.Index(item, i => i.Index(indexName));
However, the actual object is a lot larger than this, and I really don't need most of it. I've found this, which looks like what I think I want to do, but in an older version: multi field mapping elasticsearch
I think "mapping" is what I'm going for, but like I said, I'm new to Elasticsearch and NEST and I'm trying to learn the terminology.
Be gentle! :) It's my first time to ask a question on SO. Thanks!
In addition to Colin's and Selçuk's answers, you can also fully control the mapping through the fluent (and object initializer syntax) mapping API. Here's an example based on your requirements
void Main()
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var connectionSettings = new ConnectionSettings(pool);
var client = new ElasticClient(connectionSettings);
client.Map<MyType>(m => m
.Index("index-name")
.AutoMap()
.Properties(p => p
.String(s => s
.Name(n => n.CompanyName)
.Fields(f => f
.String(ss => ss
.Name("raw")
.NotAnalyzed()
)
)
)
.Date(d => d
.Name(n => n.CreatedDate)
.Index(NonStringIndexOption.No)
)
.String(s => s
.Name(n => n.CompanyDescription)
.Store(false)
)
.Nested<MyChildType>(n => n
.Name(nn => nn.Locations.First())
.AutoMap()
.Properties(pp => pp
/* properties of MyChildType */
)
)
)
);
}
public class MyType
{
// Index this & allow for retrieval.
public int Id { get; set; }
// Index this & allow for retrieval.
// **Also**, in my searching & sorting, I need to sort on this **entire** field, not just individual tokens.
public string CompanyName { get; set; }
// Don't index this for searching, but do store for display.
public DateTime CreatedDate { get; set; }
// Index this for searching BUT NOT for retrieval/displaying.
public string CompanyDescription { get; set; }
// Nest this.
public List<MyChildType> Locations { get; set; }
}
public class MyChildType
{
// Index this & allow for retrieval.
public string LocationName { get; set; }
// etc. other properties.
}
This produces the mapping
{
"properties": {
"id": {
"type": "integer"
},
"companyName": {
"type": "string",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
}
}
},
"createdDate": {
"type": "date",
"index": "no"
},
"companyDescription": {
"type": "string",
"store": false
},
"locations": {
"type": "nested",
"properties": {
"locationName": {
"type": "string"
}
}
}
}
}
Calling .AutoMap() causes NEST to infer the mapping based on the property types and any attributes applied to them. Then .Properties() overrides any of the inferred mappings. For example
CompanyName is mapped as a multi_field with the field companyName analyzed using the standard analyzer and companyName.raw not analyzed. You can reference the latter in your queries using .Field(f => f.CompanyName.Suffix("raw"))
Locations is mapped as a nested type (automapping by default would infer this as an object type mapping). You can then define any specific mappings for MyChildType using .Properties() inside of the Nested<MyChildType>() call.
As far as I can see, you don't have any complex types that you are trying map. So you can easily use NEST attributes to map your objects.
Check this out:
[Nest.ElasticsearchType]
public class MyType
{
// Index this & allow for retrieval.
[Nest.Number(Store=true)]
int Id { get; set; }
// Index this & allow for retrieval.
// **Also**, in my searching & sorting, I need to sort on this **entire** field, not just individual tokens.
[Nest.String(Store = true, Index=Nest.FieldIndexOption.Analyzed, TermVector=Nest.TermVectorOption.WithPositionsOffsets)]
string CompanyName { get; set; }
// Don't index this for searching, but do store for display.
[Nest.Date(Store=true, Index=Nest.NonStringIndexOption.No)]
DateTime CreatedDate { get; set; }
// Index this for searching BUT NOT for retrieval/displaying.
[Nest.String(Store=false, Index=Nest.FieldIndexOption.Analyzed)]
string CompanyDescription { get; set; }
[Nest.Nested(Store=true, IncludeInAll=true)]
// Nest this.
List<MyChildType> Locations { get; set; }
}
[Nest.ElasticsearchType]
public class MyChildType
{
// Index this & allow for retrieval.
[Nest.String(Store=true, Index = Nest.FieldIndexOption.Analyzed)]
string LocationName { get; set; }
// etc. other properties.
}
After this declaration, to create this mapping in elasticsearch you need to make a call similar to:
var mappingResponse = elasticClient.Map<MyType>(m => m.AutoMap());
With AutoMap() call NEST will read your attributes from your POCO and create a mapping request accordingly.
Also see "Attribute Based Mapping" section from here.
Cheers!
At the time of writing, Nest does not offer a way to map a property in your class to multiple fields in your document mapping using built in attributes. However, it does provide the facilities needed to do anything with your mappings that you could do if you wrote the JSON yourself.
Here's a solution I've put together for my own needs. It shouldn't be hard to use it as the starting point for whatever you need to do.
First, here's an example of the mapping I want to generate
{
"product":{
"properties":{
"name":{
"type":"string",
"index":"not_analyzed",
"fields":{
"standard":{
"type":"string",
"analyzer":"standard"
}
}
}
}
}
}
The product document would then have the name field, which is indexed but not analyzed, and the name.standard field, which uses the standard analyzer.
The C# class that I generate the mapping from looks like this
[ElasticsearchType]
public class Product
{
[WantsStandardAnalysisField]
public string Name { get; set; }
}
Note the WantsStandardAnalysisField attribute. That's a custom attribute with no special properties added. Literally just:
public class WantsStandardAnalysisField : Attribute {}
If I were to use AutoMap as-is, my custom attribute would be ignored and I would get a mapping that has the name field, but not name.standard. Luckily, AutoMap accepts an instance of IPropertyVisitor. A base class called NoopPropertyVisitor implements the interface and does nothing at all, so you can subclass it and override only the methods you care about. When you use a property visitor with AutoMap, it will generate a document mapping for you but give you a chance to modify it before it gets sent to Elastic Search. All we need to do is look for properties marked with our custom attribute and add a field to them.
Here's an example that does that:
public class ProductPropertyVisitor : NoopPropertyVisitor
{
public override void Visit(IStringProperty type, PropertyInfo propertyInfo, ElasticsearchPropertyAttributeBase attribute)
{
base.Visit(type, propertyInfo, attribute);
var wsaf = propertyInfo.GetCustomAttribute<WantsStandardAnalysisField>();
if (wsaf != null)
{
type.Index = FieldIndexOption.NotAnalyzed;
type.Fields = new Properties
{
{
"standard",
new StringProperty
{
Index = FieldIndexOption.Analyzed,
Analyzer = "standard"
}
}
};
}
}
}
As you can see, we can do pretty much anything we want with the generated property, including turning off analysis for the main property and adding a new field with its own settings. For fun, you could add a couple properties to the custom attribute allowing you to specify the name of the field you want and the analyzer to use. You could even modify the code to see if the attribute has been added multiple times, letting you add as many fields as you want.
If you were to run this through any method that generates a mapping using AutoMap, such as:
new TypeMappingDescriptor<Product>().AutoMap(new ProductPropertyVisitor())
You'll get the desired multi-field mapping. Now you can customize mappings to your heart's content. Enjoy!
I think you have at least 2 possibilities to solve your problem:
On indexing: Create something like a metadata model, which is stored just for retrieving. See the _source field to limit the return to this field.
On searching: Specify the fields you want to query: if you don`t want to query the CreatedDate, just don't include it in your search.
In my case I am using both of these approaches to get very fast results :-)

Categories