0

I am looking for some ideas on the best way to solve a class of similar problems. I am using Linq2SQL below, but I suspect the same issues would apply to linq2Entities.

I have a database table that has some 30 columns. This is an old application that is using Linq2Sql for the data layer (few hundred tables). I had some very common formatting that I wanted to encapsulate in a convenient place, so I put them into properties on the table using a partial class. For example, say a Users table with 30+ columns including a user's first, middle, and last name in separate columns and so one property called FirstnameLastname concatenates the first and last names with a space ("Sam Spade"), and another property called LastnameFirstname concatenates them in a different way ("Spade, Sam"), and others that, for example, might have to be smart about optional middle names, honorifics, etc. If it's not obvious, the reason to encapsulate these is that I can make sure the name is always formatted correctly throughout the app, and I can change the formatting in one place to change it everywhere (or implement language- or locale-specific differences in the formatting).

These two properties look like this currently:

public partial class User : IAudit, ICloneable
{
    ...
    public string FirstnameLastname => Firstname + " " + Lastname;
    public string LastnameFirstname => Lastname + ", " + Firstname;
    ...
}

There are other properties that are much more complicated, but are variations on the theme.

This works great, and I can write something extremely readable like this:

dc.Users.Single(u => u.UserId == 20).FirstnameLastname;

or

from u in Users
where u.UserId == 20
select u.FirstnameLastname

However, what I didn't think about at the time was that this generates a query that pulls down all 30+ columns of the table for the one row where UserId is 20. This is wasteful, but not particularly annoying. However, let's say I'm doing a join between seven tables and I use this property in a very large select. Now, I'm adding 30 columns to the result set for every row when I only need to add Firstname and Lastname (and linq is throwing away the rest of the data).

If instead I write:

dc.Users.Where(u => u.UserId == 20).Select(u => u.Firstname + " " + u.Lastname)

then it only brings down two columns, Firstname and Lastname (in fact, in this super-simple example it encodes the expression in SQL, which might or might not be possible for more complex properties).

The question is, is there a way I can hint to Linq2Sql that I only need the few columns the property requires? Is there a better way to do this that will result in clean, self-documenting code like these properties? It seems like predicates are what I need, but I'm not seeing how to make that work. Obviously, these properties are also used outside of linq statements, when I have a user object at hand and want to get the proper formatting.

I'm looking for a general methodology discussion, not necessarily a solution covering this specific example.

Thanks for reading!

Cfun
  • 8,442
  • 4
  • 30
  • 62
Scott Gartner
  • 862
  • 14
  • 22
  • The ORM does not know which properties of the object you will be using after the data is retrieved and entity is formed specially if the property you are accessing is not mapped to any column in the table. `FirstnameLastname` is not mapped to any column and ORM does not know how it is getting its value that's why `dc.Users.Single(u => u.UserId == 20).` will build the entire user Object by selecting all the columns. On approach to solve is to create a View out of the table with the columns `FirstnameLastname`, `LastnameFirstname` etc and map an entity with the view and query the view via ORM. – Chetan May 21 '20 at 04:11
  • 1
    Other approach is to use `select` with specific properties and build use Object. `dc.Users.Where(u => u.UserId == 20).Select( x= > new User {FirstName = x.FirstName, LastName = x.LastName).Single();` – Chetan May 21 '20 at 04:15
  • @ChetanRanpariya I get that it can't know, which is why I was thinking the solution would not be dictating what columns to return, but would somehow hint what columns are required. If link2SQL found that it was returning the whole object then it would still do that. My thinking was that it seemed like there must be a way for it to know it's evaluating the linq statement and do something different. For example, that's what happens when you use Contains in a query, it actually generates different SQL, but if you used Contains outside of linq it would call the base method. This seems similar. – Scott Gartner May 21 '20 at 16:13
  • @ChetanRanpariya As to your second suggestion, if I do that there's no way for me to call the property on the Users object since I would only have a dynamic object and no User object. The goal is to put the shared functionality in a place where it would be hard to miss by a future programmer. User.FirstnameLastname for example will come up in the helper list in Visual Studio. Some static function on User or some function not on the User object would not, so future programmers would be likely to code the formatting again (adding tech debt). – Scott Gartner May 21 '20 at 16:17
  • @CFun Can you explain the point of the edit that you made? Why is it better to put tick marks around all those symbols? – Scott Gartner May 21 '20 at 16:22
  • 1
    @ScottGartner IMHO i found it more eyes friendly. – Cfun May 21 '20 at 20:57
  • In my second suggestion there is no dynamic object, its the user object only but with only two properties having values. You will still get LastnameFirstname property values of those objects. ORM decides which columns to add in select statement depending on the Select clause of LINQ method. If there is no Select used in the linq query it will select all the columns. Also the select clause should include the columns which are mapped to the columns in database table. The derived properties can not be translated to SQL by the ORM. – Chetan May 22 '20 at 02:24
  • The appropriate approach for you to build an instance of `Func` dynamically and pass it to `select` method. Example https://dotnetfiddle.net/OfpOgK the example is based on Generic collections not using any ORM but it has enough to give you the idea. – Chetan May 22 '20 at 02:40
  • You can also look at [here](https://stackoverflow.com/questions/16516971/linq-dynamic-select/45205267) and [here](https://stackoverflow.com/questions/27634801/dynamically-build-select-clause-linq) to understand how to generate the select statement dynamically. – Chetan May 22 '20 at 02:43
  • @ChetanRanpariya Doh! I missed the class name in your suggestion, that might work, but it does require the caller to know what properties are required by any properties it plans to use (which also means it could break if the library changed which properties were actually required). I'll give that some thought, thanks. – Scott Gartner May 26 '20 at 18:56
  • @ChetanRanpariya The fiddle is interesting, but it actually highlights a problem with your proposal that I haven't resolved yet, which is that City can't print out because City isn't in the select list. I'm not sure how to go from your example to a blended select list. I created a slightly more complex version to take the knowledge of which columns were required out of the caller's hands (so that it doesn't break if the properties change) and to attempt to mix the select functions. I have not succeeded, but [here's the fiddle](https://dotnetfiddle.net/noiAIM). – Scott Gartner May 26 '20 at 19:54
  • Your fiddle code is cleaner as the caller no need to worry about the column names or properties being selected as part of the selector. The caller can just choose any selector to use while writing the linq. The linq can not infer the usage of the properties in advance while preparing the SQL query so it is kind of necessary to provide list of properties to select to linq query. There is concept of computed columns in latest entity framework. I am not sure it that's available in LINQ to SQL – Chetan May 29 '20 at 02:42

0 Answers0