Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use DefaultBoolean, DefaultInt and similar for distinguishing between nullable primitive type and non-nullable with default DB constraint #33729

Open
janseris opened this issue May 15, 2024 · 5 comments

Comments

@janseris
Copy link

janseris commented May 15, 2024

If e.g. bool or int has default value set in database, there is an issue specifying if null is to be inserted or default value should be used by the DB.
The problem is created by the nature of the C# language which has default "zero-like" value for all primitive types ("structs") which does not exactly correspond to DB ability to specify default value.
By using a new structure which will add 3rd value, the problem is solved.
For example, DevExpress uses this for UI Controls.

https://docs.devexpress.com/CoreLibraries/DevExpress.Utils.DefaultBoolean

The data type could have implicit conversion to the primitive type for output mapping purposes because the issue is only present on input mapping (C# => database) not in output mapping (database => C#).

Use something like that to be able to distinguish "no value" and "set default value" for SQL Server (and probably all other DB engines which support default values in nullable columns as well).

Real life situation:
Now I am stuck on EF Core Power tools mapping bool column with default value to bool? to allow inserting "null" from C# which triggers using default value on DB level but this creates confusion when reading data from DB because now the column is represented as bool? in C# while it's not nullable in DB.
If the behavior is disabled, I cannot then let database insert default value for me.
But I need both - ability to use default DB value AND represent the correct data type in C# when reading from DB and the DefaultBoolean with implicit conversion operator to bool (similarly for int etc.) should be solving it.

@roji
Copy link
Member

roji commented May 15, 2024

Why not just use a nullable type (e.g. bool?)?

@janseris
Copy link
Author

janseris commented May 15, 2024

Why not just use a nullable type (e.g. bool?)?

Because bool? means "value can be missing" which is not a valid statement about a boolean value.
Using or expecting null in that case does not make sense.

If there are multiple columns where some are bool with default constraint and other are bool? in database, they are both shown as bool? but when selecting, it gets pretty complicated to assume that you can use Nullable<T>.Value safely (how can you guarantee which one is really bool? and which one is bool with default constraint at compile time (a great C# code safety feature))?
Programmers assume wrong. The language/compiler does not assume.

Or same situation with int with default constraint.

@janseris janseris changed the title use DefaultBoolean, DefaultInt and similar for distinguishing between null primitive type and default DB value use DefaultBoolean, DefaultInt and similar for distinguishing between null primitive type and non-nullable with default DB constraint May 15, 2024
@janseris janseris changed the title use DefaultBoolean, DefaultInt and similar for distinguishing between null primitive type and non-nullable with default DB constraint use DefaultBoolean, DefaultInt and similar for distinguishing between nullable primitive type and non-nullable with default DB constraint May 15, 2024
@roji
Copy link
Member

roji commented May 16, 2024

[...] but when selecting, it gets pretty complicated to assume that you can use Nullable.Value safely

In EF LINQ queries, you should be able to always access Nullable<T>.Value safely (contrary to regular C#) - EF translates the query in such a way that this will always work.

It's true that once you've loaded the entity and are outside of a LINQ query, you now have a bool?, and there's a bit of ambiguity as to whether you can just assume there's a non-value there (because the corresponding database column is non-nullable, with a default constraint) or whether you need an actual check. You can of course look at the property to see if it has [Required] on it (at which point you know you don't need to check), which doesn't seem that bad.

Beyond that, IMHO introducing a whole new type just to make that distinction apparent when coding is a bit sledgehammer that isn't really justified. It would also mean users start integrating an EF-specific "defaultable" type on their CLR types, which goes against the plain-.NET POCO philosophy of EF, and which many/most users wouldn't appreciate. So while I understand (and agree) with what you're saying in theory, I don't think it makes sense for EF to introduce a special defaultable type and plumb support for it all through its internals. If .NET already had a standard Defaultable<T> type alongside Nullable<T> to indicate "default", we'd certainly consider supporting it (and would probably have done so already), but as things stand, given the little value this would bring, I wouldn't see us introducing our own type.

@janseris
Copy link
Author

janseris commented May 16, 2024

You can of course look at the property to see if it has [Required] on it (at which point you know you don't need to check), which doesn't seem that bad.

Well yes if you use DB-first approach with default settings then you have to look into the database or look into DbContext OnModelCreating (for every property for every model you have) which is inconvenient and prone to error.

Imagine real world this entity and that you don't know what the properties mean (so you don't know whether they are bool? or bool with default constraint) (note: bool? is not present here because I disabled EF Core Power Tools feature "scaffold type with default constraint as nullable" which btw means I now cannot use default constraint and vice versa). How long would it take to write code with inline intellisense vs looking into the C# model or database or OnModelCreating to verify yourself everything manually? And this is just one entity.

public int ID { get; set; }
public int SkupinaTypuOpravneniID { get; set; }
public string Nazev { get; set; }
public string Popis { get; set; }
public string Zkratka { get; set; }
public string NazevCislaOpravneni { get; set; }
public string RegexProValidaciCislaOpravneni { get; set; }
public string VysvetleniFormatuCislaOpravnení { get; set; }
public int? TypickaDobaPlatnostiMesice { get; set; }
public bool JeVzdyNaDobuUrcitou { get; set; }
public bool JeVzdyNaDobuNeurcitou { get; set; }
public string NazevRozsahuOpravneni { get; set; }
public bool MaObory { get; set; }
public bool MaximalneJednoPlatneOpravneniNaOsobu { get; set; }
public bool MaUredniRazitko { get; set; }
public bool PovinnyOtiskRazitkaNaStraneA { get; set; }
public bool MaElektronickouVerziRazitka { get; set; }
public bool JeUredniOpravneni { get; set; }
public bool MaStupne { get; set; }
public string NazevStupne { get; set; }
public bool MaDodatecneUdaje { get; set; }
public string NazevDodatecnychUdaju { get; set; }
public int? SirkaRazitkaMilimetry { get; set; }
public int? VyskaRazitkaMilimetry { get; set; }

Yes I understand it would be pretty non-standard for EF to introduce such type without it existing in C# in general so this won't be changed anytime soon.

t's true that once you've loaded the entity and are outside of a LINQ query, you now have a bool?, and there's a bit of ambiguity as to whether you can just assume there's a non-value there (because the corresponding database column is non-nullable, with a default constraint) or whether you need an actual check.
The largest upvote for this from my side is compile-time safety which the type would guarantee vs only runtime safety which is currently present in this scenario.

But at the same time I cannot think of anything why would C# in general introduce something like Defaultable<T> so this will never be changed?

@roji
Copy link
Member

roji commented May 16, 2024

Well yes if you use DB-first approach with default settings then you have to look into the database or look into DbContext OnModelCreating (for every property for every model you have) which is inconvenient and prone to error.

If you use database-first, I'm not sure I see EF generating a Defaultable<T> in any case; the fact that some int column in the database has a default value configured doesn't mean the user wants the .NET property to be Defaultable<int> as opposed to int.

How long would it take to write code with inline intellisense vs looking into the C# model or database or OnModelCreating to verify yourself everything manually? And this is just one entity.

One simple option - which you may not like - is to simply always check bool? for null. For those cases where it's mapped to a non-nullable column in the database, null will never actually be there so the check is superfluous, but it also doesn't do any harm. If the developer really cares enough about that check, they can always look at the property to see if it has [Required], or similar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants