Can null check cause client side evaluation? - c#

I'd want to ask why query like this is being evaluated on the client side:
_context
.Items
.Include(x => x.Status)
.Include(x => x.Orders)
.ThenInclude(x => x.User)
.Include(x => x.Orders)
.ThenInclude(x => x.OrderStatus)
.Where(x => x.Orders.Any())
.Where(x => x.Order != null)
.Where(x => x.Order.User.SomeProperty.ToLower() == user.SomeProperty.ToLower());
where user used in user.SomeProperty.ToLower() is just Identity user and it isn't null.
public class Item
{
public Guid Id = { get; protected set; }
public List<Order> Orders { get; set; } = new List<Order>();
public Order Order => Orders.FirstOrDefault(x => x.OrderStatus.Name = "Active");
public Status Status { get; set; }
}
public class Order
{
public Guid Id = { get; protected set; }
public User User = { get; set; }
public Status OrderStatus = { get; set; }
}
public class Status
{
public Guid Id = { get; protected set; }
public string Name = { get; set; }
}
EF Core warnings say that null check is one of the reasons, but I cannot understand why would null check cannot be translated
warn: Microsoft.EntityFrameworkCore.Query[20500]
The LINQ expression 'where (Property([x].Order, "Id") != null)' could not be translated and will be evaluated locally.
warn: Microsoft.EntityFrameworkCore.Query[20500]
The LINQ expression 'where ([x].Order.User.SomeProperty==__user_SomeProperty_0)' could not be translated and will be evaluated locally.

EF can not translate query in methods or properties. Move this
public Order Order => Orders.FirstOrDefault(x => x.OrderStatus.Name = "Active");
To the actual query
edit. You can use extension methods to reuse queries instead
edit2: Create an extension method
public static class FooQueryExtensions
{
public static IQueryable<FooResult> MyFooQuery(this IQueryable<Foo> source)
{
return source.SelectMany(...).Where(...); //basicly do what yuo want
}
}
used like
_context.Set<Foo>().MyFooQuery().Where(result => more query);

Related

EF Core query not reflecting updated property without saving

With this code snippet, I query for an entity in my SQL database, and then update a property on that entity.
This query should filter out that entity because I just modified it, but the first one does not. For some reason, the second query does return what I expect.
In my scenario, the ProductionOrderStep entity is a one-to-many relationship to the ProductionOrder entity.
I do not want to add a call to SaveChanges.
Is this a bug in EF Core? Or is this intended behaviour?
[HttpGet("test")]
public void Test()
{
var productionOrderStepId = 356664;
var productionOrderId = 305712;
var pos = context.ProductionOrderStep
.Where(x => x.ProductionOrderStepId == productionOrderStepId)
.FirstOrDefault();
pos.Status = 2; // Changes status from 1 to 2
var incorrectResult = context.ProductionOrder
.Where(x => x.ProductionOrderId == productionOrderId)
.SelectMany(x => x.ProductionOrderSteps)
.Where(pos => pos.Status < 2)
.ToList();
var correctResult = context.ProductionOrder
.Where(x => x.ProductionOrderId == productionOrderId)
.SelectMany(x => x.ProductionOrderSteps)
.ToList()
.Where(pos => pos.Status < 2)
.ToList();
}
public class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options): base(options)
{}
public virtual DbSet<ProductionOrder> ProductionOrder { get; set; }
public virtual DbSet<ProductionOrderStep> ProductionOrderStep { get; set; }
}
public class ProductionOrderStep
{
public int ProductionOrderStepId { get; set; }
public int ProductionOrderId { get; set; }
public int Status { get; set; }
public virtual ProductionOrder ProductionOrder { get; set; }
}
public class ProductionOrderStepConfiguration : IEntityTypeConfiguration<ProductionOrderStep>
{
public void Configure(EntityTypeBuilder<ProductionOrderStep> builder)
{
builder.HasOne(d => d.ProductionOrder)
.WithMany(p => p.ProductionOrderSteps)
.HasForeignKey(d => d.ProductionOrderId);
}
}
public class ProductionOrder
{
public int ProductionOrderId { get; set; }
public virtual ICollection<ProductionOrderStep> ProductionOrderSteps { get; set; }
}
It is intended behaviour. A basic breakdown of the behaviour you are seeing:
var incorrectResult = context.ProductionOrder
.Where(x => x.ProductionOrderId == productionOrderId)
.SelectMany(x => x.ProductionOrderSteps)
.Where(pos => pos.Status < 2)
.ToList();
This builds an SQL query that will attempt to load any ProductionOrderSteps for a given ProductionOrder where their Status < 2. That WHERE execution step goes to SQL. Since you have updated an entity and not committed the change to the database, that query will not know about your change so EF will not return your expected row.
var correctResult = context.ProductionOrder
.Where(x => x.ProductionOrderId == productionOrderId)
.SelectMany(x => x.ProductionOrderSteps)
.ToList()
.Where(pos => pos.Status < 2)
.ToList();
In this case, you are telling EF to run a query to load all Production Order Steps for a given Production Order. Since your updated entity is already tracked, EF will return that updated reference along with the other Production Order Steps that it might load from the DB. THe Where condition is done in-memory so the Status will reflect your updated change.

