I set up a Generic repository using this code for update
private void AttachIfNot(TEntity entityToActive)
{
if (_dbContext.Entry(entityToActive).State == EntityState.Detached)
{
_dbSet.Attach(entityToActive);
}
}
private void UpdateEntity(TEntity entityToUpdate)
{
AttachIfNot(entityToUpdate);
_dbContext.Entry(entityToUpdate).State = EntityState.Modified;
}
It just attach the entity and set the modified state to save.
But when I use efocre ownsone to map a value object,the update entity function is not working.
I found out that it only works when I set Valueobject to modified too.
_dbContext.Entry(entityToUpdate).State = EntityState.Modified;
_dbContext.Entry(entityToUpdate.Valueobject).State = EntityState.Modified;
But It is hard for me to specify all the value objects in a Generic Repository.
This is code also has problems with one to many or other relations.
The working way is like this:
Classroom classroom = new Classroom
{
Id = 1,
Name = "b",
Students = new List<Student>
{
new Student()
{
Name = "aa",
Id = 2
}
}
};
if (_defaultDbContext.Entry(classroom).State == EntityState.Detached)
{
_defaultDbContext.Classrooms.Attach(classroom);
foreach(var stu in classroom.Students)
{
_defaultDbContext.Students.Attach(stu);
}
}
_defaultDbContext.Entry(classroom).State = EntityState.Modified;
foreach (var stu in classroom.Students)
{
_defaultDbContext.Entry(stu).State = EntityState.Modified;
}
_defaultDbContext.SaveChanges();
I found out one way is get the entity form repo then update it using automapper:
targetEntity = repo.GetById(entityId);
automapper.map(souceEntity,targetEntity);
//or
automapper.map(souceDto,targetEntity);
_dbContext.Save();
The entity comes by query, so the change will be tracked.
But I have to configure the automapper with this entity map when I want to change entity
CreateMap<EntityType, EntityType>();
I think it's not the best solution. Is there a bettere way?
DbContext.Update would be fine to fix this problem.
see:
https://www.learnentityframeworkcore.com/dbcontext/change-tracker
There are some base data specific for Dev/Test/Prod environments.
We are using now Entity Framework Migrations for all the environments but do not know how to specify migrations for particular environments in a way that we specify a migration to be executed only on Dev/Test/Prod.
This could be accomplished in Fluent Migrator with Tag attributes. But what about Entity Framework?
When you say 'base data' I assume you mean Seeding each environment. Migrations provide a seeding mechanism for this. Within the Seed() you can differentiate environments as you would in regular code. We like to use Web.config transform setting:
protected override void Seed(BookService.Models.BookServiceContext context)
{
if (ConfigurationManager.AppSettings["DeployEnvironment"] == "UAT")
{
context.Authors.AddOrUpdate(x => x.Id,
new Author() { Id = 1, Name = "Test User" },
);
}
else if (ConfigurationManager.AppSettings["DeployEnvironment"] == "PROD")
{
context.Authors.AddOrUpdate(x => x.Id,
new Author() { Id = 1, Name = "Production User" },
);
}
}
Another option is compiler directives:
protected override void Seed(BookService.Models.BookServiceContext context)
{
#if DEBUG
context.Authors.AddOrUpdate(x => x.Id,
new Author() { Id = 1, Name = "Test User" },
);
#else
context.Authors.AddOrUpdate(x => x.Id,
new Author() { Id = 1, Name = "Production User" },
);
#endif
}
As far as applying the migrations themselves, we follow this process in development. When we are ready to deploy to UAT, we can just point the connection string at UAT and run migrations or we can create a script to update the database. In PROD we do this.
I have a project that I created and enabled Migrations on. It created it with 4.3 so I think it is the latest. I have some code in the constructor of the context that executes the update (see code below) and that seems to work everytime I add something like a nullable string column or do something that does not change the database in non consistent manner. My scenario is I change my model, and when I watch sql trace, it does the alter columns for me automatically.
My question is I want to do the "up" and "down" methods but am confused on when they run. That is say I'm on version 1 now, I put some code in my "up" method to add a column, then later when I want to go to version 3, how does it know which "up" method to call?
Confused. -Peter
namespace MigrationsAutomaticDemo.Migrations
{
using System.Data.Entity.Migrations;
public partial class AddBlogRating : DbMigration
{
public override void Up()
{
AddColumn("Blogs", "Rating", c => c.Int(nullable: false, defaultValue: 3));
}
public override void Down()
{
DropColumn("Blogs", "Rating");
}
}
}
,
public SiteDB()
{
UpdateDatabase();
}
// http://joshmouch.wordpress.com/2012/04/22/entity-framework-code-first-migrations-executing-migrations-using-code-not-powershell-commands/
public static int IsMigrating = 0;
private static void UpdateDatabase()
{
if (0 == Interlocked.Exchange(ref IsMigrating, 1))
{
// Manually creating configuration:
var migratorConfig = new DbMigrationsConfiguration<SiteDB>();
migratorConfig.AutomaticMigrationsEnabled = true;
// Using configuration defined in project:
//var migratorConfig = new DbMigrationsConfiguration();
// 3
//var dbMigrator = new DbMigrator(new Settings());
var dbMigrator = new DbMigrator(migratorConfig);
dbMigrator.Update();
Interlocked.Exchange(ref IsMigrating, 0);
}
}
If you enable the automatic migration in the migration configuration, then you don't need to specify the target migration. The migrator will automatically scaffold the changes based on the snapshot of current context and target database.
var migratorConfig = new DbMigrationsConfiguration<SiteDB>();
migratorConfig.AutomaticMigrationsEnabled = true;
In your case, you want to upgrade your database by using specific migration. All you need to do is mentioning explicitly which migration to upgrade.
Configuration configuration = new Configuration();
DbMigrator migrator = new DbMigrator(configuration);
migrator.Update("201204250656061_AddBlogRatingVersion2");
migrator.Update("201204250656061_AddBlogRatingVersion3");
migrator.Update("201204250656061_AddBlogRatingVersionX");
I use entity framework migration (in Automatic migration mode). Everything is okay, but I have one question:
How should I seed data when I have many-to-many relationships?
For example, I have two model classes:
public class Parcel
{
public int Id { get; set; }
public string Description { get; set; }
public double Weight { get; set; }
public virtual ICollection<BuyingItem> Items { get; set; }
}
public class BuyingItem
{
public int Id { get; set; }
public decimal Price { get; set; }
public virtual ICollection<Parcel> Parcels { get; set; }
}
I understand how to seed simple data (for PaymentSystem class) and one-to-many relationships, but what code should I write in the Seed method to generate some instances of Parcel and BuyingItem? I mean using DbContext.AddOrUpdate(), because I don't want to duplicate data every time I run Update-Database.
protected override void Seed(ParcelDbContext context)
{
context.AddOrUpdate(ps => ps.Id,
new PaymentSystem { Id = 1, Name = "Visa" },
new PaymentSystem { Id = 2, Name = "PayPal" },
new PaymentSystem { Id = 3, Name = "Cash" });
}
protected override void Seed(Context context)
{
base.Seed(context);
// This will create Parcel, BuyingItems and relations only once
context.AddOrUpdate(new Parcel()
{
Id = 1,
Description = "Test",
Items = new List<BuyingItem>
{
new BuyingItem() { Id = 1, Price = 10M },
new BuyingItem() { Id = 2, Price = 20M }
}
});
context.SaveChanges();
}
This code creates Parcel, BuyingItems and their relationship, but if I need the same BuyingItem in another Parcel (they have a many-to-many relationship) and I repeat this code for the second parcel - it will duplicate BuyingItems in the database (though I set the same Ids).
Example:
protected override void Seed(Context context)
{
base.Seed(context);
context.AddOrUpdate(new Parcel()
{
Id = 1,
Description = "Test",
Items = new List<BuyingItem>
{
new BuyingItem() { Id = 1, Price = 10M },
new BuyingItem() { Id = 2, Price = 20M }
}
});
context.AddOrUpdate(new Parcel()
{
Id = 2,
Description = "Test2",
Items = new List<BuyingItem>
{
new BuyingItem() { Id = 1, Price = 10M },
new BuyingItem() { Id = 2, Price = 20M }
}
});
context.SaveChanges();
}
How can I add the same BuyingItem in different Parcels?
Updated Answer
Make sure you read "Using AddOrUpdate Properly" section below for a complete answer.
First of all, let's create a composite primary key (consisting of parcel id and item id) to eliminate duplicates. Add the following method in the DbContext class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Parcel>()
.HasMany(p => p.Items)
.WithMany(r => r.Parcels)
.Map(m =>
{
m.ToTable("ParcelItems");
m.MapLeftKey("ParcelId");
m.MapRightKey("BuyingItemId");
});
}
Then implement the Seed method like so:
protected override void Seed(Context context)
{
context.Parcels.AddOrUpdate(p => p.Id,
new Parcel { Id = 1, Description = "Parcel 1", Weight = 1.0 },
new Parcel { Id = 2, Description = "Parcel 2", Weight = 2.0 },
new Parcel { Id = 3, Description = "Parcel 3", Weight = 3.0 });
context.BuyingItems.AddOrUpdate(b => b.Id,
new BuyingItem { Id = 1, Price = 10m },
new BuyingItem { Id = 2, Price = 20m });
// Make sure that the above entities are created in the database
context.SaveChanges();
var p1 = context.Parcels.Find(1);
// Uncomment the following line if you are not using lazy loading.
//context.Entry(p1).Collection(p => p.Items).Load();
var p2 = context.Parcels.Find(2);
// Uncomment the following line if you are not using lazy loading.
//context.Entry(p2).Collection(p => p.Items).Load();
var i1 = context.BuyingItems.Find(1);
var i2 = context.BuyingItems.Find(2);
p1.Items.Add(i1);
p1.Items.Add(i2);
// Uncomment to test whether this fails or not, it will work, and guess what, no duplicates!!!
//p1.Items.Add(i1);
//p1.Items.Add(i1);
//p1.Items.Add(i1);
//p1.Items.Add(i1);
//p1.Items.Add(i1);
p2.Items.Add(i1);
p2.Items.Add(i2);
// The following WON'T work, since we're assigning a new collection, it'll try to insert duplicate values only to fail.
//p1.Items = new[] { i1, i2 };
//p2.Items = new[] { i2 };
}
Here we make sure that the entities are created/updated in the database by calling context.SaveChanges() within the Seed method. After that, we retrieve the required parcel and buying item objects using context. Thereafter we use the Items property (which is a collection) on the Parcel objects to add BuyingItem as we please.
Please note, no matter how many times we call the Add method using the same item object, we don't end up with primary key violation. That is because EF internally uses HashSet<T> to manage the Parcel.Items collection. A HashSet<Item>, by its nature, won't let you add duplicate items.
Moreover, if you somehow manage to circumvent this EF behavior as I have demonstrated in the example, our primary key won't let the duplicates in.
Using AddOrUpdate Properly
When you use a typical Id field (int, identity) as an identifier expression with AddOrUpdate method, you should exercise caution.
In this instance, if you manually delete one of the rows from the Parcel table, you'll end up creating duplicates every time you run the Seed method (even with the updated Seed method I have provided above).
Consider the following code,
context.Parcels.AddOrUpdate(p => p.Id,
new Parcel { Id = 1, Description = "Parcel 1", Weight = 1.0 },
new Parcel { Id = 2, Description = "Parcel 1", Weight = 1.0 },
new Parcel { Id = 3, Description = "Parcel 1", Weight = 1.0 }
);
Technically (considering the surrogate Id here), the rows are unique, but from the end-user point of view, they are duplicates.
The true solution here is to use the Description field as an identifier expression. Add this attribute to the Description property of the Parcel class to make it unique: [MaxLength(255), Index(IsUnique=true)]. Update the following snippets in the Seed method:
context.Parcels.AddOrUpdate(p => p.Description,
new Parcel { Description = "Parcel 1", Weight = 1.0 },
new Parcel { Description = "Parcel 2", Weight = 2.0 },
new Parcel { Description = "Parcel 3", Weight = 3.0 });
// Make sure that the above entities are created in the database
context.SaveChanges();
var p1 = context.Parcels.Single(p => p.Description == "Parcel 1");
Note, I'm not using the Id field as EF is going to ignore it while inserting rows. And we are using Description to retrieve the correct parcel object, no matter what Id value is.
Old Answer
I would like to add a few observations here:
Using Id is probably not going to do any good if the Id column is a database generated field. EF is going to ignore it.
This method seems to be working fine when the Seed method is run once. It won't create any duplicates, however, if you run it for a second time (and most of us have to do that often), it may inject duplicates. In my case it did.
This tutorial by Tom Dykstra showed me the right way of doing it. It works because we don't take anything for granted. We don't specify IDs. Instead, we query the context by known unique keys and add related entities (which again are acquired by querying context) to them. It worked like a charm in my case.
You must fill many-to-many relation in the same way as you build many-to-many relation in any EF code:
protected override void Seed(Context context)
{
base.Seed(context);
// This will create Parcel, BuyingItems and relations only once
context.AddOrUpdate(new Parcel()
{
Id = 1,
Description = "Test",
Items = new List<BuyingItem>
{
new BuyingItem() { Id = 1, Price = 10M },
new BuyingItem() { Id = 2, Price = 20M }
}
});
context.SaveChanges();
}
Specifying Id which will be used in database is crucial otherwise each Update-Database will create new records.
AddOrUpdate doesn't support changing relations in any way so you cannot use it to add or remove relations in next migration. If you need it you must manually remove relation by loading Parcel with BuyingItems and calling Remove or Add on navigation collection to break or add new relation.
Ok. I understand how I should be in that situation:
protected override void Seed(Context context)
{
base.Seed(context);
var buyingItems = new[]
{
new BuyingItem
{
Id = 1,
Price = 10m
},
new BuyingItem
{
Id = 2,
Price = 20m,
}
}
context.AddOrUpdate(new Parcel()
{
Id = 1,
Description = "Test",
Items = new List<BuyingItem>
{
buyingItems[0],
buyingItems[1]
}
},
new Parcel()
{
Id = 2,
Description = "Test2",
Items = new List<BuyingItem>
{
buyingItems[0],
buyingItems[1]
}
});
context.SaveChanges();
}
There are no duplicates in database.
Thank you, Ladislav, you gave me a right vector to find a solution for my task.
The controller above has a standard edit ActionResult. I simply find rows in a database by ID and update it. Before db.SaveChanges() there is log.Save() static function that saves all changes in model to separate tables in the database.It simply check old and new values from ChangeTracker.
The problem is, i want use log.Save() after db.SaveChanges(), not before, to be sure that data was really saved.
But after, in the ChangeTracker there aren't any changes so log.Save() doesn't have anything to save.
Controller:
[HttpPost]
public ActionResult edit(int id, MyModel model)
{
var hihi = db.MyModel.First(s => s.ID == model.ID);
hihi.col1 = model.col1;
hihi.col2 = model.col2;
...
log.Save(Log.ChangeType.Edit, db, id);
^ Here i save changes to log.
db.SaveChanges();
return RedirectToAction("Index");
}
Log Class:
public void Save(ChangeType changeType, DBContext parentContext, int id)
{
DBContext db = new DBContext();
foreach (System.Data.Entity.Infrastructure.DbEntityEntry ee in parentContext.ChangeTracker.Entries())
{
foreach (string column in ee.OriginalValues.PropertyNames)
{
string oldValue = ee.OriginalValues[column].ToString();
string newValue = ee.CurrentValues[column].ToString();
if (oldValue != newValue)
{
var model = new LogModel
{
Log_Time = DateTime.Now,
Log_Operator = User.Ope_ID,
Log_Table = ee.Entity.ToString().Replace("xxx.Models.", ""),
Log_Key = id,
Log_Column = column,
Log_Type = (int)changeType,
Log_OldValue = oldValue,
Log_NewValue = newValue
};
var log = db.Log.Add(model);
db.SaveChanges();
}
}
}
}
public enum ChangeType
{
Create = 1,
Delete = 2,
Edit = 3
}
... or maybe someone has another way to save all changes in a database to another table on all controller actions, so after the project release I can see what users do.
PS. I don't what user triggers.
SaveChanges in EF4 is virtual, so you can override it, add custom logging etc.
Why don't you use try{} catch{} within Log Class and change the return parameter from 'void' to 'bool'. This would return true if the db.SaveChanges() succeeds. Then within "ActionResult edit" use bool result = log.Save(Log.ChangeType.Edit, db, id); to retrieve if the log saved the changes, then use a simple if-sentence to validate if you can save all changes to db or not.