I have class Customer and Order class.
Customer class has Order Class And DeliveryAddress class as virtual property
class Customer
{
public int custId{get;set;}
public string custName{get;set;}
[ForiegnKey("custId")]
public virtual ICollection<Order> Orders{get;set;}
[ForiegnKey("custId")]
public virtual ICollection<DeliveryAddress> Addresses{get;set;}
}
class Order
{
public int OrderId{get;set;}
public int custId{get;set;}
public string OrdDescription{get;set;}
[ForiegnKey("custId")]
public virtual Customer Customer{get;set;}
}
public class DomainService
{
public void CreateAndSave()
{
Customer A = new Customer();
mycontext.Entity(A).State= EntityState.Added;
Order ord1= new Order();
mycontext.Entity(ord1).State= EntityState.Added;
Order ord2= new Order();
mycontext.Entity(ord2).State= EntityState.Added;
DeliveryAddress adr1= new DeliveryAddress();
mycontext.Entity(adr1).State= EntityState.Added;
DeliveryAddress adr2= new DeliveryAddress();
mycontext.Entity(adr2).State= EntityState.Added;
A.DeliveryAddress.Add(adr1);
A.DeliveryAddress.Add(adr2);
mycontext.SaveChanges();
//After added records into Customer and Order table.I immediately call stored procedure which will update few columns in the newly added records
}
}
My Question is how do i update the in-memory instance of Customer and Order class instances in mycontext to get reflected with the changes done at stored procedure.
I have tried using Reload method but A.Orders shows count of 4, 2 records from above code and another 2 as Dynamic Proxies objects.
Another problem with dynamic proxies are when i update columns in one of the record in DeliveryAddress table, it is trying to update all column in Order table.
Please help how to reload and track object in in-memory so that i can proceed with further update using context in EF
Related
I'm having trouble retrieving the ID of newly added object in EF Core using the UoW pattern. I have this service:
public class OrderService : IOrderService
{
private IUnitOfWork _uow;
private IOrderRepository _orderRepository;
private IPaymentRepository _paymentRepository;
public OrderService(IUnitOfWork uow,
IOrderRepository orderRepository,
IPaymentRepository paymentRepository)
{
_uow = uow;
_orderRepository = orderRepository;
_paymentRepository = paymentRepository;
}
public int CreateOrder(Logic.Order order)
{
var id = _orderRepository.CreateOrder(order);
var payment = new Data.Payment();
payment.OrderId = id; // at this point, this is -2147353458 something
_paymentRepository.CreatePayment(payment);
// committed at this point but I can't get id unless I re-query
_uow.Commit();
// this is still -2147353458
return id;
}
}
So CreateOrder just adds a new order and then the newly generated ID is returned and used by the Payment object in CreatePayment. The problem with this since after adding, it is not committed yet so EF Core generates a temp id (something like -2147483324) so this is what I get. I then pass this to payment but this part is ok since I think EF is tracking it. The problem is what I return to the UI.
The service is called by the UI and after comitting, I can't get the ID. That's been my problem for hours now.
I've recently came across the same problem as well. Am here just to share my solution for reference.
Rather than to committing the transaction for the Id, you could try utilizing EF relationships.
Ex: the payment and order Model
public class Order
{
public int Id{ get; set; }
public Payment Payment { get; set; }
}
public class Payment
{
public int Id{ get; set; }
public int OrderId{ get; set; }
[ForeignKey("OrderId")]
public Order Order { get; set; }
}
Then in your transaction, you could simply assign the order to payment, EF will automatically insert the created Order Id to payment upon committing the transaction :
public int CreateOrder(Logic.Order order)
{
_orderRepository.CreateOrder(order);
var payment = new Data.Payment();
payment.Order = order;
_paymentRepository.CreatePayment(payment);
_uow.Commit();
return order.id;
}
You need to create an abstract method just like that "void Commit(EntityBase entity)" in your Uow, inside the method call your saveChanges this way you ensure that memory address is the same, so outside of the method you are able to access the property Id, be careful if you're using some Mapper because this may change you Memory Address. Make sure that your are using mapper only after Call UoW.Commit!
public class EntityBase
{
public int Id {get;set}
}
public class AnyOtherEntity : EntityBase
{
}
public void Commit(EntityBase entity)
{
yourContext.SaveChanges(entity);
}
Uow.Commit(AnyOtherEntity);
AnyOtherEntity.Id;
There is no option except commiting the transaction if you want to retrieve the generated unique Id immediately. Also,
// this is still -2147353458
return id;
You can't expect to be changed primitive type after commit. You should get it by order.Id, because after the transaction committed the EF will update entity because EF is tracking the entity.
public int CreateOrder(Logic.Order order)
{
_orderRepository.CreateOrder(order);
// commit the transaction
_uow.Commit();
var payment = new Data.Payment();
// Get the id of inserted row
payment.OrderId = order.Id;
_paymentRepository.CreatePayment(payment);
_uow.Commit();
return order.Id;
}
I have simplified the code below to show the root of the problem. My real code is using GenericRepository and UnitOfWork pattern but I get the same exception with this simplified code too.
I am using Entity Framework 6, Code First
It uses the following POCO entities
public class Order
{
public int Id {get;set;}
public virtual List<OrderProducts> OrderProducts {get;set;}
...
}
public class Product
{
public int Id {get;set;}
...
}
public class OrderProduct
{
public int OrderId {get;set;}
public int ProductId {get;set;}
public int Quantity
public virtual Order Order { get; set; }
public virtual Product Product{ get; set; }
}
The user is able to create a new product and add it to the order products on the same screen.
//Pull an order from the database:
var existingOrder = db.Orders.FirstOrDefault(x => x.Id == inputModel.OrderId);
//Iterate the OrderProductInputModels (IMs) in the Inputmodel
foreach (var orderProductIM in inputModel.OrderProductIMs )
{
var orderProduct = existingOrder.OrderProducts.SingleOrDefault(o => o.Id == orderProductIM.Id);
//if its an existing order product (already in db)
if (orderProduct != null)
{
//just update its property values
}
//if it has been added
else
{
//we need to create a new product first
var newProduct= new Product() { <set some properties> };
orderProduct= new OrderProduct()
{
Product=newProduct,
Order=existingOrder
}
//Add the OrderProduct to the order
existingOrder.OrderProducts.Add(orderProduct);
}
db.SaveChanges();
On save changes, I get the following error.
[System.Data.Entity.Infrastructure.DbUpdateConcurrencyException] = {"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries."}
Why is this?
I expected entity framework to see that the existingOrders nested properties were newly added and unattached, update the order and create the new OrderProduct and Product.
Should it not be other way around in your if clause as you are checking for null ( then only it is a new order product else update. Issue is here:
//if its an existing order product (already in db)
if (orderProduct == null)
{
//just update its property values
}
//if it has been added
else
{
When you are looping around all the OrderProducts, you are constantly updating the database but the existingOrder object is not getting refreshed. Update that or add all the objects first and then update the database.
Finally solved it by creating a test project and reverse code first engineering the database. Noticed that OrderProduct entity was not generated. On inspecting the database, the primary key was not set. Once I set the primary key in the database, the issue was resolved. Thanks for all the suggestions.
I have below model
public class Order
{
[Key]
public virtual string OrderNo {get;set;}
public virtual IList<OrderItem> Items {get;set;}
}
public class OrderItem
{
[Key]
public virtual string ItemNo {get; set;}
public virtual string ParentItemNo {get;set;}
public virtual string OrderNo {get;set;}
public virtual OrderItem ParentItem {get;set;}
public virtual IList<OrderItem> ChildItems {get;set;}
public virtual IList<ItemProperty> ItemProperties {get;set;}
public virtual Order Order {get;set;}
}
public class ItemProperty
{
[Key]
public virtual string PropertyNo {get; set;}
public virtual string ParentPropertyNo {get;set;}
public virtual string OrderItemNo {get;set;}
public virtual ItemProperty ParentProperty {get;set;}
public virtual IList<ItemProperty> ChildProperties {get;set;}
public virtual OrderItem OrderItem {get;set;}
}
I'm running on disconnected area (with service our disconnected from the Entity Framework context)
I create a order and save to database
client:
service.CreateOrder(new Order() { OrderN="fksjdf1" });
server:
using(EfDbContext context = new EfDbContext())
{
context.Orders.Add(order);
context.SaveChanges();
}
I need to add one or more OrderItems to previously added order
client:
var order = service.GetOrder("fksjdf1");
var item1 = new OrderItem();
item1.ItemNo="i1";
item1.Order=order;
item1.OrderNo=order.OrderNo;
item1.ItemProperties.Add(new ItemProperty()
PropertyNo="p1",
OrderItem = item1
})
order.Items.Add(item1);
var item2 = new OrderItem();
item2.ItemNo="i2";
item2.Order=order;
item2.OrderNo=order.OrderNo;
item2.ItemProperties.Add(new ItemProperty()
PropertyNo="p2",
OrderItem = item2
});
item2.ItemProperties.Add(new ItemProperty()
PropertyNo="p3",
OrderItem = item2
})
order.Items.Add(item2);
service.UpdateOrder(order);
server:
using(EfDbContext context = new EfDbContext())
{
DbEntityEntry dbEntityEntry = context.Entry(order);
if (dbEntityEntry.State == EntityState.Detached)
{
// ERROR
context.Set<Order>().Attach(order);
}
dbEntityEntry.State = EntityState.Modified;
context.SaveChanges();
}
Error: A referential integrity constraint violation occurred: The property values that define the referential constraints are not consistent between principal and dependent objects in the relationship.
Why I see this error? Why I not update this entity?
How can i use entity framework from disconnected area?
EDIT 1:
public Order GetOrder(string orderNo)
{
using (EfDbContext context = new EfDbContext())
{
context.Configuration.ProxyCreationEnabled = false;
var order = context.Orders
.Include(o => o.OrderItems
.Select(z => z.ItemProperties
.Select(y => y.ChildProperties)))
.Where(o => o.OrderNo == orderNo)
.FirstOrDefault();
}
}
You probably have this error because you don't set OrderItemNo in ItemProperty, like so:
var item1 = new OrderItem();
item1.ItemNo="i1";
item1.Order=order;
item1.OrderNo=order.OrderNo;
item1.ItemProperties.Add(new ItemProperty {
PropertyNo="p1",
OrderItem = item1,
OrderItemNo = item1.ItemNo // = "i1"
});
order.Items.Add(item1);
// and the same for item2
When the order gets attached to the context, related order items and item properties get attached as well. The navigation property OrderItem of ItemProperty refers to an entity with a key you supplied ("i1"), but the foreign key OrderItemNo doesn't have this key value. That's the inconsistency the exception is complaining about.
Edit
Unfortunately it is not that easy to update a detached object graph in the database. Setting the state of an entity to Modified only marks this entity as Modified and no related entity.
Your GetOrder method makes things complicated because you are eagerly loading already existing items and more related stuff to the client, then add new OrderItems on client side and send this new modified object graph back to the server. On server side you now have the problem that you must handle the already existing items differently (don't insert them into the database) than the new items (insert them into the database). To do this, you need to detect which items are old and which are new. If you don't transport some flag in the entities themselves that would indicate if the entity is new or not, you must query the database again and compare the object graph loaded from the DB with the detached object graph sent from the client. A general sketch how it can be done is here: https://stackoverflow.com/a/5540956/270591 (That's only for a parent with child collection. If grandchild collections are involved - like in your model - it is getting more complex.)
In my opinion you can simplify the whole procedure if you would not only have this global service.UpdateOrder(order) method that tries to handle all possible changes in the object graph but also some specialized methods that leverage the knowledge you have about the change of the graph - in this case a specialized method for adding new OrderItems to an existing order. It could look like this:
var order = service.GetOrder("fksjdf1");
var newOrderItems = new List<OrderItem>();
var item1 = new OrderItem();
item1.ItemNo="i1";
item1.OrderNo=order.OrderNo; // leave the Order property null
item1.ItemProperties.Add(new ItemProperty { PropertyNo="p1" });
newOrderItems.Add(item1);
// the same for item2
newOrderItems.Add(item2);
service.AddNewOrderItems(newOrderItems);
And the service method would be:
public void AddNewOrderItems(List<OrderItem> newOrderItems)
{
using(EfDbContext context = new EfDbContext())
{
foreach (var newOrderItem in newOrderItems)
context.Set<OrderItem>().Add(newOrderItem);
context.SaveChanges();
}
}
Hi I'm new with Entity Framework, this is what i need, have two classes, let's say "Customer" and "Orders":
Class CustomerBE<br/>
{
Public int CustomerID { get;set;}
...Other properties<br/>
}
Class OrderBE<br/>
{
Public int OrderID{ get; set; }
Public int CustomerID{ get;set;}
Public CustomerBE Customer {get;set}
}
How to mapping the order to customer ?, i have read some others posts and some others examples, what they are doing is to create a icollection of orders in customerBE, something like:
Class CustomerBE
{
Public int CustomerID{get;set;}
Public Virtual ICollection Orders<orderBE>{get;set;}
}
And then they map the customer to have many orders, using the orders collection in the customer class, but, for me that is incorrect, the consumer of the class, will be able to use the orders property on the customer class to access all the orders from the customer, and i don't what to let them do that:
ICollection<OrderBE> customerOrders = customer.Orders //I don't what this
In what scenario i would like to get all the customer orders?, usually we wan't the customer orders by a given criteria (date, status, etc.), so instead of use that property, for me, i think it's better to use the orders repository to access the customer orders by a given criteria, something like:
ICollection customerOrders = ordersRepository.GetCustomerOrdersByDate(customer.ID, Today.Date) //This is what i want, not orders = customer.oders
so any body knows how to do the mappings between order and customer using code first approach without have an orders collection in the customer class ?
Use the Fluent API in your DbContext class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<OrderBE>().HasRequired(x => x.Customer);
}
I'm trying to update multiple records using entity framework, but am not sure how to proceed.
Basic setup:
class Appointment
{
public int Id {get; set;}
public double Charge {get; set;}
public DateTime Time {get; set;}
}
The view presents a list of appointments, and then the call to controller Post action passes in an Ienumerable<Appointment>.
public async Task<int> UpdateAppointments(IEnumerable<Appointment> appointments){
// code goes here
var appointmentsToUpdate = await _context
.Appointments
.Where(a => a.time > DateTime.Now).ToListAsync();
// what to do here??
// loop through appointmentsToUpdate and find the relevant
// record inside appointment, and then do an update?
// Seems like a merge would be more efficient.
}
What i want to do is merge appointments and appointmentsToUpdate and update the appointment time. In another scenario, with a different authorization, I want the administrator, for example, to only be able to change the appointment charge, so deleting all records and appending the new records isn't an option.
It seems like you can do this with pure sql statements, but then the appointments parameter is passed in as an IEnumerable, not as a table already in the database as in this answer: Bulk Record Update with SQL
First of all, can you do this kind of update using Linq? Does it translate directly to entity framework (core)?
Without extension projects or store SQL the best you can do is to attach the Appointments as unchanged entities, and mark the target property as modified.
The Appointments you attach just need the Key Properties and the Time populated.
Like this:
class Db : DbContext
{
public DbSet<Appointment> Appointments { get; set; }
public void UpdateAppointmentTimes(IEnumerable<Appointment> appointments)
{
foreach(var a in appointments)
{
this.Appointments.Attach(a);
this.Entry(a).Property(p => p.Time).IsModified = true;
}
this.SaveChanges();
}
. . .
Which will update only the changed column for all those appointments in a single transaction.