I have a boolean query that I want to add dynamically to using the object intializer syntax but I am having trouble doing so. Basically, I have 5 stages of checking parameters, and if they exist they get added to the boolquery. Here's what I am trying to do (obviously doesn't work):
SomeBoolQuery.Must.ToList().Add(someQueryContainer);
How can I make the above work so that I can dynamically add queries to the BoolQuery? No, I can't do it via this:
SomeBoolQuery.Must = new QueryContainer[] {query1, query2};
Because I don't know how many queries I am going to have and I can't add them all at once. I need a dynamic solution.
I was thinking maybe this:
SomeBoolQueryContainer &= someQuery;
and then at the end:
SomeBoolQuery.Must = new QueryContainer[] {someBoolQueryContainer};
But that seems a little redundant to say the least. Any ideas?
EDIT: The last option tried above doesn't seem to work. not returning any results.
Adding bool query dynamically using Fluent API :
ElasticQueryContainer elasticQueryContainer = GetSearchQuery(searchRequest);
var response = client.Search<IDocument>(s1 => s1
.Query(q => q
.Bool(bq => bq
.Should(elasticQueryContainer.orQuery.ToArray())
.Must(elasticQueryContainer.andQuery.ToArray())
.MustNot(elasticQueryContainer.notQuery.ToArray())
)));
public class ElasticQueryContainer
{
public List<QueryContainer> orQuery { get; set; }
public List<QueryContainer> andQuery { get; set; }
public List<QueryContainer> notQuery { get; set; }
public ElasticQueryContainer()
{
orQuery = new List<QueryContainer>();
andQuery = new List<QueryContainer>();
notQuery = new List<QueryContainer>();
}
}
public ElasticQueryContainer GetSearchQuery(SearchRequest searchRequest )
{
//...Populate ElasticQueryContainer : orQuery, andQuery, notQuery dynamically
var elasticQueryContainer = new ElasticQueryContainer();
if (!String.IsNullOrEmpty(searchRequest.FullTextSearch))
{
elasticQueryContainer.andQuery.Add(new QueryStringQuery
{
Query = searchRequest.FullTextSearch.ToLower(),
DefaultOperator = searchRequest.FullTextOperator == SearchEnums.FullTextSearchOperator.AND ? Operator.And : Operator.Or
});
}
}
The SearchRequest is a class having properties which has to be searched.
public class SearchRequest
{
public String FullTextSearch { get; set; }
public SearchEnums.FullTextSearchOperator FullTextOperator { get; set; }
}
For Object intializer syntax, you can use it this way :
new BoolQuery()
{
MustNot = elasticQueryContainer.notQuery.ToArray(),
Should = elasticQueryContainer.orQuery.ToArray(),
Must = elasticQueryContainer.andQuery.ToArray()
};
You can also refer to : https://www.elastic.co/guide/en/elasticsearch/client/net-api/2.x/bool-query-usage.html
Related
I'm developing an Azure Mobile App service to interface to my Xamarin application.
I've created, connected and successfully populated an SQL Database, but when I try to add some filters to my request, for example an orderby() or where() clauses, it returns me a Bad Request error.
For example, this request: https://myapp.azurewebsites.net/tables/Race?$orderby=iRound%20desc,iYear%20desc&$top=1&ZUMO-API-VERSION=2.0.0 gives me {"message":"The query specified in the URI is not valid. Could not find a property named 'IYear' on type 'MyType'."}.
My configuration method is this:
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.AddTablesWithEntityFramework()
.ApplyTo(config);
config.MapHttpAttributeRoutes();
Database.SetInitializer(new CreateDatabaseIfNotExists<MainDataContext>());
app.UseWebApi(config);
and my DbContext is this:
public class MainDataContext : DbContext
{
private const string connectionStringName = "Name=MS_TableConnectionString";
public MainDataContext() : base(connectionStringName)
{
Database.Log = s => WriteLog(s);
}
public void WriteLog(string msg)
{
System.Diagnostics.Debug.WriteLine(msg);
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<TableColumnAttribute, string>(
"ServiceTableColumn", (property, attributes) => attributes.Single().ColumnType.ToString()));
}
public DbSet<Race> Race { get; set; }
public DbSet ...ecc...
}
Following this guide, I added a migration after creating my TableControllers. So the TableController for the example type shown above is pretty standard:
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public class RaceController : TableController<Race>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
MainDataContext context = new MainDataContext();
DomainManager = new EntityDomainManager<Race>(context, Request);
}
// GET tables/Race
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<Race> GetAllRace()
{
return Query();
}
// GET tables/Race/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<Race> GetRace(string id)
{
return Lookup(id);
}
// PATCH tables/Race/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<Race> PatchRace(string id, Delta<Race> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/Race
public async Task<IHttpActionResult> PostRace(Race item)
{
Race current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/Race/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeleteRace(string id)
{
return DeleteAsync(id);
}
}
As you can see, I already tried to add the EnableQuery attribute to my TableController, as seen on Google. I also tried to add these filters to the HttpConfiguration object, without any success:
config.Filters.Add(new EnableQueryAttribute
{
PageSize = 10,
AllowedArithmeticOperators = AllowedArithmeticOperators.All,
AllowedFunctions = AllowedFunctions.All,
AllowedLogicalOperators = AllowedLogicalOperators.All,
AllowedQueryOptions = AllowedQueryOptions.All
});
config.AddODataQueryFilter(new EnableQueryAttribute
{
PageSize = 10,
AllowedArithmeticOperators = AllowedArithmeticOperators.All,
AllowedFunctions = AllowedFunctions.All,
AllowedLogicalOperators = AllowedLogicalOperators.All,
AllowedQueryOptions = AllowedQueryOptions.All
});
I don't know what to investigate more, as things seems to be changing too fast for a newbie like me who's first got into Azure.
EDIT
I forgot to say that asking for the complete table, so for example https://myapp.azurewebsites.net/tables/Race?ZUMO-API-VERSION=2.0.0, returns correctly the entire dataset. The problem occurs only when adding some clauses to the request.
EDIT 2
My model is like this:
public class Race : EntityData
{
public int iRaceId { get; set; }
public int iYear { get; set; }
public int iRound { get; set; }
ecc..
}
and the database table that was automatically created is this, including all the properties inherited from EntityData:
Database table schema
Digging into the source code, Azure Mobile Apps sets up camelCase encoding of all requests and responses. It then puts them back after transmission accordign to rules - so iRaceId becomes IRaceId on the server.
The easiest solution to this is to bypass the auto-naming and use a JsonProperty attribute on each property within your server-side DTO and client-side DTO so that they match and will get encoding/decoded according to your rules.
So:
public class Race : EntityData
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("raceId")]
public int iRaceId { get; set; }
[JsonProperty("year")]
public int iYear { get; set; }
[JsonProperty("round")]
public int iRound { get; set; }
etc..
}
I want my repsository to be independent of the data access technology. Currently I am working on a Xamrin.Forms App that uses Azure Mobile App Services for data access. For performance and flexibility reasons I want my repository to look simmilar like the following:
Task<IEnumerable<IDomainObject>> GetDomainObjectAsync(Func<IQueryable<IDomainObject>, IQueryable<IDomainObject>> query)
Suppose my IDomainObject looks like the following:
public interface IDomainObject
{
string Name { get; }
}
and my DataAccess Object:
internal class AzureDomainObject : IDomainObject
{
public string Name { get; set; }
public string Id { get; set; }
}
As far as I found out and tested I can do the following to query the database within my repository implementation:
public async Task<IEnumerable<IDomainObject>> GetDomainObjectAsync(Func<IQueryable<IDomainObject>, IQueryable<IDomainObject>> query)
{
// _table of type IMobileServiceTable<AzureDomainObject> gotten by MobileServiceClient
var tableQuery = _table.GetQuery();
tableQuery.Query = tableQuery.Query.Take(4); // 1) this was for testing and it works (ordering etc also works)
// tableQuery.Query = query(tableQuery.Query); // 2) this was my initial idea how to use the input param
await _table.ReadAsync(tableQuery);
}
My poblem now is how to use the input param query to replace 1) with 2).
tableQuery.Query expects an IQueryable<AzureDomainObject> but query is of type IQueryable<IDomainObject>.
Neither .Cast<AzureDomainObject>() nor .OfType<AzureDomainObject>() work to convert. Nor does (IQueryable<IAzureDomainObject>)query; work.
Cast and OfType throw NotSupportedException and the hard cast throws an InvalidCastException.
I also tried to extract the Expression from the query input param and assign it to the tableQuery.Query. But then a runtime exception occurs that they are not compatible.
Another idea I had was to use the ReadAsync(string) overload and pass the string representation of the passed query param. But this way I don't know how to generate the string.
So the final question is: Does anyone knows how to hide both AzureDomainObject and IMobileServiceTable from the domain model but keep the flexibility and performance benefits of IQueryable in the repository interface?
According to your description, I checked this issue and here is my implementation for this scenario, you could refer to them.
Model:
public class TodoItem : IDomainObject
{
public string Id { get; set; }
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
[JsonProperty(PropertyName = "complete")]
public bool Complete { get; set; }
}
public interface IDomainObject
{
string Id { get; set; }
}
Repository:
public interface IAzureCloudTableRepository<T> where T : IDomainObject
{
Task<IEnumerable<T>> GetDomainObjectAsync(Func<IQueryable<T>, IQueryable<T>> query);
}
public class AzureCloudTableRepository<T> : IAzureCloudTableRepository<T> where T : IDomainObject
{
IMobileServiceTable<T> table;
public AzureCloudTableRepository(MobileServiceClient client)
{
this.table = client.GetTable<T>();
}
public async Task<T> CreateItemAsync(T item)
{
await table.InsertAsync(item);
return item;
}
public async Task<IEnumerable<T>> GetDomainObjectAsync(Func<IQueryable<T>, IQueryable<T>> query)
{
var tableQuery = this.table.CreateQuery();
tableQuery.Query = tableQuery.Query.Take(4); //the internal fixed query
tableQuery.Query = query(tableQuery.Query); //the external query
return await tableQuery.ToEnumerableAsync();
}
}
TEST:
var mobileService = new MobileServiceClient("https://{your-app-name}.azurewebsites.net");
var todoitem = new AzureCloudTableRepository<TodoItem>(mobileService);
var items = await todoitem.GetDomainObjectAsync((query) =>
{
return query.Where(q => q.Text!=null);
});
I am new at automapper and it is a very good stuff easy to use, but now I have a problem with it. Trying to convert my derived class to base and it gives me
AutoMapper.AutoMapperMappingException
Missing type map configuration or unsupported mapping.
Mapping types: ClientEventDb -> EventId
Database.ClientEventDb -> EventId
Destination path: ClientEvent
Source value:
Event:Login
Automapper wants to convert ClientEventDb to EventId? I don't understand why. EventId is an enum...
Please help me I have run out of ideas.
Here is the code which I run:
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.Take(1000).ToArray();
}
Mapper.CreateMap<ClientEventDb, ClientEvent>();
Console.WriteLine("hello");
return edbl.Select(edb => Mapper.Map<ClientEvent>(edb)).ToArray();
Here are my classes
[Table("events", Schema = "public")]
public class ClientEventDb : ClientEvent
{
public ClientEventDb(string userName, EventId happening, object userObject = null)
: base(userName, happening, userObject)
{
}
public ClientEventDb()
{
}
}
[ProtoContract]
[Table("events", Schema = "public")]
public class ClientEvent : ClientEventBase
{
[ProtoMember(1)]
[Column("username")]
public string UserName { get; private set; }
[ProtoMember(2)]
[Column("time")]
public DateTime DateTime { get; private set; }
[ProtoMember(3)]
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; private set; }
[ProtoMember(4)]
[Column("data")]
public byte[] UserObject { get; set; }
public ClientEvent(string userName,EventId happening, object userObject=null) : base(happening)
{
UserName = userName;
DateTime = DateTime.Now;
//UserObject = null;
if (userObject!=null) throw new NotImplementedException();
}
public ClientEvent()
{
}
protected ClientEvent Clone()
{
return (ClientEvent)MemberwiseClone();
}
}
[ProtoContract]
[ProtoInclude(10, typeof(ClientEvent))]
public class ClientEventBase
{
[Column("eventid")]
[ProtoMember(1)]
public int EventIdValue { get; set; } //must be public because of entity framework
[NotMapped]
public EventId EventId
{
get { return (EventId) EventIdValue; }
set { EventIdValue = (int) value; }
}
public ClientEventBase(EventId eventId)
{
EventId = eventId;
}
public ClientEventBase()
{
}
public override string ToString()
{
return String.Format("Event:{0}",EventId);
}
}
public enum EventId
{
Login = 1,
Logout,
ExitApplication,
}
UPDATE
bugfix: ClientEvent [Key] attribute moved to id property
Solution was this (thx to stuartd):
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.ToArray();
}
Mapper.CreateMap<ClientEventDb, ClientEvent>().ConstructUsing((ClientEventDb src) => new ClientEvent());
return edbl.Select(Mapper.Map<ClientEvent>).ToArray();
AutoMapper is confused as its made to map between similar properties in different classes, you are using it incorrectly - you just need to go from the derived class to the base which does not require AutoMapper. You could use this to do what you need....
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.Take(1000).ToArray();
}
return edbl.Cast<ClientEvent>().ToList();
I'd be looking at why you even feel you need a derived ClientEventDb though - understand we dont have the whole picture here but it seems to do nothing in addition to what the base class already does.
The issue is that ClientEvent has two constructors but you have not told AutoMapper which to use.
If you want it to use your constructor with parameters, change your mapping code to this and it will work:
Mapper.CreateMap<ClientEventDb, ClientEvent>()
.ConstructUsing(src => new ClientEvent(src.UserName, src.EventId));
Or to make AutoMapper use the default constructor:
Mapper.CreateMap<ClientEventDb, ClientEvent>()
.ConstructUsing((ClientEventDb src) => new ClientEvent());
I have an autocompleter as a partial view which I would like to use twice in the same page. There are three models involved
public partial class CustomParent
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int64 CustomParentId { get; set; }
public Geoname ParentGeoname { get; set; }
public Geoname ChildGeoname { get; set; }
public CustomParent()
{
ParentGeoname = new Geoname();
ChildGeoname = new Geoname();
}
}
public partial class Geoname
{
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int64 GeonameId { get; set; }
public string Name { get; set; }
}
public partial class GeonameWithFilter
{
public GeonameWithFilter()
{
FilterString = "";
}
public Geoname Geoname { get; set; }
public String FilterString { get; set; }
}
I have a controller set up
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "CustomParentId,ParentGeoname,ChildGeoname")] CustomParent customParent)
{...}
I set up the two Html partials on my create view initially only using the customparent and geoname models and it worked fine setting the values in of the parent and child geonames as expected. I now require additional parameters to be passed to the partial view so I created an encapsulating class (GeonameWithFilter). I made changes to my partial view and to my two html.partials on the view page which now look like this :
#Html.Partial("_GeonameAutocomplete",new GeonameWithFilter(){Geoname = Model.ParentGeoname,FilterString="'featurecodes':'CUPA'"},
new ViewDataDictionary()
{
TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "ParentGeoname" }
})
#Html.Partial("_GeonameAutocomplete", new GeonameWithFilter() { Geoname = Model.ChildGeoname, FilterString = "'featurecodes':'ADM1,ADM2,ADM3,ADM4'" },
new ViewDataDictionary()
{
TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "ChildGeoname" }
})
The problem is that the customparent.parentGeoname and customparent.childGeoname are not now getting returned to my controller. I'm guessing this is because the partial view's model is not the same class as my page models parentGeoname and childGeoname but cannot work out or find any examples of how to handle such a circumstance if indeed it is possible.
Well - it took me most of a day, but I now have what I required.
I gave up on the encapsulation and instead added my basic geoname model to the new ViewDataDictionary and nullified the default model for the Html.Partial.
I then added the filterstring parameter as a key in a new ViewDataDictionary which I used as the argument for the one with the model and TemplateInfo.
Many thanks to https://stackoverflow.com/users/7714/craig-stuntz for his answer to a different question Shorthand for creating a ViewDataDictionary with both a model and ViewData items? that pointed me in the direction I have gone.
My autocompleter now just use #ViewData["FilterString"] to access the filter parameters. The GeonameWithFilter encapsulation is no longer needed. My two Html.Partials on my view page now look like this:
#Html.Partial("_GeonameAutocomplete", null,
new ViewDataDictionary(new ViewDataDictionary() { {"FilterString", "featurecodes:'CUPA'" }})
{
Model = Model.ParentGeoname,
TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ParentGeoname" }
})
#Html.Partial("_GeonameAutocomplete", null,
new ViewDataDictionary(new ViewDataDictionary() { { "FilterString", "featurecodes:'ADM1,ADM2,ADM3,ADM4'" } })
{
Model = Model.ChildGeoname,
TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ChildGeoname" }
})
If anyone knows a better way to achieve the end result I would still like to hear it.
I'm working in Entity Framework 5 and having problems creating an expression to use inside a method.
I believe the problem is that normally I would call the expression in a lambda expression such as dbContext.Counties.Select(GetLargeCities()), but in the code I am working with, I am projecting the Counties entity into a view model called CountyWithCities. Where I would normally call the expression, I have a singleton c and don't know how to call the expression there.
The reason I want to accomplish this using an expression is because I want the GetCountiesWithCities method to hit the database once, with Entity Framework constructing a complex graph for all the objects in the result.
For some reason the code below is producing the error `The name 'GetLargeCities' does not exist in the current context."
public class CountyWithCities // this is a view model
{
public int CountyID { get; set; }
public string CountyName { get; set; }
public IEnumerable<City> Cities { get; set; }
}
public class City // this is an entity
{
public int CityID { get; set; }
public string CityName { get; set; }
public int Population { get; set; }
}
public IEnumerable<CountyWithCities> GetCountiesWithCities(int StateID)
{
return dbContext.States
.Where(s => s.StateID = StateID)
.Select(s => s.Counties)
.Select(c => new CountyWithCities
{
CountyID = c.CountyID,
CountyName = c.CountyName,
Cities = GetLargeCities(c) // How do I call the expression here?
});
}
public Expression<Func<County, IEnumerable<City>>> GetLargeCities = county =>
county.Cities.Where(city => city.Population > 50000);
Thanks!
I normally do this with an extension method.
public static IQueriable<City> LargeCities(this IQueriable<County> counties){
return counties
.SelectMany(county=>county.Cities.Where(c=>c.Population > 50000));
}
usage:
dbContext.Counties.LargeCities()
public IEnumerable<CountyWithCities> GetCountiesWithCities(int StateID)
{
return dbContext.States
.Where(s => s.StateID = StateID)
.Select(s => s.Counties)
.LargeCities()
.GroupBy(c=>c.County)
.Select(c => new CountyWithCities
{
CountyID = g.Key.CountyID,
CountyName = g.Key.CountyName,
Cities = g.AsQueriable() // i cant remember the exact syntax here but you need the list from the group
});
}