C# Build where clause dynamically in Linq? for RavenDB - c#

My code like bellow:
This Main class :
public class Product {
public string Id { set; get; }
public IList<Attr> Attributes { set; get; }
}
This child class of main class :
public class Attr
{
public string Key { set; get; }
public object Value { set; get; }
}
Filter item class:
public class Filter
{
public CompareType Type { set; get; }
public string Key { set; get; }
public object Value { set; get; }
}
Linq extension fuction for querying :
public static class LINQExtension
{
public static bool isMatch(this Product prod, this List<Filter> filters)
{
foreach(Filter F in filters){
Attr attribute = prod.Attributes.Any(A => A.Key == F.Key);
switch(F.Type){
case CompareType.CONTAIN: return ((string)attribute.Value).Contains(F.Value);
case ....
default: return false;
}
}
}
}
Filtering products result: (Not working)
public ActionResult FilterProducts(string word, decimal min, decimal max){
List<Filter> Conditions = new List<Filter> {
new Filter {Key = "Price", Type = CompareType.BETWEEN, Value = new decimal[] {min, max} },
new Filter {Key = "Title", Type = CompareType.CONTAIN, Value = word }
...
};
var Result = Session.Query<Product>().Where(P => P.isMatch(Conditions)).ToList();
return View(Result);
}
When it tried to run give errors like below:
{"Could not understand expression: .Where(P => P.isMatch(value(App.Controllers.HomeController+<>c__DisplayClass2).Conditions)).ToList()"}

In general, RavenDB's linq provider implementation is not equal to Linq-to-Objects provider.
Under the hood, Raven's client API serializes linq query experssion to Lucene query, then makes a REST call to server with that query. (You can use Fiddler to see it happen)
For example, given a database named Test with Northwind sample data and the query code (and assuming you have Fiddler active)
using (var store = new DocumentStore
{
Url = "http://localhost.fiddler:8080",
DefaultDatabase = "Test"
})
{
store.Initialize();
using (var session = store.OpenSession())
{
var result = session.Query<Order>().Where(x =>
x.Company == "companies/58" && x.Freight < 30m).ToList();
}
}
you will see the following REST call to the server (after url decoding)
http://localhost:8080/databases/Test/indexes/dynamic/Orders?&query=Company:companies/58 AND Freight_Range:{* TO Dx30}&pageSize=128&SortHint-Freight_Range=Double
What you see highlighted in the url is Linq query "serialized" into Lucene query.
In your case the error that you are seeing is simply Raven's linq implementation cannot understand how to transform your custom code into Lucene query

Related

How can I read just one field in MongoDB C#?

Im trying to read just one field from the MongoDB with a given ID, but its giving me all the object... How can I do it ? What should I do? I tried with this repository but its returning me all the object
Here is my class:
public class Hall
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string Name { get; set; }
public List<string> Surveys { get; set; }
}
And here is my repository with the connection to Mongo:
public Task<List<Hall>> ReadSurveysHall(string hallId)
{
var filter = Builders<Hall>.Filter.(x => x.Id, hallId);
return _mongoManager.Find(filter, MongoConstants.HallCollection);
}
I just want it to give me just the list of Survey's string.
Thank you in advance.
In order to return only the list of surveys, you can change your method like this:
public async Task<List<string>> ReadSurveysHall(string hallId)
{
var filter = Builders<Hall>.Filter.(x => x.Id, hallId);
var halls = await _mongoManager.Find(filter, MongoConstants.HallCollection);
var hall = halls.FirstOrDefault();
if (hall == null)
return new List<string>();
return hall.Surveys;
}
You can further optimize this by using a projection so that MongoDB only delivers the data that you are interested in, e.g.:
var projection = Builders<Hall>.Projection.Exclude(x => x.Id).Include(x => x.Surveys);
var options = new FindOptions<Hall, Hall>()
{
Projection = projection,
};
How you use that projection depends on the capabilities of _mongoManager. Take a look at the Find methods and check whether any of these accepts a projection or some kind of FindOptions as a parameter.

Entity Framework "FromSqlRaw or FromSqlInterpolated was called with non-composable SQL and with a query composing over it." error for a return object

