-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Implement DbDataSource #70006
Implement DbDataSource #70006
Conversation
Note regarding the This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change. |
Tagging subscribers to this area: @roji, @ajcvickers Issue DetailsThis is a WIP PR for DbDataSource (see #64812), there's still some work to be done here. Closes #64812 With regards to the API review comments (#64812 (comment)):
The current naming in the PR for getting/renting a connection is CreateConnection (for closed) and CreateOpenConnection (for open, as a convenience).
|
src/libraries/System.Data.Common/src/System/Data/Common/DbDataSource.cs
Outdated
Show resolved
Hide resolved
} | ||
catch | ||
{ | ||
// Swallow to allow the original exception to bubble up |
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.
There might not be an original exception.
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.
That's true - I had an offline discussion with @NinoFloris about whether Close exceptions should generally bubble up.
On the one hand, Close failures aren't really relevant to the execution itself, which completed successfully - which is why the current code doesn't (ever) bubble them up (the idea behind this API is that the user doesn't care about connection-related details any more). It's a bit comparable to what happens if an internal logging failure occurs in some library; usually you don't want that to be bubbled up to the user, since logging is secondary and failure there doesn't affect the main operation.
On the other hand, Open exceptions do bubble up (since we can't execute), so it's maybe somewhat inconsistent.
I'm still leaning to not bubbling it up (updating comment to reflect the reasoning), but am interested in opinions to the contrary.
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.
Should it log the swallowed exceptions to DataCommonEventSource? There is ExceptionBuilder.TraceExceptionWithoutRethrow for that purpose, but it seems to be used in DataTable-related classes only.
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.
Good suggestion... I suspect it's only currently used in DataTable-related classes is that the main System.Data classes are abstractions only, and therefore don't need to swallow/report exceptions. I'll make that change - it would allow visibility to anyone who's really interested in these exceptions, and I don't see a downside to it.
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.
The commit message is "Trace swallowed exceptions and fix comments" but it doesn't add any tracing.
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.
Pushed it
src/libraries/System.Data.Common/src/System/Data/Common/DbDataSource.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Data.Common/src/System/Data/Common/DbDataSource.cs
Outdated
Show resolved
Hide resolved
public override void Prepare() | ||
=> _wrappedCommand.Prepare(); |
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.
In some providers (e.g., MySqlConnector), Prepare(Async)
can only be called if the command is associated with an open connection. (In MySQL Server, a prepared command is strongly associated with just one server session, so it needs to be known which session the command should be prepared on. Moreover, it doesn't make sense to open a connection, prepare the command, then close the connection without executing the command.)
So this method as written will fail with MySqlConnector. (And maybe other providers, too; I haven't checked them.)
Maybe that's MySqlConnector's problem to deal with, and it should either document that failure as a "known issue", or implement its own DbCommandWrapper
that opens the connection in Prepare
, keeps it open, and reuses that open connection in ExecuteXyz
?
For now, I don't think the core logic in DbDataSource needs to be adjusted to accommodate this, but simply raising the issue for awareness, in case it is very common across most ADO.NET providers.
(The workaround I envision would be setting a _isOpen
field to true
in Prepare
, then checking that field in other methods so Open
isn't called twice. However, this is probably overkill since calling Prepare
on a one-shot command is almost certainly pointless in most use cases.)
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.
Alternatively, throw ExceptionBuilder.NotSupportedOnDataSourceCommand();
in this implementation, and let providers opt into connectionless Prepare (via a specialised class) if they can support it. This approach might make sense if most providers can't support this.
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.
@bgrainger yeah, when I originally thought about this, I thought the base implementation should just delegate and let the provider throw, which is what most providers do (SqlClient doesn't throw for Prepare without a connection, but that seems to just be a no-op, which isn't useful behavior either). But the provider exception ("no open connection") is likely to be a bit cryptic in this context, and ExceptionBuilder.NotSupportedOnDataSourceCommand seems more informative.
In the DbDataSource world, Prepare on a connectionless command could mean "prepare this on the server in general", for databases where prepared statements aren't bound to specific connections (FWIW PostgreSQL is the same as MySQL in this respect, and AFAIK most databases). So there's at least some reasonable implementation for this - though it would definitely require a specific data-source (connectionless) implementation, and simple delegation wouldn't work out-of-the-box.
Another way to look at this (as you hint), is to open a connection and keep it open until the command is disposed. This would allow multiple prepared executions via the same "connectionless" command without having to deal with a connection, but it goes against the connectionless model we're introduced here. Another issue here is that it's (unfortunately) fairly common for users (and even ORMs, specifically EF6) to not dispose commands; in this case, it would mean that the connection is never disposed, causing a leak. I'd probably avoid this just for that.
So I'll change the default implement to throw ExceptionBuilder.NotSupportedOnDataSourceCommand() as you suggest; providers with connectionless prepare can indeed override and implement their logic in any case.
src/libraries/System.Data.Common/src/System/Data/Common/DbDataSource.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Data.Common/src/System/Data/Common/DbDataSource.cs
Outdated
Show resolved
Hide resolved
Thanks for the feedback @bgrainger, addressed your comments - let me know if you have anything else. |
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.
🐑:it:
@roji I don't know if this is still relevant, but here we go: One reason to bubble up all exceptions (if at all possible) is that developers need to be alerted of problems. Like, if there is network flakiness or some server issue, it needs to show up in error reporting. Yes, it might not make a difference during normal execution but there is still something wrong if There could also be internal bugs to some library or provider that better show up in error logs. There could be rare state corruption going on, and swallowing would hinder debugging efforts. |
@GSPP definitely relevant - we can still change stuff here until the release.
Right, and that's why these problems are logged - non-critical issues which don't hinder successful execution don't necessarily have to be raised as exceptions, which interrupt the actual user experience (e.g. fail a web request although the database operation did execute successfully). A good comparison here is what happens when calling DbConnection.Dispose; it's supposed to close an open connection, but Dispose isn't supposed to surface any errors (in general in .NET). |
Closes #64812
See naming discussion in #64812 (comment)