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

Commit 30945f9

Browse files
authored
Merge pull request #170 from linq2db/version6
Release 6.1.0
2 parents d22e4f8 + 0cfb92f commit 30945f9

File tree

11 files changed

+129
-28
lines changed

11 files changed

+129
-28
lines changed

Build/linq2db.Default.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>6.0.0</Version>
3+
<Version>6.1.0</Version>
44

55
<Authors>Svyatoslav Danyliv, Igor Tkachev, Dmitry Lukashenko, Ilya Chudin</Authors>
66
<Product>Linq to DB</Product>
@@ -27,6 +27,7 @@
2727
<GenerateAssemblyVersionAttribute>true</GenerateAssemblyVersionAttribute>
2828
<GenerateAssemblyFileVersionAttribute>true</GenerateAssemblyFileVersionAttribute>
2929
<GenerateNeutralResourcesLanguageAttribute>false</GenerateNeutralResourcesLanguageAttribute>
30+
<DisableImplicitNamespaceImports>true</DisableImplicitNamespaceImports>
3031

3132
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
3233

Directory.Packages.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
<PackageVersion Include="NUnit" Version="3.13.2" />
66
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
77

8-
<PackageVersion Include="linq2db" Version="3.4.2" />
9-
<PackageVersion Include="linq2db.Tools" Version="3.4.2" />
8+
<PackageVersion Include="linq2db" Version="3.4.3" />
9+
<PackageVersion Include="linq2db.Tools" Version="3.4.3" />
1010

1111
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.0.0" />
1212

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="net6.0">
1818
<dependency id="Microsoft.EntityFrameworkCore.Relational" version="6.0.0-preview.6.21352.1" />
19-
<dependency id="linq2db" version="3.4.2" />
19+
<dependency id="linq2db" version="3.4.3" />
2020
</group>
2121
</dependencies>
2222
</metadata>

Source/LinqToDB.EntityFrameworkCore/EFCoreMetadataReader.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ public T[] GetAttributes<T>(Type type, bool inherit = true) where T : Attribute
8585
return e;
8686
});
8787

88+
filterBody = LinqToDBForEFTools.TransformExpression(filterBody, null, null, _model);
89+
8890
// we have found dependency, check for compatibility
8991

9092
var filterLambda = Expression.Lambda(filterBody, filter.Parameters[0]);

Source/LinqToDB.EntityFrameworkCore/ILinqToDBForEFTools.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public interface ILinqToDBForEFTools
7777
/// <param name="ctx">Optional DbContext instance.</param>
7878
/// <param name="model">EF Core data model instance.</param>
7979
/// <returns>Transformed expression.</returns>
80-
Expression TransformExpression(Expression expression, IDataContext dc, DbContext? ctx, IModel? model);
80+
Expression TransformExpression(Expression expression, IDataContext? dc, DbContext? ctx, IModel? model);
8181

8282
/// <summary>
8383
/// Extracts <see cref="DbContext"/> instance from <see cref="IQueryable"/> object.

