Speed of course, and a huge standard library that works with multiple languages, Ruby and Python are dog slow compared to C#; can't speak to Go, haven't used it.
Core doesn't actually have a huge standard library. That's kinda the big problem with it. It's basically, at the moment, a really shit version of .Net 4.61.
For example in the 1.0 release the ORM is a complete joke at the moment. It can't do fairly basic stuff, like worse than ORMs from 10 years ago.
EntityFramework? In my 5 or 6 years with .NET I've had more problems with EntityFramework than not. Most of the time it's overkill or just not optimized for the queries I could write myself. I'm a huge fan of Dapper.Net. Looks like there's initial support for core as well.
These days, it's really very, very good. And it's been amazing since EF4, for about 5 years now. Trying to push it too far is a mistake and can cause performance problems, but, as with all ORMs, sometimes you need to drop down to raw queries.
Missed this comment but I don't take offense to it.
In all honesty, if you have a well designed system it's pretty good at what it does. I've used it without any issues on small personal projects and it was great at what it did. Although getting it to work with Postgres is needlessly "complicated". Not complicated per-say but not as easy as it just working with SQL Server.
What happens when you take a legacy system and move over to .Net and try to use EF on a poorly architectured data model? Chaos.
"Oh you want to search for orders that have these attributes that are stored in a bit-encoded string field that is only joined from 3 different tables?"
I neither expect or would use an ORM that was considered part of the standard library, it's going to be far too crappy trying to please everyone. ORM's are something you get aftermarket, from core I just want raw database drivers, from those the much better open source libs can build real ORM's that are tailored to difference audiences. The core should be libraries for dealing with the OS and network, frameworks are things I'll look for open source or build to suite me, not something I want from core.
I really wanted to give this a try, but the lack of first-class MySQL support makes it a non-starter for me at this point. There are third party drivers, but they don't seem to work with many Entity Framework features.
Yes yes yes, but better yet, write your own. Micro ORM's are "micro", it's pretty trivial to create something better than Dapper and there's no framework in the world better than your own, to you, because there won't ever be anything to complain about, just mold it to work exactly how you like.
Dapper..
var dogs = connection.Query<Dog>("select Age = @Age, Id = @Id",
new { Age = (int?)null, Id = guid });
yuk, I prefer
var dogs = Dog.FindAll(And(IsNull("Age"), Equal("Id", guid)));
A touch of generics done properly, a bit of reflection, a static import, and you can do much better than onnection.Query<Dog>. Write the API you want, implementing what is basically an active record without relationships is not terribly difficult and will pay off for years to come in your own code.
Edit: Dappers API is ugly as fuck, that's an immediate non-starter for me, and the cost of reflection on loading objects is the wrong problem to be optimizing and would only show up in an absurd micro benchmark intended to show off speed that doesn't matter, it's small compared to the execution time of the queries. If you like it, use it, but lets not pretend it's so much effort it isn't worth redoing. It's certainly not the end all be all of ORMs.
No, there's no (technical) reason to redundantly solve the problems that dapper has solved. In particular the emitted IL which avoids costly reflection is not something many people will correctly re-implement.
> A touch of generics done properly, a bit of reflection,
Dapper avoids reflection, its performance is very close to a hard-coded SqlCommand.
If you're worried about the speed difference between reflection and a hand written command, you're spending your time in the wrong part of the code. Reflection isn't the bottleneck, the database is, by far.
Yes, if you speed test the difference between reflection and handwritten, it'll look slow, but such micro-benchmarks are unrealistic of production code. If you look for slowness you'll find it everywhere you look, but if you profile real code you'll find the only slow code that actually matters and you'll find that database IO is the bottleneck generally.
> var dogs = Dog.FindAll(And(IsNull("Age"), Equal("Id", guid)));
That's pretty ugly and not idiomatic for .NET which favors using typed LINQ providers for data querying. We develop OrmLite, a fast, code-first POCO ORM which lets you use LINQ to query and map to clean POCO's so you could instead query the above with:
:) Well, it's pretty ugly now that expressions exist, however prior to expressions it was quite nice since S expressions is all they are and I've had this for nearly a decade in .Net, long long before such niceties like expressions existed. And I'll just bolt expressions into my library when I feel like it and type this
rather than relying on an external dependency that doesn't work the way I want. The need to do this...
db.Select<Dog>
Use generics at the method level, I find intolerably ugly when you could have created db with said type and have access to an already typed API. What you're doing there, I personally consider bad design and would refuse to use. When I use a typed list... I don't go
someList.Add<Thing>(aThing)
Because the decision to type the list was done at list creation time. Forcing me to pass a type on every call to the db, less than attractive. The best uses of generics don't look like you're using generics because you only have to do this once
class Dog : Persistable<Dog> {}
To have a perfectly typed class side API that doesn't shit generics all over the client side code. The T is specified once and only once, and never seen again and now all the Select/Save/Delete/Create methods no longer require the client to constantly type <Dog> every time they touch your lib.
In all fairness however, it would be trivial to wrap your lib in a persistable template and achieve exactly the API I wanted, should I ever desire to do so. Which is actually a much better argument for using your lib than any you've given.
> Use generics at the method level, I find intolerably ugly
Then you shouldn't be using .NET, generic methods are extremely common and especially elegant here since it can work with any POCO which can also be reused elsewhere in any library that works with POCOs. The Generic method is what enables the Typed API without adding any artificial artifacts and wrappers. It's intuitive as to what the lambda is typed to and what value is returned - it's definitely more idiomatic and much preferred over Active Record implementations.
In your Example `Dog` ends up being a .NET 1.0-style non-serializable bloated Active Record inheriting from a heavy base class that's non-reusable and coupled to your implementation that you're forced to create for each Type you want to use. It's also not clear what DB connection is being used, what its life-cycle is and whether it's being reused across different types, how you can create transactions spanning multiple types, etc - in Micro ORM's its clear, you use the same db connection to query any Type.
There's a reason why most modern Micro ORM's are mapping to clean disconnected POCO's and don't force unnecessary wrappers for legacy inhibitive abstractions like this.
I believe the above poster was saying that you can have your cake and eat it too by making sure that the type is passed at the time of the DAO construction so to avoid the need of specifying the type at every method. You can still use a common interface and you don't need to follow a 'bloated active record' style for just doing this.
Generics can apply to classes, not just methods, as you can see in the typed lists included in the framework, you can use generic methods without them taking generic params as you can get the generic param from the class itself resulting in vastly cleaner use of generics akin to the built in typed lists.
Look at a typed list, that's the "clean" way to use generics, note it looks nothing like your ORM's interface.
> In your Example `Dog` ends up being a .NET 1.0-style non-serializable bloated Active Record
Incorrect, active records are perfectly serializable, not the least bit bloated, and I'm not "forced" to do anything, I'm using the interface I prefer having worked with every pattern under the sun for nearly two decades including repositories and POCO's. You aren't showing me anything I don't know, I understand deeply what your lib does and why it works the way it works: I'm disagreeing because I disagree, not because I don't understand something.
> It's also not clear what DB connection is being used, what its life-cycle is and whether it's being reused across different types, how you can create transactions spanning multiple types, etc -
Of course it's not clear to you how everything else works when we've only seen a single select example, you're making all kinds of assumptions here without cause. None of those things are clear in yours either from the select example shown here. It's only clear when you go read your full page of examples; as I haven't given a full page of examples, this critique is just silly.
> in Micro ORM's its clear, you use the same db connection to query any Type.
Active record is a micro ORM as well, and I understand all of those semantics about my ORM just as you do about yours. Perhaps you should refresh yourself on what a micro ORM is.
> There's a reason why most modern Micro ORM's are mapping to clean disconnected POCO's and don't force unnecessary wrappers for legacy inhibitive abstractions like this.
Yes there is, preference of the ORM writers, nothing more. Your preference for POCO's is nor more or less good than someone else's preference for active records; I've written code in every ORM style and I'll take a good old active record pattern over a repository pattern every time because at the end of the day, it's just more pragmatic and allows me to get more work done in less time while writing better looking code.
Both patterns are valid patterns, your dismissal of non repository patterns is sophomoric. I've been there, I used to think repositories were so vastly superior, then I grew up and had to hire and train other people and found the ability to get others to understand what's going on overrides my obsession with OO perfections. Active records are practical, they're why Ruby on Rails got so huge so fast. Ignore this pattern at your own peril.
You're going to seriously try serialize active records? Are you going to ship them to your clients as well to deseiralize? coupled with all their server data logic implementations? Active Records aren't DTO's and this doesn't work in the real-world, you can of course try to force it, but then you'll just be fighting fires trying to workaround new runtime serialization and implementation coupling problems.
> Of course it's not clear to you how everything else works when we've only seen a single select example
It's not clear because it's hidden behind your active record wrapper so the call-site doesn't know anything about it or provide any way they can change it.
> None of those things are clear in yours either from the select example shown here.
It's absolutely clear since `db` is just the open ADO.NET DB Connection which the query is obviously using. Users don't need to know anything more about the Micro ORM to use transactions as it just works naturally with ADO.NET DB Transactions.
> Yes there is, preference of the ORM writers, nothing more.
It's the preference of devs with experience writing ORM's as to what provides the optimal, intuitive API with the least friction, max productivity and reuse. You can of course stick to creating a new ORM based on a custom Active Record pattern which you'll end up maintaining forever and no-one else will have experience using or want to use. So any of your code-bases using it will be stuck using a bespoke legacy DAL that will be unmaintained the second you stop maintaining it and move on to NIH'ing something else.
> Active records are practical, they're why Ruby on Rails got so huge so fast. Ignore this pattern at your own peril.
They're ignored in .NET because they provide no added value over what's already available, there's no peril to be avoided, they're worse in every way and have zero chance of returning with any meaningful impact in .NET.
You've succeeded in convincing me you that you have limited experience and an inability to see that the real world is much messier than your ideals. Do please stop trying to speak for .Net as if you represent the entire community, you don't, and you're just embarrassing yourself.
In the real world, the corporate world, you'll find tons and tons of custom active record implementations because they always provide something external mapping libraries don't, context aware simplicity. So go pick up a Martin Fowler book on patterns and read what someone who actually knows what they're talking about says about it since you seem to think you've got it all figured out, it's obviously pointless trying to have a discussion with you.
LOL, then you're as clueless as evaluating people as you are evaluating technology. If you think mechanically forcing the creation of unnecessary non-reusable classes eschews simplicity you have no idea about that either no matter how many pattern books you want to try hide behind - BTW if you had any clue about Martin Fowler you'd know he'd never reuse Active Records as DTO's (pretty embarrassing given you've started quoting him - go read up on his published DTO, Facade and Gateway patterns). But you're right there's no point in a discussion, you're stuck in your legacy ways (a .NET 1.0 API for your ideal theoretical .NET AR DAL really?) and can't see the approaches .NET has rightfully moved to - there's a reason you'd need to NIH your own DAL to get "your ideal API" you want (hint: it's not because it's an undiscovered ingenuity) - have fun NIH'ing your own tech debt!
You still don't need to write your own implementation. DapperExtensions got you covered, see predicates. They're not visually pleasing, but so do your implementation.
By the way, you got a nasty NIH syndrome to tackle, just saying.
I've had my own micro ORM before any of those other ORMs existed or the term micro ORM was a thing, so one can hardly call that NIH. In fact there weren't any ORM's in .Net at all when I wrote mine.
I love using good external libs, use them all the time. What I have is lots of experience with shitty ORM's and a fascination with the ORM problem that's led me to write several ORMs of my own just for the fun of it.
Frankly I think it's one of the great teaching programs you can write, you'll learn a ton writing an ORM if you've never done it before.
Thanks for the tip on DapperExtensions, I'll take a look.
I tend to prefer things which are more lightweight, but my coworkers don't. I'm mostly wanting to pitch this as a better alternative to Spring Boot and JPA.
Are there any guides for OSX/Linux that don't end with printing hello world. A lot of what seems to be out there is very basic or starts talking about Windows when it starts to get interesting
Too bad the tooling (e.g. restore, build, package) and (I think) the project files format is still preview.
So far, my experience has been that small isolated project seem to work fine, but as soon as I try to break a more complex solution out into separate nuget dependencies things start to get hairy.
Nuget dependency versions don't support npm-like syntax, so you can forget about the "^1.0.0.0" notation.
Syntax like "[1.0.0.0-2.0.0.0)" might be supported, but honestly I've been fighting with other issues enough that I can't recall for sure if making the version syntax ugly makes it happy.
Another surprise to those from javascript/npm land is multiple versions of the same library in your dependency tree isn't supported. So you basically need to support redirecting to a commonly supported version (which, without examples showing version ranges, you're back at the version range syntax pains).
If anyone's tackled these pain points I'd love to hear.
I'm new to the CLR build system myself, and have sort of gotten stuck on project formatting. The F# side seems to have some newer build systems, and are trying to go towards a simpler build/project system.
For dependencies see Paket[1]. It allows versioning, and only the parent dependencies not the entire dependency tree.
For building much like Gradle there is FAKE [2]. It's an F# DSL for build tasks, and has type safety etc. But I've seen examples with C# project as well.
40 comments
[ 5.0 ms ] story [ 44.8 ms ] thread(I use Python at my day job, and C# on my own side projects at home. F# seems... interesting).
For example in the 1.0 release the ORM is a complete joke at the moment. It can't do fairly basic stuff, like worse than ORMs from 10 years ago.
These days, it's really very, very good. And it's been amazing since EF4, for about 5 years now. Trying to push it too far is a mistake and can cause performance problems, but, as with all ORMs, sometimes you need to drop down to raw queries.
In all honesty, if you have a well designed system it's pretty good at what it does. I've used it without any issues on small personal projects and it was great at what it did. Although getting it to work with Postgres is needlessly "complicated". Not complicated per-say but not as easy as it just working with SQL Server.
What happens when you take a legacy system and move over to .Net and try to use EF on a poorly architectured data model? Chaos.
"Oh you want to search for orders that have these attributes that are stored in a bit-encoded string field that is only joined from 3 different tables?"
[1] https://github.com/StackExchange/dapper-dot-net
Dapper..
yuk, I prefer A touch of generics done properly, a bit of reflection, a static import, and you can do much better than onnection.Query<Dog>. Write the API you want, implementing what is basically an active record without relationships is not terribly difficult and will pay off for years to come in your own code.Edit: Dappers API is ugly as fuck, that's an immediate non-starter for me, and the cost of reflection on loading objects is the wrong problem to be optimizing and would only show up in an absurd micro benchmark intended to show off speed that doesn't matter, it's small compared to the execution time of the queries. If you like it, use it, but lets not pretend it's so much effort it isn't worth redoing. It's certainly not the end all be all of ORMs.
> A touch of generics done properly, a bit of reflection,
Dapper avoids reflection, its performance is very close to a hard-coded SqlCommand.
Yes, if you speed test the difference between reflection and handwritten, it'll look slow, but such micro-benchmarks are unrealistic of production code. If you look for slowness you'll find it everywhere you look, but if you profile real code you'll find the only slow code that actually matters and you'll find that database IO is the bottleneck generally.
That's pretty ugly and not idiomatic for .NET which favors using typed LINQ providers for data querying. We develop OrmLite, a fast, code-first POCO ORM which lets you use LINQ to query and map to clean POCO's so you could instead query the above with:
You can see more OrmLite features and try them out "live" with just a browser with the Interactive Tour on: http://gistlyn.com/ormlitesomeList.Add<Thing>(aThing)
Because the decision to type the list was done at list creation time. Forcing me to pass a type on every call to the db, less than attractive. The best uses of generics don't look like you're using generics because you only have to do this once
To have a perfectly typed class side API that doesn't shit generics all over the client side code. The T is specified once and only once, and never seen again and now all the Select/Save/Delete/Create methods no longer require the client to constantly type <Dog> every time they touch your lib.In all fairness however, it would be trivial to wrap your lib in a persistable template and achieve exactly the API I wanted, should I ever desire to do so. Which is actually a much better argument for using your lib than any you've given.
Then you shouldn't be using .NET, generic methods are extremely common and especially elegant here since it can work with any POCO which can also be reused elsewhere in any library that works with POCOs. The Generic method is what enables the Typed API without adding any artificial artifacts and wrappers. It's intuitive as to what the lambda is typed to and what value is returned - it's definitely more idiomatic and much preferred over Active Record implementations.
In your Example `Dog` ends up being a .NET 1.0-style non-serializable bloated Active Record inheriting from a heavy base class that's non-reusable and coupled to your implementation that you're forced to create for each Type you want to use. It's also not clear what DB connection is being used, what its life-cycle is and whether it's being reused across different types, how you can create transactions spanning multiple types, etc - in Micro ORM's its clear, you use the same db connection to query any Type.
There's a reason why most modern Micro ORM's are mapping to clean disconnected POCO's and don't force unnecessary wrappers for legacy inhibitive abstractions like this.
Look at a typed list, that's the "clean" way to use generics, note it looks nothing like your ORM's interface.
> In your Example `Dog` ends up being a .NET 1.0-style non-serializable bloated Active Record
Incorrect, active records are perfectly serializable, not the least bit bloated, and I'm not "forced" to do anything, I'm using the interface I prefer having worked with every pattern under the sun for nearly two decades including repositories and POCO's. You aren't showing me anything I don't know, I understand deeply what your lib does and why it works the way it works: I'm disagreeing because I disagree, not because I don't understand something.
> It's also not clear what DB connection is being used, what its life-cycle is and whether it's being reused across different types, how you can create transactions spanning multiple types, etc -
Of course it's not clear to you how everything else works when we've only seen a single select example, you're making all kinds of assumptions here without cause. None of those things are clear in yours either from the select example shown here. It's only clear when you go read your full page of examples; as I haven't given a full page of examples, this critique is just silly.
> in Micro ORM's its clear, you use the same db connection to query any Type.
Active record is a micro ORM as well, and I understand all of those semantics about my ORM just as you do about yours. Perhaps you should refresh yourself on what a micro ORM is.
> There's a reason why most modern Micro ORM's are mapping to clean disconnected POCO's and don't force unnecessary wrappers for legacy inhibitive abstractions like this.
Yes there is, preference of the ORM writers, nothing more. Your preference for POCO's is nor more or less good than someone else's preference for active records; I've written code in every ORM style and I'll take a good old active record pattern over a repository pattern every time because at the end of the day, it's just more pragmatic and allows me to get more work done in less time while writing better looking code.
Both patterns are valid patterns, your dismissal of non repository patterns is sophomoric. I've been there, I used to think repositories were so vastly superior, then I grew up and had to hire and train other people and found the ability to get others to understand what's going on overrides my obsession with OO perfections. Active records are practical, they're why Ruby on Rails got so huge so fast. Ignore this pattern at your own peril.
> Of course it's not clear to you how everything else works when we've only seen a single select example
It's not clear because it's hidden behind your active record wrapper so the call-site doesn't know anything about it or provide any way they can change it.
> None of those things are clear in yours either from the select example shown here.
It's absolutely clear since `db` is just the open ADO.NET DB Connection which the query is obviously using. Users don't need to know anything more about the Micro ORM to use transactions as it just works naturally with ADO.NET DB Transactions.
> Yes there is, preference of the ORM writers, nothing more.
It's the preference of devs with experience writing ORM's as to what provides the optimal, intuitive API with the least friction, max productivity and reuse. You can of course stick to creating a new ORM based on a custom Active Record pattern which you'll end up maintaining forever and no-one else will have experience using or want to use. So any of your code-bases using it will be stuck using a bespoke legacy DAL that will be unmaintained the second you stop maintaining it and move on to NIH'ing something else.
> Active records are practical, they're why Ruby on Rails got so huge so fast. Ignore this pattern at your own peril.
They're ignored in .NET because they provide no added value over what's already available, there's no peril to be avoided, they're worse in every way and have zero chance of returning with any meaningful impact in .NET.
In the real world, the corporate world, you'll find tons and tons of custom active record implementations because they always provide something external mapping libraries don't, context aware simplicity. So go pick up a Martin Fowler book on patterns and read what someone who actually knows what they're talking about says about it since you seem to think you've got it all figured out, it's obviously pointless trying to have a discussion with you.
>>someList.Add<Thing>(aThing)
Implicitly you do.
By the way, you got a nasty NIH syndrome to tackle, just saying.
I love using good external libs, use them all the time. What I have is lots of experience with shitty ORM's and a fascination with the ORM problem that's led me to write several ORMs of my own just for the fun of it.
Frankly I think it's one of the great teaching programs you can write, you'll learn a ton writing an ORM if you've never done it before.
Thanks for the tip on DapperExtensions, I'll take a look.
So far, my experience has been that small isolated project seem to work fine, but as soon as I try to break a more complex solution out into separate nuget dependencies things start to get hairy.
Nuget dependency versions don't support npm-like syntax, so you can forget about the "^1.0.0.0" notation. Syntax like "[1.0.0.0-2.0.0.0)" might be supported, but honestly I've been fighting with other issues enough that I can't recall for sure if making the version syntax ugly makes it happy.
Another surprise to those from javascript/npm land is multiple versions of the same library in your dependency tree isn't supported. So you basically need to support redirecting to a commonly supported version (which, without examples showing version ranges, you're back at the version range syntax pains).
If anyone's tackled these pain points I'd love to hear.
For dependencies see Paket[1]. It allows versioning, and only the parent dependencies not the entire dependency tree.
For building much like Gradle there is FAKE [2]. It's an F# DSL for build tasks, and has type safety etc. But I've seen examples with C# project as well.
[1] https://fsprojects.github.io/Paket/getting-started.html [2] http://fsharp.github.io/FAKE/
At one point this was advertised in blog posts as something that would be possible. Has this changed?
I think it's one of the killer features that help ease the burden of deployment for a lot of folks.