Breeze work-around for multi valued property queries - c#

I'm trying to assemble ad-hoc queries to Breeze.
I have a physician.contact.addresses relationship.
When I try:
myPred = new pred('contact.addresses.street1', op.StartsWith, "a");
And execute it I get:
"The parent value for a property access of a property 'Addresses' is not a single value. Property access can only be applied to a single value."
To try a work-around I've tried parsing out those many-relationships and am passing it to the breeze controller in .withParameters like this:
var criteriaStr = toManyArray.length ? ko.utils.stringifyJson(toManyArray) : "";
query = query.withParameters({ searchParms: criteriaStr });
where toManyArray is an array of fieldName:value pairs.
on the controller side:
[HttpGet]
public IQueryable<Physician> Physician(string searchParms = null)
{
if (searchParms != null)
{
var ser = new JavaScriptSerializer();
var searchCritAry = ser.Deserialize<String[]>(searchParms);
foreach (var aryItem in searchCritAry)
{
// aryItem looks like this:
// f_str_street_from_addresses:a
var predEnt = aryItem.Split(':')[0].Split('_')[4];
var predField = aryItem.Split(':')[0].Split('_')[2];
var predVal = aryItem.Split(':')[1];
switch (predEnt)
{
case "addresses":
switch (predField)
{
case "street":
//physPool =
_contextProvider.Context.Physicians
.Where(p => p.Contact.Addresses.Any(a => a.Street1.StartsWith(predVal)));
break;
case "street2":
//physPool =
_contextProvider.Context.Physicians
.Where(p => p.Contact.Addresses.Any(a => a.Street2.StartsWith(predVal)));
break;
}
break;
}
}
// here I want to combine the .Where clauses from above with the breeze preds
return _contextProvider.Context.Physicians;
}
return _contextProvider.Context.Physicians;
}
It's not working and only returning a selection using the predicates that are passed in the normal way through Breeze's query. I don't see how to pass the filtered IQueryable to Breeze's _contextProvider.
Thanks for any suggestions.

Take a look at the new "any/all" support in Breeze ( as of version 1.4.7). Some examples may be found here: http://www.breezejs.com/documentation/query-examples
You can construct your query/predicate like this:
var pred = new Breeze.Predicate('contact.addresses', "any", 'street1', op.StartsWith, "a");
var query = EntityQuery.from("Physicians").where(pred);
or simply
var query = EntityQuery.from("Physicians")
.where("contact.addresses", "any", "street1", "startsWith", 'a')
You may want to add an expand if you want the contact and address information sent down as well.
query = query.expand("contact.addresses");
If you have a need for nested any/all expressions you may need to add the following configuration to your BreezeController to determine just how deep you want to allow the expressions to go (2 in the example below):
[BreezeController(MaxAnyAllExpressionDepth = 2)]

Related

How to sort dynamically in mongodb

I have a "sort" query parameter. This maybe looking like this:
?sort=-name,+number
Now I want to read this and build a sort query for the c# MongoDB-Driver.
I thought of something like this:
var sortByList = parameters.Sort?.Split(',');
foreach (var sortBy in sortByList)
{
if (sortBy.StartsWith('-'))
// add to sort query decending ?!?
else
// add to sort query ascending ?!?
}
The only way I found so far is to build a string like "{name:-1, number:1}" and give this to the .Sort-method. But so I have build to build many strings that I really don't need.
Isn't there a way to do something like:
var sortDefinition = new SortDefintion();
var sortByList = parameters.Sort?.Split(',');
foreach (var sortBy in sortByList)
{
if (sortBy.StartsWith('-'))
sortDefinition.Add(sortBy.Substring(1), -1);
else
sortDefinition.Add(sortBy.Substring(1), 1);
}
return db.GetCollection<BsonDocument>("sortTest").Sort(sortDefintion).ToList();
You can solve it like this:
var bldr = Builders<BsonDocument>.Sort;
var sortDefinitions = sortByList.Select(x =>
{
SortDefinition<BsonDocument> sortDef;
var propName = x.Substring(1);
if (x.StartsWith("-"))
sortDef = bldr.Descending(propName);
else
sortDef = bldr.Ascending(propName);
return sortDef;
});
var sortDef = bldr.Combine(sortDefinitions);
return db
.GetCollection<BsonDocument>("sortTest")
.Find(Builders<BsonDocument>.Filter.Empty)
.Sort(sortDef)
.ToList();
Above code uses a Linq Select to create a SortDefinition<BsonDocument> for each string in the list and combines these definitions into a single sort definition afterwards. This sort definition is then applied to the Find.

Querying JSON with SelectTokens

