I am trying to implement elasticsearch into my webshop but having some troubles on using filters. The filtering is done dynamically.
Example:
I start with showing all the products that are indexed. So no filter is applied. Visitors can choose their own filters like: color, size, brand, type, category, ....
But I don't now how to build the search result with elasticsearch and NEST.
This is my solution without filtering:
var query = ElasticClient.Search<Product>(s => s
.From(from)
.Size(size)
);
I also have another question on indexing a collection<> or list<>. I had to use JsonIgnore on those collections. Could I index those as well?
This is my class:
/// <summary>
/// Represents a product
/// </summary>
public partial class Product {
private ICollection<ProductCategory> _productCategories;
private ICollection<ProductManufacturer> _productManufacturers;
private ICollection<ProductPicture> _productPictures;
/// <summary>
/// Gets or sets the name
/// </summary>
public virtual string Name { get; set; }
/// <summary>
/// Gets or sets the short description
/// </summary>
public virtual string ShortDescription { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity is published
/// </summary>
public virtual bool Published { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the entity has been deleted
/// </summary>
public virtual bool Deleted { get; set; }
/// <summary>
/// Gets or sets the date and time of product creation
/// </summary>
public virtual DateTime CreatedOnUtc { get; set; }
/// <summary>
/// Gets or sets the date and time of product update
/// </summary>
public virtual DateTime UpdatedOnUtc { get; set; }
/// <summary>
/// Gets or sets the collection of ProductCategory
/// </summary>
[JsonIgnore] /* added - wesley */
public virtual ICollection<ProductCategory> ProductCategories
{
get { return _productCategories ?? (_productCategories = new List<ProductCategory>()); }
protected set { _productCategories = value; }
}
/// <summary>
/// Gets or sets the collection of ProductManufacturer
/// </summary>
[JsonIgnore] /* added - wesley */
public virtual ICollection<ProductManufacturer> ProductManufacturers
{
get { return _productManufacturers ?? (_productManufacturers = new List<ProductManufacturer>()); }
protected set { _productManufacturers = value; }
}
/// <summary>
/// Gets or sets the collection of ProductPicture
/// </summary>
[JsonIgnore] /* added - wesley */
public virtual ICollection<ProductPicture> ProductPictures
{
get { return _productPictures ?? (_productPictures = new List<ProductPicture>()); }
protected set { _productPictures = value; }
}
}
Is there someone who can help me?
Be sure to read the whole documentation on writing queries here: http://nest.azurewebsites.net/nest/writing-queries.html
What follows is a pasted excerpt from there.
Conditionless queries
Writing complex boolean queries is one thing but more often then not you'll want to make decisions on how to query based on user input.
public class UserInput
{
public string Name { get; set; }
public string FirstName { get; set; }
public int? LOC { get; set; }
}
and then
.Query(q=> {
QueryDescriptor<ElasticSearch> query = null;
if (!string.IsNullOrEmpty(userInput.Name))
query &= q.Term(p=>p.Name, userInput.Name);
if (!string.IsNullOrEmpty(userInput.FirstName))
query &= q
.Term("followers.firstName", userInput.FirstName);
if (userInput.LOC.HasValue)
query &= q.Range(r=>r.OnField(p=>p.Loc).From(userInput.Loc.Value))
return query;
})
This again turns tedious and verbose rather quickly too. Therefor nest allows you to write the previous query as:
.Query(q=>
q.Term(p=>p.Name, userInput.Name);
&& q.Term("followers.firstName", userInput.FirstName)
&& q.Range(r=>r.OnField(p=>p.Loc).From(userInput.Loc))
)
If any of the queries would result in an empty query they won't be sent to elasticsearch.
So if all the terms are null (or empty string) on userInput except userInput.Loc it wouldn't even wrap the range query in a boolean query but just issue a plain range query.
If all of them empty it will result in a match_all query.
This conditionless behavior is turned on by default but can be turned of like so:
var result = client.Search<ElasticSearchProject>(s=>s
.From(0)
.Size(10)
.Strict() //disable conditionlessqueries by default
///EXAMPLE HERE
);
However queries themselves can opt back in or out.
.Query(q=>
q.Strict().Term(p=>p.Name, userInput.Name);
&& q.Term("followers.firstName", userInput.FirstName)
&& q.Strict(false).Range(r=>r.OnField(p=>p.Loc).From(userInput.Loc))
)
In this example if userInput.Name is null or empty it will result in a DslException. The range query will use conditionless logic no matter if the SearchDescriptor uses .Strict() or not.
Also good to note is that conditionless query logic propagates:
q.Strict().Term(p=>p.Name, userInput.Name);
&& q.Term("followers.firstName", userInput.FirstName)
&& q.Filtered(fq => fq
.Query(qff =>
qff.Terms(p => p.Country, userInput.Countries)
&& qff.Terms(p => p.Loc, userInput.Loc)
)
)
If both userInput.Countries and userInput.Loc are null or empty the entire filtered query will be not be issued.
Related
I have the below view in SQL Server represented using PetaPoco in my C# application:
/// <summary> Rep Level Keys. </summary>
[TableName("vXPATRepLevelKeys")]
[ExplicitColumns]
public partial class vXPATRepLevelKeys : dbo.Record<vXPATRepLevelKeys>
{
/// <summary> Gets or sets the RepLevelKey. </summary>
public string RepLevelKey { get; set; }
}
However, when I attempt to select from the view using:
var result = _database.Fetch<xPAT.vXPATRepLevelKeys>("SELECT * FROM vXPATRepLevelKeys").OrderBy(x => x.RepLevelKey);
var asStrings = result.Select(x => x.RepLevelKey).ToList();
I just get a list of NULL values. asStrings has 33 items in the list, all being NULL. However, when I run the above view myself, I get 33 non-null results.
I'm new to PetaPoco (tbh, I'm not even sure if it is a PetaPoco related issue) and have inherited this application, which I'm attempting to add this new view to so any help is greatly appreciated.
If you use the [ExplicitColumns] attribute, you must use the [Column] attribute on each property
[Column]
public string RepLevelKey { get; set; }
My question is: How to pass Collections and Referenced object types without writing so many HiddenFors?
This is my model:
public partial class AddressObject
{
public int ID { get; set; }
public string KeyNumber { get; set; }
public int AddressID { get; set; }
public int ObjectTypeID { get; set; }
public double ResidentalArea { get; set; }
public short ResidentsNumber { get; set; }
public Nullable<short> TuristBedsNumber { get; set; }
public virtual Address Address { get; set; }
public virtual ObjectType ObjectType { get; set; }
public virtual ICollection<TrashCan> TrashCans { get; set; }
public virtual ICollection<ObjectOwner> ObjectOwners { get; set; }
}
This is my View:
#Html.HiddenFor(model => model.Address.CityID);
#Html.HiddenFor(model => model.Address.Hood);
#Html.HiddenFor(model => model.Address.ID);
#Html.HiddenFor(model => model.Address.Number);
#Html.HiddenFor(model => model.Address.Region);
#Html.HiddenFor(model => model.Address.Street);
#Html.HiddenFor(model => model.Address.City.Addresses);
#Html.HiddenFor(model => model.Address.City.ID);
#Html.HiddenFor(model => model.Address.City.Name);
#Html.HiddenFor(model => model.Address.City.PostalCode);
#Html.HiddenFor(model => model.AddressID);
#Html.HiddenFor(model => model.ObjectOwners);
#Html.HiddenFor(model => model.ObjectType.ID);
#Html.HiddenFor(model => model.ObjectType.Type);
#Html.HiddenFor(model => model.ObjectTypeID);
#Html.HiddenFor(model => model.TrashCans);
#Html.HiddenFor(model => model.TuristBedsNumber);
I don't want to write everything for Address. I just want to pass Address. My collection is ObjectOwners. I want to do the same thing. Solution exists?
EDIT: I have Controller and method in in it ActionResult(AddressObject addressObject). Inside that controller, I'm calling a unitOfWork with repository for saving that entity. My
HiddenFors are wrapped with #HtmlBeginForm("Save", "AddressObject"). When I pass my model to controller, my Address object AddressObject.Address is null = Count 0. I want to be able to pass whole object with my referenced objects and collection without writing hiddenfors for all properties of referenced object or collection.
EDIT2:
I have master detail scenario(that's not matter), textboxes are binded to AddressObject for my View's model. So I have #Html.TextBoxFor(m => m.AddressID) for example. The problem is, when I change for example AddressObject.TurisBedsNumber, every referenced object or collection become null and because of that my AddressObject is not persistent when it is passed from View to the Controller back with my updated properties. I want my references and other properties be untouched as they were before Updating. I've tried with Mvc Futures and Serializing whole object and my object and its collections and referenced objects are ok. The problem is, when I deserialize my object, that object is not updated with new TuristBedNumber properties; it's old value. I want to know how to save state of my collection and other objects. I can save my state with HiddenFor helper (too many properties to write) or I could get AddressObject or Collection from my repository and update it in my controller; again, too many properties. I want to be able to say "hey you collection and my referenced objects, you'll not change no matter what". I want to serialize them whole, but only them.
Someone asked for controller but it is common:
public ActionResult(AddressObject addressObject) { unitOfWork.Update(addressObject) }
Yes I have ValidState etc....
The following is a helper class I use for creating hidden inputs for properties. If the property is a complex type, its called recursively to create hidden inputs for each property of the complex type. In your case you would use #Html.HiddenInputFor(m => m.Address) to generate all 17 inputs. Be sure to add the namespace in your web.config.
using System;
using System.Collections;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;
using Sandtrap.Web.Extensions;
namespace Sandtrap.Web.Html
{
/// <summary>
///
/// </summary>
public static class HiddenInputHelper
{
/// <summary>
/// Returns the html for a hidden input(s) of a property.
/// </summary>
/// <typeparam name="TModel"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="helper"></param>
/// <param name="expression"></param>
/// <param name="includeID">
/// A value indicating the the 'id' attribute should be rendered for the input.
/// </param>
/// <remarks>
/// If the property is a complex type, the methods is called recursively for each property
/// of the type. Collections and complex types with null value (except those with the
/// Required attribute) are ignored.
/// </remarks>
public static MvcHtmlString HiddenInputFor<TModel, TValue>
(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, bool includeID = false)
{
string name = ExpressionHelper.GetExpressionText(expression);
ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
StringBuilder html = new StringBuilder();
return MvcHtmlString.Create(HiddenInput(metaData, name, includeID));
}
/// <summary>
/// Returns the html for a hidden input(s) of a property defined by its metadata.
/// The string is not html-encoded.
/// </summary>
/// <param name="metaData">
/// The metadata of the property.
/// </param>
/// <param name="name">
/// The name of the property (rendered as the 'name' attribute).
/// </param>
/// <param name="includeID">
/// A value indicating the the 'id' attribute should be rendered for the input.
/// </param>
/// <remarks>
/// If the property is a complex type, the methods is called recursively for each property
/// of the type. Collections and complex types with null value (except those with the
/// Required attribute) are ignored.
/// </remarks>
public static string HiddenInputForMetadata(ModelMetadata metaData, string name, bool includeID = false)
{
return HiddenInput(metaData, name, includeID);
}
#region .Helper methods
/// <summary>
/// Returns the html for a hidden input(s) of a property.
/// </summary>
/// <param name="metaData">
/// The property metadata.
/// </param>
/// <param name="name">
/// The name of the property (rendered as the 'name' attribute).
/// </param>
/// <param name="includeID">
/// A value indicating the the 'id' attribute should be rendered for the input.
/// </param>
private static string HiddenInput(ModelMetadata metaData, string name, bool includeID)
{
StringBuilder html = new StringBuilder();
if (metaData.ModelType.IsArray && metaData.Model != null)
{
// Primarily for database time stamps, this need to called before checking IsComplexType
// otherwise an endless loop is created
html.Append(HiddenInput(name, Convert.ToBase64String(metaData.Model as byte[]), includeID));
}
else if (metaData.IsComplexType)
{
foreach (ModelMetadata property in metaData.Properties)
{
if (property.IsCollection() && !property.ModelType.IsArray)
{
// This would just render the Count and Capacity property of List<T>
continue;
}
if (property.Model == null && property.ModelType != typeof(string) && !property.IsRequired)
{
// Ignore complex types that are null and do not have the RequiredAttribute
continue;
}
// Recursive call to render a hidden input for the property
string prefix = string.Format("{0}.{1}", name, property.PropertyName);
html.Append(HiddenInput(property, prefix, includeID));
}
}
else
{
html.Append(HiddenInput(name, metaData.Model, includeID));
}
return html.ToString();
}
/// <summary>
/// Returns the html for a hidden input.
/// </summary>
/// <param name="name">
/// The name of the property.
/// </param>
/// <param name="value">
/// The value of the property.
/// </param>
/// <param name="includeID">
/// A value indicating the the 'id' attribute should be rendered for the input.
/// </param>
/// <returns></returns>
private static string HiddenInput(string name, object value, bool includeID)
{
TagBuilder input = new TagBuilder("input");
input.MergeAttribute("type", "hidden");
if (includeID)
{
input.MergeAttribute("id", HtmlHelper.GenerateIdFromName(name));
}
input.MergeAttribute("name", name);
input.MergeAttribute("value", string.Format("{0}", value));
return input.ToString();
}
#endregion
}
}
The following extension method is also required
public static bool IsCollection(this ModelMetadata metaData)
{
if (metaData.ModelType == typeof(string))
{
return false;
}
return typeof(IEnumerable).IsAssignableFrom(metaData.ModelType);
}
I have a view model that represents all the fields available for searching. I'd like to add some logic that would be able to identify if the search values are all the same and determine whether to hit the DB again for their query.
I think I would have to do something like..
after user submits form save form values to some
temporary field.
upon second submission compare temp value to form values collection.
if values are equal set property in view
model IsSameSearch = true
I'd like to use the Post Redirect Get Pattern too. So that My search View doesn't do anything except post the form values to another action that processes and filters the data, which is then "Getted" using Ajax.
The SearchViewModel contains many many search parameters. Here is an abbreviated version.
public bool UseAdvancedSearch { get; set; }
public bool isSameSearch { get; set; }
/// <summary>
/// Gets or sets the page.
/// </summary>
[HiddenInput]
[ScaffoldColumn(false)]
public int Page { get; set; }
[HiddenInput]
[ScaffoldColumn(false)]
public string SortOption { get; set; }
/// <summary>
/// Gets or sets the address keywords.
/// </summary>
[Display(Name="Address")]
public string AddressKeywords { get; set; }
/// <summary>
/// Gets or sets the census.
/// </summary>
public string Census { get; set; }
/// <summary>
/// Gets or sets the lot block sub.
/// </summary>
public string LotBlockSub { get; set; }
/// <summary>
/// Gets or sets the owner keywords.
/// </summary>
[Display(Name="Owner")]
public string OwnerKeywords { get; set; }
/// <summary>
/// Gets or sets the section township range.
/// </summary>
public string SectionTownshipRange { get; set; }
/// <summary>
/// Gets or sets the strap.
/// </summary>
///
[Display(Name="Account Number/Parcel ID")]
public string Strap { get; set; }
/// <summary>
/// Gets or sets the subdivision.
/// </summary>
public string Subdivision { get; set; }
/// <summary>
/// Gets or sets the use code.
/// </summary>
[Display(Name = "Use Code")]
public string UseCode { get; set; }
/// <summary>
/// Gets or sets the zip code.
/// </summary>
[Display(Name="Zip Code")]
public string ZipCode { get; set; }
If you are getting data from Entity Framework you could cache the data at EF level. Look at the package entity framework extended https://github.com/loresoft/EntityFramework.Extended. It is as simple as adding method .FromCache () to the query you use to retrieve and filter the data and it will cache the query result. Make sure you load all the data required using includes etc.
You wouldn't have to worry about same search in model as the caching provider would look at filter settings and determine that it was different. Alternatively cache the data before filtering and then filter the cached results. This is more appropriate if you have lots of filter parameters with significant variance as you will only have to cache 1 large result rather than thousands of smaller results.
You can get more advanced and specify cache period e.g. Cache for 10 minutes
What you are describing is called caching.
One way to accomplish that in your scenario would be to implement GetHashCode() in a way that it would take into account all your fields/properties to compute a unique value. That way you can use your Hash as the key entry in your cache, and store the results with that key.
For that actual caching you could just use the MemoryCache class provided by the .Net Framework if you are not deploying to a web farm.
Also, if you are familiar with IoC and DI (such as using Unity), things like this can be implemented as an Interceptor, and only requiring you to add an attribute to the method you'd like to cache. That way you implement caching only once as a cross-cutting concern and not fill up your application code with things like this.
This is the line that the error is showing on:
public IOAuth2ServiceProvider<IElance> ElanceServiceProvider { get; set; }
It's showing the error on the IElance Type on that line, but here's the interface:
public interface IElance : IApiBinding
{
/// <summary>
/// Search all jobs, list jobs associated with an employee or a freelancer, and retrieve information for a specific job.
/// </summary>
IJobOperations JobOperations { get; }
/// <summary>
/// Access all messages, users, messages, and Work View™ data associated with an Elance Workroom.
/// </summary>
IWorkRoomOperations WorkroomOperations { get; }
/// <summary>
/// View all of the information associated with an employee or a freelancer.
/// </summary>
IProfileOperations ProfileOperations { get; }
/// <summary>
/// View detailed information on freelancers, and retrieve a list of all freelancers employed by an employer.
/// </summary>
IFreelancerOperations FreelancerOperations { get; }
/// <summary>
/// List Elance groups, and retrieve lists of members and jobs belonging to a group.
/// </summary>
IGroupOperations GroupOperations { get; }
/// <summary>
/// Obtain ancillary Elance information, such as the current list of all job categories.
/// </summary>
IUtilityOperations UtilityOperations { get; }
}
I can't for the life of me figure out why it's telling me this. Am I missing something obvious? I would greatly appreciate any direction on this error.
This is probably caused by IApiBinding or one of its base interfaces not being public. Another possibility is that one of the types used by the interface members is not public.
I have the following ExportMetaData attributes set on my class:
[Export(typeof(IDocumentViewer))]
[ExportMetadata("Name", "MyViewer")]
[ExportMetadata("SupportsEditing", true)]
[ExportMetadata("Formats", DocFormat.DOC, IsMultiple = true)]
[ExportMetadata("Formats", DocFormat.DOCX, IsMultiple = true)]
[ExportMetadata("Formats", DocFormat.RTF, IsMultiple = true)]
I also have a supporting interface:
public interface IDocumentViewerMetaData {
/// <summary>
/// Gets the format.
/// </summary>
/// <value>The format.</value>
IEnumerable<DocFormat> Formats { get; }
/// <summary>
/// Gets the name of the viewer
/// </summary>
/// <value>The name.</value>
string Name { get; }
/// <summary>
/// Gets a value indicating whether this viewer supports editing
/// </summary>
/// <value><c>true</c> if [supports editing]; otherwise, <c>false</c>.</value>
bool SupportsEditing { get; }
}
And of course my ImportMany:
[ImportMany(typeof(IDocumentViewer))]
public IEnumerable<Lazy<IDocumentViewer, IDocumentViewerMetaData>> _viewers { get; set; }
What I would like to do is use a strongly-typed attribute class instead of using the ExportMetaData attribute. I have not figured out a way to do this while also supporting single values (Name, SupportsEditing, in the example above).
I envision doing something similiar the following (or whatever is suggested as best):
[Export(typeof(IDocumentViewer))]
[DocumentViewerMetadata(Name = "MyViewer")]
[DocumentViewerMetadata(SupportsEditing = true)]
[DocumentViewerMetadata(Format = DocFormat.DOC)]
[DocumentViewerMetadata(Format = DocFormat.DOCX)]
I am fairly certain that there IS a way to do this, I just haven't found the right way to connect the dots. :)
You can subclass the ExportAttribute with your own implementation, and decorate it with a MetadataAttribute to allow MEF to use its properties to project the metadata proxy it uses during composition:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property),
MetadataAttribute]
public class ExportDocumentViewerAttribute : ExportAttribute, IDocumentViewerMetadata
{
public ExportDocumentViewer(string name, bool supportsEditing, params DocFormat[] formats)
: base(typeof(IDocumentViewer))
{
if (string.IsNullOrEmpty(name))
throw new ArgumentException("Export requires a name", "name");
Name = name;
SupportsEditing = supportsEditing;
Formats = formats ?? Enumerable.Empty<DocFormat>();
}
public string Name { get; private set; }
public bool SupportsEditing { get; private set; }
public IEnumerable<DocFormat> Formats { get; private set; }
}
[ExportDocumentViewer("Word", true, DocFormat.DOC, DocFormat.DOCX)]
public WordDocumentViewer : IDocumentViewer
{
// Stuff
}
Note you don't actually need to decorate it with your IDocumentViewerMetadata contract, as MEF will project it regardless, I just prefer to so that I know if I make changes to the metadata contract, that my custom export attribute conforms.