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

Commit 47c18f9

Browse files
authored
Merge pull request #47 from linq2db/master
Release 3.2.0
2 parents db7df7a + 116455c commit 47c18f9

File tree

12 files changed

+265
-93
lines changed

12 files changed

+265
-93
lines changed

Build/linq2db.Default.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>3.1.0</Version>
3+
<Version>3.2.0</Version>
44

55
<Description>Allows to execute Linq to DB (linq2db) queries in Entity Framework Core DbContext.</Description>
66
<Title>Linq to DB (linq2db) extensions for Entity Framework Core</Title>

NuGet.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
<configuration>
33
<packageSources>
44
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
5-
<add key="linq2db" value="https://www.myget.org/F/linq2db/api/v3/index.json" />
5+
<add key="linq2db" value="https://pkgs.dev.azure.com/linq2db/linq2db/_packaging/linq2db/nuget/v3/index.json" />
66
</packageSources>
77
</configuration>

NuGet/linq2db.EntityFrameworkCore.nuspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<dependencies>
1717
<group targetFramework=".NETStandard2.0">
1818
<dependency id="Microsoft.EntityFrameworkCore.Relational" version="3.1.3" />
19-
<dependency id="linq2db" version="3.0.0-preview.2" />
19+
<dependency id="linq2db" version="3.0.0-rc.0" />
2020
</group>
2121
</dependencies>
2222
</metadata>

Source/LinqToDB.EntityFrameworkCore/EFCoreMetadataReader.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using System.Linq;
55
using System.Linq.Expressions;
66
using System.Reflection;
7+
using LinqToDB.Expressions;
8+
using LinqToDB.Reflection;
79
using Microsoft.EntityFrameworkCore;
810
using Microsoft.EntityFrameworkCore.Metadata;
911
using Microsoft.EntityFrameworkCore.Metadata.Internal;
@@ -47,6 +49,52 @@ public T[] GetAttributes<T>(Type type, bool inherit = true) where T : Attribute
4749
{
4850
return new[] { (T)(Attribute)new TableAttribute(et.GetTableName()) { Schema = et.GetSchema() } };
4951
}
52+
if (typeof(T) == typeof(QueryFilterAttribute))
53+
{
54+
var filter = et.GetQueryFilter();
55+
56+
if (filter != null)
57+
{
58+
var queryParam = Expression.Parameter(typeof(IQueryable<>).MakeGenericType(type), "q");
59+
var dcParam = Expression.Parameter(typeof(IDataContext), "dc");
60+
var contextProp = Expression.Property(Expression.Convert(dcParam, typeof(LinqToDBForEFToolsDataConnection)), "Context");
61+
var filterBody = filter.Body.Transform(e =>
62+
{
63+
switch (e)
64+
{
65+
case ConstantExpression cnt:
66+
{
67+
if (typeof(DbContext).IsSameOrParentOf(cnt.Type))
68+
{
69+
Expression newExpr = contextProp;
70+
if (newExpr.Type != cnt.Type)
71+
newExpr = Expression.Convert(newExpr, cnt.Type);
72+
return newExpr;
73+
}
74+
break;
75+
}
76+
}
77+
78+
return e;
79+
});
80+
81+
// we have found dependency, check for compatibility
82+
83+
var filterLambda = Expression.Lambda(filterBody, filter.Parameters[0]);
84+
Expression body = Expression.Call(Methods.Queryable.Where.MakeGenericMethod(type), queryParam, filterLambda);
85+
86+
var checkType = filter.Body != filterBody;
87+
if (checkType)
88+
{
89+
body = Expression.Condition(
90+
Expression.TypeIs(dcParam, typeof(LinqToDBForEFToolsDataConnection)), body, queryParam);
91+
}
92+
93+
var lambda = Expression.Lambda(body, queryParam, dcParam);
94+
95+
return new[] { (T) (Attribute) new QueryFilterAttribute { FilterFunc = lambda.Compile() } };
96+
}
97+
}
5098
}
5199

52100
if (typeof(T) == typeof(TableAttribute))

Source/LinqToDB.EntityFrameworkCore/ILinqToDBForEFTools.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,5 +112,12 @@ public interface ILinqToDBForEFTools
112112
/// <param name="info"></param>
113113
/// <param name="logger"></param>
114114
void LogConnectionTrace(TraceInfo info, ILogger logger);
115+
116+
/// <summary>
117+
/// Enables attaching entities to change tracker.
118+
/// Entities will be attached only if AsNoTracking() is not used in query and DbContext is configured to track entities.
119+
/// </summary>
120+
bool EnableChangeTracker { get; set; }
121+
115122
}
116123
}

Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,5 +509,16 @@ public static DbContext GetCurrentContext(IQueryable query)
509509
{
510510
return Implementation.GetCurrentContext(query);
511511
}
512+
513+
/// <summary>
514+
/// Enables attaching entities to change tracker.
515+
/// Entities will be attached only if AsNoTracking() is not used in query and DbContext is configured to track entities.
516+
/// </summary>
517+
public static bool EnableChangeTracker
518+
{
519+
get => Implementation.EnableChangeTracker;
520+
set => Implementation.EnableChangeTracker = true;
521+
}
522+
512523
}
513524
}

Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsDataConnection.cs

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
using JetBrains.Annotations;
88
using Microsoft.EntityFrameworkCore;
9+
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
10+
using Microsoft.EntityFrameworkCore.Infrastructure;
11+
using Microsoft.EntityFrameworkCore.Storage;
912

