Stop empty strings at the database level with EF code first - entity-framework

Consider the following POCO entity for Entity Framework Code First:
public class Foo
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; }
}
Which will generate the following table:
CREATE TABLE [dbo].[Foo] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (100) NOT NULL,
CONSTRAINT [PK_dbo.Foo] PRIMARY KEY CLUSTERED ([Id] ASC)
);
Now, I understand that the default behavior of EF is to convert empty strings to null. So even if I explicitly feed it an empty string I will get a validation exception, which is perfect. The following code will throw a DbEntityValidationException:
var f = new Foo { Name = "" };
context.Foos.Add(f);
context.SaveChanges();
But, the problem is if I have an external application which accesses the database directly, I can perform the following query and it succeeds:
insert into dbo.Foo(Name)
values ('')
The best solution is arguably to not allow anyone to connect directly to the database and force them through a business layer. In reality however this may not always be possible. Especially if, say, I myself am importing external data via an SSIS package.
My best understanding says that applications should be set up to reject as much bad data at the lowest level possible. In this case this would mean the at database level. So if were creating the database the old fashioned way, I would add a constraint to check (Name <> '') and stop dirty data from ever being inserted in the first place.
Is there a way to get EF Code First to generate this constraint for me, or some other way to get it to enforce a non-empty-string (minimum length 1) at the database level - preferably using an attribute? Or is my only recourse to add the constraint manually in a migration?

There is MinLength attribute but it does not enforce the constraint on database level, you should add this constraint using migration I think.
public partial class test : DbMigration
{
public override void Up()
{
Sql("ALTER TABLE [dbo].[YOUR_TABLE] ADD CONSTRAINT " +
"[MinLengthConstraint] CHECK (DATALENGTH([your_column]) > 0)");
}
public override void Down()
{
Sql("ALTER TABLE [dbo].[YOUR_TABLE] DROP CONSTRAINT [MinLengthConstraint]");
}
}
You can add sql code generators for EF to generate these codes for MinLength attribute, I'll give you a simplified hint here:
First mark properties with MinLength
public class Test
{
public int Id { get; set; }
[MinLength(1)]
public string Name { get; set; }
}
Add MinLenghtAttribute to conventions and provide the value, which is the Length :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Add(
new AttributeToColumnAnnotationConvention<MinLengthAttribute, int>(
"MinLength",
(property, attributes) => attributes.Single().Length));
}
the generated code for migration will be:
CreateTable(
"dbo.Tests",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(
annotations: new Dictionary<string, AnnotationValues>
{
{
"MinLength",
new AnnotationValues(oldValue: null, newValue: "1")
},
}),
})
.PrimaryKey(t => t.Id);
Override the SqlServerMigrationSqlGenerator to use this convention in order to generate the constraint sql code:
public class ExtendedSqlGenerator : SqlServerMigrationSqlGenerator
{
protected override void Generate(AddColumnOperation addColumnOperation)
{
base.Generate(addColumnOperation);
AddConstraint(addColumnOperation.Column, addColumnOperation.Table);
}
protected override void Generate(CreateTableOperation createTableOperation)
{
base.Generate(createTableOperation);
foreach (var col in createTableOperation.Columns)
AddConstraint(col, createTableOperation.Name);
}
private void AddConstraint(ColumnModel column, string tableName)
{
AnnotationValues values;
if (column.Annotations.TryGetValue("MinLength", out values))
{
var sql = string.Format("ALTER TABLE {0} ADD CONSTRAINT " +
"[MinLengthConstraint] CHECK (DATALENGTH([{1}]) >= {2})"
,tableName, column.Name, values.NewValue);
Generate(new SqlOperation(sql));
}
}
}
the code above contains generation for AddColumn and CreateTable operations you must add codes for AlterColumn, DropTable and DropColumns as well.
Register the new code generator:
internal sealed class Configuration : DbMigrationsConfiguration<TestContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
SetSqlGenerator("System.Data.SqlClient", new ExtendedSqlGenerator());
}
}

Related

How to update the datatype of alternative key once its created?

I want to changed the datatype of a column marked as alternate key.
I changed the datatype in the code and created new migration script but the script throws error.
public class Person
{
[Key]
public int PersonId { get; set; }
public int Email { get; set; }
}
public class TestEntities:DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=(LocalDb)\\MSSQLLocalDB;Initial Catalog=ff;Integrated Security=SSPI;");
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().HasAlternateKey(a => a.Email);
base.OnModelCreating(modelBuilder);
}
public DbSet<Person> Person { get; set; }
}
Here Email(int datatype in code which is coded mistakenly during development) needs to be unique. So I used fluent api to make in unique.
Then I created initial migration script and ran the script
The table is created with email as unique key constraint
The Table as this point doesnot have any rows.
Now I corrected the datatype of email to string.
Then I created second migration script and when i try to run I get the following error
The object 'AK_Person_Email' is dependent on column 'Email'.
ALTER TABLE ALTER COLUMN Email failed because one or more objects access this column.
How do you update the datatype.
You cannot change datatype of a column that is included in an alternate key (or primary key, foreign key or index). To achieve your goal, drop the alternate key, change datatype of the column and create the alternate key again.

