my supermarket model contains a StockItem class and an Alert class which contains a StockItem field:
public class StockItem
{
public int ID { get; set; }
public string Name { get; set; }
public int CurrentQuantity { get; set; }
public int MinQuantity { get; set; }
}
public class Alert
{
public int ID { get; set; }
public int Message{ get; set; }
public virtual StockItem StockItem { get; set; }
}
I Have a function that fetches all StockItems with one DbContext:
using (var db = new MyDbContext())
{
return db.StockItems.ToList();
}
And another function that process these items, and adding new Alerts in a another DbContext:
foreach (var item in items)
{
if (item.CurrentQuantity < item.MinQuantity)
{
using (var db = new MyDbContext())
{
db.Alerts.Add(new Alert(){StockItem = item, Message = "Low Quantity"});
db.SaveChanges();
}
}
}
The problem is: When an Alert is Saved, a new Stock Item (with a different id) is added to the database, although it is already there!
any solutions?
I think you should Attach the stockitem first.
Try this:
foreach (var item in items)
{
if (item.CurrentQuantity < item.MinQuantity)
{
using (var db = new MyDbContext())
{
db.StockItems.Attach(item);
db.Alerts.Add(new Alert {StockItem = item, Message = "Low Quantity"});
db.SaveChanges();
}
}
}
using (var db = new MyDbContext())
{
var items = db.StockItems.ToList();
foreach (var item in items)
{
if (item.CurrentQuantity < item.MinQuantity)
{
db.Alerts.Add(new Alert {StockItem = item,
Message = "Low Quantity"});
db.SaveChanges();
}
}
}
In this case you dont need to do attach. EF can only track changes in its own life cycle, in your first case when you do,
using (var db = new MyDbContext())
{
return db.StockItems.ToList();
}
You are disposing MyDbContext, so EF makes all stock items as independent (detached items), and when you add them to different context, context assumes that it is a new item and it will insert the one.
The best way will be to keep Context alive throughout the changes you want to make. Also note, keeping context alive for longer time does not mean you will keep database connection open all the time. EF will open and close database connection automatically only when you are executing query and you are calling save changes.
Otherwise you have to attach as Ben suggested.
Related
I use Code First with Entity Framework.
I have a class with virtual property to another class (lazy loading).
public class Order{
public int Id { get; set; }
public virtual ICollection<OrderItem> OrderItems { get; set; }
}
If I get Order from database and do not include OrderItem, then close DbContext, is it possible to load them later? If yes, how?
eg.
private static Order GetFirstOrder(Func<Order, bool> predicate)
{
using (var db = new MyContext())
{
return db.Orders.First(predicate);
}
}
private static void DoSomething()
{
var order = GetFirstOrder(a => a.Id == 1);
//do something with OrderItems later?
}
Lazy loading will be available as long as the context of the query is alive.
If it's closed, then it's over, you'll have to re-query (some GetOrderItemsByOrder query), or re-attach. Do something "manually", in any case.
You should Include the collection name.
var myItemWithCollection = (from s in db.tableName.Include("ListName").Where(s => s.Id.Equals(ItemId)) select s).FirstOrDefault();
I'm posting the exact entity:
public class Person : ContactableEntity
{
public Plan Plan { get; set; }
public int Record { get; set; }
public int PersonTypeValue { get; set; }
}
I'm using the following code to update in a disconected context fashion:
public void Update(DbSet MySet, object Obj)
{
MySet.Attach(Obj);
var Entry = this.Entry(Obj);
Entry.State = EntityState.Modified;
this.SaveChanges();
}
This is a method exposed by my dbContext
Called this way:
PersistentManager.Update(PersistentManager.Personas,UpdatedPersona);
The problem is, EF will update any property but the referenced Plan object.
Can someone tell me where is the mistake?
In advance : the entity reaches the point of update with all the properties correctly set.
EF just fails to update the FK in the Database (no exception though)
Update:
tried solving the problem like this but it didn't work:
PersistentMgr.Contacts.Attach(Obj);
PersistentMgr.Entry(Obj).State = EntityState.Modified;
PersistentMgr.Entry(Obj.Plan).State = EntityState.Modified;
PersistentMgr.SaveChanges();
You need...
this.Entry(person).State = EntityState.Modified;
this.Entry(person.Plan).State = EntityState.Modified;
...because when you set the state of the person to Modified the person gets attached to the context in state Modified but related entities like person.Plan are attached in state Unchanged.
If the relationship between Person and Plan has been changed while the entities were detached it is more difficult (especially, like in your model, when no foreign key is exposed as property ("independent association")) to update the entities correctly. You basically need to load the original object graph from the database, compare it with detached graph if relationships have been changed and merge the changes into the loaded graph. An example is here (see the second code snippet in that answer).
Edit
Example to show that it works (with EF 5.0):
using System.Data;
using System.Data.Entity;
using System.Linq;
namespace EFModifyTest
{
public class Person
{
public int Id { get; set; }
public Plan Plan { get; set; }
public int Record { get; set; }
public int PersonTypeValue { get; set; }
}
public class Plan
{
public int Id { get; set; }
public string SomeText { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Person> Contacts { get; set; }
public DbSet<Plan> Plans { get; set; }
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
// Create a person with plan
using (var ctx = new MyContext())
{
ctx.Database.Initialize(true);
var plan = new Plan { SomeText = "Old Text" };
var person = new Person { Plan = plan, Record = 1, PersonTypeValue = 11 };
ctx.Contacts.Add(person);
ctx.SaveChanges();
}
// see screenshot 1 from SQL Server Management Studio
Person detachedPerson = null;
// Load the person with plan
using (var ctx = new MyContext())
{
detachedPerson = ctx.Contacts.Include(c => c.Plan).First();
}
// Modify person and plan while they are detached
detachedPerson.Record = 2;
detachedPerson.PersonTypeValue = 12;
detachedPerson.Plan.SomeText = "New Text";
// Attach person and plan to new context and set their states to Modified
using (var ctx = new MyContext())
{
ctx.Entry(detachedPerson).State = EntityState.Modified;
ctx.Entry(detachedPerson.Plan).State = EntityState.Modified;
ctx.SaveChanges();
}
// see screenshot 2 from SQL Server Management Studio
}
}
}
Screenshot 1 from SQL Server Management Studio (before the modification, Person table is left, Plan table is right):
Screenshot 2 from SQL Server Management Studio (after the modification, Person table is left, Plan table is right):
If it doesn't work for you there must be an important difference to my test model and code. I don't know which one, you must provide more details.
Edit 2
If you change the relationship from Person to another (existing) Plan you must load the original and then update the relationship. With independent associations (no FK property in model) you can update relationships only by using change tracking (aside from more advanced modifications of relationship entries in the ObjectContext change tracker):
var originalPerson = this.Contacts.Include(c => c.Plan)
.Single(c => c.Id == person.Id);
this.Plans.Attach(person.Plan);
this.Entry(originalPerson).CurrentValues.SetValues(person);
originalPerson.Plan = person.Plan;
this.SaveChanges();
I am currently using EF4.3 and Code First. Creation of my objects works (via my views - just using the auto-generated Create), but when I attempt to edit an object, it does not save any changes that, utlimately, tie back to my navigation properties. I have been reading on relationships, but I don't understand how to tell my context that the relationship has changed.
Here is some example code of my implementation.
#* Snippet from my view where I link into my ViewModel. *#
<div class="row">
<div class="editor-label">
#Html.LabelFor(model => model.ManagerID)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.ManagerID, ViewBag.Manager as SelectList, String.Empty)
#Html.ValidationMessageFor(model => model.ManagerID)
</div>
</div>
Here is my Controller implementation (POST of my Edit):
[HttpPost]
public ActionResult Edit(ProjectViewModel projectViewModel)
{
if (ModelState.IsValid)
{
Project project = new Project();
project.ProjectID = projectViewModel.ProjectID;
project.Name = projectViewModel.Name;
project.ProjectManager = repository.GetUser(projectViewModel.ManagerID);
repository.InsertOrUpdateProject(project);
repository.Save();
return RedirectToAction("Index");
}
ViewBag.Manager = new SelectList(repository.GetUsers(), "UserID", "FullName", projectViewModel.ManagerID);
return View(projectViewModel);
}
Within my Project object:
public class Project
{
public int ProjectID { get; set; }
[Required]
public string Name { get; set; }
// Navigation Properties
public virtual User Manager { get; set; }
}
Here is the corresponding method from the repository (where my context resides):
public void InsertOrUpdateProject(Project project)
{
if (program.ProjectID == default(int))
{
context.Projects.Add(project);
}
else
{
context.Entry(project).State = EntityState.Modified;
}
}
Just to be clear, this does work to update my properties, but it does not update my navigation properties (in this case, Manager). Appreciate any help.
Setting the state to Modified only marks scalar properties as modified, not navigation properties. You have several options:
A hack (you won't like it)
//...
else
{
var manager = project.Manager;
project.Manager = null;
context.Entry(project).State = EntityState.Modified;
// the line before did attach the object to the context
// with project.Manager == null
project.Manager = manager;
// this "fakes" a change of the relationship, EF will detect this
// and update the relatonship
}
Reload the project from the database including (eager loading) the current manager. Then set the properties. Change tracking will detect a change of the manager again and write an UPDATE.
Expose a foreign key property for the Manager navigation property in your model:
public class Project
{
public int ProjectID { get; set; }
[Required]
public string Name { get; set; }
public int ManagerID { get; set; }
public virtual User Manager { get; set; }
}
Now ManagerID is a scalar property and setting the state to Modified will include this property. Moreover you don't need to load the Manager user from the database, you can just assign the ID you get from your view:
Project project = new Project();
project.ProjectID = projectViewModel.ProjectID;
project.Name = projectViewModel.Name;
project.ManagerID = projectViewModel.ManagerID;
repository.InsertOrUpdateProject(project);
repository.Save();
There are several options here, I will list 3 of them:
Option 1: Using GraphDiff
*This needs the Configuration.AutoDetectChangesEnabled of your context set to true.
Just install GraphDiff with NuGet
Install-Package RefactorThis.GraphDiff
Then
using (var context = new Context())
{
var customer = new Customer()
{
Id = 12503,
Name = "Jhon Doe",
City = new City() { Id = 8, Name = "abc" }
};
context.UpdateGraph(customer, map => map.AssociatedEntity(p => p.City));
context.Configuration.AutoDetectChangesEnabled = true;
context.SaveChanges();
}
For more details about GraphDiff look here.
Option 2: Find and Edit
Searching your entity with EF to track it to the context. Then edit the properties.
*This needs the Configuration.AutoDetectChangesEnabled of your context set to true.
var customer = new Customer()
{
Id = 12503,
Name = "Jhon Doe",
City = new City() { Id = 8, Name = "abc" }
};
using (var context = new Contexto())
{
var customerFromDatabase = context.Customers
.Include(x => x.City)
.FirstOrDefault(x => x.Id == customer.Id);
var cityFromDataBase = context.Cities.FirstOrDefault(x => x.Id == customer.City.Id);
customerFromDatabase.Name = customer.Name;
customerFromDatabase.City = cityFromDataBase;
context.Configuration.AutoDetectChangesEnabled = true;
context.SaveChanges();
}
Option 3: Using a scalar property
In a matter of performance this is the best way, but it mess your class with database concerns. Because you will need to create a scalar (primitive type) property to map the Id.
*In this way there is no need to set the Configuration.AutoDetectChangesEnabled to true. And also you won't need to do a query to the database to retrieve the entities (as the first two options would - yes GraphDiff does it behind the scenes!).
var customer = new Customer()
{
Id = 12503,
Name = "Jhon Doe",
City_Id = 8,
City = null
};
using (var contexto = new Contexto())
{
contexto.Entry(customer).State = EntityState.Modified;
contexto.SaveChanges();
}
I am not sure exactly what you mean by navigation properties? Do you mean like a foreign key relationship? If so then try the following data annotation:
public class Project
{
public int ProjectID { get; set; }
[Required]
public string Name { get; set; }
[ForeignKey("YourNavigationProperty")]
public virtual UserManager { get; set; }
}
Update your EF Context, and see what happens?
UPDATE
public class Project
{
public int ProjectID { get; set; }
[Required]
public string Name { get; set; }
[ForeignKey("ManagerId")]
public ManagerModel UserManager { get; set; }
}
public class ManagerModel
{
[Key]
public int ManagerId { get; set; }
public String ManagerName { get; set; }
}
See if that works?
I have a parent object book, and a property of that object is publisher. Everytime I ad a book, it is adding a new publisher, even if the publisher already exists. Can someone tell me how to add the book and instead of adding the publisher again, just reference an existing one? The code i am using is below... Thanks in advance!
public class Book
{
public int BookID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime CreateDate { get; set; }
public virtual Publisher Publisher { get; set; }
}
public class Publisher
{
public int PublisherID { get; set; }
public string Address { get; set; }
}
public class SqlCEDataStore : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Publishers> Publishers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.IncludeMetadataInDatabase = false;
}
}
public class TimeSinkRepository : IRepository<Book>
{
private static SqlCEDataStore context = new SqlCEDataStore();
public int Add(Book entity)
{
context.Books.Add(entity);
return context.SaveChanges();
}
}
var book = new Book()
{
Title = "New Title",
Description = "New Description",
CreateDate = DateTime.Now,
Publisher = new Publisher() { PublisherID = 1 }
};
var repository = new BookRepository();
var result = repository.Add(book);
The problem is in the line:
Publisher = new Publisher() { PublisherID = 1 }
Object context doesn't know that this is existing publisher. It is newly created entity so Object context will perform insert operation. You have to say object context that the publisher object is not newly created. One way to do that is modification of your Add method:
public int Add(Book entity)
{
context.Books.Add(entity);
// 0 means new one, other values mean existing one
if (entity.Publisher.PublisherID > 0)
{
context.ObjectStateManager.ChangeObjectState(entity.Publisher, EntityState.Unchanged);
}
context.SaveChanges();
}
It you can solve this by making sure the Publisher is attached to Publishers context before adding the Book entity (this way it knows it's a Publisher from the dbcontext and not a new one that it needs to add (again))
context.Publishers.Attach(book.Publisher); // This is only possible if the Publisher is not new
context.Books.Add(book);
the problem is in this line
Publisher = new Publisher() { PublisherID = 1 }
You should do a fetch method so something like this
- Get the Publisher you want from the context (eg where id = 1)
- Set the returned object as the publisher for your new book object
- The context should sort the rest out for you. when you save the book. (no need to mess with the object state manager)
Good luck, if you cant get this working put up some code of it and i will help you though it.
I'm new to EF 4.0, so maybe this is an easy question. I've got VS2010 RC and the latest EF CTP. I'm trying to implement the "Foreign Keys" code-first example on the EF Team's Design Blog, http://blogs.msdn.com/efdesign/archive/2009/10/12/code-only-further-enhancements.aspx.
public class Customer
{
public int Id { get; set;
public string CustomerDescription { get; set;
public IList<PurchaseOrder> PurchaseOrders { get; set; }
}
public class PurchaseOrder
{
public int Id { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
public DateTime DateReceived { get; set; }
}
public class MyContext : ObjectContext
{
public RepositoryContext(EntityConnection connection) : base(connection){}
public IObjectSet<Customer> Customers { get {return base.CreateObjectSet<Customer>();} }
}
I use a ContextBuilder to configure MyContext:
{
var builder = new ContextBuilder<MyContext>();
var customerConfig = _builder.Entity<Customer>();
customerConfig.Property(c => c.Id).IsIdentity();
var poConfig = _builder.Entity<PurchaseOrder>();
poConfig.Property(po => po.Id).IsIdentity();
poConfig.Relationship(po => po.Customer)
.FromProperty(c => c.PurchaseOrders)
.HasConstraint((po, c) => po.CustomerId == c.Id);
...
}
This works correctly when I'm adding new Customers, but not when I try to retrieve existing Customers. This code successfully saves a new Customer and all its child PurchaseOrders:
using (var context = builder.Create(connection))
{
context.Customers.AddObject(customer);
context.SaveChanges();
}
But this code only retrieves Customer objects; their PurchaseOrders lists are always empty.
using (var context = _builder.Create(_conn))
{
var customers = context.Customers.ToList();
}
What else do I need to do to the ContextBuilder to make MyContext always retrieve all the PurchaseOrders with each Customer?
You could also use:
var customers = context.Customers.Include("PurchaseOrders").ToList();
Or enable LazyLoading in the ContextOptions :
context.ContextOptions.LazyLoadingEnabled = true;
Just be careful with deferred loading if you are serializing the objects or you may end up querying the entire database.
Well the solution turned out to be simple, as I suspected it might. I called the context.LoadProperty() method for each individual customer:
using (var context = _builder.Create(_conn))
{
var customers = context.Customers.ToList();
foreach (var customer in customers)
{
context.LoadProperty<Customer>(customer, c => c.PurchaseOrders);
}
return customers;
}