Skip to content

Commit

Permalink
Improved ChangeTracker feeding. Improved ConnectionString retrieval f…
Browse files Browse the repository at this point in the history
…or NpgSql (#68)

* Improved ChangeTracker feeding. Improved ConnectionString retrieval for NpgSql.

* Updated linq2db version.

* Increased library version to 3.7.0
  • Loading branch information
sdanyliv authored Oct 20, 2020
1 parent 75ab81c commit 064c8f0
Show file tree
Hide file tree
Showing 24 changed files with 723 additions and 9 deletions.
2 changes: 1 addition & 1 deletion Build/linq2db.Default.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>3.6.0</Version>
<Version>3.7.0</Version>

<Description>Allows to execute Linq to DB (linq2db) queries in Entity Framework Core DbContext.</Description>
<Title>Linq to DB (linq2db) extensions for Entity Framework Core</Title>
Expand Down
2 changes: 1 addition & 1 deletion NuGet/linq2db.EntityFrameworkCore.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<dependencies>
<group targetFramework=".NETStandard2.0">
<dependency id="Microsoft.EntityFrameworkCore.Relational" version="3.1.3" />
<dependency id="linq2db" version="3.1.2" />
<dependency id="linq2db" version="3.1.5" />
</group>
</dependencies>
</metadata>
Expand Down
26 changes: 24 additions & 2 deletions Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;

using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
Expand Down Expand Up @@ -390,6 +390,9 @@ public static DataConnection CreateLinq2DbConnectionDetached([JetBrains.Annotati
return dc;
}


static ConcurrentDictionary<Type, Func<DbConnection, string>> _connectionStringExtractors = new ConcurrentDictionary<Type, Func<DbConnection, string>>();

/// <summary>
/// Extracts database connection information from EF.Core provider data.
/// </summary>
Expand All @@ -398,7 +401,26 @@ public static DataConnection CreateLinq2DbConnectionDetached([JetBrains.Annotati
public static EFConnectionInfo GetConnectionInfo(EFProviderInfo info)
{
var connection = info.Connection;
var connectionString = connection?.ConnectionString;
string connectionString = null;
if (connection != null)
{
var connectionStringFunc = _connectionStringExtractors.GetOrAdd(connection.GetType(), t =>
{
// NpgSQL workaround
var originalProp = t.GetProperty("OriginalConnectionString", BindingFlags.Instance | BindingFlags.NonPublic);
if (originalProp == null)
return c => c.ConnectionString;
var parameter = Expression.Parameter(typeof(DbConnection), "c");
var lambda = Expression.Lambda<Func<DbConnection, string>>(
Expression.MakeMemberAccess(Expression.Convert(parameter, t), originalProp), parameter);
return lambda.Compile();
});

connectionString = connectionStringFunc(connection);
}

if (connection != null && connectionString != null)
return new EFConnectionInfo { Connection = connection, ConnectionString = connectionString };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Data;
using System.Linq;
using System.Linq.Expressions;

using Microsoft.EntityFrameworkCore.Metadata;
Expand Down Expand Up @@ -100,7 +101,33 @@ private void OnEntityCreatedHandler(EntityCreatedEventArgs args)
if (_stateManager == null)
_stateManager = Context.GetService<IStateManager>();

var entry = _stateManager.StartTrackingFromQuery(_lastEntityType, args.Entity, ValueBuffer.Empty);

// It is a real pain to register entity in change tracker
//
InternalEntityEntry entry = null;

foreach (var key in _lastEntityType.GetKeys())
{
//TODO: Find faster way
var keyArray = key.Properties.Where(p => p.PropertyInfo != null || p.FieldInfo != null).Select(p =>
p.PropertyInfo != null
? p.PropertyInfo.GetValue(args.Entity)
: p.FieldInfo.GetValue(args.Entity)).ToArray();

if (keyArray.Length == key.Properties.Count)
{
entry = _stateManager.TryGetEntry(key, keyArray);

if (entry != null)
break;
}
}

if (entry == null)
{
entry = _stateManager.StartTrackingFromQuery(_lastEntityType, args.Entity, ValueBuffer.Empty);
}

args.Entity = entry.Entity;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="linq2db" Version="3.1.2" />
<PackageReference Include="linq2db" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.3" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.0.0-alpha0001" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.3" />
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using Microsoft.EntityFrameworkCore;
using NUnit.Framework;

namespace LinqToDB.EntityFrameworkCore.Tests
namespace LinqToDB.EntityFrameworkCore.PostgreSQL.Tests
{
public class NpgSqlTests : TestsBase
{
Expand Down
105 changes: 105 additions & 0 deletions Tests/LinqToDB.EntityFrameworkCore.PostgreSQL.Tests/SampleTests/AAA.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System;
using System.Threading.Tasks;

namespace LinqToDB.EntityFrameworkCore.PostgreSQL.Tests.SampleTests
{
public class Unit
{

}

public static class ExceptionExtensions
{
public static Unit Throw(this Exception e) => throw e;
}

public static class AAA
{
public static ArrangeResult<T, Unit> Arrange<T>(this T @object, Action<T> action)
{
action(@object);
return new ArrangeResult<T, Unit>(@object, default);
}

public static ArrangeResult<T, Unit> Arrange<T>(T @object)
=> new ArrangeResult<T, Unit>(@object, default);

public static ArrangeResult<T, TMock> Arrange<T, TMock>(this TMock mock, Func<TMock, T> @object)
=> new ArrangeResult<T, TMock>(@object(mock), mock);

public static ActResult<T, TMock> Act<T, TMock>(this ArrangeResult<T, TMock> arrange, Action<T> act)
{
try
{
act(arrange.Object);
return new ActResult<T, TMock>(arrange.Object, arrange.Mock, default);
}
catch (Exception e)
{
return new ActResult<T, TMock>(arrange.Object, arrange.Mock, e);
}
}

public static ActResult<TResult, TMock> Act<T, TMock, TResult>(this ArrangeResult<T, TMock> arrange, Func<T, TResult> act)
{
try
{
return new ActResult<TResult, TMock>(act(arrange.Object), arrange.Mock, default);
}
catch (Exception e)
{
return new ActResult<TResult, TMock>(default, arrange.Mock, e);
}
}

public static void Assert<T, TMock>(this ActResult<T, TMock> act, Action<T> assert)
{
act.Exception?.Throw();
assert(act.Object);
}

public static void Assert<T, TMock>(this ActResult<T, TMock> act, Action<T, TMock> assert)
{
act.Exception?.Throw();
assert(act.Object, act.Mock);
}

public static Task<ArrangeResult<T, Unit>> ArrangeAsync<T>(T @object)
=> Task.FromResult(new ArrangeResult<T, Unit>(@object, default));

public static async Task<ActResult<TResult, TMock>> Act<T, TMock, TResult>(this Task<ArrangeResult<T, TMock>> arrange, Func<T, Task<TResult>> act)
{
var a = await arrange;
try
{
return new ActResult<TResult, TMock>(await act(a.Object), a.Mock, default);
}
catch (Exception e)
{
return new ActResult<TResult, TMock>(default, a.Mock, e);
}
}

public static async Task Assert<T, TMock>(this Task<ActResult<T, TMock>> act, Func<T, Task> assert)
{
var result = await act;
await assert(result.Object);
}

public readonly struct ArrangeResult<T, TMock>
{
internal ArrangeResult(T @object, TMock mock) => (Object, Mock) = (@object, mock);
internal T Object { get; }
internal TMock Mock { get; }
}

public readonly struct ActResult<T, TMock>
{
internal ActResult(T @object, TMock mock, Exception exception)
=> (Object, Mock, Exception) = (@object, mock, exception);
internal T Object { get; }
internal TMock Mock { get; }
internal Exception Exception { get; }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace LinqToDB.EntityFrameworkCore.PostgreSQL.Tests.SampleTests
{
public sealed class Child : IHasWriteableId<Child, long>
{
public Id<Child, long> Id { get; set; }
public Id<Entity, long> ParentId { get; set; }
public string Name { get; set; }
public Entity Parent { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace LinqToDB.EntityFrameworkCore.PostgreSQL.Tests.SampleTests
{
public static class DataContextExtensions
{
public static Id<T, long> Insert<T>(this IDataContext context, T item)
where T : IHasWriteableId<T, long>
{
item.Id = context.InsertWithInt64Identity(item).AsId<T>();
return item.Id;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Collections.Generic;

namespace LinqToDB.EntityFrameworkCore.PostgreSQL.Tests.SampleTests
{
public sealed class Detail : IHasWriteableId<Detail, long>
{
public Id<Detail, long> Id { get; set; }
public Id<Entity, long> MasterId { get; set; }
public string Name { get; set; }
public Entity Master { get; set; }
public IEnumerable<SubDetail> Details { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Collections.Generic;

namespace LinqToDB.EntityFrameworkCore.PostgreSQL.Tests.SampleTests
{
public sealed class Entity : IHasWriteableId<Entity, long>
{
public Id<Entity, long> Id { get; set; }
public string Name { get; set; }

public IEnumerable<Detail> Details { get; set; }
public IEnumerable<Child> Children { get; set; }
public IEnumerable<Entity2Item> Items { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace LinqToDB.EntityFrameworkCore.PostgreSQL.Tests.SampleTests
{
public sealed class Entity2Item
{
public Id<Entity, long> EntityId { get; set; }
public Entity Entity { get; set; }
public Id<Item, long> ItemId { get; set; }

public Entity2Item()
{
}

public Item Item { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace LinqToDB.EntityFrameworkCore.PostgreSQL.Tests.SampleTests
{
public interface IHasId<T, TId> where T: IHasId<T, TId>
{
Id<T, TId> Id { get; }
}

public interface IHasWriteableId<T, TId> : IHasId<T, TId> where T: IHasWriteableId<T, TId>
{
new Id<T, TId> Id { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Collections.Generic;

namespace LinqToDB.EntityFrameworkCore.PostgreSQL.Tests.SampleTests
{
public static class Id
{
public static Id<T, long> AsId<T>(this long id) where T : IHasId<T, long> => id.AsId<T, long>();

public static Id<T, TId> AsId<T, TId>(this TId id) where T : IHasId<T, TId>
=> new Id<T, TId>(id);
}

public readonly struct Id<T, TId> where T : IHasId<T, TId>
{
internal Id(TId value) => Value = value;
TId Value { get; }

public static implicit operator TId (in Id<T, TId> id) => id.Value;
public static bool operator == (Id<T, TId> left, Id<T, TId> right)
=> EqualityComparer<TId>.Default.Equals(left.Value, right.Value);
public static bool operator != (Id<T, TId> left, Id<T, TId> right) => !(left == right);

public override string ToString() => $"{typeof(T).Name}({Value})";
public bool Equals(Id<T, TId> other) => EqualityComparer<TId>.Default.Equals(Value, other.Value);
public override bool Equals(object obj) => obj is Id<T, TId> other && Equals(other);
public override int GetHashCode() => EqualityComparer<TId>.Default.GetHashCode(Value);
}
}
Loading

0 comments on commit 064c8f0

Please sign in to comment.