1013
namespace LinqToDB.EntityFrameworkCore
1114
{
@@ -16,20 +19,29 @@ namespace LinqToDB.EntityFrameworkCore
1619

1720
public class LinqToDBForEFToolsDataConnection : DataConnection, IExpressionPreprocessor
1821
{
19-
readonly DbContext _context;
2022
readonly IModel _model;
2123
readonly Func<Expression, IDataContext, DbContext, IModel, Expression> _transformFunc;
2224

25+
private IEntityType _lastEntityType;
26+
private Type _lastType;
27+
private IStateManager _stateManager;
28+
29+
public bool Tracking { get; set; }
30+
31+
public DbContext Context { get; }
32+
2333
public LinqToDBForEFToolsDataConnection(
2434
[CanBeNull] DbContext context,
2535
[NotNull] IDataProvider dataProvider,
2636
[NotNull] string connectionString,
2737
IModel model,
2838
Func<Expression, IDataContext, DbContext, IModel, Expression> transformFunc) : base(dataProvider, connectionString)
2939
{
30-
_context = context;
31-
_model = model;
32-
_transformFunc = transformFunc;
40+
Context = context;
41+
_model = model;
42+
_transformFunc = transformFunc;
43+
if (LinqToDBForEFTools.EnableChangeTracker)
44+
OnEntityCreated += OnEntityCreatedHandler;
3345
}
3446

3547
public LinqToDBForEFToolsDataConnection(
@@ -40,9 +52,11 @@ public LinqToDBForEFToolsDataConnection(
4052
Func<Expression, IDataContext, DbContext, IModel, Expression> transformFunc
4153
) : base(dataProvider, transaction)
4254
{
43-
_context = context;
44-
_model = model;
45-
_transformFunc = transformFunc;
55+
Context = context;
56+
_model = model;
57+
_transformFunc = transformFunc;
58+
if (LinqToDBForEFTools.EnableChangeTracker)
59+
OnEntityCreated += OnEntityCreatedHandler;
4660
}
4761

4862
public LinqToDBForEFToolsDataConnection(
@@ -52,16 +66,43 @@ public LinqToDBForEFToolsDataConnection(
5266
IModel model,
5367
Func<Expression, IDataContext, DbContext, IModel, Expression> transformFunc) : base(dataProvider, connection)
5468
{
55-
_context = context;
56-
_model = model;
57-
_transformFunc = transformFunc;
69+
Context = context;
70+
_model = model;
71+
_transformFunc = transformFunc;
72+
if (LinqToDBForEFTools.EnableChangeTracker)
73+
OnEntityCreated += OnEntityCreatedHandler;
5874
}
5975

6076
public Expression ProcessExpression(Expression expression)
6177
{
6278
if (_transformFunc == null)
6379
return expression;
64-
return _transformFunc(expression, this, _context, _model);
80+
return _transformFunc(expression, this, Context, _model);
6581
}
82+
83+
private void OnEntityCreatedHandler(EntityCreatedEventArgs args)
84+
{
85+
if (!LinqToDBForEFTools.EnableChangeTracker
86+
|| !Tracking
87+
|| Context.ChangeTracker.QueryTrackingBehavior == QueryTrackingBehavior.NoTracking)
88+
return;
89+
90+
var type = args.Entity.GetType();
91+
if (_lastType != type)
92+
{
93+
_lastType = type;
94+
_lastEntityType = Context.Model.FindEntityType(type);
95+
}
96+
97+
if (_lastEntityType == null)
98+
return;
99+
100+
if (_stateManager == null)
101+
_stateManager = Context.GetService<IStateManager>();
102+
103+
var entry = _stateManager.StartTrackingFromQuery(_lastEntityType, args.Entity, ValueBuffer.Empty);
104+
args.Entity = entry.Entity;
105+
}
106+
66107
}
67108
}

Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsImplDefault.cs

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -474,11 +474,18 @@ private static Expression WithConvertToObject(Expression valueExpression)
474474
public virtual MappingSchema GetMappingSchema(IModel model, IMetadataReader metadataReader,
475475
IValueConverterSelector convertorSelector)
476476
{
477-
var result = _schemaCache.GetOrCreate(Tuple.Create(model, metadataReader, convertorSelector), e =>
478-
{
479-
e.SlidingExpiration = TimeSpan.FromHours(1);
480-
return CreateMappingSchema(model, metadataReader, convertorSelector);
481-
});
477+
var result = _schemaCache.GetOrCreate(
478+
Tuple.Create(
479+
model,
480+
metadataReader,
481+
convertorSelector,
482+
EnableChangeTracker
483+
),
484+
e =>
485+
{
486+
e.SlidingExpiration = TimeSpan.FromHours(1);
487+
return CreateMappingSchema(model, metadataReader, convertorSelector);
488+
});
482489

483490
return result;
484491
}
@@ -693,6 +700,7 @@ static List<Expression> CompactTree(List<Expression> items, ExpressionType nodeT
693700
public virtual Expression TransformExpression(Expression expression, IDataContext dc, DbContext ctx, IModel model)
694701
{
695702
var ignoreQueryFilters = false;
703+
var tracking = true;
696704

697705
Expression LocalTransform(Expression e)
698706
{
@@ -730,7 +738,10 @@ Expression LocalTransform(Expression e)
730738
isTunnel = true;
731739
}
732740
else if (generic == AsNoTrackingMethodInfo)
741+
{
733742
isTunnel = true;
743+
tracking = false;
744+
}
734745
else if (generic == IncludeMethodInfo)
735746
{
736747
var method =
@@ -767,7 +778,7 @@ Expression LocalTransform(Expression e)
767778
else if (generic == ThenIncludeMethodInfo)
768779
{
769780
var method =
770-
Methods.LinqToDB.ThenLoadSingle.MakeGenericMethod(methodCall.Method
781+
Methods.LinqToDB.ThenLoadFromSingle.MakeGenericMethod(methodCall.Method
771782
.GetGenericArguments());
772783

773784
return Expression.Call(method,
@@ -777,7 +788,7 @@ Expression LocalTransform(Expression e)
777788
else if (generic == ThenIncludeEnumerableMethodInfo)
778789
{
779790
var method =
780-
Methods.LinqToDB.ThenLoadMultiple.MakeGenericMethod(methodCall.Method
791+
Methods.LinqToDB.ThenLoadFromMany.MakeGenericMethod(methodCall.Method
781792
.GetGenericArguments());
782793

783794
return Expression.Call(method,
@@ -830,10 +841,17 @@ Expression LocalTransform(Expression e)
830841

831842
var newExpression = expression.Transform(e => LocalTransform(e));
832843

833-
if (!ignoreQueryFilters)
844+
if (ignoreQueryFilters)
834845
{
835-
var ignoredExpressions = new HashSet<Expression>();
836-
newExpression = ApplyQueryFilters(newExpression, ignoredExpressions, dc, ctx, model);
846+
var elementType = newExpression.Type.GetGenericArguments()[0];
847+
newExpression = Expression.Call(Methods.LinqToDB.IgnoreFilters.MakeGenericMethod(elementType),
848+
newExpression, Expression.NewArrayInit(typeof(Type)));
849+
}
850+
851+
if (dc is LinqToDBForEFToolsDataConnection dataConnection)
852+
{
853+
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
854+
dataConnection.Tracking = tracking;
837855
}
838856

839857
return newExpression;
@@ -1317,11 +1335,6 @@ public virtual ILogger CreateLogger(IDbContextOptions options)
13171335
var coreOptions = options?.FindExtension<CoreOptionsExtension>();
13181336

13191337
var logger = coreOptions?.LoggerFactory?.CreateLogger("LinqToDB");
1320-
if (logger != null)
1321-
{
1322-
if (DataConnection.TraceSwitch.Level == TraceLevel.Off)
1323-
DataConnection.TurnTraceSwitchOn();
1324-
}
13251338

13261339
return logger;
13271340
}
@@ -1336,5 +1349,11 @@ public virtual ILogger CreateLogger(IDbContextOptions options)
13361349
/// </summary>
13371350
public static PostgreSQLVersion PostgreSqlDefaultVersion { get; set; } = PostgreSQLVersion.v93;
13381351

1352+
/// <summary>
1353+
/// Enables attaching entities to change tracker.
1354+
/// Entities will be attached only if AsNoTracking() is not used in query and DbContext is configured to track entities.
1355+
/// </summary>
1356+
public virtual bool EnableChangeTracker { get; set; } = true;
1357+
13391358
}
13401359
}

Source/LinqToDB.EntityFrameworkCore/linq2db.EntityFrameworkCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="linq2db" Version="3.0.0-preview.2" />
12+
<PackageReference Include="linq2db" Version="3.0.0-rc.0" />
1313
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.3" />
1414
</ItemGroup>
1515

Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/NpgSqlTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public NpgSqlTests()
2323
var optionsBuilder = new DbContextOptionsBuilder<NpgSqlEnititesContext>();
2424
//new SqlServerDbContextOptionsBuilder(optionsBuilder);
2525

26-
optionsBuilder.UseNpgsql("Server=localhost;Port=5432;Database=test_ef_data;User Id=postgres;Password=TestPassword;Pooling=true;MinPoolSize=10;MaxPoolSize=100;");
26+
optionsBuilder.UseNpgsql("Server=localhost;Port=5433;Database=test_ef_data;User Id=postgres;Password=TestPassword;Pooling=true;MinPoolSize=10;MaxPoolSize=100;");
2727
optionsBuilder.UseLoggerFactory(TestUtils.LoggerFactory);
2828

2929
_options = optionsBuilder.Options;

0 commit comments

Comments
 (0)