Entity framework savechanges() not working EF5, .Net4 - c#

I have the following code to update some inventory values...
private static void UpdateInventory(int prodId, int qty)
{
using (var context = new uStore7_1Entities())
{
//Get the catalogNo and ProductUnitID of the product passed in so we can find all identical products that might just be boxed differently
var currProdItem = context.Products.Where(c => c.ProductID.Equals(prodId))
.Select(c => new {c.CatalogNo, c.ProductUnitID}).FirstOrDefault();
//Get the units per box factor for calculating total product ordered
var prodIdAmount =
context.ProductUnits.Where(pa => pa.ProductUnitID.Equals(currProdItem.ProductUnitID))
.Select(pa => pa.Amount)
.FirstOrDefault();
//Calculate the total number of units for this item
var prodUnits = qty*prodIdAmount;
//Get the entire list of products with the specified catalog number excluding the product passed in
var uStoreProducts =
context.Products.Where(p => p.CatalogNo.Equals(currProdItem.CatalogNo) && !p.ProductID.Equals(prodId))
.Select(p => p.ProductID);
//Loop through each product in the uStoreProductsList
foreach (var uStoreProduct in uStoreProducts)
{
var currentProduct = uStoreProduct;
//Get the current product's ProductUnitId to get the 'pieces' per "box"
var currentUnitId =
context.Products.Where(u => u.ProductID.Equals(currentProduct))
.Select(u => u.ProductUnitID)
.FirstOrDefault();
//Use the ProductUnitId to get the "Amount" from the ProductUnits table.
var inventoryFactor =
context.ProductUnits.Where(i => i.ProductUnitID.Equals(currentUnitId))
.Select(i => i.Amount)
.FirstOrDefault();
//Divide the quantity passed
var qtyInUnits = prodUnits/inventoryFactor;
var inventory =
context.ProductInventories.Where(pi => pi.ProductID.Equals(currentProduct))
.Select(pi => pi.InventoryQuantity)
.FirstOrDefault();
/*var inventory = (from i in context.ProductInventories
where i.ProductID == currentProduct
select i).FirstOrDefault();
*/
if (inventory != null)
{
var newinv = inventory - qtyInUnits;
inventory = newinv;
//context.SaveChanges();
}
}
context.SaveChanges();
}
}
The SaveChanges() does not seem to be updating anything. I have debugged it and the inventory value gets changed to the value needed, but for some reason it isn't updating. I've tried it inside the loop and outside the loop, both with no change. Any ideas? What am I missing here?

Your problem is object replacement, rather than re-assignment. When "newinv" is created, it's detached from your context, whereas "inventory" is the attached object. When "newinv" is assigned to "inventory", then it loses the association with the context.
Try this:
var prodInventory =
context.ProductInventories
.Where(pi => pi.ProductID.Equals(currentProduct))
.FirstOrDefault();
if (prodInventory != null)
{
var newinv = prodInventory.InventoryQuantity - qtyInUnits;
prodInventory.InventoryQuantity = newinv; // This updates the actual context object now.
}

Your code does not update anything. All you do is gather entities and assign calculated values in local variables. But you never actually change a property of one of your entities.
Note that selecting properties of your entities, storing them in variables and replacing the value of this variable does not work. You need to select the entity if you want to modify it.

Related

How to make a List<> from 2 list of object

