I am trying to move a project that was using EF 6 to EF Core 2.
The migrations in the old project produced a database with a "dbo." prefix on everything, e.g. tables are "dbo.Something", foreign keys are "FK_dbo.Something_dbo.SomethingElse_Id".
But EF Core doesn't follow this convention, it leaves out the "dbo.".
Is there a way to get EF Core to follow the old convention so my existing databases will still work?
You can change the naming convention using the OnModelCreating method of the DbContext. Here's an example for a blog post I wrote:
public class BloggingContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Singularize table name
// Blogs => Blog
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
// Add NuGet package "Humanizer" to use Singularize()
entityType.Relational().TableName = entityType.Relational().TableName.Singularize();
}
// Prefix column names with table name
// Id => Blog_Id
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
property.Relational().ColumnName = entityType.Relational().TableName + "_" + property.Relational().ColumnName;
}
}
// Rename Foreign Key
// FK_Post_Blog_BlogId => FK_Post_Blog_BlogId_Test
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
foreach (var fk in entityType.FindForeignKeys(property))
{
fk.Relational().Name = fk.Relational().Name + "_Test";
}
}
}
// Rename Indices
// IX_Blog_Url => IX_Blog_Url_Test
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var index in entityType.GetIndexes())
{
index.Relational().Name = index.Relational().Name + "_Test";
}
}
}
}
There is no straightforward way to achieve this. The only way is to construct the PK and FK names manually. Override the OnModelCreating method of DBContext as below. If you already have definitions within OnModelCreating, then make sure to paste the code in the end (after your fluent api configurations, if any).
protected override void OnModelCreating(ModelBuilder builder)
{
foreach (var pk in builder.Model
.GetEntityTypes()
.SelectMany(t => t.GetKeys()))
{
pk.Relational().Name = "PK_dbo." + pk.DeclaringEntityType.ClrType.Name;
}
foreach (var fk in builder.Model
.GetEntityTypes()
.SelectMany(t => t.GetForeignKeys()))
{
fk.Relational().Name = "FK_dbo." + fk.DeclaringEntityType.ClrType.Name +
"_dbo." + fk.PrincipalEntityType.ClrType.Name +
"_" + fk.Properties.First().Name;
}
}
Related
By convention, each property will be set up to map to a column with the same name as the property. If I want to change the default mapping strategy, I can do it by using either Fluent API or Data Annotation. But, I want to set a custom mapping strategy for all the properties in all entities to the database columns. My database is exists and the column names are like ID, CUSTOMER_NAME, CREDIT_AMOUNT and so on, so the column names don't follow PascalCase notation. All object names are in upper case and individual words separated with "_" symbol. This is true for the entire database. And I want to map this naming to a class like this:
public class Payment
{
public int ID { set; get; }
public string CustomerName { get; set; }
public decimal CreditAmount { get; set; }
}
The database is large and I don't want to map each property and class names to the appropriated database objects. Is there any global way to define this type of mapping like this?
CustomerName -> CUSTOMER_NAME,
CreditAmount -> CREDIT_AMOUNT and so on.
Possible way of doing that convention with reflection like this:
Getting Entity types from DBSet properties of DbContext class
Then getting properties (columns) of entity types
So
In your DbContext class add this line:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//see below for this extension method
this.ApplyCaseMappingRule(modelBuilder);
base.OnModelCreating(modelBuilder);
}
Extension method source:
public static class Extensions
{
public static void ApplyCaseMappingRule<TDbContext>(this TDbContext _, ModelBuilder modelBuilder) where TDbContext:DbContext
{
var ignoreType = typeof(NotMappedAttribute);
var dbSetProps = typeof(TDbContext).GetProperties();
foreach (var dbSetProp in dbSetProps)
{
if (dbSetProp.PropertyType.TryGetEntityTypeFromDbSetType(out var entityType))
{
modelBuilder.Entity(entityType, option =>
{
option.ToTable(Mutate(dbSetProp.Name));
var props = entityType.GetProperties();
foreach (var prop in props)
{
//check if prop has Ignore attribute
var hasIgnoreAttribute =
prop.PropertyType.CustomAttributes.Any(x => x.AttributeType == ignoreType);
if (hasIgnoreAttribute) continue;
option.Property(prop.PropertyType, prop.Name).HasColumnName(Mutate(prop.Name));
}
});
}
}
}
private static bool TryGetEntityTypeFromDbSetType(this Type dbSetType, out Type entityType)
{
entityType = null;
if (dbSetType.Name != "DbSet`1") return false;
if (dbSetType.GenericTypeArguments.Length != 1) return false;
entityType = dbSetType.GenericTypeArguments[0];
return true;
}
public static IEnumerable<string> SplitCamelCase(this string source)
{
const string pattern = #"[A-Z][a-z]*|[a-z]+|\d+";
var matches = Regex.Matches(source, pattern);
foreach (Match match in matches)
{
yield return match.Value;
}
}
public static string Mutate(string propName)
{
return string.Join("_", propName.SplitCamelCase().Select(x => x.ToUpperInvariant()));
}
Tested on .NET 5 with EF 5.0.0
You just need to iterate the Entities and Properties from modelBuilder.Model, eg
string ToDatabaseIdentifier(string propertyName)
{
var sb = new System.Text.StringBuilder();
for (int i = 0; i < propertyName.Length; i++)
{
var c = propertyName[i];
if (i>0 && Char.IsUpper(c) && Char.IsLower(propertyName[i-1]))
{
sb.Append('_');
}
sb.Append(Char.ToUpper(c));
}
return sb.ToString();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var e in modelBuilder.Model.GetEntityTypes())
{
e.SetTableName(ToDatabaseIdentifier(e.Name));
foreach (var p in e.GetProperties())
{
p.SetColumnName(ToDatabaseIdentifier(p.Name));
}
}
base.OnModelCreating(modelBuilder);
}
I wanted to create a table during the Startup with the Entity Framework. I have context in separate project.
Startup.cs in method: Configure(IApplicationBuilder app)
try
{
using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
scope.ServiceProvider.GetRequiredService<MyContext>().Database.Migrate();
}
}
catch (Exception ex)
{
throw;
}
This Piece of code is not creating the table.
However when i tried to create the migration by
dotnet ef migrations add AddTable_CLient -c MyContext. The table is being created but the
scope.ServiceProvider.GetRequiredService<MyContext>().Database.Migrate(); doesnot create the table for me. And It shows table has been created already.
public class DesignTimeDbContextFactory: IDesignTimeDbContextFactory<VerittyContext>
{
public MyContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<MyContext>();
builder.UseSqlServer("Server=localhost;Database=ClientManagement;Trusted_Connection=True;MultipleActiveResultSets=true",
optionsBuilder => optionsBuilder.MigrationsAssembly(typeof(MyContext).GetTypeInfo().Assembly.GetName().Name));
return new VerittyContext(builder.Options);
}
}
MyContext.cs
public DbSet<Company> Company { get; set; }
public DbSet<CompanyContact> CompanyContact { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Company>();
modelBuilder.Entity<CompanyContact>();
}
I wanted the table to be created automatically when the table is being added to Context class. I don't wanted to add the migration manually. Does using the code below is not the way to add the table to Database automatically?
using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
scope.ServiceProvider.GetRequiredService<MyContext>().Database.Migrate();
}
Please Help.
I have two entities (tables) Action and ActionLog. The ActionLog is derived from Action. I need to map entity Action to table Action when it is used alone and map it to table ActionLog when it is used inside an inheritance relationship.
Entities:
Action entity properties:
Action_Property1
Action_Property2
Action_Property3
ActionLog entity properties:
All the inherited properties from Action entity
ActionLog_Property1
Tables:
Action table columns:
Action_Property1
Action_Property2
Action_Property3
ActionLog table columns:
Action_Property1
Action_Property2
Action_Property3
ActionLog_Property1
Is this possible using EF6 Code First mapping in a single context?
Edit 1:
I try to be more explicit. I need something like this:
using(var ctx = new DbContext())
{
var action = new Action
{
Action_Property1 = 1,
Action_Property2 = 2,
Action_Property3 = 3
};
ctx.Actions.Add(action);
ctx.SaveChanges();
}
The lines above should write the Action entity to Action table.
using(var ctx = new DbContext())
{
var actionLog = new ActionLog
{
Action_Property1 = 1,
Action_Property2 = 2,
Action_Property3 = 3,
ActionLog_Property1 = 1
};
ctx.ActionLogs.Add(actionLog);
ctx.SaveChanges();
}
The lines above should write the ActionLog entity to ActionLog table.
Yes, it's possible. Using Mapping the Table-Per-Concrete Class (TPC) Inheritance
it can do so
public class InheritanceMappingContext : DbContext
{
public DbSet<Action> Action { get; set; }
public DbSet<ActionLog> ActionLog { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ActionLog>().Map(m =>
{
m.MapInheritedProperties(); // add inherited property to actionLog
m.ToTable("ActionLog");
});
modelBuilder.Entity<Action>().Map(m =>
{
m.ToTable("Action");
});
}
}
Sure, map your base class:
public class YourBaseClassMapping<T> : EntityTypeConfiguration<T> where T : YourBaseClassEntity
{
protected YourBaseClassMapping()
{
HasKey(x => x.Id);
...
}
}
and then map inherited class:
public class InheritedClassMapping<T> : EntityTypeConfiguration<T> where T : InheritedClassMapping
{
public InheritedClassMapping()
{
// new mapping for inherited class
}
}
and add both mappings to DbModelBuilder as other mappings:
public class YourDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new YourBaseClassMapping());
modelBuilder.Configurations.Add(new InheritedClassMapping());
}
}
How can I go about changing the naming convention of the auto-generated many-to-many table?
Assume I have two classes:
public class User
{
public int UserId { get; set; }
public virtual List<Role> Roles { get; set; }
}
public class Role
{
public int RoleId { get; set; }
public virtual List<User> Users { get; set; }
}
By Default, this will create a table called UserRoles.
I can change the name of that one table to UsersInRoles, for example, by using the following in the OnModelCreating override of my DbContext:
modelBuilder.Entity<User>()
.HasMany(p => p.Roles)
.WithMany(p => p.Users)
.Map(mc =>
{
mc.MapLeftKey("UserId");
mc.MapRightKey("RoleId");
mc.ToTable("UsersInRoles");
});
However, what I really want to do is change the naming convention so that by default, all auto-generated many-to-many tables use this new convention. I cannot figure out how to do that, or if it's even possible. I do not like having to specify 9 lines of extra code every time I specify one of these relationships.
I am currently using EF version 6.0.0-rc1.
The ability to control relationships was removed from the basic conventions API before release because it wasn't in a usable state. You can access all of the properties and tables in the model through model based conventions. An overview of model based conventions is available here: http://msdn.microsoft.com/en-US/data/dn469439
This solution involves a little more digging around in the metadata API, EntitySet is the correct type for this scenario
This convention should rename the generated relation table:
public class MyConvention : IStoreModelConvention<EntitySet>
{
public void Apply(EntitySet set, DbModel model)
{
var properties = set.ElementType.Properties;
if (properties.Count == 2)
{
var relationEnds = new List<string>();
int i = 0;
foreach (var metadataProperty in properties)
{
if (metadataProperty.Name.EndsWith("_ID"))
{
var name = metadataProperty.Name;
relationEnds.Add(name.Substring(0, name.Length - 3));
i++;
}
}
if (relationEnds.Count == 2)
{
set.Table = relationEnds.ElementAt(0) + "_" + relationEnds.ElementAt(1) + "_RelationTable";
}
}
}
I'm hoping to set up an EF Code First convention where all of the column names of the properties have a lowercase first letter.
However, I have other fluent API code that changes column names from the default. I can't seem to find a way to get access to the current column name of a property in order to lowercase the first letter. Starting with the PropertyInfo, as in modelBuilder.Properties() is not enough because the column name may have already been set to be different than the member name.
How do I generically tell EF Code First to lowercase the first letter of all column names?
OK, the DBA's are speaking. Let's bow our heads in reverence and see what we can do. I'm afraid that in EF 5 (and lower) there's not much you can do to make it easy. In EF 6 there is this feature of Custom Code First Conventions which actually make it a piece of cake. I just tried a small sample:
// Just some POCO
class Person
{
public int PersonId { get; set; }
public string PersonName { get; set; }
}
// A custom convention.
class FirstCharLowerCaseConvention : IStoreModelConvention<EdmProperty>
{
public void Apply(EdmProperty property, DbModel model)
{
property.Name = property.Name.Substring(0, 1).ToLower()
+ property.Name.Substring(1);
}
}
class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>();
// Add the convention to the modelbuilder.
modelBuilder.Conventions.Add(new FirstCharLowerCaseConvention());
base.OnModelCreating(modelBuilder);
}
}
After running
using (var db = new MyContext())
{
db.Database.Create();
}
my database has a People table with personId and personName.
And some simple CRUD actions work flawlessly:
using (var db = new MyContext())
{
var p = new Person { PersonName = "Another Geek" };
db.Set<Person>().Add(p);
db.SaveChanges();
}
using (var db = new MyContext())
{
var x = db.Set<Person>().ToList();
}
So if the DBA's want their conventions, you can demand a new toy :)