Linq filtering by class array properties - c#

My schema is as
I am currently getting the products with valiants like so
products = context.Products
.Include(x => x.Skus)
.Include(x => x.ProductVariants)
.ThenInclude(pv => pv.Option)
.Include(x => x.ProductVariants)
.ThenInclude(pv => pv.Value);
Now i am trying to add a filter functionality by OptionId and ValueId
The following list holds both the OptionId and the ValueId for every option selected at the UI
List<Filter> filters;
where Filter is
public class Filter
{
public int Oid { get; set; } //OptionId
public int Vid { get; set; } //ValueId
}
How could i add filter functionality on this one?
After using
var v = context.Products.Include(x => x.ProductVariants)
.Where(prod => prod.ProductVariants
.Any(v => filters.Any(f => f.Oid == v.OptionId && f.Vid == v.ValueId)));
i got the error
The LINQ expression 'DbSet<ProductVariant>()
.Where(p0 => EF.Property<Nullable<int>>(EntityShaperExpression:
EntityType: Product
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
, "Id") != null && object.Equals(
objA: (object)EF.Property<Nullable<int>>(EntityShaperExpression:
EntityType: Product
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
, "Id"),
objB: (object)EF.Property<Nullable<int>>(p0, "ProductId")))
.Any(p0 => __filters_0
.Any(f => f.o == p0.OptionId && f.v == p0.ValueId))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
Update
While using
var vv = context.ProductVariants
.Where(v => filters.Any(f => f.Oid == v.OptionId && f.Vid == v.ValueId)).AsEnumerable();
The error is now
The LINQ expression 'DbSet<ProductVariant>()
.Where(p => __filters_0
.Any(f => f.Oid == p.OptionId && f.Vid == p.ValueId))' could not be translated.
Update
The error persists even with only filter by option
var vv = context.ProductVariants
.Where(v => filters.Any(f => f.Oid == v.OptionId)).AsEnumerable();
Update The classes used are
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public IEnumerable<ProductVariant> ProductVariants { get; set; }
public IEnumerable<Sku> Skus { get; set; }
}
public enum FilterType
{
CheckBox,
Radio,
Button,
List
}
public class Option
{
public int OptionId { get; set; }
public string OptionName { get; set; }
public FilterType FilterType { get; set; }
}
public class Value
{
public int ValueId { get; set; }
public string OptionValue { get; set; }
}
public class Sku
{
public int SkuId { get; set; }
public int ProductId { get; set; }
public decimal Price { get; set; }
[ForeignKey("ProductId")]
public Product Product { get; set; }
}
public class ProductVariant
{
public int Id { get; set; }
public int ProductId { get; set; }
public int OptionId { get; set; }
public int ValueId { get; set; }
public int SkuId { get; set; }
[ForeignKey("ProductId")]
public Product Product { get; set; }
[ForeignKey("OptionId")]
public Option Option { get; set; }
[ForeignKey("ValueId")]
public Value Value { get; set; }
[ForeignKey("SkuId")]
public Sku Sku { get; set; }
}
Update
I have narrowed the error to be related with the Filter class
By using
List<int> ints = new List<int>();
ints.Add(1);
and
var vv = context.ProductVariants
.Where(v => ints.Any(f => f == v.OptionId));
it just works. Should i use an expression or something else?
static Expression<Func<...

It'll likely end up looking like:
.Where(prod => prod.Variants.Any(v => filters.Any(f => f.Oid == v.OptionId && f.Vid == v.VariantId)))
If your ProductVariants entity has no Id (not sure how you set up your entities), you might need f.Oid == v.Option.Id && f.Vid == v.Variant.Id
Unless you actually need to select fields out of Sku, Option and Value they could be dropped from the query; realistically only product and product variant are needed for the filtering
Edit:
Looks like the ORM is struggling to turn the linq into SQL. Starting from product variant might help:
context.ProductVariants
.Where(v => filters.Any(f => f.Oid == v.OptionId && f.Vid == v.VariantId))
If that works out, add your product in

I wasn't able to replicate the problem with this code in a .net core winforms app and ef5:
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Linq;
using System.Windows.Forms;
namespace WFNetCoreCSWithEF5
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var f = new List<Filter>() { new Filter { Oid = 1, Vid = 2 } };
var x = new ProductDbContext().ProductVariant.Where(pc => f.Any(f => f.Oid == pc.OptionId && f.Vid == pc.ValueId));
}
}
public class Filter
{
public int Oid { get; set; } //OptionId
public int Vid { get; set; } //ValueId
}
public class Product
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public IEnumerable<ProductVariant> ProductVariants { get; set; }
//public IEnumerable<Sku> Skus { get; set; }
}
public enum FilterType
{
CheckBox,
Radio,
Button,
List
}
public class Option
{
public int OptionId { get; set; }
public string OptionName { get; set; }
public FilterType FilterType { get; set; }
}
public class Value
{
public int ValueId { get; set; }
public string OptionValue { get; set; }
}
public class Sku
{
public int SkuId { get; set; }
//public int ProductId { get; set; }
public decimal Price { get; set; }
//[ForeignKey("ProductId")]
//public Product Product { get; set; }
}
public class ProductVariant
{
public int Id { get; set; }
public int ProductId { get; set; }
public int OptionId { get; set; }
public int ValueId { get; set; }
public int SkuId { get; set; }
[ForeignKey("ProductId")]
public Product Product { get; set; }
[ForeignKey("OptionId")]
public Option Option { get; set; }
[ForeignKey("ValueId")]
public Value Value { get; set; }
[ForeignKey("SkuId")]
public Sku Sku { get; set; }
}
public class ProductDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<ProductVariant> ProductVariant { get; set; }
public DbSet<Sku> Skus { get; set; }
public DbSet<Option> Options { get; set; }
public DbSet<Value> Values { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlServer(#"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=c:\temp\new3.mdf;Integrated Security=True;Connect Timeout=30");
}
}
And these DOS commands:
dotnet ef migrations add X
dotnet ef database update
To create the db

Okay, ended up with PredicateBuilder
if (filters.Any())
{
var predicate = PredicateBuilder.False<Product>();
foreach (var filter in filters)
predicate = predicate.Or(p => p.ProductVariants.Any(x => x.OptionId == filter.o & x.ValueId == filter.v));
products = products.AsQueryable().Where(predicate);
}
products = products.AsQueryable()
.Include(x => x.ProductVariants)
.ThenInclude(pv => pv.Option)
.Include(x => x.ProductVariants)
.ThenInclude(x => x.Value);

Related

EF Core child ID doesn't match Database results