Filter on nested object in LINQ

I have the following query in LINQ to return a policy object with a list of its policy risks, and within the policy risk a list of its transaction movements.
corePolicy = _db.Policies
.OrderByDescending(p => p.BatchId)
.Include(policy => policy.PolicyRisks.Select(policyRisks =>
policyRisks.TransactionMovements))
.Include(policy => policy.PolicyRisks.Select(policyRisks =>
policyRisks.PolicyRiskClaims.Select(prc =>
prc.TransactionMovements)))
.AsNoTracking()
.First(p =>
p.PolicyReference == policyFromStaging.PolicyReference &&
p.PolicySourceSystemId == policyFromStaging.PolicySourceSystemId);
How do I apply a .Where() clause to the transaction movements, say TransactionReference == 'ABC'
Below is my data model
[Table("business.Policy")]
public class Policy
{
[Required, StringLength(50)]
public string PolicyReference { get; set; }
public int PolicySourceSystemId { get; set; }
public System PolicySourceSystem { get; set; }
}
[Table("business.PolicyRisk")]
public class PolicyRisk
{
public ICollection<TransactionMovement> TransactionMovements { get; set; }
}
[Table("business.TransactionMovement")]
public class TransactionMovements
{
public string TransactionReference { get; set; }
}
Having Where statement in Include is not possible. You can have Any something like below:
corePolicy = _db.Policies
.OrderByDescending(p => p.BatchId)
.Include(policy => policy.PolicyRisks.Select(policyRisks =>
policyRisks.TransactionMovements))
.Include(policy => policy.PolicyRisks.Select(policyRisks =>
policyRisks.PolicyRiskClaims.Select(prc =>
prc.TransactionMovements)))
.AsNoTracking()
.Where(p => p.PolicyRisks.Any(pr => pr.PolicyRiskClaims.Any(prc => prc.TransactionMovements.Any(tm => tm.TransactionReference == 'ABC'))))
.First(p =>
p.PolicyReference == policyFromStaging.PolicyReference &&
p.PolicySourceSystemId == policyFromStaging.PolicySourceSystemId);
This will include policies if any of the transaction reference is ABC (on its child collections). So, again you have to have a looping mechanism in c# side to get the policy which have this TransactionReference.
If you want to avoid having loop, then I would suggest writing your linq from other end meaning, start from TransactionMovement and go back to Policy.
You can have WHERE clause in Include filter with some third party library as mentioned here.

Query over many-to-many entity with nhibernate

