-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
AddColumn migrations created with bad default #6035
Comments
@roji How otherwise would you handle the already existing rows in the table? |
That's a good question, but it seems like it's a migration detail. In other words, at the end of the process I should get the table definition that corresponds to my model, i.e. the same table definition I would have gotten if I created the table with the column in the first place. One solution would be to add another migration operation to remove the default after the column is added. This way rows that existed before the addition have default values, but rows created afterwards don't. Another possibility which seems better to me, is to force the developer to take manual action here, i.e. to generate the column addition without a default, making it fail if there are any rows. After all, adding a non-nullable non-default column requires a real decision from the developer - putting a zero for ints may not what what they want. It may even make them think and make the new column nullable, depending on their scenario. |
@roji non nullable property obviously means NOT NULL column, so the developer made the decision already, I think there's no need to force any other manual action. I agree that leaving the default value as part of the scheme is a bad idea though. I would rather have something like
The auto generated migration is just a helper, you always should review the generated migrations and script and customize it if needed. What do you think? ps. Truncate Table is DDL 😄 |
The developer made the decision about the column being not null, but not regarding what should happen to existing rows... Basically it's a matter of whether 0 for existing rows should be a default, or something we force developers to choose. Both options make sense I guess; I definitely prefer the second because it seems like something the developer should confront - they may not have thought about the existing rows and just putting a zero there may cause trouble later. But I also understand the need for migrations to be seamless and not require too much manual intervention. It's a decision that has to be made. Regarding your specific proposal ( |
It's two operations. Update existing rows and adding the new column, no need to create a default value at all. The table needs to be locked though so there won't be a new row between the update and the altering of the table. @roji Now that I rethink about it, I prefer the migration to leave the value blank and not compile and force the developer to fill the value. |
Uh, how do you update the existing rows if you haven't yet added the column?? Or am I totally misunderstanding? If you add a column with a default, then remove the default everything seems nice and safe, no concurrency/locking issues... Anyway, I guess we can wait for Monday to see what the team thinks... |
OMG I'm an idiot... 😳 Yes you're right. You need to add default values and remove it later. |
We all get brain malfunctions every now and then :) |
Actually, I was off, but not that off. That is the reason I got confused. |
See also: #5995 |
Triage: We should not try and set a default value if the column is set as identity, since it is not valid (and SQL Server would fill in the missing values anyway). On a more general note, using the CLR default when you add a non-nullable column is just what we scaffold and we anticipate that folks would often change this to something that makes sense for their app. We don't think we need to change anything here. |
@rowanmiller, note the discrepancy between what happens if the non-nullable column is present when the table is created, and when it's added later (i.e. between the CreateTable migration and the AddColumn migration) - in the first case there's no default, in the second there is. This is the part that bothers me most; if you always set the CLR default as the column default it would at least be consistent (although still wrong IMHO). Again, I get the reasoning behind the default - when adding a column to an existing table there's the issue of existing rows, which doesn't exist when creating a new table. But that seems to be a detail; it seems that the database schema for a given model should be the same regardless of the history/migration path taken to reach it. |
@roji we discussed this at length and decided the complexity of removing the default after adding the column isn't worth the benefit. Since the column is non-nullable, EF will always send a value in INSERT statements, so the database default is never used. It would only be used if SQL was run against the database out side of EF. Another factor in our decision is that we've had this behavior in EF6.x since the 4.3 release and have not had any complaints about it. |
Thanks for looking at this, I didn't realize removing the default was such a complicated operation. I'll describe how this bit me, I'm interested in how you deal with it. In the current Npgsql provider, you can mark an int property as Now, as long as the column is present when the table is first created, everything works fine. But when the column is added later, the default is added by EFCore, and the migrations provider doesn't create a How do you guys handle this sort of thing? |
I think that is similar to the issue we are planning to fix here, where the default value should not be added for database generated columns. BTW there is also DefaultValueSQL, which is different from a static default value and is where something like "newid()" would be used for SQL Server generated GUID columns. |
@roji Also, look at SqlServerValueGenerationStrategy. This is how we define a high-level group of behaviors for SQL Server value generation across the stack. This is what allows Migrations and the update pipeline to know what to create/expect for different strategies. |
@rowanmiller I check for DefaultValueSQL in addition to DefaultValue, if any of them are set I bypass the implicit sequence creation. @ajcvickers, if I understand correctly you support two strategies:
In the PostgreSQL world, the standard for auto-increment columns (keys or otherwise) is the Requiring users to manually specify sequences for each value-generated would be awkward to say the least, as would be an additional annotation just to say "please create a sequence for this". This is why I made things simple: if your column is numeric, has ValueGeneratedOnAdd and doesn't have a default, I deduce you want a sequence. The only problem with this strategy is this issue - that some defaults aren't actually defaults... Do you have any suggestion for supporting this scenario without imposing explicit sequence creation in the model or an added annotation? I hope I haven't misunderstood how you do thing in SqlServer and missed something. |
@roji Is the problem that the PostgreSQL provider is using the "defaultValue" set in the Migrations model as the way to choose what strategy to use? I don't think this can work. I think you need to look in the actual EF model to determine which strategy is being used and then create the appropriate Migrations model from this. @bricelam might have more insight. |
@ajcvickers, ah, that seems like a good way forward. The model would retain the user's intent (no default) even if the database has a default because of the migrations... I'll look into that, thanks! |
Yep, that seems like it works. My NpgsqlMigrationsAnnotationProvider checks the property for Note that this overrides the default value 0 set in the migration operation. Thanks for the help! |
Reopening this one as I think we should still avoid scaffolding a default value for new columns when the column is marked as store generated. |
What does "store generated" mean? |
@gdoron it means that values for the column are generated by the database (e.g. SQL Server IDENTITY) so there is no need for a default value. All other non-nullable columns will still have a default value generated. |
@rowanmiller I admit I'm a bit confused as well by the terminology (have always been a little)... Columns with default are also generated by the database. In addition, the Npgsql/PostgreSQL model seems to complicate this somewhat - let's take autoincrement (serial) columns. On the CLR model, the user is simply supposed to specify To try to explain this in terms of the SqlServer provider, imagine a new value generation strategy in addition to identity and hilo. With this strategy, every I think it's worth disambiguating whether, when we say "default value", we mean that on the CLR model or in the database column definition, because the two are not the same. |
@ajcvickers, coming back to this after a long pause I have a small question with regards to your comment above. In that comment you suggest that I look at the model directly to find out whether a property in question has a default or not - looking at the migration operation doesn't work because EFCore adds a default there even if one isn't defined on the model. My question is whether this should happen directly in my MigrationsSqlGenerator, or whether an annotation should be generated in my MigrationsAnnotationProvider, and be examined in the MigrationsSqlGenerator. On the one hand, I can see that SqlServerMigrationsAnnotationProvider generates a |
Use Use annotations to expose store-specific features that the user may want to configure--imagine that they're creating a column without a backing property in the model. SQLite adds an annotation to set AUTOINCREMENT on the column because it is the best thing to do when working with EF, but the user free to change (remove) it if they know what they're doing. I guess as a principle, try not to make decisions for the user in the SQL generator unless there is a way for them to override it (e.g. configure the max length in the call to CreateColumn). |
@bricelam thanks for the guidance. |
Hope I'm not reporting a dup here.
Testing with SqlServer, I've added this property to my model:
Generating a migration creates the following code:
Note the
defaultValue: 0
, which I didn't specify in the model in any way. Note also that if the column is defined when creating the table, the correct CreateTable migration is generated:This causes an issue with Npgsql (npgsql/efcore.pg#68) since the default value is examined in the migration decision process.
The text was updated successfully, but these errors were encountered: