Entity Framework: Add column requires altering of Seed data? - c#

first off, I'm pretty certain that this must have been asked before but have been unable to find an exact answer via googling, so please bear with me.
I have inherited a code first entity framework project which uses migrations. I've added a (non-nullable) column to a table and I need to insert values into this column for all existing entries - which are not the default value:
public override void Up()
{
AddColumn("dbo.QuestionType", "Duplicated", c => c.Boolean(nullable: false, defaultValue: false));
Sql("UPDATE dbo.QuestionType SET Duplicated = 1");
}
However there was originally some seed data added to this table:
context.QuestionTypes.AddOrUpdate(
e => e.Name,
new QuestionType() { Name = QuestionTypeNames.INTERVIEWER});
which means that the update statement is immediately overwritten by the data in the call to the Seed method (called after each migration).
My questions are:
Is it 'safe' to simply add the extra value into the Seed data (or will this cause everything to break for earlier migrations where the new column doesn't exist on the database).
Alternatively is there any way to prevent the Seed method from running after this migration (and all subsequent migrations).
Thanks

Entity Framework is great, but to be able to use it effectively, I'm afraid we really have no choice but to be diligent in making sure that the migrations and the seed method are in sync and work well with each other.
Yes, if you put the new value in the seed (ie. new QuestionType() { Name = QuestionTypeNames.INTERVIEWER, Duplicated = true}), this will cause systems which do not have the latest migration applied to break. This is because the seed method will be looking for the Duplicated column but will not find it.
No, I don't think there is a way to do this, at least not automatically. One solution is this: since you have access to the context object in the seed method, you can just query the database to check if the particular migration already exists. You can then wrap the specific seeder for QuestionTypes in an if statement, utilizing the result of the earlier query.

Related

Entity Framework Data Migration

We´ve changed some logic in our application and now we add some fields within a migration but the existing entries should get some values also. Therefor we need to load the entry, calculate some values and need to add this value to the new field. So how to access the data which is requested with the Sql-Method? The defaultValue-Flag of the AddColumn-Method will not work because every entry has a value calculated different.
Here somekind of workflow:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<uint>(
name: "Field",
table: "Table",
type: "INTEGER",
nullable: false,
defaultValue: 0u);
var builder = migrationBuilder.Sql("SELECT * FROM Table");
var data = builder.AccessData; // here I´m unsure how to access the data
data.Field = Calculator(data);
migrationBuilder.UpdateData(data); // this one is also not cleare
}
A MigrationBuilder is a way to express in a fluent, C#-friendly fashion the SQL code that will be executed during the migration itself. Even if the code you write and the "intermediate files" generated by the migration tool look as C#, at the end the migration becomes pure SQL that neither knows nor can communicate with your application.
Therefore, if you need to process existing data, all your logic must happen DB-side and must be expressed as operations on the MigrationBuilder. Of course in the Up and Down methods of the migration you can leverage all the power of C# to build the raw SQL statements to be provided to the migrationBuilder.Sql(...). Also, consider that the whole migration is executed in a transaction and that a "volatile" stored procedure can turn out to be useful to extract a complex migration logic.

How to transition partial update operation from ObjectContext to DbContext

