Do you have an idea how to write the following code nicer/shorter :)
I need to check if the field form form is not empty and then add
criterion to the query,
thanks :)
AbstractCriterion restrictions = null;
if (model.DateFrom != null)
AddRestriction(ref restrictions,
Restrictions.Ge(Projections.Property<Invoice>(x => x.DateIn), model.DateForm));
if (model.DateTo != null)
AddRestriction(ref restrictions,
Restrictions.Le(Projections.Property<Invoice>(x => x.DateIn), model.DateTo));
if (!string.IsNullOrEmpty(model.Prop1))
AddRestriction(ref restrictions,
Restrictions.Eq(Projections.Property<Invoice>(x => x.Prop1), model.Prop1));
// ... many more conditions :)
return m_session.QueryOver<Invoice>().Where(restrictions).List();
You don't need the ref keyword for starters. I think this is nicer without sacrificing readability:
var query = session.QueryOver<Invoice>();
Action<object, ICriterion> addIfNonNull = (o, c) =>
{
if (o != null)
{
query.And(c);
}
};
addIfNonNull(model.Prop1, Restrictions.Eq(Projections.Property<Invoice>(x => x.Prop1), model.Prop1));
etc.
Related
I am new to elasticsearch so I will need some help. Unfortunately, I didnt found the answer in other topics here on SO.
I have some .net core application which I inherited and now there is a need to implement some changes.
I already have a method of getting data from elasticsearch, but after getting them, I am not sure how to change it and use it in application.
To be precise, I need to parse first and last name and to remove special characters, specific serbian latin letters like "šđžčć" etc... I already have a method for this parsing written but not sure how to call it...
So, my question is can I and how can I do this?
What I have now is the following:
var result = await _elasticClient.SearchAsync<CachedUserEntity>(
s =>
s.Index(_aliasName)
.Query(q => andQuery));
CachedUserEntity, among others, contains property about FirstName and LastName.
Inside results.Documents, I am getting the data about FirstName and LastName from elasticsearch, but I am not sure how to access it in order to update it via aformentioned NameParser ...
Sorry if the question is too easy, not to say stupid :)
I wont use updateByQuery here, for some reasons. I would scroll on documents (i use matchAll on my exemple, you obviously need to replace it with your query), or, if you dont know how to identify documents to update, only update usefull documents in UpdateManyWithIndex/UpdateManyPartial function.
For performance, we have to update severals documents at once, so we use bulk/updateMany function.
You can use both solution, the classic update, or the second (partial update) with an object containing the targeteds fields.
On server sides, both solutions will have the same cost / performance.
var searchResponse = Client.Search<CachedUserEntity>(s => s
.Query(q => q
MatchAll()
)
.Scroll("10s")
);
while (searchResponse.Documents.Any())
{
List<CachedUserEntity> NewSearchResponse = RemoveChar(searchResponse);
UpdateManyWithIndex<CachedUserEntity>(NewSearchResponse, _aliasName);
searchResponse = Client.Scroll<Project>("2h", searchResponse.ScrollId);
}
public void UpdateManyWithIndex<C>(List<C> obj, string index) where C : class {
var bulkResponse = Client.Bulk(b => b
.Index(index).Refresh(Elasticsearch.Net.Refresh.WaitFor) // explicitly provide index name
.UpdateMany<C>(obj, (bu, d) => bu.Doc(d)));
}
Or, using partial update object
Note: in this case Indix is already set on my client (add .index if needed)
var searchResponse = Client.Search<CachedUserEntity>(s => s
.Query(q => q
MatchAll()
)
.Scroll("2h")
);
while (searchResponse.Documents.Any())
{
List<object> listPocoPartialObj = GetPocoPartialObjList(searchResponse);
UpdateManyPartial(listPocoPartialObj);
searchResponse = Client.Scroll<Project>("2h", searchResponse.ScrollId);
}
private List<object> GetPocoPartialObjList(List<CachedUserEntity> cachedList) {
List<object> listPoco = new List<object>();
//note if you dont have cachedList.Id, take a look at result.source, comments if needed
foreach (var eltCached in cachedList) {
listPoco.Add( new object() { Id = cachedList.Id, FirstName = YOURFIELDWITHOUTSPECIALCHAR, LastName = YOURSECONDFIELDWITHOUTSPECIALCHAR});
}
return listPoco;
}
public bool UpdateManyPartial(List<object> partialObj)
{
var bulkResponse = Client.Bulk(b => b
.Refresh(Elasticsearch.Net.Refresh.WaitFor)
.UpdateMany(partialObj, (bu, d) => bu.Doc(d))
);
if (!bulkResponse.IsValid)
{
GetErrorMsgs(bulkResponse);
}
return (bulkResponse?.IsValid == true);
}
Using QueryOver I am creating a query like this
BulkActionItem bulkActionItemAlias1 = null;
BulkActionItem bulkActionItemAlias2 = null;
var query = GetSession().QueryOver<Student>(() => studentAlias)
.JoinAlias(() => studentAlias.BulkNotifications, () => bulkActionItemAlias1, NHibernate.SqlCommand.JoinType.LeftOuterJoin);
if (query.UnderlyingCriteria.GetCriteriaByAlias("bulkActionItemAlias2") == null
query = query.JoinAlias(() => studentAlias.BulkNotifications, () => bulkActionItemAlias2, NHibernate.SqlCommand.JoinType.LeftOuterJoin);
This will crash because I have the same join twice with different aliases. Is it possible to check if a join already exists on a query, even with a different alias?
I haven't found a built-in way to accomplish this. Typically I use an out parameter with extension methods to keep track of what tables are part of the query. For example:
bool joinedOnBulkNotifications;
BulkNotification notificationAlias = null;
Expression<Func<object>> aliasExpr = () => notificationAlias;
var query = GetSession().QueryOver<Student>(() => studentAlias)
.FilterByBulkNotificationStatus(
someCondition, aliasExpr, out joinedOnBulkNotifications);
public static class QueryExtensions
{
public static IQueryOver<Student, Student> FilterByBulkNotificationStatus(
this IQueryOver<Student, Student> query,
bool someCondition,
Expression<Func<object>> aliasExpr,
out bool joinedOnBulkNotifications)
{
joinedOnBulkNotifications = false;
if (someCondition)
{
joinedOnBulkNotifications = true;
query.JoinAlias(s => s.BulkNotifications, aliasExpr);
}
return query;
}
}
The issue is that you might need to reuse the alias you created later. You might be tempted to pass in a BulkNotification and use that, but this only works if the parameter name matches the name of the variable you pass to the extension method. NHibernate uses the name of the variable to create an alias name, so if these two do not match, you'll get an error. Because of this, you need to wrap the alias in an Expression and use that instead.
This isn't a very clean option, so I hope someone has a better solution.
I hit a situation today where a field in our legacy db that should never be empty... was empty.
I am using NHibernate 3.2 against this database and the queries that are affected are written in QueryOver.
My current query is this
return Session
.QueryOver<FacilityGroup>()
.Where(fg => fg.Owner.Id == Token.OwnerId &&
fg.UserName == Token.UserName)
.OrderBy(fg => fg.Code).Asc
.TransformUsing(Transformers.DistinctRootEntity);
I want it to be this:
return Session
.QueryOver<FacilityGroup>()
.Where(fg => fg.Owner.Id == Token.OwnerId &&
fg.UserName == Token.UserName &&
!string.IsNullOrEmpty(fg.Code))
.OrderBy(fg => fg.Code).Asc
.TransformUsing(Transformers.DistinctRootEntity);
When I try this I get an exception "Unrecognised method call: System.String:Boolean IsNullOrEmpty(System.String)"
So NHibernate can't translate string.IsNullOrEmpty. Fair enough. However when I try this
return Session
.QueryOver<FacilityGroup>()
.Where(fg => fg.Owner.Id == Token.OwnerId &&
fg.UserName == Token.UserName &&
!(fg.Code == null || fg.Code.Trim() == "" ))
.OrderBy(fg => fg.Code).Asc
.TransformUsing(Transformers.DistinctRootEntity);
I get an InvalidOperationException "variable 'fg' of type 'Domain.Entities.FacilityGroup' referenced from scope '', but it is not defined"
Any thoughts?
Ok... I guess I asked this question too soon. I figured out a way around this.
What I was able to do was invoke the 'trim' function from hql via a SQL Function Projection. I ended up writing it as IQueryOver Extention method to keep it flexible. I will post it here in case anyone needs it.
public static class QueriesExtentions
{
public static IQueryOver<E, F> WhereStringIsNotNullOrEmpty<E, F>(this IQueryOver<E, F> query, Expression<Func<E, object>> propExpression)
{
var prop = Projections.Property(propExpression);
var criteria = Restrictions.Or(Restrictions.IsNull(prop), Restrictions.Eq(Projections.SqlFunction("trim", NHibernateUtil.String, prop), ""));
return query.Where(Restrictions.Not(criteria));
}
}
and here it is in use
return Session
.QueryOver<FacilityGroup>()
.Where(fg => fg.Owner.Id == Token.OwnerId && fg.UserName == Token.UserName )
.WhereStringIsNotNullOrEmpty(fg => fg.Code)
.OrderBy(fg => fg.Code).Asc
.TransformUsing(Transformers.DistinctRootEntity);
We're using Raven to validate logins so people can get into our site.
What we've found is that if you do this:
// Context is an IDocumentSession
Context.Query<UserModels>()
.SingleOrDefault(u => u.Email.ToLower() == email.ToLower());
The query only filters on the first 128 docs of the documents in
Raven. There are several thousand in our database, so unless your
email happens to be in that first 128 returned, you're out of luck.
None of the Raven samples code or any
sample code I've come across on the net performs any looping using
Skip() and Take() to iterate through the set.
Is this the desired behavior of Raven?
Is it the same behavior even if you use an advanced Lucene Query? ie; Do advanced queries behave any differently?
Is the solution below appropriate? Looks a little ugly. :P
My solution is to loop through the set of all documents until I
encounter a non null result, then I break and return .
public T SingleWithIndex(string indexName, Func<T, bool> where)
{
var pageIndex = 1;
const int pageSize = 1024;
RavenQueryStatistics stats;
var queryResults = Context.Query<T>(indexName)
.Statistics(out stats)
.Customize(x => x.WaitForNonStaleResults())
.Take(pageSize)
.Where(where).SingleOrDefault();
if (queryResults == null && stats.TotalResults > pageSize)
{
for (var i = 0; i < (stats.TotalResults / (pageIndex * pageSize)); i++)
{
queryResults = Context.Query<T>(indexName)
.Statistics(out stats)
.Customize(x => x.WaitForNonStaleResults())
.Skip(pageIndex * pageSize)
.Take(pageSize)
.Where(where).SingleOrDefault();
if (queryResults != null) break;
pageIndex++;
}
}
return queryResults;
}
EDIT:
Using the fix below is not passing query params to my RavenDB instance. Not sure why yet.
Context.Query<UserModels>()
.Where(u => u.Email == email)
.SingleOrDefault();
In the end I am using Advanced Lucene Syntax instead of linq queries and things are working as expected.
RavenDB does not understand SingleOrDefault, so it performs a query without the filter. Your condition is then executed on the result set, but per default Raven only returns the first 128 documents.
Instead, you have to call
Context.Query<UserModels>()
.Where(u => u.Email == email)
.SingleOrDefault();
so the filtering is done by RavenDB/Lucene.
Is there a lambda equivalent of IN? I will like to select all the funds with ids either 4, 5 or 6. One way of writing it is:
List fundHistoricalPrices = lionContext.FundHistoricalPrices.Where(fhp => fhp.Fund.FundId == 5 || fhp.Fund.FundId == 6 || fhp.Fund.FundId == 7).ToList();
However, that quickly becomes unmanageable if I need it to match say 100 different fundIds. Can I do something like:
List
fundHistoricalPrices =
lionContext.FundHistoricalPrices.Where(fhp
=> fhp.Fund.FundId in(5,6,7)).ToList();
It's somewhere along these lines, but I can't quite agree with the approach you have taken. But this will do if you really want to do this:
.Where(fhp => new List<int>{5,6,7}.Contains( fhp.Fund.FundId )).ToList();
You may want to construct the List of ids before your LINQ query...
You can use the Contains() method on a collection to get the equivalent to in.
var fundIds = new [] { 5, 6, 7 };
var fundHistoricalPrices = lionContext.FundHistoricalPrices.Where(fhp => fundIds.Contains(fhp.Fund.FundId)).ToList();
You could write an extension method like this :
public static bool In<T>(this T source, params T[] list)
{
if(null==source) throw new ArgumentNullException("source");
return list.Contains(source);
}
Then :
List fundHistoricalPrices = lionContext.FundHistoricalPrices.Where(fhp => fhp.Fund.FundId.In(5,6,7)).ToList();
No, the only similar operator i'm aware of is the Contains() function.
ANother was is to construct your query dynamically by using the predicate builder out of the LINQkit: http://www.albahari.com/nutshell/predicatebuilder.aspx
Example
int[] fundIds = new int[] { 5,6,7};
var predicate = PredicateBuilder.False<FundHistoricalPrice>();
foreach (int id in fundIds)
{
int tmp = id;
predicate = predicate.Or (fhp => fhp.Fund.FundId == tmp);
}
var query = lionContext.FundHistoricalPrices.Where (predicate);