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

Re-consider how default values are reverse engineered #9627

Closed
ajcvickers opened this issue Aug 30, 2017 · 36 comments
Closed

Re-consider how default values are reverse engineered #9627

ajcvickers opened this issue Aug 30, 2017 · 36 comments
Assignees
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. providers-beware type-enhancement
Milestone

Comments

@ajcvickers
Copy link
Member

Feedback on 2.0 (e.g. #9507 #9617) has made me think about what the real intent is behind having columns with default values. Specifically, I think it is likely wrong to assume that the EF model should have a store-generated property just because there is some default on the column. It may well be the cause the the application will always supply a value and so, when using EF, the default is never used. The default may instead be there because:

  • It was needed when adding a new column (either by Migrations or manually)
  • It is there for legacy reasons--old/non-EF app made use of it in hand-crafted SQL, but EF never will
  • It's just part of the way all columns are created by the DBA or similar--the column gets a default regardless of whether it is needed or not

Going beyond the most general case, if the CLR default would generate the same value as the database default, then having EF do store generation is a no-op anyway. If an app was relying on this for bools in 1.1, and we now generate nullable bools, everything will still work, but the app needs to be updated to use nullable bools.

@ajcvickers
Copy link
Member Author

Triage: we will update reverse engineering to not make the property store-generated if the default value from the column matches the default value of the CLR property.

@ajcvickers ajcvickers added this to the 2.1.0 milestone Aug 30, 2017
@JuanIrigoyen
Copy link

JuanIrigoyen commented Aug 30, 2017

This change break with the model, if you need to use the default value in a entity, the logical is set these in the constructor of the class or in the property how default value. The problem is when the database modify the record after insert with a trigger, calculate fields, another procedure or function of transact-Sql that EF don´t know. For example if your default value is a function in sql server how default([dbo].[NEWDOC_Order]()) EF never to know this. In this case the default value will be lost and you always must to read the records inserted before return it to EF, but if EF know the function, numeric or string values, getdate() or other basic functions these can be set in the constructor or in the properties. If I have a bool field and in my model the field refer to bool?, the validations an others functions are different. In numeric fields If I make a sum, I would be forced to change the function to not read the null values. If I have a default value in a table with not null in my model must be the same because I must to change my validations and programs for this cause. I´m sure this behavior is not correct.

In my opinion the use of default values ​​should be something like this:

public partial class Customer
{
    public Customer()
    {
        Date = DateTime.Now;    // Default Value "Getdate()"
        Total = 100;	        // Default Value 100
    }

    public string CustomerId { get; set; }
    public string Name { get; set; }

    [DefaultValue("Getdate()")]
    public DateTime Date { get; set; }

    [DefaultValue(100)]
    public decimal Total { get; set; }
}

// Or using default values in properties

public partial class Customer
{
    public Customer()
    {

    }

    public int CustomerId { get; set; }

    public string Name { get; set; }

    public DateTime Date { get; set; } = System.DateTime.Now;  	    // Default Value "Getdate()"

    public decimal Total { get; set; } = 100;                       // Default Value 100
}

@ryanelian
Copy link

ryanelian commented Sep 1, 2017

Can you guys just hotfix this nullable bool scaffold behavior instead of shipping on 2.1.0? Maybe ship a 2.0.1? Just add a quick flag when invoking the command line scaffold tool or something perhaps, please?

Currently my workaround is to downgrade the separate project containing the DbContext class + entity classes + the EF Core + its dotnet ef CLI tool to 1.1.2, re-scaffold (using a handy PowerShell script) then re-upgrade to 2.0.0, but it's quite a stupid thing to do...

I mean, 2.1.0 / Q1 2018 is a pretty long wait...

EDIT: ErikEJ's Toolbox is very cool. Love it. But I still prefer my black and white console: .\scaffold-dbcontext.ps1 and be done without a single mouse click...

@ErikEJ
Copy link
Contributor

ErikEJ commented Sep 1, 2017

@ryanelian you can use my SQLite Toolbox to generate the v1 scaffolding, without installing anything in your solution 😀 - also works for SQL Server

@ajcvickers ajcvickers modified the milestones: 2.1.0, 2.0.1 Sep 1, 2017
@ajcvickers
Copy link
Member Author

Triage: we plan to make the following change in 2.0.1: for non-nullable bool columns in SQL Server that have a column default of false, we will scaffold a non-nullable bool property without any store generation flags.

Post 2.0.1 we will do what is described in the triage comment above.

We will not change the behavior where the default in the database is not the CLR default. Specifically, for bool columns that have a default of true, we will still scaffold a nullable bool. This is because non-nullable bool properties with a store-generated value of true are not useful--the value in the database will always be true--see #8400 and #7163 for more details.

@JuanIrigoyen
Copy link

JuanIrigoyen commented Sep 2, 2017

I can´t understand. 'non-nullable bool properties with a store-generated value of true are not useful'. If you want to insert same fields and you have only two values (only true or false) without null values. I have this configuration in more than hundred forms. For me it´s the same problem. In the database you have 'bool not null default value(1)' and in the model you have bool?.... Please at least set a parameter in the scaffolding for control this behavior.

In the photo you can see when I insert a record I have Monday, Tuesday, Wednesday, etc, Same fields have true and others with false how default values. In the database all fields are bool not nullable with 1 or 0 how default value. All use the same control based in a checkedit control that don´t let null values.
capture

@ajcvickers
Copy link
Member Author

@JuanIrigoyen This is the scenario I am talking about. Entity with a non-nullable bool:

public class Fooo
{
    public int Id { get; set; }
    public bool Flag { get; set; }
}

Configured in EF to have a store-generated default of true:

modelBuilder
     .Entity<Fooo>()
     .Property(e => e.Flag)
     .HasDefaultValueSql("(1)");

With this configuration, Flag will always be saved as true; never false, and hence is not useful. For example:

using (var context = new TestContext())
{
    context.Database.EnsureDeleted();
    context.Database.EnsureCreated();

    context.Add(new Fooo { Flag = false });
    context.SaveChanges();
}

using (var context = new TestContext())
{
    Debug.Assert(context.Fooos.First().Flag);
}

This behavior hasn't changed from EF Core 1.0--there's lots of discussion in the linked issues that talk about this. The most complete is probably in #7089. Essentially, the issue is that the sentinel to tell EF to use use a store-generated value is the same as the value 'false'. The ways to make this work are:

  • Stop having EF make use of the store generated value--e.g. by removing the HasDefaultValue call.
  • Make the property nullable so that the sentinel ('null') is different from the valid value 'false'.

@JuanIrigoyen
Copy link

JuanIrigoyen commented Sep 2, 2017

ok, I understand, thank you for the explanation, In this case I prefer the first option, because a bool? value is different from bool and it is important that the model is similar to the table in the database. In this case the default value is deleted for the value set and it´s not necessary to call HasDefaultValue method.

If you set the property nullable we need to change all controls because even the bool nullable in a forms is represented differently. bool? can show three states.
capture2

@ryanelian
Copy link

ryanelian commented Sep 2, 2017

This is because non-nullable bool properties with a store-generated value of true are not useful--the value in the database will always be true--

I can't understand.

This is my table:

CREATE TABLE Employee
(
	EmployeeId VARCHAR(64) CONSTRAINT PK_Employee PRIMARY KEY,
	Username VARCHAR(256) CONSTRAINT UQ_Employee_Username UNIQUE NOT NULL,
	Password VARCHAR(256),
	PasswordUpdatedAt DATETIME2,
	Name VARCHAR(256) NOT NULL,
	Email VARCHAR(256),
	IsEnabled BIT NOT NULL DEFAULT 1,
)

This is from EF Core 1.1.2 scaffold (navigation properties and some stuffs are omitted for brevity):

public partial class Employee
{
    [Column(TypeName = "varchar(64)")]
    public string EmployeeId { get; set; }
    [Required]
    [Column(TypeName = "varchar(256)")]
    public string Username { get; set; }
    [Column(TypeName = "varchar(256)")]
    public string Password { get; set; }
    public DateTime? PasswordUpdatedAt { get; set; }
    [Required]
    [Column(TypeName = "varchar(256)")]
    public string Name { get; set; }
    [Column(TypeName = "varchar(256)")]
    public string Email { get; set; }
    public bool IsEnabled { get; set; }
}
public partial class MyDbContext : DbContext
{
    public virtual DbSet<Employee> Employee { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Employee>(entity =>
        {
            entity.HasIndex(e => e.Username)
                .HasName("UQ_Employee_Username")
                .IsUnique();

            entity.Property(e => e.IsEnabled).HasDefaultValueSql("1");
        });
    }
}

This is the C# code for setting IsEnabled to false on rows already created (update operation). Nothing fancy:

e.IsEnabled = form.IsEnabled;
await DB.SaveChangesAsync();

The field is very clearly set to false when I view the data directly using SQL Server Management Studio. (Forgive me for not showing the other data, they're abit confidential):

false

Flag will always be saved as true; never false, and hence is not useful.

Please help me understand this phenomenon.
(Sorry if I misunderstood the previously shown scenario / your example above.)

@ajcvickers
Copy link
Member Author

@ryanelian It can be updated to false, but never inserted as false.

@ryanelian
Copy link

ryanelian commented Sep 3, 2017

Ahh. That explains it.

Perhaps to maintain compatibility with database context created with version 1 scaffold, BIT NOT NULL DEFAULT X properties should be scaffolded with Auto-Property Initializers as an interim / temporary / emergency solution?

public partial class Employee
{
    public bool IsEnabled { get; set; } = true; // or false
}

i.e. For now, no need to implement that for each and every data type. Just make an compatibility flag to scaffold boolean property like that, please?

That way you won't break compat for 2.0.0 and 1.X.X users...

@bricelam bricelam assigned smitpatel and unassigned bricelam Sep 8, 2017
@smitpatel smitpatel added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Sep 12, 2017
@ajcvickers
Copy link
Member Author

Hi @ryanelian. We are gathering information on the use of EF Core pre-release builds. You reported this issue shortly after the release of 2.0.0 RTM. It would be really helpful if you could let us know:

  • Did you consider testing your code against the pre-release builds?
  • Was there anything blocking you from using pre-release builds?
  • What do you think could make it easier for you to use pre-release builds in the future?

Thanks in advance for any feedback. Hopefully this will help us to increase the value of pre-release builds going forward.

@ryanelian
Copy link

ryanelian commented Sep 13, 2017

Did you consider testing your code against the pre-release builds?

Yes

Was there anything blocking you from using pre-release builds?

When I installed 2.0.0 preview1 SDK on my machine, I was not able to compile my 1.1.X project from Visual Studio 2017 Update 2 even when global.json exists for unknown reason. (AKA I cannot work.)

So I stopped playing with pre-release builds...

What do you think could make it easier for you to use pre-release builds in the future?

Don't break Visual Studio 😕

@smitpatel
Copy link
Member

Impact: Refer to first post.
Risk: Low. Only in SqlServer, Columns with type bit and default value of (0) will not get default value constraint and will scaffold non-nullable bool which would match with 1.1 behavior.

@ajcvickers
Copy link
Member Author

Note from triage: post 2.1 we should consider adding a flag that will cause reverse engineering to ignore column defaults when defining the EF model. However, some thought needs to go into how this would interact with update-from-database. Leaving it up to @bricelam to decide how to track this--using an existing issue or creating a new one.

@bricelam bricelam added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Feb 5, 2018
@bricelam
Copy link
Contributor

bricelam commented Feb 5, 2018

Filed #10881 (Option to ignore DEFAULT constraints)

@bricelam
Copy link
Contributor

bricelam commented Feb 5, 2018

Notes from implementing this:

  1. Some tools remove DEFAULT constraints by setting them to NULL. This doesn't remove the constraint, but effectively disables it.
  2. Adding NOT NULL columns requires specifying a DEFAULT (typically 0). We do this in Migrations.

Both these classes will now be ignored by Reverse Engineering. 🎉

@ErikEJ
Copy link
Contributor

ErikEJ commented Feb 6, 2018

@bricelam Are the default values generated by scaffolding used by EF anywhere?
Or are they there for roundtrippabilty

@bricelam
Copy link
Contributor

bricelam commented Feb 6, 2018

By reverse engineering .HasDefaultValueSql("something") EF will not specify a value in the INSERT statement when sentinel value (CLR default by convention) is present (causing the default value to be generated), and after inserting it will propagate the generated value back into the entity.

@bricelam
Copy link
Contributor

bricelam commented Feb 6, 2018

So the actual value only really matters for round-tripping, but since we have to put something...

@ErikEJ
Copy link
Contributor

ErikEJ commented Feb 6, 2018

OK, so it is used by the Update pipeline - thanks! I will add them, then:
https://github.com/ErikEJ/SqlCeToolbox/blob/master/src/GUI/ReverseEngineer20/ReverseEngineer/SqlServerDacpacDatabaseModelFactory.cs#L263

And what about sequences in the Model?

@bricelam
Copy link
Contributor

bricelam commented Feb 6, 2018

Sequences, constraint names, non-unique indexes, and index filters aren't currently used by the runtime and are mostly there for round-tripping.

@johnkwaters
Copy link

I ran in to this today too. I was wanting to set a default value with my code first DB generation, so that the column would end up with XXX bit NOT NULL DEFAULT 0
The reason I do this is if you are inserting manually (not through EF), you don't want to have to specify the default values. But if there is no default, you cannot omit them in your insert. Hence, a useful default.
Your line of reasoning seems flawed in that it centers on EF being the ONLY access path to the DB to consider. But what about my case where you want to generate a schema that is friendly to other access patterns, without a bunch of warnings?

@cgountanis
Copy link

You can either wait for the version that it is fixed or use the ignore when creating the connection in startup. Pretty sure there was an example above.

@cgountanis
Copy link

I thought the newer versions were going to fix the need for BoolWithDefaultWarning on bool properties within a model.

@ajcvickers
Copy link
Member Author

@cgountanis That warning should not be generated when reverse engineering with the 2.1 tools. Can you please create a new issue describing specifically when you are seeing this? Please include the column definition and the generated code.

@cgountanis
Copy link

@ajcvickers #12198

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. providers-beware type-enhancement
Projects
None yet
Development

No branches or pull requests

10 participants