Code
var items = await ctx.Cartitems.Where((c) => c.Cartid == GetCartId() && c.Toodeid == product).ToListAsync();
ctx.Cartitems.RemoveRange(items);
await ctx.SaveChangesAsync();
Removes product from shopping cart in EF Core.
It isseus to commands to database: SELECT and DELETE.
How to remove items from database so that only single command is sent to server ?
ASP.NET 5 MVC Core application using EF Core with Npgsql
If you have the CartItemIds, or the IDs that make up a composite PK, then you can short-cut the delete operations by attaching a stub cart ID and marking it for deletion. Depending on the lifetime scope of your DbContext and where this call sits, you many need to check each one against the local DbContext cache before attaching.
So, assuming your UI can pass a collection of CartItemIds to flush:
// This looks for any locally loaded cart items. We will remove these rather than
// attaching stubs which would raise an exception. This doesn't hit the DB.
var localCachedCartItems = _context.CartItems.Local
.Where(x => cartItemIds.Contains(x.CartItemId))
.ToList();
// Get the IDs to exclude from the Id list to create stubs.
var localCachedCartItemIds = localCachedCartItems
.Select(x => x.CartItemId)
.ToList();
// Build stubs for the remaining cart items.
var cartItemsToRemove = cartItemIds.Except(localCachedCartItemIds)
.Select(id => new CartItem { CartItemId = id })
.ToList();
foreach(var stub in cartItemsToRemve)
{
_context.Attach(stub);
_context.Entity(stub).State = EntityState.Deleted;
}
_context.SaveChanges();
If you are using locally scoped DbContexts (I.e. /w using blocks) then you can skip the Local check and just create the stubs:
using(var context = new AppDbContext())
{
var cartItemsToRemove = cartItemIds
.Select(id => new CartItem { CartItemId = id })
.ToList();
foreach(var stub in cartItemsToRemve)
{
_context.Attach(stub);
_context.Entity(stub).State = EntityState.Deleted;
}
context.SaveChanges();
}
Entity Framework primarily works as a change tracker, that records changes to tracked entities which are then executed and committed when calling SaveChanges. But in order to track changes like a removal, it needs to actually track those entities first. And for that, it needs to query those from the database to know which entities actually exist.
If you only want to delete entities by their primary identifier, then you could use some low-level stuff by attaching fake entities to the change tracker and then removing those, which causes EF to delete the objects from the database. Things like this require some more internal knowledge though and are not really easy to understand. So I’d recommend you to avoid approaches like these.
What you can do is use an extension that offers bulk updates on top of Entity Framework. These will usually go aroud the change tracker (meaning that the changes are executed immediately) and do not require you to load the entities into the context first. There are a few libraries that do this:
Entity Framework Extensions, a paid library with advanced functionality.
Entity Framework Plus, the free community version of EF Extensions. (GitHub)
EFCore.BulkExtensions
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.)
The code below returns a single record from my database, as-expected. However, it fails to track changes in the UI. I'm binding myObject's properties to various fields using angularjs. Everything initially loads fine, but if I make changes to the object and use breeze's EntityManager.saveChanges(), it doesn't detect than anything has changed. I can create a new entity and save it just fine, but trying to understand how to handle updates. Do I need to detach the entity after I retrieve it, then reattach and save?
Or is this the wrong way to assign a retrieved entity to my javascript (using TypeScript) object?
myObject = data.results[0];
let query = breeze.EntityQuery.from("myTable").where('id', '==', incomingId).expand(['relatedTable']);
dataService.executeQuery(query).then((data) => {
this.myObject = data.results[0];
});
I had to add '.toType("myType")' clause onto the query. I used 'metadataStore.registerEntityTypeCtor' to register my types in the EntityManager, but this step was still necessary for me, which I don't entirely understand.
let query = breeze.EntityQuery.from("myTable").where('id', '==', incomingId).expand(['relatedTable']).toType("myType");
dataService.executeQuery(query).then((data) => {
this.myObject = data.results[0];
});
Edit: I also found that using the EFContextProvider to do my querying improved my results and lessened issues incredibly. In the past, I had only used the Context, and while it worked, my entity types were not understood as well by Breeze on the client side.
readonly EFContextProvider<Context> _contextProvider = new EFContextProvider<Context>();
[HttpGet]
public IQueryable<Dispute> Disputes()
{
return _contextProvider.Context.Disputes;
}
I have this code in a Windows Service targeted to .Net 4.5 that uses a database-first Entity Framework layer:
var existingState = DataProcessor.GetProcessState(workerId);
existingState.ProcessStatusTypeId = (int)status;
existingState.PercentProgress = percentProgress;
existingState.ProgressLog = log;
DataProcessor.UpdateProcessState(existingState);
And this code in a data processing class in the same solution:
public ProcessState GetProcessState(int id)
{
using (var context = new TaskManagerEntities())
{
var processes = (from p in context.ProcessStates.Include("ProcessType").Include("ProcessStatusType")
where p.IsActive && p.ProcessStateId == id
select p);
return processes.FirstOrDefault();
}
}
public ProcessState UpdateProcessState(ProcessState processState)
{
using (var context = new TaskManagerEntities())
{
context.ProcessStates.Add(processState);
context.Entry(processState).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
return processState;
}
ProcessState is a parent to two other classes, ProcessStatusType and ProcessType. When I run that code in the windows service, it retrieves a record, updates the entity and saves it. Despite the fact that the ProcessType child is never used in the above code, when the save on the ProcessState entity is performed, EF does an insert on the ProcessType table and creates a new record in it. It then changes the FK in the ProcessStatus entity to point it at the new child and saves it to the database.
It does not do this in the ProcessStatusType table, which is set up with an essentially identical FK parent-child relationship.
I now have a database full of identical ProcessType entries that I don't need, and I don't know why this is occurring. I feel like I'm making some obvious mistake that I can't see because this is my first EF project. Is the issue that I'm allowing the context to expire in between calls but maintaining the same entity?
Using Add will set the state of all elements to Added, which is causing the child elements to be inserted. The parent element is not inserted as you specify EntityState.Modified for this element.
Try using the following in the UpdateProcessState rather than using Add.
context.ProcessStates.Attach(processState);
context.Entry(processState).State = EntityState.Modified;
context.SaveChanges();
Attach will set the state of all elements to Unchanged and by specifying Modified for the parent element you are indicating that only this element should be updated.
On another note. You should use the strongly-typed Include(x => x.ProcessType) rather than Include("ProcessType").
I am new to NHibernate. We are using Dapper for retrieval and planning to use NHibernate for CRUD.
I am trying to remove the child object through parent list object in one to many relationship.
This works when I retrieve the object using NHibernate session and remove the item.
var mercedes = Read("chevrolet"); //queries from nhibernate session
var model = mercedes.Models.Where(c => c.Id == 181).Single();
mercedes.Models.Remove(model);
When I manually create the object and attach thecars models, it is unable to remove it.
var mercedes = new Make() { Id = 77, Name = "chevrolet" };//manually created the object
mercedes.Models = GetAllModels(77);//I have it in-memory
var model = mercedes.Models.Where(c => c.Id == 173).Single();
mercedes.Models.Remove(model);
I think I am doing something weird. But I am able to add/update the models using second approach, so why can't i remove it. Any insights please.
When you create a new domain object it isn't attached to the NHibernate session (unless you are creating a new object and call Save, for instance). As such, deleting from the models collection doesn't actually do anything.
Your second example doesn't seem like a particularly good practice but you can recreate an object and attach it to your NHibernate session using Session.Lock (Merge or Update are fairly normal depending on your preferred behavior).
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).