Exclude nulls from ThenInclude - c#

I have EF context as below
_context.Offices
.Where( x => x.ID == id )
.Include( x => x.County)
.ThenInclude( x => x.Employees)
.ThenInclude( s => s.Department)
.ThenInclude( m => m.DeptUser )
.Include( x => x.XYZ);
My problem is that DeptUser can be null. How do I exclude null items?

Related

Linq includes in nested group by query

I have a relatively complex query below with a few nested group by queries. The problem is that I don't know how I can add includes to any of the group by queries. Is there a way to include subproperties in the sub group queries in EF6?
return db.PatientOrders
.Include(x => x.Patient) // this has no effect
.Where(x => !x.ProcessedOn.HasValue && x.Patient.Home.PharmacyId == pharmacyID)
.GroupBy(x => x.Patient.Home)
.ToDictionary(x => x.Key, x => x
.ToList()
.GroupBy(y => y.Patient.Department)
.ToDictionary(y => y.Key, y => y
.Include(x => x.OrderLines) // this does not compile
.ToList()
.GroupBy(z => z.Patient)
.ToDictionary(z => z.Key, z => z.ToList(), new PatientEqualityComparer()), new HomeDepartmentEqualityComparer()), new HomeEqualityComparer());
I figured out a way to do it but I'm not sure if the solution is any good performance-wise.
// group by reshapes query so previous includes are lost
// solution: flatten after group by then do includes then group by again
return db.PatientOrders
.GroupBy(x => x.Patient.Home) // Group
.SelectMany(g => g.AsEnumerable()) // Ungroup
.Include(x => x.Patient)
.Include(x => x.Patient.Home)
.Include(x => x.Patient.Doctor)
.Include(x => x.Patient.Department)
.Include(x => x.OrderLines)
.Include(x => x.OrderLines.Select(y => y.Product))
.Where(x => !x.ProcessedOn.HasValue && x.Patient.Home.PharmacyId == pharmacyID)
.AsEnumerable() // Switch to LINQ to Objects
.GroupBy(x => x.Patient.Home) // Group again
.ToDictionary(x => x.Key, x => x
.ToList()
.GroupBy(y => y.Patient.Department)
.ToDictionary(y => y.Key, y => y
.ToList()
.GroupBy(z => z.Patient)
.ToDictionary(z => z.Key, z => z.ToList(), new PatientEqualityComparer()), new HomeDepartmentEqualityComparer()), new HomeEqualityComparer());

How to get synonyms working in elastic search through C#?

For the last few hours I've been trying to get the synonyms working in Elastic Search but so far failed.
When I do the search it's like the synonyms never get indexed.
Below is the part of code where I think I may be making a mistake.
Please have a look and any help will be much appreciated.
var createIndexTask = Client.CreateIndexAsync(synonymsindexname, i => i
.Settings(s => s
.Analysis(a => a
.TokenFilters(tf => tf
.Synonym("my_synonym", sf => sf
.Format(SynonymFormat.Solr)
.SynonymsPath("synonyms/solrsynonyms.txt")
)
)
.Analyzers(an => an
.Custom("my_analyzer", ca => ca
.Tokenizer("standard")
.Filters("lowercase", "stop", "my_synonym")
)
)
)
)
.Mappings(m => m
.Map<ClinicalCode>(c => c
.AutoMap()
.Properties(p => p
.Completion(comp => comp
.Contexts(ctx => ctx
.Category(csug => csug
.Name("ShortDescription")
.Path(x => x.ShortDescription)
)
)
.Name(n => n.Suggests)
.Analyzer("simple")
)
.Text(t => t
.Name(n => n.LongDescription)
.Analyzer("my_analyzer")
.CopyTo(ct => ct
.Field("allFields")
)
)
.Text(t => t
.Name(n => n.ShortDescription)
.Analyzer("my_analyzer")
.CopyTo(ct => ct
.Field("allFields")
)
)
.Text(t => t
.Name(n => n.ICDCode)
.Analyzer("my_analyzer")
.CopyTo(ct => ct
.Field("allFields")
)
)
.Keyword(k => k
.Name(n => n.ICDCode)
)
)
)
)
);

