Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class UpsertCommandBuilder<TEntity> where TEntity : class
private readonly IEntityType _entityType;
private readonly ICollection<TEntity> _entities;
private Expression<Func<TEntity, object>>? _matchExpression;
private List<Expression<Func<TEntity, object>>>? _excludeExpressions;
private List<Expression<Func<TEntity, object?>>>? _excludeExpressions;
private Expression<Func<TEntity, TEntity, TEntity>>? _updateExpression;
private Expression<Func<TEntity, TEntity, bool>>? _updateCondition;
private bool _allowIdentityMatch;
Expand Down Expand Up @@ -67,7 +67,7 @@ public UpsertCommandBuilder<TEntity> AllowIdentityMatch()
/// </summary>
/// <param name="exclude">The expression that will identify the columns to exclude</param>
/// <returns>The current instance of the UpsertCommandBuilder</returns>
public UpsertCommandBuilder<TEntity> Exclude(Expression<Func<TEntity, object>> exclude)
public UpsertCommandBuilder<TEntity> Exclude(Expression<Func<TEntity, object?>> exclude)
{
if (_updateExpression != null)
throw new InvalidOperationException(Resources.FormatCantCallMethodWhenMethodHasBeenCalledAsTheyAreMutuallyExclusive(nameof(Exclude), nameof(WhenMatched)));
Expand Down Expand Up @@ -252,7 +252,7 @@ private IUpsertCommandRunner GetCommandRunner()
private UpsertCommandArgs<TEntity> CreateCommandArgs()
{
var matchProperties = _matchExpression != null
? ProcessPropertiesExpression(_entityType, _matchExpression, true)
? ProcessPropertiesExpression(_entityType, _matchExpression!, true)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, is this line required here?

Copy link
Author

@Dreamescaper Dreamescaper Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it complains about conversion without !:

Argument of type 'Expression<Func<TEntity, object>>' cannot be used for parameter 'propertiesExpression' of type 'Expression<Func<TEntity, object?>>' in 'IProperty[] UpsertCommandBuilder.ProcessPropertiesExpression(IEntityType entityType, Expression<Func<TEntity, object?>> propertiesExpression, bool match)' due to differences in the nullability of reference types.

I thought about making _matchExpression object? as well, but probably it shouldn't ever point to nullable property.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth validating that through tests, as the expression might be inflated on in memory EF

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you clarify what to validate exactly?

I thought about adding integration test with nullable Exclude property, but currently Nullable=false for integration tests project.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. good point - in which case it's probably safe to just merge it :D

I'll probably run a test (or check if we have one that won't have issues excluding columns that have null values). It shouldn't, but I want to try sanity check just to cover all bases

Copy link
Author

@Dreamescaper Dreamescaper Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about making _matchExpression object? as well, but probably it shouldn't ever point to nullable property.

Ah, I assume you mean this sentence. It's not that it wouldn't work or fail if matchExpression leads to nullable property - ProcessPropertiesExpression doesn't care if it's nullable or not, it simply inspects expression, that's it.

It's just semantically matchExpression leads to PK or unique index, so the column shouldn't be nullable.

: _entityType.GetProperties().Where(p => p.IsKey()).ToArray();
if (!_allowIdentityMatch && matchProperties.Any(p => p.ValueGenerated != ValueGenerated.Never))
throw new InvalidMatchColumnsException();
Expand Down Expand Up @@ -281,7 +281,7 @@ private UpsertCommandArgs<TEntity> CreateCommandArgs()
};
}

private static IProperty[] ProcessPropertiesExpression(IEntityType entityType, Expression<Func<TEntity, object>> propertiesExpression, bool match)
private static IProperty[] ProcessPropertiesExpression(IEntityType entityType, Expression<Func<TEntity, object?>> propertiesExpression, bool match)
{
static string UnknownPropertiesExceptionMessage(bool match)
=> match
Expand Down