-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Global query filter generates wrong sql command #19764
Comments
Is there any way to temporarily override this behavior until we have an official fix for this issue? |
@alirezanet there is no way currently to alter query filter behavior, without changing the model. You can get LEFT JOIN by making Also, can you provide full repro code? I tried to reverse-engineer the scenario based on the code you provided, ended up with this: [ConditionalFact]
public virtual void Test19764()
{
using var ctx = new Context19764();
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
var a1 = new Address19764 { Name = "address1" };
var a2 = new Address19764 { Name = "address2" };
var e2_1 = new MyEntity2_19764 { Address = a1 };
var e2_2 = new MyEntity2_19764 { Address = a2 };
var e1_1 = new MyEntity19764 { Reciever = e2_1, Sender = e2_2 };
ctx.Entities.Add(e1_1);
ctx.Entities2.AddRange(e2_1, e2_2);
ctx.Addresses.AddRange(a1, a2);
ctx.SaveChanges();
var query = ctx.Entities.ToList();
}
public class Context19764 : DbContext
{
public DbSet<MyEntity19764> Entities { get; set; }
public DbSet<MyEntity2_19764> Entities2 { get; set; }
public DbSet<Address19764> Addresses { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=.;Database=Repro19764;Trusted_Connection=True;MultipleActiveResultSets=True");
}
public bool _Skip_Condition = false;
public List<int> _ValidIDs = new List<int> { 1, 2, 3, 4 };
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity19764>().HasOne(e => e.Sender).WithOne().HasForeignKey<MyEntity19764>(e => e.SenderId).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MyEntity19764>().HasOne(e => e.Reciever).WithOne().HasForeignKey<MyEntity19764>(e => e.RecieverId).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MyEntity2_19764>().HasOne(e => e.Address).WithOne().HasForeignKey<Address19764>(a => a.MyEntity2_19764Id);
modelBuilder.Entity<MyEntity19764>().HasQueryFilter(q =>
(_Skip_Condition ||
(_ValidIDs.Contains(q.Reciever.Address.Id)) ||
(_ValidIDs.Contains(q.Sender.Address.Id))));
}
}
public class MyEntity19764
{
public int Id { get; set; }
public int RecieverId { get; set; }
public MyEntity2_19764 Reciever { get; set; }
public int SenderId { get; set; }
public MyEntity2_19764 Sender { get; set; }
}
public class MyEntity2_19764
{
public int Id { get; set; }
public Address19764 Address { get; set; }
}
public class Address19764
{
public int Id { get; set; }
public string Name { get; set; }
public int? MyEntity2_19764Id { get; set; }
} and I'm getting the following query (on Sql Server using current master branch) : SELECT [e].[Id], [e].[RecieverId], [e].[SenderId]
FROM [Entities] AS [e]
INNER JOIN [Entities2] AS [e0] ON [e].[RecieverId] = [e0].[Id]
LEFT JOIN [Addresses] AS [a] ON [e0].[Id] = [a].[MyEntity2_19764Id]
INNER JOIN [Entities2] AS [e1] ON [e].[SenderId] = [e1].[Id]
LEFT JOIN [Addresses] AS [a0] ON [e1].[Id] = [a0].[MyEntity2_19764Id]
WHERE ((@__ef_filter___Skip_Condition_0 = CAST(1 AS bit)) OR [a].[Id] IN (1, 2, 3, 4)) OR [a0].[Id] IN (1, 2, 3, 4) Which returns correct results. |
thank you @maumar. I tried to simplify the code in my example. my real code is : builder.Entity<Industry>().HasQueryFilter(q =>
(_uai.UserDataClaims._Skip_industry ||
(_uai.UserDataClaims.Industry_id.Contains(q.Id)) ||
(_uai.UserDataClaims.Industry_province.Contains(q.WorkshopAddress.ProvinceId)) ||
(_uai.UserDataClaims.Industry_state.Contains(q.WorkshopAddress.StateId)) ||
(_uai.UserDataClaims.Industry_isic2.Contains(q.IsicCodeId)) ||
(_uai.UserDataClaims.Industry_isic4.Contains(q.IsicCode.ParentId))) &&
_uai.UserClaims.Intersect(new string[] { "IndustryFull", "IndustryView", "god" }).Any()
);
builder.Entity<WasteTransfer>().HasQueryFilter(q =>
_uai.UserDataClaims._Skip_wastetransfer ||
(_uai.UserDataClaims.Wastetransfer_reciever_province.Contains(q.RecieverIndustry.WorkshopAddress.ProvinceId)) ||
(_uai.UserDataClaims.Wastetransfer_sender_province.Contains(q.SenderIndustry.WorkshopAddress.ProvinceId))
);
I don't know this is relevant or not but I think this is happening because I have some other query filters on my industry entity too (MyEntity2). in my real code return value is null only when the user doesn't have access to either SenderIndustry or ReceiverIndustry (because it creates two separate SELECT statements for inner joins). is this test pushed to the master? maybe I can help to reproduce the problem and update the test. |
can you update the test like this : public bool _Skip_Condition = true;
public bool _Skip_Condition2 = false;
public List<int> _ValidIDs = new List<int> { 1, 2, 3, 4 };
public List<int> _ValidIDs2 = new List<int> { 5, 6, 7, 8 };
public List<int> _ValidIDs3 = new List<int> { 9, 10, 11, 12 };
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity19764>().HasOne(e => e.Sender).WithOne().HasForeignKey<MyEntity19764>(e => e.SenderId).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MyEntity19764>().HasOne(e => e.Reciever).WithOne().HasForeignKey<MyEntity19764>(e => e.RecieverId).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MyEntity2_19764>().HasOne(e => e.Address).WithOne().HasForeignKey<Address19764>(a => a.MyEntity2_19764Id);
modelBuilder.Entity<MyEntity2_19764>().HasQueryFilter(q =>
(_Skip_Condition2 ||
(_ValidIDs2.Contains(q.Address.Id)) ||
(_ValidIDs.Contains(q.Address.Id))));
modelBuilder.Entity<MyEntity19764>().HasQueryFilter(q =>
(_Skip_Condition ||
(_ValidIDs.Contains(q.Reciever.Address.Id)) ||
(_ValidIDs3.Contains(q.Sender.Address.Id))));
} |
I'm able to reproduce this now |
@maumar - Post repro please. |
[ConditionalFact]
public virtual void Test19764()
{
using var ctx = new Context19764();
ctx.Database.EnsureDeleted();
ctx.Database.EnsureCreated();
var a1 = new Address19764 { Name = "address1" };
var a2 = new Address19764 { Name = "address2" };
var e2_1 = new MyEntity2_19764 { Address = a1 };
var e2_2 = new MyEntity2_19764 { Address = a2 };
var e1_1 = new MyEntity19764 { Reciever = e2_1, Sender = e2_2 };
ctx.Entities.Add(e1_1);
ctx.Entities2.AddRange(e2_1, e2_2);
ctx.Addresses.AddRange(a1, a2);
ctx.SaveChanges();
var query = ctx.Entities.ToList();
}
public class Context19764 : DbContext
{
public DbSet<MyEntity19764> Entities { get; set; }
public DbSet<MyEntity2_19764> Entities2 { get; set; }
public DbSet<Address19764> Addresses { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=.;Database=Repro19764;Trusted_Connection=True;MultipleActiveResultSets=True");
}
public bool _Skip_Condition = true;
public bool _Skip_Condition2 = false;
public List<int> _ValidIDs = new List<int> { 1, 2, 3, 4 };
public List<int> _ValidIDs2 = new List<int> { 5, 6, 7, 8 };
public List<int> _ValidIDs3 = new List<int> { 9, 10, 11, 12 };
//public bool _Skip_Condition = false;
//public List<int> _ValidIDs = new List<int> { 1, 2, 3, 4 };
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity19764>().HasOne(e => e.Sender).WithOne().HasForeignKey<MyEntity19764>(e => e.SenderId).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MyEntity19764>().HasOne(e => e.Reciever).WithOne().HasForeignKey<MyEntity19764>(e => e.RecieverId).OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<MyEntity2_19764>().HasOne(e => e.Address).WithOne().HasForeignKey<Address19764>(a => a.MyEntity2_19764Id);
modelBuilder.Entity<MyEntity2_19764>().HasQueryFilter(q =>
//_Skip_Condition2 ||
q.Address.Id == 3);
modelBuilder.Entity<MyEntity19764>().HasQueryFilter(q =>
_Skip_Condition ||
q.Reciever.Address.Id == 1);
}
}
public class MyEntity19764
{
public int Id { get; set; }
public int RecieverId { get; set; }
public MyEntity2_19764 Reciever { get; set; }
public int SenderId { get; set; }
public MyEntity2_19764 Sender { get; set; }
}
public class MyEntity2_19764
{
public int Id { get; set; }
public Address19764 Address { get; set; }
}
public class Address19764
{
public int Id { get; set; }
public string Name { get; set; }
public int? MyEntity2_19764Id { get; set; }
} |
per design discussion, added a model validation phase that warns against using required navigations where required side has the query filter and the optional side doesn't (tracked by #19801). Also improved docs to illustrate the issue, closing. |
global query filters generate the wrong SQL query when Entity has multiple references to another table.
Steps to reproduce with example
this global filter generate two seprate
INNER JOIN
and causing null result even when_Skip_Condition
isTure
(having multiple INNER JOIN here ignores OR conditions)the generated SQL is something like this
Exprected result
Further technical details
EF Core version: 3.1.1
Database provider: (e.g. Pomelo.EntityFrameworkCore.MySql)
Target framework: (e.g. .NET Core 3.1)
tip
this code was working correctly in previous versions (EF core 2.1).
UPDATE
I think this is happening because
MyEntity2
has some other query filters too. but anyway LEFT JOIN in this case can solve the problemThe text was updated successfully, but these errors were encountered: