I have the following model:
public class Device
{
//....
private ICollection<TagDevice> _tagDevices;
public virtual ICollection<TagDevice> TagDevices { get => _tagDevices ?? (_tagDevices = new List<TagDevice>()); protected set => _tagDevices = value; }
}
public class TagDevice
{
//....
public int TagId { get; set; }
}
I need to select all Devices, which has ALL TagIds from int array.
For example:
device1 has tags [1, 2, 3]
device2 has tags [2, 3, 4]
device3 has tags [3, 4, 5]
tagsApplied is [2,3]
result: returned device1 and device2
I try
query = query.Where(p => tagsApplied.Contains( )
but this method allows only one element, not element list
How to do it?
If i suppose the query is list of Device, change the contains function by Excepting List a from List b, like the following code :
IEnumerable<string> result = query
.Where(device => !tagApplied.Except(device.TagDevices.Select(t=>t.TagId)).Any())
.Select(x => x.Name);
Doc of Except
i hope that will help you fix the issue
Using LINQKit, you can create an extension method that creates an or expression testing against each member of an array:
public static class LinqKitExt { // using LINQKit
public static IQueryable<T> WhereAllIsContained<T,TKey>(this IQueryable<T> dbq, Expression<Func<T,IEnumerable<TKey>>> keyFne, IEnumerable<TKey> searchTerms) {
Expression<Func<T,bool>> pred = PredicateBuilder.New<T>();
foreach (var s in searchTerms)
pred = pred.And(a => keyFne.Invoke(a).Contains(s));
return dbq.Where(pred.Expand());
}
}
With this extension available, you can do
query = query.WhereAllIsContained(d => d.TagDevices.Select(td => td.TagId), tagsApplied);
I believe this should translate to SQL, but I don't have an EF Core model with a collection property to test.
Related
I am trying to create a linq statement to filter otu results using an any clause. My issue is that I dont have a single value to compare against.
In the example below I have a PropertyTaxBill entity that is the parent. Each one has a collection of TaxPropertyAssessmentDetails attached to it.
In this query people can specify that they only want to deal with bills pertaining to a specific class strata so I check to see if any values exist in the classStrata variable. If so then the user selected specific ones. I was trying to do an any clause on the classStrata but instead of giving it a single value to match on I was trying to select all the values in the TaxPropertyAssessmentDetails collection attached to the PropertyTaxBill. Is this possible?
using (var dataContext = contextProvider.GetContext())
{
var query = dataContext.PropertyTaxBills.Where(x => x.Id > 1);
var classStrata = new int[0];
if (classStrata != null && classStrata.Any())
{
query = query.Where(x => classStrata.Any(y => y == x.TaxPropertyAssessmentDetails.SelectMany(z => z.PropertyTaxClassStrataId)));
}
}
You need to use contains so that ef is able to translate to a ef query.
Not sure that i understood your model correctly. Here's a example with a model. Please tell me if i understood incorrect so i can fix it.
public void TestMethod1()
{
IEnumerable<TaxBill> TaxBills = new List<TaxBill>();
var query = TaxBills.Where(x => x.Id > 1);
var classStrata = new int[0];
if (classStrata != null && classStrata.Any())
{
query = query.Where(x => x.AssessmentDetails.Any(ad => ad.TaxClassStrataId.Any(cs => classStrata.Contains(cs))));
}
}
And the entities
public class TaxBill
{
public int Id { get; set; }
public ICollection<AsessmentDetails> AssessmentDetails { get; set; }
}
public class AsessmentDetails
{
public ICollection<int> TaxClassStrataId { get; set; }
}
I have an ObservableCollection like the following-
private ObservableCollection<KeyedList<int, Anime>> _grp;
public ObservableCollection<KeyedList<int, Anime>> GroupedAnimeByGenre
{
get
{
return _grp;
}
set
{
_grp = value;
RaisePropertyChanged("GroupedAnimeByGenre");
}
}
I am using this to populate a LongListSelector with grouping. The KeyedList is implemented like this-
public class KeyedList<TKey, TItem> : List<TItem>
{
public TKey Key { protected set; get; }
public KeyedList(TKey key, IEnumerable<TItem> items)
: base(items)
{
Key = key;
}
public KeyedList(IGrouping<TKey, TItem> grouping)
: base(grouping)
{
Key = grouping.Key;
}
}
I have the following code to feed the ObservableCollection. Keep in mind AnimeList2 is a temporary Collection.
var groupFinale = AnimeList2.GroupBy(txt => txt.id).Where(grouping => grouping.Count() > 1).ToObservableCollection();
GroupedAnimeByGenre = groupFinale ;
But I am unable to convert/use groupFinale with GroupedAnimeByGenre. I am missing the extension method part as I am not well aware of the syntax. Please help
If you remove the ToObservableCollection() call and take just that part
var groupFinale = AnimeList2.GroupBy(txt => txt.id).Where(grouping => grouping.Count() > 1);
you'll see that the type of groupFinale is IEnumerable<IGrouping<int, Anime>>. Hence applying ToObservableCollection() will result in ObservableCollection<IGrouping<int, Anime>>. However, the type of the GroupedAnimeByGenre is ObservableCollection<KeyedList<int, Anime>>. So you need to convert IEnumerable<IGrouping<int, Anime>> to IEnumerable<KeyedList<int, Anime>> which in LINQ is performed by the Select method.
Shortly, you can use something like this
var groupFinale = AnimeList2
.GroupBy(txt => txt.id)
.Where(grouping => grouping.Count() > 1)
.Select(grouping => new KeyedList<int, Anime>(grouping))
.ToObservableCollection();
You can make such conversion easier by providing an extension method (similar to BCL provided ToArray() / ToList()) that will allow skipping the type arguments like this
public static class KeyedList
{
public static KeyedList<TKey, TItem> ToKeyedList<TKey, TItem>(this IGrouping<TKey, TItem> source)
{
return new KeyedList<TKey, TItem>(source);
}
}
Then you can use simply
var groupFinale = AnimeList2
.GroupBy(txt => txt.id)
.Where(grouping => grouping.Count() > 1)
.Select(grouping => grouping.ToKeyedList())
.ToObservableCollection();
How do I created the appropriate AbstractIndexCreationTask for the following scenario?
For a scenario of multiple blogs, how do I get the tags from specific blogs and the tag-count for these?
Members of interest for data-structure stored in RavenDB:
public class BlogPost {
public string BlogKey { get; set; }
public IEnumerable<string> Tags { get; set; }
/* ... */
}
The method I need to implement has the following signature:
public Dictionary<string, int> GetTagsByBlogs(string tag, params string[] blogKeys)
In normal LINQ I would write something like this:
var tags = from post in blogPosts
from tag in post.Tags
where blogKeys.Contains(post.BlogKey)
group tag by tag into g
select new {
Tag = g.Key,
Count = g.Count(),
};
But neither SelectMany or GroupBy are supported in RavenDB. I've tried different combinations for a map-reduce solution, but I can't figure out how to do this since the map and the reduce differ in data-structure.
How to create a tag cloud is described in the knowledge base of RavenDB.
In your case, you have to include the BlogKey in the index, especially in the group by clause:
public class Tags_Count : AbstractIndexCreationTask<BlogPost, Tags_Count.ReduceResult>
{
public class ReduceResult
{
public string BlogKey { get; set; }
public string Name { get; set; }
public int Count { get; set; }
}
public Tags_Count()
{
Map = posts => from post in posts
from tag in post.Tags
select new {
BlogKey = post.BlogKey,
Name = tag.ToString().ToLower(),
Count = 1
};
Reduce = results => from tagCount in results
group tagCount by new {
tagCount.BlogKey,
tagCount.Name } into g
select new {
BlogKey = g.Key.BlogKey,
Name = g.Key.Name,
Count = g.Sum(x => x.Count)
};
Sort(result => result.Count, SortOptions.Int);
}
}
Then query that index with the desired BlogKey:
var result = session.Query<Tags_Count.ReduceResult, Tags_Count>()
.Where(x => x.BlogKey = myBlogKey)
.OrderByDescending(x => x.Count)
.ToArray();
If you need to query for multiple blogs, you can try this query:
var tagsByBlogs = session.Query<Tags_Count.ReduceResult, Tags_Count>()
.Where(x => x.BlogKey.In<string>(blogKeys))
.OrderByDescending(x => x.Count)
.ToArray();
AFAIK that is as far as you can get with an index. You still have to aggregate the results on the client side as you did in your original question, except that you can skip the filtering on blogKeys:
var tags = from tag in tagsByBlogs
group tag by Name into g
select new {
Tag = g.Key,
Count = g.Count(),
};
Take a look at faceted search, you can specify the criteria at query time, like so:
var facetResults = s.Query<BlogPost>("BlogIndex")
.Where(x => x.BlogKey == "1" || x.BlogKey == "5" ...)
.ToFacets("facets/BlogFacets");
Then the grouping (and counts) is done on all the results that match the where clause.
Update You'll need an index that looks something like this:
from post in blogPosts
from tag in post.Tags
select new
{
post.BlogKey
Tag = tag
}
This problem occurs in both NHibernate 2 and 3. I have a Class A that has a member set of class B. Querying the classes directly executes nicely. But when I pass one of the expressions involving class B into a method I get the following error:
System.ArgumentException: Object of type 'System.Linq.Expressions.ConstantExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.
As far as I can see I am passing the exact same expression into the Any() method. But for some reason they are treated differently. I have done some debugging and it looks like in the first method, the expression is treated as an expression with NodeType 'Quote', while the same expression in the 2nd method seems to be treated as an expression with NodeType 'Constant'. The parent expression of the expression in the 2nd method has a NodeType 'MemberAccess'. So it looks like the expression tree is different in the different test methods. I just don't understand why and what to do to fix this.
Classes involvend:
public class A
{
public virtual int Id { get; set; }
public virtual ISet<B> DataFields { get; set; }
}
public class B
{
public virtual int Id { get; set; }
}
Sample test code:
[TestMethod]
public void TestMethod1()
{
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where<A>(a => a.DataFields
.Any(b => b.Id == 1));
Console.Write("Number of records is {0}", records.Count());
}
}
[TestMethod]
public void TestMethod2()
{
GetAsWhereB(b => b.Id == 1);
}
private void GetAsWhereB(Func<B, bool> where)
{
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where(a => a.DataFields
.Any(where));
Console.Write("Number of records is {0}", records.Count());
}
}
This is one problem:
private void GetAsWhereB(Func<B, bool> where)
That's taking a delegate - you want an expression tree otherwise NHibernate can't get involved. Try this:
private void GetAsWhereB(Expression<Func<B, bool>> where)
As an aside, your query is hard to read because of your use of whitespace. I would suggest that instead of:
var records = session.Query<A>().Where<A>(a => a.DataFields.
Any(b => b.Id == 1));
you make it clear that the "Any" call is on DataFields:
var records = session.Query<A>().Where<A>(a => a.DataFields
.Any(b => b.Id == 1));
I'd also suggest that you change the parameter name from "where" to something like "whereExpression" or "predicate". Some sort of noun, anyway :)
Not quite sure if this is the proper solution or not. The problem feels like a bug and my solution like a workaround. Nonetheless the following works for me, which boils down to creating a 'copy' of the given expression by using its body and parameter to construct a new expression.
private void GetAsWhereB(Func<B, bool> where)
{
Expression<Func<T, bool>> w = Expression.Lambda<Func<T, bool>>(where.Body, where.Parameters);
using (ISession session = sessionFactory.OpenSession())
{
var records = session.Query<A>()
.Where(a => a.DataFields
.Any(w));
Console.Write("Number of records is {0}", records.Count());
}
}
Ok, last edit, promise:
With the following class:
public partial class MembershipModule : BaseConnection<MembershipModule>
{
private const string AccessPrivilege = "Access";
public bool Accessible()
{
return this.Privileges().Any(p => p.Name.Equals(AccessPrivilege));
}
public IQueryable<MembershipAction> Privileges()
{
var privileges = from p in LinqUtil.Context.MembershipModuleActions
where
p.MembershipModule.Id.Equals(this.Id) &&
p.MembershipRolePrivileges.Any(rp => rp.ModuleActionId.Equals(p.Id))
select p.MembershipAction;
return privileges;
}
}
Why does this work
public static List<MembershipModule> Collection()
{
List<MembershipModule> collection = new List<MembershipModule>();
if(!MembershipUser.Connected)
return collection;
foreach(MembershipModule m in LinqUtil.Context.MembershipModules)
{
if(m.Accessible())
collection.Add(m);
}
return collection;
}
While this doesn't?
public static List<MembershipModule> Collection()
{
if(!MembershipUser.Connected)
return new List<MembershipModule>();
return LinqUtil.Context.MembershipModules.Where(m => m.Accessible()).ToList();
}
looks to me like you are trying to execute a linq to object operation and it is trying to convert it to a sql statement.
Keep in mind: If this were to succede, I expect that the sql related to the folowing call would happen once per MembershipModule:
return this.Privileges().Any(p => p.Name.Equals(AccessPrivilege));
Have you tried skipping using the Accesible method, and hitting the Privileges collection directly? Something like this the code below. I have changed the return statement. This is the gist, may not be 100% correct:
public static List<MembershipModule> Collection()
{
if(!MembershipUser.Connected)
return new List<MembershipModule>();
var mod = MembershipModule.SearchById(new Guid("b012d35f-6af1-47de-9e54-e5df957c07e1"));
var y = from p in LinqUtil.Context.MembershipModuleActions
where
p.MembershipModule.Id.Equals(mod.Id) &&
p.MembershipRolePrivileges.Any(rp => rp.ModuleActionId.Equals(p.Id))
select p.MembershipAction;
var u = y.Any(p => p.Name.Equals(AccessPrivilege));
return LinqUtil.Context.MembershipModules.Where(m => m.Privileges().Any(p => p.Name.Equals(AccessPrivilege)).ToList();
}
Could it have to do with a static method (public static List<_membershipModule> Collection()) of a class trying access instance members (AccessPrivilege)?