2

Imagine you have an application with the domain object User:

public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Surname { get; set; }
        public bool Premium { get; set; }
        public int OrdersNumber { get; set; }
    }

Your application has 3 views:

  • the first one must show id, name and surname of all the users
  • the second one must show name and surname of a user, and if he's a premium user
  • the last one must show name, surname and number of orders of a user

So I would call 3 different queries on my db:

  • the first one that loads Id, Name and Surname of all the users
  • the second one that loads Name, Surname and Premium starting from an id
  • the third one that loads Name, Surname and OrdersNumber starting from an id

The problem is when I map the result of the queries to a User object:

  • in the first case I would obtain objects with Premium=false and OrdersNumber=0
  • in the second case I would obtain an object with OrdersNumber=0
  • in the third case I would obtain an object with Premium=false

These data are not correct, they've simply set to their default value. So what should I to do?

  • I can ignore the problem, and use the properties only when I know that their values have been loaded
  • I can modify Premium and OrdersNumber in order to accept null value (the problem of this solution is that maybe User objects (on domain) have always Premium and OrdersNumber data, so the class doesn't respect domain rules.):
public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Surname { get; set; }
        public bool? Premium { get; set; }
        public int? OrdersNumber { get; set; }
    }
  • everytime I need users data, I load all the fields, even the ones I don't kneed. The problem of this solution is the waste of time and memory.

Which is the best solution?

5 Answers 5

2

