• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

loresoft / FluentCommand / 26594173245

28 May 2026 06:28PM UTC coverage: 55.553% (+0.7%) from 54.902%
26594173245

push

github

pwelter34
Move JSON support, add docs and examples

1358 of 3215 branches covered (42.24%)

Branch coverage included in aggregate %.

103 of 234 new or added lines in 9 files covered. (44.02%)

371 existing lines in 26 files now uncovered.

4389 of 7130 relevant lines covered (61.56%)

312.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.92
/src/FluentCommand/Query/UpsertEntityBuilder.cs
1
using System.Linq.Expressions;
2
using System.ComponentModel.DataAnnotations;
3

4
using FluentCommand.Query.Generators;
5
using FluentCommand.Reflection;
6

7
namespace FluentCommand.Query;
8

9
/// <summary>
10
/// Provides a builder for constructing SQL UPSERT statements for a specific entity type with fluent, chainable methods.
11
/// </summary>
12
/// <typeparam name="TEntity">The type of the entity.</typeparam>
13
public class UpsertEntityBuilder<TEntity> : UpsertBuilder<UpsertEntityBuilder<TEntity>>
14
    where TEntity : class
15
{
16
    private static readonly TypeAccessor _typeAccessor = TypeAccessor.GetAccessor<TEntity>();
7✔
17

18
    /// <summary>
19
    /// Initializes a new instance of the <see cref="UpsertEntityBuilder{TEntity}"/> class.
20
    /// </summary>
21
    /// <param name="queryGenerator">The <see cref="IQueryGenerator"/> used to generate SQL expressions.</param>
22
    /// <param name="parameters">The list of <see cref="QueryParameter"/> objects for the query.</param>
23
    public UpsertEntityBuilder(
24
        IQueryGenerator queryGenerator,
25
        List<QueryParameter> parameters)
26
        : base(queryGenerator, parameters)
13✔
27
    {
28
    }
13✔
29

30
    /// <summary>
31
    /// Adds a value for the specified entity property and value.
32
    /// </summary>
33
    /// <typeparam name="TValue">The type of the value.</typeparam>
34
    /// <param name="property">An expression selecting the property to insert or update.</param>
35
    /// <param name="parameterValue">The value to insert or update for the property.</param>
36
    /// <returns>The same builder instance for method chaining.</returns>
37
    public UpsertEntityBuilder<TEntity> Value<TValue>(
38
        Expression<Func<TEntity, TValue>> property,
39
        TValue? parameterValue)
40
    {
41
        var propertyAccessor = GetPropertyAccessor(property);
×
42
        return Value(propertyAccessor.Column, parameterValue);
×
43
    }
44

45
    /// <summary>
46
    /// Conditionally adds a value for the specified entity property and value if the condition is met.
47
    /// </summary>
48
    /// <typeparam name="TValue">The type of the value.</typeparam>
49
    /// <param name="property">An expression selecting the property to insert or update.</param>
50
    /// <param name="parameterValue">The value to insert or update for the property.</param>
51
    /// <param name="condition">A function that determines whether to add the value, based on the property name and value.</param>
52
    /// <returns>The same builder instance for method chaining.</returns>
53
    public UpsertEntityBuilder<TEntity> ValueIf<TValue>(
54
        Expression<Func<TEntity, TValue>> property,
55
        TValue? parameterValue,
56
        Func<string, TValue?, bool> condition)
57
    {
58
        var propertyAccessor = GetPropertyAccessor(property);
×
59
        return ValueIf(propertyAccessor.Column, parameterValue, condition);
×
60
    }
61

62
    /// <summary>
63
    /// Adds values from the specified entity. If column names are provided,
64
    /// only those that match an entity property name will be included. Key columns are inferred from properties marked with <see cref="KeyAttribute"/>.
65
    /// </summary>
66
    /// <param name="entity">The entity to insert or update.</param>
67
    /// <param name="columnNames">The column names to include (optional).</param>
68
    /// <returns>The same builder instance for method chaining.</returns>
69
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="entity"/> is <c>null</c>.</exception>
70
    public UpsertEntityBuilder<TEntity> Values(
71
        TEntity entity,
72
        IEnumerable<string>? columnNames = null)
73
    {
74
        ArgumentNullException.ThrowIfNull(entity);
13✔
75

76
        var properties = _typeAccessor.GetProperties();
13✔
77
        if (properties == null)
13!
UNCOV
78
            return this;
×
79

80
        var columnSet = new HashSet<string>(columnNames ?? [], StringComparer.OrdinalIgnoreCase);
13!
81

82
        foreach (var property in properties)
272✔
83
        {
84
            if (columnSet.Count > 0 && !columnSet.Contains(property.Column))
123!
85
                continue;
86

87
            if (property.IsNotMapped || property.IsDatabaseGenerated)
123✔
88
                continue;
89

90
            Value(property.Column, property.GetValue(entity), property.MemberType);
111✔
91
        }
92

93
        AddEntityKeys();
13✔
94

95
        return this;
13✔
96
    }
97

98
    /// <summary>
99
    /// Adds a key column used to determine whether a row already exists.
100
    /// </summary>
101
    /// <typeparam name="TValue">The type of the property value.</typeparam>
102
    /// <param name="property">An expression selecting the key property.</param>
103
    /// <returns>The same builder instance for method chaining.</returns>
104
    public UpsertEntityBuilder<TEntity> Key<TValue>(Expression<Func<TEntity, TValue>> property)
105
    {
106
        var propertyAccessor = GetPropertyAccessor(property);
6✔
107
        return Key(propertyAccessor.Column);
6✔
108
    }
109

110
    /// <summary>
111
    /// Conditionally adds a key column used to determine whether a row already exists.
112
    /// </summary>
113
    /// <typeparam name="TValue">The type of the property value.</typeparam>
114
    /// <param name="property">An expression selecting the key property.</param>
115
    /// <param name="condition">A function that determines whether to add the key column.</param>
116
    /// <returns>The same builder instance for method chaining.</returns>
117
    public UpsertEntityBuilder<TEntity> KeyIf<TValue>(
118
        Expression<Func<TEntity, TValue>> property,
119
        Func<string, bool>? condition = null)
120
    {
UNCOV
121
        var propertyAccessor = GetPropertyAccessor(property);
×
122
        return KeyIf(propertyAccessor.Column, condition);
×
123
    }
124

125
    /// <summary>
126
    /// Adds an OUTPUT clause for the specified entity property.
127
    /// </summary>
128
    /// <typeparam name="TValue">The type of the property value.</typeparam>
129
    /// <param name="property">An expression selecting the property to output.</param>
130
    /// <param name="tableAlias">The alias for the table (optional).</param>
131
    /// <param name="columnAlias">The alias for the output column (optional).</param>
132
    /// <returns>The same builder instance for method chaining.</returns>
133
    public UpsertEntityBuilder<TEntity> Output<TValue>(
134
        Expression<Func<TEntity, TValue>> property,
135
        string? tableAlias = null,
136
        string? columnAlias = null)
137
    {
138
        var propertyAccessor = GetPropertyAccessor(property);
7✔
139
        return Output(propertyAccessor.Column, tableAlias, columnAlias);
7✔
140
    }
141

142
    /// <summary>
143
    /// Conditionally adds an OUTPUT clause for the specified entity property if the condition is met.
144
    /// </summary>
145
    /// <typeparam name="TValue">The type of the property value.</typeparam>
146
    /// <param name="property">An expression selecting the property to output.</param>
147
    /// <param name="tableAlias">The alias for the table (optional).</param>
148
    /// <param name="columnAlias">The alias for the output column (optional).</param>
149
    /// <param name="condition">A function that determines whether to add the OUTPUT clause.</param>
150
    /// <returns>The same builder instance for method chaining.</returns>
151
    public UpsertEntityBuilder<TEntity> OutputIf<TValue>(
152
        Expression<Func<TEntity, TValue>> property,
153
        string? tableAlias = null,
154
        string? columnAlias = null,
155
        Func<string, bool>? condition = null)
156
    {
UNCOV
157
        var propertyAccessor = GetPropertyAccessor(property);
×
158
        return OutputIf(propertyAccessor.Column, tableAlias, columnAlias, condition);
×
159
    }
160

161
    /// <summary>
162
    /// Builds the SQL UPSERT statement using the current configuration.
163
    /// </summary>
164
    /// <returns>A <see cref="QueryStatement"/> containing the SQL UPSERT statement and its parameters.</returns>
165
    public override QueryStatement? BuildStatement()
166
    {
167
        if (TableExpression == null)
13!
168
            Into(_typeAccessor.TableName, _typeAccessor.TableSchema);
13✔
169

170
        AddEntityKeys();
13✔
171

172
        return base.BuildStatement();
13✔
173
    }
174

175
    private void AddEntityKeys()
176
    {
177
        var properties = _typeAccessor.GetProperties();
26✔
178
        foreach (var property in properties)
544✔
179
        {
180
            if (property.IsNotMapped || !property.IsKey)
246✔
181
                continue;
182

183
            Key(property.Column);
14✔
184
        }
185
    }
26✔
186

187
    private static IMemberAccessor GetPropertyAccessor<TModel, TValue>(
188
        TypeAccessor typeAccessor,
189
        Expression<Func<TModel, TValue>> property)
190
    {
191
        ArgumentNullException.ThrowIfNull(property);
13✔
192

193
        var propertyAccessor = typeAccessor.FindProperty(property);
13✔
194
        if (propertyAccessor is null)
13!
UNCOV
195
            throw new ArgumentException("The specified property does not exist on the entity.", nameof(property));
×
196

197
        return propertyAccessor;
13✔
198
    }
199

200
    private static IMemberAccessor GetPropertyAccessor<TValue>(Expression<Func<TEntity, TValue>> property)
201
        => GetPropertyAccessor(_typeAccessor, property);
13✔
202
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc