How to use collection.Aggregate.Project() in MongoDB? - c#

Consider the following piece of code:
var results = collection.Aggregate()
...
.Lookup( ... )
.Project( ??? );
I need to call Project() on the above query. I haven't been able to figure out how to build a projection definition of type ProjectionDefinition<T1, T2>, which is what Project() requires.
The Builders class doesn't seem to work in this case:
var projection = Builders<Event>.Projection.Include(x => x).Include("agg_res.SomeField");
as it instantiates a definition of type ProjectionDefinition<T>.

I found the answer. An aggregation can perform a lookup and projection at the same time by using a different overload of Lookup():
var results = collection.Aggregate()
.Match(filter)
.Lookup<Event, User, AggregatedEvent>(users, e => e.OwnerId, u => u.Id, r => r.OwnerUsers)
.ToList();
This allows one to use lambdas to indicate which fields should be matched and where to place the join results (OwnerUsers in the above example). Note that AggregatedEvent extends Event and includes an array field called OwnerUsers. This is where the matches are placed.

Related

Not able to Select after using Expression Tree in Group By

I have been referring this post to group by using expression tree. Here is my code:
String[] fields = { "DepartmentID", "SkillID" };
var groupLambda = GroupByExpression<Person>(fields);
var query = dbContext.People.GroupBy(groupLambda.Compile());
var queryResult = query.ToList();
Here is the method GroupByExpression which uses solution given in aforesaid post (Thanks Daniel!):
public static Expression<Func<TItem, object>> GroupByExpression<TItem>(string[] propertyNames)
{
var properties = propertyNames.Select(name => typeof(TItem).GetProperty(name)).ToArray();
var propertyTypes = properties.Select(p => p.PropertyType).ToArray();
var tupleTypeDefinition = typeof(Tuple).Assembly.GetType("System.Tuple`" + properties.Length);
var tupleType = tupleTypeDefinition.MakeGenericType(propertyTypes);
var constructor = tupleType.GetConstructor(propertyTypes);
var param = Expression.Parameter(typeof(TItem), "x");
var body = Expression.New(constructor, properties.Select(p => Expression.Property(param, p)));
var expr = Expression.Lambda<Func<TItem, object>>(body, param);
return expr;
}
I want to be able to identify fields in the group by keys with strong names in select part like query.Select(x => new { x.Key.DepartmentID, x.Key.SkillID });
How do I do this?
Now... I won't give you the solution to the question you asked, but I'll try to help you :-)
If you want to do dynamic queries, you should probably use DynamicLinq
With DynamicLinq you can do things like:
IQueryable query = context.YourTable;
var groups = query.GroupBy("new (Field1, Field2)");
I'm rereading your question...
I want to be able to identify fields in the group by keys with strong names in select part like query.Select(x => new { x.Key.DepartmentID, x.Key.SkillID });
You can't. GroupBy in general will return a IGrouping<TKey, TSource>. TKey is dynamic (because you build it based on strings), so you can't "extract" it and pass it to the compiler, so you can't do the select with strong names.
There is a single exception: if you know the types and numbers of the GroupBy TKey then something can be done. So, you gave us:
String[] fields = { "DepartmentID", "SkillID" };
If you always have two int then you can cast your query with:
.Cast<IGrouping<Tuple<int, int>, Person>>()
.Select(x => new { x.Key.DepartmentID, x.Key.SkillID });
Note that, as I've written in a comment, your GroupBy will be executed client-side, and everything after the GroupBy will be executed client-side (where client-side == where your program is vs sql-side == where your sql server is)!
DynamicLinq will solve the problem of executing the query sql-side instead of client-side, but won't solve the problem of strong vs weak naming (after a DynamicLinq you can: A) use .Cast<>() method or B) return a dynamic object/IEnumerable<dynamic>)
The syntax you're using new { x.Key.DepartmentID, x.Key.SkillID } constructs an anonymous class at compile time. If you want to create an anonymous class at runtime, see here. However, that won't allow you to "identify fields in the group by keys with strong names". If you want to construct an anonymous class at runtime, but be able to use those names at compile time, I'm afraid that's impossible.

Return an IQueryable<dynamic> to filter in the parent method

For example, I have this code:
IQueryable<MyModel> q = new List<MyModel>().AsQueryable(); // this is just an example, this is obviously not a list
var query = from item in q select new { item.Property };
var oneItem = query.FirstOrDefault(x => x.SomeProperty == somevalue);
var allItems = query.ToArray();
Now in a bit more complex situation, I need to get oneItem and allItems in two different methods. So to follow DRY, i'd like to move my query to a private method and then in the consuming ones just call this.GetQuery().FirstOrDefault() or .ToArray() as required.
However, when I try to have the method as IQueryable<dynamic> I get the 'An expression tree may not contain a dynamic operation' error. If I change it to IQueryable<object> then my filtering in the oneItem doesn't work.
You need to return
IQueryable<MyObject>
You can make your methods/classes dry by using genrics eg
IQuerable<T> GetQueryable()
Then the consumer can specify what T should be and your away.
You can't use dynamic with linq. See here to understand why.
For two methods to communicate they must understand the same type so you really want to project into a named type.
If you insist on using dynamic programming it can be done but you will need a lot of casting because dynamic is not a type but just a way of treating object:
IQueryable<MyModel> q = new List<MyModel>().AsQueryable(); // this is just an example, this is obviously not a list
IQueryable<object> query = from item in q select (object)new { item.Property };
var oneItem = query.FirstOrDefault(x => ((dynamic)x).SomeProperty == somevalue);
object[] allItems = query.ToArray();

Merging two iqueryables that are projected to one entity from different tables

I have tried This answer, This one and this one to merge two iqueryables. But I always receive the following error:
The type 'Estudio' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.
I'm mapping from two different but similar Entity Framework Entities (EXAMEN and EXPLORACION) to my domain entity Estudio, with the following code.
IQueryable<Estudio> listExamen = context.Set<EXAMEN>().Project().To<Estudio>();
IQueryable<Estudio> listExploracion = context.Set<EXPLORACION>().Project().To<Estudio>();
var listCombined = listExamen.Concat(listExploracion);
Is there anyway of generate a IQueryable (not enumerable) with the merging of both list? If AsEnumerable() is used, then the following filters (Order, Take, etc) are executed on memory. So I need to merge the list but still be able to apply filter to the merged list wihtout execute the queries.
//This will force the next condition is executed on memory
var listCombined = listExamen.AsEnumerable().Concat(listExploracion);
Is that possible?
I would try to select your data into an anonymous type in your linq query, perform the union, and add your criteria.
var listExamen = context.Examen
.Select(x => new { x.Prop1, x.Prop2, ... }); // Add properties
var listExploracion = context.Exploraction
.Select(x => new { x.Prop1, x.Prop2, ... }); // Add identical properties
var listCombined = listExamen.Concat(listExploracion);
var whereAdded = listCombines
.Where(x => x.Prop1 == someValue);
var result = whereAdded
.Skip(skipCount)
.Take(takeCount)
.ToList();
Note: I have no idea if you can use Common Table Expressions (the SQL necessity for skip/take) in combination with a Union-query
Note: I've changed the methods used to create the expressions, since I do not know your methods (Project, To)
So I think the solution is not to cast to a specific type, but to an anonymous type, since that probably can be translated to SQL.
Warning: didn't test it
My solution was to revise my mapping code. Instead of using individual property-based mappers, I had to project the entire entity at once, making sure that all of the properties were given in the same order.
So, instead of the ForMember syntax:
Mapper.CreateMap<Client, PersonResult>()
.ForMember(p => p.Name, cfg => cfg.MapFrom(c => c.Person.FirstName + " " + c.Person.LastName))
...
I used the ProjectUsing syntax:
Mapper.CreateMap<Client, PersonResult>()
.ProjectUsing(c => new PersonResult()
{
Name = c.Person.FirstName + " " + c.Person.LastName
...
});
This must be because of the way AutoMapper constructs its projections.
One way to work around this is to add dummy types:
class Estudio<T> : Estudio { }
And new mapping:
Mapper.CreateMap<Estudio , Estudio>();
Mapper.CreateMap<EXAMEN , Estudio<EXAMEN>>();
Mapper.CreateMap<EXPLORACION, Estudio<EXPLORACION>>();
One caveat is that all fields in Estudio need some value in mapping.
You can't use ignore. Returning 0 or "" is fine.
Now we can do:
var a = context.Set<EXAMEN>().ProjectTo<Estudio<EXAMEN>>();
var b = context.Set<EXPLORACION>().ProjectTo<Estudio<EXPLORACION>>();
return a.ProjectTo<Estudio>().Concat(b.ProjectTo<Estudio>());

Nest SuggestCompletion usage, throws 'is not a completion suggest field' exception

I'm a complete beginner to elasticsearch and I have been trying to use elasticsearch's completion suggester using Nest for auto-complete on a property.
Here is my mapping (as mentioned here: ):
var createResult = client.CreateIndex(indexName, index => index
.AddMapping<Contact>(tmd => tmd
.Properties(props => props
.Completion(s =>
s.Name(p => p.CompanyName.Suffix("completion"))
.IndexAnalyzer("standard")
.SearchAnalyzer("standard")
.MaxInputLength(20)
.Payloads()
.PreservePositionIncrements()
.PreserveSeparators())
)
)
);
var resultPerson = client.IndexMany(documents.OfType<Person>(), new SimpleBulkParameters { Refresh = true });
var resultCompany = client.IndexMany(documents.OfType<Company>(), new SimpleBulkParameters { Refresh = true });
And while indexing I'm just making use of IndexMany method and passing the IEnumberable<Contact> (Contact has a property by name CompanyName, Contact is an abstract class, both Person and Company are concrete implementations of it). The search throws an exception saying ElasticSearchException[Field [companyName] is not a completion suggest field]. And the query looks like below:
SearchDescriptor<Contact> descriptor = new SearchDescriptor<Contact>();
descriptor = descriptor.SuggestCompletion("suggest", c => c.OnField(f => f.CompanyName).Text(q));
var result = getElasticClientInstance("contacts").Search<Contact>(body => descriptor);
string qe = result.ConnectionStatus.ToString();
What am I doing wrong here, I looked into Nest's tests on SuggestCompletion but not much help, meaning the test only depict on how to get suggestions but not on how to set index mappings for SuggestCompletion.
I also tried setting up edgeNgram tokenizer as mentioned in this post but, couldn't proceed there as well.
Any direction or an example on how to proceed would greatly help.
UPDATE
You are try to create a property with the name "companyName.completion" but at that position its not valid and it will use the last token "completion". So its actually mapping a field called completion.... try changing the call to: .Name(p => p.CompanyName)
Other observations
You specify a mapping for the Contact but while indexing you use the Person and Company types.
In elasticsearch terms you mapped:
/index/contact/
but your documents are going into:
/index/person/ and /index/company
NEST won't automatically map all implementation of a specific class and elasticsearch has no way of knowing the three are related.
I would refactor the mapping to a method and call it for all the types involved.
var createResult = client.CreateIndex(indexName, index => index
.AddMapping<Contact>(tmd => MapContactCompletionFields(tmd))
.AddMapping<Person>(tmd => MapContactCompletionFields(tmd))
.AddMapping<Company>(tmd => MapContactCompletionFields(tmd))
);
private RootObjectMappingDescriptor<TContact> MapContactCompletionFields<TContact>(
RootObjectMappingDescriptor<TContact> tmd)
where TContact : Contact
{
return tmd.Properties(props => props
.Completion(s => s
.Name(p => p.CompanyName.Suffix("completion"))
.IndexAnalyzer("standard")
.SearchAnalyzer("standard")
.MaxInputLength(20)
.Payloads()
.PreservePositionIncrements()
.PreserveSeparators()
)
);
}
That method returns the descriptor so you can further chain on it.
Then when you do a search for contacts:
var result = getElasticClientInstance("contacts").Search<Contact>(
body => descriptor
.Types(typeof(Person), typeof(Company))
);
That types hint will cause the search to looking /index/person and /index/company and will know how to give you back a covariant list of documents.
So you can do result.Documents.OfType<Person>() after the previous call.

