I have two tables with a Many-To-Many relationship like this:
User(emailaddress, Name)
UserAlerts(emailaddress, AlertId)
Alert(AlertId,Title)
Alerts have already been added to the database. When inserting a new user, I am doing a lookup on the AlertRepository. The problem is, Instead of creating a record in the User and the UsertAlerts tables only, its also adding an extra Alert record.
I am using the following code:
public ActionResult Register(UserModel model, int[] Alerts)
User user = new MidTier.Models.User();
user.Name = model.Name;
user.EmailAddress = model.EmailAddress;
if (Alerts!=null)
{
IRepository<Alert> alertRepository = new AlertRepository();
foreach (int alertId in Alerts)
{
Alert alert = alertRepository.First(a=>a.ID== alertId);
alertRepository.Detach(alert);
if (alert != null)
{
alert.Enabled = true;
user.Alerts.Add(alert);
}
}
}
userRepository.Attach(user);
userRepository.Add(user);
userRepository.Save();
Why don't you try to search little bit before you ask a question? This problem is asked several times per week. In your previous question I said you that you should use same context for loading Alert and storing User. You didn't do it and complicated whole situation.
The context doesn't know anything about existence of the alert. Once you call Add for user it will add all entities which are not tracked yet. There are three ways to solve this:
Use the same context in both repositories and do not detach alerts. Because of loading alerts, context will know about their existence and doesn't insert them again.
If you don't use the same context for loading you must attach the Alert to the new context before you add it to User. That is hard to do when you wrap EF code to repositories.
If you don't use the same context and you will not attach Alert to the new context before you add it to User you must modify your Add method for User and after adding User to the context you must iterate every alert and change its state to Unchanged.
Related
I've read multiple questions similar to this one but none are exactly my situation.
Using linq-to-sql I insert a new record and submit changes. Then, in the same web request, I pull that same record, and update it, then submit changes. The changes are not saved. The DatabaseContext is the same across both these operations.
Insert:
var transaction = _factory.CreateTransaction(siteId, userId, questionId, type, amount, transactionId, processor);
using (IUnitOfWork unitOfWork = UnitOfWork.Begin())
{
transaction.Amount = amount;
_transactionRepository.Add(transaction);
unitOfWork.Commit();
}
Select and Update:
ITransaction transaction = _transactionRepository.FindById(transactionId);
if (transaction == null) throw new Exception(Constants.ErrorCannotFindTransactionWithId.FormatWith(transactionId));
using (IUnitOfWork unitOfWork = UnitOfWork.Begin())
{
transaction.CrmId = crmId;
transaction.UpdatedAt = SystemTime.Now();
unitOfWork.Commit();
}
Here's the unit of work code:
public virtual void Commit()
{
if (_isDisposed)
{
throw new ObjectDisposedException(GetType().Name);
}
_database.SubmitChanges();
}
I even went into the designer.cs file and put a breakpoint on the field that is being set but not updated. I stepped through and it entered and execute the set code, so the Entity should be getting "notified" of the change to this field:
public string CrmId
{
get
{
return this._CrmId;
}
set
{
if ((this._CrmId != value))
{
this.OnCrmIdChanging(value);
this.SendPropertyChanging();
this._CrmId = value;
this.SendPropertyChanged("CrmId");
this.OnCrmIdChanged();
}
}
}
Other useful information:
ObjectTracking is enabled
No errors or exceptions when second SubmitChanges is called (just silently fails update)
SQL profiler shows insert and select but not the subsequent update statement. Linq-To-Sql is not generating the update statement.
There is only one database, one database string, so the update is not going to another database
The table has a primary key.
I don't know what would cause Linq-To-Sql to not issue the update command and not raise some kind of error. Perhaps the problem stems from using the same DataContext instance? I've even refreshed the object from the database using the DataContact.Refresh method before it is pulled for the update, but that didn't help.
I have found what is likely to be the root cause. I am using Unity. The initial insert is being performed in a service class with a PerWebRequest lifetime. The select and update is happening in a class with a Singleton lifetime. So my assumption that the DataContext instances are the same was incorrect.
So, in my class with the Singleton lifetime, I get a fresh instance of the database repository and perform the update and no problem.
Now I still don't know why the original code didn't work and my approach could still be considered more a workaround than a solution, but it did solve my problem and hopefully will be useful to others.
I'd appreciate any advice.
I've been searching for the right approach of modifying a collection, but not sure which is the best one.
I've an entity with nested collection:
public class Customer
{
//customer properties
ICollection <Address> Addresses{get; set;}
}
My Edit view for Customer includes the Addresses as well, i.e. user adds Addresses dynamically, and the whole collection is passed to the controller on form submittion.
In the controller I update the Customer as usually:
Context.Entry(customer).State = EntityState.Modified;
And also I have to update the collection of Address:
customer.Addresses.ToList()
.ForEach(p =>
Context.Entry(p).State = EntityState.Modified);
It works fine untill I add a new record to Addresses. Since it doesn't exist in DB, saving throws the error.
I know I could check if the entry exists in DB - modify, otherwise - add.
But there's a problem in Address entity. It's primary key, say ID, is Identity, i.e. auto-increments when inserted to DB.
So, initially in the collection new Address will have ID equal to 0. Then if I add one Address to the context, then before adding another one,I check if the second Address is in context, it will return true, because the first one is also with ID = 0.
Also, okay, I can drop the whole collection and add it again, but it can affect the performance.
So, I'd be so grateful for your advices.
You could iterate over the addresses and check the id. Based on the value it is possible to set the state of the entity entry to Added or Modified. In that case you don't have to recreate the collection.
foreach (var a in customer.Addresses)
{
model.Entry(a).State = a.ID == 0 ? EntityState.Added :EntityState.Modified;
}
You could use the same approach for the customer of course.
I am developing an application for generating estimates on products such as cars.
So, when a car make an model is selected, I need to present various options to the user (options may be in different groups like Wheels, Seating Upholstery, Trunk Accessories)
Depending upon the group, the customer may pick one or more options in that group; if a certain option is selected - some other options may get disabled; not all options apply to every make an model
So, there are several rules to be defined for different groups to indicate what combination is allowed and what is not allowed?
How should I go about designing the database for this and is there a pattern that I can leverage as I develop this application?
I solved a similar requirement with the following structure, rewritten in your terms above:
Parts
Groups
Car
With the following notes:
Parts are standalone in their own right, each with a part number.
A car template is standalone in its own right.
Parts can be added to a option group, and a number of options groups belongs to a car.
An option group cannot exist without a car.
A group can depend on another group
I need to protect against circular references
I started out playing with my model by writing the test case before i wrote the class code (Test Driven Development), which gave me code (in C#) as:
var dieselEngine = new Sku("diesel 2_litre,",1000);
var petrolEngine2 = new Sku("petrol_2_litre",800);
var petrolEngine25 = new Sku("petrol_25_litre",900);
var petrolTurbo = new Sku("petrol_turbo",2000);
var dieselTurbo = new Sku("diesel_turbo",2000);
var car = new Car("myCar");
car.AddGroup("Engines");
car.AddSkuToGroup("Engines", diselEngine);
car.AddSkuToGroup("Engines", petrolEngine2);
car.AddSkuToGroup("Engines", petrolEngine25);
car.AddGroup("Turbos");
car.AddSkuToGroup("Turbos", petrolTurbo);
car.AddSkuToGroup("Turbos", dieselTurbo);
car.SetRequirement(diselEngine, dieselTurbo);
car.SetRequirement(petrolTurbo, petrolEngine2);
car.SetRequirement(petrolTurbo, petrolEngine25);
I add the dependency option on the groups, rather than on the Sku, since a part may exist across multiple cars but may have different dependencies for each specific car.
I have to put everything through the root car object, which will check and enforce all my business rules (such as checking for and protecting against circular references).
Should all access via the car object feel clunky, you could always have the car.AddGroup function return a group to make the code make more sense to read:
var engines = car.AddGroup("Engines");
engines.AddSkuToGroup(diselEngine);
engines.AddSkuToGroup(petrolEngine2);
engines.AddSkuToGroup(petrolEngine25);
But do not forget the business rules can only be enforced by the car, since the car has visibility of all the components. So we always chain up via the root:
class ConfigurableProduct
{
List<Group> groups = new List<Group>();
Group NewGroup(string name)
{
var group = new Group(this, name);
this.groups.Add(group);
return group;
}
bool ContainsSku(string skuId)
{
foreach (var group in this.Groups)
{
if (group.ContainsSku(skuId))
return true;
}
return false;
}
}
class Group
{
Group(ConfigurableProduct parent, string name)
{
this.parent = parent;
this.name = name;
}
string name;
List<string> skuIds = new List<string>();
ConfigurableProduct parent;
void AddSkuToGroup(string skuId)
{
// enforce invariants via parent, call functions as reuqired
if (this.parent.containsSku(skuId))
throw new Exception("SKU already exists in this configurable template, cannot exist twice");
// do other things, like check circular references etc, all via this.parent
}
bool ContainsSku(string toFind)
{
foreach (var skuId in this.skuIds)
{
if (skuId == toFind)
return true;
}
return false;
}
}
For the actual database storage i would worry about persistence last, it could be a text file, MSSQL database, MySQL database, MongoDB, there are so many options.
I find it always useful to concentrate on how i want to use the code in my application, and not the specifics of how the database needs to be used, as the storage can abstracted via a repository interface that returns a class (a plain old POCO class, but in this case we have started to flesh out with business logic to protect against invalid states).
For the front end, you may want to push this all down via JSON to something like angular or knockout that can render the options available dynamically, and show or hide different elements depending on the dependencies between groups.
Working fronted example
I am not sure what front end binding you are using (or if you will only be using razor, in that case you will need to store state on the server and refresh each selection), but I have provided an example using Knockoutjs here: http://jsfiddle.net/g18c/5jt9bwsv/1/ with working dependencies and dynamic javascript object builder.
Loops through provided JSON products by group
Creates calculated fields that change depending on a target dependency
Binds to a view via knockout
The selected SKUs could then simply be passed up to the server, any business rules can also be implemented in the front end javascript.
Of course anything data that is sent from the client to the server would need to be validated by building up the product graph on the server and checking that the provided SKUs are valid (i.e. you wouldn't want to allow a Diesel Turbo to be selected for a Petrol Engine).
I'll preface this question with the following: I know there are a million posts on the internet about the old "An object with the same key already exists in the ObjectStateManager" issue. My scenario is a bit more complicated, I think.
I have a UnitOfWork class which creates a DbContext and passes it to any repository which is called. The pattern I'm using closely follows the Unit of Work tutorial on the ASP.NET site. Unlike the tutorial, my repositories take in Business entities, map them to data entities, and perform some CRUD action. My Business logic only works with Business entities. Here is what I'm trying to do in a sample Business Manager class:
_unitOfWork.Repository.Add(entity);
_unitOfWork.Save(); // context.SaveChanges() under the hood
...Perform some operations on the model...
_unitOfWork.Repository.Update(entity);
_unitOfWork.Save();
Here is a sample Update method from the repository:
public virtual void Update(entity)
{
var dataEntity = // map from business entity to data;
_context.Entry(dataEntity).State = EntityState.Modified;
}
It obviously fails on the last line. Here is where my confusion sets in:
The entity's State is Detached
When I attempt to change the State to Modified or Unchanged, it gives me the ObjectStateManager exception above.
When I attempt to detach the entity from the context (((IObjectContextAdapter)_context).ObjectContext.Detach(entity);) I get an exception about how the entity is not attached to the context, therefore, it cannot detach it. Very confusing (something fundamental I'm missing, for sure).
Many other posts suggest I make a database call, update that entity in the repository, then _unitOfWork.Save(). I don't like this approach. I shouldn't need to make an unnecessary network call to update an entity.
The Update method in the repository needs to handle two scenarios: 1) updating an entity which is not currently tracked by the context, and 2) updating an entity which IS currently tracked by the context. The second piece is what I'm struggling with.
Any help or insight is appreciated.
Thanks!
This means that there already is an object attached to the context with the same key as the new dataEntity. The existing object and the new entity both represent the same entry in the database but they are two different objects.
This may indicate that the lifespan of your _context is too long, but that's hard to judge from your code. It is certain though that the context was previously used to fetch an entity from the database that is subsequently duplicated by var dataEntity = ....
You may have to shorten the lifespan of the context, I can't tell. If you think it's OK you may want to use the Local collection to check whether the entity is already there. That will save the database round trip that Find may still make.
I found a hybrid solution which appears to work:
public virtual void Update(TB entity)
{
var dataEntity = Mapper.Map<TB, TD>(entity);
var pkey = _dbSet.Create().GetType().GetProperty("Id").GetValue(dataEntity);
var entry = _context.Entry(dataEntity);
if (entry.State == EntityState.Detached)
{
var attachedEntity = _dbSet.Find(pkey);
if (attachedEntity != null)
{
var attachedEntry = _context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(dataEntity);
}
else
{
entry.State = EntityState.Modified;
}
}
else
{
entry.State = EntityState.Modified;
}
}
I'm using EF 5.0 to create a web and I have some issues disposing my context. All the times that I use a context is inside a using sentence, so the context should be disposed automatically but in a specific moment I get the next error when I try to attach an entity to a context:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
It semms that the entity is not disposed. How is the way to manage this situation? Do I have to dispose the ObjectContext to dispose the entities or is there any way to check if the Entity is attached?
Regards.
One way to do it is to detach the existing object before attaching. I don't have VS in front of me so I apologize if the code isn't exactly correct.
var existingObject = dbContext.Users.Local
.FirstOrDefault(x => x.id = newObject.id);
if (existingObject != null)
{
// remove object from local cache
dbContext.Entry(existingObject).State = EntityState.Detached;
}
dbContext.Users.Attach(newObject);
In case this doesn't fix the problem, you'll have to go to the old way of detaching objects.
// remove object from local cache
ObjectContext objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
objectContext.Detach(existingObject);
If you do something like that:
User u;
using (Entities ent = new Entities())
{
u = ent.Users.Single(a => a.ID == 123);
}
using (Entities ent2 = new Entities())
{
//loading the same user
User user2 = ent2.Users.Single(a => a.ID == 123);
//trying to attach the same object with the same key
ent2.Attach(u);
}
then you will get this error (I haven't tested this code).
EDIT: one of the solutions is to change the object's state:
ent2.Attach(u);
ent2.ObjectStateManager.ChangeObjectState(u, EntityState.Modified);
another solution is to check if the entity is already attached:
ObjectStateEntry state = null;
if(!ent2.ObjectStateManager.TryGetObjectStateEntry(((IEntityWithKey)u).EntityKey, out state))
{
ent2.Attach(u);
}
Dispose doesn't mean "reset to factory settings". It is a way to clean up unmanaged resources like database connections and such.
The problem has nothing to do with disposing a context or not. It even has nothing to do with having multiple contexts somewhere in place. If this would be the problem you would get the "An entity object cannot be referenced by multiple instances of IEntityChangeTracker" exception which is totally different to your exception.
You can simulate your exception quite easily with only a single context:
using (var ctx = new MyContext())
{
var customer1 = new Customer { Id = 1 };
var customer2 = new Customer { Id = 1 }; // a second object with the same key
ctx.Customer.Attach(customer1);
ctx.Customer.Attach(customer2); // your exception will occur here
}
The problem causing this exception is normally more hidden, expecially if you keep in mind that attaching or setting a state (for example to Modified) will also attach all related entities in the object graph of the entity you are attaching. If in this graph are two objects with the same key you'll get the exception as well, although you didn't attach those related entities explicitly.
But it's impossible to find the exact reason without more details about your code.