I have object CartItem and Product.
I need to make one List<> from these 2 objects.
Tried:
List<(CartItem,Product)> product = new List<(CartItem,Product)>();
Code:
public async Task<IActionResult> Index()
{
var username = User.Identity.Name;
if (username == null)
{
return Login();
}
var user = _context.Users.FirstOrDefault(x => x.Email == username);
List<CartItem> cart = _context.ShoppingCartItems.Where(s => s.IDuser.Equals(user.Id)).ToList();
List<Product> product = new List<Product>();
foreach (var item in cart)
{
product.Add(_context.Product.FirstOrDefault(x => x.id == item.ProductId));
}
return View(user);
}
The correct answer would be to not do that. What would be the expected result for 5 items and 2 products?
The code is loading both CartItem and Product objects from the database using loops. That's very slow as each object requires yet another query. This can be replaced with a single line producing a single SQL query.
If CartItem has Product and User properties (as it should) all the code can be replaced with :
var cart=_context.ShoppingCartItems
.Include(i=>i.Product)
.Where(s=>s.User.Email==userName)
.ToList();
EF Core will generate the SQL query that joins User, CartItem, Product together and returns items and their products, but no User data. The Product data will be available through the CartItem.Product property
What was asked but shouldn't be used
If a List<(CartItem,Product)> is absolutely required (why???) you could use that instead of a List<Product>, and add items inside the loop:
// PLEASE don't do it this way
var dontUseThis = new List<(CartItem,Product?)>();
foreach (var item in cart)
{
var product=_context.Product.FirstOrDefault(x => x.id == item.ProductId);
dontUseThis.Add((item,product));
}
This will result in one extra SQL query per cart item.
Slightly better
A slightly better option would be to generate a WHERE product.ID IN (....) clause, to load all relevant products in a single query. After that the two lists would have to be joined with JOIN.
var productIds=cart.Select(c=>c.ProductId);
var products=_context.Product
.Where(p=>productIds.Contains(p.Id))
.ToList();
var dontUseThis = products.Join(cart,
p => p.Id,
c => c.ProductId,
(c, p) => (c,p))
.ToList();
This will reduce the N+1 queries to 2. It's still slower than letting the database do the work though
First, see the answer of #Panagiotis Kanavos. Aside that, for combining part, you can do this:
List<CartItem> cartItems; // assuming you already have this
List<Product> products; // assuming you already have this
// The combination part
var result = from p in products
join ci in cartItems on p.Id = ci.ProductId // Find out how product and cartItem relates
select new (p,ci);
// Need List?
var resultingList = result.ToList();
You can make a struct and put both variables in there:
struct EntireProduct
{
CartItem cartItem;
Product product;
}
then you can create a list of that struct:
List<EntireProduct> product = new List<EntireProduct>();

Can I take advantage of List<T> functions Remove(T) and Insert(Int32, T) when using UpdateRange(IEnumerable<T>)?

I am attempting to implement a controller method to reorder image indexes that need to be saved in the database using EF Core.
I have the following controller method:
[HttpPost]
public async Task<JsonResult> ReorderImage(int p_iImageID, int p_iNewOrderIndex)
{
if (p_iImageID <= 0)
return Json(new { Status = "Error", Message = $"Unable to retrieve item image with ID of {p_iImageID}" });
ItemImage l_oItemImage = await _Context.ItemImages.FirstOrDefaultAsync(l_oImage => l_oImage.ID == p_iImageID);
if (l_oItemImage.IsNull())
return Json(new { Status = "Error", Message = $"Unable to retrieve item image with ID of {p_iImageID}" });
List<ItemImage> l_oItemImages = await _Context.ItemImages.Where(l_oImage => l_oImage.ItemID == l_oItemImage.ItemID)
.OrderBy(l_oImage => l_oImage.Order)
.ToListAsync();
l_oItemImages.Remove(l_oItemImage);
l_oItemImages.Insert(p_iNewOrderIndex, l_oItemImage);
foreach(ItemImage l_oImage in l_oItemImages)
{
l_oImage.Order = l_oItemImages.IndexOf(l_oImage);
if (l_oItemImages.IndexOf(l_oImage) == 0)
l_oImage.IsPrimary = true;
else
l_oImage.IsPrimary = false;
l_oImage.Uri = _AzureBlobStorage.GetBlobUri(_ItemImageAzureBlobContainerName, l_oImage.GetFileName());
}
_Context.ItemImages.UpdateRange(l_oItemImages);
await _Context.SaveChangesAsync();
return Json(l_oItemImages)
}
The order and data of l_oItemImages when calling UpdateRange() and subsequently SaveChangesAsync() appears correct to me.
I've been looking at this question which mentions not creating new classes and using UpdateRange(). This seems a bit different but I can see how this might be my issue.
Am I having this issue because I'm manipulating the objects of the list using Remove(l_oItemImage) and then Insert(p_iNewOrderIndex, l_oItemImage)? Or is it because I'm using ToListAsync() to begin with when I grab the item images?
EDIT: Tried Update(l_oItemImage) in place of UpdateRange(l_oItemImages) with same results. Added image of QuickWatch showing tacked entities both are correctly showing State = Modified as well as the expected changed values for int Order and bool IsPrimary properties.
EDIT 2: Added image of QuickWatch data with highlighted changed properties on entities.
Yes, you should be able to take advantage of the List methods however I think UpdateRange is unnecessary for this common task, here is an alternative implementation.
You may want to consider something like the following instead where the Sequence is reassigned for a subset of sequenced entities:
public async Task SetSequenceAsync(int forPageComponentId, int newSequence)
{
var infoAboutItemWereChangingSequenceFor = await context.PageComponents
.Where(x => x.Id == forPageComponentId)
.Select(x => new {
OriginalSequence = x.Sequence, // I need to know it's current sequence.
x.PageId // I need to only adjust sequences for items that have the same PageId, so I need to know what the pageId is for the item we're targeting.
}).FirstOrDefaultAsync();
// Get just the data we want to modify, we're going to include the item we're targeting so this list is inclusive of it.
// Including the item we're changing to make logic below a little mor consise instead of managing the list and the item we're targeting
// seperately.
var allItemsWithSequenceThatWillChange = await context.PageComponents
.Where(x =>
x.PageId == infoAboutItemWereChangingSequenceFor.PageId // Only those items sharing the same page Id.
// Only those items we need to change the sequence for.
&& x.Sequence >= Math.Min(infoAboutItemWereChangingSequenceFor.OriginalSequence, newSequence)
&& x.Sequence <= Math.Max(infoAboutItemWereChangingSequenceFor.OriginalSequence, newSequence)
)
.Select(x =>
new PageComponent() // The type of object EF knows about.
{
// The Primary key, so Entity Framework knows what record to change the sequence on.
Id = x.Id,
// The sequence value we need to change.
Sequence = x.Sequence
}
).ToListAsync();
// Set the sequence of the item we're targeting.
allItemsWithSequenceThatWillChange
.Where(x => x.Id == forPageComponentId)
.First()
.Sequence = newSequence;
// Now update the sequence on the other items (excluding the target item)
foreach (var item in allItemsWithSequenceThatWillChange.Where(x => x.Id != forPageComponentId))
{
// Either increment or decrement the sequence depending on how the original item was moved.
item.Sequence += infoAboutItemWereChangingSequenceFor.OriginalSequence > newSequence ? 1 : -1;
// Add any other property changes here.
}
// Save changes.
await context.SaveChangesAsync();
}
Also, as a matter of simplification on your ItemImage object, I notice you have an apparently DB persisted property "IsPrimary" - you may want to change this to be calculated on the entity and even at the db level instead, eg:
public class ItemImage {
// ... Other Properties ...
public int Order { get; set; }
public bool IsPrimary {
get => Order == 0;
set {}
}
}
For a calculated column in your MSSQL Database you can query against, add to your DbContext OnModelCreating:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity(typeof(ImageObject)).Property("IsPrimary").HasComputedColumnSql("CASE WHEN [Order] = 0 THEN 1 ELSE 0 END");
}

How to get the original values from a navigation collection property in Entity Framework Core?

