Background: I have a project that is not at all large scale (2 ASP.NET web applications with like 10 pages each). These two web applications share a lot of back-end requirements, specifically the ability to manipulate a set of around 50 tables. They both hit the same RDBMS, which will always be MySQL.
Problem: A large feature-rich ORM library (Subsonic) was added to the project awhile back, which it turns out was just too much. It’s too big, there’s too many parts, there’s too much overhead. Some Collections that are generated by Subsonic take >1min to create, whereas raw inline queries (bad I know) that accomplish the same take ~2s. It’s not just Subsonic that is the problem, NHibernate and other ORM tools are just too big, with way too many features that will never be used. The scope of these projects will never get huge either. Also database agnosticism is unnecessary as we will never switch away from MySQL, and no one will ever use this tool except internally.
Clearly inlining queries is bad, unsafe, wrong. Linq is also not an option. Thus there is a need for some small custom ORM tool for me to package up in a library. My thought is to include:
- the foundation is a the Table class, whose constructor is just fed a db name and a table name
- the Table class would expose a few methods publicly
- public DataRow Record(string primaryKeyValue) //fetches a single records
- public DataRowCollection Records(string whereClauseOrRangeOfKeyValues) //fetches a collection of records
- public bool Save() //save the current state of the database by updating/inserting any rows that have changed; not sure the best way to maintain which records are dirty or not
- public bool Loaded(string primaryKeyValue) //tells whether a given record exists or not
- public DataRowCollection Query(string mysqlQuery); //run a specific custom query
- privately there would be pretty much only a few things: a sanitizer, a method to do selects, a method to do upserts and not much else
Perhaps the pooled knowledge of StackOverflow can highlight shortcomings of this basic setup or offer improvements. Thanks in advance.
EDIT: One constraint (not sure how firm it is at this point) is that I not used a pre-built library. Higher ups are so turned off by Subsonic that any third party utility is likely to be rejected. So it’s more of an architecture choice as opposed to a utility choice.
The problem with your setup is that it simply doesn’t address the issue at hand. Which, incidentally, is one of the problems with the majority of ORMs.
For example:
Let’s say you have a Projects table and you need to get a list of projects whose status is Active. You don’t need / want all of the fields, just the project name, status, date started and last update date.
How do you accomplish that in your layout? At the outset your table class won’t work (requires connection and table name only) because it’s going to require you to pull all of the columns: which is the problem you are already faced with.
Which means you have to use your Query method to get just what you want.. However, how does
tableknow to only change the status column of a single row when you call.Save()? Now you are getting really complicated because it requires yet another custom query for this to be performant. Or, at the very least, you’ll have to implement change tracking on each column and have your engine be smart enough to only update those values that have changed.Now consider that the vast majority of your queries are like this. Meaning, you want to pull records that usually involve joins crossing table boundaries, you don’t want all of the columns (for performance reasons), and your updates are going to be very targeted… This is where ORMs based on a direct mapping of your schema to classes will always fail the performance test.
So, what are you to do? My opinion is that you have to split the types of data access you are doing into two logical sets of operations. One set will involve having regulars OOP classes in order to perform validation and ensure that your business rules are properly functioning. These will be used when creating brand new records.
For the next set you can use simple DataTables or dynamic classes. Send your query to the database and pull back the records you want. Whenever doing an update, make sure that query is highly targeted. You can accomplish this with stored procs or inline sql; whichever you’re more comfortable with. Dapper is actually quite good for this use case or just use Enterprise Library with regular SqlCommand/SqlConnections..
Point is, don’t build your own ‘ORM’ unless you understand the problem with existing ones.