I have this view
[Table("vw_Entity", Schema = "c")]
public partial class vw_Entity : BaseEntity
{
public long? PredictedEntityTypeID { get; set; }
public bool IsManuallyChanged { get; set; }
}
where BaseEntity is the class that stores only my ID and UUID.
This is my DTO return object:
public class EntityDTO
{
public long ID { get; set; }
public LookupTableDetails PredictedEntityTypeId { get; set; }
public bool IsManuallyChanged { get; set; }
}
where LookupTableDetails looks like:
public class LookupTableDetails
{
public long Id { get; set; }
public string Name { get; set; }
}
Now I have this stored procedure that does basically a PATCH. I call it using the following snippet:
var data = await _context.vw_Entity.FromSqlRaw("EXECUTE core.Update_Entity #EntityID", parameters)
.Select(x => new EntityDTO()
{
ID = x.ID,
PredictedEntityTypeId = new LookupTableDetails() { Id = x.PredictedEntityTypeId, Name = x.PredictedEntityTypeId == 1 ? "Entity1" : "Entity2" },
IsManuallyChanged = x.IsManuallyChanged
}).ToListAsync();
However, this crashes with an error
FromSqlRaw or FromSqlInterpolated was called with non-composable SQL and with a query composing over it
I'm aware what this error does, if I have a object of some other class inside my view then the stored procedure couldn't map it properly and return the error but in this case, my view is clear from obstacles of that type and all I need to do is just return the LookupTableDetails in my DTO object. The error is in
PredictedEntityTypeId = new LookupTableDetails() { Id = x.PredictedEntityTypeId, Name = x.PredictedEntityTypeId == 1 ? "Entity1" : "Entity2" }
I tried most of the solutions that the Internet offers, such as wrapping it with IgnoreFilters.., AsEnumerable() etc.
Any ideas what is the cause and how can I prevent it from happening again in the future i.e fix it? :D
Since You iterate over result from the first query, try changing to:
var data = _context.vw_Entity.FromSqlRaw("EXECUTE core.Update_Entity #EntityID", parameters)
.ToList() // force change from IQueryable to IEnumerable and resolve result
.Select(x => new EntityDTO() // You are anyway using all results, so this is ok
{
ID = x.ID,
PredictedEntityTypeId = new LookupTableDetails() { Id = x.PredictedEntityTypeId, Name = x.PredictedEntityTypeId == 1 ? "Entity1" : "Entity2" },
IsManuallyChanged = x.IsManuallyChanged
}).ToList();

Elasticsearch filter range on nested objects

I have an object PurchaseOrder that has a List<VendorStatus> VendorStatuses, VendorStatus has an EstimatedShipDate column. I need to filter a purchase order by the latest EstimatedShipDate. How can I do this?
I've tried:
Filter<PurchaseOrder>.Range(r => r
.OnField(x => x.VendorStatuses.OrderByDescending(v => v.StatusUpdateDate).First().EstimatedShipDate)
.GreaterOrEquals(fromDate)
.LowerOrEquals(toDate));
and
var f = Filter<PurchaseOrder>.Range(r => r
.OnField("VendorStatuses.EstimatedShipDate")
.GreaterOrEquals(fromDate)
.LowerOrEquals(toDate));
and
var newFilter = Filter<PurchaseOrder>.Nested(nfd => nfd.Path(x => x.VendorStatuses.First().EstimatedShipDate))
.Filter(f2 => f2.Range(x => x.OnField(f => f.VendorStatuses.First().EstimatedShipDate)
.GreaterOrEquals(fromDate)
.LowerOrEquals(toDate))));
But none of these worked. How can I do this?
I'm not even able to get the date range working when running against elastic directly. The following query returns no results even though when using the head plugin i can see them listed under "po"
{
"query": {
"range": {
"po.vendorStatuses.estimatedShipDate": {
"gte": "2016-10-01",
"lte": "2016-11-01",
}
}
}
}
We're using Nest version 1.7.2 in our project with elastic 1.4.5.
Here's some more info on our types:
//that builds our search query, contains many methods to build up the FilterContainer
public class PurchaseOrderSearchQueryBuilder
{
protected FilterContainer Filter { get; set; }
public PurchaseOrderSearchQueryBuilder WithExpectedShipDate(DateTime fromDate, DateTime toDate)
{
var newFilter = Filter<PurchaseOrder>.
//method i need to implment
Filter &= newFilter;
return this;
}
}
//example working filter method
public PurchaseOrderSearchQueryBuilder WithCustomerId(long customerId)
{
if (customerId > 0)
Filter &= Filter && Filter<PurchaseOrder>.Term(p => p.CustomerId, customerId);
return this;
}
public class PurchaseOrder
{
...
public long CustomerId { get; set; }
public List<PoVendorStatus> VendorStatuses { get; set; }
}
public class PoVendorStatus
{
public long Id { get; set; }
...
public DateTime? EstimatedShipDate { get; set; }
public DateTime StatusUpdateDate { get; set; }
}
Using nest this will return all the parent object that have one inner object that has met the condition. If you want to query nested object as well the need to be mapped as nested and you need to use nested query.
//Fluent
client.Search<PurchaseOrder>(s=>s.Query(
q=>q.DateRange(
dr=>dr.Field(p=>p.VendorStatuses.First().StatusUpdateDate)
.GreaterThan(fromDate)
.LessThan(toDate))));
//Object
client.Search<PurchaseOrder>(new SearchRequest<PurchaseOrder>()
{
Query =new DateRangeQuery
{
//If you used all default mappings this might be camelCase
Field = "VendorStatuses.StatusUpdateDate",
GreaterThan = fromDate,
LessThan = toDate
}
});