Loading always all fields is the solution which requires probably the least amount of programming effort (since only one query is required instead of three; ideally that query could be generated by some ORM tool). It will also avoid a lot of hassle when the application will be extended to use the loaded ´User` objects for further processing.

Note further that performance-wise, the saved time and memory overhead of querying and storing 3 instead of 5 columns is usually negligible in most real-world applications (as long as non of those additional columns contains some very large binary or text field). So I would heavily recommend not to optimize anything by leaving out columns when you don't have a real, measurable performance or memory bottleneck.

For the (rare!) case where partially loaded objects would actually be required in this application for performance reasons, I would heavily recommend against using nullable datatypes to represent the missing columns. In case some of the columns in the user table allow NULL values, one will not be able to distinguish values which are NULL at the DB, or those which were not loaded from the DB. Better find a way to represent the meta information which part of an object was loaded in a different way, or find a way to make this requirement obsolete.

6
  • "This is always a trade-off" sounds like wisdom, but is actually not that applicable. Returning null is never acceptable. Passing null is never acceptable. And by "never" I mean sure, you can probably find some exception, but that's irrelevant. There is a rule. I would also add: using data structures in object-orientation is never acceptable (again, some rare exceptions may apply). Yes, technically there is no "best" solution, but there is easily better ones. By miles. Jan 22, 2021 at 10:23
  • @RobertBräutigam: there is nothing wrong with using C# classes to load and hold data records from a database in the proposed way. It may not conform to your idealistic point of view of "what OO programming should be", but that was actually not the question here. And the same is IMHO true for the use of null. Though I would not use nullable attributes here to represent partially loaded objects, I would have no problem to use them to represent NULL values coming from nullable columns from a database.
    – Doc Brown
    Jan 22, 2021 at 12:07
  • 2
    @RobertBräutigam: "Passing null is never acceptable. " Your blanket dismissal of null is much too broad sweeping to be considered helpful advice. By itself, there is nothing wrong with returning null. Null has a purpose - to indicate non-existence of a value. What's wrong about OP's approach is using null to indicate not-loadedness (as opposed to non-existence), which is a null abuse. "And by "never" I mean sure, you can probably find some exception, but that's irrelevant." Any 0..1-to-whatever relationship is a clear use case for null, and most definitely not irrelevant to consider.
    – Flater
    Jan 22, 2021 at 12:18
  • @Flater I know what I'm saying is unfamiliar, even unpopular. I've been building these data-oriented application too for the last two decades and believe me there's another way. The OP's problems are directly related to using data structures, so I don't think I'm off-topic. Anyway, if it is all the same to you, I'll continue to answer these kinds of questions the way I see fit. I sincerely believe that given some time our profession will eventually catch up :) Jan 22, 2021 at 12:33
  • @RobertBräutigam string is a data structure. So, no strings either? Man, I'm sure going to miss any kind of collection or array, since those are also data structures.
    – Flater
    Jan 22, 2021 at 12:39
1

General advice

This may be oversimplified advice, but it really boils down to the essence of clean coding: do not create multi-tools. It's what I like to call the Swiss army knife principle:

enter image description here

It's the classic multi-tool, but it's not a great solution for multiple reasons:

  • While it has many tools, each tool is inferior compared to the same tool made for a single purpose. A knife is better than a Swiss army knife. A saw is better than a Swiss army saw. This is due to compromises in the design required to fit so many tools onto one object.
  • If one of your tools needs upgrading (e.g. they've developed a better bottle opener), you can get your Swiss Army knife upgraded, but you lose all of your tools while in the middle of an upgrade.
  • You cannot both give your Swiss army knife to the sommelier to open a bottle of wine and the fromagier to cut the cheese.

This part of the analogy carries over to the field of software. Compromises due to shared design, a single point of failure, ... these are all problematic design approaches.

So why was this knife designed, and still so popular? Because it's easy to carry, and it's considerably harder to carry each individual tool in your pocket at the same time.

But this part of the analogy does not carry over to the field of software. We can build as many classes as we want without struggling to carry them all around.

So let's avoid all of these design issues, since we're not actually solving a problem by trying to mash all of these functionalities into a single class. One class, one tool. Another tool? Another class.


Specific advice

Each view should have its own model, which means that you can tailor each model to specifically contain what it needs, regardless of what another view needs.

The short answer here is to have 3 user DTOs, one to fit each view.

But you seem to want to reuse the same User. That's actually okay too, but only if the User DTO is always the same, i.e. contains all of the data that you've put into the class definition. You've already touched on this:

  • everytime I need users data, I load all the fields, even the ones I don't need. The problem of this solution is the waste of time and memory.

This approach is much easier to develop, but you're right that it's slightly less performant in terms of database bandwidth.

You have to weigh the loss of bandwidth by loading some data you won't use against the complexities of developing separate DTO classes, mapping all of them, and making sure that your repository interface is still clear enough so your consumers can receive the right type of DTO they need.

This is also very sensitive to needing to be expanded. If tomorrow you have a 4th view that showcases a user, it's likely going to need another subset of data fields, and thus a 4th DTO, mapping, and repository method. But if you had been using a fully fleshed out User class, you could've reused it for the 4th model since it already contains what you need.

Which is the best solution?

As with most software development issues, there is no one-size-fits-all solution.

If you have a highly trafficked application (let's say Facebook or Twitter) and a significant development budget, those little data optimizations are going to save more than it costs to develop them.

But if you have a smaller development budget and you are not squeezed for performance to this degree, then you have more freedom to waste tiny bits of performance in favor of a much simplified development approach.

As long as the data in question comes from the same table row, without any additional joins or calculations, the cost of adding it is negligible for performance (assuming sensible data columns, not outliers like a giant BASE64 string). When the data is fetched via additional joins, it's usually better to simply fetch this data in a second query than it is to try and develop two queries, one with join and one without.

But you have to weigh your own situtation. How much are you squeezed for performance? How much are you trying to avoid complexity during development?

1

So you have a class User with five properties, and depending on the context, one or more of these properties are not set. That is just asking for trouble. Because you can pass a User object with some properties set to a method needing other properties. You need forever keep track of which kind of User object you have.

A bad solution (but better than what you have) is having three classes, each containing only certain properties. At least the compiler can tell you when you get it wrong. If you think this is bad, what you have now is worse.

A very simple solution is to always set all the properties, so when you are given a User object, you always know what you’ve got.

If there are properties that are expensive to set, have a Boolean whether the property is set, a getter that automatically sets the property if it’s not set.

0

Everytime I need users data, I load all the fields, even the ones I don't need. The problem of this solution is the waste of time and memory.

Never worry about waste until you have actual numbers in front of you showing that it is making users unhappy.

I've worked on many systems with ORMs that, by default, load all the fields of a database record into the in-memory object. These ORMs do provide facilities to "trim" some of the un-needed fields from the query, but in these systems, it was almost never used. The overhead of the extra fields was, in the overwhelming majority of cases, negligible compared to everything else that was involved in the query. In the cases where we did need to optimize past it, we would skip the ORM entirely and write raw SQL queries. (When optimization is justified, it trumps having a beautiful architecture.)

-3

All your problems arise from the decision to have data structures instead of proper objects. Because a data structure has no idea how it will be used, it is forced to be generic and conform to each use-case, some of which may even have contradictory requirements. In addition you are also forced to make it work for persistence too.

Short tips how to avoid this:

  • Don't have data structures (Beans, DTOs, "value objects", etc.). Have objects that actually do things.
  • Don't return null values. Ever. If you don't have data-structures you'll have no need for nulls anyway.

Edit: Here's how that might look like. Knowing nothing else of your specific case, and assuming it has some sort of UI, also assuming you want to show those things in some sort of UI component:

public class Users {
   public Component DisplayUsersWithId() { ... }

   public Component DisplayUsersWithSubscription() { ... }

   public Component DisplayUsersWithOrders() { ... }
}

All of those return something directly usable for the UI. There is no need for a User data structure, so there is no need to load things you don't need. There is no need to map anything. You can even have paged loading in there with the necessary controls already in the Component that you return.

If your requirement is to show those tables on the UI, that is the most direct and easiest way you could go.

8
  • (a) About the "never DTO argument", what is your imaginary data-structure-less application sending to or receiving from its API consumers? How about the database (or any other data store) it interacts with? Good luck trying to pass "objects that do things" into HTTP responses or SQL queries. (b) About the "never null" argument, how are you going to represent an optional relationship between two objects without using null or something home-cooked that effectively works just like and does nothing more than reinvent null? The simple answer is that you can't.
    – Flater
    Jan 22, 2021 at 12:32
  • @Flater (a) You don't need an data structure object to talk over the network. What's wrong with connection.send("abc")? The object that has the data can itself send the data, or whatever the business requires. Then it is not a data structure anymore. (b) I don't care about representing relationships, since I don't design data structures. Objects have behavior, and there null has no meaning. Or more exactly, it has no explicitly defined meaning. Which is a problem. Jan 22, 2021 at 12:45
  • 1
    With all due respect, just saying "use proper objects" without telling what this precisely should mean for the given example, and how this should solve the described problem, does not look like an answer to the question to me. It gives me the impression you are trying to use this thread for advertising your kind of OO agenda.
    – Doc Brown
    Jan 22, 2021 at 14:20
  • @DocBrown Why did you stop reading at the first sentence? My answer contains two very specific and actionable advice, with bullet points even, to avoid the problem the OP is having. Just because you disagree doesn't mean I didn't answer properly. Jan 22, 2021 at 14:37
  • @RobertBräutigam: I read your answer in full, but maybe my comment was not clear enough. "Have objects that actually do things." is IMHO not a decent explanation for the given example of a User class and the requirement of 3 different views and the twist of trying to avoid querying only required columns from a database. You tell the OP what they should not do, but when it comes to the point what they should do instead, you stay pretty vague.
    – Doc Brown
    Jan 22, 2021 at 15:19

Not the answer you're looking for? Browse other questions tagged or ask your own question.