being old school, i would prefer my db queries being tightly coupled and efficient and know (mostly) what was going on under the hood.... if MS want to bring hibernate to .net then time to grab my coat...
We've been hearing a growing amount this year about LINQ - Microsoft's Language Integrated Query. You can expect a lot more next year, starting in February as Microsoft launches Visual Studio 2008 and SQL Server 2008. LINQ promises to close the skills and knowledge gap for developers using C# and VisualBasic trying to connect to …
being old school, i would prefer my db queries being tightly coupled and efficient and know (mostly) what was going on under the hood.... if MS want to bring hibernate to .net then time to grab my coat...
While you touch on the subject that LINQ is actually not specifically targeted at databases, what you mostly talk about in this article is actually LINQ to SQL. LINQ to SQL is actually an extension to LINQ which - as you say - addresses the impedance mismatch between OO programs and a specific relational database, SQL Server.
LINQ to SQL *do* have an abstraction (mapping) layer. While it is not as flully fledged as some ORM mappers, LINQ to SQL actually does allow for several common changes in the table structure (adding/removing/renaming columns, adding tables etc.). The mapping layer will work to absorb changes just like any ORM solution out there.
Your claim that LINQ writes directly to the tables is flat out wrong. LINQ to SQL observes the objects which were retrieved from the database. At the programmers' request it will use the mapping information to update/insert changed/newly registered objects. So, LINQ to SQL uses a retained, mapped approach, not a direct approach.
Contrary to your claims, LINQ to SQL will also work with stored procedures, for both querying and updating/inserting. Again the mapping layer allows the programmer to specify on a per class basis how the objects are retrieved/updated: By SQL DML or by stored procedures.
However, LINQ to SQL is actually "LINQ to SQL Server". Microsoft will not create LINQ to Oracle or to any other database. Microsoft leaves it to the vendors or the communities to implement those. To this end it is worth mentioning that LINQ is not just "embedded SQL", but is built upon a number of other sound language enhancements.
I disagree that LINQ is a way to let programmers forget (or not learn) SQL. LINQ to SQL will generate SQL for you, but for the more complicated queries you should really not rely on generated SQL. Most of all, LINQ to SQL drastically simplifies writing the simple queries, especially when they are required to take parameters.
A very important aspect of LINQ to SQL is also the fact that it finally and conclusively outs the exploit prone string manipulation of queries. No more queries in strings. Queries are written using the native boolean expression syntax of your language of choice.
My experience with LINQ (not just LINQ to SQL) is that it will drastically change (for the better) how I program any set/list manipulating code, not just database results. Why should I have to loop through an array to locate items with specific properties? With LINQ I can just formulate the criterias declarativelty and be done with it.
I've been developing software for years now, and most of the programs I work on have a database back-end. These are mainly located on AS/400 machines, and then also some in MySQL (or similar) and some in simpler databases like Access files.
I also program in about 6 or 7 different languages as I not only write new stuff but also maintain some legacy apps, so it depends on what I'm making and where it has to run. Believe me, SQL is a godsend. Whatever system I'm working on/with, I can always get to the databases in the same way. SQL is a cross-platform standard, fast, simple to learn and powerful.
Now, I had a look at the LINQ samples, and as far as I can tell, for an array with a small amount of numbers in it, it can have it's uses. For querying a database with lots of tables and millions of records? Hah! forget it. Just look at the samples, first you have the "GetProductList()" function which gets ALL your data to your client, and then the piece of LINQ, which looks like some weird sort of iteration construct, gets you the records you need. Who in his right mind thought thàt was a good idea?? Anyway, can we also see what's in the GetProductList() function? Some good old SQL maybe ;-)
No wonder Vista moves like a dead donkey...
Anyway, typical isn't? Replace a standard that can be used anywhere with some one vendor technology. Mmm, now where did we hear that one before?
" - addresses the impedance mismatch between OO programs and a specific relational database, SQL Server. "
As an old electrical/electronics engineer, I'm very familiar with the concept of impedance mismatch, its effect on power transfer between source/destination and its effect on reflections at transmission line junctions. I can even still derive the appropriate equations if I try hard.
Can anyone please define for me the 'impedance' of a program and a database and at least point me to an appropriate article that explains the concepts?
From the article: "For programmers, one of the best resources I've found for getting one's brain around LINQ is the intriguingly labeled 101 LINQ Samples for Visual C# that can be found here. Intriguing, why? Because there are actually 99 samples."
And I thought 101 was binary for 5.
"Application programmers are used to dealing with things such as objects, classes and methods but they are not usually familiar with SQL"
Then they should be sent back to school (or at least forced to read some books) until they are, or simply taken out and shot to free up headcount for competent people.
How is it even possible to avoid being familiar with SQL ? Anyone with a college or university level education in the field will (or ought to) have been through at least one RDBMS module during which they will (ought to ?) have been at least introduced to SQL. If you're hiring people without a formal education - not necessarily a bad thing - then SQL, as an industry standard (of sorts) should be on your training plan for them.
Anyone who isn't familiar with it has no business being anywhere near a database driven app, and as such it's usually an advertised requirement for MIS (and such) developers.
I had always believed that I'd had the misfortune to work with quite a few seriously incompetent people over the years (as well as some truly excellent ones, you know who you are), but I never came across anyone who couldn't speak SQL.
Maybe I've been luckier than I thought ?
It's software written by Smart Technologies for using their interactive whiteboards without it being physically connected to the PC. It is already a registered trademark. I feel a copyright lawsuit coming on...
Sounds suspiciously like SQLAlchemy for Python(http://www.sqlalchemy.org/)...
Microsoft playing catch up again??! ,-)
"However, LINQ to SQL is actually "LINQ to SQL Server". Microsoft will not create LINQ to Oracle or to any other database. Microsoft leaves it to the vendors or the communities to implement those. To this end it is worth mentioning that LINQ is not just "embedded SQL", but is built upon a number of other sound language enhancements." ..... and with that I would agree wholeheartedly, Uffe Seerup. It does though then leave the way open for a lead vendor/community/programmer to implement/develop all the disciplines mentioned above in Accord with all vendors and communities. And that could of course be a Microsoft or a Sovereign Wealth Fund* or even a wealthy philanthropic proxy/anonymous donor because all such Research and Development is always Program and Programmer led as they follow IT Leads.
Spookily enough, I had reason to pen the following earlier this morning, which appears to mirror/parallel the article with some prior art work ......
<<< "After moving ASCII, ASCII sound and the Instant ASCII Camera, this is the first serious step in the direction of three dimensional ASCII which is considered to be the Holly Grail of hackerdom." .... http://www.ljudmila.org/~vuk/ascii/architecture/ ....Hmmmm?
A Shift into Virtualisation, which is surely AIReal/a Real Imaginary State, must surely Present a Portal into any number of other dimensions for Alternate Reality Games Play .... Intellectual Property Placement/Mind Control via Environment Manipulation/Pixellation/Digitisation of Thought4Sight2. Being as we may be/are Beings in the Sum Total of our Thoughts via Sublime Interaction* with that we XXXXPerience in/with/because of/led by ITs Knowledge, that which we can then Imagine to See may very well be that which we receive back as Vision and its stock item, Sight.
Sight being that which we are expected to See because it is a product of previous thoughts realised and Vision being that which is planned to be Seen, in Ideas looking for Realisation/Manufacture/Third Party Positive Reinforcement/Global Support.
That suggest that a Strong Willed Entity Sharing Views/Thoughts for Vision will Create Sight and Sights/Site and Sites for Viewers/all other Beings to XXXXPerience as and in AI Future Reality Programmed 42 Appear Real although Created Virtually by Sheer Presence of Mind Shared for Realisation with Like-Minded Peers.
I will Ponder those Words 42 Improve upon their Sense 42 Simplify the Ease of their Understanding.
*...... Sublime Interaction is somewhat of a misnomer as IT is really a very Conscious HyperRadioProActivity on a very Particular Path .... in local vernacular, a man on a mission and all variations on that theme such as amfM HyperRadioProActivity in AI Mission Magical Mystery Turing where Intelligence Programs Spontaneously Communicate for Continuity [Linked Sequelling?] with a View 42 Create AI Beta Vista for Programming the Universe/Future.
Of that there is No Doubt other than that which is harboured in Others. >>> [*...... and also, on a relative matter discussing similar allied concepts, received for no good reason, one of those demon DAEMON notices .... <<<Hi. This is the qmail-send program at yahoo.com.
I'm afraid I wasn't able to deliver your message to the following addresses.
This is a permanent error; I've given up. Sorry it didn't work out.
22.214.171.124 does not like recipient.
Remote host said: 550 5.1.1 unknown or illegal alias: email@example.com Giving up on 126.96.36.199.>>> ]
It is not the first time that possible/probable Juicy Lucy amfM titbits have been apparently refused electronic passage/TEMPESTuous delivery, No matter though, posterity has IT stored to try, try and try again, if IT be necessary for earthly requirements...... and accurate chronicling of Future Events? SQLs? Oracling?
My humble apologies for the lengthier tome as opposed to a catchy shorter Script, but there is more than just a little to convey.
More anon..... :-) in the Digitised ControlLed Future?
PS Whoever invented RSS Feeding was one SMART Cookie?
Impedance mismatch is not between program and database but between an object oriented construct and its relational equivalent, and vice versa. The idea is that there is an overhead involved in mapping from one to the other that one would like to minimise. Intersystems talk about impedance mismatch a lot (because they're good it at reducing it) so you might want to try their web site.
Yes there is a large impedence mismatch between OO.
Objects force a single rigid structure and "view" on any set of data, much like the old hierarchical databases (IDMS, IMS, Coadasyl etc.) databases used to.
OO languages impose a rigid set of naming conventions, styles and often pointless restrictions on the programer.
OO is a very good for GUI applications, for other types of application it does not add much value.
Relational databases provide one language to describe data precisely and clearly -- DDL. And another language to access that data - SQL.
With SQL you can extract the data from any given dataset with clarity and precision. The database design does not force any viewpoint or structure so the SQL programmer get exactly the data required for a given situation.
The mismatch is between a rigid, jargon intensive, inflexable programming style. And an elegent powerful, flexible yet precise data base query language.
"Application programmers are used to dealing with things such as objects, classes and methods but they are not usually familiar with SQL - the language used to query relational databases."
I just spent three months attempting to hire a developer and I can tell you that 90% of people that I interviewed didn't have a clue about either! And these were people with, apparently, 5 or more years experience.
From Chris, the AS400 expert:-
"Now, I had a look at the LINQ samples, and as far as I can tell, for an array with a small amount of numbers in it, it can have it's uses. For querying a database with lots of tables and millions of records? Hah! forget it. Just look at the samples, first you have the "GetProductList()" function which gets ALL your data to your client, and then the piece of LINQ, which looks like some weird sort of iteration construct, gets you the records you need. Who in his right mind thought thàt was a good idea?? Anyway, can we also see what's in the GetProductList() function? Some good old SQL maybe ;-)"
Oh for God's sake - they're SAMPLES!!! Add a parameter to the GetProductList() method and it becomes at least a little bit more real-world. No-one is suggesting that they're good examples of how to architect an application, they're just examples of code. Why don't you blame them for not having security checks or logging in the samples as well? The samples aren't the bible, you are expected to adapt them to your own requirements...
Read Uffe's post, above. He covers the technical details very well. You've still got all your SQL, it's still there if you need it including Stored Procedures. Linq to SQL is just a very convenient syntax for querying databases, and it's only a part of what Linq is all about.
I have been doing this SQL stuff for about the length of time that Microsoft have been around.
Sorry people, almost everything out there that is not COBOL or FORTRAN is SQL. Most of the other things that anybody does in software are just pretty/shiny stuff to display or update the contents of a database.
I suspect that SQL will be around, long after Microsoft has become just another entertainment monolith.
Programmers, please just learn SQL - It really is much easier.
I go along with Uffe's post, and feel a need to add that - having looked at several of the examples - they're remarkably similar to SQL. What's the point in getting a large herd of developers to learn a similar-yet-different "almost SQL" that is probably not much easier to learn than real SQL?
So now we can have developers that are clueless about the way that databases work and write really bad queries as a result. Yet another VB from microsoft.
"Now, I had a look at the LINQ samples, and as far as I can tell, for an array with a small amount of numbers in it, it can have it's uses. For querying a database with lots of tables and millions of records? Hah! forget it. "
When querying a database using LINQ to SQL, the hard work is done at the database where it should be. For an example, I created a LINQ to SQL mapping for the example SQL 2005 pubs database. Using this mapping I can retrieve all the publishers in the database who have published one or more titles with the following code:
var context = new PubsDataContext();
var items = from p in context.Publishers
where p.Titles.Count > 0
If you enable logging on context, you'll see the following SQL statement being sent to the database when items is accessed for the first time:
SELECT [t0].[pub_id] AS [PublisherId], [t0].[pub_name] AS [Name], [t0].[city] AS
[City], [t0].[state] AS [State], [t0].[country] AS [Country]
FROM [dbo].[publishers] AS [t0]
FROM [dbo].[titles] AS [t1]
WHERE [t1].[pub_id] = [t0].[pub_id]
)) > @p0
ORDER BY [t0].[pub_name]
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) 
I'd do some more research if I were you before dismissing LINQ out of hand.
Anyone who is dismissing Linq or ORMs is probably a luddite who hasn't tried them or doesn't have enterprise apps to build and maintain
The impedance mismatch is between tools and compilers that manage different languages, which are elements of the same application.
Linq is LANGUAGE integrated query. In other words, while I write my C# or VB, i can write my data access code inline, and get the background compilation check direct against a data source. I have one tool and one language to learn, and a framework (which emits pretty optimal sql) talks to the database (or XML files, or roll-your-own) tier.
Other frameworks exist. As ever, Microsoft is not first to the market with the concept, but looks to me like they've learnt from others mistakes.
The Linq approach works when I have a database that belongs to a single application. In that case I like having the data access logic closer to the application developer, save them switching languages/tools and having more complex build and test processes.
If on the other hand I have a central database, used by lots of applications, I'm probably going to write stored procedures and ask the applications to call them; which, by the way, doesn't stop those applications using Linq. Just the ORM pieces.
If it's a really big database, like an AS400 with billions of records, I'm probably gonna wrap the logic in Web Services (or just a RESTful uri framework) where I can anyway, and ask people to integrate with those.
I quite agree with James that OO is inflexible, but I would like to take (small) issue with the statement that OO is good for GUI programming.
In my opinion, this is only true because nearly all modern GUI APIs are OO. Trying to program against an OO API with something other than OO programming is, of course, even more painful than doing so in an OO language, so OO languages win at GUI programming mainly for this reason.
But I have yet to see why dynamic method calls, local mutable state, etc., is a particular good model for GUI programming. I agree that being able to have different "things" share a common interface is a good thing for GUI, but doing this by letting "things" be objects with mutable state that inherit from a common abstract superclass and use virtual method calls to implement the common interface doesn't seem all that natural to me.
This is, however, getting a bit away from LINQ. I think LINQ has some very nice ideas in abstracting away from the underlying data structures and treating different kinds of collections as algebraic entities that share common properties and program against these properties using algebraic notions (instead of loops or visitors), but I find that the retro-fitting to VB and C# makes it less elegant than it could be.
LINQ is not SQL.
LINQ *is not* SQL.
LINQ is also not SQL embedded in a language.
Even if LINQ introduced a few keywords with an uncanny likeless with SQL DML, these are but syntactic sugar.
var q = from db.Customers where c.City=="London" select c;
is equivalent to (and indeed translated into):
var q = db.Customers.Where(c => c.City=="London");
When working with LINQ I have found that the SQL-like syntax is often more verbose than the method syntax. As in the above example.
LINQ to SQL is a way to use LINQ for querying and updating a database on a SQL Server.
@Chris: LINQ will not pull in the entire table. This is where the "expression trees" comes in. A boolean expression used in a query, like c.City == "London" (C# syntax) is represented as an expression tree. LINQ to SQL inspects the tree and generate the equivalent SQL.
Why is it nessecary to mock around with error prone strings to query a database? I makes it a pain to work with database. Even simple queries blows up when using multiple parameters.
Why is it nessecary to loop through my arrays and lists to locate items which satisfy certain criterias. Why can't I query, join, intersect, project on something first class as arrays and collections, when these operations are perfectly well understood using something as foreign as a database?
Why is it nessecary to use different techniques for querying relational data, hierarchical data (XML), in-memory collections, directory services, system instrumentation, reflection or web services? Wouldn't it be great if the way to query these data sources were just variants over the same theme? That is what LINQ is.
Microsoft has supplied LINQ "application" for SQL Server, XML and in-memory objects/lists. But LINQ is open for anyone to build their own "LINQ to LDAP" or "LINQ to comma-delimited-files".
IMO LINQ will have a deep impact on how future programming languages are shaped. To enable LINQ C# and VB.NET have also aquired constructs from functional and logic programming. Expression trees a well suited to design smarter, declarative validation and rule engines.
Microsoft has clearly taken the lead here. Don't be surprised to see a push to incorporate something like LINQ into Java, Ruby (which actually have most of the nessecary parts, except expression trees) and other popular programming languages.
I’ve read a load of posts here in the for/against vein. Some have it right, others are way off mark.
I don’t think it’s the posters fault that they are off mark; I feel it’s more to do with the wording of the article.
First, remember that .Net Framework clients should be detached from the database server. That is, only the data pertinent to the query should appear client side, those results are added to, changed and deleted. Then the client program should post the results back to the server as a whole, never as a row changes, is deleted or added. I think this is called “Disconnected Data”.
With this in mind, you should never get huge amounts of data sent from a server to a client in the first place. Just get what you need. This is where server side views/functions/stored procedures come into their own! I don’t know what they are called in non SQL Server setups.
If you know the DB, it’s far better to have it do all the work server side and return the results to the client, either directly or via a (web) service. The DB admin or the programmer should know how to do this.
I’d argue that a good DB admin with knowledge of how the DB works, and how it’s partitioned and indexed is far better than a client side OO programmer. As long as the programmer can communicate effectively, the DB admin should be able to create SQL that gets the data the programmer wants in the most efficient way.
I don’t see Linq as means of doing that. I see it as allowing the client programmer to query the data he/she has got on the client side. Regardless of weather the data is stored in custom types, xml, datasets or datatables.
That, in itself, is useful to client side programmers, because it allows them process data regardless of the way the data is held.
So, DB admins are still very much in a job!
Also, forget built in tools like the Visual Studio 2005 query builder, it creates crap SQL and reformats it to such an extent that it’s almost unreadable. Use something like Sql Server Management Studio or Sql Server Enterprise Manager to create queries, they leave the formatting alone.
Hopefully, I haven’t added to the confusion here. By the way I’m not a DB admin, I’m closer to client side programming.
I’m sorry that you feel there was too much focus on database but, even after re-reading, I feel it was made perfectly clear that LINQ can link to sources other than databases. I simply then had to focus on one source, so I chose what was for me the most obvious one.
> LINQ to SQL *do* have an abstraction (mapping) layer.
Yes, it does. In truth, I don’t believe I ever said it didn’t.
> Your claim that LINQ writes directly to the tables is flat out wrong.
What I actually said that “This would also address another criticism levelled at LINQ, which is that whilst it isn't SQL, it still writes directly to the tables. As soon as the table structure is altered, the application breaks.”
Let me expand that, because I agree it is cryptic. Clearly LINQ has to use a mapping layer since LINQ works with objects and databases use tables. However, take a look at example three (011) from our 101 samples. It includes the code:
var expensiveInStockProducts =
from p in products
where p.UnitsInStock > 0 && p.UnitPrice > 3.00M
It does not require a huge exposure to sample databases to recognize our old friend NorthWind here. If we were in any doubt, the appearance of Sir Rodney's Marmalade in the answer set must allay any misgivings. (Let’s not even begin to speculate as to why the Microsoft guys have seen fit to use such an old sample set of data rather than the shiny, new Adventure Works…….)
Northwind, of course, includes a table called ‘Products’ and ‘Products’ includes two columns called ‘UnitsInStock’ and ‘UnitPrice’.
So here, no matter what mapping could be done, the LINQ code it actually addressing a table; the mapping is essentially one to one. We can argue about whether this is wise, but the bottom line is that this is being done in the sample code that Microsoft has produced to teach people to write LINQ.
Now I argue in the article that databases also have a mapping layer (views, stored procedures and functions) and I also argue that most people don’t use them. So, whilst I agree that LINQ has a mapping layer, it is almost certain that people won’t use it effectively; its effective use is not being demonstrated to them and experience suggests that people don’t make use of abstraction layers here anyway. If the mapping isn’t used as an abstraction layer, that is functionally the same as it not being there.
However, I am happy to amend my original statement to: “This would also address another criticism levelled at LINQ, which is that whilst it isn't SQL, it can still effectively write directly to the tables. As soon as the table structure is altered, the application breaks.”
> Contrary to your claims, LINQ to SQL will also work with stored procedures,
I have to say, having reread the article several times, I simply cannot find any evidence that I claimed this. I mentioned stored procedures in relation to well designed databases. I never claim that LINQ will not work with stored procedures. I might (if pressed) argue that people are unlikely to use that ability, but that's different.
> I disagree that LINQ is a way to let programmers
> forget (or not learn) SQL.
Well, we will simply have to disagree about that. I think it is.
>My experience with LINQ (not just LINQ to SQL) is
>that it will drastically change (for the better)
>how I program any set/list manipulating code,
>not just database results.
I agree. I’m a database freak so I find SQL a perfectly natural language to use. But that doesn’t mean it is perfect, no language ever is. I want to try LINQ in some real projects and decide whether it has enough advantages is practice to make it worth adopting. Time alone will tell.
Linq isn't SQL nor is it an ORM tool. True, Linq statements are tightly coupled to the queried elements (tables in the case of a query against a DB) but the forthcoming Entity Framework for ADO.NET will add that necessary layer of abstraction, divorcing the the application layer entirely from the physical implementation.
"Application programmers are used to dealing with things such as objects, classes and methods but they are not usually familiar with SQL - the language used to query relational databases"
Err... then they are not Application programmers are they? They're bedroom hackers, or ivory tower theorists. Unfortunately there are a lot of them about.
Has anyone tried to hire a Computer Science graduate in the UK recently to become an Applications Programmer? I interviewed 4 CS MSc post-grads (all from Russell Group uni's) a few weeks ago - their lack of knowledge about what's required of real-world programmers left me seriously shocked. One guy had done several "database" courses in his MSc but hadn't come across SQL at all.
is that LINQ-to-SQL is not targetting people who can actually write SQL statements. Its goal is to try and haul the vast mass of "Access developers" away from JET by making integration with SQL Server easier and cheaper than the horror that is a workgroup-sized Access DB with multiple clients all sharing the same backend.
It's just a tool, and it's going to sell VS 2008 licenses, so LINQ is already a resounding success from Microsoft's point of view.
SQL is fine, I work with it in the field every day. It's very gratifying to write perfect SQL, tune for performance, and watch your app hum along as users are slamming the database. But I'd say the downfall is how long it takes to derive a proper schema -- do you really have all the corner cases of your application figured out? -- if such a thing is even possible up-front. On top of that, the problems of refactoring.
I know everyone on this site has the SQL standard memorized and has never ever made a design flaw that impacted a production application, but in past situations when I had to rapid-prototype a new app or a new function, database stuff was always the bottleneck on my development time. And well it should be, you might say -- maybe so, I can't tell you for sure.
So where does LINQ fit in? I'm not sure if there's a "killer app" for it, but it should help some people. If I were back in application development land, I could imagine hacking out an Excel or CSV file, writing some LINQ code against that, then doing the grunt work to change my data source to a relational DB when application logic isn't the bottleneck on my development time anymore.
The app "just works" after I change the data source. (Right? No comment...) For stored procedures, hack out a C# function using LINQ to emulate the procedure, then switch over to the actual procedure call when you're ready.
Of course, I'd have in the back of my mind the whole time the way I "really" wanted things to look in the database, along with potential integration issues. Or, you know, I could just design the schema up-front and let LINQ write my queries for me, and tune when a performance bottleneck actually shows up.
Lots and lots of in-house projects limp along just fine with carefully hand-written, yet absolutely terrible SQL. LINQ has to at least be capable of generating absolutely terrible SQL really quickly, thereby freeing up other development time. Not every app is a #10 Oracle-bloated ERP system where a missing clustered index will burn the office down and send your children to jail.
I don't think it's going to be a "Hibernate killer"...I believe for "real" Hibernate apps you're supposed to auto-generate your tables based on your Hibernate XML, thereby buying whole hog into their paradigm. I tried to put Hibernate on top of existing development databases a couple years ago and they certainly didn't make it easy on you back then. The key differentiator is the integration into the whole .NET stack.
And it's not an SQL killer, at all. But it gets us a little closer to the dream of being "data store agnostic" which was, after all, the intention of SQL in the first place. I don't use or support Microsoft's products in any way, but this is one of the things you can get when one company unilaterally controls an entire software stack.
Biting the hand that feeds IT © 1998–2017