elasticsearch nest 7.1 aggregation Fields generic shorthand field expression - elasticsearch

so reading this:
https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.x/field-inference.html
given a class of
public class MyData {
public Guid UserId { get; set; }
public decimal Value { get; set; }
}
I would expect to be able to do something like:
var fieldExpression = Field<MyData >(p => p.Value);
but I get an error of The non-generic type Field cannot be used with type arguments.
I am using Nest 7.1.0.
My goal was to be able to create a method that can have aggregations and queries passed in and then combined into running on a instance of a nest ElasticClient.
something like (I think)
var sr = new SearchRequest<MyData>
{
Aggregations = new ChildrenAggregation("name_of_child_agg", typeof(decimal?))
{
Aggregations =
new AverageAggregation("average_per_child", Field<MyData>(p => p.value))
&& new MaxAggregation("max_per_child", Field<MyData>(p => p.value))
&& new MinAggregation("min_per_child", Field<MyData>(p => p.value))
}
};
_client.Search<MyData>(sr);

As stated in the documentation you need to add a static import to be able to write the code using the same style
using static Nest.Infer;
Otherwise you need to use simple new Field(..) instantiation

Related

Include/Exclude fields in query with MongoDB C# driver 2.4

We have a collection contains documents in the server. Each document is like:
{ _id: "...", Prop1: "", Prop2: "", Prop3: "", LargeField: "", ... }
There're many other fields but they're not required by the client.
I want to load documents as MyDoc class whose definition is:
public class MyDoc {
public string Id { get; set; }
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public string Prop3 { get; set; }
public string LargeField { get; set; }
}
I've tried:
var client = new MongoClient(uri);
var database = client.GetDatabase("MyDatabase");
var collection = database.GetCollection<MyDocs>("MyDocs");
var allDocs = collection.Find().ToList();
Then it will load all the fields for each document, so I have to put [BsonIgnoreExtraElements] on MyDoc. The issue here is that the document is large but I only needs a limit subset of fields. Is it possible to let the driver know I only need the fields defined in the class?
If not, is it possible to exclude some of the fields like the LargeField to make the result set smaller? I've tried:
var fieldsBuilder = Builders<MyDoc>.Projection;
var fields = fieldsBuilder.Exclude(d => d.LargeField);
var allDocs = collection.Find().Project(fields).ToList();
But now allDocs becomes BsonDocument list instead of the MyDoc list. How to query MyDoc with projection?
Can someone help? It's rather simple in legacy MongoDB driver but I don't know how to do it in the new driver. Thanks.
I had a similar issue, I believe you need to specify the generic type, for some reason Project automatically assumes BsonDocument. This should fix it from BsonDocument to your class.
Change:
var allDocs = collection.Find().Project(fields).ToList();
To:
var allDocs = collection.Find<MyDoc>().Project<MyDoc>(fields).ToList();
As for how to include only certain fields, this can be done just as you are doing it with builder (using Include) or with a string in json form like:
var allDocs = collection.Find<MyDoc>().Project<MyDoc>("{Prop1: 1, Prop2: 1}").ToList();
I would highly recommend checking out projection on this guy's post:
https://www.codementor.io/pmbanugo/working-with-mongodb-in-net-part-3-skip-sort-limit-and-projections-oqfwncyka
From this article:
This brings us to another difference: with a projection definition, it implicitly converts the document type from Student to BsonDocument, so what we get back is a fluent object that, in result, will be a BsonDocument (even though what we're working with is the Student type). If we want to work with Student, we have to indicate that we still want to keep the type to Student.
A newer way:
var fieldsBuilder = Builders<MyDoc>.Projection;
var fields = fieldsBuilder.Exclude(d => d.BigField1).Exclude(d => d.BigField2);
return Collection.Find(x => x.id.Equals(id)).Project<MyDoc>(fields).ToEnumerable();
Gina
We can use FindAsync as well. and to use projection in FindSync, we have to pass the FindOptions something like this: It will select countryName, ID and population.
FindAsync loads documents one by one from DB cursor in async manner, it's a good choice when you have a huge database.
Relevant Code:
cursor = await collection.FindAsync(y => y.CountryID > 205,
new FindOptions<MyDoc, MyDoc>()
{
BatchSize = 20,
NoCursorTimeout = true,
AllowPartialResults = true,
Projection = "{'_id':1,'Name':1,'Population':1}"
}).ConfigureAwait(false);

Expression.Property(param, field) is "trolling" me [System.ArgumentException] = {"Instance property 'B.Name' is not defined for type A"}

Once again, I am facing an issue, this time with LINQ Expression builder and this time I am even struggling to find the reason why it's not working. I have a Database-First EF project with quite a few tables. For this specific case, I have to use 2 of them - DocHead and Contragent. MyService.metadata.cs looks like this:
[MetadataTypeAttribute(typeof(DocHead.DocHeadMetadata))]
public partial class DocHead
{
// This class allows you to attach custom attributes to properties
// of the DocHead class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class DocHeadMetadata
{
// Metadata classes are not meant to be instantiated.
private DocHeadMetadata()
{
}
public string doc_Code { get; set; }
public string doc_Name { get; set; }
public string doc_ContrCode { get; set; }
//...
[Include]
public Contragent Contragent { get; set; }
}
}
[MetadataTypeAttribute(typeof(Contragent.ContragentMetadata))]
public partial class Contragent
{
// This class allows you to attach custom attributes to properties
// of the Contragent class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class ContragentMetadata
{
// Metadata classes are not meant to be instantiated.
private ContragentMetadata()
{
}
public string Code { get; set; }
public string Name { get; set; }
//...
I take some docHeads like this:
IQueryable<DocHead> docHeads = new MyEntities().DocHead;
Then I try to sort them like this:
docHeads = docHeads.OrderByDescending(x => x.Contragent.Name);
It is all working like I want it. I get those docHeads sorted by the name of the joined Contragent. My problem is that I will have to sort them by a field, given as a string parameter. I need to be able to write something like this:
string field = "Contragent.Name";
string linq = "docHeads = docHeads.OrderByDescending(x => x." + field + ")";
IQueryable<DocHead> result = TheBestLinqLibraryInTheWorld.PrepareLinqQueryable(linq);
Unfortunately, TheBestLinqLibraryInTheWorld does not exist (for now). So, I have set up a method as a workaround.
public static IQueryable<T> OrderByField<T>(this IQueryable<T> q, string SortField, bool Ascending)
{
var param = Expression.Parameter(typeof(T), "x");
var prop = Expression.Property(param, SortField); // normally returns x.sortField
var exp = Expression.Lambda(prop, param); // normally returns x => x.sortField
string method = Ascending ? "OrderBy" : "OrderByDescending";
Type[] types = new Type[] { q.ElementType, exp.Body.Type };
var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp); // normally returns sth similar to q.OrderBy(x => x.sortField)
return q.Provider.CreateQuery<T>(mce);
}
Normally... yes, when it comes to own properties of the class DocHead - those prefixed with doc_. The disaster strikes when I call this method like this:
docHeads = docHeads.OrderByField<DocHead>("Contragent.Name", true); // true - let it be Ascending order
To be more specific, the exception in the title is thrown on line 2 of the method OrderByField():
var prop = Expression.Property(param, SortField);
In My.edmx (the model), the tables DocHead and Contragent have got a relation already set up for me, which is the following: 0..1 to *.
Once again, I have no problem writing "static" queries at all. I have no problem creating "dynamic" ones via the method OrderByField(), but only when it comes to properties of the class DocHead. When I try to order by a prop of the joined Contragent class - the disaster strikes. Any help will be greatly appretiated, thank you!
The problem is that Expression.Property method does not support nested properties. It does exactly what it says - creates expression that represents a property denoted by propertyName parameter of the object denoted by the expression parameter.
Luckily it can easily be extended. You can use the following simple Split / Aggregate trick anytime you need to create a nested property access expression:
var prop = SortField.Split('.').Aggregate((Expression)param, Expression.Property);

How do you manually construct a lambda expression?

I'm designing an application using ASP.NET Web API and Entity Framework 5 and LINQ to Entities. The Web API doesn't serve up the entities directly, it converts them to a set of data transfer objects that are similar but not identical to my entities. The API will be used by a Silverlight application initially but I will have to support non-.NET clients (e.g. iOS apps) down the road. I'd also like to give the client the ability to run a robust set of queries against the API.
These requirements have lead me to consider the query object pattern. Essentially, I want to create a homegrown query object client-side, post it to the Web API, and convert the query object to a lambda expression that I can use in LINQ to Entities. This last part is what's tripping me up.
Starting with a simple comparison query, I want to be able to convert an object that looks like the following into a lambda expression at runtime.
public enum QueryOperator
{
None = 0,
GreaterThan,
GreaterThanOrEqualTo,
EqualTo,
NotEqualTo,
LessThanOrEqualTo,
LessThan
}
public class SimpleQuery<T>
{
public SimpleQuery()
{
this.Field = null;
this.Operator = QueryOperator.None;
this.Value = null;
}
public string Field { get; set; }
public QueryOperator Operator { get; set; }
public object Value { get; set; }
public IEnumerable<T> Execute(IQueryable<T> queryTarget)
{
// ????
}
}
How can I do this?
I've had to do things like this in the past. Here's what I came up with:
public IEnumerable<T> Execute(IQueryable<T> queryTarget)
{
return queryTarget.Where(this.GetWhereExpression<T>());
}
private Expression<Func<T, bool>> GetWhereExpression<T>()
{
var param = Expression.Parameter(typeof(T), "x");
var prop = Expression.Property(param, this.Field);
var value = Expression.Constant(this.Value, prop.Type);
Expression compare = null;
switch(this.Operator)
{
case QueryOperator.EqualTo:
compare = Expression.Equal(prop, value);
break;
...
}
return Expression.Lambda(compare, param);
}

Linq Expressions on Child entities

I am building dynamic linq expressions which is working fine for a single entity.
For example:
I have a class called Employee and empeduinfo
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
public class EmpEduInfo
{
public int Id { get; set; }
public string Name { get; set; }
public int EmpId { get; set; }
}
I need to get all the the employees and empeduinfo class starts with "x"
I prepared expression for startswith("x")
var temp= entities.employees.Include("EmpEduInfo").Where(mydynamicexpression);
In this case it is filtering only parent table not on child.
I need to prepare generic expression so than i need to filter both parent and child objects dynamically.
Without using expression I know a solution:
var temp= (from ee in entities.Employee.Include("EmpEduInfo").Where(x => x.name.StartsWith("t"))
where ee.EmpEduInfo.Where(x => x.name.StartsWith("t")).Count()>0
select ee).ToList();
using expressions I am building generic expression to provide dynamic advance search rather than writing in each and every entity.
Here is my expression details
// Get the method information for the String.StartsWith() method
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
// Build the parameter for the expression
ParameterExpression empparam= Expression.Parameter(typeof(employee), "ename");;
// Build the member that was specified for the expression
MemberExpression field = Expression.PropertyOrField(empparam, "name");
// Call the String.StartsWith() method on the member
MethodCallExpression startsWith = Expression.Call(field, mi, Expression.Constant("t"));
var namelamda = Expression.Lambda<Func<employee, bool>>(startsWith, new ParameterExpression[] { empparam });
var temp = entities.employees.Include("empedudetails").Where(namelamda).ToList();
You can look at the Expression the compiler generates using IQueryable:
IQueryable<Employee> query =
from ee in entities.Employee ...
var expression = query.Expression;
Look at expression in a debugger to see what you need to generate - LINQPad is good for this.
You might want to simplify your query a bit first:
IQueryable<Employee> query =
from ee in entities.Employee.Include("EmpEduInfo")
where
ee.name.StartsWith("t") &&
ee.EmpEduInfo.Any(x => x.name.StartsWith("t"))
select ee;
I was trying in EF 4.0 either we have write DB extentions for the same.
Option is provided in EF 4.1
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application
Thanks.

NHibernate: HasMany components and Where/Contains clause

I'm trying to work out how to create a query using Linq to NHibernate.
I have two classes like this:
public class Foo
{
private ISet<Bar> _bars = new HashedSet<Bar>();
public virtual ISet<Bar> Bars
{
get { return _bars; }
set { _bars = value; }
}
}
public class Bar
{
public string Name { get; set; }
public string Color { get; set; }
}
Foo's Bar collection is mapped as a one-to-many component collection.
Now I want to run a query that should look something like this:
var myBar = new Bar { Name = "test", Color = "testColor" };
var matchingFoos = Session.Linq<Foo>
.Where(foo => foo.Bars.Contains(myBar),
new BarEqualityComparer())
.ToList();
I am not sure if this is correct, but whenever I run this query I get a NullReferenceException from inside a method called NHibernate.Linq.Visitors.WhereArgumentsVisitor.GetCollectionContainsCriteria method.
Could anyone help me out with an alternative means of running this query?
The BarEqualityComparer will be the point of failure for sure. There is no simple way for the provider to translate custom class to an SQL statement.

Resources