I am using EF to update my database.
I first query the database based upon some criteria. This returns the record I am expecting (or returns null). Naturally during this query, there is a slight delay as it executes the query.
I then change a property in this case a nullable boolean from false to true or create a new entry.
I then add this object to my Entities, and finally choose myEntitiy.SaveChanges(). This does not seem to execute. There is no delay. Yet the code passes to the next line, so no exception is thrown.
This is what I have
public class MyCompany : AbstractCompany
{
public override bool UpdateStatus(string emailAddress, bool isOptIn)
{
var personDetail = (from a in base.Entities.MyCompanyTable
where a.EmailAddress == emailAddress
select a).SingleOrDefault();
if (personDetail == null)
{
personDetail = new MyCompanyTable();
personDetail.EmailAddress = emailAddress;
}
personDetail.IsSubscribed = isOptIn;
base.Entities.MyCompanyTable.Add(personDetail); //also tried attach, same issue over
return base.SaveData();
}
}
And my base class is
public abstract bool UpdateStatus(string emailAddress, bool isOptIn);
protected Entities Entities
{
get { return new Entities(); }
}
protected bool SaveData()
{
var x = Entities.GetValidationErrors();//returns 0 items
try
{
Entities.SaveChanges();
return true;
}
catch (Exception e)
{
return false;
}
}
What have I done wrong?
Entity Framework uses change tracking to detect changes in any entities in the current context.
However, your Entities property instantiates a new instance of your context everytime it is called. So when you query you use one context and then when you save you use another! EF has no way to detect that you made any changes!
It would be best to instantiate your context in the base class' constructor:
public abstract class BaseClass
{
protected BaseClass()
{
Entities = new Entities();
}
protected Entities Entities { get; private set; }
}
That should fix it up.
Related
i've an unexpected issue trying to remove child entity from parent single database record.
After doing some tests we have replicated the problem.
Our C# code use Northwind database.
NWModel context = new NWModel();
Orders order = context.Orders.Where(w => w.OrderID == 10248).FirstOrDefault();
context.Entry(order).Collection(typeof(Order_Details).Name).Load();
order.Order_Details.RemoveAt(1);
System.Data.Entity.Infrastructure.DbChangeTracker changeset = context.ChangeTracker;
var changes = changeset.Entries().Where(x => x.State == System.Data.Entity.EntityState.Modified).ToList<Object>();
var detetions = changeset.Entries().Where(x => x.State == System.Data.Entity.EntityState.Deleted).ToList<Object>();
All works fine with original Order_Details table setup.
var deletions correctly has got deleted record.
In order to reproduce the issue, we have added on Order_Details table a new PK identity int field (OrderDetailId int identity); after doing that:
var deletions contain no record while var changes contain Order_Detail record.
EF set Orders property of Order_Detail to null and mark the record as Updated.
I've found a lot of articles regarding this issue, all of them suggests to mark Order_Detail Orders property to [Required].
I've tried to set [Required] attribute on FK entity as suggested in this post, (this article describe EFCore behaviour that is the same as EF6 behaviour) but is does not solve my issue.
Is this behaviour expected?
we would appreciate any comments or suggestions.
Thanks
AFAIK this is the correct behavior.
Indeed, you do not delete the detail. You remove it from the order details collection, that is you cut the relation between the order and the detail. As this relation is materialized by a navigation property on the detail side, then two things occur:
the collection is updated,
the navigation property is set to null.
Logically from this point if you SaveChanges you should have an exception because a detail cannot exist without an order, and you haven't yet deleted the detail, only cut the relation. So you should ctx.Set<orderDetail>().Remove(detail). (a bit annoying)
This is why in this case, I usually use composit key for details: detailId + orderId.
So when you remove the detail, the orderId is set to null <=> the PK is considered as null => the entity is marked for deletion.
Following tschmit007 suggestion, finally we have implemented this workaround.
On Orders entity we have used ObservableListSource<> instead of ICollection<>; into ObservableListSource<> class, void RemoveItem override method, we could manage ctx.Set<orderDetail>().Remove(detail) method.
In this way all child record deletion works as expected.
Here is implemented code:
public partial class Orders
{
public Orders()
{
Order_Details = new ObservableListSource<Order_Details>();
}
[Key]
public int OrderID { get; set; }
……………
public virtual ObservableListSource<Order_Details> Order_Details { get; set; }
}
public class ObservableListSource<T> : ObservableCollection<T>, IListSource
where T : class
{
private IBindingList _bindingList;
bool IListSource.ContainsListCollection { get { return false; } }
IList IListSource.GetList()
{
return _bindingList ?? (_bindingList = this.ToBindingList());
}
private bool _bRemoveInProgress = false;
protected override void RemoveItem(int index)
{
if (!_bRemoveInProgress && index>=0)
{
_bRemoveInProgress = true;
DbContext cntx = this[index].GetDbContextFromEntity();
Type tp = this[index].GetDynamicProxiesType();
cntx.Set(tp).Remove(this[index]);
base.RemoveItem(index);
}
_bRemoveInProgress = false;
}
}
public static class DbContextExtender
{
public static Type GetDynamicProxiesType(this object entity)
{
var thisType = entity.GetType();
if (thisType.Namespace == "System.Data.Entity.DynamicProxies")
return thisType.BaseType;
return thisType;
}
public static DbContext GetDbContextFromEntity(this object entity)
{
var object_context = GetObjectContextFromEntity(entity);
if (object_context == null)
return null;
return new DbContext(object_context, dbContextOwnsObjectContext: false);
//return object_context;
}
private static ObjectContext GetObjectContextFromEntity(object entity)
{
var field = entity.GetType().GetField("_entityWrapper");
if (field == null)
return null;
var wrapper = field.GetValue(entity);
var property = wrapper.GetType().GetProperty("Context");
var context = (ObjectContext)property.GetValue(wrapper, null);
return context;
}
}
I am attempting to unit test a PUT Request by checking values. However, I run into one simple issue. I have a test context like such:
class TestAppContext : ContextInterface
{
public DbSet<User> Users {get; set;}
public DbSet<Request> Requests { get; set; }
public TestAppContext()
{
this.Users = new TestUsersDbSet();
this.Requests = new TestRequestsDbSet();
}
public int SaveChanges(){
return 0;
}
public void MarkAsModified(Object item) {
}
public void Dispose() { }
}
When running a PUT with a DbContext the Entry(item).State is set to EntityState.Modified in the MarkAsModified method, then changes the changes are saved. How do I emulate this in my test context so that the DbSet reflects the changes from the PUT request?
I've gotten as far as doing this:
public void MarkAsModified(Object item) {
if (item.GetType() == typeof(User))
{
}
else if (item.GetType() == typeof(Request))
{
}
}
So that I can determine what is being modified, but how do I actually save the changes into the DbSet for that record?
Both records are identified on a variable id which is an int.
In your test context just keep a List<Object> markedAsModified field, then in the call to MarkAsModified add the object to that list if it doesn't already exist. Then in your test you can have Assert statements the check the contents of that list to make sure the right objects were passed to that function..
I am very new to entity framework and I am having a problem with a web api based site (connected to mssql) that I am writing. I keep getting seemingly random errors (mostly seeming to be database related). These errors happen most often when the site is first published but they do sometimes happen when it has been hours since the last publish. A selection of the errors:
Invalid operation. The connection is closed.
There is already an open DataReader associated with this Command which must be closed first.
The connection was not closed. The connection's current state is connecting.
The context cannot be viewed while the model is being created
Underlying provider failed to open
My context looks like this:
public class Context : DbContext
{
public Context() : base("name=DefaultConnection")
{
}
public override int SaveChanges()
{
DateTime now = DateTime.Now;
foreach (ObjectStateEntry entry in (this as IObjectContextAdapter).ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified))
{
if (!entry.IsRelationship)
{
IHasUpdated updated = entry.Entity as IHasUpdated;
if (updated != null)
updated.updated = now;
}
}
return base.SaveChanges();
}
public DbSet<Branch> Branches { get; set; }
public DbSet<Company> Companies { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UsefulLink> UsefulLinks { get; set; }
}
There are many more DbSets than this. Should I be creating a separate context for each?
One of my basic controllers:
public class UsefulLinksController : ApiController
{
private Context db = new Context();
[ResponseType(typeof(UsefulLinksWrapper))]
public IHttpActionResult GetUsefulLinks([FromUri]UsefulLinkParams prams)
{
UsefulLinksWrapper wrapper = new UsefulLinksWrapper();
Meta meta = new Meta();
IQueryable<UsefulLink> query = db.UsefulLinks;
if (prams.sortBy == null)
{
prams.sortBy = "ID";
}
// Paging
query = query.OrderBy(prams.sortBy + " " + prams.sortDirection).Skip(prams.offset - 1).Take(prams.limit);
List<UsefulLink> data = query.ToList();
meta.totalCount = query.Count();
meta.offset = 1;
meta.limit = prams.limit;
wrapper.meta = meta;
wrapper.data = data;
return Ok(wrapper);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
private bool UsefulLinkExists(int id)
{
return db.UsefulLinks.Count(e => e.ID == id) > 0;
}
}
I don't seem to see these errors when I run the site locally though there are two of us hitting it when it is published so perhaps the issue stems from multiple users?
Chris, I notice in your controller you are sharing your db context with all of the methods in your controller class.
This is generally not a best practice in Entity Framework (see: EntityFramework 4 ObjectContext Lifetime). You should keep your context alive as briefly as possible. Leaving the context alive to share across multiple methods could result in many of the errors that you list above.
I would recommend trying to instantiate a new instance of the context, instead, wherever it is used and quickly disposing of it.
This should generally result in more stable behavior.
So the below:
class SomeClass
{
private context = new Context(); //sharing your context with all methods
public someMethod()
{
context.doSomething;
}
public someMethod2()
{
context.doSomething;
}
}
should become:
class SomeClass
{
public someMethod()
{
Context context = new Context(); //now your context is declared and disposed of within each method
context.doSomething;
}
public someMethod2()
{
Context context = new Context(); //now your context is declared and disposed of within each method
context.doSomething;
}
}
Or even better, you can use a using construct to ensure that your context is properly disposed of:
class SomeClass
{
public someMethod3()
{
using(Context context = new Context()) //now wrapping the context in a using to ensure it is disposed
{
context.doSomething;
}
}
}
I would recommend trying the above changes and seeing if your behavior becomes more stable.
Since I do not know how your page uses the methods UsefulLinksController and in which order, I would say UsefulLinkExists is perhaps the culprit due to lazy loading
Lazy loading means delaying the loading of related data until you
specifically request it
Which would explain why your "reader" remains "open".
Try:
return db.UsefulLinks.ToList().Count(e => e.ID == id) > 0;
In any case, you can disable lazy loading by default in the context constructor as such noted here:
public MyEntitiesContext() : base("name=MyEntitiesContext", "MyEntitiesContext")
{
this.ContextOptions.LazyLoadingEnabled = false;
OnContextCreated();
}
As far as I know, it applies to EF4 and up.
I have a model that I get from a POST request. Since my view defines its POCO type, the object created from the submitted data is also a POCO. Being a POCO, it doesn't have various virtual properties overridden. Therefore, those virtual properties return null. This, in turn, means that I have to make separate queries based on the foreign keys to navigate through its properties (if I want to do anything more complex than just saving it).
Can I, given the POCO of my model, get the proxy that has all the overridden functionality?
(I had assumed that this is what db.Entry().Entity was for, but it still returns me the POCO object, not the proxy. I'm inspecting the runtime type of the object by mouse-over during breakpoint pauses.)
Something along the lines of this code will do what you need. I've used automapper to copy values from the passed in entity to the proxied version.
The code checks whether the passed in entity is a proxy or not and handles it accordingly.
public class Repository<T> where T : class
{
private readonly Context context;
private bool mapCreated = false;
public Repository(Context context)
{
this.context = context;
}
protected virtual T InsertOrUpdate(T e, int id)
{
T instance = context.Set<T>().Create();
if (e.GetType().Equals(instance.GetType()))
instance = e;
else
{
if (!mapCreated)
{
Mapper.CreateMap(e.GetType(), instance.GetType());
mapCreated = true;
}
instance = Mapper.Map(e, instance);
}
if (id == default(int))
context.Set<T>().Add(instance);
else
context.Entry<T>(instance).State = EntityState.Modified;
return instance;
}
}
UPDATE version as described by #Colin in the comments that does not need automapper
public class Repository<T> where T : class
{
private readonly Context context;
public Repository(Context context)
{
this.context = context;
}
protected virtual T InsertOrUpdate(T e, int id)
{
T instance = context.Set<T>().Create();
if (e.GetType().Equals(instance.GetType()))
{
instance = e;
}
else
{
DbEntityEntry<T> entry = context.Entry(instance);
entry.CurrentValues.SetValues(e);
}
context.Entry<T>(instance).State =
id == default(int)
? EntityState.Added
: EntityState.Modified;
return instance;
}
}
db.Entry().Entity will always return you a POCO, and will not return the proxy object that handles the implementation of virtual navigation properties:
var o = db.Entry(myPoco).Entity; // always returns a POCO
You will normally get a proxy object instead of a POCO when calling Find() or Where() against the database context. However, within the context in which an object is first added to the database, these methods will (unexpectedly?) return the POCO instead of the proxy object. You actually have to leave the context and open a new one to get the proxy:
// create a new POCO object, and connect to it to another object already in the DB
MyPoco myPoco = new MyPoco();
myPoco.MyOtherPocoId = myPoco2.MyOtherPocoId; // make reference to existing object
using (var db = new MyContext())
{
// Add myPoco to database.
db.MyPocos.Add(myPoco);
db.SaveChanges();
// One would think you get a proxy object here, but you don't: just a POCO
var test10 = db.MyPocos.Find(myPoco.Id); // returns a MyPoco
var test11 = db.MyPocos.Where(x => x.Id == myPoco.Id).First(); // returns a MyPoco
var test12 = db.Entry(myPoco).Entity; // returns a MyPoco
// ...so, you can't access the referenced properties through virtual navigation properties:
MyOtherPoco otherPoco1 = myPoco.Poco2; // returns NULL
}
// leave the context and build a new one
using (var db = new MyContext())
{
// Now, the same Find() and Where() methods return a proxy object
var test20 = db.MyPocos.Find(myPoco.Id); // returns a proxy object
var test21 = db.MyPocos.Where(x => x.Id == myPoco.Id).First(); // returns a proxy object
// ...which means the virtual properties can be accessed as expected:
MyOtherPoco otherPoco = myPoco.Poco2; // works as expected
// Note that db.Entry().Entity still returns a POCO:
var test22 = db.Entry(myPoco).Entity; // returns a MyPoco
}
There may be some magic incantation to make the context in which the object is added give you back a proxy object, but I haven't come across it.
If you want to do this via the MVC controller, you might use something like this as an action:
[HttpPost]
public ActionResult Update(int? id, FormCollection form)
{
// assumes repository will handle
// retrieving the entity and
// including and navigational properties
var entity = repository.Get(id);
if (entity == null)
{
throw new InvalidOperationException(string.Format("Not found: {0}", id));
}
if (TryUpdateModel(entity))
{
try
{
//
// do other stuff, additional validation, etc
repository.Update(entity);
}
catch (Exception ex)
{
//
// exception cleansing/handling
// additional model errors
return View(entity);
}
return View("Success", entity);
}
return View(entity);
}
I have two entities:
public class Order:Entity
{
public virtual User User { get; set; }
...
}
public class User:Entity
{
public virtual ICollection<Order> Orders { get; set; }
...
}
Next, I create order:
var order = _orderService.CreateTransientOrder(orderNumbers, PcpSession.CurrentUser);
PcpSession.Order = order;
this is CreateTransientOrder. It's only create Order, but not save into database:
public Order CreateTransientOrder(string orderNumbers, User currentUser)
{
...fill fields
order.User = currentUser;
return order;
}
Now it's all ok. Next, I save order to the database:
_orderService.CreateOrder(PcpSession.Order);
This is CreateOrder:
public void CreateOrder(Order order)
{
order.OrderDate = DateTime.Now;
_repository.Save(order);
_repository.SaveChanges();
}
This is my Save method of repository:
public void Save<T>(T entity) where T : class, IEntity
{
_context.Set<T>().Add(entity);
}
When the SaveChanges is called in the database creates new user with new ID and order have new User_Id. In the debugger in the CreateOrder method, Id is equal current user. Where is a problem?
Thanks.
User is probably not being tracked by the context. When you add order to the context it also adds the related entities and then on save changes creates a new user (or attempts to). Attach() the user to the context before you call _context.Set<T>().Add(entity);.
I guess the problem is not related with the code you have provided. It seems to be related to where you are initializing PcpSession.CurrentUser.
It seems PcpSession.CurrentUser object is not attached to the context. Either fetch this entity to the context before making you Order related calls or attach it.
You need attach your Entity if not attach in context.
for exemple in Repository Generic
> public void Add(T entity)
> {
> entity.Create = DateTime.Now;
> db.Set<T>().Attach(entity); // Add Line
> db.Set<T>().Add(entity);
> Save();
> }
I do not know if it's clean but it regulates the problem