I am looking into Migrations in an effort to clean up our deployment processes. The less manual intervention required when pushing a change to production the better.
I have run into 3 major snags with the migrations system. They are show stoppers if I can not figure out a clean way around them.
1. How do I add Seed data per migration:
I execute the command "add-migration" which scaffolds a new migration file with Up and Down functions. Now, I want to automatically make changes to the data with both Up and Down changes. I don't want to add the Seed data to the Configuration.Seed method as this runs for all migrations which ends in all sorts of duplication problems.
2. If the above is not possible, how do I avoid duplications?
I have an enum that I loop through to add the values to the database.
foreach(var enumValue in Enum.GetValues(typeof(Access.Level)))
{
context.Access.AddOrUpdate(
new Access { AccessId = ((int)enumValue), Name = enumValue.ToString() }
);
}
context.SaveChanges();
Even though I am using AddOrUpdate, I still get duplicates in the database. The above code brings me to my 3rd and final problem:
3. How can I seed Primary Keys?
My enumerable with the above code is:
public class Access
{
public enum Level
{
None = 10,
Read = 20,
ReadWrite = 30
}
public int AccessId { get; set; }
public string Name { get; set; }
}
I am specifying the values that I want as my primary key, but Entity Framework seems to ignore it. They still end up being 1,2,3. How do I get it to be 10,20,30?
Are these limitations of EF at the moment or are they intentional constraints to prevent some other kind of catastrophe I am not seeing?
When I have fixed data that I want to insert with a migration, I put the inserts directly in the Up() migration using calls to Sql("Insert ..."). See the note halfway down this page: how to insert fixed data
You prevent duplicates in the Seed method by calling the AddOrUpdate overload that takes an identifier expression specifying the natural key - see this answer and this blog entry.
Primary keys that are integers are created as identity fields by default. To specify otherwise use the [DatabaseGenerated(DatabaseGeneratedOption.None)] attribute
I think this is a good explanation of Initializer and Seed methods
Here is an example of how to use the AddOrUpdate method:
foreach(var enumValue in Enum.GetValues(typeof(Access.Level)))
{
context.Access.AddOrUpdate(
x => x.Name, //the natural key is "Name"
new Access { AccessId = ((int)enumValue), Name = enumValue.ToString() }
);
}
As a possible solution to item 1, I made an implementation of the IDatabaseInitializer strategy which will run the Seed method of each pending migration only, you will need to implement a custom IMigrationSeed interface in each of your DbMigration classes, the Seed method will then be implemented right after Up and Down methods of every migration class.
This helps to solve two problems for me:
Group Database Model Migration with Database Data Migration (or Seeding)
Check what part of the Seed migration code should really be running, not checking data in the database but using already known data which is the database model that was just created.
The interface looks like this
public interface IMigrationSeed<TContext>
{
void Seed(TContext context);
}
Below is the new implementation that will call this Seed method
public class CheckAndMigrateDatabaseToLatestVersion<TContext, TMigrationsConfiguration>
: IDatabaseInitializer<TContext>
where TContext : DbContext
where TMigrationsConfiguration : DbMigrationsConfiguration<TContext>, new()
{
public virtual void InitializeDatabase(TContext context)
{
var migratorBase = ((MigratorBase)new DbMigrator(Activator.CreateInstance<TMigrationsConfiguration>()));
var pendingMigrations = migratorBase.GetPendingMigrations().ToArray();
if (pendingMigrations.Any()) // Is there anything to migrate?
{
// Applying all migrations
migratorBase.Update();
// Here all migrations are applied
foreach (var pendingMigration in pendingMigrations)
{
var migrationName = pendingMigration.Substring(pendingMigration.IndexOf('_') + 1);
var t = typeof(TMigrationsConfiguration).Assembly.GetType(
typeof(TMigrationsConfiguration).Namespace + "." + migrationName);
if (t != null
&& t.GetInterfaces().Any(x => x.IsGenericType
&& x.GetGenericTypeDefinition() == typeof(IMigrationSeed<>)))
{
// Apply migration seed
var seedMigration = (IMigrationSeed<TContext>)Activator.CreateInstance(t);
seedMigration.Seed(context);
context.SaveChanges();
}
}
}
}
}
The good thing here is you have a real EF context to manipulate Seed Data, just like standard EF Seed implementation. However this can get strange if for example you decide to delete a table that was Seeded in a previous migration, you will have to refactor your existing Seed code accordingly.
EDIT:
As an alternative to implement the seed method after the Up and Down, you can create a partial class of the same Migration class, I found this useful as it allows me to safely delete the migration class when I want to re-seed the same migration.
Hi I have found a very useful information for your problem in this link:
Safari Books Online
"1. How do I add Seed data per migration:"
As you see in the example you need to create a new confiugration for seeding.
This seed Configuration must be called after migration.
public sealed class Configuration : DbMigrationsConfiguration
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(SafariCodeFirst.SeminarContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
"2. If the above is not possible, how do I avoid duplications?"
AddOrUpdate Must help you exactly to avoding the duplicates if you get an error here you might have a configuration error post the call stack please. See the example!
"3. How can I seed Primary Keys?"
Here it is also on your key definition. If your key DatabaseGenerated(DatabaseGeneratedOption.Identity) than you do not have to provide it. In some other senarios you need to create a new one it is depending on the key type.
"Are these limitations of EF at the moment or are they intentional constraints to prevent some other kind of catastrophe I am not seeing?"
Not that I know!
OK, so with a bit of bashing I have managed to bash EF into submission.
Here is what I did:
1. There is no way that I found to see data for a specific migration. It all must go into the common Configuration.Seed method.
2. To avoid duplicates I had to do 2 things.
For my enums, I wrote the following seed code:
foreach (var enumValue in Enum.GetValues(typeof(Access.Level)))
{
var id = (int)enumValue;
var val = enumValue.ToString();
if(!context.Access.Any(e => e.AccessId == id))
context.Access.Add(
new Access { AccessId = id, Name = val }
);
}
context.SaveChanges();
So basically, just checking if it exists and adding if not
3. In order for the above to work, you need to be able to insert Primary Key Values. Luckily for me this table will always have the same static data so I could deactivate the auto increment. To do that, the code looks like:
public class Access
{
public enum Level
{
None = 10,
Read = 20,
ReadWrite = 30
}
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AccessId { get; set; }
public string Name { get; set; }
}
Related
How to get a strongly typed Id...
public sealed class FileUploadId
{
public int Value { get; }
public FileUploadId(int value)
{
Value = value;
}
}
...which is used within my FileUpload class...
public class FileUpload : EntityBase, IAggregateRoot
{
private FileUpload() { /* Required by EF */ }
public FileUpload(string name, string relativePath, FileUploadType type, string contentType, long? size = null)
{
/* Guard clauses... */
Id = new FileUploadId(0);
/* Other properties... */
}
public FileUploadId Id { get; }
/* Other properties... */
}
...working with identity (int auto increment)?
I tried ValueGeneratedOnAdd()in my TypeConifiguration, but without success...
public class FileUploadTypeConfig : IEntityTypeConfiguration<FileUpload>
{
public void Configure(EntityTypeBuilder<FileUpload> builder)
{
builder.HasKey(x => x.Id);
builder.Property(x => x.Id).HasConversion(x => x.Value, x => new FileUploadId(x)).ValueGeneratedOnAdd();
/* Other configurations... */
}
}
I know there's another option with the HiLo algorithm. But I want to get it work with default int id increment. Is this possible in any way?
I managed to get strongly typed ids with auto incrementing ints working with .net6 and EFCore6 by:
Configuring .HasConversion()
Adding .ValueGeneratedOnAdd()
Adding .Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Ignore)
Making sure the strongly typed id is never null
Edit:
There is a pitfall with this approach.
It has to do with the change tracking. Since ef core 3.0 and this GitHub issue, when you are using auto incrementing for your key and the key value is not it's default, the entity is added in the 'Modified' state instead of the 'Added' state. This is a problem for the current solution as we never set null for the strongly typed ids. We would have to manually begin tracking the entity in the 'Added' state (with the DbContext Add method) and other types of automatic tracking will not work (for example adding the entity in a navigational property collection for a one-to-many relationship). Official support for this is being tracked with this GitHub issue.
Honestly, using strongly-typed variables with SQL in EF is a true pain. The best explanation I have seen can be found in this blog post by Andrew Lock.
The core of the problem? Using strongly-typed values can result in conducting the filtering of a query (the WHERE ID = value clause) on the client, requiring the process to retrieve all of the records from the DB to perform the selection locally.
The core to the solution? Using a ValueConverter to cast the strongly-typed value to the correct SQL Server value.
Details are extensive. See the referenced article for detail on how to do it.
I'd advise against letting EF deal with the strongly typed Id. It does not handle it well and there will be cases where it will try to filter things in memory.
But you can have both properties, one for EF queries, and one for everything else.
class FileUpload
{
public int InternalId { get; private set; }
public FileUploadId Id
{
get { return new FileUploadId(InternalId); }
set { InternalId = value.Value; }
}
}
It's leaky, but it works.
I am using Entity Framework 6 DB First with SQL Server tables that each have a uniqueidentifier primary key. The tables have a default on the primary key column that sets it to newid(). I have accordingly updated my .edmx to set the StoreGeneratedPattern for these columns to Identity. So I can create new records, add them to my database context and the IDs are generated automatically. But now I need to save a new record with a specific ID. I've read this article which says you have to execute SET IDENTITY_INSERT dbo.[TableName] ON before saving when using an int identity PK column. Since mine are Guid and not actually an identity column, that's essentially already done. Yet even though in my C# I set the ID to the correct Guid, that value is not even passed as a parameter to the generated SQL insert and a new ID is generated by the SQL Server for the primary key.
I need to be able to both :
insert a new record and let the ID be automatically created for it,
insert a new record with a specified ID.
I have # 1. How can I insert a new record with a specific primary key?
Edit:
Save code excerpt (Note accountMemberSpec.ID is the specific Guid value I want to be the AccountMember's primary key):
IDbContextScopeFactory dbContextFactory = new DbContextScopeFactory();
using (var dbContextScope = dbContextFactory.Create())
{
//Save the Account
dbAccountMember = CRMEntity<AccountMember>.GetOrCreate(accountMemberSpec.ID);
dbAccountMember.fk_AccountID = accountMemberSpec.AccountID;
dbAccountMember.fk_PersonID = accountMemberSpec.PersonID;
dbContextScope.SaveChanges();
}
--
public class CRMEntity<T> where T : CrmEntityBase, IGuid
{
public static T GetOrCreate(Guid id)
{
T entity;
CRMEntityAccess<T> entities = new CRMEntityAccess<T>();
//Get or create the address
entity = (id == Guid.Empty) ? null : entities.GetSingle(id, null);
if (entity == null)
{
entity = Activator.CreateInstance<T>();
entity.ID = id;
entity = new CRMEntityAccess<T>().AddNew(entity);
}
return entity;
}
}
--
public class CRMEntityAccess<T> where T : class, ICrmEntity, IGuid
{
public virtual T AddNew(T newEntity)
{
return DBContext.Set<T>().Add(newEntity);
}
}
And here is the logged, generated SQL for this:
DECLARE #generated_keys table([pk_AccountMemberID] uniqueidentifier)
INSERT[dbo].[AccountMembers]
([fk_PersonID], [fk_AccountID], [fk_FacilityID])
OUTPUT inserted.[pk_AccountMemberID] INTO #generated_keys
VALUES(#0, #1, #2)
SELECT t.[pk_AccountMemberID], t.[CreatedDate], t.[LastModifiedDate]
FROM #generated_keys AS g JOIN [dbo].[AccountMembers] AS t ON g.[pk_AccountMemberID] = t.[pk_AccountMemberID]
WHERE ##ROWCOUNT > 0
-- #0: '731e680c-1fd6-42d7-9fb3-ff5d36ab80d0' (Type = Guid)
-- #1: 'f6626a39-5de0-48e2-a82a-3cc31c59d4b9' (Type = Guid)
-- #2: '127527c0-42a6-40ee-aebd-88355f7ffa05' (Type = Guid)
A solution could be to override DbContext SaveChanges. In this function find all added entries of the DbSets of which you want to specify the Id.
If the Id is not specified yet, specify one, if it is already specified: use the specified one.
Override all SaveChanges:
public override void SaveChanges()
{
GenerateIds();
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync()
{
GenerateIds();
return await base.SaveChangesAsync();
}
public override async Task<int> SaveChangesAsync(System.Threading CancellationToken token)
{
GenerateIds();
return await base.SaveChangesAsync(token);
}
GenerateIds should check if you already provided an Id for your added entries or not. If not, provide one.
I'm not sure if all DbSets should have the requested feature, or only some. To check whether the primary key is already filled, I need to know the identifier of the primary key.
I see in your class CRMEntity that you know that every T has an Id, this is because this Id is in CRMEntityBase, or in IGuid, let's assume it is in IGuid. If it is in CRMEntityBase change the following accordingly.
The following is in small steps; if desired you can create one big LINQ.
private void GenerateIds()
{
// fetch all added entries that have IGuid
IEnumerable<IGuid> addedIGuidEntries = this.ChangeTracker.Entries()
.Where(entry => entry.State == EntityState.Added)
.OfType<IGuid>()
// if IGuid.Id is default: generate a new Id, otherwise leave it
foreach (IGuid entry in addedIGuidEntries)
{
if (entry.Id == default(Guid)
// no value provided yet: provide it now
entry.Id = GenerateGuidId() // TODO: implement function
// else: Id already provided; use this Id.
}
}
That is all. Because all your IGuid objects now have a non-default ID (either pre-defined, or generated inside GenerateId) EF will use that Id.
Addition: HasDatabaseGeneratedOption
As xr280xr pointed out in one of the comments, I forgot that you have to tell entity framework that entity framework should not (always) generate an Id.
As an example I do the same with a simple database with Blogs and Posts. A one-to-many relation between Blogs and Posts. To show that the idea does not depend on GUID, the primary key is a long.
// If an entity class is derived from ISelfGeneratedId,
// entity framework should not generate Ids
interface ISelfGeneratedId
{
public long Id {get; set;}
}
class Blog : ISelfGeneratedId
{
public long Id {get; set;} // Primary key
// a Blog has zero or more Posts:
public virtual ICollection><Post> Posts {get; set;}
public string Author {get; set;}
...
}
class Post : ISelfGeneratedId
{
public long Id {get; set;} // Primary Key
// every Post belongs to one Blog:
public long BlogId {get; set;}
public virtual Blog Blog {get; set;}
public string Title {get; set;}
...
}
Now the interesting part: The fluent API that informs Entity Framework that the values for primary keys are already generated.
I prefer fluent API avobe the use of attributes, because the use of fluent API allows me to re-use the entity classes in different database models, simply by rewriting Dbcontext.OnModelCreating.
For example, in some databases I like my DateTime objects a DateTime2, and in some I need them to be simple DateTime. Sometimes I want self generated Ids, sometimes (like in unit tests) I don't need that.
class MyDbContext : Dbcontext
{
public DbSet<Blog> Blogs {get; set;}
public DbSet<Post> Posts {get; set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Entity framework should not generate Id for Blogs:
modelBuilder.Entity<Blog>()
.Property(blog => blog.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
// Entity framework should not generate Id for Posts:
modelBuilder.Entity<Blog>()
.Property(blog => blog.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
... // other fluent API
}
SaveChanges is similar as I wrote above. GenerateIds is slightly different. In this example I have not the problem that sometimes the Id is already filled. Every added element that implements ISelfGeneratedId should generate an Id
private void GenerateIds()
{
// fetch all added entries that implement ISelfGeneratedId
var addedIdEntries = this.ChangeTracker.Entries()
.Where(entry => entry.State == EntityState.Added)
.OfType<ISelfGeneratedId>()
foreach (ISelfGeneratedId entry in addedIdEntries)
{
entry.Id = this.GenerateId() ;// TODO: implement function
// now you see why I need the interface:
// I need to know the primary key
}
}
For those who are looking for a neat Id generator: I often use the same generator as Twitter uses, one that can handle several servers, without the problem that everyone can guess from the primary key how many items are added.
It's in Nuget IdGen package
I see 2 challenges:
Making your Id field an identity with auto generated value will prevent you from specifying your own GUID.
Removing the auto generated option may create duplicate key exceptions if the user forgets to explicitly create a new id.
Simplest solution:
Remove auto generated value
Ensure Id is a PK and is required
Generate a new Guid for your Id in the default constructor of your models.
Example Model
public class Person
{
public Person()
{
this.Id = Guid.NewGuid();
}
public Guid Id { get; set; }
}
Usage
// "Auto id"
var person1 = new Person();
// Manual
var person2 = new Person
{
Id = new Guid("5d7aead1-e8de-4099-a035-4d17abb794b7")
}
This will satisfy both of your needs while keeping the db safe. The only down side of this is you have to do this for all models.
If you go with this approach, I'd rather see a factory method on the model which will give me the object with default values (Id populated) and eliminate the default constructor. IMHO, hiding default value setters in the default constructor is never a good thing. I'd rather have my factory method do that for me and know that the new object is populated with default values (with intention).
public class Person
{
public Guid Id { get; set; }
public static Person Create()
{
return new Person { Id = Guid.NewGuid() };
}
}
Usage
// New person with default values (new Id)
var person1 = Person.Create();
// Empty Guid Id
var person2 = new Person();
// Manually populated Id
var person3 = new Person { Id = Guid.NewGuid() };
I don't think there is a real answer for this one...
As said here How can I force entity framework to insert identity columns? you can enable the mode #2, but it'll break #1.
using (var dataContext = new DataModelContainer())
using (var transaction = dataContext.Database.BeginTransaction())
{
var user = new User()
{
ID = id,
Name = "John"
};
dataContext.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[User] ON");
dataContext.User.Add(user);
dataContext.SaveChanges();
dataContext.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[User] OFF");
transaction.Commit();
}
you should change value of StoreGeneratedPattern property of identity column from Identity to None in model designer.
Note, changing of StoreGeneratedPattern to None will fail inserting of object without specified id
As you can see, you're no longer able to insert without setting by yourself an ID.
But, if you look on the bright side : Guid.NewGuid() will allow you to make a new GUID without the DB generation function.
The solution is: write your own insert query. I've put together a quick project to test this, so the example has nothing to do with your domain, but you'll ge the ideea.
using (var ctx = new Model())
{
var ent = new MyEntity
{
Id = Guid.Empty,
Name = "Test"
};
try
{
var result = ctx.Database.ExecuteSqlCommand("INSERT INTO MyEntities (Id, Name) VALUES ( #p0, #p1 )", ent.Id, ent.Name);
}
catch (SqlException e)
{
Console.WriteLine("id already exists");
}
}
The ExecuteSqlCommand returns "rows affected" (in this case 1), or throws exception for duplicate key.
I'm using Asp.net web api 2 + entity framework 6.
Basically I have 2 models:
public class MyOrderModel
{
public int Id { get; set; }
public string OrderNumber { get; set;}
public string AuthCode { get; set; }
[Required]
public List<MyOrderDetailModel> Details { get; set; }
}
public class MyOrderDetailModel
{
public int Id { get; set; }
public decimal Amount{ get; set;}
}
After ran the Package Manager Console command Enable-Migration, in Configuration.Seed(WaynyCloudTest.Models.ApplicationDbContext context), I was trying to add some pre-loaded data:
context.MyOrderModels.AddOrUpdate(
s => s.OrderNumber,
new MyOrderModel
{
OrderNumber = "0001",
AuthCode = "ABCDE",
Details = new List<MyOrderDetailModel>()
{
new MyOrderDetailModel()
{
Amount = 5.67M
}
}
};
After the first(initial) Update-database command, everything is fine and I can see above data persisted to database 2 tables.
Later, I want to update the AuthCode property value from ABCDE to ABCDEXXX,
the only change is the value assignment:
context.MyOrderModels.AddOrUpdate(
s => s.OrderNumber,
new MyOrderModel
{
OrderNumber = "0001",
// THE ONLY CHANGE!
AuthCode = "ABCDEXXX",
Details = new List<MyOrderDetailModel>()
{
new MyOrderDetailModel()
{
Amount = 5.67M
}
}
};
I would expect the EntityFramework will find the target data row in database by searching condition on OrderNumber and then update the AuthCode, but now I always got this exception in Seed function:
Entity Validation Failed - errors follow:
MyTest.Models.MyOrderModel failed validation
Details : The Details field is required.
Obviously the value was supplied for field Details, so what I've missed?
The problem is with the Id field of PostPayQRCodeFuelOrderModel. In your environment, the database uses this field as an identity (primary key) field and wants to generate the value itself.
In your case, there is an easy workaround:
context.MyOrderModel.AddOrUpdate(
p => p.OrderNumber,
new PostPayQRCodeFuelOrderModel
{
OrderNumber = "00001",
Details = new List<MyOrderDetailModel>()
{
new MyOrderDetailModel()
{
Amount = 5.67M
}
}
}
);
Assumably, OrderNumber is unique, so this should work fine. In addition, running the seed again will not duplicate this data.
UPDATE:
It is possible to keep the original MyOrderModel.AddOrUpdate(), i.e., give the Id explicitly:
First, you need to prevent the auto-generation of the primary key value for MyOrderModel:
public class MyOrderModel
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
Then, you need to re-create the MyOrderModel table. NOTE: the usual approach of adding a migration just to modify the Id field will not work, you need to re-create the table.
Running the seed multiple times works, i.e., there are no duplicates (just checked it myself).
UPDATE 2:
I don't have a full explanation, why your code is not working, but with the code below it should be possible for you to construct the creation and the update of the database objects as you wish.
This code can be run in Seed(), multiple times without duplication. The update of AuthCode is, of course, artificial, but my point was to separate the creation and the update (just in case these need to be separated in the final implementation).
The whole project is available in https://github.com/masa67/AspNetNg, branch SO34252506-1.
Here's the code:
var mom = context.MyOrderModel.Where(m => m.OrderNumber == "00001").FirstOrDefault();
if (mom == null)
{
mom = new PostPayQRCodeFuelOrderModel
{
OrderNumber = "00001",
AuthCode = "ABCDE",
Details = new List<MyOrderDetailModel>() {
new MyOrderDetailModel
{
Amount = 5.67M
}
}
};
context.MyOrderModel.AddOrUpdate(p => p.OrderNumber, mom);
context.SaveChanges();
}
mom.AuthCode = "ABCDEXXX";
context.SaveChanges();
UPDATE 3:
A couple of suggestions, if this is still not working:
Consider dropping the Required constraint for Details and handle the consistency programmatically. I would not use this constraint on navigation properties anyways (but I am only familiar with EF to the extent of how we are using it in our current project at work, so there might be different views on this).
Test your code by re-creating the database first.
Test my Solution (link above). It is working for me, so there might be a difference in configuration somewhere.
Since Details is not virtual, EF is not using lazy loading. I was expecting this to cause problems, as Details becomes null when the object is read from the database, but that was not the case in my environment. You might try eager loading, but I doubt if this has any impact:
Eager loading:
var mom = context.MyOrderModel.Where(m => m.OrderNumber == "00001").Include(m => m.Details).FirstOrDefault();
UPDATE 4:
If this is still not working, then delete the database, but re-create the migrations in addition:
Delete the existing migrations.
Do not let EF make the assumption that it knows the state of your database, but force it to create the migrations from scratch (see other SO questions for advice). However, BEFORE doing that, please do notice that this operation will most probably overwrite the Seed() function as well, so take a copy of that file before the operation.
I have a test application where I would like to drop and recreate database every time I run the application. This is my context class:
public class MySolutionContext : DbContext
{
public MySolutionContext()
: base("MySolution")
{
Database.SetInitializer<MySolutionContext>(new DropCreateDatabaseAlways());
}
public DbSet<Order> Orders { get; set; }
public DbSet<OrderItem> OrderITems { get; set; }
public void Seed(MySolutionContext context)
{
var order1 = new Order
{
Archive = false,
CompletionDate = DateTime.Now,
Person = "Bartosz"
};
var order2 = new Order
{
Archive = false,
CompletionDate = DateTime.Now,
Person = "Anna"
};
context.Orders.Add(order1);
context.Orders.Add(order2);
context.SaveChanges();
}
public class DropCreateDatabaseAlways : DropCreateDatabaseAlways<MySolutionContext>
{
protected override void Seed(MySolutionContext context)
{
context.Seed(context);
base.Seed(context);
}
}
}
When I run the application for the first time, Seed method is executed and database gets created. However, when I stop and rerun the application, Seed method is not firing at all and previously created database is being used. Why does it happen? What am I missing in my code?
The problem here is that migration is activated in your current project. Related to this article (http://entityframework.codeplex.com/workitem/1689), it's not possible to use migration AND "DropCreateDatabaseAlways" as the initializer at the same time.
If you want to use migration and "DropCreateDatabaseAlways" (which is completely useless, except from testing maybe), you'll have to write a own Init() method, which deletes and creates your database at every application start.
Database.Delete();
Database.Create();
But if you deactivate migration, you can use the initalizer "DropCreateDatabaseAlways".
If the problem is still there without migration here are some hints how to solve this kind of problem:
The Initializer is only executed if you are using the instance of the database or if the database doesn't exist. The seed method is part of your Initializer, so it doesn't get executed as well. To fix this problem you can "work" with the database by accessing part of the data like:
context.Set<Entity>.Count();
or any other method which works with the database.
Another solution is to force the database to call the Initializer:
Database.Initialize(true/false);
Hope this helps.
In a test project, I also needed to drop the database and recreate everything each time I start the tests.
Simply adding the following line was not enough:
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
I tweaked it with this :
Database.SetInitializer(new DropCreateDatabaseAlways<ApsContext>());
using (MyContext context = new MyContext())
{
context.Database.Delete();
}
It's important to know that, if migration is enabled, you need to actually call the database to create the schema. I simply call an entity trying to find something with Id = 0 (so it returns nothing).
In my OData service I have to create a custom primary key in the OnPreInsert event handler.
I know I can't use #event.Id to assign the key because it doesn't expose the setter property.
I used the reflection to set the value of this property as shown below:
public bool OnPreInsert(PreInsertEvent #event)
{
if(#event.Entity is MyEnity)
{
var myEntity = #event.Entity as MyEnity;
string newKey = GetCustomKey(...);
myEntity.myId = newKey;
var property = typeof(AbstractPreDatabaseOperationEvent).GetProperty("Id");
if (property != null)
{
property.SetValue(#event,newKey);
}
}
return false;
}
During the debug mode I can see that the value of #event.Id is initialized properly, however the key saved in the database is not the one I generated in the OnPreInsert event handler.
What am I doing wrong here?
Please, try to check this recent Q&A:
NHibernate IPreUpdateEventListener, IPreInsertEventListener not saving to DB
The point is, that as described here:
NHibernate IPreUpdateEventListener & IPreInsertEventListener
...Here comes the subtlety, however. We cannot just update the entity state. The reason for that is quite simple, the entity state was extracted from the entity and place in the entity state, any change that we make to the entity state would not be reflected in the entity itself. That may cause the database row and the entity instance to go out of sync, and make cause a whole bunch of really nasty problems that you wouldn’t know where to begin debugging.
You have to update both the entity and the entity state in these two event listeners (this is not necessarily the case in other listeners, by the way). Here is a simple example of using these event listeners...
I couldn't find some way to use the reflection to achieve what I described in my question above. I tried to use reflection because I didn't know about the Generators available in NHibernate (as I am new to NHibernate).
I have a table named sys_params which holds the next key values for different tables. My target was to fetch the next key for my table my_entity, assign it to the primary key of the new record, increment the next key value in the sys_params table and save the new record into the database.
To achieve this first I defined following classes.
public class NextIdGenerator : TableGenerator
{
}
public class NextIdGeneratorDef : IGeneratorDef
{
public string Class
{
get { return typeof(NextIdGenerator).AssemblyQualifiedName; }
}
public object Params
{
get { return null; }
}
public Type DefaultReturnType
{
get { return typeof(int); }
}
public bool SupportedAsCollectionElementId
{
get { return true; }
}
}
And then in my mapping class I defined the generator like below:
public class MyEnityMap : ClassMapping<MyEnity>
{
public MyEnityMap()
{
Table("my_entity");
Id(p => p.myId,
m=>{
m.Column("my_id");
m.Generator(new NextIdGeneratorDef(), g =>g.Params( new
{
table = "sys_params",
column = "param_nextvalue",
where = "table_name = 'my_entity'"
}));
});
.......
}
}
Hope this will help someone else. Improvements to this solution are highly appreciated.