What is the correct way to save a graph of objects whose state you don't know? By state I mean whether they are new or existing database entries that are being updated.
For instance, if I have:
public class Person
{
public int Id { get; set; }
public int Name { get; set; }
public virtual ICollection<Automobile> Automobiles { get; set; }
}
public class Automobile
{
public int Id { get; set; }
public int Name { get; set; }
public short Seats { get; set; }
public virtual ICollection<MaintenanceRecord> MaintenanceRecords { get; set ;}
public virtual Person Person { get; set; }
}
public class MaintenanceRecord
{
public int Id { get; set; }
public int AutomobileId { get; set; }
public DateTime DatePerformed { get; set; }
public virtual Automobile Automobile{ get; set; }
}
I'm editing models, similar to these objects above, and then passing those models into the data layer to save, where for this instance I happen to be using entity framework. So I'm translating these models into POCO entities internal to the DAL.
It appears that unless my models have a state indicating whether they are new or updated, I have quite a bit of work to do to "Save" the changes. I have to first select the Person entity, update it, then match any existing Automobiles and update those and add any new, then for each automobile check for any new or updated maintenance records.
Is there a faster/easier way of doing this? It's possible I can keep track of the Model state, which I guess would be helpful with this, but it would mean changes to code outside of the data layer which i would prefer to avoid. I'm just hoping there is a pattern of usage out there that I can follow for updates like this.
I ran into this issue a while back and have been following this thread on the EF Codeplex site. https://entityframework.codeplex.com/workitem/864
Seems like it is being considered for the next release, I'm assuming EF 7, which apparently is a pretty large internal overhaul of EF. This may be worth checking out... http://www.nuget.org/packages/RefactorThis.GraphDiff/
Back when I was working on this I found another EF post on SO, and someone had an example of how to do this manually. At the time I decided to do it manually, not sure why, GraphDiff looks pretty cool. Here is an example of what I did.
public async Task<IHttpActionResult> PutAsync([FromBody] WellEntityModel model)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var kne = TheContext.Companies.First();
var entity = TheModelFactory.Create(model);
entity.DateUpdated = DateTime.Now;
var currentWell = TheContext.Wells.Find(model.Id);
// Update scalar/complex properties of parent
TheContext.Entry(currentWell).CurrentValues.SetValues(entity);
//We don't pass back the company so need to attached the associated company... this is done after mapping the values to ensure its not null.
currentWell.Company = kne;
// Updated geometry - ARGHHH NOOOOOO check on this once in a while for a fix from EF-Team https://entityframework.codeplex.com/workitem/864
var geometryItemsInDb = currentWell.Geometries.ToList();
foreach (var geometryInDb in geometryItemsInDb)
{
// Is the geometry item still there?
var geometry = entity.Geometries.SingleOrDefault(i => i.Id == geometryInDb.Id);
if (geometry != null)
// Yes: Update scalar/complex properties of child
TheContext.Entry(geometryInDb).CurrentValues.SetValues(geometry);
else
// No: Delete it
TheContext.WellGeometryItems.Remove(geometryInDb);
}
foreach (var geometry in entity.Geometries)
{
// Is the child NOT in DB?
if (geometryItemsInDb.All(i => i.Id != geometry.Id))
// Yes: Add it as a new child
currentWell.Geometries.Add(geometry);
}
// Update Surveys
var surveyPointsInDb = currentWell.SurveyPoints.ToList();
foreach (var surveyInDb in surveyPointsInDb)
{
// Is the geometry item still there?
var survey = entity.SurveyPoints.SingleOrDefault(i => i.Id == surveyInDb.Id);
if (survey != null)
// Yes: Update scalar/complex properties of child
TheContext.Entry(surveyInDb).CurrentValues.SetValues(survey);
else
// No: Delete it
TheContext.WellSurveyPoints.Remove(surveyInDb);
}
foreach (var survey in entity.SurveyPoints)
{
// Is the child NOT in DB?
if (surveyPointsInDb.All(i => i.Id != survey.Id))
// Yes: Add it as a new child
currentWell.SurveyPoints.Add(survey);
}
// Update Temperatures - THIS IS A HUGE PAIN = HOPE EF is updated to handle updating disconnected graphs.
var temperaturesInDb = currentWell.Temperatures.ToList();
foreach (var tempInDb in temperaturesInDb)
{
// Is the geometry item still there?
var temperature = entity.Temperatures.SingleOrDefault(i => i.Id == tempInDb.Id);
if (temperature != null)
// Yes: Update scalar/complex properties of child
TheContext.Entry(tempInDb).CurrentValues.SetValues(temperature);
else
// No: Delete it
TheContext.WellTemperaturePoints.Remove(tempInDb);
}
foreach (var temps in entity.Temperatures)
{
// Is the child NOT in DB?
if (surveyPointsInDb.All(i => i.Id != temps.Id))
// Yes: Add it as a new child
currentWell.Temperatures.Add(temps);
}
await TheContext.SaveChangesAsync();
return Ok(model);
}
catch (Exception ex)
{
Trace.WriteLine(ex.Message);
}
return InternalServerError();
}
This is a huge pain to me too. I extracted the answer from #GetFuzzy to a more reusable method:
public void UpdateCollection<TCollection, TKey>(
DbContext context, IList<TCollection> databaseCollection,
IList<TCollection> detachedCollection,
Func<TCollection, TKey> keySelector) where TCollection: class where TKey: IEquatable<TKey>
{
var databaseCollectionClone = databaseCollection.ToArray();
foreach (var databaseItem in databaseCollectionClone)
{
var detachedItem = detachedCollection.SingleOrDefault(item => keySelector(item).Equals(keySelector(databaseItem)));
if (detachedItem != null)
{
context.Entry(databaseItem).CurrentValues.SetValues(detachedItem);
}
else
{
context.Set<TCollection>().Remove(databaseItem);
}
}
foreach (var detachedItem in detachedCollection)
{
if (databaseCollectionClone.All(item => keySelector(item).Equals(keySelector(detachedItem)) == false))
{
databaseCollection.Add(detachedItem);
}
}
}
With this method in place I can use it like this:
public void UpdateProduct(Product product)
{
...
var databaseProduct = productRepository.GetById(product.Id);
UpdateCollection(context, databaseProduct.Accessories, product.Accessories, productAccessory => productAcccessory.ProductAccessoryId);
UpdateCollection(context, databaseProduct.Categories, product.Categories, productCategory => productCategory.ProductCategoryId);
...
context.SubmitChanges();
}
However when the graph gets deeper, I have a feeling this will not be sufficient.
What your looking for is the Unit of Work pattern:
http://msdn.microsoft.com/en-us/magazine/dd882510.aspx
You can either track UoW on the client and pass it in with the DTO or have the server figure it out. Both the veritable DataSet and EF Entities have their own internal implementation of UoW. For something stand alone there is this framework, but I have never used it so have no feedback:
http://genericunitofworkandrepositories.codeplex.com/
Alternatively another option is to do real time updates with undo functionality, kind of like when you go into Gmail contacts and it saves the changes as you make them with the option to undo.
It depends HOW you are accomplishing adding/changing the entities.
I think you may be trying to do too much with an entity at any given time. Allowing editing and adding at the same time can get you into a situation where your not sure what is being done with the entity, especially in a disconnected scenario. You should only perform a single action on a single entity at a time, unless you are deleting entities. Does this seem monotonous, sure, but 99% of your users want a clean and easily understandable interface. Many time we end up making screens of our applications "god" screens where everything and anything can be done. Which 9/10 times isn't needed (YAGNI).
This way, when you edit a user, you know you are doing an update operation. If you are adding a new maintenance record, you know you are creating a new record that is attached to an automobile.
To summarize, you should limit how many operations you are making available for a single screen and make sure you provide some type of unique information for the entity so you can try to look up the entity to see if it exists.
I had the similar problem, and couldnt find my own solution. I think that problem is complex. Complete solution for updating graphs in disconected scenario with EF6 I find in extension method RefactoringThis.GraphDiff produced by Brent McKendric.
Exemple brings by author is:
using (var context = new TestDbContext())
{
// Update the company and state that the company 'owns' the collection Contacts.
context.UpdateGraph(company, map => map
.OwnedCollection(p => p.Contacts, with => with
.AssociatedCollection(p => p.AdvertisementOptions))
.OwnedCollection(p => p.Addresses)
);
context.SaveChanges();
}
See more at:
http://blog.brentmckendrick.com/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a-graph-of-detached-entities/
Related
I have 2 managed objects in my database that look like this.
public class Product : RealmObject
{
public int Id { get; set; }
public string Name { get; set; }
public string Date { get; set; }
public RealmList<Report> Reports { get; } // child objects
}
and
public class Report : RealmObject
{
public int Id { get; set; }
public string Ref { get; set; }
public string Date { get; set; }
public Product Parent { get; set; } // Parent object reference
}
Each time my app is loaded a web hit fetches a list of Products and then starts managing them in a Realm database, it displays the Products in a TableView. When you click one of the products in the table view, you get a list of Reports. The list of reports is fetched by another web hit using the product id. Every time I get a new list of reports from the web I need to remove all the old Report objects from the Realm database that are linked to one specific product (by id).
Herein lies the confusion. According to this https://realm.io/docs/xamarin/latest/#current-limitations cascading deletes is currently not supported. I assume that means deleted objects in a relationship like I have above. So for the time being what is the best approach to remove the child objects (RealmList) without breaking things. I have come up with 2 approaches so far. Here's some code.
Approach A:
// id is passed in as a param by function
var reportsById = realm.All<Report>.Where(r => r.Product.Id == id).ToList();
foreach (var report in reportsById)
{
// Delete an object with a transaction
using (var trans = realm.BeginWrite())
{
realm.Remove(report);
trans.Commit();
}
}
// Then simply add the new reports to my old Product
// Pseudo code
var newreports = getnewreports()
foreach report in newreports
product.Reports.add(report)
Approach B:
// Get the current Product object
var currentProduct = realm.All<Product>.Where(p => p.Id == id).ToList().FirstOrDefault();
foreach (var report in currentProduct.Reports)
{
// Delete an object with a transaction
using (var trans = realm.BeginWrite())
{
realm.Remove(report);
trans.Commit();
}
}
// Add new reports to product again
And finally this is the approach I used to add my child objects (reports from the web) to the parent (product).
// First
var webReports = await FetchWebReport(); // IList<Report> type
/...../
// Then
var currentProduct = Realm.blah()... // get from realm database with query
foreach (var report in webReports)
{
// Manage object with a transaction
using (var trans = realm.BeginWrite())
{
// Add reference to parent product
report.Parent = currentProduct;
// Add to child list in product
currentProduct.Reports.Add(report);
trans.Commit();
}
}
Has anybody got any ideas/input? Feel free to to pick apart my current code. Point out issues. Thanks Realm Devs. =)
Official Realm answer - you were nearly right with B ;-)
Note the following sample uses the Write(lambda) style rather than explicit transaction creation and commit. It's a bit more concise but doing the same work.
I'm also looping inside the transaction rather than doing many transactions. It's faster and means the collection of related updates are in a single transaction.
create some sample hierarchies
realm.Write (() => {
for (var pid = 1; pid <= 4; ++pid) {
var p = realm.CreateObject<Product>();
p.Id = pid;
p.Name = $"Product {pid}";
for (var rid = 1; rid <= 5; ++rid) {
var r = realm.CreateObject<Report>();
r.Id = rid+pid*1000;
r.Ref = $"Report {pid}:{rid}";
p.Reports.Add(r); // child object added to relationship
}
}
});
Do the delete
Find an object we want to do a psuedo-cascading delete on - I'm directly using the LINQ First to get the object.
var delId = 1;
var delP = realm.All<Product>().First(p => p.Id == delId);
if (delP == null)
return;
Important fix to your sample - use ToList
realm.Write(() => {
foreach (var r in delP.Reports.ToList())
realm.Remove(r);
realm.Remove(delP); // lastly remove the parent
});
Your approach in B was nearly correct but it ignores the fact that foreach (var report in currentProduct.Reports) is iterating a live list. Because the Reports container is updated each time you remove something, it will exit the loop before removing all the children.
I was trying to create a generic method to update an Entity and all it's collection properties from a detached object. For example:
public class Parent{
public int Id { get; set; }
public string ParentProperty { get; set; }
public List<Child> Children1 { get; set; }
public List<Child> Children2 { get; set; }
}
public class Child{
public int Id { get; set; }
public string ChildProperty { get; set; }
}
So, my first intention was to use something like this:
Repository<Parent>.Update(parentObj);
It would be perfect have a magic inside this method that update Parent properties and compare the list of Children of the parentObj to the current values in database and add/update/remove them accordingly, but it's too complex to my knowledge about EF/Reflection/Generic... and so I tried a second more easier way like this:
Repository<Parent>.Update(parentObj, parent => parent.Children1
parent => parent.Children2);
This method would be a little harder to use, but yet acceptable. But how I think the second parameter had to be params Expression<Func<TEntity, ICollection<TRelatedEntity>>>[] relatedEntities I had problems to specify multiple TRelatedEntity. So my try was to 3rd step with no success yet...
Now I tried to call a method to update Parent and a sequence of methods to update Childreen, like this:
Repository<Parent>.Update(parentObj);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children1);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children2);
And the code:
public virtual void Update(TEntity entityToUpdate)
{
context.Entry(entityToUpdate).State = EntityState.Modified;
}
public virtual void UpdateChild<TRelatedEntity>(TEntity entityToUpdate, Func<TEntity, object> keySelector, Expression<Func<TEntity, ICollection<TRelatedEntity>>> relatedEntitySelector) where TRelatedEntity: class
{
var entityInDb = dbSet.Find(keySelector.Invoke(entityToUpdate));
var current = relatedEntitySelector.Compile().Invoke(entityToUpdate);
var original = relatedEntitySelector.Compile().Invoke(entityInDb);
foreach (var created in current.Except(original))
{
context.Set<TRelatedEntity>().Add(created);
}
foreach (var removed in original.Except(current))
{
context.Set<TRelatedEntity>().Remove(removed);
}
foreach (var updated in current.Intersect(original))
{
context.Entry(updated).State = EntityState.Modified;
}
context.Entry(entityInDb).State = EntityState.Detached;
}
First problem was to get original values, because when I call dbSet.Find the entity is already in context (context.Entry(entityToUpdate).State = EntityState.Modified;).
So I tried to change order calling first Child:
Repository<Parent>.Update(parentObj);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children1);
Repository<Parent>.UpdateChild(parentObj, parent => parent.Id, parent => parent.Children2);
And now I have the error:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
In summary, it would be very nice the first way, but I would be satisfied with the second/third too.
Thanks very much
Edit 1
Please, I need a native solution or using Automapper (which we already use in the project), because my customer don't like external dependencies and if we need to adapt something to the project, like working with Attached objects to update their related entities, so GraphDiff mencioned in the comments doesn't fit our needs (and VS 2015 RC crashed when I tried to install the package for tests)
Have you considered getting the object from the DB and using AutoMapper to modify all the property values?
I mean:
var obj = GetObjectFromDB(...);
AutoMapObj(obj, modifiedObj);
SaveInDb();
After spending a few days trying to set up a simple application with EF and DDD I have to say that I feel quite frustrated and think that I was better off using Linq-to-SQL and forget all about DDD and EF.
With EF
a) You cannot have proper readonly collections
b) When you remove something from a collection of child items you quite often get that The relationship could not be changed because one or more of the foreign-key properties is non-nullable message
c) There is no easy way of deleting all child items of a parent and reinserting them
All these are pretty much show stoppers for me given that the workarounds I have found are quite nasty looking. Has someone managed to put together a simple repository that addresses these issues?
If yes would you be kind enough to share some code?!?
Also, and I know this is a big topic, does anyone have any hands on experience of any real world DDD benefits in large scale web applications? We all know the theory but it would be nice to have an idea if it is actually worth the hassle!
Ok, the best i can do so far without having to do all sorts of carzy workarounds is to use
AsNoTracking() when i query something. That way i get my info and EF leaves alone without doing
whatever the hell it does behind my back. I can now Remove from a collection and i can kind of
be able to delete as well (who would think that i d have to go back to sql fro this!)
Does anyone know any pitfalls of using AsNoTracking? As far as i can genearate SQL based
on my objects and populate them or update/delete them i am fine. The whole tracking thing
goes too far anyway?
namespace EShop.Models.Repositories
{
public class CustomerRepository : BaseRepository, IRepository<Customer, Int32>
{
public CustomerRepository() : base(new EShopData()) { }
#region CoreMethods
public void InsertOrUpdate(Customer customer)
{
if (customer.CustomerId > 0)
{
// you cannot use remove, if you do you ll attach and then you ll have issues with the address/cards below
// dbContext.Entry<CustomerAddress>(address).State = EntityState.Added; will fail
dbContext.Database.ExecuteSqlCommand("DELETE FROM CustomerAddress WHERE CustomerId = #CustomerId", new SqlParameter("CustomerId", customer.CustomerId));
dbContext.Database.ExecuteSqlCommand("DELETE FROM CreditCard WHERE CustomerId = #CustomerId", new SqlParameter("CustomerId", customer.CustomerId));
foreach (var address in customer.Addresses)
dbContext.Entry<CustomerAddress>(address).State = EntityState.Added;
foreach (var card in customer.CreditCards)
dbContext.Entry<CreditCard>(card).State = EntityState.Added;
dbContext.Entry<Customer>(customer).State = EntityState.Modified;
}
else
{
dbContext.Entry<Customer>(customer).State = EntityState.Added;
foreach (var card in customer.CreditCards)
dbContext.Entry<CreditCard>(card).State = EntityState.Added;
foreach (var address in customer.Addresses)
dbContext.Entry<CustomerAddress>(address).State = EntityState.Added;
}
}
public void Delete(int customerId)
{
var existingCustomer = dbContext.Customers.Find(customerId);
if (existingCustomer != null)
{
//delete cards
var creditCards = dbContext.CreditCards.Where(c => c.CustomerId == customerId);
foreach (var card in creditCards)
dbContext.Entry<CreditCard>(card).State = EntityState.Deleted;
//delete addresses
var addresses = dbContext.CustomerAddresses.Where(c => c.CustomerId == customerId);
foreach (var address in addresses)
dbContext.Entry<CustomerAddress>(address).State = EntityState.Deleted;
//delete basket
dbContext.Entry<Customer>(existingCustomer).State = EntityState.Deleted;
}
}
public Customer GetById(int customerId)
{
return dbContext.Customers.Include("Addresses").AsNoTracking().SingleOrDefault(c => c.CustomerId == customerId);
}
public IList<Customer> GetPagedAndSorted(int pageNumber, int pageSize, string sortBy, SortDirection sortDirection)
{
return null;
}
public void Save()
{
dbContext.SaveChanges();
}
#endregion CoreMethods
#region AdditionalMethods
#endregion AdditionalMethods
}
}
Response to b: When you create your database, you must either cascade deletes (that is the database removes all related child records, too) or have the foreign key nullable. Then you won't get that error. This isn't to blame on EF, it's the way how an relational database handles constraints. You can configure this in your EDMX, your code first or using DDL on the database side. Depending on your decision how you did set up your project.
Response to c: more a general feeling, but deleting all children and reinserting sounds quite error prone and has a 'smell'. At least I would do that only if it is absolutely required. From a performance point of view, updating is probably faster. Maybe you can rethink the problem why you chose to delete and reinsert?
ok i think that i ve had enough of this for now so i ll summarize my rather negative experience
a) It is kind of possible but since this is version 5 i expected something better.
probably the easiest and simpler workaround can be found here
http://edo-van-asseldonk.blogspot.co.uk/2012/03/readonly-collections-with-entity.html
or i suppose you can even come up with your own readonly collection specific to the issue at hand
such as BasketProductsReadOnlyCollection if you have a basket and a collection of its products.
b) Probably we do not have to worry about a anyway. In a "stroke of genius" microsoft made it pretty
much impossible to write proper DDD code given the problem here. If you have a Basket and Products
with a BasketId in your Products table that is not nullable then you are in trouble if you do
Basket.RemoveProduct(product). Removing something like this means that the "relationship" is removed not the record. So EF will try to set BasketId to null and if it cant it ll throw an exception (and
no i dont want to make it nullable just to suit EF, even if i wanted what if i work with a DBA who doesnt?) what you need to do is call dbContext.Products.Remove(product) to make sure that it is deleted. That basically means that your business logic code needs to be aware of dbContext
c) I cant be bothered any more! Again there are responses about this on StackOverflow and you can possibly get something up and running but it should not be that difficult and counter intuitive.
As for the bigger picture, i had a look at the N-Tier recommendations that work with "Detached "Entities. I read a book from Julia Lerman who seems to be the authority on the subject and i m not impressed. The way the whole attaching an object graph works and the recommended ways of handling this are again very counter intuitive. Her recommended approach to make things "simple" was to have each object record its state in your business code! not my cup of tea.
I dont consider myself an architectural genius or something and perhaps i m missing something (or a lot) but to me EF's efforts seem to be misplaced. They spent so much time and money implementing this
whole tracking system that is supposed to do everything for you (typical MS, they think we are too stupid or something to look after our own stuff) instead of focusing on other things that could make this prouduct a lot easier to use.
What i want from my ORM is to deliver the data for me in my objects, then LEAVE ME ALONE to process
them in whatever way i want and then i want to pass my object or object graph back to the ORM and have the freedom to tell it what i want to add/delete/update from the object graph and how without the current shenanigans of EF.
Bottom line: i think i ll give MS a couple more years on this, they ll probably get it right in the end but this is not for me yet. And will MS finally put some proper documentation/tutorials on their sites? I remember reading a 300 hundred pages PDF tutorial on NHibernate years ago.
In case anyone else is struggling with this, this is the best implementation i could come up with, look at the RemoveFromBasket, AddToBasket methods, not ideal but at least you get something up & running
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Helpers;
using EShop.Models.DomainModel;
using System.Data;
using EShop.Models.DataAccess;
using System.Data.Objects;
using System.Data.Entity.Infrastructure;
namespace EShop.Models.Repositories
{
public class BasketRepository : BaseRepository, IRepository<Basket, Int32>
{
public BasketRepository() : base(new EShopData()) { }
#region CoreMethods
public void InsertOrUpdate(Basket basket)
{
var basketInDB = dbContext.Baskets.SingleOrDefault(b => b.BasketId == basket.BasketId);
if (basketInDB == null)
dbContext.Baskets.Add(basket);
}
public void Delete(int basketId)
{
var basket = this.GetById(basketId);
if (basket != null)
{
foreach (var product in basket.BasketProducts.ToList())
{
basket.BasketProducts.Remove(product); //delete relationship
dbContext.BasketProducts.Remove(product); //delete from DB
}
dbContext.Baskets.Remove(basket);
}
}
public Basket GetById(int basketId)
{
// eager-load product info
var basket = dbContext.Baskets.Include("BasketProducts")
.Include("BasketProducts.Product.Brand").SingleOrDefault(b => b.BasketId == basketId);
return basket;
}
public IList<Basket> GetPagedAndSorted(int pageNumber, int pageSize, string sortBy, SortDirection sortDirection)
{
throw new NotImplementedException();
}
public void Save()
{
dbContext.SaveChanges();
}
#endregion CoreMethods
#region AdditionalMethods
public void AddToBasket(Basket basket, Product product, int quantity)
{
var existingProductInBasket = dbContext.BasketProducts.Find(basket.BasketId, product.ProductId);
if (existingProductInBasket == null)
{
var basketProduct = new BasketProduct()
{
BasketId = basket.BasketId,
ProductId = product.ProductId,
Quantity = quantity
};
basket.BasketProducts.Add(basketProduct);
}
else
{
existingProductInBasket.Quantity = quantity;
}
}
public void RemoveFromBasket(Basket basket, Product product)
{
var existingProductInBasket = dbContext.BasketProducts.Find(basket.BasketId, product.ProductId);
if (existingProductInBasket != null)
{
basket.BasketProducts.Remove(existingProductInBasket); //delete relationship
dbContext.BasketProducts.Remove(existingProductInBasket); //delete from DB
}
}
public void RemoveFromBasket(BasketProduct basketProduct)
{
var basket = dbContext.Baskets.Find(basketProduct.BasketId);
var existingProductInBasket = dbContext.BasketProducts.Find(basketProduct.BasketId, basketProduct.ProductId);
if (basket != null && existingProductInBasket != null)
{
basket.BasketProducts.Remove(existingProductInBasket); //delete relationship
dbContext.BasketProducts.Remove(existingProductInBasket); //delete from DB
}
}
public void ClearBasket(Basket basket)
{
foreach (var product in basket.BasketProducts.ToList())
basket.BasketProducts.Remove(product);
}
#endregion AdditionalMethods
}
}
Ok, looks like i ve managed to get everything working with EF 5 more or less the way i want them.
Problem b seems to be ok with EF5. I think that i now have a proper DDD basket class and a proper repository so quite happy with that, perhaps i wan unfair being too harsh with EF after all!
public partial class Basket
{
public Basket()
{
this.BasketProducts = new List<BasketProduct>();
}
public int BasketId { get; set; }
public int? CustomerId { get; set; }
public decimal TotalValue { get; set; }
public DateTime Created { get; set; }
public DateTime Modified { get; set; }
public ICollection<BasketProduct> BasketProducts { get; private set; }
public void AddToBasket(Product product, int quantity)
{
//BUSINESS LOGIC HERE
var productInBasket = BasketProducts.SingleOrDefault(b => b.BasketId == this.BasketId && b.ProductId == product.ProductId);
if (productInBasket == null)
{
BasketProducts.Add(new BasketProduct
{
BasketId = this.BasketId,
ProductId = product.ProductId,
Quantity = quantity
});
}
else
{
productInBasket.Quantity = quantity;
}
}
public void RemoveFromBasket(Product product)
{
//BUSINESS LOGIC HERE
var prodToRemove = BasketProducts.SingleOrDefault(b => b.BasketId == this.BasketId && b.ProductId == product.ProductId);
BasketProducts.Remove(prodToRemove);
}
}
}
public class BasketRepository : BaseRepository, IRepository<Basket, Int32>
{
public BasketRepository() : base(new EShopData()) { }
#region CoreMethods
//public void InsertOrUpdate(Basket basket, bool persistNow = true) { }
public void Save(Basket basket, bool persistNow = true)
{
var basketInDB = dbContext.Baskets.SingleOrDefault(b => b.BasketId == basket.BasketId);
if (basketInDB == null)
dbContext.Baskets.Add(basket);
if (persistNow)
dbContext.SaveChanges();
}
public void Delete(int basketId, bool persistNow = true)
{
var basket = this.GetById(basketId);
if (basket != null)
{
foreach (var product in basket.BasketProducts.ToList())
{
basket.BasketProducts.Remove(product); //delete relationship
dbContext.BasketProducts.Remove(product); //delete from DB
}
dbContext.Baskets.Remove(basket);
}
if (persistNow)
dbContext.SaveChanges();
}
public Basket GetById(int basketId)
{
// eager-load product info
var basket = dbContext.Baskets.Include("BasketProducts")
.Include("BasketProducts.Product.Brand").SingleOrDefault(b => b.BasketId == basketId);
return basket;
}
public IList<Basket> GetPagedAndSorted(int pageNumber, int pageSize, string sortBy, SortDirection sortDirection)
{
throw new NotImplementedException();
}
public void SaveForUnitOfWork()
{
dbContext.SaveChanges();
}
}
a) What are you trying to do in the first place? Can't you make the collection private and expose only public property that takes a snapshot of it?
b) To remove a child entity from the database, use dbcontext.ThatEntitySet.Remove(child), not parent.Children.Remove(child).
Or you can make an identifying relationship by making a foreign key in the child a part of the primary key. Then parent.Children.Remove(child) would remove a row from DB.
c) Seems that you're doing something stupid. If you provided details I would propose a different solution.
Big topic: Is your domain complex enough? Or you're just trying to apply... to force DDD patterns in a simple CRUD application? What business rules do you have? Invariants? What methods do your entities have? Are there any policies?
Why would you ever need an InsertOrUpdate method? I suppose that you invented it because you use just the same form for creating and updating an entity. That is a strong signal, that you're just doing a CRUD app.
Suppose I have the following model classes in an Entity Framework Code-First setup:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Person> People { get; set; }
}
The database created from this code includes a TeamPersons table, representing the many-to-many relationship between people and teams.
Now suppose I have a disconnected Person object (not a proxy, and not yet attached to a context) whose Teams collection contains one or more disconnected Team objects, all of which represent Teams already in the database. An object such as would be created by the following, just for example, if a Person with Id 1 and a Team with Id 3 already existed in the db:
var person = new Person
{
Id = 1,
Name = "Bob",
Teams = new HashSet<Team>
{
new Team { Id = 3, Name = "C Team"}
}
};
What is the best way of updating this object, so that after the update the TeamPersons table contains a single row for Bob, linking him to C Team ? I've tried the obvious:
using (var context = new TestContext())
{
context.Entry(person).State = EntityState.Modified;
context.SaveChanges();
}
but the Teams collection is just ignored by this. I've also tried various other things, but nothing seems to do exactly what I'm after here. Thanks for any help.
EDIT:
So I get that I could fetch both the Person and the Team[s] from the db, update them and then commit changes:
using (var context = new TestContext())
{
var dbPerson = context.People.Find(person.Id);
dbPerson.Name = person.Name;
dbPerson.Teams.Clear();
foreach (var id in person.Teams.Select(x => x.Id))
{
var team = context.Teams.Find(id);
dbPerson.Teams.Add(team);
}
context.SaveChanges();
}
This is a pain if Person's a complicated entity, though. I know I could use Automapper or something to make things a bit easier, but still it seems a shame if there's no way of saving the original person object, rather than having to get a new one and copy all the properties over...
The general approach is to fetch the Team from the database and Add that to the Person's Teams collection. Setting EntityState.Modified only affects scalar properties, not navigation properties.
Try selecting the existing entities first, then attaching the team to the person object's team collection.
Something like this: (syntax might not be exactly correct)
using (var context = new TestContext())
{
var person = context.Persons.Where(f => f.Id == 1).FirstOrDefault();
var team = context.Teams.Where(f => f.Id == 3).FirstOrDefault();
person.Teams.Add(team);
context.Entry(person).State = EntityState.Modified;
context.SaveChanges();
}
That's where EF s**ks. very inefficient for disconnected scenario. loading data for the update/delete and every for re-attaching updated, one cannot just attached the updated entity to the context as an entity with the same key might already existed in the context already, in which case, EF will just throw up. what need to be done is to check if an entity with the same key is already in the context and attached or updated accordingly. it's worse to update entity with many to many relationship child. removing deleted child is from the child's entity set but not the reference property, it's very messy.
You can use the Attach method. Try this:
using (var context = new TestContext())
{
context.People.Attach(person);
//i'm not sure if this foreach is necessary, you can try without it to see if it works
foreach (var team in person.Teams)
{
context.Teams.Attach(team);
}
context.Entry(person).State = EntityState.Modified;
context.SaveChanges();
}
I didn't test this code, let me know if you have any problems
I've been looking for a proper way to mark a property to NOT be changed when updating a model in MVC.
For example, let's take this small model:
class Model
{
[Key]
public Guid Id {get; set;}
public Guid Token {get; set;}
//... lots of properties here ...
}
then the edit method MVC creates looks like this:
[HttpPost]
public ActionResult Edit(Model model)
{
if (ModelState.IsValid)
{
db.Entry(model).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
now if my View does not contain the Token, it will be nullified through that edit.
I'm looking for something like this:
db.Entry(model).State = EntityState.Modified;
db.Entry(model).Property(x => x.Token).State = PropertyState.Unmodified;
db.SaveChanges();
The best way so far I found is to be inclusive and set all properties I want included by hand, but I really only want to say which ones to be excluded.
we can use like this
db.Entry(model).State = EntityState.Modified;
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();
it will update but without Token property
Anyone looking for how to achieve this on EF Core. It's basically the same but your IsModified needs to be after you add the model to be updated.
db.Update(model);
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();
#svendk updated:
And if you (as me) are wondering why model don't have the token either before or after db.SaveChanges(), it's because with Update, the entity is actually not retrieved - only an SQL Update clause is sent - so the context don't know of your model's preexisting data, only the information you gave it in db.Update(mode). Even if you Find(model.id) you are not getting your context updated, as there is already loaded a model in the context, it is still not retrieved from database.
If you (as me) wanted to return the finished model as it looks like in the database, you can do something like this:
db.Update(model);
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();
// New: Reload AFTER savechanges, otherwise you'll forgot the updated values
db.Entry(model).Reload();
Now model is loaded from database with all the values, the updated and the (other) preexisting ones.
Create new model that will have limited set of properties that you want to update.
I.e. if your entity model is:
public class User
{
public int Id {get;set;}
public string Name {get;set;}
public bool Enabled {get;set;}
}
You can create custom view model that will allow user to change Name, but not Enabled flag:
public class UserProfileModel
{
public int Id {get;set;}
public string Name {get;set;}
}
When you want to do database update, you do the following:
YourUpdateMethod(UserProfileModel model)
{
using(YourContext ctx = new YourContext())
{
User user = new User { Id = model.Id } ; /// stub model, only has Id
ctx.Users.Attach(user); /// track your stub model
ctx.Entry(user).CurrentValues.SetValues(model); /// reflection
ctx.SaveChanges();
}
}
When you call this method, you will update the Name, but Enabled property will remain unchanged. I used simple models, but I think you'll get the picture how to use it.
I made an easy way to edit properties of entities I will share with you.
this code will edit Name and Family properties of entity:
public void EditProfileInfo(ProfileInfo profileInfo)
{
using (var context = new TestContext())
{
context.EditEntity(profileInfo, TypeOfEditEntityProperty.Take, nameof(profileInfo.Name), nameof(profileInfo.Family));
}
}
And this code will ignore to edit Name and Family properties of entity and it will edit another properties:
public void EditProfileInfo(ProfileInfo profileInfo)
{
using (var context = new TestContext())
{
context.EditEntity(profileInfo, TypeOfEditEntityProperty.Ignore, nameof(profileInfo.Name), nameof(profileInfo.Family));
}
}
Use this extension:
public static void EditEntity<TEntity>(this DbContext context, TEntity entity, TypeOfEditEntityProperty typeOfEditEntityProperty, params string[] properties)
where TEntity : class
{
var find = context.Set<TEntity>().Find(entity.GetType().GetProperty("Id").GetValue(entity, null));
if (find == null)
throw new Exception("id not found in database");
if (typeOfEditEntityProperty == TypeOfEditEntityProperty.Ignore)
{
foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
{
if (!item.CanRead || !item.CanWrite)
continue;
if (properties.Contains(item.Name))
continue;
item.SetValue(find, item.GetValue(entity, null), null);
}
}
else if (typeOfEditEntityProperty == TypeOfEditEntityProperty.Take)
{
foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
{
if (!item.CanRead || !item.CanWrite)
continue;
if (!properties.Contains(item.Name))
continue;
item.SetValue(find, item.GetValue(entity, null), null);
}
}
else
{
foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
{
if (!item.CanRead || !item.CanWrite)
continue;
item.SetValue(find, item.GetValue(entity, null), null);
}
}
context.SaveChanges();
}
public enum TypeOfEditEntityProperty
{
Ignore,
Take
}
I guess you don't want the property to be changed just in some cases, because if you are not going to use it never in your application, just remove it from your model.
In case you want to use it just in some scenarios and avoid its "nullification" in the case above, you can try to:
Hide the parameter in the view with HiddenFor:
#Html.HiddenFor(m => m.Token)
This will make your original value to be kept unmodified and passed back to the controller.
Use TryUpdateModel: http://msdn.microsoft.com/en-us/library/dd460189(v=vs.108).aspx
Load again your object in the controller from your DBSet and run this method. You can specify both a white list and a blacklist of parameters that shall or shall not be update.
I use dapper but my solution will work for EF too. If you are potentially going to change your ORM in the future my solution might be better for you.
class Model
{
public Foo { get; set; }
public Boo { get; set; }
public Bar { get; set; }
// More properties...
public void SafeUpdate(Model updateModel, bool updateBoo = false)
{
// Notice Foo is excluded
// An optional update
if (updateBoo)
Boo = updateModel.Boo;
// A property that is always allowed to be updated
Bar = updateModel.Bar;
// More property mappings...
}
}
As you can observe I allow updates for only the properties that I wish.
A downside of my approach is that you'll need to manually update this method if you introduce new properties (that are allowed to be updated) to your model. But I believe this in not always a downside but sometimes an advantage, in the sense that you'll need to be aware of what is being updated, this might be beneficial in terms of security.
Let us see a demonstration of this approach.
// Some code, DI etc...
public IActionResult Put([FromBody] Model updateModel)
{
var safeModel = new Model();
safeModel.Update(updateModel);
// Add validation logic for safeModel here...
_modelRepository.Update(safeModel);
}