I'm trying to write a semi-advanced LINQ to SQL query to search through my entities in a .NET 6 project. I want to give the user the ability to enter something like "user search a OR alernative lookup b OR seperate query from joe" into a search. After handling the user input, my LINQ query is set up below:
bool isNameSearch = true;
bool isNoteSearch = true;
List<List<string>> _multiSearchList = new()
{
new List<string>() { "user", "search", "a" },
new List<string>() { "alternative", "lookup", "b" },
new List<string>() { "seperate", "query", "from", "joe" }
};
var _query = (from tblHeader in _DbContext.batches
where tblHeader.isDeleted != true
select tblHeader)
_query = _query.Where(x =>
_multiSearchList.Any(y =>
y.All(z =>
(isNameSearch && x.Name.Contains(z)) ||
(isNoteSearch && x.Note.Contains(z))
)
)
);
var _results = await _query.ToListAsync();
var _totalCount = await _query.CountAsync();
The problem is that although this query works with a local collection, it can't be translated to sql. Is there a way to accomplish this?
Since EF Core do not support queries with local collections (exception is Contains), I would suggest using LINQKit which has PredicateBuilder.
Then you can build predicate for your specific case:
bool isNameSearch = true;
bool isNoteSearch = true;
List<List<string>> _multiSearchList = new()
{
new List<string>() { "user", "search", "a" },
new List<string>() { "alternative", "lookup", "b" },
new List<string>() { "seperate", "query", "from", "joe" }
};
var _query = (from tblHeader in _DbContext.batches
where tblHeader.isDeleted != true
select tblHeader)
var mainPredicate = PredicateBuilder.New(_query);
foreach (var sentense in _multiSearchList)
[
var predicate = PredicateBuilder.New(_query);
foreach(var word in sentense)
[
var subPredicate = PredicateBuilder.New(_query);
if (isNameSearch)
subPredicate = subPredicate.Or(x => x.Name.Contains(word));
if (isNoteSearch)
subPredicate = subPredicate.Or(x => x.Note.Contains(word));
predicate = predicate.And(subPredicate)
]
mainPredicate = mainPredicate.Or(predicate);
]
_query = _query.Where(mainPredicate);
Related
Does anyone know how to do multiple aggregations with nest?
I have found quite a few examples unfortunately none of them work.
Here's what I have:
Vehicles fields = new Vehicles();
//create a terms query
var query = new TermsQuery
{
IsVerbatim = true,
Field = "VehicleOwnerId",
Terms = new string[] { 25 },
};
var aggregations = new Dictionary<string, IAggregationContainer>
{
{ "years", new AggregationContainer
{
Terms = new TermsAggregation(nameof(fields.Year))
{
Field = new Field(nameof(fields.Year))
}
}
}
//,
//{ "makes", new AggregationContainer
// {
// Terms = new TermsAggregation("Make")
// {
// Field = new Field(nameof(fields.Make))
// }
// }
//}
};
//create the search request
var searchRequest = new SearchRequest
{
Query = query,
From = 0,
Size = 100,
Aggregations = aggregations
};
var result = client.SearchAsync<InventoryLiveView>(searchRequest).Result;
var years = result.Aggregations.Terms("years");
Dictionary<string, long> yearCounts = new Dictionary<string, long>();
foreach (var item in years.Buckets)
{
yearCounts.Add(item.Key, item.DocCount ?? 0);
}
If I just execute the code like this it works. Years returns the aggregates as expected. If I try to add another field (like the one commented out above) it fails and I get zero records.
How can I get multiple aggregates in one query? I see examples of it all over, but none of the examples I've tried seem to work and most seem to be outdated (including some in the Nest documentation).
I have also tried this approach which is pretty close to the documentation.
//create the search request
var searchRequest = new SearchRequest
{
Query = query,
From = 0,
Size = 100,
//Aggregations = aggregations
Aggregations = new AggregationDictionary
{
{
"childAgg", new ChildrenAggregation("childAgg", typeof(Vehicles ))
{
Aggregations = new AggregationDictionary
{
{"years", new TermsAggregation(nameof(fields.VehicleYear))},
{"makes", new TermsAggregation(nameof(fields.VehicleMakeName))},
{"models", new TermsAggregation(nameof(fields.VehicleModelName))},
}
}
}
}
};
var result = client.SearchAsync<Vehicles>(searchRequest).Result;
This just produces a null reference exception.
I guess I'll never have too worry about getting to proud as a programmer :)
It's too often that the solution to the problem makes me feel stupid when it reveals itself.
So my issue was that the field I was trying to use in the aggregation was text and couldn't be used. I switched everything to the ID fields and multiple aggregations work as expected.
So this version of the code works like a champ:
Vehicle fields = new Vehicle ();
//create a terms query
var query = new TermsQuery
{
IsVerbatim = true,
Field = "VehicleOwnerId",
Terms = new string[] { "30" },
};
string[] Fields = new[]
{
nameof(fields.Year),
nameof(fields.MakeId),
nameof(fields.ModelId)
};
var aggregations = new Dictionary<string, IAggregationContainer>();
foreach (string sField in Fields)
{
var termsAggregation = new TermsAggregation(sField)
{
Field = sField
};
aggregations.Add(sField, new AggregationContainer { Terms = termsAggregation });
}
//create the search request
var searchRequest = new SearchRequest
{
Query = query,
From = 0,
Size = 10,
Aggregations = aggregations
};
var result = client.SearchAsync<InventoryLiveView>(searchRequest).Result;
var years = result.Aggregations.Terms(nameof(fields.Year));
Dictionary<string, long> yearCounts = new Dictionary<string, long>();
foreach (var item in years.Buckets)
{
yearCounts.Add(item.Key, item.DocCount ?? 0);
}
The exact error from elasticsearch, which I saw using postman was:
Fielddata is disabled on text fields by default. Set fielddata=true on [MakeName] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead.
Here is my example using SearchDescriptors. My only problem is how to serialize returned results into a proper Key Value list. Is Looping through a fields list the best way to return results.
SearchDescriptor<Advert> agghDescriptor = new SearchDescriptor<Advert>();
agghDescriptor.Aggregations(ag => ag.Terms("make", a => a.Field(f => f.Make)) &&
ag.Terms("region", a => a.Field(f => f.Region)) &&
ag.Terms("city", a => a.Field(f => f.City)) &&
ag.Terms("category", a => a.Field(f => f.Category)) &&
ag.Terms("application", a => a.Field(f => f.Application)) &&
ag.Terms("portalId", a => a.Field(f => f.PortalId)) &&
ag.Terms("isActiveAuctionAdvert", a => a.Field(f => f.IsActiveAuctionAdvert)) &&
ag.Terms("isBargainAccount", a => a.Field(f => f.IsBargainAccount)) &&
ag.Terms("condition", a => a.Field(f => f.Condition))
);
agghDescriptor.Size(0);
var json2 = _client.RequestResponseSerializer.SerializeToString(agghDescriptor);
var aggregationResult = _client.Search<Advert>(agghDescriptor);
List<string> fields = new List<string>();
fields.Add("make");
fields.Add("category");
fields.Add("region");
List<Aggregation> aggregations = new List<Aggregation>();
foreach (var field in fields)
{
var aggrs = aggregationResult.Aggregations.Terms(field);
List<AggregateItem> aggregateItems = new List<AggregateItem>();
foreach (var item in aggrs.Buckets)
{
aggregateItems.Add(new AggregateItem()
{
Count = item.DocCount ?? 0,
Key = item.Key
});
}
aggregations.Add(new Aggregation()
{
Name = field,
Aggregates = aggregateItems
});
}
I am trying to display/list data after using aggregation function but it isn't happening.
This code works absolutely fine.
var connectionstring = "mongodb://localhost:27017";
var client = new MongoClient(connectionstring);
var db = client.GetDatabase("school");
var col = db.GetCollection<BsonDocument>("students");
var filter = new BsonDocument("type", "homework");
var filter2 = Builders<BsonDocument>.Filter.Eq("scores.type", "homework");
var myresults = await col.Find(filter2)
.Limit(2)
.Project("{name:1,scores:1,_id:0}")
.Sort("{score:1}")
.ToListAsync();
foreach (var result in myresults)
{
Console.WriteLine(result);
}
This code fetches document as it should however when I replace
var myresults = await col.Find(filter2)
.Limit(2)
.Project("{name:1,scores:1,_id:0}")
.Sort("{score:1}")
.ToListAsync();
with this
var myresults = await col.Aggregate()
.Unwind("{$scores}")
.Group(new BsonDocument { { "_id", "$_id" }, { "lowscore", new BsonDocument("$min", "$scores.score") } })
//.Group("{_id:'$_id',lowscore:{$min:'$scores.score'}}")
.ToListAsync();
No record is being pulled.
I do not want to use Pipeline method. I simply want to display the result obtained via aggregate function.
This is my Mongo Query (I want the same result as this in C#)-
db.students.aggregate([{$sort:{_id:-1}},{$unwind:"$scores"},{$group:{_id:"$_id", lowscore:{"$min":"$scores.score"}}}])
Building aggregation pipeline is bit tricky.
Try:
var pipeline = new BsonDocument[] {
new BsonDocument{ { "$sort", new BsonDocument("_id", 1) }},
new BsonDocument{{"$unwind", "$scores"}},
new BsonDocument{{"$group", new BsonDocument{
{"_id", "$_id"},
{"lowscore",new BsonDocument{
{"$min","$scores.score"}}
}}
}}
};
var result = collection.Aggregate<BsonDocument> (pipeline).ToListAsync();
If you do pipeline.ToJson(), you'll get following JSON equivalent string which is same as of your original and tested MongoShell query.
[
{
"$sort": {
"_id": 1
}
},
{
"$unwind": "$scores"
},
{
"$group": {
"_id": "$_id",
"lowscore": {
"$min": "$scores.score"
}
}
}
]
This is wrong... {$scores} isn't even valid json. Remove the curly braces and the dollar sign from the $unwind directive.
The parameter name is field, so you need to provide a field name to it.
Try with writing only $score instead of #scores.score. may be it helpful.
db.students.aggregate([{$sort:{_id:-1}},{$unwind:"$scores"},{$group:{_id:"$_id", lowscore:{"$min":"$score"}}}])
I have a use case where I need to do aggregation on multiple columns using C#.
I am using NEST libraries for this and I am facing the following issue
Query C# :
var searchRequest = new SearchRequest
{
SearchType = SearchType.Count,
Filter = filter,
Aggregations = new Dictionary<string, IAggregationContainer>
{
{ "a", new AggregationContainer
{
ExtendedStats = new ExtendedStatsAggregator()
{
Field = "a"
}
}
},
{ "b", new AggregationContainer
{
ExtendedStats = new ExtendedStatsAggregator()
{
Field = "b"
}
}
}
}
};
When I receive response from NEST, however I am getting only result for one aggregation. I am looking at SearchResult.Agg dictionary but it has only one entry for one aggregation field instead of two.
Let me know if I am missing soemthing or is it some issue with NEST libraries
if you are using term aggregation then you need to use aggregation with filter.
var qryRes1 = client.Search<object>(x => x
.Aggregations(ag => ag
.Filter("filter", (flt => flt
.Filter(f =>
{
FilterContainer filter = null;
filter &= f.Query(qr => qr.Term(wl => wl.OnField("a").Value("the value you need to filter for field a")));
return filter;
})
.Aggregations(agr => agr
.Terms("b", tr =>
{
TermsAggregationDescriptor<object> trmAggDescriptor = new TermsAggregationDescriptor<object>();
trmAggDescriptor.Field("b");
return trmAggDescriptor;
}))))
));
var terms = qryRes1.Aggs.Filter("filter").Terms("b");
I need to preform a query that check if a collection is in given collection, just like the regular in operation but for collections.
class Post
{
public string[] Tags {get;set;}
}
session.Queury<Post>.Where(x=>x.Tags.in(new[]{".net","c#","RavenDB"})).ToList();
so if i have in my DB:
new Post{Tags= new[]{"C#",".net"}};
it will be returned
but if i have:
new Post{Tags= new[]{"C#",".net","SQLServer"}};
it will not be returned.
Update:
what i am trying to do is this:
session.Query<Post>()
.Where(x => x.Tags.All(y => y.In(new[] { "C#", ".net", "RavenDB" })))
.ToList();
but i got System.NotSupportedException.
I manage to find a solution:
static void Main(string[] args)
{
var sessionStore = new EmbeddableDocumentStore
{
RunInMemory = true,
UseEmbeddedHttpServer = true,
Conventions =
{
DefaultQueryingConsistency = ConsistencyOptions.AlwaysWaitForNonStaleResultsAsOfLastWrite
}
};
sessionStore.Initialize();
using (var session = sessionStore.OpenSession())
{
var allTags = new[] {"C#", ".net", "RavenDB", "Linux", "Mac"};
var tagsCollection = new[] {"C#", ".net", "RavenDB"};
var complementTagsCollection = allTags.Except(tagsCollection).ToList();
session.Store(new Post
{
Tags = new List<string>{"C#",".net"}
});
session.SaveChanges();
// Posts where all their tags are in tagsCollection
var result = session.Query<Post>().Where(x => !x.Tags.In(complementTagsCollection)).ToList();
}
}
The way IN works, it matches ANY of them.
If you want to match all you have to do a separate check for each.
public class Foo
{
public ObjectId _id { get; set; }
public BsonDocument properties { get; set; }
}
public void FindFoos()
{
var client = new MongoClient(ConfigurationManager.ConnectionStrings["MongoDB"].ConnectionString);
var server = client.GetServer();
var db = server.GetDatabase("FooBar");
//var collection = db.GetCollection<GeoJsonFeature<GeoJson2DGeographicCoordinates>>("Sections");
var collection = db.GetCollection<Foo>("Foos");
collection.Insert(new Foo
{
properties = new BsonDocument{
{"Foo" , "foo1"},
{"Bar" , "bar1"}
}
});
collection.Insert(new Foo
{
properties = new BsonDocument{
{"Foo" , "foo2"},
{"Bar" , "bar2"}
}
});
var query = Query<Foo>.Where(foo => foo.properties.AsQueryable().Any(property => property.Name == "Foo" && property.Value.AsString == "foo1"));
var result = collection.Find(query).First();
}
calling FindFoos results in the following exception:
Unsupported where clause: Queryable.Any(Queryable.AsQueryable(foo.properties), (BsonElement property) => ((property.Name == "Foo") && (property.Value.AsString == "foo1"))).
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.ArgumentException: Unsupported where clause: Queryable.Any(Queryable.AsQueryable(foo.properties), (BsonElement property) => ((property.Name == "Foo") && (property.Value.AsString == "foo1"))).
at the line:
var query = Query<Foo>.Where(foo => foo.properties.AsQueryable().Any(property => property.Name == "Foo" && property.Value.AsString == "foo1"));
I can do this query easily in the mongodb shell as:
db.Foos.find( {'properties.Foo' : 'foo1'} );
What is the correct way to do this query with Linq?
Try LINQ's SelectMany() method. It is used to flatten nested collections. Instead of using nested for loops, we can do the task in a more 'LINQ' way.
Ex given below -
Master m1 = new Master() { name = "A", lstObj = new List<obj> { new obj { i = 1, s = "C++" }, new obj { i = 1, s = "C#" }, new obj { i = 1, s = "Java" } } };
Master m2 = new Master() { name = "A", lstObj = new List<obj> { new obj { i = 4, s = "PHP" }, new obj { i = 5, s = "Ruby" }, new obj { i = 6, s = "Perl" } } };
List<Master> lstMasters = new List<Master> { m1, m2 };
var result = lstMasters.SelectMany(m => m.lstObj).Where(o => o.s == "PHP");
Just replace the Master class with your BsonDocument.