With Entity Framework Core, I need to check the original values of a navigation property inside my entity but i can't find a way to do it.
I'm able to read the original and the current values of the actual entity and also his reference property but the OriginalValue property is missing when I read the navigation property.
Here's what I been able to do so far.
var entries = ChangeTracker.Entries<Book>()
.Where(x => x.State == EntityState.Modified)
.ToList();
foreach (var entry in entries)
{
// read the entity current & original values
var currentTitleValue = entry.Property(x => x.Title).CurrentValue;
var originalTitleValue = entry.Property(x => x.Title).OriginalValue;
// read the reference object current & original values
var promotionReferenceEntry = entry.Reference(x => x.Promotion);
var currentPromotionPriceValue = promotionReferenceEntry.TargetEntry.Property(x => x.Price).CurrentValue;
var originalPromotionPriceValue = promotionReferenceEntry.TargetEntry.Property(x => x.Price).OriginalValue;
// read the navigation object current & original values
var authorsCollectionEntry = entry.Collection(x => x.AuthorBooks);
var currentAuthorIds = authorsCollectionEntry.CurrentValue.Select(x => x.AuthorId).ToList();
var originalAuthorIds = ?????;
}
Here is a way to get the reference properties in the context as well as its original values:
var entries = ChangeTracker.Entries<Book>()
.Where(x => x.State == EntityState.Modified)
.ToList();
foreach (var entry in entries)
{
// If it is Added/Deleted, you can't get the OriginalValues/CurrentValues respectively.
// So make it Modified for the meanwhile.
var tempState = entry.State;
entry.State = EntityState.Modified;
// Clone the Entity values (the original ID, Current ID of the navigation
var currentValues = Entry(entry.Entity).CurrentValues.Clone();
var originalValues = Entry(entry.Entity).OriginalValues.Clone();
// Set the Entity values to the OriginalValues and load the reference
Entry(entry.Entity).CurrentValues.SetValues(originalValues);
Entry(entry.Entity).Reference(x => x.Promotion).Load();
// Store the Original Reference value in a variable
var promotionReferenceEntryOriginalValue = entry.Reference(x => x.Promotion).CurrentValue;
// Set the Entity values back to CurrentValues and load the reference
Entry(entry.Entity).CurrentValues.SetValues(currentValues);
Entry(entry.Entity).Reference(x => x.Promotion).Load();
// Store the Current Reference value in a variable
var promotionReferenceEntryCurrentValue = entry.Reference(x => x.Promotion).CurrentValue;
// Set the Entry State back to its original State Added/Modified/Deleted
entry.State = tempState;
// read the entity current & original values
var currentTitleValue = entry.Property(x => x.Title).CurrentValue;
var originalTitleValue = entry.Property(x => x.Title).OriginalValue;
// read the reference object current & original values
//var promotionReferenceEntry = entry.Reference(x => x.Promotion);
var currentPromotionPriceValue = promotionReferenceEntryCurrentValue.Price; // promotionReferenceEntry.TargetEntry.Property(x => x.Price).CurrentValue;
var originalPromotionPriceValue = promotionReferenceEntryOriginalValue.Price; // promotionReferenceEntry.TargetEntry.Property(x => x.Price).OriginalValue;
}
To make it dynamic, remove the type <Book> from ChangeTracker.Entries<Book>() and loop though the entry.Entity properties inside the foreach (var entry in entries) loop:
foreach (var propertyInfo in entry.Entity.GetType().GetProperties())
{
var propertyName = propertyInfo.Name;
//Do the loads here...
//Get the values
}
Check if it is a navigation reference or a collection reference by trying to access it using extra methods and check if it is null or not, not null means it is a valid property to try and load() it:
private DbPropertyEntry GetProperty(DbEntityEntry entry, string propertyName)
{
try
{
return entry.Property(propertyName);
}
catch { return null; }
}
private DbReferenceEntry GetReference(DbEntityEntry entry, string propertyName)
{
try
{
return entry.Reference(propertyName);
}
catch { return null; }
}
private DbCollectionEntry GetCollection(DbEntityEntry entry, string propertyName)
{
try
{
return entry.Collection(propertyName);
}
catch { return null; }
}

Linq replace column value with another value

