Skip to content

Commit eb3de4f

Browse files
committed
Created factory methods in IEntityPropertiesProvider to get better IntelliSense support.
1 parent 5937c95 commit eb3de4f

File tree

26 files changed

+429
-327
lines changed

26 files changed

+429
-327
lines changed

samples/Thinktecture.EntityFrameworkCore.SqlServer.Samples/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ private static async Task DoBulkInsertSpecifiedColumnsIntoRealTableAsync(DemoDbC
193193
// alternative ways to specify the column:
194194
// * c => new { c.Id }
195195
// * c => c.Id
196-
// * new SqlBulkInsertOptions { PropertiesProvider = PropertiesProvider.From<Customer>(c => new { c.Id })}
196+
// * new SqlServerBulkInsertOptions { PropertiesToInsert = IPropertiesProvider.Include<Customer>(c => new { c.Id })}
197197
await ctx.BulkInsertAsync(new[] { customersToInsert }, c => new { c.Id });
198198

199199
var insertedCustomer = await ctx.Customers.FirstAsync(c => c.Id == customersToInsert.Id);

samples/Thinktecture.EntityFrameworkCore.Sqlite.Samples/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.Extensions.DependencyInjection;
22
using Thinktecture.Database;
3+
using Thinktecture.EntityFrameworkCore.BulkOperations;
34

45
namespace Thinktecture;
56

@@ -139,7 +140,7 @@ private static async Task DoBulkInsertSpecifiedColumnsIntoRealTableAsync(DemoDbC
139140
// alternative ways to specify the column:
140141
// * c => new { c.Id }
141142
// * c => c.Id
142-
// * new SqlBulkInsertOptions { PropertiesProvider = PropertiesProvider.From<Customer>(c => new { c.Id })}
143+
// * new SqliteBulkInsertOptions { PropertiesToInsert = IPropertiesProvider.Include<Customer>(c => new { c.Id })}
143144
await ctx.BulkInsertAsync(new[] { customersToInsert }, c => new { c.Id, c.FirstName, c.LastName });
144145

145146
var insertedCustomer = await ctx.Customers.FirstAsync(c => c.Id == customersToInsert.Id);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using Microsoft.EntityFrameworkCore.Metadata;
2+
using Thinktecture.EntityFrameworkCore.Data;
3+
4+
namespace Thinktecture.EntityFrameworkCore.BulkOperations;
5+
6+
internal class DefaultPropertiesEntityPropertiesProvider : IEntityPropertiesProvider
7+
{
8+
public IReadOnlyList<PropertyWithNavigations> GetPropertiesForTempTable(IEntityType entityType, bool? inlinedOwnTypes)
9+
{
10+
return DetermineProperties(entityType, inlinedOwnTypes, IEntityPropertiesProvider.TempTableFilter);
11+
}
12+
13+
public IReadOnlyList<PropertyWithNavigations> GetKeyProperties(IEntityType entityType, bool? inlinedOwnTypes)
14+
{
15+
var pk = entityType.FindPrimaryKey()?.Properties;
16+
17+
if (pk is null or { Count: 0 })
18+
throw new InvalidOperationException($"The entity '{entityType.Name}' has no primary key. Please provide key properties to perform JOIN/match on.");
19+
20+
return pk.Select(p => new PropertyWithNavigations(p, Array.Empty<INavigation>())).ToList();
21+
}
22+
23+
public IReadOnlyList<PropertyWithNavigations> GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes)
24+
{
25+
return DetermineProperties(entityType, inlinedOwnTypes, IEntityPropertiesProvider.InsertAndUpdateFilter);
26+
}
27+
28+
public IReadOnlyList<PropertyWithNavigations> GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes)
29+
{
30+
return DetermineProperties(entityType, inlinedOwnTypes, IEntityPropertiesProvider.InsertAndUpdateFilter);
31+
}
32+
33+
private static IReadOnlyList<PropertyWithNavigations> DetermineProperties(
34+
IEntityType entityType,
35+
bool? inlinedOwnTypes,
36+
Func<IProperty, IReadOnlyList<INavigation>, bool> filter)
37+
{
38+
var properties = entityType.GetProperties()
39+
.Where(p => filter(p, Array.Empty<INavigation>()))
40+
.Select(p => new PropertyWithNavigations(p, Array.Empty<INavigation>()))
41+
.ToList();
42+
43+
foreach (var navigation in entityType.GetOwnedTypesProperties(inlinedOwnTypes))
44+
{
45+
var navigations = new[] { navigation };
46+
properties.AddPropertiesAndOwnedTypesRecursively(navigation.TargetEntityType, navigations, inlinedOwnTypes, filter);
47+
}
48+
49+
return properties;
50+
}
51+
}
Lines changed: 5 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,13 @@
11
using System.Linq.Expressions;
2-
using System.Reflection;
3-
using Microsoft.EntityFrameworkCore.Metadata;
4-
using Thinktecture.EntityFrameworkCore.Data;
52

