Skip to content

UseOptions / UseMappingSchema #4861

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

Open
wants to merge 48 commits into
base: master
Choose a base branch
from
Open

UseOptions / UseMappingSchema #4861

wants to merge 48 commits into from

Conversation

igor-tkachev
Copy link
Member

@igor-tkachev igor-tkachev commented Mar 8, 2025

A new method, UseOptions, has been introduced to allow temporary changes to IDataContext options, with automatic restoration afterward.

IDataContext options are immutable, meaning they cannot be changed once set. With this feature, it is now possible to override options within a specific execution context while ensuring they revert to their original state when no longer needed.

Several overloads are available to fit different use cases:

using (db.UseOptions(o => o
	.WithOptions<LinqOptions>    (co => co with { OptimizeJoins = false })
	.WithOptions<BulkCopyOptions>(bo => bo with { BulkCopyType = BulkCopyType.RowByRow })))
{
	// ...
}
using (db.UseOptions<DataContextOptions>(o => o with { CommandTimeout = 45 }))
{
	// ...
}
using (db.UseLinqOptions(o => o with { CompareNulls = CompareNulls.LikeSql }))
{
	// ...
}

@sdanyliv
Copy link
Member

sdanyliv commented Mar 9, 2025

I don't like this API. UseOptions may change data provider on the fly and probably has a lot of other sideeffects. If you need some specific feature, we can discuss.

@igor-tkachev
Copy link
Member Author

igor-tkachev commented Mar 9, 2025

I don't like this API. UseOptions may change data provider on the fly and probably has a lot of other sideeffects. If you need some specific feature, we can discuss.

I get the concern, but UseOptions doesn’t allow changing everything on the fly. Things like switching the data provider or other options that fundamentally change IDataContext won’t have any effect. The main goal here is to tweak settings that affect SQL generation and execution modes within the current context. So it’s more about flexibility in query behavior rather than altering core IDataContext functionality.

Maybe we need to mention it in the documentation to avoid confusion.

@sdanyliv
Copy link
Member

sdanyliv commented Mar 9, 2025

I wouldn't introduce it this way. Instead, you can use the Builder pattern to restrict modifications to only the allowed options. While direct options passing may seem flexible, it often leads to maintainability issues down the road. The API should be self-descriptive.

@igor-tkachev
Copy link
Member Author

Introducing a separate Builder pattern just to restrict modifications would add unnecessary complexity. Users will need to learn yet another part of an already feature-rich API, making it harder to adopt. Instead, we can rely on clear documentation and enforce restrictions at runtime by throwing exceptions when unsupported options are modified.

@igor-tkachev igor-tkachev marked this pull request as draft March 9, 2025 18:41
@sdanyliv
Copy link
Member

sdanyliv commented Mar 9, 2025

We can't ensure clear documentation when additional options are introduced. However, users might discover undocumented features that work and start relying on them. Later, if we make changes, everything undocumented could stop working. I want to prevent users from making mistakes due to a lack of documentation. Additionally, a strictly limited builder would be more precise than simply modifying the options class.

Exceptions are useful, but why not prevent mistakes entirely by designing the builder in a way that makes them impossible? If something isn’t allowed, it simply won’t compile—this is far better than letting users try unsupported options and fail at runtime.

@igor-tkachev
Copy link
Member Author

Our API is already complex enough, and we shouldn’t make it even harder to use. The proposed UseOptions method follows the existing options-setting API, so users don’t have to learn something entirely new.

We already have a Builder-based API - Merge API, and to be honest, I can't use it without constantly referring to the documentation, examples, or even the source code. All those "prevent mistakes entirely by designing the builder" arguments don’t help in practice. Complex APIs that builders tend to become are a bad idea. Reusing the existing, familiar API is a good one.

As for user mistakes, in the latest update, I’ve added the necessary checks to ensure that invalid modifications throw an exception. I wouldn’t have even done that, but maybe this will give you more peace of mind :)

@igor-tkachev igor-tkachev marked this pull request as ready for review March 9, 2025 23:22
@viceroypenguin
Copy link
Contributor

We already have a Builder-based API - Merge API, and to be honest, I can't use it without constantly referring to the documentation, examples, or even the source code. All those "prevent mistakes entirely by designing the builder" arguments don’t help in practice. Complex APIs that builders tend to become are a bad idea. Reusing the existing, familiar API is a good one.

