I wonder if there is any chance to change name of foreign key constraint generated by Entity Framework when using code first.
I have two entities - User and Group - with many-to-many relationship so there is an association table GroupUser. Unfortunately, auto-generated foreign key constraints are named FK_dbo.GroupUser_dbo.User_User_UserId and FK_dbo.GroupUser_dbo.Group_Group_GroupId.
I would like to have foreign key constraints called like FK_GroupUser_UserId and FK_GroupUser_GroupId. That looks much cleaner to me.
It's not possible to customize the foreign key constraint name with data annotations or DbModelBuilder Fluent API. However, you can control the name with code-based migrations.
First option: When the tables get created via migrations:
The migration code that gets automatically generated for the join table would look like this:
public partial class MyMigration : DbMigration
{
public override void Up()
{
CreateTable("GroupUser",
c => new
{
UserId = c.Int(nullable: false),
GroupId = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.UserId, t.GroupId })
.ForeignKey("User", t => t.UserId, cascadeDelete: true)
.ForeignKey("Group", t => t.GroupId, cascadeDelete: true)
.Index(t => t.UserId)
.Index(t => t.GroupId);
// ...
}
}
Here you can modify the two ForeignKey method calls to set a custom constraint name before you call update-database:
.ForeignKey("User", t => t.UserId, cascadeDelete: true,
name: "FK_GroupUser_UserId")
.ForeignKey("Group", t => t.GroupId, cascadeDelete: true,
name: "FK_GroupUser_GroupId")
Second option: When the tables already exist you can drop the constraint and add a new renamed one in a migration:
public partial class MyMigration : DbMigration
{
public override void Up()
{
DropForeignKey("UserGroup", "UserId", "User");
DropForeignKey("UserGroup", "GroupId", "Group");
AddForeignKey("UserGroup", "UserId", "User",
name: "FK_GroupUser_UserId");
AddForeignKey("UserGroup", "GroupId", "Group",
name: "FK_GroupUser_GroupId")
// ...
}
}
You can implement a custom sql generator class derived from SqlServerMigrationSqlGenerator from System.Data.Entity.SqlServer
For more datail plese see the answer
Related
It's been days that I'm trying to achieve the following :
Add a deletedAt property on every entities by default without having to use a trait, or adding inheritance (or mapped superclasses) to all of my already created entities.
The goal here is to also make it work with doctrine migration script so that it would automatically add the associated column in every tables.
Tried to subscribe to the Doctrine\ORM\Events::loadClassMetadata event this way
class ClassMetadataEventSubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return [
Events::loadClassMetadata
];
}
public function loadClassMetadata(LoadClassMetadataEventArgs $args) {
$metadata = $args->getClassMetadata();
$metadata->mapField([
'fieldName' => 'deletedAt',
'type' => 'datetime',
'nullable' => true,
'columnName' => 'deleted_at'
]);
}
}
But I get the ReflectionProperty::_construct() Exception
App\Entity\Etudiant::$deletedAt does not exist
Exception trace:
at /srv/api/vendor/doctrine/persistence/lib/Doctrine/Persistence/Mapping/RuntimeReflectionService.php:90
ReflectionProperty->__construct()
Doctrine\Persistence\Mapping\RuntimeReflectionService->getAccessibleProperty()
Doctrine\ORM\Mapping\ClassMetadataInfo->wakeupReflection()
Doctrine\ORM\Mapping\ClassMetadataFactory->wakeupReflection()
Doctrine\Persistence\Mapping\AbstractClassMetadataFactory->loadMetadata()
Doctrine\Persistence\Mapping\AbstractClassMetadataFactory->getMetadataFor()
Doctrine\Persistence\Mapping\AbstractClassMetadataFactory->getAllMetadata()
Doctrine\Migrations\Provider\OrmSchemaProvider->createSchema()
Doctrine\Migrations\Generator\DiffGenerator->createToSchema()
Doctrine\Migrations\Generator\DiffGenerator->generate()
Doctrine\Migrations\Tools\Console\Command\DiffCommand->execute()
Also, the deletedAt property is here to enable soft deletion through gedmo/doctrine-extensions
Using Code First, I created a model representing a Donor. I used a migration to generate a table to store all the Donor objects. I used annotations to specify the primary key to be a composite of two properties of my model, Name and TeamId.
I added a navigation property to the model called HourlySnapshots which is an ICollection<HourlySnapshot> representing a one-to-many relationship. (HourlySnapshot is another domain model I created.) I ran the migration and it generated another table to store all the HourlySnapshot objects. As expected, it added two columns that weren't in my model to store the foreign key composed of Name and TeamId.
In order to initialize the HourlySnapshots table, I included a conventional Id property in the HourlySnapshot object to be used as a primary key. What I'm trying to do is switch the primary key of the HourlySnapshots table from the Id column to a composite of the foreign key (which is a composite of Name and TeamId) and another property of HourlySnapshot called Timestamp. In other words, make it a composite of three columns Name, TeamId, and Timestamp.
Can you think of a way to do this with Code First? I can easily do this by opening the table definition and editing there, but I would like to adhere to the Code First workflow, so that the migrations include all the changes.
After some more research, I was able to accomplish this with the Fluent API. I don't think it's possible using annotations or conventions. Here is what I ended up with in my ApplicationDBContext class...
public DbSet<Donor> Donors { get; set; }
public DbSet<HourlySnapshot> HourlySnapshots { get; set; }
public DbSet<DailySnapshot> DailySnapshots { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Donor>()
.HasKey(d => new { d.Name, d.TeamId });
modelBuilder.Entity<HourlySnapshot>()
.HasKey(s => new { s.Name, s.TeamId, s.Timestamp });
modelBuilder.Entity<Donor>()
.HasMany(d => d.HourlySnapshots)
.WithOptional()
.HasForeignKey(s => new { s.Name, s.TeamId });
modelBuilder.Entity<DailySnapshot>()
.HasKey(s => new { s.Name, s.TeamId, s.TimeStamp });
modelBuilder.Entity<Donor>()
.HasMany(d => d.DailySnapshots)
.WithOptional()
.HasForeignKey(s => new { s.Name, s.TeamId });
base.OnModelCreating(modelBuilder);
}
Instead of the default 5 tables, I would like to use just UserLoginsTable and UserTable. Because I just have one 'User' type (Admin) and there's no point of having the other tables.
I'm using ASP MVC 5 framework and Entity Framework 6 (Code First approach).
How can I achieve this in my application?
when you run add-migration "ApplicationDbContext", its give you the code of migration file which contains all the queries, what you need here is write the code to delete other 4 tables like:
enable-migrations
add-migration "drop tables" //selected specific context
write down the below code inside the migration code class
DropTable("dbo.AspNetUserClaims");
DropTable("dbo.AspNetUserLogins");
DropTable("dbo.AspNetUserRoles");
DropTable("dbo.AspNetRoles");
finally run the below command
update-database
You create your own User inheriting IdentityUser like this:
public class User : IdentityUser
{
}
Then you ignore the other classes in your DbContext, including the original IdentityUser, including your Users DbSet:
public class MyDbContext : IdentityDbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// avoid some base class sets...
modelBuilder.Ignore<IdentityUser>();
modelBuilder.Ignore<IdentityUserRole>();
modelBuilder.Ignore<IdentityRole>();
modelBuilder.Ignore<IdentityUserClaim>();
// keep identity's original configurations for AspNetUsers and AspNetUserLogins
EntityTypeConfiguration<User> configuration = modelBuilder.Entity<User>().ToTable("AspNetUsers");
configuration.HasMany<IdentityUserLogin>(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId);
IndexAttribute indexAttribute = new IndexAttribute("UserNameIndex")
{
IsUnique = true
};
configuration.Property((Expression<Func<User, string>>)(u => u.UserName)).IsRequired().HasMaxLength(0x100).HasColumnAnnotation("Index", new IndexAnnotation(indexAttribute));
configuration.Property((Expression<Func<User, string>>)(u => u.Email)).HasMaxLength(0x100);
modelBuilder.Entity<IdentityUserLogin>().HasKey(l => new { LoginProvider = l.LoginProvider, ProviderKey = l.ProviderKey, UserId = l.UserId }).ToTable("AspNetUserLogins");
}
}
The reason why I ignored the original IdentityUser and created my own User is because of this exception:
The navigation property 'Roles' is not a declared property on type
'IdentityUser'. Verify that it has not been explicitly excluded from
the model and that it is a valid navigation property.
I tried using modelBuilder.Entity<IdentityUser>().Ignore(u => u.Roles); but it didn't solve, though if someone knows how to solve this, we could keep things simpler, I would appreciate any suggestions.
Say I have a custom validation attribute ValidateFooIsCompatibleWith model like so:
public class FooPart
{
public string Foo { get; set; }
public string Eey { get; set; }
}
public class FooableViewModel
{
public FooPart Foo1 { get; set; }
[ValidateFooIsCompatibleWith("Foo1")]
public FooPart Foo2 { get; set; }
}
Let's say I also have custom EditorTemplates defined for FooPart:
#Html.TextBoxFor(m => m.Foo)
#Html.TextBoxFor(m => m.Eey)
And thus my view is essentially:
#Html.EditorFor(m => m.Foo1)
#Html.EditorFor(m => m.Foo2)
Server side, the validation works fine. However, no matter what I try, I can't get the rendered html to add the rule.
If I implement IClientValidatable, it turns out that GetClientValidationRules() never gets called. (I have successfully used IClientValidatable with "simple" fields before).
I also tried registering my own custom adapter by inheriting from DataAnnotationsModelValidator<TAttribute> and registering it in the global.asax with DataAnnotationsModelValidatorProvider.RegisterAdapter(...) That approach too fails to call GetClientValidationRules().
** Update **
If a add both a custom ModelMetadataProvider and a custom ModelValidatorProvider so that I can set breakpoints, I notice an interesting bit of behavior:
a request is made to the ModelMetadataProvider for metadata with a ContainerType of FooableViewModel and a ModelType of FooPart. However, no corresponding request is made to the ModelValidatorProvider, so I can't insert my custom client validation rules there.
requests are made to the ModelValidatorProvider with a ContainerType of FooPart and a ModelType of string for both the Foo and Eey properties. But at this level, I don't know the attributes applied to the FooPart property.
How can I get the MVC framework to register my custom client validation rules for complex types?
I found a solution:
First, Create a custom model metadata provider (see https://stackoverflow.com/a/20983571/24954) that checks the attributes on the complex type, and stores a client validatable rule factory in the AdditionalValues collection, e.g. in the CreateMetadataProtoype override of the CachedDataAnnotationsModelMetadataProvider
var ruleFactories = new List<Func<ModelMetadata, ControllerContext, IEnumerable<ModelClientValidationRules>>>();
...
var clientValidatable = (IClientValidatable)attribute;
ruleFactories.Add(clientValidatable.GetClientValidationRules);
...
result.AdditionalValues.Add("mycachekey", ruleFactories);
Next, register this as the default metadata provider in the global.asax
protected void Application_Start()
{
ModelMetadataProviders.Current = new MyCustomModelMetadataProvider();
....
}
Then I created an html helper that would process the modelmetata and create/merge the "data-val*" html attributes from each of AdditionalValues collection.
public static IDictionary<string, Object> MergeHtmlAttributes<TModel>(this HtmlHelper<TModel>, object htmlAttributes = null)
{
var attributesDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
//ensure data dictionary has unobtrusive validation enabled for the element
attributesDictionary.Add("data-val", "true");
//loop through all the rule factories, and execute each factory to get all the rules
var rules = ruleFactory(helper.Html.ViewData.ModelMetadata, helper.Html.ViewContext);
//loop through and execute all rules in the ruleFactory collection in the AdditionalValues
//and add the data-val attributes for those.
attributesDictionary.add("data-val-" + rule.ValidationType, ruleErrorMessage);
//similarly for anything in the rule.ValidationParameters
attributesDictionary.Add("data-val-" + rule.ValidationType + "-" + parameterName, parameterValue);
}
Finally, in my editor template, call the html helper (which has a model type of `FooPart1) for each complex type property, e.g.
#Html.TextBoxFor(m => m.Foo, Html.MergeHtmlAttributes(new { #class="Bar"}))
#Html.TextBoxFor(m => m.Eey, Html.MergeHtmlAttributes())
I actually ended up creating a second interface (with the same signature as IClientValidatable) that allowed me to customize rules (primarily for error messages) for the individual fields of a complex type. I also extended the helper to take a string argument that could be formatted with my custom rules.
jQuery.validator.setDefaults({
success: "valid"
});
$( "#foo" ).validate({
rules:
{
rule1: {required: true, min: 3},
parent:
{
required: function(element) {return $("#age").val() < 13;}
}
}
});
Complex types seem to hassle me for no good reason so try the Jquery validator. Depending on what you're trying to validate it might get the job done.
I am doing data-migration for my project. But I have one question, for example:
I have Book table with following fields:
ID Name Color
1 Java red
2 MVC blue
3 .Net blue
I tried to change the name of field from "Color" to "BookColor" using Code First tech. But after migration the table looked like this:
ID Name BookColor
1 Java null
2 MVC null
3 .Net null
I lost my field values. How can I make sure that all value are transfered?
I'm using Entity Framework with MVC3
EDIT This is my DBMigration Class:
public partial class AddCreative : DbMigration
{
public override void Up()
{
AddColumn("Authors", "Names", c => c.String(maxLength: 4000));
DropColumn("Authors", "Name");
}
public override void Down()
{
AddColumn("Authors", "Name", c => c.String(maxLength: 4000));
DropColumn("Authors", "Names");
}
}
I have changed Name to Names after changing (I lost my data in name field).
I had no problems using the following:
First, setup the migrations:
PM> Enable-Migrations
PM> Add-Migration RenameBookColorColumn
Then we setup the migrations class to perform the rename:
public class RenameBookColorColumn : DbMigration
{
public override void Up()
{
this.RenameColumn("Books", "Color", "BookColor");
}
public override void Down()
{
this.RenameColumn("Books", "BookColor", "Color");
}
}
Next, make a call to Update-Database so we can perform the changes:
PM> Update-Database -Verbose
Using NuGet project 'Example'.
Using StartUp project 'ExampleTest'.
Target database is: 'ExampleContext' (DataSource: .\SQLEXPRESS, Provider: System.Data.SqlClient, Origin: Convention).
Applying explicit migrations: [201207051400010_RenameBookColorColumn].
Applying explicit migration: 201207051400010_RenameBookColorColumn.
EXECUTE sp_rename #objname = N'Books.Color', #newname = N'BookColor',
#objtype = N'COLUMN' [Inserting migration history record]
And voila, it's renamed and the data is retained.
You can work around the limitation by using the following code.
public partial class AddCreative : DbMigration
{
public override void Up()
{
AddColumn("Authors", "Names", c => c.String(maxLength: 4000));
Sql("UPDATE [Authors] SET [Names] = [Name]");
DropColumn("Authors", "Name");
}
public override void Down()
{
AddColumn("Authors", "Name", c => c.String(maxLength: 4000));
Sql("UPDATE [Authors] SET [Name] = [Names]");
DropColumn("Authors", "Names");
}
}