63
namespace Thinktecture.EntityFrameworkCore.BulkOperations;
74

85
/// <summary>
9-
/// Provides entity properties to work with.
6+
/// Obsolete class for backwards compatibility.
107
/// </summary>
11-
public sealed class EntityPropertiesProvider : IEntityPropertiesProvider
8+
[Obsolete($"This class is obsolete. Please use factory methods defined on '{nameof(IEntityPropertiesProvider)}' instead.", DiagnosticId = "TTEF1000")]
9+
public sealed class EntityPropertiesProvider
1210
{
13-
private readonly IReadOnlyList<MemberInfo> _members;
14-
15-
/// <summary>
16-
/// <see cref="IEntityPropertiesProvider"/> with 0 properties.
17-
/// </summary>
18-
public static readonly IEntityPropertiesProvider Empty = new EntityPropertiesProvider(Array.Empty<MemberInfo>());
19-
20-
/// <summary>
21-
/// Initializes new instance of <see cref="EntityPropertiesProvider"/>.
22-
/// </summary>
23-
/// <param name="members">Members to use.</param>
24-
public EntityPropertiesProvider(IReadOnlyList<MemberInfo> members)
25-
{
26-
_members = members ?? throw new ArgumentNullException(nameof(members));
27-
}
28-
29-
/// <inheritdoc />
30-
public IReadOnlyList<PropertyWithNavigations> GetPropertiesForTempTable(
31-
IEntityType entityType,
32-
bool? inlinedOwnTypes,
33-
Func<IProperty, IReadOnlyList<INavigation>, bool> filter)
34-
{
35-
return GetProperties(entityType, inlinedOwnTypes, filter);
36-
}
37-
38-
/// <inheritdoc />
39-
public IReadOnlyList<PropertyWithNavigations> GetKeyProperties(
40-
IEntityType entityType,
41-
bool? inlinedOwnTypes,
42-
Func<IProperty, IReadOnlyList<INavigation>, bool> filter)
43-
{
44-
return GetProperties(entityType, inlinedOwnTypes, filter);
45-
}
46-
47-
/// <inheritdoc />
48-
public IReadOnlyList<PropertyWithNavigations> GetPropertiesForInsert(
49-
IEntityType entityType,
50-
bool? inlinedOwnTypes,
51-
Func<IProperty, IReadOnlyList<INavigation>, bool> filter)
52-
{
53-
return GetProperties(entityType, inlinedOwnTypes, filter);
54-
}
55-
56-
/// <inheritdoc />
57-
public IReadOnlyList<PropertyWithNavigations> GetPropertiesForUpdate(
58-
IEntityType entityType,
59-
bool? inlinedOwnTypes,
60-
Func<IProperty, IReadOnlyList<INavigation>, bool> filter)
61-
{
62-
return GetProperties(entityType, inlinedOwnTypes, filter);
63-
}
64-
65-
private IReadOnlyList<PropertyWithNavigations> GetProperties(
66-
IEntityType entityType,
67-
bool? inlinedOwnTypes,
68-
Func<IProperty, IReadOnlyList<INavigation>, bool> filter)
69-
{
70-
return _members.ConvertToEntityProperties(entityType, inlinedOwnTypes, filter);
71-
}
72-
7311
/// <summary>
7412
/// Extracts members from the provided <paramref name="projection"/>.
7513
/// </summary>
@@ -79,12 +17,9 @@ private IReadOnlyList<PropertyWithNavigations> GetProperties(
7917
/// <exception cref="ArgumentNullException"><paramref name="projection"/> is <c>null</c>.</exception>
8018
/// <exception cref="ArgumentException">No members couldn't be extracted.</exception>
8119
/// <exception cref="NotSupportedException">The <paramref name="projection"/> contains unsupported expressions.</exception>
20+
[Obsolete($"This method is obsolete. Please use '{nameof(IEntityPropertiesProvider)}.{nameof(IEntityPropertiesProvider.Include)}' instead.", DiagnosticId = "TTEF1000")]
8221
public static IEntityPropertiesProvider From<T>(Expression<Func<T, object?>> projection)
8322
{
84-
ArgumentNullException.ThrowIfNull(projection);
85-
86-
var members = projection.ExtractMembers();
87-
88-
return members.Count == 0 ? Empty : new EntityPropertiesProvider(members);
23+
return IEntityPropertiesProvider.Include(projection);
8924
}
9025
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.Reflection;
2+
using Microsoft.EntityFrameworkCore.Metadata;
3+
using Thinktecture.EntityFrameworkCore.Data;
4+
5+
namespace Thinktecture.EntityFrameworkCore.BulkOperations;
6+
7+
internal sealed class ExcludingEntityPropertiesProvider : IEntityPropertiesProvider
8+
{
9+
private readonly IReadOnlyList<MemberInfo> _members;
10+
11+
public ExcludingEntityPropertiesProvider(IReadOnlyList<MemberInfo> members)
12+
{
13+
_members = members ?? throw new ArgumentNullException(nameof(members));
14+
}
15+
16+
public IReadOnlyList<PropertyWithNavigations> GetPropertiesForTempTable(IEntityType entityType, bool? inlinedOwnTypes)
17+
{
18+
return Filter(IEntityPropertiesProvider.Default.GetPropertiesForTempTable(entityType, inlinedOwnTypes));
19+
}
20+
21+
private IReadOnlyList<PropertyWithNavigations> Filter(IReadOnlyList<PropertyWithNavigations> properties)
22+
{
23+
return properties.Where(p => _members.All(m => m != p.Property.PropertyInfo && m != p.Property.FieldInfo))
24+
.ToList();
25+
}
26+
27+
public IReadOnlyList<PropertyWithNavigations> GetKeyProperties(IEntityType entityType, bool? inlinedOwnTypes)
28+
{
29+
return Filter(IEntityPropertiesProvider.Default.GetKeyProperties(entityType, inlinedOwnTypes));
30+
}
31+
32+
public IReadOnlyList<PropertyWithNavigations> GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes)
33+
{
34+
return Filter(IEntityPropertiesProvider.Default.GetPropertiesForInsert(entityType, inlinedOwnTypes));
35+
}
36+
37+
public IReadOnlyList<PropertyWithNavigations> GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes)
38+
{
39+
return Filter(IEntityPropertiesProvider.Default.GetPropertiesForUpdate(entityType, inlinedOwnTypes));
40+
}
41+
}
Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Linq.Expressions;
2+
using System.Reflection;
13
using Microsoft.EntityFrameworkCore.Metadata;
24
using Thinktecture.EntityFrameworkCore.Data;
35

