I want to get all records from a database with #where, then update them. To do this, I have created a query like this:
public async Task MarkAllAsActive()
{
var currentUserId = _userManager.GetCurrentUserId();
await _workOrders.Where(row => row.Status == WorkOrderStatus.Draft).ForEachAsync(row =>
{
row.Status = WorkOrderStatus.Active;
_uow.MarkAsChanged(row, currentUserId);
});
}
But this query selects all fields from the database which isn't good. To solve this I try to select just specific fields like ID, Status:
public async Task MarkAllAsActive()
{
var currentUserId = _userManager.GetCurrentUserId();
await _workOrders.Select(row=>new WorkOrder { Id=row.Id,Status=row.Status}).Where(row => row.Status == WorkOrderStatus.Draft).ForEachAsync(row =>
{
row.Status = WorkOrderStatus.Active;
_uow.MarkAsChanged(row, currentUserId);
});
}
But it return this error:
The entity or complex type 'DataLayer.Context.WorkOrder' cannot be constructed in a LINQ to Entities query.
I've seen a similar post and the same error, but my problem is different because I want to update.
How can I do this?
Sadly you have to fetch the entire entity.
In order to update an entity with EF, the class type edited has to be a DbContext mapped entity .
If you want to Update without fetching Entities to the server , and without writing any SQL you can use Entity Framework Extended Library .
See the update section on the site.
Fetching entity within same entity will not work in your case, as you are getting only selected columns. e.g. You are fetching WorkOrder entity in WorkOrder again.
I would suggest you to use DTO to load selected columns only. It should work. But at the time of update you will have to copy same to db object.
Related
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 hour ago.
Improve this question
I'm using .NET 6 and EF Core 6.0.13. I have two databases Foo and FooArchive with identical schemas. I need to archive (migrate) data that are older than a year from Foo to FooArchive for 7 tables. What's the best way to do this with EF Core? I will describe below what I tried and the issues I'm running into.
NOTE: There are no foreign keys or any relationships defined for any table in both DBs and so no navigation properties, etc.
There are FooContext and FooArchiveContext classes both using the same entity models but different connections and injected into the repository class
Query for the customer ids whose account is older than 365 days on a different transaction
var customerIds = GetCustomerIds();
Loop through CustomerIds collection and archive one customer at a time
foreach(var customerId in customerIds)
{
using(var fooTx = _fooContext.Database.BeginTransaction())
using(var fooArchiveTx = _fooArchiveContext.Database.BeginTransaction())
{
//Series of left joins to get the data from 7 tables
var recordsToArchive = (from cust in _fooContext.Customers
join ord in _fooContext.Orders on ord.CustId equals cust.Id into co
from ord in co.DefaultIfEmpty()
join odt in _fooContext.OrderDetails on odt.OrderId equals ord.id into ordt
.
where cust.id = customerId
select new {
Customer = cust,
Order = ord,
OrderDetail = odt,
.
.
}).ToList();
var customer = recordsToArchive.Select(x => x.Customer).Distinct().First();
var orders = recordsToArchive.Select(x => x.Order).Where(x != null).Distinct();
var orderDetails = recordsToArchive.Select(x => x.OrderDetail).Where(x != null).Distinct();
.
.
// Check if the record to be migrated is already in FooArchiveContext
var existingRecord = _fooArchiveContext.Customers.FirstOrDefault(x => x.Id == customerId);
if(existingRecord == null)
{
_fooArchiveContext.Customers.Add(customer);
_fooArchiveContext.Orders.AddRange(orders);
_fooArchiveContext.OrderDetails.AddRange(orderDetails);
.
}
else
{
_fooArchiveContext.Customers.Update(customer);
_fooArchiveContext.Orders.UpdateRange(orders);
_fooArchiveContext.OrderDetails.UpdateRange(orderDetails);
.
}
_fooArchiveContext.SaveChanges();
//Remove the record from fooContext
_fooContext.OrderDetails.RemoveRange(orderDetails);
_fooContext.Orders.RemoveRange(orders);
_fooContext.Customers.Remove(customer);
.
_fooContext.SaveChanges();
fooArchiveTx.Commit();
fooTx.Commit();
}
}
Is what I'm doing the right approach? I think I may have to use the AutoMapper to copy entities in between two contexts. It works in the InMemory database but fails when I try it against the actual SQL Server instances. I get an error
Cannot insert explicit value for identity column in table 'Orders' when IDENTITY_INSERT is set to OFF
I would like to keep the same Ids as in the original db instance (fooContext).
I guess I can remove the Id in the entity object and save. Then query for the new Id and update the related entities but sounds tackier than the code I already have. I've seen SO answers where EF core is turning identity insert option on and off before and after calling SaveChanges() like below but haven't tried.
db.Users.Add(user);
db.Database.ExecuteSqlRaw("SET IDENTITY_INSERT MyDB.Users ON");
db.SaveChanges();
db.Database.ExecuteSqlRaw("SET IDENTITY_INSERT MyDB.Users OFF");
transaction.Commit();
Thanks for your help.
If I understand your problem correctly
Your approach of looping through each customer and archiving their records one at a time seems reasonable. However, there are a few areas where you can improve your implementation.
Firstly, you should avoid querying the database multiple times for the same data. In your code, you are querying the same data multiple times to get the customers, orders, and order details. This can be improved by using the Include method to eagerly load the related entities along with the primary entity.
Secondly, you should avoid duplicating code. In your code, you have duplicate code for adding and updating the entities in the archive database. You can reduce the duplication by using the Attach method to attach the entities to the context and then calling Update or Add depending on whether the entity is already in the context or not.
Thirdly, you should use a bulk insert/update operation instead of adding/updating the entities one at a time. EF Core does not have built-in support for bulk operations, but you can use third-party libraries like Entity Framework Extensions or Z.EntityFramework.Plus to perform bulk operations.
Finally, you should avoid setting the identity column values explicitly. Instead, let the database generate the identity values for you. To do this, you can remove the identity column from your entity models or use the ValueGeneratedOnAdd() method in your entity configuration.
With these improvements in mind, here's an example implementation of your code:
using (var fooTx = _fooContext.Database.BeginTransaction())
using (var fooArchiveTx = _fooArchiveContext.Database.BeginTransaction())
{
var cutoffDate = DateTime.UtcNow.AddYears(-1);
var customerIds = _fooContext.Customers
.Where(c => c.CreatedAt < cutoffDate)
.Select(c => c.Id)
.ToList();
foreach (var customerId in customerIds)
{
var customer = _fooContext.Customers
.Include(c => c.Orders)
.ThenInclude(o => o.OrderDetails)
.FirstOrDefault(c => c.Id == customerId);
if (customer != null)
{
if (_fooArchiveContext.Customers.Any(c => c.Id == customerId))
{
_fooArchiveContext.Attach(customer);
_fooArchiveContext.Update(customer);
}
else
{
_fooArchiveContext.Add(customer);
}
_fooArchiveContext.SaveChanges();
_fooArchiveContext.Orders.BulkInsert(customer.Orders);
_fooArchiveContext.OrderDetails.BulkInsert(customer.Orders.SelectMany(o => o.OrderDetails));
_fooContext.OrderDetails.RemoveRange(customer.Orders.SelectMany(o => o.OrderDetails));
_fooContext.Orders.RemoveRange(customer.Orders);
_fooContext.Customers.Remove(customer);
_fooContext.SaveChanges();
}
}
fooArchiveTx.Commit();
fooTx.Commit();
}
In this code, we first get the list of customer IDs whose accounts are older than a year. We then loop through each customer and retrieve their orders and order details using the Include method. We then check if the customer already exists in the archive database and use the Attach and Update methods to update the existing customer, or the Add method to add a new customer.
We then use the BulkInsert method from the Entity Framework Extensions library to insert the orders and order details in bulk. We also remove the orders, order details, and customer from the source database using the RemoveRange method.
Finally, we call SaveChanges on the archive and source contexts and commit the transactions.
I have the following code and would like to know if there is a way to refactor in order to remove duplicated logic.
This results current user with eager loading.
var currentEmployee = RosterContext.Employees
.Where(e => e.User.Id == id)
.Include(e => e.Job.Department).FirstOrDefault();
.
var job = RosterContext.Employees.Where(e=>e.Job.Department.Id == currentEmployee.Job.DepartmentId).ToList();
I created another same context which compares the first line of code to result all employee names who work in same department. My question is, as I am using two linq expression that uses the same context (Employees) am i able to combine both linq queries into one?
It may become a long linq expression but it should serve on getting the current user object followed by comparing user object to get all employees that share the same department id?
It makes sense to try an ORM framework, such as Entity Framework or NHibernate.
ORM framewok will model database FK relationship as a scalar property on one side and vector property (collection) on the other side of the relationship.
For instance Department would have a collection property Jobs, and a Job entity would have a scalar Department property.
DB queries with joins on FK become just dependency property navigation, for example - to access the list of employees in current department you would just return something like employee.Department.Employees - that is, assuming your entities are all loaded (which is rather simple to achieve in EF, using include statement)
In Entity Framework you have the using clause to attach children. So for example in pure EF you could do:
var department = context.Department.Include(d => d.Jobs).First(d => d.DepartmentId == departmentId);
https://msdn.microsoft.com/en-us/library/gg671236%28v=vs.103%29.aspx#Anchor_1
With a repository, you may need to do something like this:
EF Including Other Entities (Generic Repository pattern)
EF Code First supports relationships out of the box. You can either use the conventions or explicitly specify the relationship (for example, if the foreign key property is named something weird). See here for example: https://msdn.microsoft.com/en-us/data/hh134698.aspx
When you've configured your models right, you should be able to access department like so:
var currentUser = _unitOfWork.Employee.GetEmployeeByID(loggedInUser.GetUser(user).Id);
var job = currentUser.Job;
var department = job.Department;
// or
var department = _unitOfWork.Employee.GetEmployeeByID(loggedInUser.GetUser(user).Id).Job.Department;
To show all employees that work in the same department:
var coworkers = department.Jobs.SelectMany(j => j.Employees);
Update
To use eager loading with a single repository class (you shouldn't need multiple repository classes in this instance, and therefore don't need to use Unit of Work):
public class EmployeeRepository {
private readonly MyContext _context = new MyContext(); // Or whatever...
public IList<Employee> GetCoworkers(int userId) {
var currentEmployee = _context.Employees
.Where(e => e.UserId == userId)
.Include(e => e.Job.Department) // Use eager loading; this will fetch Job and Department rows for this user
.FirstOrDefault();
var department = currentEmployee.Job.Department;
var coworkers = department.Jobs.SelectMany(j => j.Employees);
return coworkers;
}
}
And call it like so...
var repo = new EmployeeRepository();
var coworkers = repo.GetCoworkers(loggedInUser.GetUser(user).Id);
You probably would be able to make the repository query more efficient by selecting the job and department of the current user (like I've done) and then the related jobs and employees when coming back the other way. I'll leave that up to you.
I have the following in my controller get method
private PeopleContext Peopledb = new PeopleContext();
private IARContext db = new IARContext();
public ActionResult OwnerList()
{
var owners = from s in db.Owners
where s.Dormant == false
orderby s.Post.PostName
select s;
var viewModel = owners.Select(t => new OwnerListViewModel
{
Created = t.Created,
Post = Peopledb.Posts.FirstOrDefault(x => x.PostId == t.SelectedPostId).PostName.ToString(),
});
return PartialView("_OwnerList", viewModel);
}
I'm getting this error when I try and load the page:
The specified LINQ expression contains references to queries that are associated with different contexts.
I know the issue is that LINQ cant query two different contexts but having tried several solutions on here I cant seem to fix the issue and sucesfully query the Peopledb.Posts table to find the related PostName to display for each instance in the db.Owners table.
You can try this:
var owners = (from s in db.Owners
where s.Dormant == false
orderby s.Post.PostName
select s).ToList();
This will execute the code in one context and have the List<Owner> in memory for the other context.
Also take a look for the execution of the .Select part, does it execute a separate query for each owner? If so you should optimize it, you can get the posts beforehand using the ids and then build your viewmodel.
Entity Framework context can work only with single database. If you want to get data from another database in this context, you can create proxy View, that will reflect this data in db of your dbcontext.
Just transition from Linq-to-Entities to Linq-to-Objects using AsEnumerable():
var viewModel = owners.AsEnumerable()
.Select(t => new OwnerListViewModel
...
I'm trying to do something that should be "simple", I want to pull out a piece of data from my database but I only want to pull out the description (the database table for this item has first name, last name, address etc etc).
So when I call my database call I want to just grab the description and then update it, I don't want to grab anything else as this will cost network power and may cause lag if uses multiple times in a few seconds.
Here is my code that i'm trying to fix
using (var context = new storemanagerEntities())
{
var stock = context.stocks.Where(x => x.id == model.Id).Select(
x => new stock()
{
description = x.description
}).FirstOrDefault();
stock.description = model.Description;
context.SaveChanges();
}
The error I am catching is this
**The entity or complex type 'StoreManagerModel.stock' cannot be constructed in a LINQ to Entities query.**
I'm sure using the "new" keyword is probably the problem, but does anyone have any ideas on how to solve this?
--update
This code works, but it doesn't seem to actually update the database
public void UpdateDescription(StockItemDescriptionModel model)
{
using (var context = new storemanagerEntities())
{
var stock = context.stocks.Where(x => x.id == model.Id)
.AsEnumerable()
.Select(
x => new stock
{
description = x.description
}).FirstOrDefault();
stock.description = model.Description;
context.SaveChanges();
}
}
At the moment it would seem it maybe my MySQl driver which is 6390, it seems to work in another version I tried, sorry I haven't found the answer yet
You can do it even without getting any entity from the database by creating a stub entity:
context.Configuration.ValidateOnSaveEnabled = false;
// Create stub entity:
var stock = new stock { id = model.Id, description = model.Description };
// Attach stub entity to the context:
context.Entry(stock).State = System.Data.Entity.EntityState.Unchanged;
// Mark one property as modified.
context.Entry(stock).Property("description").IsModified = true;
context.SaveChanges();
Validation on save is switched off, otherwise EF will validate the stub entity, which is very likely to fail because it may have required properties without values.
Of course it may be wise to check whether the entity does exist in the database at all, but that can be done by a cheap Any() query.
You can't project to a mapped entity type during an L2E query, you would need to switch the context back to L2F. For optimal performance it's recommended to use AsEnumerable over ToList to avoid materializing the query too early e.g.
var stock = context.stocks.Where(x => x.id == model.Id)
.AsEnumerable()
.Select(x => new stock
{
description = x.description
})
.FirstOrDefault();
As to your actual problem, the above won't allow you to do this as is because you have effectively created a non-tracking entity. In order for EF to understand how to connect your entity to your DB you would need to attach it to the context - this would require that you also pull down the Id of the entity though i.e.
Select(x => new stock
{
id = x.id,
description = x.description
})
...
context.stocks.Attach(stock);
stock.description = model.Description;
context.SaveChanges();
Is there any shorter way to do this update?
void Update(Table1 table1Entry, Table2[] table2entries)
{
entities.Table1.Attach(table1Entry);
var table2EntriesIds = table2entries.Select(a => a.Id);
var updates = entities.Table2
.Where(a => table2EntriesIds.Contains(a.Id));
foreach(var update in updates)
{
entities.Table2.Attach(update);
}
var deletions = entities.Table2
.Where(a => a.Table1Id == table1Entry.Id);
.Where(a => !table2EntriesIds.Contains(a.Id));
foreach(var deletion in deletions)
{
entities.DeleteObject(deletion);
}
var insertions = table2entries.Except(matches);
foreach(var insertion in insertions)
{
entities.AddToTable2(insertion);
}
entities.SaveChanges();
}
where Table2 has an Table1_Id foreign key.
The idea is correct. You can optimize it so for example you will not load separately relations to update and relations to delete but you will still have to manually synchronize current detached state of your entities with state in the database. The only way to synchronize the state of the entity graph is to do it manually per entity and relation.
The question is if your code works. I think it doesn't. It doesn't update any records because it doesn't change state of the records to modified. You also cannot attach again record loaded from the context. As the last point if those table1 and table2 are somehow related I don't see any code working with the relation itself (unless you use FK properties).