I kinda need a brainstorm here..
Here's my scenario:
public class UserSystem
{
public virtual User User { get; set; }
public virtual System System { get; set; }
}
public class User
{
public User() { }
public virtual int UsrId { get; set; }
}
public class System
{
public System() { }
public virtual decimal SistId { get; set; }
public virtual IList<Perf> SystPerf { get; set; }
}
public class Perf
{
public Perf() { }
public virtual int PerfId { get; set; }
public virtual System System { get; set; }
public virtual string Perf_Adm_Portal { get; set; }
}
I need to get all the users that has Perf_Adm_Portal == "S". I know its kinda simple but I am doing something wrongly...
I tryed this:
var list = session.Query<UserSystem>()
.Fetch(x => x.User)
.Fetch(x => x.System)
.ThenFetch(x => x.SystPerf)
.Where(x => x.System.SistId == someId)
//.Where(x => x.Sistema.SystPerf.Where(x => x.Perf_Adm_Portal == "S"))
.ToList<USerSystem>();
that commented line its just what I want but it doesn't work... doesn't even compile. So this query returns me all users (including those who has the flag Perf_Adm_Portal != "S") and then I just treat them in memory... but its taking so long to execute this query and I know that there is a better solution... can you guys help me? Regards
**Edit
Nevermind, guys... I just realize that I have a third table (UserPerf).
And each perf has its own System.
So I retrieve all admins of a system like this:
var list = session.Query<UserPerf>()
.Where(up => up.Perf.Perf_Adm_Portal.ToLower().Equals("yes"))
.Where(up => up.Perf.System.SistId == sistId)
.Select(up => up.User)
.ToList<User>();
Sorry for the trouble...
#Radim Köhler, thanks for your time! But I think what I was trying to do it's impossible without that third table below.
Regards,
I would say, that this kind of query should work for you:
var inner = session.Query<UserSystem>()
.Where(e => e.System.SystPerf.Any(p => p.Perf_Adm_Portal == "S"))
.Select(e => e.User.UserId);
var query = session.Query<User>()
.Where(i => inner.Contains(u.userId));
var list = query
// .Skip(y).Take(x) // paging
.ToList();

Fluent NHibernate "Could not resolve property"

I have read a lot of the questions about that same error but none since to match my exact problem. I'm trying to access the property of an object, itself part of a root object, using Fluent NHibernate. Some answers say I need to use projections, others that I need to use join, and I think it should work through lazy loading.
Here are my two classes along with the Fluent mappings:
Artist class
public class Artist
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Album> Albums { get; set; }
public virtual string MusicBrainzId { get; set; }
public virtual string TheAudioDbId { get; set; }
public Artist() { }
}
public class ArtistMap : ClassMap<Artist>
{
public ArtistMap()
{
LazyLoad();
Id(a => a.Id);
Map(a => a.Name).Index("Name");
HasMany(a => a.Albums)
.Cascade.All();
Map(a => a.MusicBrainzId);
Map(a => a.TheAudioDbId);
}
}
Album class
public class Album
{
public virtual int Id { get; set; }
public virtual Artist Artist { get; set; }
public virtual string Name { get; set; }
public virtual IList<Track> Tracks { get; set; }
public virtual DateTime ReleaseDate { get; set; }
public virtual string TheAudioDbId { get; set; }
public virtual string MusicBrainzId { get; set; }
public Album() { }
}
public class AlbumMap : ClassMap<Album>
{
public AlbumMap()
{
LazyLoad();
Id(a => a.Id);
References(a => a.Artist)
.Cascade.All();
Map(a => a.Name).Index("Name");
HasMany(a => a.Tracks)
.Cascade.All();
Map(a => a.ReleaseDate);
Map(a => a.TheAudioDbId);
Map(a => a.MusicBrainzId);
}
}
And the error happens when this code is interpreted:
var riAlbum = session.QueryOver<Album>()
.Where(x => x.Name == albumName && x.Artist.Name == artist)
.List().FirstOrDefault();
The error happens when Fluent NHibernate tries to resolve the x.Artist.Name value:
{"could not resolve property: Artist.Name of: Album"}
What would be the correct way of doing this?
You have to think of your QueryOver query as (nearly) directly translating into SQL. With this in mind, imagine this SQL query:
select
Album.*
from
Album
where
Album.Name = 'SomeAlbumName' and
Album.Artist.Name = 'SomeArtistName'
This won't work because you can't access a related table's properties like that in a SQL statement. You need to create a join from Album to Artist and then use a Where clause:
var riAlbum =
session.QueryOver<Album>()
.Where(al => al.Name == albumName)
.JoinQueryOver(al => al.Artist)
.Where(ar => ar.Name == artistName)
.List()
.FirstOrDefault();
Also, since you're using FirstOrDefault, you may want to consider moving that logic to the database end. Currently, you're pulling back every record matching your criteria and then taking the first one. You could use .Take to limit the query to 1 result:
var riAlbum =
session.QueryOver<Album>()
.Where(al => al.Name == albumName)
.JoinQueryOver(al => al.Artist)
.Where(ar => ar.Name == artistName)
.Take(1)
.SingleOrDefault<Album>();
Another explanation is that you are missing your mapping of this property or field in a NHibernateClassMapping definition. I came here about why I was getting this error based on the following scenario.
var query = scheduleRepository.CurrentSession().Query<Schedule>()
.Where(x => x.ScheduleInfo.StartDate.Date < dateOfRun.Date);
This was giving me a Could Not Resolve Property error for StartDate. This was a head scratcher, since I use this syntax all the time.
My mapping file was the following:
public class ScheduleInfoMapping : NHibernateClassMapping<ScheduleInfo>
{
public ScheduleInfoMapping()
{
DiscriminateSubClassesOnColumn("Type");
Map(x => x.Detail).MapAsLongText();
}
}
which was missing the StartDate. Changed to:
public class ScheduleInfoMapping : NHibernateClassMapping<ScheduleInfo>
{
public ScheduleInfoMapping()
{
DiscriminateSubClassesOnColumn("Type");
Map(x => x.Detail).MapAsLongText();
Map(x => x.StartDate);
}
}
Which resolved the error.

Automapper Project.To breaks db IQueryable

I have entities:
[Table("Representatives")]
public class Representative
{
[Column("RepID"), Key]
public int Id { get; set; }
[Column("MemID")]
public int MemberId { get; set; }
public virtual Member Member { get; set; }
}
[Table("Members")]
public class Member
{
[Column("MemID"), Key]
public int Id { get; set; }
[ForeignKey("MemberId")]
public virtual ICollection<Representative> Representatives { get; set; }
}
where Representatives relate to Member as many to one. I want to retrieve some representatives with their parent members and project it all to some view model. I've configured automapper like this:
Mapper.CreateMap<Representative, RepresentativeViewModel>()
.ForMember(m => m.RepId, a => a.MapFrom(x => x.Id))
.ForMember(m => m.MemberModel, a => a.MapFrom(x => x.Member));
Mapper.CreateMap<Member, MemberViewModel>()
.ForMember(m => m.MemId, a => a.MapFrom(x => x.Id))
.ForMember(m => m.OrganizationName, a => a.MapFrom(x => x.Name));
If I run this code:
var prj = ctx.Representatives.Where(r => r.Id == 1).Project().To<RepresentativeViewModel>();
then I get expression tree that contains func invocation (debugged):
...
.Lambda #Lambda2<System.Func`2[ConsoleApplication1.Representative,ConsoleApplication1.RepresentativeViewModel]>(ConsoleApplication1.Representative $var1)
{
.New ConsoleApplication1.RepresentativeViewModel(){
Name = $var1.Name,
RepId = $var1.Id,
MemberModel = .Invoke (.Lambda #Lambda3<System.Func`2[ConsoleApplication1.Member,ConsoleApplication1.MemberViewModel]>)($var1.Member)
}
}
.Lambda #Lambda3<System.Func`2[ConsoleApplication1.Member,ConsoleApplication1.MemberViewModel]>(ConsoleApplication1.Member $var2)
{
.New ConsoleApplication1.MemberViewModel(){
MemId = $var2.Id,
OrganizationName = $var2.Name
}
}
...
so for some reason builder invokes Func instead of calling Expression. Do you have ideas why it's so? How can I make my mapper build correct expression and SQL query?

Categories

Resources