-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Concrete initial v3 API change proposal #1976
Comments
I applaude the removal of one of the nugets and just strong name it, I'm just not sure why you think it would not require recompilation? Either my Solution uses the strongname nuget and I need to remove it in my Sln and rebuild, or it uses the other one, which was previously not strong named and now it is and that's a breaking change, according to MS.
I like that too, but with the recompilation required due to breaking change stuff and many running WarningsAsErrors, I'd expect that you could simply fully Obsolete the method (with error) and not only as Warning. I would simply forget about recompiling and at most focus on having few impacted manual changes, e.g. provide codefixer for your AsList() and/or one for CancellationToken support. |
I don't think that. I'm saying we can't get around that. Adding the strong name is a hard break, but you're right that this may cause "just works without recompile" problems. To be further considered. |
I think these proposals look good. I'm looking forward to a simplified internal structure of Dapper. There have been a few times where I've had interest in contributing, but the codebase is a little tricky to navigate. It's usually just easier to use a workaround in my code. |
@chipmunkofdoom2 the internal complexity (in part due to the runtime metaprogramming) is a large part of why I want to push new feature work into AOT. The AOT path is much, much simpler. |
Curiosity: why "starting from the interfaces (IDbConnection etc) rather than the concrete base types (DbConnection) was a mistake"? For people using Dapper to query their own concreate |
My feedback is that if I was to update any of my projects to v3, or one of the dependencies did (rare case, and would have to expose Dapper types), I wouldn't mind "compiling" it again. I'd rather see Dapper take full advantage of a breaking change window and provide tools to migrate the code (code analyzers/fixers, as previously suggested). |
great question; short version: because interfaces are not extensible (at least, before .NET 6?), a ton of stuff we absolutely depend on (that was added after the initial cut of
The short version is: we're heavily constrained without this. For async, we currently do a type check and push things to |
I'm not sure how many people do this, but I've always created a mini abstraction layer around Dapper in every project, because there are a few benefits to doing so:
So concerning these breaking API changes, this is my personal opinion:
|
Right; so, I absolutely don't expect this to work in .NET Framework. I did some checking on .NET 6, though, and it didn't seem to present any problem whatsoever there. Maybe there is a middle-ground here where:
Or maybe we just leave the strong name alone entirely and just go "meh" 🤷 - on the basis that not breaking people is more important, and it isn't really causing us daily pain, it is just an eyesore. Emphasis: I don't care about eyesores in the .NET Framework build - if we need to keep .StrongName for that, well... honestly, that's just "stuff we haven't quite killed yet" - I don't see any need to rug-pull .NET Framework folks: they've already suffered enough. |
I like the middle ground approach, especially the part with the rule in advisor. |
What is the expectation in terms of dependencies? Currently NetStandard 2.0 and .NET FX 4.5.1 are supported, what would this clean up, being a major version update, be a good time to allow use of newer capabilities? Eg. rather than
allow incremental return of data be fully exposed with
As with @amoerie I (and the teams I lead) typically use wrappers for diagnostics (sql, parameters (optionally), timing) so seeing the API not changing too much is really valuable. |
No strong need to drop anything - we are using newer capabilities when available already. If it was hurting me, I'd readily drop up to about net472, but... meh
that already exists, it is (and would remain) |
Why try so hard to avoid runtime breaks in a major version? Are people really just doing a DLL replacement on a major version without recompiling? |
In short, yes. Because you can have many assemblies which have Dapper as a downstream dependency and you end up with only one Dapper.dll in the deploy (without shenanigans), and all those intermediate assemblies aren't being recompiled in your build, only when those libraries are each updated and released. |
We're in the position where it is never just SomeApp => Dapper - there are lots of things that ref Dapper as a wrapper, both public 3rd party libs, and private in-house libs at random companies. If we have the chain
If we retain compat, then if we rev and SomeApp takes that new version directly or transitively: no problem, they get the new code. But: if we break, there is a lot of work to figure out a: which component caused the update, and b: unpick the supply chain such that A, B, C, and D are all compatible with the updated bits. And emphasis: the author of B got fed up with IT and now grows watermelons off-grid; the private keys are lost to time - so even forking it would involve a hard break of B. OK, I'm being a little melodramatic, but: not much. I've seen many things not too dissimilar to this. The problem with running a popular library is that people use it and depend on it. |
Both these remarks made me wonder if these constraints could be relaxed somehow.
Deprecation features of nuget could allow to hint people for package changes, if considered appropriate. Apart from the fact that this is not very satisfying from a brand point of view, I suspect there are most probably cases where this would cause issues, so please forgive me if this approach is obviously doomed to failure. |
@Greybird Unfortunately that's not really maintainable because we end up with a new package every major version and every consumer of every version doesn't even know there's an upgrade - and you get say Dapper.dll, Dapper.v3.dll, Dapper.v4.dll, etc. and unless you're changing the namespace each time creating n caches/overlaps/behaviors at best, we run into ambiguity errors for all namespaces/types/extensions that overlap. Playing nice in the same running application across versions is the ultimate thing here, no matter which level along the way conflicts are happening at. In a lot of the proposals here for workarounds, we'd just be moving where the conflicts happen, and it'd less intuitive (if not sometimes impossible) for users to fix compared to any other major version break from a package change. I'd say if we're going to break, at least do it in a way that everyone else does, so that there's a known set of issues/constraints/fixes users have rather than creating new sets of those. |
Very incomplete first stab at API overhaul linked above; no impl yet - IMI only the shipped.txt and unshipped.txt files are useful right now |
In my 6 years of experience, I haven't used EF/Core often, mainly due to performance concerns, project requirements, or the complexity of my SQL queries. Instead, I've primarily relied on ADO.NET by creating a generic SqlHelper with DbProviderFactory. Over the past 2 years, I've incorporated Dapper into some projects. Here are my suggestions, many of which align with @mgravell points. Please disregard any suggestions that are already implemented, as I haven't extensively used Dapper: Consider creating extension methods for DbConnection (as opposed to just IDbConnection) to take advantage of additional ADO.NET features not available in IDbConnection. Provide async versions of ExecuteReader, ExecuteNonQuery, and ExecuteScalar with proper cancellation support. Instead of returning IEnumerable, it might be more efficient to return IReadOnlyList. In practice, most developers use List or arrays, and using IReadOnlyList would improve performance when iterating with foreach and provide index access. For parameterized queries, it could be beneficial to accept an IEnumerable<KeyValuePair<string, object>> instead of just "object." This change aligns with common practice, as developers often use dictionaries for parameters. Explore the possibility of replacing meta-programming and IL code with AOT/source code generation for mapping DbDataReader to types. This approach could reduce memory usage, which could be significant in larger projects with numerous queries. |
Appreciate the feedback!
Changing the signate: already in the VNext spike Taking advantage of the features: already in VCurrent (we cast as needed)
I believe this is already in the VNext spike
Already in the VNext spike, although we went
We have DynamicParameters for that, although we could also support dictionary directly
See https://aot.dapperlib.dev/gettingstarted So: we're not in much disagreement here |
Dapper has grown organically over time, and not all decisions were good ones. We have worked hard to try to avoid runtime or compile-time breaks, but this means we have been constrained by those poor decisions.
I would like to get to work on shipping a V2->V3 transition that fixes this, and get some breaks over and done with. As discussed here, some feature work may be shunted to the AOT project, but we need the right API for things to be effective.
What are we trying to solve?
The key problems in the API in V2 are:
IDbConnection
etc) rather than the concrete base types (DbConnection
) was a mistakeCommandDefinition
was well intentioned, but again: a mistake - we should expose cancellation via a standardCancellationToken
parameterDbDataSource
as a root primitiveContraints on changes:
Planned implementation
The first big change is: strong name the library and retire Dapper.StrongName; bad call.
My proposal for the code, effectively, is to retire the existing API but leave it in place (so: no missing methods) by removing the
this
modifier (so they no longer resolve as extension methods), and create a new set of extension methods - probably on a newstatic class
that expose the preferred API, meaning: the shape we want, and the most generalized version only (no hive of forwarded methods with increasing numbers of parameters reflecting our incremental understanding of the required API). Code that is already compiled will continue unchanged using the old methods (the missingthis
is irrelevant and doesn't break the API). When code is recompiled, it'll resolve to the new methods.For example:
Key changes:
IDbConnection
toDbConnection
buffered
is now explicit on the method signature rather than a parameter (see the existingQueryUnbufferedAsync
)async
methods will gainCancellationToken
parametersHowever:
buffered: false
, or b: switch toQueryUnbuffered
)List<T>
in the default buffered case makes reality explicit; in theory this could change how calling code operates, but this is considered to be a positive thing - for example,foreach
will be more efficient if it knows it is starting fromList<T>
due to duck-typingWe'll need to list out the full proposal set, but before we get too far: I want to gather feedback on the overall shape of this suggestion.
The text was updated successfully, but these errors were encountered: