I have an IEnumerable<RuleSelection> with these properties:
public class RuleSelection{
public int RuleId { get; set;}
public int? CriteriaId { get; set; }
public int? CriteriaSourceId{ get; set; }
}
RuleId in RuleSelection is not unique.
Can I write a linq query to normalize these into IEnumerable<Rule> which would be:
public class Rule{
public int RuleId { get; set; }
public IEnumerable<int> Criteria { get; set; }
public IEnumerable<int> CriteriaSource { get; set; }
}
Rule.RuleId would be unique and the properties Criteria and CriteriaSource would include all the CriteriaId's and CriteriaSourceId's for the RuleId respectively.
It sounds like you want something like:
var rules = selections.GroupBy(rs => rs.RuleId)
.Select(g => new Rule {
RuleId = g.Key,
Criteria = g.Select(rs => rs.CriteriaId)
.Where(c => c != null)
.Select(c => c.Value)
.ToList(),
CriteriaSource = g.Select(rs => rs.CriteriaSourceId)
.Where(c => c != null)
.Select(c => c.Value)
.ToList(),
});
Using my FullOuterGroupJoin extension method
LINQ - Full Outer Join
you could:
theRules.FullOuterGroupJoin(theRules,
r => r.RuleId,
r => r.RuleId,
(crit, critSource, id) => new Rule {
RuleId = id,
Criteria = crit
.Where(r => r.CriteriaId.HasValue)
.Select(r => r.CriteriaId.Value),
CriteriaSource = critSource
.Where(r => r.CriteriaSourceId.HasValue)
.Select(r => r.CriteriaSourceId.Value),
}
);
To write this:
var rules =
from sel in selections
group sel by sel.RuleId into rg
select new Rule {
RuleId = rg.Key,
Criteria = rg.Select(r => r.CriteriaId).FilterValues(),
CriteriaSource = rg.Select(r => r.CriteriaSourceId).FilterValues(),
};
I created the following FilterValues extension (to eliminate duplication):
public static IEnumerable<T> FilterValues<T>(
this IEnumerable<T?> items)
where T : struct
{
// omitting argument validation
return
from item in items
where item.HasValue
select item.Value;
}
I set out to provide essentially a pure query-syntax version of JonSkeet's answer. I gave up on that in effort to remove duplication for the property assignments and wound up with this combination extension & query-syntax approach.
Related
Given the following Models
public class ApiImageModel
{
public int ID { get; set; }
...
public List<TagModel> Tags { get; set; } = new();
}
and
public class TagModel
{
public int ID { get; set; }
...
public string Name { get; set; }
public List<ApiImageModel> Images { get; set; } = new();
}
How to query a list of ApiImageModel based on a given set of TagModels using Linq?
I am struggling with this for a while now and I'm certainly missing something basic but I can't put a pin on it.
I tried this approach for EF6:
EF6 How to query where children contains all values of a list
like so, holding all TagModel-IDs in an array "tagIDs":
int[] tagIDs;
...
IQueryable<ApiImageModel> images = context.Images.Where(image => tagIDs.All(id => image.Tags.Any(tag => tag.ID == id)));
But visual studio rewards me with an "InvalidOperationException":
The LINQ expression 'DbSet<ApiImageModel>()
.Where(a => __tagIDs_0
.All(id => DbSet<Dictionary<string, object>>("ApiImageModelTagModel")
.Where(a0 => EF.Property<Nullable<int>>(a, "ID") != null && object.Equals(
objA: (object)EF.Property<Nullable<int>>(a, "ID"),
objB: (object)EF.Property<Nullable<int>>(a0, "ImagesID")))
.Join(
inner: DbSet<TagModel>(),
outerKeySelector: a0 => EF.Property<Nullable<int>>(a0, "TagsID"),
innerKeySelector: t => EF.Property<Nullable<int>>(t, "ID"),
resultSelector: (a0, t) => new TransparentIdentifier<Dictionary<string, object>, TagModel>(
Outer = a0,
Inner = t
))
.Any(ti => ti.Inner.ID == id)))' could not be translated.
I'd be glad for some help :)
Assuming that your tags tagIDs are unique, you can do the following:
int[] tagIDs;
var tagCount = tagIDs.Length;
...
var images = context.Images
.Where(image => image.Tags.Where(tag => tagIDs.Contains(tag.ID)).Count() == tagCount);
Here we use Contains to grab tags in which we are interested and if their Count() is equal to tagIDs.Length - all tags are present in image's Tags relation.
I have an Entity Framework entity
public class Entiy
{
public string EntityProperty1 { get; set; }
public string EntityProperty2 { get; set; }
public string EntityProperty3 { get; set; }
public Guid? SomeId { get; set; }
}
And also an OtherObject and a DTO which contains properties that'd be equal to the other classes.
public class OtherObject
{
public string OtherObjectProperty1 { get; set; }
public string OtherObjectProperty2 { get; set; }
public string OtherObjectProperty3 { get; set; }
public Guid SomeId { get; set; }
}
public class DTO
{
public string EntityProperty1 { get; set; }
public string EntityProperty2 { get; set; }
public string EntityProperty3 { get; set; }
public string OtherObjectProperty1 { get; set; }
public string OtherObjectProperty2 { get; set; }
public string OtherObjectProperty3 { get; set; }
}
In my service class I get an IQueryable of my entities and I send in an argument which contains an enumerable of OtherObject. It works fine when I just want to create DTOs using the Entities properties.
public IEnumerable<DTO> GetDtos(IEnumerable<OtherObject> otherObjects)
{
return _someRepository.GetAll()
.Where(a => otherObjects.Select(b => b.SomeId).Contains(a.SomeId))
.Select(d => new DTO()
{
EntityProperty1 = d.EntityProperty1
EntityProperty2 = d.EntityProperty2
EntityProperty3 = d.EntityProperty3
})
.ToList();
}
However, I would also like to join in some properties from my OtherObject class. If I do the following
public IEnumerable<DTO> GetDtos(IEnumerable<OtherObject> otherObjects)
{
return _someRepository.GetAll()
.Where(a => otherObjects.Select(b => b.SomeId).Contains(a.SomeId))
.Select(d => new DTO()
{
EntityProperty1 = d.EntityProperty1
EntityProperty2 = d.EntityProperty2
EntityProperty3 = d.EntityProperty3
OtherObjectEntity1 = otherObjects.FirstOrDefault(a => a.SomeId == d.SomeId).OtherObjectEntity1
})
.ToList();
}
I get the following error: "Unable to create a constant value of type XXX. Only primitive types or enumeration types are supported in this context"
You can add a property from otherObject by adding another Select to the first query
public IEnumerable<DTO> GetDtos(IQueryable<OtherObject> otherObjects)
{
var ids = otherObjects.Select(x => x.SomeId);
return _someRepository.GetAll()
.Where(x => ids.Contains(x.SomeId)
.ToList()
.Select(x =>
new DTO()
{
EntityProperty1 = x.EntityProperty1,
EntityProperty2 = x.EntityProperty2,
EntityProperty3 = x.EntityProperty3,
OtherObjectEntity1 = otherObjects.FirstOrDefault(p => p.SomeId == x.SomeId).OtherObjectEntity1
};
}
Also I extracted otherObjects' ids into local variable to optimize the query a little bit.
Easiest way, is to do a ToList after the Where clause, then do DTO projection afterwards. Doing an early ToList would overselect all columns of the repository, which is bad for performance.
Another option, materialized the first three columns first..
var result =
someRepository.GetAll()
.Where(a => otherObjects.Select(b => b.SomeId).Contains(a.SomeId))
.Select(d => new DTO()
{
EntityProperty1 = d.EntityProperty1
EntityProperty2 = d.EntityProperty2
EntityProperty3 = d.EntityProperty3
}).
.ToList()
..then re-project the DTO:
result = result(d =>
new DTO() {
EntityProperty1 = d.EntityProperty1,
EntityProperty2 = d.EntityProperty2,
EntityProperty3 = d.EntityProperty3,
OtherObjectEntity1 = otherObjects.FirstOrDefault(a => a.SomeId == e.SomeId).OtherObjectEntity1
}
Yet another approach is to use IQueryable on IQueryable. That would generate subquery in SELECT statement.
public IEnumerable<DTO> GetDtos(IQueryable<OtherObject> otherObjects)
{
return _someRepository.GetAll()
.Where(a => otherObjects.Select(b => b.SomeId).Contains(a.SomeId))
.Select(d => new DTO()
{
EntityProperty1 = d.EntityProperty1
EntityProperty2 = d.EntityProperty2
EntityProperty3 = d.EntityProperty3,
OtherObjectEntity1 = otherObjects.FirstOrDefault(a => a.SomeId == e.SomeId).OtherObjectEntity1
})
.ToList();
}
Or use join (Milosz Wieczorek's answer), it's more efficient than subquery.
Did you try using Join instead od Where ?
It would look something like this:
_someRepository.GetAll()
.Join(otherObjects, a => a.SomeId, b => b.SomeId, (d, e) => new DTO
{
EntityProperty1 = d.EntityProperty1
EntityProperty2 = d.EntityProperty2
EntityProperty3 = d.EntityProperty3
OtherObjectEntity1 = e.OtherObjectEntity1
})
.ToList();
In my opinion it would be allso more efficient than your implementation.
EDIT
Thanks for the comments, I checked the query with database and it didn't work.
I decided to fix the query, if you would like to use Join :)
var ids = otherObjects.Select(i => i.Id);
// When
var results = repository.GetAll()
.Where(a => ids.Contains(a.Id))
.ToList()
.Join(otherObjects, a => a.Id, b => b.Id, (a, b) => new SimpleDto
{
Id = a.Id,
Name = a.Name,
Attribute = b.Attribute
})
.ToList();
How to write 'Where Any In' in LINQ to Entity?
Here is my model :
class Chair
{
public int Id { get; set; }
public int TableId { get; set; }
public Table Table { get; set; }
}
class Table
{
public int Id { get; set; }
public ICollection<Chair> Chairs { get; set; }
public ICollection<Category> Categories { get; set; }
public Table()
{
Chairs = new List<Chair>();
Categories = new List<Category>();
}
}
class Category
{
public int Id { get; set; }
public ICollection<Table> Tables { get; set; }
}
I also got a simple list of Category :
List<Category> myCategories = new List<Category>(c,d,e);
I want to get only that Chairs that belongs to Table that got one of the Category from myCategories List. Thats what im trying to do :
var result =
ctx.Chairs.Where(x => x.Table.Categories.Any(y => myCategories.Any(z => z.Id == y.Id))).ToList();
I think its ok but what i get is error :
"Unable to create a constant value of type 'ConsoleApplication1.Category'. Only primitive types or enumeration types are supported in this context"
Try to compare with in-memory categories Ids collection, instead of categories collection.
var myCategoriesIds = myCategories.Select(c => c.Id).ToArray();
var result =
context.Chairs
.Where(
x => x.Table.Categories.Any(
y => myCategoriesIds.Contains(y.Id)))
.ToList();
this is because ctx.Chairs is a collection that is in database, you should retrieve that collection first in order to compare it with in-memory data:
var result = ctx
.Chairs
.AsEnumerable() // retrieve data
.Where(x =>
x.Table.Categories.Any(y =>
myCategories.Any(z => z.Id == y.Id)))
.ToList();
EDIT: that wouldn't be the correct thing to do if you have a lot of entities on database, what you can do is to split it into two queries:
var tables = ctx.Tables
.Where(x =>
x.Categories.Any(y =>
myCategories.Any(z => z.Id == y.Id)));
var result = ctx.Chairs
.Where(x =>
tables.Any(t=> t.Id == x.TableId))
.ToList();
You can select Ids from myCategories and use it last statement.
var CategoryIds = myCategories.Select(ct => ct.Id);
var result = ctx.Chairs.Where(x => x.Table.Categories.Any(y => CategoryIds.Any(z => z == y.Id))).ToList();
I have object
public class OrderItem
{
public string idProduct { get; set; }
public int quantity { get; set; }
public List<WarehouseItem> WarehouseInfo = new List<WarehouseItem>();
}
public class WarehouseItem
{
public string Name{ get; set; }
public string LocnCode{ get; set; }
}
and i need select items which have WarehouseInfo.LocnCode == "A1"
It is doesnt work when I use something like
var items = itemList.Where(x => x.WarehouseInfo.Where(y => y.LocnCode.Equals("A1")));
Your requirements could be interpreted one of three ways, so here's three solutions:
Give me all OrderItems where ANY WarehouseItem has a LocnCode of "A1":
var items = itemList.Where(i => i.WarehouseInfo.Any(w => w.LocnCode == "A1"));
Give me all WarehouseItems within the OrderItems that have a LocnCode of "A1":
var items = itemList.SelectMany(i => i.WarehouseInfo)
.Where(w => w.LocnCode.Equals("A1"));
Give me all OrderItems where ANY WarehouseItem has a LocnCode of "A1", and filter WarehouseInfo to only those WarehouseItems:
This can't be done in a simple Linq query because there's no way to change the contents of the existing objects. You're going to have to create new objects with the filtered values:
var items = itemList.Where(i => i.WarehouseInfo.Any(w => w.LocnCode == "A1"))
.Select(i => new OrderItem
{
idProduct = i.idProduct,
quantity = i.quantity,
WarehouseInfo = i.WarehouseInfo.Where(w => w.LocnCode.Equals("A1"));
.ToList()
}
);
Try
var items = itemList.Where(x => x.WarehouseInfo.Any(y => y.LocnCode.Equals("A1")));
The Where takes a predicate that should return a bool. Any will return true if at least one item in the collection returns true for the given predicate.
I've got a Tag object:
public class Tag
{
public int TagID { get; set; }
public string Name { get; set; }
public virtual ICollection<Job> Jobs { get; set; }
public Tag()
{
Jobs = new HashSet<Job>();
}
}
and extended:
public class RecentTag : Tag
{
public int Count { get; set; }
}
...and I'm trying to retrieve a list of RecentTag objects with Count from the query added to each object:
public IEnumerable<RecentTag> GetRecentTags(int numberofdays)
{
var tags = Jobs
.Where(j => j.DatePosted > DateTime.Now.AddDays(-(numberofdays)))
.SelectMany(j => j.Tags)
.GroupBy(t => t, (k, g) => new
{
RecentTag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
// return RecentTags { TagID, Name, Count, Jobs }
}
So, how do I cast results of the query to RecentTag type and return the list of extended objects?
Any help would be appreciated. Thanks in advance!
if Jobs is actually a collection of Tags, and they are in fact RecentTag objects, then you can simply use the Cast method.
var rtags = tags.Cast<RecentTags>;
However, if Jobs is not a collection of tags, then you need to project into a RecentTags objects..
var rtags = tags.Select(x => new RecentTags() { // assign the members });
I ended up doing:
public IEnumerable<RecentTag> GetRecentTags(int numberofdays)
{
DateTime startdate = DateTime.Now.AddDays(-(numberofdays));
IEnumerable<RecentTag> tags = Jobs
.Where(j => j.DatePosted > startdate) // Can't use DateTime.Now.AddDays in Entity query apparently
.SelectMany(j => j.Tags)
.GroupBy(t => t, (k, g) => new RecentTag
{
TagID = k.TagID,
Name = k.Name,
Count = g.Count()
})
.OrderByDescending(g => g.Count)
.Select(a => a);
return tags;
}