| | 1 | | using System.Linq.Expressions; |
| | 2 | | using System.Runtime.CompilerServices; |
| | 3 | |
|
| | 4 | | namespace NLightning.Infrastructure.Repositories.Database.Helpers; |
| | 5 | |
|
| | 6 | | using Persistence.Contexts; |
| | 7 | |
|
| | 8 | | public static class PrimaryKeyHelper |
| | 9 | | { |
| | 10 | | public static Expression<Func<TEntity, bool>>? GetPrimaryKeyExpression<TEntity>(object id, NLightningDbContext conte |
| | 11 | | where TEntity : class |
| 0 | 12 | | { |
| 0 | 13 | | var keyProperties = context.Model.FindEntityType(typeof(TEntity))?.FindPrimaryKey()?.Properties |
| 0 | 14 | | ?? throw new InvalidOperationException("Entity does not have a primary key defined."); |
| | 15 | |
|
| | 16 | | object[] keyValuesToUse; |
| | 17 | |
|
| 0 | 18 | | if (keyProperties.Count > 1) // We're dealing with composite keys |
| 0 | 19 | | { |
| 0 | 20 | | if (id is not ITuple idTuple) |
| 0 | 21 | | throw new ArgumentException($"The provided id must be a tuple with {keyProperties.Count} items " + |
| 0 | 22 | | $"for entity {typeof(TEntity).Name}.", nameof(id)); |
| | 23 | |
|
| 0 | 24 | | if (idTuple.Length != keyProperties.Count) |
| 0 | 25 | | throw new ArgumentException($"The number of items in the provided tuple ({idTuple.Length}) does not" + |
| 0 | 26 | | $" match the number of primary key properties ({keyProperties.Count}) " + |
| 0 | 27 | | $"for entity {typeof(TEntity).Name}.", nameof(id)); |
| | 28 | |
|
| 0 | 29 | | keyValuesToUse = new object[keyProperties.Count]; |
| 0 | 30 | | for (var i = 0; i < keyProperties.Count; i++) |
| 0 | 31 | | { |
| 0 | 32 | | var value = idTuple[i]; |
| | 33 | |
|
| 0 | 34 | | keyValuesToUse[i] = value ?? throw new ArgumentNullException( |
| 0 | 35 | | nameof(id), $"Item {i} in the provided tuple cannot be null."); |
| 0 | 36 | | } |
| 0 | 37 | | } |
| | 38 | | else // We're dealing with a single key |
| 0 | 39 | | { |
| 0 | 40 | | if (id is ITuple) |
| 0 | 41 | | throw new ArgumentException($"The provided id must not be a tuple for entity {typeof(TEntity).Name}.", |
| 0 | 42 | | nameof(id)); |
| | 43 | |
|
| 0 | 44 | | keyValuesToUse = |
| 0 | 45 | | [ |
| 0 | 46 | | id ?? throw new ArgumentNullException(nameof(id), "The provided id cannot be null.") |
| 0 | 47 | | ]; |
| 0 | 48 | | } |
| | 49 | |
|
| 0 | 50 | | var parameter = Expression.Parameter(typeof(TEntity), "e"); |
| 0 | 51 | | Expression? predicateBody = null; |
| | 52 | |
|
| 0 | 53 | | for (var i = 0; i < keyProperties.Count; i++) |
| 0 | 54 | | { |
| 0 | 55 | | var keyProperty = keyProperties[i]; |
| 0 | 56 | | var keyValue = keyValuesToUse[i]; |
| | 57 | | object? correctlyTypedKeyValue; |
| | 58 | |
|
| 0 | 59 | | var propertyClrType = keyProperty.ClrType; |
| 0 | 60 | | if (keyValue.GetType() != propertyClrType) |
| 0 | 61 | | { |
| | 62 | | try |
| 0 | 63 | | { |
| 0 | 64 | | var underlyingType = Nullable.GetUnderlyingType(propertyClrType); |
| 0 | 65 | | correctlyTypedKeyValue = Convert.ChangeType(keyValue, underlyingType ?? propertyClrType); |
| 0 | 66 | | } |
| 0 | 67 | | catch (Exception ex) |
| 0 | 68 | | { |
| 0 | 69 | | throw new ArgumentException( |
| 0 | 70 | | $"Key value '{keyValue}' (type: {keyValue.GetType().Name}) for property '{keyProperty.Name}' " + |
| 0 | 71 | | $"could not be converted to the expected type '{propertyClrType.Name}'.", ex); |
| | 72 | | } |
| 0 | 73 | | } |
| | 74 | | else |
| 0 | 75 | | { |
| 0 | 76 | | correctlyTypedKeyValue = keyValue; |
| 0 | 77 | | } |
| | 78 | |
|
| 0 | 79 | | var memberAccess = Expression.Property(parameter, keyProperty.Name); |
| 0 | 80 | | var constantValue = Expression.Constant(correctlyTypedKeyValue, propertyClrType); |
| 0 | 81 | | var equality = Expression.Equal(memberAccess, constantValue); |
| | 82 | |
|
| 0 | 83 | | predicateBody = predicateBody == null ? equality : Expression.AndAlso(predicateBody, equality); |
| 0 | 84 | | } |
| | 85 | |
|
| | 86 | | // This should not be reached if keyProperties exist and keyValuesToUse is populated. |
| 0 | 87 | | return predicateBody == null ? null : Expression.Lambda<Func<TEntity, bool>>(predicateBody, parameter); |
| 0 | 88 | | } |
| | 89 | | } |