I want to query a JArray and get back another JArray based on some conditions. Now using LINQ I can first query it, return an IEnumerable<JToken> and convert it to another JArray like this:
IEnumerable<JToken> ienmTotalObjects = arrResults.Where(x => x["uResultId"]?.ToString() == arrTaskResults[intResult]["uResultId"].ToString() && x["iElementId"]?.ToString() == strUniqueElementId);
JArray arrTotalObjects = new JArray(ienmTotalObject);
Now I just came to know about the JSON.NET SelectTokens(https://www.newtonsoft.com/json/help/html/SelectToken.htm) and seems like a pretty handy feature to query without converting to IEnumerable, However I'm unable to find a way to apply it in my case scenarios. Curious if it it is really possible ? Any help is appreciated.
From the question I can guess the 2 strcutures of arrResults and arrTaskResults
var arrResults = JArray.Parse(#"[
{ iElementId: 1, ""uResultId"" :""aa"" },
{ iElementId: 2, ""uResultId"" :""bb"" }
]");
var arrTaskResults = JArray.Parse(#"[
{ ""uResultId"" :""aa"" },
{ ""uResultId"" :""bb"" }
]");
However, I have no idea what intResult and strUniqueElementId are so these are set here
var intResult = 0;
var strUniqueElementId = "1";
We can now do the same queries but using SelectToken method passing in a JPath:
var s = arrTaskResults.SelectToken($"$[{intResult}].uResultId");
var selectTokens = arrResults.SelectTokens($"$[?(#.uResultId=='{s}' && #.iElementId=={strUniqueElementId})]");
Executing this will output the following:
[
{
"iElementId": 1,
"uResultId": "aa"
}
]

NEST - IndexMany doesn't index my objects

I've used NEST for elasticsearch for a while now and up until now I've used the regular ElasticSearchClient.Index(...) function, but now I want to index many items in a bulk operation.
I found the IndexMany(...) function, but I must do something wrong because nothing is added to the elastic search database as it does with the regular Index(...) function?
Does anyone have any idea?
Thanks in advance!
I found the problem. I had to specifiy the index name in the call to IndexMany
var res = ElasticClient.CreateIndex("pages", i => i.Mappings(m => m.Map<ESPageViewModel>(mm => mm.AutoMap())));
var page = new ESPageViewModel
{
Id = dbPage.Id,
PageId = dbPage.PageId,
Name = dbPage.Name,
Options = pageTags,
CustomerCategoryId = saveTagOptions.CustomerCategoryId,
Link = dbPage.Link,
Price = dbPage.Price
};
var pages = new List<ESPageViewModel>() { page };
var res2 = ElasticClient.IndexManyAsync<ESPageViewModel>(pages, "pages");
This works as expected. Guess I could specify a default index name in the configuration to avoid specifying the index for the IndexMany call.
If you are using C# you should create a list of objects that you want to insert then call the IndexMany function.
Example :
List<Business> businessList = new List<Business>();
#region Fill the business list
...............................
#endregion
if (businessList.Count == 1000) // the size of the bulk.
{
EsClient.IndexMany<Business>(businessList, IndexName);
businessList.Clear();
}
And in the end check again
if (businessList.Count > 0)
{
EsClient.IndexMany<Business>(businessList, IndexName);
}

Reusing projections in queries, the part after "select new"

In my ASP.NET MVC Application, I have many actions that return JSONs.
In these JSONs, some sub-structures are repeated.
For example
ajax: "/Bands/Members" is supposed to return the members of a band, which are all musicians
ajax: "/Musicians/All" is supposed to return all the musicians on the system
ajax: "/Musicians/Search" is supposed to return all the musicians that match something
etc...
Here I show (1):
public JsonResult Members(long musicBandId)
{
MusicBand b = db.MusicBands.SingleOrDefault(b => b.MusicBandId == musicBandId);
if (b == null)
return null;
return Json(new
{
error = false,
message = "",
persons = from m in b.Members select new
{
musicianId = p.MusicianId,
name = m.Name,
instrument = new
{
instrumentId = m.instrument.InstrumentId,
model = m.instrument.Model
}
}
}, JsonRequestBehavior.AllowGet);
}
And here I show (2):
public JsonResult All(int page, int pageSize)
{
var musicians = db.Musicians;
var pageOfMusicians = musicians.Skip((page-1) * pageSize).Take(pageSize);
return Json(new
{
error = false,
message = "",
musiciansCount = musicians.Count(),
page = page,
pageSize = pageSize
musicians = from m in pageOfMusicians select new
{
musicianId = m.MusicianId,
name = m.Name,
instrument = new
{
instrumentId = m.instrument.InstrumentId,
model = m.instrument.Model
}
}
}, JsonRequestBehavior.AllowGet);
}
This has several problems:
If I want to change the JSON format, I have to change it in every single action!
example: If I want to change "name" to "fullname", I have to change it in Members() and All()
Lot of "copy pasting": If I'm creating a new action that somewhere in the structure returns a musician, I need to copy and paste that piece of code that represents the musician
{
musicianId = p.MusicianId,
name = p.Name,
instrument = new
{
instrumentId = instrument.InstrumentId,
model = instrument.Model
}
}
Code is too large
What solution exists to this problem?
If you propose a framework, please show me how would the previous queries look with that framework
Notice I'm always using Linq-to-entities in my examples, and I would like to keep it like that because of performance issues. I know that with Linq-to-objects I could to something like:
from m in pageOfMusicians.ToList() select m.GetDTO()
Being GetDTO() some method that can be run with Linq-to-Objects.
But then, again, I want to stick to Linq-to-Entities
Alternative 1
If you don't worry about using dynamics mixed with regular typed C# code you could make a utility method like...
public static dynamic PrepareForMusiciansView(IQuerable<Musician> musicians)
{
return musicians.Select(m => new
{
musicianId = m.MusicianId,
name = m.Name,
instrument = new
{
instrumentId = m.instrument.InstrumentId,
model = m.instrument.Model
}
}
}
...and then...
return Json(new
{
error = false,
message = "",
musiciansCount = musicians.Count(),
page = page,
pageSize = pageSize
musicians = Util.PrepareForMusiciansView(pageOfMusicians)
}, JsonRequestBehavior.AllowGet);
The method name should clearly reflect its purpose in terms of your application. Maybe you want to focus more on the serialization function and use a name like PrepareForJson. Also, it should be consistent with your coding standards. I would avoid it if nowhere else dynamics is used.
Alternative 2
Use AutoMapper (available via NuGet).
With AutoMapper you'd typically have DTO classes for Musician and Instrument, having the properties you want to expose in the view. If these properties have the same names as those in the source classes, AutoMapper will match them by name convention.
Using AutoMapper always involves defining the mappings and executing mappings. Defining the mappings should be done once at application startup. It looks like...
Mapper.CreateMap<Musician, MusicianDto>();
Mapper.CreateMap<Instrument, InstrumentDto>();
There are different ways to execute the mappings, but when working with IQueryable the preferred way is
musicians = pageOfMusicians.Project().To<MusicianDto>()
This projects IQueryable<Musician> to IQueryable<MusicianDto>, including the nested Instrument (if the DTO has a property of that name).
These are two viable alternatives I can think of to reduce the awkward new {} statements to reusable one-liners. It really depends on your application and coding style which alternative you prefer.

Having trouble with a very basic search -- no results

I have Elasticsearch up and running. Using Sense within Marvel, I am able to get a result, with this query:
GET _search
{
"query": {
"query_string": {
"query": "massa"
}
}
}
My c# code, trying to recreate the above:
var node = new Uri("http://localhost:9200");
var settings = new ConnectionSettings(node).SetDefaultIndex("mediaitems");
var client = new ElasticClient(settings);
var results = client.Search<stuff>(s => s
.Query(qs => qs.QueryString(q => q.Query("massa"))));
var d = results.Documents;
But unfortunately I'm not getting any results, nothing in "results.Documents". Any suggestions? Maybe a way to see the generated json? What is the simplest way to just query everything in an index? Thanks!
Even though your search results are going to be mapped to the proper type because you are using .Search<stuff>, you still need to set the default type as part of your query.
var node = new Uri("http://localhost:9200");
var settings = new ConnectionSettings(node).SetDefaultIndex("mediaitems");
var client = new ElasticClient(settings);
var results = client.Search<stuff>(s => s
.Type("stuff") //or .Type(typeof(stuff)) if you have decorated your stuff class correctly.
.Query(qs => qs.QueryString(q => q.Query("massa"))));
var d = results.Documents;
Additionally, your results response contains a ConnectionStatus property. You can interrogate this property to see the Request and Response to/from Elasticsearch to see if your query is being executed as you expect.
Update: You can also set a default type the index settings as well.
var settings = new ConnectionSettings(node).SetDefualtIndex("mediaitems");
settings.MapDefaultTypeIndices(d=>d.Add(typeof(stuff), "mediaitems");
You can also check nest raw client
var results = client.Raw.SearchPost("mediaitems", "stuff", new
{
query = new
{
query_string = new
{
query = "massa"
}
}
});
You can get the values of search request URL and JSON request body as under:
var requestURL = response.RequestInformation.RequestUrl;
var jsonBody = Encoding.UTF8.GetString(response.RequestInformation.Request);
You can find other useful properties in RequestInformation for debugging.

Categories

Resources