EntityFramework: Model 1:0..1 relationship with fluent api + conventions

I have the following classes generated from an edmx model:
public partial class A
{
public int Id { get; set; }
public virtual B B { get; set; }
}
public partial class B
{
public int Id { get; set; }
public virtual A A { get; set; }
}
The existing db doesn't use the EF default which expects A.Id to be the primary key of table B:
CREATE TABLE [dbo].[B] (
[Id] INT IDENTITY (1, 1) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
CREATE TABLE [dbo].[A] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[BId] INT NULL,
CONSTRAINT [fk] FOREIGN KEY ([BId]) REFERENCES [dbo].[B] ([Id])
);
With an edmx model, I can explicitly configure the multiplicity of each end, but I haven't found how to get the equivalent model using the fluent-api. When I do something like the following and generate a new db, the foreign key gets placed in table A instead of table B.
modelBuilder.Entity<A>().HasOptional(a => a.B).WithRequired(b => b.A);
I'm guessing I need to use a convention, but so far I've been unable to get the desired output.
UPDATE:
The closest solution I've found so far is to use the following which generates the correct SQL in the db:
modelBuilder.Entity<A>()
.HasOptional(a => a.B)
.WithOptionalDependent(b => b.A)
.Map(c => c.MapKey("BId"));
However, it's conceptually modeled as a 0..1:0..1 relationship and I haven't found how to set a CASCADE delete rule that deletes B when A is deleted.
I wasn't able to find a direct solution, but using the following code seems to meet my requirements of preserving the existing schema and creating a conceptual model that has the same multiplicities & delete behaviors as my original edmx model.
I'd still be interested in any solutions that don't require updating the conceptual model during the post-processing IStoreModelConvention.
{
var overridesConvention = new OverrideAssociationsConvention();
modelBuilder.Conventions.Add(overridesConvention);
modelBuilder.Conventions.Add(new OverrideMultiplictyConvention(overridesConvention));
}
private class OverrideAssociationsConvention : IConceptualModelConvention<AssociationType>
{
...
public List<AssociationEndMember> MultiplicityOverrides { get; } = new List<AssociationEndMember>();
public void Apply(AssociationType item, DbModel model)
{
if (multiplicityOverrides.Contains(item.Name))
{
// Defer actually updating the multiplicity until the store model is generated
// so that foreign keys are placed in the desired tables.
MultiplicityOverrides.Add(item.AssociationEndMembers.Last());
}
if (cascadeOverrides.Contains(item.Name))
{
item.AssociationEndMembers.Last().DeleteBehavior = OperationAction.Cascade;
}
}
}
private class OverrideMultiplictyConvention : IStoreModelConvention<EdmModel>
{
private readonly OverrideAssociationsConvention overrides;
public OverrideMultiplictyConvention(OverrideAssociationsConvention overrides)
{
this.overrides = overrides;
}
public void Apply(EdmModel item, DbModel model)
{
overrides.MultiplicityOverrides.ForEach(o => o.RelationshipMultiplicity = RelationshipMultiplicity.One);
}
}

How should I update a newly added column in Entity Framework Code First Migration

I am learning EF Code First Migrations. I have added a new column to an exiting table
public partial class Test2 : DbMigration
{
public override void Up()
{
AddColumn("dbo.Recipes", "DeafultNumberOfServes", c => c.Int(nullable: false));
}
public override void Down()
{
DropColumn("dbo.Recipes", "DeafultNumberOfServes");
}
}
I want to now update the existing rows in that table and set the DeafultNumberOfServes to 4
It works in the Seed method of my Configuration class but it feels dodgy
protected override void Seed(MenuPlannerDBContext context)
{
context.Recipes.AddOrUpdate(
new Recipe { RecipeID = 1, Description = "Recipe 1" },
new Recipe { RecipeID = 2, Description = "Recipe 2" },
new Recipe { RecipeID = 3, Description = "Recipe 3" }
);
var result = context.Recipes.Where(x => x.DeafultNumberOfServes == 0).ToList();
result.ForEach(x => x.DeafultNumberOfServes = 4);
context.SaveChanges();
}
I have also tried to put it in the Migration class itself but it is as if the sql never get run and all the rows contain the default 0 in that column.
public partial class Test2 : DbMigration
{
public override void Up()
{
AddColumn("dbo.Recipes", "DeafultNumberOfServes", c => c.Int(nullable: false));
Sql("UPDATE dbo.Recipes SET DeafultNumberOfServes = 4");
}
public override void Down()
{
DropColumn("dbo.Recipes", "DeafultNumberOfServes");
}
}
So what would be the best way to handle adding a column and then updating all existing rows to a value?
Reading between the lines, it looks like you want the DeafultNumberOfServes property to default to 4 rather than 0 if the user hasn't specified it. You can specify the database default by changing the code in the Up migration to this:
public override void Up()
{
AddColumn("dbo.Recipes",
"DeafultNumberOfServes",
c => c.Int(nullable: false, defaultValue: 4));
}
This is mainly useful when you have existing data and want to add a non-nullable column without hitting an error. It doesn't mean that the value will default to 4 if the user doesn't specify it. That is because EF will send the default value for an int (i.e. zero) unless you set it in code, and that will overwrite the database value.
So you need to set the default in code. You do that by specifying it in the constructor or in a backing field (which is my preference):
public class Recipe
{
private _DefaultNumberOfServes = 4;
public int DeafultNumberOfServes
{
get{ return _DefaultNumberOfServes;}
set{ _DefaultNumberOfServes = value}
}
}
BTW, the reason your migration looks like it isn't working is because the migration runs on an empty table before your seed - so there is nothing to update - the table is empty because you are using an initialiizer that is "dropping and recreating the database always". I would advise changing to a "migrate to latest version" initializer instead so that you get into the pitfalls of migrating to a database with existing data sooner rather than later.

Nullable "scalar navigation properties" in EF 4.0: Mapping a non-nullable column from a separate database table to a nullable scalar entity property?

Using Entity Framework version 4.0 (or any other version that is compatible with .NET 4.0), I want to map this existing relational database schema:
to this logical object model:
which I have tried setting up as follows: (I hope the German captions won't be too disorienting.)
Entity Framework gives me this error:
Error 3031: Problem in mapping fragments …: Non-nullable column FooBs.B in table FooBs is mapped to a nullable entity property.
In the logical model, B ought to be nullable. However, in the database, it isn't, because it resides in a separate table. (I like to avoid nullable database columns.) It only becomes nullable when Foos and FooBs are joined (due to the 1:0..1 cardinality).
How can I fix my mapping, without altering either the database schema or the object model?
P.S.: I also tried this EF 6.0 code-first mapping:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Foo>()
.HasKey(f => f.Id)
.Property(f => f.Id).HasColumnName("FooId").HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<Foo>().Map(f => {
f.Property(_ => _.A);
f.ToTable("Foos");
}).Map(f => {
f.Property(_ => _.B);
f.ToTable("FooBs");
});
}
But this doesn't work either: When reading from the database, EF ignores all records for which there is no sub-record in FooBs; when writing to the database, it attempts to insert NULL into FooBs.B for all Foo that have their B property set to null.
There is a rather "dirty" solution that should work. It would require some code changing but would leave your Foo entity with field A and B.
Foo class:
class Foo {
[Key]
public int FooId { get; set; }
public int A { get; set; }
[NotMapped]
public int? B {
get {
return FooB == null ? null : FooB.B;
}
set {
if(value == null) {
FooB = null;
} else {
if(FooB == null)
FooB = new FooB();
FooB.B = (int)value;
}
public virtual FooB FooB{ get; set; }
}
And mapped to database class FooB:
class FooB {
[Key, ForeignKey("FooId")]
public int FooId { get; set; }
public int B { get; set; }
}
On side note - it seems like very strange way to add essentially single nullable column to a table, as there is no logical way where FooB could have more than one non-nullable column, that wouldn't result in deleting whole entity on setting columns value to null.
Another option is creating a database view that would behave like you want and map that to entity.

Update optional FK to Required with Automatic Migrations

I have a table which has an optional FK to another table and want to change that FK to a required relationship.
I have Automatic Migrations enabled and enabled destructive changes for this update. All entities in the database also have this key populated.
I changed this:
modelBuilder.Entity<Blog>().HasOptional(b => b.AuthorSecurable).WithMany().Map(b => b.MapKey("AuthorSecurableId"));
to:
modelBuilder.Entity<Blog>().HasRequired(b => b.AuthorSecurable).WithMany().Map(b => b.MapKey("AuthorSecurableId"));
and got the following error:
'FK_dbo.Blogs_dbo.Securables_AuthorSecurableId' is not a constraint.
Could not drop constraint. See previous errors.
There are no previous errors I could see (no inner exception ect.)
This post says you can get around this error with the following:
ALTER TABLE [dbo].[Blogs] NOCHECK CONSTRAINT [FK_dbo.Blogs_dbo.Securables_AuthorSecurable_Id]
so i did:
public override void Up()
{
Sql("ALTER TABLE [dbo].[Blogs] NOCHECK CONSTRAINT [FK_dbo.Blogs_dbo.Securables_AuthorSecurable_Id]");
DropForeignKey("dbo.Blogs", "AuthorSecurableId", "dbo.Securables");
DropIndex("dbo.Blogs", new[] { "AuthorSecurableId" });
AlterColumn("dbo.Blogs", "AuthorSecurableId", c => c.Int(nullable: false));
AddForeignKey("dbo.Blogs", "AuthorSecurableId", "dbo.Securables", "Id", cascadeDelete: true);
CreateIndex("dbo.Blogs", "AuthorSecurableId");
}
But still got the same error
EDIT:
the full code is avaliable here and a minimal models are below:
public class Blog
{
public int Id { get; set; }
public Securable AuthorSecurable { get; set; }
}
public class Securable
{
public int Id { get; set; }
}