Let´s say I have
class Product
{
string name;
List<Order> orders;
}
class Order
{
string name;
}
If I try to map analyzers to Product name it work, but not for Order.name
//This work and adds the analyzer on the mapping list.
var resp = client.Map<Product>(map => map
.Properties(props => props
.String(s =>s
.Name(p => p.Name)
.IndexAnalyzer("normalize")
)
));
//This does not.
var resp = client.Map<Product>(map => map
.Properties(props => props
.String(s =>s
.Name(p => p.orders.First().Name)
.IndexAnalyzer("normalize")
)
));
Am I doing something wrong, or is this a bug?
Some more info:
Those classes are just an example to show the problem.
If I add [ElasticProperty(Analyzer = "normalize")] on the variable it works.
Actually the setup look something like Product Inherists from BaseProdcuts and BaseProductis is the one who has the List
As answered here, .Name(p => p.Orders.First().Name) is telling ES to map the field 'Name' on the Product document. Instead, you want to map to the 'Name' field on Orders, which is an array in your Product document.
Try this instead:
client.Map<Product>(m => m
.Properties(pp => pp
// Map Product.Name
.String(s => s
.Name(p => p.Name)
.IndexAnalyzer("normalize")
)
// Map Product.Orders.Name
.Object<List<Order>>(o => o
.Name(p => p.Orders)
.Properties(op => op
.String(s => s
.Name(os => os.First().Name)
.IndexAnalyzer("normalize"))))
));
Related
I'm trying to get Ingredients through junction table for my Recipes.
_context.RecipeIngredients
.Include(rI => rI.Recipe)
.ThenInclude(r => r.RecipeIngredients)
.Where(rI => ingredients.Contains(rI.IngredientId))
.GroupBy(rI => rI.Recipe)
.Select(g => new
{
Recipe = g.Key,
MatchingIngredients = (double)g.Count() / (double)g.Key.RecipeIngredients.Count(),
g.Key.ComplexityTag,
g.Key.TypeTag,
g.Key.RecipeIngredients,
})
.OrderByDescending(r => r.MatchingIngredients)
.Take(MaxAmountBestRecipes)
.AsEnumerable()
.Select(a => new RecipeDTO()
{
Title = a.Recipe.Title,
Steps = a.Recipe.Steps,
ComplexityTag = a.ComplexityTag?.Complexity,
TypeTag = a.TypeTag?.Type,
IngredientAmount = a.RecipeIngredients?.ToDictionary(rI => rI.Ingredient.Name, rI => rI.Quantity),
})
.ToList();
I discovered it is caused by g.Key.RecipeIngredients, but I can't find any workaround, solution for this problem. I tried eagar loading(as you can see), and lazy loading, both didn't work. I hope there is solution in one query to db in linq. Moreover, will it work like in the above line, after update.:
IngredientAmount = a.RecipeIngredients?.ToDictionary(rI => rI.Ingredient.Name, rI => rI.Quantity)
EDIT
I have divied linq query and here you have after which statement ArgumentNullException is thrown:
var tmp = _context.RecipeIngredients
.Where(rI => ingredients.Contains(rI.IngredientId))
.GroupBy(rI => rI.Recipe)
.Select(g => new
{
Recipe = g.Key,
MatchingIngredients = (double)g.Count() / (double)g.Key.RecipeIngredients.Count(),
g.Key.ComplexityTag,
g.Key.TypeTag,
g.Key.RecipeIngredients
})
.ToList();
Include doesn't work when you change the shape of the query (e.g. when you're querying for RecipeIngredients but are projecting to another type).
The only place I'm thinking a NULL is a problem is the key selector when creating the dictionary. Since Include won't do anything for you, ri.Ingredient will always be NULL. Include the Ingredients in the original projection (and remove the Include since it's useless):
_context.RecipeIngredients
.Where( rI => ingredients.Contains( rI.IngredientId ) )
.GroupBy( rI => rI.Recipe )
.Select( g => new
{
Recipe = g.Key,
MatchingIngredientCount = (double)g.Count() / (double)g.Key.RecipeIngredients.Count(),
g.Key.ComplexityTag,
g.Key.TypeTag,
g.Key.RecipeIngredients,
// this eager-loads the `Ingredient` entities
// EF will automatically wire them up to the `RecipeIngredient` entities
// if tracking is enabled
Ingredients = g.Key.RecipeIngredients.Select( ri => ri.Ingredient ),
} )
.OrderByDescending(r => r.MatchingIngredients)
.Take(MaxAmountBestRecipes)
.ToArray()
.Select( a = new ...
{
...
IngredientAmount = a.RecipeIngredients.ToDictionary(
ri => ri.Ingredient.Name, // ri.Ingredient should now not be NULL
ri => ri.Quantity )
} );
Edit: if you don't need the entire RecipeIngredient or Recipe entities in your results, just project what you need in the orig name with the RecipeIngredients in the first projection:
IngredientNamesAndQuantity = g.Key.RecipeIngredients.Select( ri => new
{
ri.Quantity,
ri.Ingredient.Name,
}
Then use that projection to build your dictionary:
IngredientAmount = a.IngredientNamesAndQuantity.ToDictionary(
at => at.Name,
at => at.Quantity )
I want to simplify a linq Query that contains multiple includes.
My model is simple: a site is linked to one contract, that is linked to one client. On that client I need to get with a single request the telephones, mails and honorifics (appelRef).
I want a single request because behind the request is translated by entity framework into a SQL Server request.
Here is the linq request:
var search =
from IMT.Site s in imtContext.IMTObjects.OfType<IMT.Site>()
.Include(
s => s.LienContratSiteRef
.Select(l => l.Contrat)
.Select(c => c.LienContratClientRef
.Select(l => l.Client)
.Select(cl => cl.Telephones ) ) )
.Include(s => s.LienContratSiteRef
.Select(l => l.Contrat)
.Select(c => c.LienContratClientRef
.Select(l => l.Client)
.Select(cl => cl.Mails ) ) )
.Include(s => s.LienContratSiteRef
.Select(l => l.Contrat)
.Select(c => c.LienContratClientRef
.Select(l => l.Client)
.Select(cl => cl.AppelRef ) ) )
where s.Reference.ToString() == siteId
select s;
Yor can notice the block
.Include(
s => s.LienContratSiteRef
.Select(l => l.Contrat)
.Select(c => c.LienContratClientRef
.Select(l => l.Client)
..is repeated three time. Is ther a way to factorize that code block ?
Update: there are intermedray objects LienContratSiteRef and LienContratClientRef and relationships are 0 - *, so that LienContratSiteRef.Contrat and LienContratClientRef.Client are collections.
I also tried:
.Include(
s => s.LienContratSiteRef
.Select(l => l.Contrat)
.Select(c => c.LienContratClientRef
.Select(l => l.Client)
.Select(cl => new { Tels = cl.Telephones, Mail = cl.Mails, Appel = cl.AppelRef} ) ) )
but It results with a runtime error:
The Include path expression must refer to a navigation property
defined on the type.
The s => ... inside the include could be refactored into a delegate, then just .Include that delegate multiple times.
It looks like the signature would be Func<Site, IEnumerable<Client>>?
E.g.
static IEnumerable<Client> Foo(Site site) => site.LienContratSiteRef
.Select(l => l.Contrat)
.Select(c => c.LienContratClientRef
.Select(l => l.Client)
String-based Chaining
The Include() method supports a dot-delimited string parameter which can be used to pull an complete graph of objects down as opposed to making multiple chained select calls:
.Include("LienContratSiteRif.Contrat.LienContratClientRef.Client")
From there, if you wanted to include multiple additional properties, I believe that you could have another include for those sub properties:
.Include("LienContratSiteRif.Contrat.LienContratClientRef.Client, Client.Telephones, ...")
Lambda-based Chaining
You should be able to accomplish something similar by chaining your lambda-based includes into a single Include() call as well using:
.Include(c => LienContratSiteRif.Contrat.LienContratClientRef.Client)
It looks like you're trying to make an entity framework projection!
A project allows you to select only the properties that you want to return. (just like a SQL Select)
To use a projection, your code should roughly look like this:
var search = imtContext.IMTObjects.OfType<IMT.Site>()
.Where(s => s.Reference.ToString() == siteId)
.Select(s => new {
Telephones = s.LienContratSiteRef.Contrat.Select(c => c.LienContratClientRef.Client.Select(cli => cli.Telephones),
Mails = s.LienContratSiteRef.Contrat.Select(c => c.LienContratClientRef.Client.Select(cli => cli.Mails),
AppelRef = s.LienContratSiteRef.Contrat.Select(c => c.LienContratClientRef.Client.Select(cli => cli.AppelRef)
}).ToList();
If Telephones, Mails, AppelRefare also collections, then you can do aggregate the collections together like this in memory after the query has been run:
var telephones = search.SelectMany(x => x.Telephones).ToList();
var mails = search.SelectMany(x => x.Mails).ToList();
var appelRefs = search.SelectMany(x => x.AppelRef).ToList();
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))
)
)
)
);
I need to delete items in a collection managed by Entity 6. The items that have to be deleted are in an other list
Currently I do this :
idToDelete = model.Courses.Where(x => x.Deleted).Select(x => x.Id);
entity.Courses
.Where(ent => idToDelete.Contains(ent.Id))
.ToList()
.ForEach(ent =>
_contexte.Entry(ent).State = EntityState.Deleted);
This code works fine.
How to perform the same in only one linq instruction?
entity.Courses.RemoveRange(entity.Courses.Where(e => e.Deleted));
Actually, reading your question again, it looks like the list of IDs is in a view model, and possibly your entity model doesn't have the Deleted property. So presumably you need something rather more like what you had:
_contexte.Courses.RemoveRange(
_context.Courses.Where(c => model.Courses.Where(x => x.Deleted).Select(y => y.Id).Contains(c.Id));
...or more readably:
var idsToDelete = model.Courses.Where(c => c.Deleted).Select(e => e.Id);
var entitiesToDelete = _contexte.Courses.Where(c => idsToDelete.Contains(c.Id));
_contexte.Courses.RemoveRange(entitiesToDelete);
You can use let to declare your idToDelete within the query:
(from ent in in entity.Courses
let idsToDelete = model.Courses.Where(x => x.Deleted).Select(x => x.Id)
where idsToDelete.Contains(ent.Id)
select ent).ToList()
.ForEach(ent => _contexte.Entry(ent).State = EntityState.Deleted);
I am trying to do a query like:
var a = session.QueryOver<Site>()
.SelectList(
x => x.Select(p => p.SiteName)
.Select(p => p.SiteId).Select(p => p.RegionLocation.City))
.List<object[]>();
but I get the error
could not resolve property: RegionLocation.City of: Entities.Site
The property exists and I can retrieve it using LINQ but QueryOver does not work. What am I doing wrong ?
As far as i remember, with QueryOver, you have to join all entities in the association in order to be able to access it's properties.
This means you ought to do something like:
(notice the .JoinQueryOver)
var a = session.QueryOver<Site>()
.JoinQueryOver(s => s.RegionLocation)
.SelectList(
x => x.Select(p => p.SiteName)
.Select(p => p.SiteId)
.Select(p => p.RegionLocation.City))
.List<object[]>();
Or maybe this will work:
RegionLocation regionLocationAlias = null;
var a = session.QueryOver<Site>()
.JoinAlias(s => s.RegionLocation, () => regionLocationAlias)
.SelectList(
x => x.Select(p => p.SiteName)
.Select(p => p.SiteId)
.Select(() => regionLocationAlias.City))
.List<object[]>();
Also you might want to have a look at https://github.com/davybrion/NHibernateWorkshop/tree/master/NHibernateWorkshop
There's lots of great examples!
Specifically for your problem, have a look at: https://github.com/davybrion/NHibernateWorkshop/blob/master/NHibernateWorkshop/Querying/QueryOver/Projecting.cs