Well, im doing a linq query to get a list of results with the same column, and then i need to replace that column value with a new one.
First Code:
var db = GetContext();
var result = from f in GetContext().ProjectStateHistories
where f.ProjectId.Equals(oldProjectId)
select f;
foreach (var item in result)
{
var projectStateHistoryUpdate = db.ProjectStateHistories.Find(item.Id);
projectStateHistoryUpdate.ProjectId = newProjectId;
db.Entry(projectStateHistoryUpdate).State = EntityState.Modified;
}
db.SaveChanges();
I searched for some answers, and i found that i can use Select, and make a new object (Linq replace null/empty value with another value)
Second Code:
var result = (from f in GetContext().ProjectStateHistories
where f.ProjectId.Equals(oldProjectId)
select f).Select(d=> new { Id = d.Id, EventName = d.EventName, LogUser = d.LogUser, ProjectId = newProjectId, TimeStamp = d.TimeStamp });
And even, Third Code:
var db = GetContext();
var result = (from f in db.ProjectStateHistories
where f.ProjectId.Equals(oldProjectId)
select f).Select(d=> new { ProjectId = newProjectId});
But only the First Code works.
I wanted to ask what i am doing wrong, since i think it is better to change the value with a query, instead of using a foreach.
See code below:
var db = GetContext();
(from f in db.ProjectStateHistories
where f.ProjectId.Equals(oldProjectId)
select f)
.ToList()
.ForEach(i => i.ProjectId = newProjectId);
db.SaveChanges();
Alternatively:
var db = GetContext();
db.ProjectStateHistories
.Where(f => f.ProjectId.Equals(oldProjectId))
.ToList()
.ForEach(f => f.ProjectId = newProjectId);
db.SaveChanges();
The shortest way I know of to replace your code is this:
var db = getcontext();
db.ProjectStateHistories
.Where(f => f.ProjectId.Equals(oldProjectId))
.ToList()
.ForEach(f => f.ProjectId = newProjectId);
db.SaveChanges();
Other answers can be found here
I've just had a thought that could help you, I am just free coding here!
If you just put the for each as part of the select, and then save your changes will that work?
foreach (var source in db.ProjectStateHistories.Where(x => x.ProjectId == oldProjectId))
{
source.ProjectId= newProjectId;
db.Entry(source).State = EntityState.Modified;
}
db.SaveChanges();
I think this is a more efficient way of doing it.
Also the .Select() method is only really useful if you need to Project to a view Model, it won't change the variables in the database, just show them in the newly declared object.
Thanks,
Phill

Matching objects by property name and value using Linq

I need to be able to match an object to a record by matching property names and values using a single Linq query. I don't see why this shouldn't be possible, but I haven't been able to figure out how to make this work. Right now I can do it using a loop but this is slow.
Heres the scenario:
I have tables set up that store records of any given entity by putting their primary keys into an associated table with the key's property name and value.
If I have a random object at run-time, I need to be able to check if a copy of that object exists in the database by checking if the object has property names that match all of the keys of a record in the database ( this would mean that they would be the same type of object) and then checking if the values for each of the keys match, giving me the same record.
Here's how I got it to work using a loop (simplified a bit):
public IQueryable<ResultDataType> MatchingRecordFor(object entity)
{
var result = Enumerable.Empty<ResultDataType>();
var records = _context.DataBaseRecords
var entityType = entity.GetType();
var properties = entityType.GetProperties().Where(p => p.PropertyType.Namespace == "System");
foreach (var property in properties)
{
var name = property.Name;
var value = property.GetValue(entity);
if (value != null)
{
var matchingRecords = records.Where(c => c.DataBaseRecordKeys.Any(k => k.DataBaseRecordKeyName == name && k.DataBaseRecordValue == value.ToString()));
if (matchingRecords.Count() > 0)
{
records = matchingRecords;
}
}
}
result = (from c in records
from p in c.DataBaseRecordProperties
select new ResultDataType()
{
ResultDataTypeId = c.ResultDataTypeID,
SubmitDate = c.SubmitDate,
SubmitUserId = c.SubmitUserId,
PropertyName = p.PropertyName
});
return result.AsQueryable();
}
The last statement joins a property table related to the database record with information on all of the properties.
This works well enough for a single record, but I'd like to get rid of that loop so that I can speed things up enough to work on many records.
using System.Reflection;
public IQueryable<ResultDataType> MatchingRecordFor(object entity)
{
var records = _context.DataBaseRecords;
var entityType = entity.GetType();
var properties = entityType.GetProperties().Where(p => p.PropertyType.Namespace == "System");
Func<KeyType, PropertyInfo, bool> keyMatchesProperty =
(k, p) => p.Name == k.DataBaseRecordKeyName && p.GetValue(entity).ToString() == k.DataBaseRecordValue;
var result =
from r in records
where r.DataBaseRecordKeys.All(k => properties.Any(pr => keyMatchesProperty(k, pr)))
from p in r.DataBaseRecordProperties
select new ResultDataType()
{
ResultDataTypeId = r.ResultDataTypeId,
SubmitDate = r.SubmitDate,
SubmitUserId = r.SubmitUserId,
PropertyName = p.PropertyName
});
return result.AsQueryable();
}
Hopefully I got that query language right. You'll have to benchmark it to see if it's more efficient than your original approach.
edit: This is wrong, see comments

Categories