@@ -8,51 +10,101 @@ namespace Thinktecture.EntityFrameworkCore.BulkOperations;
810
/// </summary>
911
public interface IEntityPropertiesProvider
1012
{
13+
/// <summary>
14+
/// An <see cref="IEntityPropertiesProvider"/> with 0 properties.
15+
/// </summary>
16+
public static readonly IEntityPropertiesProvider Empty = new IncludingEntityPropertiesProvider(Array.Empty<MemberInfo>());
17+
18+
/// <summary>
19+
/// An <see cref="IEntityPropertiesProvider"/> with all properties of an entity.
20+
/// </summary>
21+
public static readonly IEntityPropertiesProvider Default = new DefaultPropertiesEntityPropertiesProvider();
22+
23+
/// <summary>
24+
/// Creates a new <see cref="IEntityPropertiesProvider"/> with specified <paramref name="members"/>.
25+
/// </summary>
26+
/// <param name="members">Members to create the provider for.</param>
27+
/// <returns>A new instance of <see cref="IEntityPropertiesProvider"/>.</returns>
28+
public static IEntityPropertiesProvider Include(IReadOnlyList<MemberInfo> members)
29+
{
30+
return new IncludingEntityPropertiesProvider(members);
31+
}
32+
33+
/// <summary>
34+
/// Extracts members from the provided <paramref name="projection"/> and creates an <see cref="IEntityPropertiesProvider"/>
35+
/// which provides the specified members to the callers.
36+
/// </summary>
37+
/// <param name="projection">Projection to extract the members from.</param>
38+
/// <typeparam name="T">Type of the entity.</typeparam>
39+
/// <returns>An instance of <see cref="IEntityPropertiesProvider"/> containing members extracted from <paramref name="projection"/>.</returns>
40+
/// <exception cref="ArgumentNullException"><paramref name="projection"/> is <c>null</c>.</exception>
41+
/// <exception cref="ArgumentException">No members couldn't be extracted.</exception>
42+
/// <exception cref="NotSupportedException">The <paramref name="projection"/> contains unsupported expressions.</exception>
43+
public static IEntityPropertiesProvider Include<T>(Expression<Func<T, object?>> projection)
44+
{
45+
ArgumentNullException.ThrowIfNull(projection);
46+
47+
var members = projection.ExtractMembers();
48+
49+
return members.Count == 0 ? Empty : new IncludingEntityPropertiesProvider(members);
50+
}
51+
52+
/// <summary>
53+
/// Extracts members from the provided <paramref name="projection"/> and creates an <see cref="IEntityPropertiesProvider"/>
54+
/// which provides all properties of the corresponding entity besides the specified members.
55+
/// </summary>
56+
/// <param name="projection"></param>
57+
/// <typeparam name="T"></typeparam>
58+
/// <returns></returns>
59+
public static IEntityPropertiesProvider Exclude<T>(Expression<Func<T, object?>> projection)
60+
{
61+
ArgumentNullException.ThrowIfNull(projection);
62+
63+
var members = projection.ExtractMembers();
64+
65+
return members.Count == 0 ? Default : new ExcludingEntityPropertiesProvider(members);
66+
}
67+
1168
/// <summary>
1269
/// Determines properties to include into a temp table into.
1370
/// </summary>
1471
/// <param name="entityType">Entity type.</param>
1572
/// <param name="inlinedOwnTypes">Indication whether inlined (<c>true</c>), separated (<c>false</c>) or all owned types to return.</param>
16-
/// <param name="filter">Filter.</param>
1773
/// <returns>Properties to include into a temp table.</returns>
18-
IReadOnlyList<PropertyWithNavigations> GetPropertiesForTempTable(
19-
IEntityType entityType,
20-
bool? inlinedOwnTypes,
21-
Func<IProperty, IReadOnlyList<INavigation>, bool> filter);
74+
IReadOnlyList<PropertyWithNavigations> GetPropertiesForTempTable(IEntityType entityType, bool? inlinedOwnTypes);
2275

2376
/// <summary>
2477
/// Determines properties to include into a temp table into.
2578
/// </summary>
2679
/// <param name="entityType">Entity type.</param>
2780
/// <param name="inlinedOwnTypes">Indication whether inlined (<c>true</c>), separated (<c>false</c>) or all owned types to return.</param>
28-
/// <param name="filter">Filter.</param>
2981
/// <returns>Properties to include into a temp table.</returns>
30-
IReadOnlyList<PropertyWithNavigations> GetKeyProperties(
31-
IEntityType entityType,
32-
bool? inlinedOwnTypes,
33-
Func<IProperty, IReadOnlyList<INavigation>, bool> filter);
82+
IReadOnlyList<PropertyWithNavigations> GetKeyProperties(IEntityType entityType, bool? inlinedOwnTypes);
3483

3584
/// <summary>
3685
/// Determines properties to insert into a (temp) table.
3786
/// </summary>
3887
/// <param name="entityType">Entity type.</param>
3988
/// <param name="inlinedOwnTypes">Indication whether inlined (<c>true</c>), separated (<c>false</c>) or all owned types to return.</param>
40-
/// <param name="filter">Filter.</param>
4189
/// <returns>Properties to insert into a (temp) table.</returns>
42-
IReadOnlyList<PropertyWithNavigations> GetPropertiesForInsert(
43-
IEntityType entityType,
44-
bool? inlinedOwnTypes,
45-
Func<IProperty, IReadOnlyList<INavigation>, bool> filter);
90+
IReadOnlyList<PropertyWithNavigations> GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes);
4691