LINQ What does 'select new' do?

I've built the following LINQ query
var activeMembers = from m in context.py3_membershipSet
join c in context.ContactSet on m.py3_Member.Id equals c.ContactId
where m.statuscode.Value == 1
orderby m.py3_name
select m;
But I've since seen an example formatted as such:
var contacts =
(
from c in xrm.ContactSet
join a in xrm.AccountSet on c.ParentCustomerId.Id equals a.Id
where a.Name == "Acme Pty Ltd"
select new
{
Name = c.FullName,
DOB = c.BirthDate,
Gender = (c.FormattedValues.Contains("gendercode") ? c.FormattedValues["gendercode"] : "Ambiguous")
}
);
(I realise this is a different set of data) What does the inclusion of the 'select new' actually do in this case?
What are the benefits of it over my example in the first code block?
I realise some might find this a tedious question, but I a want to learn LINQ and need to learn it fast. But I also don't want to run something -that I don't fully understand- on a clients live CRM
LINQ returns a collection of anonymous objects, either way. select new let's you define the layout of that object and which properties/property names are included in that anonymous object.
You can also use select new ClassName { } to return a list of instances of an entity class that you define.
As previous answers have noted both methods return an anonymous type. To fully answer your question though: "What are the benefits of the second statement over the first?"
The first statement returns all of the fields of m as-is. If you have 7 "columns" then activeMembers will contain all of them and whatever data they contain.
In the second statement you're projecting the results into a custom anonymous object that has only 3 fields. Not only that but you can perform logic on the "source fields" before storing them in the anonymous object. This gives you much of the flexibility of storing them in a container class without you actually having to define that class in code.
You could also do select new SomeClass { } which would project your results into a predefined class container.
The following pseudo-code may or may not be helpful in understanding the difference:
var myQuery = from p in someContext.someTable
group p by p.someField into g
select new MyContainer {
Field1 = g.Sum(a => a.field1)
Field2 = g.Max(a => a.field2)
};
myQuery in the above is now a collection of MyContainer. If I had omitted the class MyContainer then it would be a collection of an Anonymous Type containing the fields that I specified. Obviously, the difference here is that MyContainer has to be defined elsewhere in your code whereas the anonymous class is built/defined for you at compile time. In:
var myQuery = from p in someContext.someTable
select p;
myQuery is an anonymous class containing all of the fields and their values in someTable.
You are using the Queryable.Select<TSource, TResult>(IQueryable<TSource>, Expression<Func<TSource, TResult>>) method where ContactSet is the TSource and the of anonymous object return type is the TResult. Your code could also be written as
...Select(r => new {
Name = c.FullName,
DOB = c.BirthDate,
Gender = (c.FormattedValues.Contains("gendercode") ? c.FormattedValues["gendercode"] : "Ambiguous")
})
where the select method is returning a collection of anonymous types.
Also, there is a bit more going on under the hood since it looks like you're querying the database. Your implementation of IQueryable looks at the Select method that you've written, and translates the expression you've provided into valid SQL so that it can retrieve all the necessary information for the anonymous object that you're returning. Notice that I said your implemenation of IQuerable translates the provided expression (not function) into sql. The Select extension method only accepts expressions, not functions because it can't translate functions to sql.

Categories

Resources