Elastic NEST Filter on Aggregations

Ive now successfully got my c# Test win forms app working with aggregations and now I want to be able to filter on said aggregations if someone selects one (or more) of them.
Here is my query that is working exactly as I want and I am getting both price range buckets and buckets for each of the specification terms that it can find in the index.
.Aggregations(a => a
.Nested("specifications", n => n
.Path(p => p.ProductSpecification)
.Aggregations(aa => aa.Terms("groups", sp => sp.Field(p => p.ProductSpecification.Suffix("name"))
.Aggregations(aaa => aaa
.Terms("attribute", tt => tt.Field(ff => ff.ProductSpecification.Suffix("value"))))
)
)
)
.Range("price_range", ra => ra
.Field(p => p.Price)
.Ranges(
r => r.To(50),
r => r.From(50).To(100),
r => r.From(100).To(150),
r => r.From(150).To(200),
r => r.From(200).To(250),
r => r.From(250)
)
))
.Index("myindex")
.Type("product")
.Query(q => q
.MultiMatch(m => m
.Query(searchBox.Text + "*")
.Fields(ff => ff
.Field(f => f.Name, boost: nameBoost)
.Field(f => f.Description, boost: descriptionBoost)
.Field(f => f.ProductCode)))));
Can someone point me in the direction of how I can go about filtering down results based on selecting any of these buckets.
UPDATE (30/01/18) I have now added this to my query
&& q.Nested(n => n
.Path(p => p.ProductSpecification)
.Query(q2 => q2
.Terms(t => t
.Field(f => f.ProductSpecification.Suffix("name"))
.Terms("Guarantee",)
)).Query(q3 => q3
.Terms(t2 => t2
.Field(f2 => f2.ProductSpecification.Suffix("value"))
.Terms("3 years","10 years")
)))
));
This is enabling me to pass multiple values into one Spec filter, but what im not sure on how to achieve is how to filter on multiple specs so the above filters on Guarantee with values of either 3 or 10 years
but if I also wanted to pass values of "Grey" and "Copper" to the spec of "Colour"
Adding "Colour" into My first set of terms and "Grey" and "Copper" into my second terms list breaks all filtering.
I think im close here just need a little direction
If I understand your question correctly, when the user selects a price range from the price range facets that are applicable to the query that is being executed, you'd like to apply this price range to the query?
The Post Filter serves this purpose, by applying a filter to the search results after aggregations have been calculated.
Assuming a user has selected the 50-100 price range, your query would like something like
var response = client.Search<Product>(s => s
.Aggregations(a => a
.Nested("specifications", n => n
.Path(p => p.ProductSpecification)
.Aggregations(aa => aa
.Terms("groups", sp => sp
.Field(p => p.ProductSpecification.Suffix("name"))
.Aggregations(aaa => aaa
.Terms("attribute", tt => tt
.Field(ff => ff.ProductSpecification.Suffix("value"))
)
)
)
)
)
.Range("price_range", ra => ra
.Field(p => p.Price)
.Ranges(
r => r.To(50),
r => r.From(50).To(100),
r => r.From(100).To(150),
r => r.From(150).To(200),
r => r.From(200).To(250),
r => r.From(250)
)
)
)
.Index("myindex")
.Type("product")
.Query(q => q
.MultiMatch(m => m
.Query(searchBox.Text + "*")
.Fields(ff => ff
.Field(f => f.Name, boost: nameBoost)
.Field(f => f.Description, boost: descriptionBoost)
.Field(f => f.ProductCode)
)
)
)
.PostFilter(pf => pf
.Range(r => r
.Field(f => f.Price)
.GreaterThanOrEquals(50)
.LessThan(100)
)
)
);

How do I specify an index for Elasticsearch using NEST?

If I run the code below it will create a mapping on ALL indices, which I don't want. I am unable to find the documentation for specifying just the index I want.
How do I specify which index to apply this mapping to?
var client = new ElasticClient();
var response = client.Map<Company>(m => m
.Properties(props => props
.Number(n => n
.Name(p => p.ID)
.Type(NumberType.Integer)
)
)
);
Add .Index() to the put mapping descriptor
var response = client.Map<Company>(m => m
.Index("index-name")
.Properties(props => props
.Number(n => n
.Name(p => p.ID)
.Type(NumberType.Integer)
)
)
);
This puts a mapping into an existing index. If an index doesn't yet exist, you can create it and define a mapping for it in one request. For example
var createIndexResponse = client.CreateIndex("index-name", c => c
// settings for the index
.Settings(s => s
.NumberOfShards(3)
.NumberOfReplicas(1)
.RefreshInterval("5s")
)
// mappings for the index
.Mappings(m => m
.Map<Company>(mc => mc
.Properties(props => props
.Number(n => n
.Name(p => p.ID)
.Type(NumberType.Integer)
)
)
)
)
);
Something like this also will do.
string IndexName = "my_index";
this.client.CreateIndex(IndexName, c =>
c.AddMapping<CForm>
(m => m.Properties(ps => ps.Attachment
(a => a.Name(o => o.Document)
.TitleField(t => t.Name(x => x.Name)
.TermVector(TermVectorOption.WithPositionsOffsets))))));
// Create Mappings for the fields with specific properties.
// You can also make field1 a multi-field and make it both analyzed and not_analyzed
// to get the best of both worlds (i.e. text matching on the analyzed field + aggregation on the exact value
// of the not_analyzed raw sub-field).
// Field: Plan
var result = this.client.Map<CForm>(m => m
.Properties(props => props
.MultiField(s => s
.Name(p => p.Plan)
.Fields(pprops => pprops
.String(ps => ps.Name(p => p.Plan).Index(FieldIndexOption.NotAnalyzed))
.String(ps => ps.Name("original").Index(FieldIndexOption.Analyzed))
)
)
)
);

NHibernate QueryOver and string.format

I am working with QueryOver in NHibernate and I want to customize one property of my projected DTO using the following syntax:
IEnumerable<PersonResponseMessage> persons =
session.QueryOver<PersonEntity>()
.SelectList(list => list
.Select(p => p.Active).WithAlias(() => dto.Active)
.Select(p => p.Alert).WithAlias(() => dto.Alert)
.Select(p => p.Comments).WithAlias(() => dto.Comments)
.Select(p => string.Format("{0}api/Person/{1}", uriHelper.Root, p.Id)).WithAlias(() => dto.DetailsUrl)
)
.TransformUsing(Transformers.AliasToBean<PersonResponseMessage>())
.List<PersonResponseMessage>();
Unfortunately NHibernate cannot do this and throws an exception saying that:
Variable P referenced from scope "" is not defined
There are in common two ways. Partially we can move that concat operation on the DB side, as documented here:
16.7. Projection Functions
In this case, we'll use the Projections.Concat:
.SelectList(list => list
.Select(p => p.Active).WithAlias(() => dto.Active)
.Select(p => p.Alert).WithAlias(() => dto.Alert)
.Select(p => p.Comments).WithAlias(() => dto.Comments)
// instead of this
//.Select(p => string.Format("{0}api/Person/{1}", uriHelper.Root, p.Id))
// .WithAlias(() => dto.DetailsUrl)
// use this
.Select(p => Projections.Concat(uriHelper.Root, Projections.Concat, p.Id))
.WithAlias(() => dto.DetailsUrl)
)
.TransformUsing(Transformers.AliasToBean<PersonResponseMessage>())
.List<PersonResponseMessage>();
But I would vote for ex-post processing on the Application tier, in C#:
.SelectList(list => list
.Select(p => p.Active).WithAlias(() => dto.Active)
.Select(p => p.Alert).WithAlias(() => dto.Alert)
.Select(p => p.Comments).WithAlias(() => dto.Comments)
// just the ID
.Select(p => p.Id).WithAlias(() => dto.Id)
)
.TransformUsing(Transformers.AliasToBean<PersonResponseMessage>())
.List<PersonResponseMessage>()
// do the concat here, once the data are transformed and in memory
.Select(result =>
{
result.DetailsUrl = string.Format("{0}api/Person/{1}", uriHelper.Root, p.Id)
return result;
});

Categories