Skip to content
This repository was archived by the owner on Feb 1, 2025. It is now read-only.

Commit 4529f83

Browse files
committed
Back-port changes for #117.
1 parent 2058c11 commit 4529f83

File tree

6 files changed

+142
-21
lines changed

6 files changed

+142
-21
lines changed

Source/LinqToDB.EntityFrameworkCore/EFCoreMetadataReader.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -191,25 +191,28 @@ public T[] GetAttributes<T>(Type type, MemberInfo memberInfo, bool inherit = tru
191191
var fk = navigation.ForeignKey;
192192
if (!navigation.IsDependentToPrincipal())
193193
{
194-
var thisKey = string.Join(",", fk.PrincipalKey.Properties.Select(p => p.Name));
194+
// Could not track when EF decides to do INNER JOIN
195+
var canBeNull = true;
196+
197+
var thisKey = string.Join(",", fk.PrincipalKey.Properties.Select(p => p.Name));
195198
var otherKey = string.Join(",", fk.Properties.Select(p => p.Name));
196199
associations.Add(new AssociationAttribute
197200
{
198-
ThisKey = thisKey,
199-
OtherKey = otherKey,
200-
CanBeNull = !fk.IsRequired,
201+
ThisKey = thisKey,
202+
OtherKey = otherKey,
203+
CanBeNull = canBeNull,
201204
IsBackReference = false
202205
});
203206
}
204207
else
205208
{
206-
var thisKey = string.Join(",", fk.Properties.Select(p => p.Name));
209+
var thisKey = string.Join(",", fk.Properties.Select(p => p.Name));
207210
var otherKey = string.Join(",", fk.PrincipalKey.Properties.Select(p => p.Name));
208211
associations.Add(new AssociationAttribute
209212
{
210-
ThisKey = thisKey,
211-
OtherKey = otherKey,
212-
CanBeNull = !fk.IsRequired,
213+
ThisKey = thisKey,
214+
OtherKey = otherKey,
215+
CanBeNull = !fk.IsRequired,
213216
IsBackReference = true
214217
});
215218
}

Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/IssueTests.cs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
using System.Linq;
22
using LinqToDB.EntityFrameworkCore.BaseTests;
3-
using LinqToDB.EntityFrameworkCore.SqlServer.Tests.Models.Northwind;
3+
using LinqToDB.EntityFrameworkCore.SqlServer.Tests.Models.IssueModel;
44
using Microsoft.EntityFrameworkCore;
55
using NUnit.Framework;
6-
using NUnit.Framework.Constraints;
76

87
namespace LinqToDB.EntityFrameworkCore.SqlServer.Tests
98
{
109
[TestFixture]
1110
public class IssueTests : TestsBase
1211
{
13-
private DbContextOptions<IssueContext> _options;
12+
private DbContextOptions<IssueContext>? _options;
1413
private bool _created;
1514

1615
public IssueTests()
16+
{
17+
InitOptions();
18+
}
19+
20+
private void InitOptions()
1721
{
1822
var optionsBuilder = new DbContextOptionsBuilder<IssueContext>();
19-
//new SqlServerDbContextOptionsBuilder(optionsBuilder);
2023

2124
optionsBuilder.UseSqlServer("Server=.;Database=IssuesEFCore;Integrated Security=SSPI");
2225
optionsBuilder.UseLoggerFactory(TestUtils.LoggerFactory);
@@ -34,6 +37,7 @@ private IssueContext CreateContext()
3437
ctx.Database.EnsureCreated();
3538
_created = true;
3639
}
40+
3741
return ctx;
3842
}
3943

@@ -53,5 +57,26 @@ public void Issue73Test()
5357
AreEqual(efItems, linq2dbItems);
5458
}
5559

60+
[Test]
61+
public void Issue117Test()
62+
{
63+
using var ctx = CreateContext();
64+
65+
var userId = 1;
66+
67+
var query =
68+
from p in ctx.Patents.Include(p => p.Assessment)
69+
where p.Assessment == null || (p.Assessment.TechnicalReviewerId != userId)
70+
select new { PatentId = p.Id, UserId = userId };
71+
72+
var resultEF = query.ToArray();
73+
74+
using var db = ctx.CreateLinqToDbConnection();
75+
76+
_ = query.ToLinqToDB(db).ToArray();
77+
78+
Assert.That(db.LastQuery, Does.Not.Contain("INNER"));
79+
}
80+
5681
}
5782
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace LinqToDB.EntityFrameworkCore.SqlServer.Tests.Models.IssueModel
4+
{
5+
public class Patent
6+
{
7+
[Key]
8+
public int Id { get; set; }
9+
public PatentAssessment? Assessment { get; set; }
10+
}
11+
12+
public class PatentAssessment
13+
{
14+
[Key]
15+
public int PatentId { get; set; }
16+
public Patent Patent { get; set; } = null!;
17+
public int? TechnicalReviewerId { get; set; }
18+
}
19+
}

Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/Models/IssueModel/IssueContext.cs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
using System.Reflection;
2-
using LinqToDB.EntityFrameworkCore.BaseTests.Models.Northwind;
3-
using LinqToDB.EntityFrameworkCore.SqlServer.Tests.Models.IssueModel;
4-
using LinqToDB.EntityFrameworkCore.SqlServer.Tests.Models.Northwind.Mapping;
5-
using LinqToDB.Expressions;
6-
using LinqToDB.Extensions;
7-
using Microsoft.EntityFrameworkCore;
8-
9-
namespace LinqToDB.EntityFrameworkCore.SqlServer.Tests.Models.Northwind
1+
using Microsoft.EntityFrameworkCore;
2+
3+
namespace LinqToDB.EntityFrameworkCore.SqlServer.Tests.Models.IssueModel
104
{
115
public class IssueContext : DbContext
126
{
137
public DbSet<Issue73Entity> Issue73Entities { get; set; } = null!;
148

9+
public DbSet<Patent> Patents { get; set; } = null!;
10+
1511
public IssueContext(DbContextOptions options) : base(options)
1612
{
1713

@@ -43,6 +39,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
4339
},
4440
});
4541
});
42+
43+
modelBuilder
44+
.Entity<Patent>()
45+
.HasOne(p => p.Assessment)
46+
.WithOne(pa => pa.Patent)
47+
.HasForeignKey<PatentAssessment>(pa => pa.PatentId)
48+
.OnDelete(DeleteBehavior.Restrict);
49+
4650
}
4751
}
4852
}

Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/QueryableExtensions.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,62 @@ public static IIncludableQueryable<TEntity, TProp> IncludeInline<TEntity, TProp,
4848