I have the following classes:
[Table("Artikel_Meldung", Schema = "BDB")]
public class Artikel_Meldung
{
[Key]
public int Artikel_MeldungId { get; set; }
public int Meldung_Id { get; set; }
public Meldung Meldung { get; set; }
public Artikel Artikel { get; set; }
public long ArtNr { get; set; }
[Column(TypeName = "nvarchar(2)")]
public string Ausfkz { get; set; }
}
[Table("Meldung", Schema = "BDB")]
public class Meldung
{
[Key]
public int Meldung_Id { get; set; }
public string Erfasser { get; set; }
public Artikel ErsatzArtikel { get; set; }
[NotMapped]
public Artikel Parent { get; set; }
public long ErsatzArtNr { get; set; }
[Column(TypeName = "nvarchar(2)")]
public string ErsatzAusfKz { get; set; }
public virtual ICollection<Artikel_Meldung> Artikel_Meldungen { get; set; }
public string Kommentar { get; set; }
public DateTime StartZeitpunkt { get; set; }
public DateTime EndeZeitpunkt { get; set; }
}
And retrieving the results with the following command:
query = srv_apps_db.Artikel.Where(x => x.Artikelbezeichnung.Contains(search_string) || x.ModbezKd.Contains(search_string))
.Include(art => art.Supplier)
.Include(art => art.Warengruppe)
.Include(a => a.Inventory)
.Include(art => art.Specials.Where(spe => spe.GueltigAb <= DateTime.Now.Date && spe.GueltigBis >= DateTime.Now.Date && (spe.FilialNr == Filiale || spe.FilialNr == 0)))
.Include(art => art.Orders.Where(ord => ord.PosNr.Substring(0, 1) != "5"))
.Include(art => art.Artikel_Bilder).ThenInclude(img => img.Bild).ThenInclude(s => s.ImgTyp)
.Include(art => art.Artikel_Meldungen).ThenInclude(x=>x.Meldung);
artikel = await query.AsSplitQuery()
.AsNoTracking()
.ToListAsync();
Everything works so far, but as I inspected the retrievd objects I noticed that Meldung.Meldung_Id is always 1 off the actual value in the Database.
Json-Result of my API-Call:
{
"artikel_MeldungId": 608,
"meldung_Id": 609,
"meldung": {
"meldung_Id": 609,
"erfasser": "NZI",
"ersatzArtikel": {
"artNr": 10080405,
"ausfKz": "02",
....
}
}
The values are correct but the Id doesn't match is this a bug or an error in my code or expected behaviour on EF?
Thanks in advance
regards Tekkion

EF core grouping not working with related model

I'm newbie at entity framework. I can manage to create tables and working CRUD ops.
My models are below
public class Break
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("duration")]
public int Duration { get; set; }
[JsonProperty("countable")]
public bool Countable { get; set; }
}
public class BreakState
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("level")]
public int Level { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("isSupervisor")]
public bool IsSupervisor { get; set; }
[JsonProperty("isActive")]
public bool IsActive { get; set; }
}
public class BreakRequest : BaseModel
{
[JsonProperty("dn")]
public string DN { get; set; }
[JsonProperty("agent")]
public string Agent { get; set; }
[JsonProperty("break")]
public Break Break { get; set; }
[JsonProperty("steps")]
public List<BreakStep> Steps { get; set; }
}
public class BreakStep : BaseModel
{
[JsonProperty("state")]
public BreakState BreakState { get; set; }
[JsonProperty("note")]
public string Note { get; set; }
[JsonProperty("request_id")]
public int BreakRequestId { get; set; }
[JsonIgnore]
public BreakRequest BreakRequest { get; set; }
}
Now I need to get complex data from context.
public int GetUsedBreak(string agent) {
int sum_breaks_used = 0;
var shift = GetShift(agent);
if (shift != null) {
using (var dbBreak = new BreakTimeDbContext())
{
var breaks_countable = dbBreak.BreakRequests
.Where(x => x.DN == agent && x.Break.Countable && x.Steps.Count == 4 && x.CreatedAt > shift.Start)
.Select(x => x.Id).ToArray();
if (breaks_countable.Length > 0) {
sum_breaks_used = dbBreak.BreakSteps
.Include(i => i.BreakState)
.Where(w => breaks_countable.Contains(w.BreakRequestId))
.GroupBy(x => x.BreakRequestId)
.Select(g => new {
BreakId = g.Key,
Level3Time = g.Where(w => w.BreakState.Level == 3).First().CreatedAt,
Level4Time = g.Where(w => w.BreakState.Level == 4).First().CreatedAt
})
.Sum(x => ( x.Level4Time - x.Level3Time ).Minutes);
}
}
}
return sum_breaks_used;
}
with this function, I try to get sum of date diffrences.
But I get the error below
An unhandled exception has occurred while executing the request.
System.InvalidOperationException: The LINQ expression 'GroupByShaperExpression:
KeySelector: b.BreakRequestId,
ElementSelector:EntityShaperExpression:
EntityType: BreakStep
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
.Where(w => w.BreakState.Level == 3)' could not be translated. Either rewrite the query in a form that can be translated,
or switch to client evaluation explicitly by inserting a call to
'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See
https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
I need only level 4 and level 3 differences.
How can I achieve it with linq?
NOTE: title of the question may not be clear, can change it

Entity Framework get SUM from child property

I have the following model where I'd like to get the sum of all OrderTotalItems for all Orders of a Customer where the OrderTotalType (Enumeration) is "total" or 99:
public class Customer
{
...
public ICollection<Order> Orders { get; set; } = new Collection<Order>();
}
public class Order
{
...
public ICollection<OrderTotalItem> OrderTotalItems { get; set; } = new Collection<OrderTotalItem>();
}
public class OrderTotalItem
{
[Required]
public int Id { get; set; }
[Required]
[Column(TypeName = "decimal(10, 4)")]
public decimal Value { get; set; }
[Required]
public OrderTotalType Type { get; set; }
}
I am building a CustomerAdminDTO to include all relevant data of a customer for the admin client:
public class CustomerAdminDto
{
public int Id { get; set; }
public string UserId { get; set; }
public Gender Gender { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string VATId { get; set; } = "";
public bool VATIdValid { get; set; } = false;
public DateTime Added { get; set; }
public DateTime LastModified { get; set; }
public decimal OrdersTotal { get; set; }
public CustomerStatusShortDto CustomerStatus { get; set; }
public CustomerAddressDto CustomerAddress { get; set; }
public CustomerAddressDto BillingAddress { get; set; }
public ICollection<OrderListShortDto> Orders { get; set; }
}
In my data service I fill the DTO like that
var customerAdmin = await _context.Customers
.Include(x => x.Addresses)
.Include(x => x.CustomerStatus)
.Include(x => x.Orders)
.ThenInclude(x => x.OrderTotalItems)
.Where(x => x.UserId == userid)
.Select(customer => new CustomerAdminDto
{
Id = customer.Id,
UserId = customer.UserId,
Gender = customer.Gender,
FirstName = customer.FirstName,
LastName = customer.LastName,
VATId = customer.VATId,
VATIdValid = customer.VATIdValid,
Added = customer.Added,
LastModified = customer.LastModified,
OrdersTotal = customer.Orders.Sum(x => x.OrderTotalItems
.Where(x => x.Type == Enums.OrderTotalType.Total)
.Sum(x => x.Value)),
CustomerStatus = new CustomerStatusShortDto
{
Id = customer.CustomerStatus.Id,
Name = customer.CustomerStatus.Name,
},
...
}
.FirstOrDefaultAsync();
where everything works, except the OrdersTotal.
API compiles fine but throws the following error at runtime:
Microsoft.Data.SqlClient.SqlException (0x80131904): Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
Thanks for your hints!
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
This error in SQL server means that you tried to call aggregation function (customer.Orders.Sum() in your case) on other expression that contains aggregation function (.Sum(x => x.Value) in your case). In order to avoid this you can simplify your LINQ expression for OrdersTotal:
OrdersTotal = customer.Orders.SelectMany(o => o.OrderTotalItems).Where(x => x.Type == Enums.OrderTotalType.Total).Sum(x => x.Value)
There is only one aggregation here so it should work fine

EF include list is always null

By some reason EF wont load the included list properly so it ends up being null all the time.
Here is the entities i'm using:
[Table("searchprofilepush")]
public class SearchProfilePush
{
public int Id { get; set; }
public int AccountId { get; set; }
public bool Push { get; set; }
public int UserPushId { get; set; }
public UserPush UserPush { get; set; }
public int SearchProfileId { get; set; }
public SearchProfile SearchProfile { get; set; }
public ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
}
[Table("searchprofilemediatypepush")]
public class SearchProfileMediaTypePush
{
public int Id { get; set; }
public MediaTypeType MediaType { get; set; }
public bool Push { get; set; }
public int SearchProfilePushId { get; set; }
public SearchProfilePush SearchProfilePush { get; set; }
}
Then when i'm trying to do this:
var searchProfilePush = _dataContext.SearchProfilePush.Include(w => w.SearchProfileMediaTypePush).FirstOrDefault(w => w.AccountId == accountId && w.SearchProfileId == searchProfileId);
My included list is always null.
I guess it's some obvious reason why this doesn't work but i just can't figure it out.
Thanks!
EDIT:
Here is the sql query:
SELECT \"Extent1\".\"id\", \"Extent1\".\"accountid\", \"Extent1\".\"push\", \"Extent1\".\"userpushid\", \"Extent1\".\"searchprofileid\" FROM \"public\".\"searchprofilepush\" AS \"Extent1\" WHERE \"Extent1\".\"accountid\" = #p__linq__0 AND #p__linq__0 IS NOT NULL AND (\"Extent1\".\"searchprofileid\" = #p__linq__1 AND #p__linq__1 IS NOT NULL) LIMIT 1
EDIT 2:
I have now mapped my entities both way and the list is still always null.
Edit 3:
This is how i created my database tables.
The documentation I read for loading related entities has some differences with the sample code and your code. https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
First, when you define your ICollection, there is no keyword virtual:
public virtual ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
Next, in the example close to yours, where they load related items using a query, the first or default is not using a boolean expression. The selective expression is in a where clause:
// Load one blogs and its related posts
var blog1 = context.Blogs
.Where(b => b.Name == "ADO.NET Blog")
.Include(b => b.Posts)
.FirstOrDefault();
So you can try:
var searchProfilePush = _dataContext.SearchProfilePush
.Where(w => w.AccountId == accountId && w.SearchProfileId == searchProfileId)
.Include(w => w.SearchProfileMediaTypePush)
.FirstOrDefault();
Can you make these two changes and try again?
A few things will be an issue here. You have no keys defined or FKs for the relationship:
[Table("searchprofilepush")]
public class SearchProfilePush
{
[Key]
public int Id { get; set; }
public int AccountId { get; set; }
public bool Push { get; set; }
public int UserPushId { get; set; }
public UserPush UserPush { get; set; }
public int SearchProfileId { get; set; }
public SearchProfile SearchProfile { get; set; }
public ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
}
[Table("searchprofilemediatypepush")]
public class SearchProfileMediaTypePush
{
[Key]
public int Id { get; set; }
public MediaTypeType MediaType { get; set; }
public bool Push { get; set; }
public int SearchProfilePushId { get; set; }
[ForeignKey("SearchProfilePushId")]
public SearchProfilePush SearchProfilePush { get; set; }
}
Personally I prefer to explicitly map out the relationships using EntityTypeConfiguration classes, but alternatively they can be set up in the Context's OnModelCreating. As a starting point have a look at http://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx for basic EF relationship configuration.
for a SearchProfilePush configuration:
modelBuilder.Entity<SearchProfilePush>()
.HasMany(x => x.SearchProfileMediaTypePush)
.WithRequired(x => x.SearchProfilePush)
.HasForeignKey(x => x.SearchProfilePushId);

How to write below mysql query using Entity Framework with Lambda expression?

SELECT
`Distinct1`.`UserId`,
`Distinct1`.`FirstName`,
`Distinct1`.`LastName`,
`Distinct1`.`EmailAddress`,
FROM ( SELECT DISTINCT
`Extent2`.`UserId`,
`Extent2`.`FirstName`,
`Extent2`.`LastName`,
`Extent2`.`EmailAddress`,
FROM `AssistantTo` AS `Extent1`
INNER JOIN `User` AS `Extent2` ON `Extent1`.`AssistantId` = `Extent2`.`UserId`
INNER JOIN `CustomTagUser` as `Extent3` ON `Extent3`.`UserId` = `Extent2`.`UserId`
WHERE `Extent1`.`OwnerId` = 274 AND `Extent3`.`CustomTagId` = 114
) AS `Distinct1`
This is my Table structure :
I tried using following query but it is giving me error.
var assistants =
dbContext.AssistantsTo
.Include(x => x.Assistant)
.Include(x => x.Assistant.CustomTagUser)
.Where(at =>
at.OwnerId == currentUser.UserId
&& (at.Assistant.CustomTagUser.Count(y => y.CustomTagId == filter) > 0)
)
.Select(at => at.Assistant)
.Distinct()
.ToList();
Error : {"Unknown column 'Extent1.AssistantId' in 'where clause'"}
Basically I have issue with giving filter for
`Extent3`.`CustomTagId` = 114
I think we can use any() but I have bad experience using any() with large data & mysql.
Model classes
public class AssistantTo
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int AssistantToId { get; set; }
public int AssistantId { get; set; }
[ForeignKey("AssistantId")]
public virtual User Assistant { get; set; }
public int OwnerId { get; set; }
[ForeignKey("OwnerId")]
public virtual User Owner { get; set; }
}
public class CustomTagUser
{
[Key]
public int CustomTagUserId { get; set; }
public int CustomTagId { get; set; }
public int UserId { get; set; }
public DateTime CreatedOn { get; set; }
[ForeignKey("CustomTagId")]
public virtual CustomTags CustomTags { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
}
[Table("User")]
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
.....
public ICollection<CustomTagUser> CustomTagUser { get; set; }
}
}
Basically I have main problem with applying this part in EF
AND Extent3.CustomTagId = 114
var assistants =
dbContext.AssistantsTo
.Include(x => x.Assistant)
.Include(x => x.Assistant.CustomTagUser)
.Where(at =>
at.OwnerId == currentUser.UserId &&
(
at.Assistant.CustomTagUser.Select(x => x.CustomTagId).Contains(filter)
)
.Select(at => at.Assistant)
.Distinct()
.ToList();
try this one...

Categories

Resources