I'm trying to implement a contains specification for nested properties. I am currently working off a contains specification below that only supports a simple property.
For an object structure like below and applying the specification to the Manager Entity.
public class Manager
{
public string Name { get; set; }
public Department Department { get; set; }
public IList<Employee> Employees { get; set; }
}
public class Department
{
public string Name { get; set; }
}
public class Employee
{
public string Name { get; set; }
}
It is able to support a filter like below.
[
{
"Name",
"John"
}
]
The problem comes when I need to support a more complicated query like below.
[
{
"Employees.Name",
"Jake"
}
]
I think the specification will also have to behave different for a filter like this because it is not looking into a collection and just a single object.
[
{
"Department.Name",
"Accounting",
}
]
The contains specification that I currently have now
private static Expression<Func<T, bool>> CreateExpression(IDictionary<string, string> filter)
{
var type = typeof(T);
var parameterExpression = Expression.Parameter(typeof(T));
var aggregatedExpression = filter.Aggregate((Expression)null, (agg, pair) =>
{
var propertyInfo = type
.GetProperties()
.FirstOrDefault(t => t.Name.Equals(pair.Key, StringComparison.InvariantCultureIgnoreCase));
if (propertyInfo == null)
{
throw new Exception("PropertyInfo was not found.");
}
// Call to string on value if property is of the listed property types
var propertyExpression = (Expression)Expression.Property(parameterExpression, propertyInfo);
if (propertyInfo.PropertyType == typeof(long) ||
propertyInfo.PropertyType == typeof(long?) ||
propertyInfo.PropertyType == typeof(int) ||
propertyInfo.PropertyType == typeof(int?) ||
propertyInfo.PropertyType == typeof(DateTime) ||
propertyInfo.PropertyType == typeof(DateTime?) ||
propertyInfo.PropertyType == typeof(Guid) ||
propertyInfo.PropertyType == typeof(Guid?))
{
var toStringMethod = propertyExpression.Type.GetMethod(nameof(ToString), new Type[] { });
if (toStringMethod != null)
{
propertyExpression = Expression.Call(propertyExpression, toStringMethod);
}
}
var containsMethod = propertyExpression.Type.GetMethod(nameof(Queryable.Contains), new[] { propertyExpression.Type });
if (containsMethod == null)
{
throw new Exception("Contains method is not found.");
}
// Call contains on the property (i.e. Name.Contains("John")
var expression = Expression.Call(propertyExpression, containsMethod, Expression.Constant(pair.Value));
// Multiple Contains to be aggregated into OrElse
agg = agg == null ? expression : (Expression)Expression.OrElse(agg, expression);
return agg;
});
return Expression.Lambda<Func<T, bool>>(aggregatedExpression, parameterExpression);
}
Related
When I run this code:
var result = _client.Index<EntityType>(item, i => i.Index(n));
I'm getting this error:
Exception has occurred: CLR/System.StackOverflowException An unhandled
exception of type 'System.StackOverflowException' occurred in
Elasticsearch.Net.dll
The full method:
public bool Index<EntityType>(EntityType item, int attempt = 0) where EntityType : class, IDomainEntity<int>
{
const int maxRetries = 5;
if (item == null)
{
return false;
}
var type = item.GetType();
var attributes = type.CustomAttributes;
string n = "";
foreach (var attribute in attributes)
{
foreach (var arg in attribute.NamedArguments)
{
if (arg.MemberName == "RelationName")
{
n = arg.TypedValue.Value.ToString().ToLower();
}
}
}
var result = _client.Index<EntityType>(item, i => i.Index(n));
if (!CheckResponse(result) && attempt < maxRetries)
{
RefreshClient<EntityType>();
attempt++;
return Index(item, attempt);
}
RefreshClient<EntityType>();
return result.IsValid;
}
I added [PropertyName("propertyToIgnoreInElasticsearch", Ignore = true)] from NEST to my POCO fields which were causing an infinite loop while Indexing. It ignores a field from the Elasticsearch Index so it is not indexed.
for example:
[Serializable]
public abstract class VeganItem<VeganItemEstablishmentType>
{
[Required]
public string Name { get; set; }
[PropertyName("veganItemEstablishments", Ignore = true)]
public virtual ICollection<VeganItemEstablishmentType> VeganItemEstablishments { get; set; }
}
I'm using Elastisearch.NET with NEST 2.3. I want to use attribute mapping but I only want to index certain properties. As I understand it all properties are indexed unless you ignore them using for example [String(Ignore = true)] Is it possible to ignore all properties by default and only index the ones that have a nest attribute attached to them? Like JSON.NETs MemberSerialization.OptIn
You could do this using a custom serializer to ignore any properties not marked with a NEST ElasticsearchPropertyAttributeBase derived attribute.
void Main()
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var connectionSettings = new ConnectionSettings(
pool,
new HttpConnection(),
new SerializerFactory(s => new CustomSerializer(s)));
var client = new ElasticClient(connectionSettings);
client.CreateIndex("demo", c => c
.Mappings(m => m
.Map<Document>(mm => mm
.AutoMap()
)
)
);
}
public class Document
{
[String]
public string Field1 { get; set;}
public string Field2 { get; set; }
[Number(NumberType.Integer)]
public int Field3 { get; set; }
public int Field4 { get; set; }
}
public class CustomSerializer : JsonNetSerializer
{
public CustomSerializer(IConnectionSettingsValues settings, Action<JsonSerializerSettings, IConnectionSettingsValues> settingsModifier) : base(settings, settingsModifier) { }
public CustomSerializer(IConnectionSettingsValues settings) : base(settings) { }
public override IPropertyMapping CreatePropertyMapping(MemberInfo memberInfo)
{
// if cached before, return it
IPropertyMapping mapping;
if (Properties.TryGetValue(memberInfo.GetHashCode(), out mapping))
return mapping;
// let the base method handle any types from NEST
// or Elasticsearch.Net
if (memberInfo.DeclaringType.FullName.StartsWith("Nest.") ||
memberInfo.DeclaringType.FullName.StartsWith("Elasticsearch.Net."))
return base.CreatePropertyMapping(memberInfo);
// Determine if the member has an attribute
var attributes = memberInfo.GetCustomAttributes(true);
if (attributes == null || !attributes.Any(a => typeof(ElasticsearchPropertyAttributeBase).IsAssignableFrom(a.GetType())))
{
// set an ignore mapping
mapping = new PropertyMapping { Ignore = true };
Properties.TryAdd(memberInfo.GetHashCode(), mapping);
return mapping;
}
// Let base method handle remaining
return base.CreatePropertyMapping(memberInfo);
}
}
which produces the following request
PUT http://localhost:9200/demo?pretty=true
{
"mappings": {
"document": {
"properties": {
"field1": {
"type": "string"
},
"field3": {
"type": "integer"
}
}
}
}
}
I have an enum type field called Title.
[Serializable]
public enum Title
{
NotSet,
Miss = 4,
Mr = 1,
Mrs = 3,
Ms = 2
}
I want to bind a property of type Title to the Razor View but I don't want it to be a required field. However, on tabbing out or OnBlur, it is showing as required, although I have not specified this as required.
Is there any way I can get around this?
create
namespace YourApplicationName.Helper
{
public class ModelValueListProvider : IEnumerable<SelectListItem>
{
List<KeyValuePair<string, string>> innerList = new List<KeyValuePair<string, string>>();
public static readonly ModelValueListProvider TitleList = new TitleListProvider();
protected void Add(string value, string text)
{
string innerValue = null, innerText = null;
if (value != null)
innerValue = value.ToString();
if (text != null)
innerText = text.ToString();
if (innerList.Exists(kvp => kvp.Key == innerValue))
throw new ArgumentException("Value must be unique", "value");
innerList.Add(new KeyValuePair<string, string>(innerValue, innerText));
}
public IEnumerator<SelectListItem> GetEnumerator()
{
return new ModelValueListProviderEnumerator(innerList.GetEnumerator());
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private struct ModelValueListProviderEnumerator : IEnumerator<SelectListItem>
{
private IEnumerator<KeyValuePair<string, string>> innerEnumerator;
public ModelValueListProviderEnumerator(IEnumerator<KeyValuePair<string, string>> enumerator)
{
innerEnumerator = enumerator;
}
public SelectListItem Current
{
get
{
var current = innerEnumerator.Current;
return new SelectListItem { Value = current.Key, Text = current.Value };
}
}
public void Dispose()
{
try
{
innerEnumerator.Dispose();
}
catch (Exception)
{
}
}
object System.Collections.IEnumerator.Current
{
get
{
return Current;
}
}
public bool MoveNext()
{
return innerEnumerator.MoveNext();
}
public void Reset()
{
innerEnumerator.Reset();
}
}
private class TitleListProvider : ModelValueListProvider
{
public TitleListProvider (string defaultText = null)
{
if (!string.IsNullOrEmpty(defaultText))
Add(string.Empty, defaultText);
Add(Title.NotSet, "NotSet");
Add(Title.Miss , "Miss");
Add(Title.Mr , "Mr");
Add(Title.Mrs , "Mrs");
Add(Title.MS, "MS");
}
public void Add(Title value, string text)
{
Add(value.ToString("d"), text);
}
}
}
}
in your model
public Title? Titleformation { get; set; }
public string[] SelectedTitle { get; set; }
in your view, also add the name space to your view
#using YourApplicationName.Helper;
#Html.ListBoxFor(m => m.SelectedTitle , new SelectList(ModelValueListProvider.TitleList, "Value", "Text"))
hope this help you
Enums require values, and cannot be null (aka not set) despite what someone commented above. What I do for salutations is have a "none" member of the enum, and whenever I print this out, I just check in the code to see if the value of the enum is > 0 (aka, the none option) and don't print it.
public enum Salutation { none,
[Description("Mr.")] Mr,
[Description("Mrs.")] Mrs,
[Description("Ms.")]Ms,
[Description("Miss")] Miss }
Use a class rather than enum ie:
public class Title
{
NotSet;
Miss = 4;
Mr = 1;
Mrs = 3;
Ms = 2;
}
I'm developing a simple web app where I need to bind all types implementing and interface of a specific type. My interface has one single property like this
public interface IContent {
string Id { get;set; }
}
a common class using this interface would look like this
public class Article : IContent {
public string Id { get;set; }
public string Heading { get;set; }
}
to be clean here the article class is just one of many different classes implementing IContent so therefor I need a generic way of storing and updating these types.
So in my controller I have the put method like this
public void Put(string id, [System.Web.Http.ModelBinding.ModelBinder(typeof(ContentModelBinder))] IContent value)
{
// Store the updated object in ravendb
}
and the ContentBinder
public class ContentModelBinder : System.Web.Http.ModelBinding.IModelBinder {
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) {
actionContext.ControllerContext.Request.Content.ReadAsAsync<Article>().ContinueWith(task =>
{
Article model = task.Result;
bindingContext.Model = model;
});
return true;
}
}
The code above does not work because it does not seem to get hold of the Heading property even though if I use the default model binder it binds the Heading correctly.
So, in the BindModel method I guess I need to load the correct object from ravendb based on the Id and then update the complex object using some kind of default model binder or so? This is where I need some help.
Marcus, following is an example which would work fine for both Json and Xml formatter.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using System.Web.Http;
using System.Web.Http.SelfHost;
namespace Service
{
class Service
{
private static HttpSelfHostServer server = null;
private static string baseAddress = string.Format("http://{0}:9095/", Environment.MachineName);
static void Main(string[] args)
{
HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(baseAddress);
config.Routes.MapHttpRoute("Default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
try
{
server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
Console.WriteLine("Service listenting at: {0} ...", baseAddress);
TestWithHttpClient("application/xml");
TestWithHttpClient("application/json");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine("Exception Details:\n{0}", ex.ToString());
}
finally
{
if (server != null)
{
server.CloseAsync().Wait();
}
}
}
private static void TestWithHttpClient(string mediaType)
{
HttpClient client = new HttpClient();
MediaTypeFormatter formatter = null;
// NOTE: following any settings on the following formatters should match
// to the settings that the service's formatters have.
if (mediaType == "application/xml")
{
formatter = new XmlMediaTypeFormatter();
}
else if (mediaType == "application/json")
{
JsonMediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
jsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
formatter = jsonFormatter;
}
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Get;
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
HttpResponseMessage response = client.SendAsync(request).Result;
Student std = response.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("GET data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std))
{
Console.WriteLine("both are equal");
}
client = new HttpClient();
request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Post;
request.Content = new ObjectContent<Person>(StudentsController.CONSTANT_STUDENT, formatter);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
Student std1 = client.SendAsync(request).Result.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("POST and receive data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std1))
{
Console.WriteLine("both are equal");
}
}
}
public class StudentsController : ApiController
{
public static readonly Student CONSTANT_STUDENT = new Student() { Id = 1, Name = "John", EnrolledCourses = new List<string>() { "maths", "physics" } };
public Person Get()
{
return CONSTANT_STUDENT;
}
// NOTE: specifying FromBody here is not required. By default complextypes are bound
// by formatters which read the body
public Person Post([FromBody] Person person)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState));
}
return person;
}
}
[DataContract]
[KnownType(typeof(Student))]
public abstract class Person : IEquatable<Person>
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
// this is ignored
public DateTime DateOfBirth { get; set; }
public bool Equals(Person other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (this.Id != other.Id)
return false;
if (this.Name != other.Name)
return false;
return true;
}
}
[DataContract]
public class Student : Person, IEquatable<Student>
{
[DataMember]
public List<string> EnrolledCourses { get; set; }
public bool Equals(Student other)
{
if (!base.Equals(other))
{
return false;
}
if (this.EnrolledCourses == null && other.EnrolledCourses == null)
{
return true;
}
if ((this.EnrolledCourses == null && other.EnrolledCourses != null) ||
(this.EnrolledCourses != null && other.EnrolledCourses == null))
return false;
if (this.EnrolledCourses.Count != other.EnrolledCourses.Count)
return false;
for (int i = 0; i < this.EnrolledCourses.Count; i++)
{
if (this.EnrolledCourses[i] != other.EnrolledCourses[i])
return false;
}
return true;
}
}
}
I used #kiran-challa solution and added TypeNameHandling on Json media type formatter's SerializerSettings.
I have an application that uses documents, that contain list of attributes in a dictionary, for some reason we need to use a static index and query/filter over these attributes.
A prototype looks like this:
class Program
{
static void Main(string[] args)
{
IDocumentStore store = new DocumentStore() { DefaultDatabase = "Test", Url = "http://localhost:8081" };
store.Initialize();
IndexCreation.CreateIndexes(typeof(Program).Assembly, store);
using (var session = store.OpenSession())
{
session.Store(new Document { Id = "1", Name = "doc_name", Attributes = new Dictionary<string, object> { { "Type", "1" }, { "Status", "Active" } } });
session.SaveChanges();
}
using (var session = store.OpenSession())
{
// works
var l1 = session.Query<Document, Documents_Index>().Where(a => a.Attributes["Type"] == "1").ToList();
// not working
var l2 = session.Query<Document, Documents_Index>().Where(a => a.Attributes["Status"] == "Active").ToList();
}
}
}
public class Documents_Index : AbstractIndexCreationTask<Document>
{
public Documents_Index()
{
Map = docs => docs.Select(a =>
new
{
a.Name,
a.Attributes,
Attributes_Type = a.Attributes["Type"]
});
}
}
[Serializable]
public class Document
{
public string Id { get; set; }
public string Name { get; set; }
public Dictionary<string, object> Attributes { get; set; }
}
But since I need to query using any arbitrary Attribute name/value this index does solve our problem. Actually the list of attributes is known at run-time (so we tried modifying the Map expression to inject any number of attribute names, but so far we weren't successful). Is there a way how to define the index in some dynamic fashion?
You need to write it like:
public class Documents_Index : AbstractIndexCreationTask<Document>
{
public Documents_Index()
{
Map = docs => docs.Select(a =>
new
{
a.Name,
_ = a.Attributes.Select(x=>CreateField("Attributes_"+x.Key, x.Value),
});
}
}