4949
return includable;
5050
}
51+
52+
public static Expression<Func<T, bool>> MakePropertiesPredicate<T, TValue>(Expression<Func<TValue, TValue, bool>> pattern, TValue searchValue, bool isOr)
53+
{
54+
var parameter = Expression.Parameter(typeof(T), "e");
55+
var searchExpr = Expression.Constant(searchValue);
56+
57+
var predicateBody = typeof(T).GetProperties()
58+
.Where(p => p.PropertyType == typeof(TValue))
59+
.Select(p =>
60+
ExpressionReplacer.GetBody(pattern, Expression.MakeMemberAccess(
61+
parameter, p), searchExpr))
62+
.Aggregate(isOr ? Expression.OrElse : Expression.AndAlso);
63+
64+
return Expression.Lambda<Func<T, bool>>(predicateBody, parameter);
65+
}
66+
67+
public static IQueryable<T> FilterByProperties<T, TValue>(this IQueryable<T> query, TValue searchValue,
68+
Expression<Func<TValue, TValue, bool>> pattern, bool isOr)
69+
{
70+
return query.Where(MakePropertiesPredicate<T, TValue>(pattern, searchValue, isOr));
71+
}
72+
73+
class ExpressionReplacer : ExpressionVisitor
74+
{
75+
readonly IDictionary<Expression, Expression> _replaceMap;
76+
77+
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
78+
{
79+
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
80+
}
81+
82+
public override Expression Visit(Expression node)
83+
{
84+
if (node != null && _replaceMap.TryGetValue(node, out var replacement))
85+
return replacement;
86+
return base.Visit(node);
87+
}
88+
89+
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
90+
{
91+
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
92+
}
93+
94+
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
95+
{
96+
return new ExpressionReplacer(replaceMap).Visit(expr);
97+
}
98+
99+
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
100+
{
101+
if (lambda.Parameters.Count != toReplace.Length)
102+
throw new InvalidOperationException();
103+
104+
return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
105+
.ToDictionary(i => (Expression)lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
106+
}
107+
}
51108
}
52109
}

Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/ToolsTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,19 @@ public void TestCreateTempTable([Values(true, false)] bool enableFilter)
809809
}
810810

811811

812+
[Test]
813+
public void TestForeignKey([Values(true, false)] bool enableFilter)
814+
{
815+
using (var ctx = CreateContext(enableFilter))
816+
{
817+
var resultEF = ctx.Employees.Include(e => e.ReportsToNavigation).ToArray();
818+
var result = ctx.Employees.Include(e => e.ReportsToNavigation).ToLinqToDB().ToArray();
819+
820+
AreEqual(resultEF, result);
821+
}
822+
}
823+
824+
812825
[Test]
813826
public void TestCommandTimeout()
814827
{

0 commit comments

Comments
 (0)