I am using entity framework 5.0. I am in a rocess od changing my app from ObjectContext to DbContext model. DbContext should be according to microsoft the recommended approach. I use database forst approach and I have generated model form database.
But, at a very first simple task there is a problem. Namely simple update of a record is broken.
Let's have a simple table Item, for ilustration only:
Item
(
ItemId int NOT NULL, -- Primary key
Name nvarchar(50) NOT NULL,
Description NVARCHAR(50)
)
I have noticed that using DbContext does not support updating a record not as ObjectContext does.
In my application I have a simple update method.
public void UpdateItem()
{
MyContext context = new MyContext();
Item item = new Item();
item.ItemId = 666;
context.Items.Attach(item);
// From this point onward EF tracks the changes I make to Item
Item.Description = "Some description";
context.SaveChanges();
}
Using ObjectContext this method correctly updates a record. Using SQL profiler I can see that it generates something like this (greatly simplified!!!)
UPDATE Item
SET Description = 'Some description'
WHERE ItemId = 666
If, however I try to do the same thing in DbContext I get the exception:
System.Exception: Items.aspx.cs - logged from CustomError() ---> System.Data.Entity.Validation.DbEntityValidationException: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
at System.Data.Entity.Internal.InternalContext.SaveChanges()
at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
at System.Data.Entity.DbContext.SaveChanges()
And no database UPDATE is issued to Sql server.
I guess that DbContext validates all the properties and the property Name is null. This by design. I do not intend to modify it, I do not even know what is it and I do not need to know what is it.
Only the property Description was changed. Clearly ObjectContext does not track changes correctly.
How can this problem be resolved?
I have researched the issue and found the something on updating records.
For example this link: https://stackoverflow.com/a/15339512/4601078
db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();
But this is horrible code. For every property on should add a line like:
entry.Property(e => e.Email).IsModified = true;
This produces ugly unreadable code, an I suspect lamda expression are not stelar in performance.
Even worse are those who propose to make a roundtrip to DB to fetch existing records with all properties populated, update it and the save changes. This is a no go with regard to performance.
So, how to tackle with simple entity updates or is this DbContext just another item in microsofts collection of dead ends which serve no real purpose?
DbContext doesn't really track changes by watching properties, it compares the values to previously known values. And validation always works on the entire entity so the only real choice if you want to do things this way is to disable validation during this operation. See Entity Framework validation with partial updates
If you know for sure that the changes you apply are valid, or you have custom code to validate them, you can turn off validation by EF altogether:
db.Configuration.ValidateOnSaveEnabled = false;
This works OK as long as you do it your way: attach a new entity with a known Id (aka a stub entity) and then modify its properties. EF will only update the properties it detects as having been modified (indeed by comparing original and current values, not, as ObjectContext did, by change notifications). You shouldn't mark the entity itself as modified.
If you don't want to turn off EF's validation, but neither want to mark individual properties as modified, I think this could be a useful alternative (brought to my attention by Alex's answer).

Change column in Entity Framework 7?

I have an nvarchar in a table with the length of 6. I created it with code first and the data notation [StringLength(6)].
I want to change the length to 20. I tried to change it in the code and make a new migration but that did nothing.
I did not find a way to do it with code first. The only way that worked for me was to make a query on the Database direcly.
ALTER TABLE [Tablename] ALTER COLUMN [Columname] nvarchar(20).
I had the concern that this would throw an exception because of the DatabaseContextModelSnapshot file but it worked fine. If you do this approach I would recommend to also make the changes in code and to make a new Migration for it. It is not necessary but that way your migrations & code are consistent with the Database.
One thing you can do,Use Fluent Api to change your column name in onModelCreating() method.
this method can be override in your class where your are inheriting from DbContext class.

How should custom code be run during an Entity Framework database migration?

I have a very simple Entity Framework 5 DbMigration that is adding a new boolean column to an existing table with thousands of records in it. I want the initial value of that column for each existing row to be set based on the value in two other columns in the same table. This should only affect existing records, therefore should only be run when the migration is performed and never run again.
This is basically the logic that needs to be executed:
var users = (from u in context.Users select u).ToList();
users.ForEach(u =>
{
u.MyNewBoolColumn = (u.Column1 != null && u.Column2 == "some-value");
context.Users.AddOrUpdate(u);
});
There are two options I can think of, but I don't like either of them:
Create two separate migrations, since the column won't exist until after the first one is completed. This seems sloppy and I'm also not sure how to get the database context in the second migration to actually perform the update.
Run code in a DbMigrationsConfiguration implementation. However, this code would run every single time and I won't be able to tell if it has already run and shouldn't update records.
Is there another, better option?
A migration represents a change in the schema, therefor you can't use AddOrUpdate() stuff inside a migration. But you can, and this is what I would suggest you, run plain SQL code using the Sql() method.

How can I set the value of a Navigation Property in the code behind?

I have an Entity called Cost, which has a required property of CostType
The Cost class has a GetNew() method which sets all the Cost's defaults:
public static GetNew()
{
Cost cost = new Cost ();
foo.CostType = Lists.CostTypes.FirstOrDefault();
// Other Default Values
return foo;
}
The Lists.CostTypes is a static list which is pulled from EF at startup and used in ComboBoxes
I am having problems setting the CostType within my code, after first setting it in the GetNew() method.
For example, the following code reads an excel file, and sets the default type based on a column in the Excel file, or null if it can't find a match
Cost cost = Cost.GetNew();
cost.CostType = Lists.CostTypes.FirstOrDefault(t => t.Name == row[0].ToString());
My problem is, during the Save operation I get the following error:
The operation failed: The relationship could not be changed because
one or more of the foreign-key properties is non-nullable. When a
change is made to a relationship, the related foreign-key property is
set to a null value. If the foreign-key does not support null values,
a new relationship must be defined, the foreign-key property must be
assigned another non-null value, or the unrelated object must be
deleted.
My Add Operation looks like this:
public static void AddObject(EntityObject obj, string entitySetName)
{
context.AddObject(entitySetName, obj);
context.SaveChanges();
}
If I remove the line of code that manually sets the Cost when it reads the excel file, the save works fine.
If I change the line of code to read Lists.Costs[2], it saves fine.
If I remove the line of code in GetNew() which sets the default, I get an error telling me that I violated the PK rule of CostTypes, meaning it's trying to insert the Cost Type.
Changing the ComboBox showing Type to something else still gives the same error.
After loading costs from the excel file, my regular Add/Edit forms throw the same error when I change the Type and try and save. If I don't load an excel file, they work fine.
I'm still learning Entity Framework, but so far it has been nothing but a frustration and a headache to use. Does someone know what my problem is and how I can fix it?
EDIT
Here's the info requested by Slauma. I am keeping it simple and excluding unrelated objects
Costs are in one table and CostTypes are in another table. In the database, the Costs.TypeId column is not allowed to be null, and is a Foreign Key to CostTypes. The Id field for both tables is auto-generated.
My EF model is just a generic one with the two database tables added. The only change I made to it was to rename some fields and remove the CostTypes.Costs Navigation Property.
The Excel file that gets imported maps most costs to their matching CostType.Name, however it IS possible that the string in the excel file doesn't match a CostType, so Lists.CostTypes.FirstOrDefault(t => t.Name == row[0].ToString()) can assign aNULLvalue to theCost.Typeproperty. That doesn't seem to be a problem though, because the form still comes up with the list of costs and their default selected items. Item's with aNULLCostType do not have an item selected in the CostTypeComboBox` and trigger a validation error that must be corrected before saving.
The code to load the CostType list is
public static List<T> GetList<T>(string sortProperty)
where T : EntityObject
{
using (var context = new TContext())
{
return ApplyOrder<T>(context.CreateObjectSet<T>(), sortProperty, "OrderBy").ToList();
}
}
The ApplyOrder code can be found here.
The GetList method is called from
public static class Lists
{
public static List<CostType> CostTypes { get; private set; }
static Lists()
{
CostTypes = DAL<CostEntities>.GetList<CostType>("Name");
}
}
I figured it out.... it was a mix of a few different things
Creating a new Cost and setting the Type was adding the cost to the shared data context. If that Cost wasn't included in the list of costs to save, or it failed it's validation error, or the user cancelled out of the Import dialog, the cost still existed in context.ObjectStateManager._addedObjects, even though I never called AddObject or AttachObject. Once I realized that I started callling DeleteObject on costs that were not going to be saved and it cleared up the 1st error I was getting.
The 2nd error I was getting (duplicate PK) was because I was looping through my new Costs and calling AddObject and SaveChanges on each one. Since setting Cost.Type to an attached CostType was automatically adding my Cost to the context, the first cost to get saved was actually adding all the new Costs to the database while the 2nd cost was trying to call AddObject/SaveChanges on what EF saw as an object that already existed
Here is not really a satisfying answer but a mix of guesses and open questions based on your infos in the question and in the comments to your question:
First of all: Your list Lists.CostTypes contains obviously entities which are detached from the context where you are later adding and saving new objects in. Because you have a using block: using (var context = new TContext()) you are retrieving your CostType entities in another context.
To tell EF that these CostType entities already exist in the database you must attach the entities to your second context (context.CostTypes.Attach(costType)) where you save your changes in (or use the same context in your method where you retrieve the list). I don't see in your code that you do this. (CostType is a navigation reference property, not a foreign key property, right?)
On the other hand when the CostType entities are not attached you should get duplicated CostTypes in your database because EF will consider them as new objects (to insert in the DB) when you call AddObject for your Cost entity since EF will always put the whole object graph of detached entities into Added state. Do you get duplicated CostTypes in the DB in your working examples? If not, something important is missing in your code snippets.
The last paragraph assumes that the key for CostType is autogenerated in the DB, as you said. If not, you would get a PK constraint violation instead of duplicated entities.
If the keys for CostType and Cost are really autogenerated identities, I am wondering where the PK violation you mentioned can come from. Every insertion would create a new unique primary key. There could never a PK violaton occur. Can you show the exception message in detail?
Did you check that all Cost entities you want to save really have a non-null CostType property (after the user has fixed all validation errors)? I cannot see any other possible reason in your code why you would get your "Relationship-could-not-be-changed-exception", except that at least for one of the Cost objects CostType is null.

Categories

Resources