In my very small C# application, I handle the SQL queries directly from the code using basic ADO.NET. I do not want to use an ORM, such as Entity Framework.
Now, I have a repository for CRUD operations. But I find I need flexibility for more complex queries.
For example, here is the (naive and simplified) process of logging into the application:
- The user types a password (no username, Windows style).
- A
Count
SQL-query counts the users with those credentials. - If there is a user with those credentials, a users repository load the user's information from the database and retrieves a
User
object.
Now, where should that Count
method be? It certainly can't be in the repository, so where should it be? Is there a pattern for handling such non-CRUD operations against the database?
2 Answers 2
It certainly can't be in the repository
Sure it can. This may actually the best place for it. Your repos responsibility is to provide an abstraction for a subset of the database, which might be replaced by a mock repository for testing purposes. Putting something like a Count
method into a repo will still fit into that "pattern". Note if client code of the repo does not need to use SQL directly, this makes that client code much easier to unit test (without a database).
Of course, if you get a dozen of such query methods, it might become necessary to split up the repo class further into separate classes, but how and when you do this is up to you, there is no brain-dead cargo-cult design pattern for this.
-
Right, your Count() repository method calls ExecuteScalar() under the covers. One may have multiple interfaces IFooRepository, IBarRepository once the method count grows.Jon Raynor– Jon Raynor09/28/2017 17:20:16Commented Sep 28, 2017 at 17:20
-
Correct me if I'm wrong, but shouldn't a repository be a common set of CRUD operations (common interface) to keep persistence impl abstracted away? I think including a simple count in that interface would still fit into that, but he included other examples. I have a stored procedure that is doing several joins that won't have any CUD operations and is joining several datasets (which have their own repos). I don't feel like my proc fits into a repository, but I do want to keep all data access abstracted away from the service layer. I feel like there should be another pattern utilized hereAdam Plocher– Adam Plocher09/09/2018 23:26:22Commented Sep 9, 2018 at 23:26
-
1@AdamPlocher: "I think including a simple count wouls still fit into that" - yes, exactly what I wrote. "he included other examples" - huh? where did you find those? "but I do want to keep all data access abstracted away from the service layer" - sure, as I wrote, at some point you will need additional classes, and your situation is probably such a case. I think your only misconception here is that for every useful design decision there must a "pattern". Start using your own experience, don't believe anything in your program needs to have a pattern name.Doc Brown– Doc Brown09/10/2018 00:07:54Commented Sep 10, 2018 at 0:07
Typically this done via:
- ExecuteReader - Returns 1 or more data records =>
Select * from table WHERE ...
- ExecuteScalar - Returns a single value =>
Select count(*) from table WHERE ...
- ExecuteNonQuery - Does not return anything =>
Delete from table WHERE ...
One can also have a return value from a stored procedure as well that can be referenced as well as out parameters that can be inspected.
Your data layer should support these operations. (ADO.Net does)
-
Thanks. My question is how the data layer should support them, and if is there a pattern for it. For CRUD operations, there is a very well-established pattern: a repository. But what about non-CRUD operations? Thanks a lot!Michael Haddad– Michael Haddad09/27/2017 19:14:19Commented Sep 27, 2017 at 19:14
-
2@Sipo: There's no "pattern" required here, other than the use of
Execute**
. Just put the code into your Data Access Layer and use the resulting function in your upstream layer. BTW, these are essentially the same methods you can use from EF or Dapper.Robert Harvey– Robert Harvey09/27/2017 19:16:55Commented Sep 27, 2017 at 19:16 -
@RobertHarvey- My data access layer is composed of more than one class. It's a collection of classes inside a namespace. Is it a good solution to dedicate a class (utility) for such operations?Michael Haddad– Michael Haddad09/27/2017 19:23:34Commented Sep 27, 2017 at 19:23
-
2Well, I consider custom queries a part of the business logic, so I generally put them in the Business Logic Layer or Service Layer. I usually expose an
ExecuteNonQuery
method on my Data Access Layer so that I don't have to take a reference to Entity Framework in my other layers.Robert Harvey– Robert Harvey09/27/2017 19:26:50Commented Sep 27, 2017 at 19:26
It certainly can't be in the repository
Why not?