I recently started using Entity Framework 6's code-first custom migrations. It's working well, but one thing I'd like to do is generate a pair of CreateIndex() and DropIndex() statements when attempting to rename an index, instead of using RenameIndex() like the default CSharpMigrationCodeGenerator wants to.
For example, I currently use data annotations in a fluent mapping to rename an index like this:
Property(x => x.TeacherId).HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("IX_Students_TeacherId")));
The problem here is that by default EF6 wants to generate the following code when I add a new migration to capture this change to the model:
using System;
using System.Data.Entity.Migrations;
namespace MyApp.Migrations
{
public partial class RenameIndexTest : DbMigration
{
public override void Up()
{
// BAD: [RenameIndex] will generate a "EXEC sp_rename" statement.
RenameIndex(table: "dbo.Students", name: "IX_TeacherId", newName: "IX_Students_TeacherId");
}
public override void Down()
{
RenameIndex(table: "dbo.Students", name: "IX_Students_TeacherId", newName: "IX_TeacherId");
}
}
}
But what I really need EF6 to generate is this:
using System;
using System.Data.Entity.Migrations;
namespace MyApp.Migrations
{
public partial class RenameIndexTest : DbMigration
{
public override void Up()
{
// GOOD: We generate separate SQL statements to drop & add the index.
DropIndex(table: "dbo.Students", name: "IX_TeacherId");
CreateIndex(table: "dbo.Students", name: "IX_Students_TeacherId", column: "TeacherId");
}
public override void Down()
{
DropIndex(table: "dbo.Students", name: "IX_Students_TeacherId");
CreateIndex(table: "dbo.Students", name: "IX_TeacherId", column: "TeacherId");
}
}
}
Our data team has a hard requirement that developers use T-SQL DROP/CREATE statements when renaming indexes. Thus far, I haven't been able to find a way to override the behavior of the RenameIndex() statement, using a custom class that uses CSharpMigrationCodeGenerator as its base class, because the RenameIndexOperation class doesn't have any information about the column(s) an index has been created on.
This is as far as I've been able to get on my own:
namespace MyApp.Migrations
{
internal class CustomCSharpMigrationCodeGenerator : CSharpMigrationCodeGenerator
{
protected override string Generate(IEnumerable<MigrationOperation> operations, string #namespace, string className)
{
var customizedOperations = new List<MigrationOperation>();
foreach (var operation in operations)
{
if (operation is RenameIndexOperation)
{
var renameIndexOperation = operation as RenameIndexOperation;
var dropIndexOperation = new DropIndexOperation(operation.AnonymousArguments)
{
Table = renameIndexOperation.Table,
Name = renameIndexOperation.Name
};
var createIndexOperation = new CreateIndexOperation(operation.AnonymousArguments)
{
Table = renameIndexOperation.Table,
Name = renameIndexOperation.NewName,
// HELP: How do I get this information about the existing index?
// HELP: How do I specify what columns the index should be created on?
IsUnique = false,
IsClustered = false
};
// Do not generate a RenameIndex() statement; instead, generate a pair of DropIndex() and CreateIndex() statements.
customizedOperations.Add(dropIndexOperation);
customizedOperations.Add(createIndexOperation);
}
else
{
customizedOperations.Add(operation);
}
}
return base.Generate(customizedOperations, #namespace, className);
}
}
}
Does this make sense? And more importantly, does anyone have any suggestions or ideas on how to proceed? Either way, thanks in advance!
I'm closing this question out. I was never able to do exactly what I sought to... it wasn't a dealbreaker, I simply was hoping EF6 had some way of (easily) exerting control over the name of indexes being created.
IIRC, I did what #steve-greene suggested and manually specified the name of the index using the Sql() method.
Related
I use Entity Framework 6.2 Code First (.net framework 4.6.1) and I map few entities to view via Table Attribute. It works for select operations and I handle Insert/Update/Delete with writing trigger to view at sql server side. It works as expected, however when I add a new migration, Entity Framework generate RenameTable scripts for used Table Attribute (actuallyis expected behavior for EF). But I want to intercept migration generation and change these entities tableName to original name.
my code like;
[MapToView("Users","UsersView")]
public class User
{
...
}
I wrote MapToView Attribute, this attribute inherited by TableAttribute and pass to second parameter to TableAttribute. I create this Attribute because if I intercept migration generation, return original table name with this attribute parameters.
In this case when I run "add-migration migrationName" it creates migration scripts like this;
RenameTable(name: "dbo.Users", newName: "UsersView");
but i want to create empty migration when I run "add-migration migrationName" script.
anyone can help me?
I solve the problem.
First: Problem is; When I Map Entity to View EF Code-first generate migration with ViewName. This is problem because I want to use View Instead of Table. So I solve problem with this instructions;
1- I Create BaseEntityConfiguration that Inherited from EntityTypeConfiguration and all entity configuration classes are inherited by.
for example:
public class UserConfig: BaseEntityConfiguration<User> //Generic Type is Entity
{
public UserConfig()
{
}
}
2- I Create MapToViewAttribute that inherited by TableAttribute
public class MapToViewAttribute : TableAttribute
{
public string TableName { get; }
public string ViewName { get; }
public MapToViewAttribute(string tableName, string viewName) : base(viewName)
{
TableName = tableName;
ViewName = viewName;
}
}
3- I Use MapToViewAttribute for example User Entity to use View.
[MapToView("User","UserView")]
public class User
{
...
}
And in BaseEntityConfiguration's Constructor I Get Generic Type and custom attributes. If any entity has MapToView Attribute, I pass to TableName parameter to ToTable Method. So at runtime EF uses View for these entities but doesn't create migration with RenameTable for these entities.
protected BaseEntityConfiguration()
{
var baseType = typeof(TEntityType);
var attributes = baseType.GetCustomAttributes(true);
foreach (var attr in attributes)
{
if (attr.GetType() == typeof(MapToViewAttribute))
{
var tableName = ((MapToViewAttribute)attr).TableName;
ToTable(tableName);
}
}
}
Last EF don't use your configuration files, so you must tell the EF to use this in DbContext class's InternalModelCreate method.
My implementation like this;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var typesToRegister = Assembly.GetExecutingAssembly()
.GetTypes().Where(IsConfigurationType);
foreach (var type in typesToRegister)
{
dynamic configurationInstance = type.BaseType != null
&& type.BaseType.IsGenericType
&& type.BaseType.GetGenericTypeDefinition() == typeof(BaseEntityConfiguration<>)
? Activator.CreateInstance(type, culture)
: Activator.CreateInstance(type);
modelBuilder.Configurations.Add(configurationInstance);
}
modelBuilder.Types().Configure(t => t.ToTable(t.ClrType.Name));
BaseDbContext.InternalModelCreate(modelBuilder);
}
But if you use this approach you must create Insert, Update and Delete Triggers/Rule (if you use SQLServer trigger is an option but if you use postgresql rule is better option) because EF uses this view for insert, update and delete operations.
I am currently using Mehdi El Gueddari's DbContextScope project, I think by the book, and it's awesome. But I came across a problem I'm unsure how to solve today. I have a query that I need to execute using a different database login/user because it requires additional permissions. I can create another connection string in my web.config, but I'm not sure how to specify that for this query, I want to use this new connection string. Here is my usage:
In my logic layer:
private static IDbContextScopeFactory _dbContextFactory = new DbContextScopeFactory();
public static Guid GetFacilityID(string altID)
{
...
using (_dbContextFactory.CreateReadOnly())
{
entity = entities.GetFacilityID(altID)
}
}
That calls into my data layer which would look something like this:
private AmbientDbContextLocator _dbcLocator = new AmbientDbContextLocator();
protected CRMEntities DBContext
{
get
{
var dbContext = _dbcLocator.Get<CRMEntities>();
if (dbContext == null)
throw new InvalidOperationException("No ambient DbContext....");
return dbContext;
}
}
public virtual Guid GetFaciltyID(string altID)
{
return DBContext.Set<Facility>().Where(f => f.altID = altID).Select(f => f.ID).FirstOrDefault();
}
Currently my connection string is set in the default way:
public partial class CRMEntities : DbContext
{
public CRMEntities()
: base("name=CRMEntities")
{}
}
Is it possible for this specific query to use a different connection string and how?
I ended up modifying the source code in a way that feels slightly hacky, but is getting the job done for now. I created a new IAmbientDbContextLocator with a Get<TDbContext> method override that accepts a connection string:
public TDbContext Get<TDbContext>(string nameOrConnectionString) where TDbContext : DbContext
{
var ambientDbContextScope = DbContextScope.GetAmbientScope();
return ambientDbContextScope == null ? null : ambientDbContextScope.DbContexts.Get<TDbContext>(nameOrConnectionString);
}
Then I updated the DbContextCollection to pass this parameter to the DbContext's existing constructor overload. Last, I updated the DbContextCollection maintain a Dictionary<KeyValuePair<Type, string>, DbContext> instead of a Dictionary<Type, DbContext> as its cached _initializedDbContexts where the added string is the nameOrConnectionString param. So in other words, I updated it to cache unique DbContext type/connection string pairs.
Then I can get at the DbContext with the connection I need like this:
var dbContext = new CustomAmbientDbContextLocator().Get<CRMEntities>("name=CRMEntitiesAdmin");
Of course you'd have to be careful your code doesn't end up going through two different contexts/connection strings when it should be going through the same one. In my case I have them separated into two different data access class implementations.
I noticed a weird thing today when I tried to save an entity and return its id in EF Core
Before:
After:
I was thinking about if it was before calling saveChanges() but it works with another entity with a similar setup.
ps: I use unit of work to save all changes at the end.
What was the reason?
It will be negative until you save your changes. Just call Save on the context.
_dbContext.Locations.Add(location);
_dbContext.Save();
After the save, you will have the ID which is in the database. You can use transactions, which you can roll back in case there's a problem after you get the ID.
The other way would be not to use the database's built-in IDENTITY fields, but rather implement them yourself. This can be very useful when you have a lot of bulk insert operations, but it comes with a price — it's not easy to implement.
Apparently, this isn't a bug it's a feature: https://github.com/aspnet/EntityFrameworkCore/issues/6147
It's not too arduous to override this behavior like so:
public class IntValueGenerator : TemporaryNumberValueGenerator<int>
{
private int _current = 0;
public override int Next(EntityEntry entry)
{
return Interlocked.Increment(ref _current);
}
}
Then reference the custom value generator here:
public class CustomContext: DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var type in modelBuilder.Model.GetEntityTypes().Select(c => c.ClrType))
{
modelBuilder.Entity(type, b =>
{
b.Property("Id").HasValueGenerator<IntValueGenerator>();
});
}
base.OnModelCreating(modelBuilder);
}
}
This will result in temporary Id's that increment from "1", however, more effort is required to prevent key conflicts when attempting to save.
I'm a little confused as to the purpose of a data model in Entity Framework code-first. Because EF will auto-generate a database from scratch for you if it doesn't already exist using nothing more than the data model (including data annotations and Fluent API stuff in DbContext.OnModelCreating), I was assuming that the data model should fully describe your database's structure, and you wouldn't need to modify anything fundamental after that.
However, I came across this Codeplex issue in which one of the EF Triage Team members suggests that custom indexes be added in data migrations, but not as annotations to your data model fields, or Fluent API code.
But wouldn't that mean that anyone auto-generating the database from scratch would not get those custom indexes added to their DB? The assumption seems to be that once you start using data migrations, you're never going to create the database from scratch again. What if you're working in a team and a new team member comes along with a new SQL Server install? Are you expected to copy over a database from another team member? What if you want to start using a new DBMS, like Postgres? I thought one of the cool things about EF was that it was DBMS-independent, but if you're no longer able to create the database from scratch, you can no longer do things in a DBMS-independent way.
For the reasons I outlined above, wouldn't adding custom indexes in a data migration but not in the data model be a bad idea? For that matter, wouldn't adding any DB structure changes in a migration but not in the data model be a bad idea?
Are EF code-first models intended to fully describe a database's structure?
No, they don't fully describe the database structure or schema.Still there are methods to make the database fully described using EF. They are as below:
You can use the new CTP5’s ExecuteSqlCommand method on Database class which allows raw SQL commands to be executed against the database.
The best place to invoke SqlCommand method for this purpose is inside a Seed method that has been overridden in a custom Initializer class. For example:
protected override void Seed(EntityMappingContext context)
{
context.Database.ExecuteSqlCommand("CREATE INDEX IX_NAME ON ...");
}
You can even add Unique Constraints this way.
It is not a workaround, but will be enforced as the database will be generated.
OR
If you are badly in need of the attribute, then here it goes
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
public class IndexAttribute : Attribute
{
public IndexAttribute(string name, bool unique = false)
{
this.Name = name;
this.IsUnique = unique;
}
public string Name { get; private set; }
public bool IsUnique { get; private set; }
}
After this , you will have an initializer which you will call in your OnModelCreating method as below:
public class IndexInitializer<T> : IDatabaseInitializer<T> where T : DbContext
{
private const string CreateIndexQueryTemplate = "CREATE {unique} INDEX {indexName} ON {tableName} ({columnName});";
public void InitializeDatabase(T context)
{
const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance;
Dictionary<IndexAttribute, List<string>> indexes = new Dictionary<IndexAttribute, List<string>>();
string query = string.Empty;
foreach (var dataSetProperty in typeof(T).GetProperties(PublicInstance).Where(p => p.PropertyType.Name == typeof(DbSet<>).Name))
{
var entityType = dataSetProperty.PropertyType.GetGenericArguments().Single();
TableAttribute[] tableAttributes = (TableAttribute[])entityType.GetCustomAttributes(typeof(TableAttribute), false);
indexes.Clear();
string tableName = tableAttributes.Length != 0 ? tableAttributes[0].Name : dataSetProperty.Name;
foreach (PropertyInfo property in entityType.GetProperties(PublicInstance))
{
IndexAttribute[] indexAttributes = (IndexAttribute[])property.GetCustomAttributes(typeof(IndexAttribute), false);
NotMappedAttribute[] notMappedAttributes = (NotMappedAttribute[])property.GetCustomAttributes(typeof(NotMappedAttribute), false);
if (indexAttributes.Length > 0 && notMappedAttributes.Length == 0)
{
ColumnAttribute[] columnAttributes = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), false);
foreach (IndexAttribute indexAttribute in indexAttributes)
{
if (!indexes.ContainsKey(indexAttribute))
{
indexes.Add(indexAttribute, new List<string>());
}
if (property.PropertyType.IsValueType || property.PropertyType == typeof(string))
{
string columnName = columnAttributes.Length != 0 ? columnAttributes[0].Name : property.Name;
indexes[indexAttribute].Add(columnName);
}
else
{
indexes[indexAttribute].Add(property.PropertyType.Name + "_" + GetKeyName(property.PropertyType));
}
}
}
}
foreach (IndexAttribute indexAttribute in indexes.Keys)
{
query += CreateIndexQueryTemplate.Replace("{indexName}", indexAttribute.Name)
.Replace("{tableName}", tableName)
.Replace("{columnName}", string.Join(", ", indexes[indexAttribute].ToArray()))
.Replace("{unique}", indexAttribute.IsUnique ? "UNIQUE" : string.Empty);
}
}
if (context.Database.CreateIfNotExists())
{
context.Database.ExecuteSqlCommand(query);
}
}
private string GetKeyName(Type type)
{
PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public);
foreach (PropertyInfo propertyInfo in propertyInfos)
{
if (propertyInfo.GetCustomAttribute(typeof(KeyAttribute), true) != null)
return propertyInfo.Name;
}
throw new Exception("No property was found with the attribute Key");
}
}
Then overload OnModelCreating in your DbContext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer(new IndexInitializer<MyContext>());
base.OnModelCreating(modelBuilder);
}
Apply the index attribute to your Entity type, with this solution you can have multiple fields in the same index just use the same name and unique.
OR
You can do the migrations later on.
Note:
I have taken a lot of this code from here.
The question seems to be if there is value in having migrations added mid-stream, or if those will cause problems for future database initializations on different machines.
The initial migration that is created also contains the entire data model as it exists, so by adding migrations (enable-migrations in the Package Manager Console) you are, in effect, creating the built-in mechanism for your database to be properly created down the road for other developers.
If you're doing this, I do recommend modifying the database initialization strategy to run all your existing migrations, lest EF should start up and get the next dev's database out of sync.
Something like this would work:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<YourNamespace.YourDataContext, Migrations.Configuration>());
So, no, this won't inherently introduce problems for future work/developers. Remember that migrations are just turned into valid SQL that executes against the database...you can even use script mode to output the TSQL required to make the DB modifications based on anything in the migrations you have created.
Cheers.
I am currently trying to persist the following class with the GORM MongoDB plugin for grails:
class Result {
String url
def Result(){
}
static constraints = {
}
static mapWith="mongo"
static mapping = {
collection "results"
database "crawl"
}
}
The code I'm running to persist this class is the following:
class ResultIntegrationTests {
#Before
void setUp() {
}
#After
void tearDown() {
}
#Test
void testSomething() {
Result r = new Result();
r.setUrl("http://heise.de")
r.getMetaClass().setProperty("title", "This is how it ends!")
println(r.getTitle())
r.save(flush:true)
}
}
This is the result in MongoDB:
{ "_id" : NumberLong(1), "url" : "http://heise.de", "version" : 0 }#
Now the url is properly persisted with MongoDB but the dynamic property somehow is not seen by the mapper - although the println(r.getTitle()) works perfectly fine.
I am new to groovy so I thought that someone with a little more experience could help me out with this problem. Is there a way to make this dynamically added property visible to the mapping facility? If yes how can I do that?
Thanks a lot for any advice...
Rather than adding random properties to the metaClass and hoping that Grails will both scan the metaClass looking for your random properties and then persist them, why not just add a Map to your domain class, (or a new Key/Value domain class which Result can hasMany) so you can add random extra properties to it as you want.
try this doc
#Test
void testSomething() {
Result r = new Result();
r.url = "http://heise.de"
r.['title'] = "This is how it ends!" //edit: forgot the subscript
println r.['title']
r.save(flush:true)
}
BTW, Instead of using gorm or hibernate you can always use directly java api / gmongo.