4792
/// <summary>
4893
/// Determines properties to use in update of a table.
4994
/// </summary>
5095
/// <param name="entityType">Entity type.</param>
5196
/// <param name="inlinedOwnTypes">Indication whether inlined (<c>true</c>), separated (<c>false</c>) or all owned types to return.</param>
52-
/// <param name="filter">Filter.</param>
5397
/// <returns>Properties to use in update of a table.</returns>
54-
IReadOnlyList<PropertyWithNavigations> GetPropertiesForUpdate(
55-
IEntityType entityType,
56-
bool? inlinedOwnTypes,
57-
Func<IProperty, IReadOnlyList<INavigation>, bool> filter);
98+
IReadOnlyList<PropertyWithNavigations> GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes);
99+
100+
internal static bool TempTableFilter(IProperty property, IReadOnlyList<INavigation> navigations)
101+
{
102+
return navigations.Count == 0 || !property.IsKey();
103+
}
104+
105+
internal static bool InsertAndUpdateFilter(IProperty property, IReadOnlyList<INavigation> navigations)
106+
{
107+
return property.GetBeforeSaveBehavior() != PropertySaveBehavior.Ignore &&
108+
(navigations.Count == 0 || !navigations[^1].IsInlined() || !property.IsKey());
109+
}
58110
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.Reflection;
2+
using Microsoft.EntityFrameworkCore.Metadata;
3+
using Thinktecture.EntityFrameworkCore.Data;
4+
5+
namespace Thinktecture.EntityFrameworkCore.BulkOperations;
6+
7+
internal sealed class IncludingEntityPropertiesProvider : IEntityPropertiesProvider
8+
{
9+
private readonly IReadOnlyList<MemberInfo> _members;
10+
11+
public IncludingEntityPropertiesProvider(IReadOnlyList<MemberInfo> members)
12+
{
13+
_members = members ?? throw new ArgumentNullException(nameof(members));
14+
}
15+
16+
public IReadOnlyList<PropertyWithNavigations> GetPropertiesForTempTable(IEntityType entityType, bool? inlinedOwnTypes)
17+
{
18+
return GetProperties(entityType, inlinedOwnTypes, IEntityPropertiesProvider.TempTableFilter);
19+
}
20+
21+
public IReadOnlyList<PropertyWithNavigations> GetKeyProperties(IEntityType entityType, bool? inlinedOwnTypes)
22+
{
23+
return GetProperties(entityType, inlinedOwnTypes, static (_, _) => true);
24+
}
25+
26+
public IReadOnlyList<PropertyWithNavigations> GetPropertiesForInsert(IEntityType entityType, bool? inlinedOwnTypes)
27+
{
28+
return GetProperties(entityType, inlinedOwnTypes, IEntityPropertiesProvider.InsertAndUpdateFilter);
29+
}
30+
31+
public IReadOnlyList<PropertyWithNavigations> GetPropertiesForUpdate(IEntityType entityType, bool? inlinedOwnTypes)
32+
{
33+
return GetProperties(entityType, inlinedOwnTypes, IEntityPropertiesProvider.InsertAndUpdateFilter);
34+
}
35+
36+
private IReadOnlyList<PropertyWithNavigations> GetProperties(
37+
IEntityType entityType,
38+
bool? inlinedOwnTypes,
39+
Func<IProperty, IReadOnlyList<INavigation>, bool> filter)
40+
{
41+
return _members.ConvertToEntityProperties(entityType, inlinedOwnTypes, filter);
42+
}
43+
}

0 commit comments

Comments
 (0)