Source/LinqToDB.EntityFrameworkCore/Internal/LinqToDBForEFQueryProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ namespace LinqToDB.EntityFrameworkCore.Internal
2121
/// It may change or be removed without further notice.
2222
/// </summary>
2323
/// <typeparam name="T">Type of query element.</typeparam>
24+
#pragma warning disable CA2252 // This API requires opting into preview features
2425
public class LinqToDBForEFQueryProvider<T> : IAsyncQueryProvider, IQueryProviderAsync, IQueryable<T>, IAsyncEnumerable<T>
26+
#pragma warning restore CA2252 // This API requires opting into preview features
2527
{
2628
/// <summary>
2729
/// Creates instance of adapter.

Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsDataConnection.cs

Lines changed: 107 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@
22
using System.Data;
33
using System.Linq;
44
using System.Linq.Expressions;
5-
6-
using Microsoft.EntityFrameworkCore.Metadata;
5+
using System.Reflection;
76

87
using Microsoft.EntityFrameworkCore;
8+
using Microsoft.EntityFrameworkCore.Metadata;
99
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
1010
using Microsoft.EntityFrameworkCore.Infrastructure;
1111
using Microsoft.EntityFrameworkCore.Storage;
12+
using Microsoft.Extensions.Caching.Memory;
13+
using Microsoft.Extensions.Options;
1214

1315
namespace LinqToDB.EntityFrameworkCore
1416
{
1517
using System.Diagnostics.CodeAnalysis;
1618
using Data;
1719
using DataProvider;
1820
using Linq;
21+
using Expressions;
1922

2023
/// <summary>
2124
/// linq2db EF.Core data connection.
@@ -29,6 +32,11 @@ public class LinqToDBForEFToolsDataConnection : DataConnection, IExpressionPrepr
2932
private Type? _lastType;
3033
private IStateManager? _stateManager;
3134

35+
private static IMemoryCache _entityKeyGetterCache = new MemoryCache(Options.Create(new MemoryCacheOptions()));
36+
37+
private static MethodInfo TryGetEntryMethodInfo =
38+
MemberHelper.MethodOf<IStateManager>(sm => sm.TryGetEntry(null!, Array.Empty<object>()));
39+
3240
/// <summary>
3341
/// Change tracker enable flag.
3442
/// </summary>
@@ -121,8 +129,61 @@ public Expression ProcessExpression(Expression expression)
121129
return _transformFunc(expression, this, Context, _model);
122130
}
123131

132+
private class TypeKey
133+
{
134+
public TypeKey(IEntityType entityType, IModel? model)
135+
{
136+
EntityType = entityType;
137+
Model = model;
138+
}
139+
140+
public IEntityType EntityType { get; }
141+
public IModel? Model { get; }
142+
143+
protected bool Equals(TypeKey other)
144+
{
145+
return EntityType.Equals(other.EntityType) && Equals(Model, other.Model);
146+
}
147+
148+
public override bool Equals(object? obj)
149+
{
150+
if (ReferenceEquals(null, obj))
151+
{
152+
return false;
153+
}
154+
155+
if (ReferenceEquals(this, obj))
156+
{
157+
return true;
158+
}
159+
160+
if (obj.GetType() != this.GetType())
161+
{
162+
return false;
163+
}
164+
165+
return Equals((TypeKey)obj);
166+
}
167+
168+
public override int GetHashCode()
169+
{
170+
unchecked
171+
{
172+
return (EntityType.GetHashCode() * 397) ^ (Model != null ? Model.GetHashCode() : 0);
173+
}
174+
}
175+
}
176+
124177
private void OnEntityCreatedHandler(EntityCreatedEventArgs args)
125178
{
179+
// Do not allow to store in ChangeTracker temporary tables
180+
if ((args.TableOptions & TableOptions.IsTemporaryOptionSet) != 0)
181+
return;
182+
183+
// Do not allow to store in ChangeTracker tables from different server
184+
if (args.ServerName != null)
185+
return;
186+
126187
if (!LinqToDBForEFTools.EnableChangeTracker
127188
|| !Tracking
128189
|| Context!.ChangeTracker.QueryTrackingBehavior == QueryTrackingBehavior.NoTracking)
@@ -138,6 +199,10 @@ private void OnEntityCreatedHandler(EntityCreatedEventArgs args)
138199
if (_lastEntityType == null)
139200
return;
140201

202+
// Do not allow to store in ChangeTracker tables that has different name
203+
if (args.TableName != _lastEntityType.GetTableName())
204+
return;
205+
141206
if (_stateManager == null)
142207
_stateManager = Context.GetService<IStateManager>();
143208

@@ -146,22 +211,18 @@ private void OnEntityCreatedHandler(EntityCreatedEventArgs args)
146211
//
147212
InternalEntityEntry? entry = null;
148213

149-
foreach (var key in _lastEntityType.GetKeys())
214+
var kacheKey = new TypeKey (_lastEntityType, _model);
215+
216+
var retrievalFunc = _entityKeyGetterCache.GetOrCreate(kacheKey, ce =>
150217
{
151-
//TODO: Find faster way
152-
var keyArray = key.Properties.Where(p => p.PropertyInfo != null || p.FieldInfo != null).Select(p =>
153-
p.PropertyInfo != null
154-
? p.PropertyInfo.GetValue(args.Entity)
155-
: p.FieldInfo!.GetValue(args.Entity)).ToArray();
218+
ce.SlidingExpiration = TimeSpan.FromHours(1);
219+
return CreateEntityRetrievalFunc(((TypeKey)ce.Key).EntityType);
220+
});
156221

157-
if (keyArray.Length == key.Properties.Count)
158-
{
159-
entry = _stateManager.TryGetEntry(key, keyArray);
222+
if (retrievalFunc == null)
223+
return;
160224

161-
if (entry != null)
162-
break;
163-
}
164-
}
225+
entry = retrievalFunc(_stateManager, args.Entity);
165226

166227
if (entry == null)
167228
{
@@ -171,6 +232,37 @@ private void OnEntityCreatedHandler(EntityCreatedEventArgs args)
171232
args.Entity = entry.Entity;
172233
}
173234

235+
private Func<IStateManager, object, InternalEntityEntry?>? CreateEntityRetrievalFunc(IEntityType entityType)
236+
{
237+
var stateManagerParam = Expression.Parameter(typeof(IStateManager), "sm");
238+
var objParam = Expression.Parameter(typeof(object), "o");
239+
240+
var variable = Expression.Variable(entityType.ClrType, "e");
241+
var assignExpr = Expression.Assign(variable, Expression.Convert(objParam, entityType.ClrType));
242+
243+
var key = entityType.GetKeys().FirstOrDefault();
244+
245+
var arrayExpr = key.Properties.Where(p => p.PropertyInfo != null || p.FieldInfo != null).Select(p =>
246+
Expression.Convert(Expression.MakeMemberAccess(variable, p.PropertyInfo ?? (MemberInfo)p.FieldInfo),
247+
typeof(object)))
248+
.ToArray();
249+
250+
if (arrayExpr.Length == 0)
251+
return null;
252+
253+
var newArrayExpression = Expression.NewArrayInit(typeof(object), arrayExpr);
254+
var body =
255+
Expression.Block(new[] { variable },
256+
assignExpr,
257+
Expression.Call(stateManagerParam, TryGetEntryMethodInfo, Expression.Constant(key),
258+
newArrayExpression));
259+
260+
var lambda =
261+
Expression.Lambda<Func<IStateManager, object, InternalEntityEntry?>>(body, stateManagerParam, objParam);
262+
263+
return lambda.Compile();
264+
}
265+
174266
private void CopyDatabaseProperties()
175267
{
176268
var commandTimeout = Context?.Database.GetCommandTimeout();

Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsImplDefault.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public override int GetHashCode()
8686
readonly ConcurrentDictionary<ProviderKey, IDataProvider> _knownProviders = new();
8787

8888
private readonly MemoryCache _schemaCache = new(
89-
new Microsoft.Extensions.Caching.Memory.MemoryCacheOptions()
89+
new Microsoft.Extensions.Caching.Memory.MemoryCacheOptions
9090
{
9191
ExpirationScanFrequency = TimeSpan.FromHours(1.0)
9292
});
@@ -762,7 +762,7 @@ static List<Expression> CompactTree(List<Expression> items, ExpressionType nodeT
762762
/// <param name="ctx">Optional DbContext instance.</param>
763763
/// <param name="model">EF Core data model instance.</param>
764764
/// <returns>Transformed expression.</returns>
765-
public virtual Expression TransformExpression(Expression expression, IDataContext dc, DbContext? ctx, IModel? model)
765+
public virtual Expression TransformExpression(Expression expression, IDataContext? dc, DbContext? ctx, IModel? model)
766766
{
767767
var tracking = true;
768768
var ignoreTracking = false;
@@ -775,7 +775,7 @@ TransformInfo LocalTransform(Expression e)
775775
{
776776
case ExpressionType.Constant:
777777
{
778-
if (typeof(EntityQueryable<>).IsSameOrParentOf(e.Type) || typeof(DbSet<>).IsSameOrParentOf(e.Type))
778+
if (dc != null && typeof(EntityQueryable<>).IsSameOrParentOf(e.Type) || typeof(DbSet<>).IsSameOrParentOf(e.Type))
779779
{
780780
var entityType = e.Type.GenericTypeArguments[0];
781781
var newExpr = Expression.Call(null, Methods.LinqToDB.GetTable.MakeGenericMethod(entityType), Expression.Constant(dc));
@@ -965,7 +965,7 @@ TransformInfo LocalTransform(Expression e)
965965

966966
case ExpressionType.Extension:
967967
{
968-
if (e is FromSqlQueryRootExpression fromSqlQueryRoot)
968+
if (dc != null && e is FromSqlQueryRootExpression fromSqlQueryRoot)
969969
{
970970
//convert the arguments from the FromSqlOnQueryable method from EF, to a L2DB FromSql call
971971
return new TransformInfo(Expression.Call(null,
@@ -974,7 +974,7 @@ TransformInfo LocalTransform(Expression e)
974974
Expression.New(RawSqlStringConstructor, Expression.Constant(fromSqlQueryRoot.Sql)),
975975
fromSqlQueryRoot.Argument));
976976
}
977-
else if (e is QueryRootExpression queryRoot)
977+
else if (dc != null && e is QueryRootExpression queryRoot)
978978
{
979979
var newExpr = Expression.Call(null, Methods.LinqToDB.GetTable.MakeGenericMethod(queryRoot.EntityType.ClrType), Expression.Constant(dc));
980980
return new TransformInfo(newExpr);
@@ -1099,6 +1099,7 @@ public virtual EFConnectionInfo ExtractConnectionInfo(IDbContextOptions? options
10991099
/// <param name="logger">Logger instance.</param>
11001100
public virtual void LogConnectionTrace(TraceInfo info, ILogger logger)
11011101
{
1102+
#pragma warning disable CA1848 // Use the LoggerMessage delegates
11021103
var logLevel = info.TraceLevel switch
11031104
{
11041105
TraceLevel.Off => LogLevel.None,
@@ -1147,6 +1148,7 @@ public virtual void LogConnectionTrace(TraceInfo info, ILogger logger)
11471148
break;
11481149
}
11491150
}
1151+
#pragma warning restore CA1848 // Use the LoggerMessage delegates
11501152
}
11511153

11521154
/// <summary>

Tests/LinqToDB.EntityFrameworkCore.BaseTests/Models/Northwind/NorthwindData.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@ private static void AddEntities(DbContext context)
147147
context.Set<OrderDetail>().AddRange(CreateOrderDetails());
148148
}
149149

150+
#pragma warning disable CA2252 // This API requires opting into preview features
150151
private class AsyncEnumerable<T> : IAsyncQueryProvider, IOrderedQueryable<T>
152+
#pragma warning restore CA2252 // This API requires opting into preview features
151153
{
152154
private readonly EnumerableQuery<T> _enumerableQuery;
153155

Tests/LinqToDB.EntityFrameworkCore.SqlServer.Tests/JsonConverTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public void TestJsonConvert()
133133
p.NameLocalized,
134134
p.CrashEnum,
135135
p.GuidColumn,
136-
JsonValue = JsonValue(p.JsonColumn, path)
136+
JsonValue = JsonValue(p.JsonColumn!, path)
137137
});
138138

139139
var item = items.FirstOrDefault();

0 commit comments

Comments
 (0)