New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add InsertWhenNotMatched
extension for constant value
#4239
base: master
Are you sure you want to change the base?
Add InsertWhenNotMatched
extension for constant value
#4239
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to add Update
methods as well, so as not to limit to Insert
branch of Merge()
?
Also, please consider what the API should look like if one wanted to do InsertWhenNotMatchedAnd()
with setting multiple columns.
this IMergeableSource<TTarget, TSource> merge, | ||
[InstantHandle] Expression<Func<TTarget, TV>> extract, | ||
[SkipIfConstant] TV value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this IMergeableSource<TTarget, TSource> merge, | |
[InstantHandle] Expression<Func<TTarget, TV>> extract, | |
[SkipIfConstant] TV value) | |
this IMergeableSource<TTarget, TSource> merge, | |
[InstantHandle] Expression<Func<TTarget, TV>> extract, | |
[SkipIfConstant] TV value) |
using (var db = GetDataContext(context)) | ||
using (db.CreateLocalTable<ReviewIndex>()) | ||
{ | ||
((ITable<IReviewIndex>)db.GetTable<ReviewIndex>()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
((ITable<IReviewIndex>)db.GetTable<ReviewIndex>()) | |
db.GetTable<ReviewIndex>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see some validation that the data actually made it into the database; see the other tests in this file for the types of validation that should be done.
.Merge() | ||
.Using(ReviewIndex.Data) | ||
.On(x => new { x.Id }, x => new { x.Id }) | ||
.InsertWhenNotMatched(s => s.Id, 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add more than one InsertWhenNotMatched()
to validate that more than one field can be set?
InsertWhenNotMatched
extension for constant value
Would it be possible to have an API similar to the one already existing for standalone insert/update? src
.Merge()
.On(...)
.Set(x => x.Field, 1)
.Set(x => x.Field, y => y.OtherField)
.InsertWhenNotMatched()
.Set(x => x.Field, 2)
.UpdateWhenMatched()
.Merge(); |
src
.Merge().Using()
.On()
.InsertWhenNotMatchedAnd()
.Set()
.Set()
.InsertWhenNotMatched()
.Set()
.Set()
.UpdateWhenMatched()
.Set()
.Merge(); |
Doesn't that feels backward compared to existing insert, update apis that have the action as the final call? In any case I prefer any option (before/after) that looks similar to existing |
Oh, right. I forgot that the existing methods have the action as the last one. It's backwards to my natural thinking, but I agree with keeping the behavior of existing APIs. |
Hi guys, to be consistent with the EF implementation, I would see the API like this
Also this would allow using the existing Set. Let me know and let's agree to an API |
Generally speaking I'd say internal consistency with other existing linq2db API is more important than consistency with EF. Let's go into the details, the existing signature of We can introduce an overload To lead users into the right direction, I think we would need a new type for the Set calls: interface ISettable
{
Set<V>(V property, V value)
}
// We then have 2 new overloads for dynamic setters:
void InsertWhenNotMatched(Action<ISettable, Src, Target>);
void UpdateWhenMatched(Action<ISettable, Src, Target>);
// Which could be used like so:
src
.Merge()
.On(...)
.InsertWhenNotMatched((setter, src, target) => setter
.Set(target.Field1, 2)
.Set(target.Field2, src.Delta)
)
.UpdateWhenMatched((setter, src, target) => setter
.Set(x.Field1, 0)
.Set(x.Field2, target.Field1 - src.Delta)
)
.Merge() Personnally, I don't like this proposal much:
One nice thing with linq2db // Composable logic
public static IUpdatable<T> SetLastChange(this IUpdatable<T> src)
where T: ILastChange
{
return src
.Set(x => x.ChangedBy, currentUser)
.Set(x => x.ChangedOn, DateTime.Now);
}
var query = src
.Where(x => x.Id == 3)
.Set(x => x.Name, "John") // Static setters
.SetLastChange(); // Composition
// Dynamic setters, SET is conditionally generated
if (freezeData)
query = query.Set(x => x.Locked, true);
query.Update(); And that kind of programming is much harder when all your setters must be inside an So for those reasons, personally I would stick to linq2db style: // Which could be used like so:
src
.Merge()
.On(...)
.Set(x => x.Field1, 2)
.Set(x => x.Field2, src => src.Delta)
.InsertWhenNotMatched()
.Set(x => x.Field1, 0)
.Set(x => x.Field2, (src, target) => target.Field1 - src.Delta)
.UpdateWhenMatched()
.Merge() |
Being consistent, indeed is sometimes more important than adding a new behavior. In EF, indeed you are unable to use dynamic programming style. indeed make more sense. Having these, I think it makes more sense to add new Unfortunatelly I have a local setup that prevents me from running tests, so I am afraid I will be unable to check regressions. #4241 |
Lets add new set of |
I think this is important to discuss a little more here. A long time ago, my dad had a HP calculator that used reverse polish notation. You type "3 enter 4 +" to find out what 3+4 is. It's really hard to follow unless you're used to it. In SQL we have a bit of the same, where SQL starts by selecting fields, but later comes what we are selecting from (although granted it's easy to read in english). Within LINQ, there is a very logical flow: you start by selecting the data table (FROM), and then you proceed with filter (WHERE), select, etc. Similarly with updates, in SQL it's backwards by stating what to modify before we specify what we are modifying. In LINQ in general all commands follow this flow: what -> filter/select/etc -> execute This lets us programmatically build complex queries, and as a last step, execute them ( what -> filter -> set commands -> execute In my mind, what from/to & how -> operations -> execute //what from
db.Table1
//do
.Merge()
//what to
.Using(db.Table2)
//how
.On(...)
//operations
.UpdateWhenMatched(...)
.InsertWhenNotMatched(...)
//execute
.Merge() I don't think there's anything about the current design that would lead users to believe that If there are conflicting thoughts here, I would remove ambiguity altogether, and simply add overloads to .UpdateWhenMatched(x => x.Active, false)
.UpdateWhenMatched(x => x.Price, (dest, src) => dest.Price + src.Price) Or use the builder syntax. .UpdateWhenMatched(b => b
.Set(x => x.Active, true)
.Set(x => x.Price, (dest, src) => dest.Price + src.Price)) If those are not options, I would certainly put Set after UpdateWhenMatched, just like we write On after Merge and Using, and Set after From and Where. src
.Merge()
.Using()
.On()
.InsertWhenNotMatched()
.Set()
.Set()
.UpdateWhenMatched()
.Set()
.Merge(); The biggest reason I don't care for this is clarity, as formatting guidelines would have all the periods in a row. But I think it would be clearly wrong to have the Finally, there is an additional argument to support my suggestion: if Set comes before src
.Merge()
.Using()
.On()
.Set(x => x.Price, (dest, src) => dest.Price + src.Price) // valid
.UpdateWhenMatched()
.Merge();
src
.Merge()
.Using()
.On()
.Set(x => x.Price, (dest, src) => dest.Price + src.Price) // invalid - there isn't a dest
.InsertWhenNotMatched()
.Merge(); |
I agree that mandating expression trees such as the below is not the best solution to allow for dynamic programming: .InsertWhenNotMatched((setter, src, target) => setter
.Set(target.Field1, 2)
.Set(target.Field2, src.Delta)
) It essentially leaves us almost right where we are now. I would use a typical delegate, like this: .InsertWhenNotMatched(b => b
.Set(target => target.Field1, 2)
.Set(target => target.Field2, (target, src) => src.Delta)
) Then it can be used like this: .InsertWhenNotMatched(b => {
if (updatePrice) {
b.Set(x => x.Price, newPrice);
}
}) We can explicitly state in the xml comments that the delegate will be executed immediately to build the operation (rather than when |
So to summarize, there are three different models we can use for this:
My vote is for 2, because it is more logical and reads better, and is consistent with the rest of the /to: @Shane32 |
@viceroypenguin, should we use then |
I vote for 3: Due to "conflict" with existing operation of Option 2 would be my second choice. |
Oh right, because Then yes, each operation would include a |
Indeed option 3. with a delegate doesn't have the composability / dynamic drawbacks of an expression. Merge is complicated so I have no objections to any of the three remaining contenders. I think consistency is important and the most consistent api to me would be 1 (first choice). The builder pattern has the merit of being the most straightforward to type and build; it's also very clear what merge case the Option 2 I like less because it's similar to existing apis but backwards. It also requires new method names to disambiguate what you want to do and I don't like neither FWIW I don't see what invalid usage can't be validated at compile-time in 1?
Likewise once you call |
Another "consistent" way - we use MS (3rd) option and implement the similar extensions for our Insert/Update methods. |
No description provided.