Group By is not aggregating

I am aggregating data that I retrieve from multiple identical web services. The same row count and data points are returned with only a variance in the Value. The GroupBy clause I am using is not condensing any of the rows. I have the same row count before and after the GroupBy.
MyWebServiceUrls
.AsParallel()
.SelectMany(url => GetMetricItemData(url))
.GroupBy(item => new { item.DateTime, item.Group, item.Metric }, item => item.Value)
.Select(grp => new MetricItem()
{
DateTime = grp.Key.DateTime,
Group = grp.Key.Group,
Metric = grp.Key.Metric,
Value = // type = decimal?
grp.Any(mi => mi.HasValue)
? grp.Key.Metric.AggregationType == Metric.MetricAggregationTypes.Sum
? grp.Sum(mi => mi.Value)
: grp.Average(mi => mi)
: null
})
.AsEnumerable();
The syntax looks correct based on other examples I have found.
I send this data back to my database and can aggregate with the statement GROUP BY [DateTime], [Group], [Metric] and everything works great. While I can use the database to solve this issue, I would like to know how to correctly use LINQ in this instance.
What am I missing to get this LINQ expression to work?
UPDATE:
This is the relevant MetricItem and Metric class definition:
public class MetricItem
{
public string Group { get; set; }
public DateTime DateTime { get; set; }
public Metric Metric { get; set; }
public Decimal? Value { get; set; }
}
public class Metric
{
public string Code { get; set; }
public string Label { get; set; }
private List<string> SumMetrics = new List<string>(new string[] { "TPI", "TPO", "TPIO" });
public enum MetricAggregationTypes { Sum, Average };
public MetricAggregationTypes AggregationType
{
get
{
if (SumMetrics.IndexOf(this.Code) >= 0)
return MetricAggregationTypes.Sum;
else
return MetricAggregationTypes.Average;
}
}
}
You need to override Equals and GetHashCode on the Metric class. Most Linq methods use hash codes for comparison operations, so for most objects you define yourself, you need to override this class if you plan to use something like GroupBy, Union, etc.

Select where model property contains all in list of key value pairs

I have the following class
public class Account
{
IEnumerable<AccountData> Data { get; set; }
}
where AccountData is
public class AccountData
{
public virtual long Id { get; set; }
public virtual AccountTag AccountTag { get; set; }
public virtual string Value { get; set; }
}
and where Account Tag is
public class AccountTag
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
}
I want to return all accounts where the Data field is in a list of key value pairs . long is the AccountTag.Id, and the AccountData.Value contains the string
Here is what I have so far, but this is performed on the web server and there could be thousands of accounts returned so I am looking for a linq to sql version.
public IEnumerable<Account> FindByCompanyDataTags(long companyId, IEnumerable<KeyValuePair<long, string>> tags)
{
var tempAccounts = (from acc in this.Data where acc.Company.Id == companyId orderby acc.Name select acc);
IList<Account> accounts = new List<Account>();
foreach (var account in tempAccounts)
{
var matches = true;
foreach (var t in tags)
{
if (account.Data.Any(x => x.AccountTag.Id == t.Key && x.Value.Contains(t.Value)))
{
continue;
}
matches = false;
break;
}
if (matches)
{
accounts.Add(account);
}
}
return accounts;
}
If I use resharper to convert this into a linq expression I get the following
public IEnumerable<Account> FindByCompanyDataTags(long companyId, IEnumerable<KeyValuePair<long, string>> tags)
{
var tempAccounts = (from acc in this.Data where acc.Company.Id == companyId orderby acc.Name select acc);
IList<Account> accounts = (from account in tempAccounts let matches = tags.All(t => account.Data.Any(x => x.AccountTag.Id == t.Key && x.Value.Contains(t.Value))) where matches select account).ToList();
return accounts;
}
But when I run this I get a method not supported exception.
This is really confusing me, any suggestions?
That's happening because in first case you prepare query to db during foreach execution here
foreach (var account in tempAccounts)
{....}
You get collection of Account objects and working with them on client side in memory (other words, you're using Linq to objects)
In the second case you're trying execute Linq to Sql query but provider cannot translate working with your KeyValuePair objects into sql query, therefore it raise exception said about that.
UPDATE
Try to use IQueryable and build your query thorugh consequently applying Where clause:
IQueryable<Account> tempAccountsWithWhere = tempAccounts;
foreach (var tag in tags)
{
tempAccountsWithWhere = tempAccountsWithWhere.Where(
a => a.Data.Any(
ad => ad.AccountTag.Id == tag.Key && ad.Value.Contains(tag.Value)));
}
IList<Account> accounts = tempAccountsWithWhere.ToList();

Categories

Resources