I find that interesting, because I find the current version of the Merge API to be incredibly intuitive. The intellisense based on limited types means that the next available method(s) are immediately available and obvious to select which one should be used.

@igor-tkachev
Copy link
Member Author

igor-tkachev commented Mar 10, 2025

I find that interesting, because I find the current version of the Merge API to be incredibly intuitive. The intellisense based on limited types means that the next available method(s) are immediately available and obvious to select which one should be used.

Oh, that’s probably because I personally took part in refining the Merge API to make it as polished as it is! :)

But that doesn’t mean it’s simple. Like any API built using the Builder pattern, it comes with an additional learning curve, is often inconsistent with existing APIs, and isn’t always intuitive. It also adds complexity to an already feature-rich library API.

Sometimes, a Builder is necessary - Merge API is a good example. It’s complex, but I don’t see any alternative for it. However, making the entire library follow this pattern would be a huge mistake.

In the case of UseOptions, there’s simply no need for this approach. The existing options API is already familiar to users, and we don’t gain anything by introducing an extra API. It would just make things harder for no real benefit.

@linq2db linq2db deleted a comment from azure-pipelines bot Jun 16, 2025
@linq2db linq2db deleted a comment from azure-pipelines bot Jun 17, 2025
@linq2db linq2db deleted a comment from azure-pipelines bot Jun 17, 2025
@linq2db linq2db deleted a comment from azure-pipelines bot Jun 19, 2025
@linq2db linq2db deleted a comment from azure-pipelines bot Jun 19, 2025
@linq2db linq2db deleted a comment from azure-pipelines bot Jun 19, 2025
@linq2db linq2db deleted a comment from azure-pipelines bot Jun 19, 2025
@MaceWindu
Copy link
Contributor

/azp run test-all

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@linq2db linq2db deleted a comment from igor-tkachev Jun 20, 2025
@linq2db linq2db deleted a comment from azure-pipelines bot Jun 20, 2025
@linq2db linq2db deleted a comment from linq2dbot Jun 20, 2025
@linq2db linq2db deleted a comment from igor-tkachev Jun 20, 2025
@linq2db linq2db deleted a comment from azure-pipelines bot Jun 20, 2025
@linq2db linq2db deleted a comment from linq2dbot Jun 20, 2025
@linq2db linq2db deleted a comment from linq2dbot Jun 20, 2025
@linq2db linq2db deleted a comment from linq2dbot Jun 20, 2025
@linq2db linq2db deleted a comment from igor-tkachev Jun 20, 2025
@linq2db linq2db deleted a comment from azure-pipelines bot Jun 20, 2025
@linq2dbot
Copy link

Test baselines changed by this PR. Don't forget to merge/close baselines PR after this pr merged/closed.

Copy link
Contributor

@MaceWindu MaceWindu left a comment

Choose a reason for hiding this comment

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

part 1

/// <summary>
/// Gets the default options.
/// </summary>
IOptionSet Default { get; }
Copy link
Contributor

Choose a reason for hiding this comment

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

I've checked Default implementation and use and see a lot of inconsistency around it.
It returns either private empty, public empty, public default or options from Common.Configuration: would be nice if we can rid of empty/default duplication here and have only public Default (backed either by Common.Configuration or _empty value) including options init in DataOptions (lines 70-75, especially taking into account that some code there reference to Common.Configuration which is implementation detail of specific option set and shouldn't be used here)

{
interface IReapplicable<T>
{
Action? Apply(T obj, object? previousObject);
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like we can change type from object to IOptionSet if we inherit interface from IOptionSet

Comment on lines +86 to +89
_sb.Value
.Append('.')
.Append(GetObjectID(data?.Method))
;
Copy link
Contributor

Choose a reason for hiding this comment

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

Add(GetObjectID(data?.Method))

_sb.Value
.Append('.')
.Append(GetObjectID(data))
;
return this;
Copy link
Contributor

Choose a reason for hiding this comment

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

Add(GetObjectID(data))

Copy link
Contributor

Choose a reason for hiding this comment

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

and maybe in other places where transform data? Like Add(object?)

Copy link
Contributor

@MaceWindu MaceWindu left a comment

Choose a reason for hiding this comment

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

part 2

Comment on lines +149 to +151
var action = a.Apply(obj, previousItem);
if (action != null)
actions += action;
Copy link
Contributor

Choose a reason for hiding this comment

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

could be simplified to
actions += a.Apply(obj, previousItem);
also below and DataOptions Reapply methods

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

5 participants