I need some help on adding roles to user in a many-many situation.
So I have User with many Role and Role has many User.
I figured my current Update() method in my repository wont work. How can I build a repository method that allows me to remove all previous roles and add new roles to user?
This is what I currently have:
public User UpdateUser(User user, IEnumerable<Expression<Func<User, object>>> properties)
{
if (string.IsNullOrEmpty(user.UserId))
{
throw new InvalidOperationException("user does not exist");
}
else
{
db.Users.Attach(user);
foreach (var selector in properties)
{
string propertyName = Helpers.PropertyToString(selector.Body);
db.Entry(user).Property(propertyName).IsModified = true;
}
}
db.SaveChanges();
return user;
}
Is this the right way to update a user? I'm assuming everything the detached. This is how I'm calling this to add roles to user:
User user = new User();
user.UserId = userId;
user.Roles = new Domain.Role{ RoleId = 1}; //Assuming there is a role with ID = 1
userRepo.UpdateUser(user, new List<Expression<Func<User, object>>>
{
u => u.Roles
});
If you are working in a detached scenario you might want to look into the object state manager. See these two answers for more info.
Entity Framework Code First - Add Child Entity to Parent by Primary Key
Save a relation with between two entities an N-N association
The first one is a simple example where a single child is added without roundtriping the db. The second one is more complex but I still haven't found a good way to clear the relationship without telling it which childs to delete.
There is a possibility I haven't looked at yet and it is to use the RelationshipManager which you can get from the ObjectStateManager. It contains a few methods to get releated collections so maybe you could use that somehow.
Related
I have a web app using MVC and EF. I am using Repository and Unit of Work Patterns from Microsoft online doc.
I am trying to insert multiple rows from multiple tables.
The code look something like this:
unitOfWork.Table1.Insert(row1);
unitOfWork.Save();//recId is primary key, will be auto generated after save.
table2Row.someId = table1Row.recId;
unitOfWork.Table2.Insert(row2);
unitOfWork.Save();
If anything goes wrong when inserting row2, I need to rollback row1 and row2.
How do I implement BeginTransaction/Commit/Rollback with UnitOfWork pattern?
Thanks.
To avoid these issues it is better to utilize EF as an ORM rather than a simple data translation service by using reference properties rather than relying on setting FK values directly.
Your example doesn't really appear to be providing anything more than a thin abstraction of the DbContext.
Given an example of an Order for a new Customer where you want a CustomerId to be present on the Order.
Problem: the new Customer's ID is generated by the DB, so it will only be available after SaveChanges.
Solution: Use EF navigation properties and let EF manage the FKs.
var customer = new Customer
{
Name = customerName,
// etc.
};
var order = new Order
{
OrderNumber = orderNumber,
// etc.
Customer = customer,
};
dbContext.Orders.Add(order);
dbContext.SaveChanges();
Note that we didn't have to add the Customer to the dbContext explicitly, it will be added via the order's Customer reference and the Order table's CustomerId will be set automatically. If there is a Customers DbSet on the context you can add the customer explicitly as well, but only one SaveChanges call is needed.
To set the navigation properties up see: https://stackoverflow.com/a/50539429/423497
** Edit (based on comments on 1-many relationships) **
For collections some common traps to avoid would include setting the collection references directly for entities that have already been associated with the DbContext, and also utilizing virtual references so that EF can best manage the tracking of instances in the collection.
If an order has multiple customers then you would have a customers collection in the order:
public virtual List<Customer> Customers{ get; set; } = new List<Customer>();
and optionally an Order reference in your customer:
public virtual Order Order { get; set; }
Mapping these would look like: (from the Order perspective)
HasMany(x => x.Customers)
.WithRequired(x => x.Order)
.Map(x => x.MapKey("OrderId"));
or substitute .WithRequired() if the customer does not have an Order reference.
This is based on relationships where the entities do not have FK fields declared. If you declare FKs then .Map(...) becomes .HasForeignKey(x => x.FkProperty)
From there if you are creating a new order:
var order = new Order
{
OrderNumber = "12345",
Customers = new []
{
new Customer { Name = "Fred" },
new Customer { Name = "Ginger" }
}.ToList()
};
using (var context = new MyDbContext())
{
context.Orders.Add(order);
context.SaveChanges();
}
This should all work as expected. It will save the new order and both associated customers.
However, if you load the order from the DbContext and want to manipulate the customers associated to it, then there are a couple caveats.
1. You should to eager-load the Customers collection so that EF knows about these entities.
2. You need to manipulate the collection with Add/Remove rather than setting the reference to avoid confusion about what the code looks like it does and what EF interprets.
Something like:
using (var context = new MyDbContext())
{
var order = context.Orders.Find(1);
order.Customers = new []
{
new Customer { Name = "Roy" }
}.ToList();
context.SaveChanges();
}
Will result in "Roy" being added to the Customers, rather than replacing them.
To replace them you need to remove them first, then add the new one.
using (var context = new MyDbContext())
{
var order = context.Orders.Find(1);
context.Customers.RemoveRange(order.Customers); // Assuming customers cannot exist without orders. If OrderId is nullable, this line can be omitted.
order.Customers.Clear();
order.Customers,Add(new Customer { Name = "Roy" });
context.SaveChanges();
}
This starts to fall apart if the collections are not virtual. For instance:
using (var context = new MyDbContext())
{
var order = context.Orders.Find(1);
order.Customers = new []
{
new Customer { Name = "Roy" }
}.ToList();
context.SaveChanges();
}
if the customers collection is virtual, after the SaveChanges, order.Customers will report a collection size of 3 elements. If it is not virtual it will report the size as 1 element even though there are 3 records now associated to the order in the DB. This leads to all kinds of issues where projects get caught out with invalid data state, duplicate records and the like.
Cases where you're seeing some records getting missed will likely be due to missing the virtual on the references, manipulating the collections outside of what EF is tracking, or other manipulations of the tracking state. (a common issue when projects are set up to detach/re-attach entities from contexts.)
I'm using Entity Framework 5.0 for my MVC4 project. There's a problem with it. When i give a db model to any view, controller send model with no relationship
example;
I have User class and with relation departments
when i use it in controller
using(context)
{
var user = context.Find(id);
string department = user.Department.Name;
}
its working when call in context. but when i do that
using(context)
{
var user = context.Find(id);
return View(user);
}
and call in view like
Model.Department.Name
i got error.
Here is my answer but its not good
using(context)
{
var user = context.Find(id);
string department = user.Department.Name;
return View(user);
}
when i try to user Model.Department.Name in view i got no error i must do that for every relation when i use class as model. there is have better solution for this problem ? i want use all relationship in View without call these in controller.
I hope you can understand me, sorry my english.
On your DbContext you could use the .Include method to eagerly load the relations you need:
context.Users.Include(u => u.Department).FirstOrDefault(u => u.Id == id);
or if you are using an older version of entity Framework the generic version of this method might not be available:
context.Users.Include("Department").FirstOrDefault(u => u.Id == id);
The reason for this is that you haven't "loaded" the Department in your original code. As your context is wrapped in a using statement it's being disposed of before the view is created and therefore your user object lacks the data you want.
In your second code example you have specifically called into the related Department object and therefore it now exists within the User object.
You need to eager load the Department in your original line using something like
context.User.Include(c => c.Department).Find(id);
Now your user object should have this available in the view.
What are u trying to accomplish? List a view for a user with one or many departments?
My update method is not working in an ASP.NET MVC 3 application. I have used the following EF 4.1 code:
[HttpPost]
public ActionResult UpdateAccountDetails(Account account)
{
if (ModelState.IsValid)
{
service.SaveAccount(account);
}
}
and SaveAccount looks like this:
internal void SaveAccount(Account account) {
context.SaveChanges();
}
internal void SaveAccount(Account account) {
// Load current account from DB
var accountInDb = context.Accounts.Single(a => a.Id == account.Id);
// Update the properties
context.Entry(accountInDb).CurrentValues.SetValues(account);
// Save the changes
context.SaveChanges();
}
Alternative:
internal void SaveAccount(Account account) {
context.Entry(account).State = EntityState.Modified;
context.SaveChanges();
}
The problem here is that you're not accounting for the fact that Web pages are stateless. You probably pupulate your page with the account data returned from the database, but then the object is destroyed at the end of the request.
On postback, a new Acccount object is created by the model binder, but this one is not hooked up to the database, so your database context has no idea that it even exists. So when you call SaveChanges, nothing has changed as far as it is concerned.
You have to either get a new Account object from the database and update it's fields with the data from the model binder created Account, or attach the new account object to the database.
This article should help
http://msdn.microsoft.com/en-us/library/bb896271.aspx
You may need to add context.Accounts.Attach(account); to reattach your entity to the context
You aren't making any changes, so there is really nothing to be saved. The simplest way may be doing the following:
internal void SaveAccount(Account account)
{
context.Attach(account);
ObjectStateEntry entry = context.ObjectStateManager.GetObjectStateEntry(account);
entry.SetModified();
context.SaveChanges();
}
If you're using an .edmx model in your application, make sure the property StoreGeneratedPattern is set to none for all the fields that have to be updated.
EF doesn't tell you, and acts like it is storing the changes to the database, when in fact it's not.
I am trying to add a new comment to a comment table but all records in the table are being deleted with the exception of the one I added.
As an example: Let say I have an existing comment in the database for customer 1. I want to add a new comment.
In my controller I have the following:
List<CustomerComment> comments = _commentsRepository.CustomerComments.ToList();
CustomerComment newComment = new CustomerComment()
{
CustId = 1,
RevisionNumber = revNumber,
Comment = comment,
Customer = _commentRespository.GetCustById(1),
CommentDate = DateTime.Now,
UserId = 24,
Users = _commentsRepository.GetUserById(24)
};
comments.Add(newComment);
_commentsRepository.SaveComment();
In my repository I have the following:
public Int32 SaveComment(CustomerComment comment)
{
try
{
_DB.SubmitChanges();
}
catch
{
throw;
}
return comment.CommentId;
}
While stepping through I see no changes to the data until after I create the new comment and step into the SaveComment method. What is strange is that it shows the comments already in the table for Delete and the new comment for insert.
Not understanding why it thinks the existing comments should be deleted.
I have also tried InsertOnSubmit but it does the samething so I took it out.
One thing I have noticed is that the existing comment after loading in the controller (comments) has the customer object as null. When I create the new comment I am assigning the customer to the new comment (Customer = _commentRespository.GetCustById(1).) Is this causing the delete and why doesn't the object get created and assigned when loaded.
Some additional information is that I am using POCOs and an XML mapping file.
Maybe you should not add the comment to an in memory storage try adding the new comment to the data context instead. I am presuming in your repository you have the add method... So something like _commentsRepository.add(newComment); shoukd work...
Regardless of that, why are you storing the whole customer in the database and for that matter user? you should be storing only their ids no? when you need read onky data to be thrown out into the view you may require additional data such as the customer and user details, use a dto object for that. Persistance in one thing, viewing data with certain data possibly populated from various tables is another thing...
I'm using EF4 POCOs and UnitOfWork/repository patterns with MVC 3. I'm trying to understand how I would modify a new record that is to be inserted.
My service method to insert/update looks something like this (the repository is injected in the service constructor via IoC):
public void UpdateData(Guid id, int newValue)
{
MyPoco poco = _repository.FirstOrDefault(p => p.Id = id);
if (poco == null)
{
poco = new Poco
{
//set properties
};
_repository.Add(poco);
}
poco.SomeFieldToUpdate = newValue;
}
And my changes get persisted via my UnitOfWork on a UseUnitOfWorkAttribute action filter on my controller:
void IResultFilter.OnResultExecuted(ResultExecutedContext filterContext)
{
var unitOfWork = IoCFactory.Instance.CurrentContainer.Resolve<IUnitOfWork>();
unitOfWork.Commit();
}
Of course, this works fine if this is ever hit just once, for existing or new data. And it works fine on multiple passes if it already exists.
But if the Guid value doesn't exist in the table, then it tries to do multiple inserts if this is called multiple times.
So that's my dilemma. I understand why this doesn't work, I'm just not sure the proper way to fix it. Basically, I need to somehow get a reference to the existing POCO in the UnitOfWork, and somehow update it. But the UnitOfWork is not available in my service (by design) -- and I'm not even sure I know how to pull an entity out of the UoW and update it anyway.
Am I going about this wrong or am I overlooking something simple here? Or do I have a fundamental flaw in how I've designed this? I have a feeling I may be making this harder than it should be.
Thanks in advance.
The reason why this happens is because your entity is not saved yet and you execute query to get it. Query will not find it in database and correctly return null.
You should not need to use repository / unit of work / ObjectContex as internal storage of not saved entities among service calls. If you need it you should check your application design and refactor it because something is probably wrong.
Anyway you can get not saved entity from context but it is not very nice code. You will need special method on your repository to get entity by id. You will use it instead of calling FirstOrDefault. Something like:
public MyPoco GetById(Guid id)
{
MyPoco enity = context.ObjectStateManager.GetObjectStateEntries(EntityState.Added)
.Where(e => e.Entity != null && e.Entity.GetType() == typeof(MyPoco)))
.Select(e => (MyPoco)e.Entity)
.Where(p => p.Id == id)
.SingleOrDefault();
if (entity == null)
{
entity = context.MyPocos.FirstOrDefault(p => p.Id == id);
}
}
Do you set the id you pass into UpdateData as the key on the new Poco object, like so:
poco = new Poco
{
Id = id;
//set properties
};
If yes, you could query for the object not with FirstOrDefault but by using the TryGetObjectByKey in a repository method:
public Poco GetPocoByKey(Guid id)
{
EntityKey key = new EntityKey("MyEntities.Pocos", "Id", id);
object pocoObject;
if (context.TryGetObjectByKey(key, out pocoObject))
return (Poco)pocoObject;
return null;
}
The advantage is that TryGetObjectByKey looks at first into the ObjectContext if it can find an object with the specified key. Only if not, then the database will be queried. Since you add the new Poco to the context the first time it isn't found, TryGetObjectByKey should find it in the context when you search for the object with the same key a second time, even if it has not been saved to the database yet.
Edit: This solution doesn't work!
Because TryGetObjectByKey does not find the key for objects which are in added state in the ObjectContext, even not if the key is not a DB generated key and supplied by the application (see comments below).