I am creating a query with Criteria like this:
DetachedCriteria auftragCriteria = DetachedCriteria.For<Auftrag>("a");
I join multiple tables with:
DetachedCriteria positionJoin = auftragCriteria.CreateCriteria("a.Positionen", "p", JoinType.LeftOuterJoin);
And I use a projection to fill in my object SubTypeAuftrag
ProjectionList projectionListSubTypeAuftrag = Projections.ProjectionList();
Now I need to recreate the following sql code:
cast(sum(p.length * p.width / 1000) as decimal)
I tried the following:
projectionListSubTypeAuftrag.Add(Projections.Sum<Position>(p => p.length * p.width / 1000), "M1");
This leads to an error:
System.InvalidOperationException: 'variable 'p' of type 'xxx.Base.Position' referenced from scope '', but it is not defined'
I also tried:
projectionListSubTypeAuftrag.Add(
Projections.Cast(
NHibernateUtil.Decimal,
Projections.SqlProjection("p.length * p.width/ 1000 AS result", new[] { "result" }, new IType[] { NHibernateUtil.Double })
),
"M1"
);
How can I tell nHibernate where to find the length/width column?
Maybee this will point you in the right direction.
var sqlMultiply = new VarArgsSQLFunction("(", "*", ")");
var sqlDivide = new VarArgsSQLFunction("(", "/", ")");
var multiplyLengthWidthProj = Projections.SqlFunction(sqlMultiply, NHibernateUtil.Decimal, Projections.Property(() => alias.Length), Projections.Property(() => alias.Width));
var sumProjection = Projections.ProjectionList().Add(Projections.Sum(Projections.SqlFunction(sqlDivide, NHibernateUtil.Decimal, multiplyLengthWidthProj, Projections.Constant(1000))));
How Can use a native sqlquery (session.CreateSqlQuery) as filtering subquery in another QueryOver:
// Get ids
var idsq = _session.CreateSQLQuery(
"select Id from [dbo].[SomeFunction](:parameter)")
.AddEntity(typeof(long)).
SetParameter("parameter", folderId);
// get entities by filtering where in (subquery)
MyEntity entityAlias = null;
var listOfEntities = await _session.QueryOver(() => entityAlias).
Where(x=>x.Id).IsIn(idsq).
OrderBy(x => x.Name).Asc.
ListAsync(cancelToken).ConfigureAwait(false);
You can't easily mix various styles of NHibernate... What you can do:
var crit = new SQLCriterion(SqlString.Parse("{alias}.Id IN (select Id from [dbo].[SomeFunction](?))"),
new object[] { folderId },
new IType[] { NHibernateUtil.Int64 });
and then:
var listOfEntities = await _session.QueryOver(() => entityAlias)
.Where(crit)
.OrderBy(x => x.Name).Asc
Note how I changed the text query adding {alias}.Id IN (...)
NEST doesn't appear to support the pattern replace char filter described here:
http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/analysis-pattern-replace-charfilter.html
I've created an issue at https://github.com/elasticsearch/elasticsearch-net/issues/543.
Most of my indexing is working so I would like to continue to use NEST. Is there a way I can work around this using some manual json injection at some point during the index configuration? I'm new to NEST so not sure if this is doable.
Specifically I'm hoping to use the pattern replace char filter to remove unit numbers from a street address before they are run through a custom analyzer (i.e. #205 - 1260 Broadway becomes 1260 Broadway). Because of the custom analyzer, I believe I need to use this char filter to accomplish this.
My current configuration looks like this:
elasticClient.CreateIndex("geocoding", c => c
.Analysis(ad => ad
.Analyzers(ab => ab
.Add("address-index", new CustomAnalyzer()
{
Tokenizer = "whitespace",
Filter = new List<string>() { "lowercase", "synonym" }
})
.Add("address-search", new CustomAnalyzer()
{
Tokenizer = "whitespace",
Filter = new List<string>() { "lowercase" },
CharFilter = new List<string>() { "drop-unit" }
})
)
.CharFilters(cfb => cfb
.Add("drop-unit", new CharFilter()) //missing char filter here
)
.TokenFilters(tfb => tfb
.Add("synonym", new SynonymTokenFilter()
{
Expand = true,
SynonymsPath = "analysis/synonym.txt"
})
)
)
UPDATE:
As of May 2014, NEST now supports the pattern replace char filter: https://github.com/elasticsearch/elasticsearch-net/pull/637
Instead of using the fluent settings during your index creation you can use the Settings.Add approach to add to the FluentDictionary in a more manual way, but with complete control over what settings are passed in. An example of this is shown in Create Index of the NEST Documentation. I am using this approach for a very similar reason.
You configuration would look something similar to the following:
elasticClient.CreateIndex("geocoding", c => c.
.Settings(s => s.
.Add("analysis.analyzer.address-index.type", "custom")
.Add("analysis.analyzer.address-index.tokenizer", "whitespace")
.Add("analysis.analyzer.address-index.filter.0", "lowercase")
.Add("analysis.analyzer.address-index.filter.1", "synonym")
.Add("anaylsis.analyzer.address-search.type", "custom")
.Add("analysis.analyzer.address-search.tokenizer", "whitespace")
.Add("analysis.analyzer.address-search.filter.0", "lowercase")
.Add("analysis.analyzer.address-search.char_filter.0", "drop-unit")
.Add("analysis.char_filter.drop-unit.type", "mapping")
.Add("analysis.char_filter.drop-unit.mappings.0", "<mapping1>")
.Add("analysis.char_filter.drop-unit.mappings.1", "<mapping2>")
...
)
);
You will need to replace <mapping1> and <mapping2> above with your actual char_filter mappings that you want to use. Please note that I have not used a char_filter before, so the settings values may be a little off, but should get you going in the right direction.
Just to provide a follow-up to Paige's very helpful answer, it looks like you can combine the fluent and manual Settings.Add approaches. The following worked for me:
elasticClient.CreateIndex("geocoding", c => c
.Settings(s => s
.Add("analysis.char_filter.drop_unit.type", "pattern_replace")
.Add("analysis.char_filter.drop_unit.pattern", #"#\d+\s-\s")
.Add("analysis.char_filter.drop_unit.replacement", "")
)
.Analysis(ad => ad
.Analyzers(ab => ab
.Add("address_index", new CustomAnalyzer()
{
Tokenizer = "whitespace",
Filter = new List<string>() { "lowercase", "synonym" }
})
.Add("address_search", new CustomAnalyzer()
{
CharFilter = new List<string> { "drop_unit" },
Tokenizer = "whitespace",
Filter = new List<string>() { "lowercase" }
})
)
.TokenFilters(tfb => tfb
.Add("synonym", new SynonymTokenFilter()
{
Expand = true,
SynonymsPath = "analysis/synonym.txt"
})
)
)
EsClient.CreateIndex("universal_de", c => c
.NumberOfReplicas(1)
.NumberOfShards(5)
.Settings(s => s //just as an example
.Add("merge.policy.merge_factor", "10")
.Add("search.slowlog.threshold.fetch.warn", "1s")
.Add("analysis.char_filter.drop_chars.type", "pattern_replace")
.Add("analysis.char_filter.drop_chars.pattern", #"[^0-9]")
.Add("analysis.char_filter.drop_chars.replacement", "")
.Add("analysis.char_filter.drop_specChars.type", "pattern_replace")
.Add("analysis.char_filter.drop_specChars.pattern", #"[^0-9a-zA-Z]")
.Add("analysis.char_filter.drop_specChars.replacement", "")
)
.Analysis(descriptor => descriptor
.Analyzers(bases => bases
.Add("folded_word", new CustomAnalyzer()
{
Filter = new List<string> { "lowercase", "asciifolding", "trim" },
Tokenizer = "standard"
}
)
.Add("trimmed_number", new CustomAnalyzer()
{
CharFilter = new List<string> { "drop_chars" },
Tokenizer = "standard",
Filter = new List<string>() { "lowercase" }
})
.Add("trimmed_specChars", new CustomAnalyzer()
{
CharFilter = new List<string> { "drop_specChars" },
Tokenizer = "standard",
Filter = new List<string>() { "lowercase" }
})
)
)
.AddMapping<Business>(m => m
//.MapFromAttributes()
.Properties(props => props
.MultiField(mf => mf
.Name(t => t.DirectoryName)
.Fields(fs => fs
.String(s => s.Name(t => t.DirectoryName).Analyzer("standard"))
.String(s => s.Name(t => t.DirectoryName.Suffix("folded")).Analyzer("folded_word"))
)
)
.MultiField(mf => mf
.Name(t => t.Phone)
.Fields(fs => fs
.String(s => s.Name(t => t.Phone).Analyzer("trimmed_number"))
)
)
This is how you create the index and add the mapping.
Now to search i have something like this :
var result = _Instance.Search<Business>(q => q
.TrackScores(true)
.Query(qq =>
{
QueryContainer termQuery = null;
if (!string.IsNullOrWhiteSpace(input.searchTerm))
{
var toLowSearchTerm = input.searchTerm.ToLower();
termQuery |= qq.QueryString(qs => qs
.OnFieldsWithBoost(f => f
.Add("directoryName.folded", 5.0)
)
.Query(toLowSearchTerm));
termQuery |= qq.Fuzzy(fz => fz.OnField("directoryName.folded").Value(toLowSearchTerm).MaxExpansions(2));
termQuery |= qq.Term("phone", Regex.Replace(toLowSearchTerm, #"[^0-9]", ""));
}
return termQuery;
})
.Skip(input.skip)
.Take(input.take)
);
NEW: I managed to use the pattern replace in a better way like this :
.Analysis(descriptor => descriptor
.Analyzers(bases => bases
.Add("folded_word", new CustomAnalyzer()
{
Filter = new List<string> { "lowercase", "asciifolding", "trim" },
Tokenizer = "standard"
}
)
.Add("trimmed_number", new CustomAnalyzer()
{
CharFilter = new List<string> { "drop_chars" },
Tokenizer = "standard",
Filter = new List<string>() { "lowercase" }
})
.Add("trimmed_specChars", new CustomAnalyzer()
{
CharFilter = new List<string> { "drop_specChars" },
Tokenizer = "standard",
Filter = new List<string>() { "lowercase" }
})
.Add("autocomplete", new CustomAnalyzer()
{
Tokenizer = new WhitespaceTokenizer().Type,
Filter = new List<string>() { "lowercase", "asciifolding", "trim", "engram" }
}
)
)
.TokenFilters(i => i
.Add("engram", new EdgeNGramTokenFilter
{
MinGram = 3,
MaxGram = 15
}
)
)
.CharFilters(cf => cf
.Add("drop_chars", new PatternReplaceCharFilter
{
Pattern = #"[^0-9]",
Replacement = ""
}
)
.Add("drop_specChars", new PatternReplaceCharFilter
{
Pattern = #"[^0-9a-zA-Z]",
Replacement = ""
}
)
)
)
So far found plenty of help to get the pagination working for a get(table) command.
What I need is to pick only few of the entries from a couple of linked tables based on a sql where statement.
I guess the query command is the one to use but in this case how do I do the pagination since that command does not take extra parameters such $config['per_page']
Thanks for the help
Without any more info to go on I think that what you're looking for is something like the following.
public function pagination_example($account_id)
{
$params = $this->uri->ruri_to_assoc(3, array('page'));
$where = array(
'account_id' => $account_id,
'active' => 1
);
$limit = array(
'limit' => 10,
'offset' => (!empty($params['page'])) ? $params['page'] : 0
);
$this->load->model('pagination_model');
$data['my_data'] = $this->pagination_model->get_my_data($where, $limit);
foreach($this->uri->segment_array() as $key => $segment)
{
if($segment == 'page')
{
$segment_id = $key + 1;
}
}
if(isset($segment_id))
{
$config['uri_segment'] = $segment_id;
}
else
{
$config['uri_segment'] = 0;
}
$config['base_url'] = 'http://'.$_SERVER['HTTP_HOST'].'/controller_name/method_name/whatever_your_other_parameters_are/page/';
$config['total_rows'] = $this->pagination_model->get_num_total_rows();// Make a method that will figure out the total number
$config['per_page'] = '10';
$this->load->library('pagination');
$this->pagination->initialize($config);
$data['pagination'] = $this->pagination->create_links();
$this->load->view('pagination_example_view', $data);
}
// pagination_model
public function get_my_data($where = array(), $limit = array())
{
$this->db
->select('whatever')
->from('wherever')
->where($where)
->limit($limit['limit'], $limit['offset']);
$query = $this->db->get();
if($query->num_rows() > 0)
{
$data = $query->result_array();
return $data;
}
return FALSE;
}
This should at least get you on the right track
If this isn't what you're asking I'd happy to help more if you can be a little more specific. How about some of your code.
The only other options that I can think of would be to either code a count in your select statement or not limit the query and use array_slice to select a portion of the returned array.
I have an Observable<WebResponse> (WebResponse implements IDisposable)
responseObservable
.Where(webResponse => webResponse.ContentType.StartsWith("text/html"))
.Select(webResponse => webResponse.ContentLength)
.Run()
(Ignore the pointlessness of the query!)
so, I'm discarding WebResponse instances without calling Dispose on them. This seems bad.
More abstractly: If I have an Observable<IDisposable>, how do I deal with the disposal of generated items?
Assuming that you have a method WebResponse CreateMyWebResponse() use Observable.Using like this:
var responseObservable = Observable.Using(() => CreateMyWebResponse(), Observable.Return);
Change the Where and Do bits to something like
.Do(webResponse => {
if (webResponse.ContentType.StartsWith("text/html"))
ProcessAndDispose(webResponse);
else
webResponse.Dispose(); })
perhaps?
EDIT
Based on your edit, how about
.Select(webResponse => {
int r = -1;
if (webResponse.ContentType.StartsWith("text/html"))
r = webResponse.ContentLength;
webResponse.Dispose();
return r; })
.Where(i => i != -1)
now? This would generalize into something like
FilterProjectDispose(e, pred, proj) {
e.Select(x => {
using(x) {
if (pred(x)) return Some(proj(x));
else return None; }})
.Where(x => x.IsSome)
.Select(x => x.Value)
}
assuming Some/None as in F# (I am apparently starting to forget my C#).