3

I'm using a library called LanguageExt. This library provides some tools to handle functional programming within C# code. I'm also using FluentNHibernate in order to map my domain classes to my database.

When a property is nullable I want to use Option<T> from LanguageExt. It's a struct that either holds a value or is equal to a None.

One of my class model, say Car has an optional property, say Sunroof which is of type Option<Window>. Like this:

public class Car
{
   Window _sunroof;
   Option<Window> Sunroof
   {
     get => Optional(_sunroof);
     set => _sunroof = value.IfNoneUnsafe(() => null);
   }
}

My mapping is like this:

References<Window>(x => x.Sunroof, "idSunroof")
   .Not.Nullable();

My question is: how do I map the Sunroof property using its backing field knowing that they don't share the same return type?

xlecoustillier
  • 16,183
  • 14
  • 60
  • 85
Ephasme
  • 286
  • 1
  • 10
  • I am not familiar with NHibernate. Entity Framework has DTOs called entities that help bridge the gap between the structure of the data in the database and the structure of the data in the business models. Does NHibernate have something like this? – Tyson Williams Feb 18 '19 at 19:05
  • Yeah, actually the Car class is exactly that. It's a domain model but also mapped to some table in the database via the mapping configuration done by FluentNHibernate. – Ephasme Feb 19 '19 at 10:00

2 Answers2

3

It's a domain model but also mapped to some table in the database via the mapping configuration done by FluentNHibernate.

I don't think this is a good idea. You are trying to do three (or maybe four) things in this one class that I would keep separate.

I recommend having a DTO for NHibernate (maybe called CarDto) and a business model (probably called Car). That way, CarDto can change for reasons related to the database (but not for modeling reasons) and Car can change for modeling reasons (but not for database reasons). For example, functional programming would have the business model be immutable, but NHibernate may require that its DTOs are mutable. If you use the same type for both purposes, then you can't satisfy all the design constraints.

how do I map the Sunroof property using its backing field knowing that they don't share the same return type?

I don't think you should have a property and a backing field with different types. With CarDto, use null to represent the absence of a Window. Then when mapping from CarDto to Car, map null to the None state (via the Optional function that you are currently using). Then when mapping from Car to CarDto, map None back to null (via the IfNoneUnsafe method that you are currently using).

Your Car class

  1. is NHibernate's DTO,
  2. is your business model,
  3. contains the mapping from the DTO to the business model, and
  4. contains the mapping from the business model to the DTO.

This is the three or four things (depending on if you count the mapping as one thing or two) that I mentioned above.

ADDED 2019-02-20

[your answer is] not a solution to my problem but a proposal for a better architecture

It is both.

I fully agree with what you said and I would be very happy to do that but I can't. In my code base I have more than 250 model classes which are quite badly designed and with a lot of wrongly made dependencies. I can't afford to refactor all of that at once.

I am not suggesting that you change everything at once. Far from it. In the style of Refactoring by Martin Fowler, I recommend making many small changes over time.

For example, how difficult would it be to change Car to

public class Car
{
   Option<Window> Sunroof
   {
     get => Optional(SunroofBacking);
     set => SunroofBacking = value.IfNoneUnsafe((Window) null);
   }
   Window SunroofBacking { get; set; }
}

and use (the "better" named) property Sunroof for business logic reasons and use SunroofBacking for NHibernate reasons?

Tyson Williams
  • 1,630
  • 15
  • 35
  • 1
    This is of course the way to go to get the purest possible architecture. Unfortunately, this is not always realistic when dealing with legacy code. [This article](https://enterprisecraftsmanship.com/2016/04/05/having-the-domain-model-separate-from-the-persistence-model/) for instance expresses another opinion on the matter. All of this is a nice source of endless debates of course :) – xlecoustillier Feb 19 '19 at 20:34
  • 1
    Hello @xlecoustillier. Thanks for your comment. I am definitely aware that many others prefer the "less pure" approach. In my limited experience, the "more pure" approach has always been superior. The given code is using the functional programming library Language Ext. Functional code is considered "more pure" both generally in software development as well as specifically in C# development. My answer was trying to help the OP solve their problem while also making their code more consistent with decision to use `LanguageExt.Option<>` in the first place. – Tyson Williams Feb 19 '19 at 22:52
  • 1
    I'm 100% aligned with that :) – xlecoustillier Feb 19 '19 at 23:37
  • 1
    I very much appreciate your answer but I can't accept it since it's not a solution to my problem but a proposal for a better architecture. I fully agree with what you said and I would be very happy to do that but I can't. In my code base I have more than 250 model classes which are quite badly designed and with a lot of wrongly made dependencies. I can't afford to refactor all of that at once. – Ephasme Feb 20 '19 at 08:10
  • I updated my answer to respond to your comment @Ephasme – Tyson Williams Feb 20 '19 at 14:58
  • Thanks @TysonWilliams. I read your edit and I certainly would like to go in that direction but doing this forces us to expose the SunroofBacking property in order to be able to use QueryOver() which defeat the purpose in the first place... :( – Ephasme Mar 01 '19 at 10:13
  • Defeats the purpose of what? – Tyson Williams Mar 01 '19 at 12:12
  • Sorry I wasn't clear, it defeats the purpose of having only one property so I can use QueryOver() without exposing the unsafe property SunroofBacking in order to use it in the query. – Ephasme Mar 01 '19 at 20:06
  • I see. That makes sense. Looking back, the suggestion in my edit is essentially what you ate already doing. I will keep thinking about this. – Tyson Williams Mar 02 '19 at 02:26
  • Thanks for your answer and your comments anyway! – Ephasme Apr 01 '19 at 15:18
1

I finally found a solution:

References(x => x.Sunroof, "idSunroof")
    .Access.CamelCaseField(Prefix.Underscore)
    .Class<Window>();
Ephasme
  • 286
  • 1
  • 10