public void getForm(string scode) {
Form result = DBContext.Forms.Where(f => f.Code == fCode && f.SCode == sCode).FirstOrDefault();
result.Products = result.Products.Where(p => p.Deleted== false).ToList(); // commenting this line fix the problem
return result;
}
How can we merge above two lines together to avoid below error.
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
Update:
When called in another function, it throw above error
public void savetrans(string fcode)
{
Form form = GetForm(fCode);
var transDb = new DbContext.Data.Transaction()
{
FId = form.FId,
field1= "test",
field2= "test",
field3= "test",
};
DbContext.Transactions.Add(transactionDb);
DbContext.SaveChanges();
}
If you want to delete products which are flagged as deleted:
var deletedProducts = result.Products.Where(x => x.Deleted).ToList();
foreach(var deletedProduct in deletedProducts)
result.Products.Remove(deletedProduct);
If Form.Products is a List<Product> then you can use .RemoveAll(x => x.Deleted)
If you just want to exclude deleted products when working with your entity then I'd recommend using an unmapped property for that purpose:
public class Form
{
public ICollection<Product> Products { get; set; } = new List<Product>();
[NotMapped]
public IReadOnlyCollection<Product> ActiveProducts => Products.Where(x => !x.Deleted).ToList().AsReadOnly();
// or
public IReadOnlyCollection<Product> ActiveProducts
{
get { return Products.Where(x => !x.Deleted).ToList().AsReadOnly();
}
}
Then when you want to use only the active products, use .ActiveProducts. The caveat of this approach is that you cannot use this property in EF Linq expressions, and it should only be used in a read-only capacity. For instance, don't try something like:
var products = context.Forms.Where(x => x.FormId == formId).SelectMany(x => x.ActiveProducts);
This will error out because EF wont have ActiveProducts mapped. You'd have to use .Products with the appropriate filter for Deleted.
I'll normally put logic like handling active/inactive when populating view models rather than at the entity level. Entities should reflect the data model, while business logic that applies to how you view/interact with that logic is represented by your view models. The benefit of using a view model is that the inspection of the Active/Deleted state is done once when the view models are populated, and they don't poison the entity with properties that aren't valid in certain uses.
Apparently your database has Forms and Products. There is a one-to-many relation between Forms and Products: Every Form has zero or more Products, every Product belongs to exactly one Form using a foreign key (probably Product.FormId)
Your first statement fetches one of your Forms that meets certain requirements or null if there is no such Form. Without checking the null-return value you try to change the Products of this Form.
The problem is, that there might be several Products that have a non-nullable foreign key to this Form. They are items in the ICollection of Products of the Form. If you assigning a new ICollection to Form.Products, entity framework wants to set the foreign key of the Products that were in Form.Products to zero, indicating that this product does not belong to any form anymore. But in your model description you defined that every Product should belong to exactly one Form. Hence the error.
So what you should do, depends on what you want with your procedure. Do you only want to Fetch the Form with its non-deleted products, then you should perform a query. If you want to remove all deleted products from this forms from the database, then you should perform an update
Query a certain Form with its non-deleted products
public ICollection<Form> GetFormWithNonDeletedProducts(string scode)
{
using (var dbContext = new MyDbContext(...))
{
return dBContext.Forms // from the collection of all Forms
.Where(form => form.Code == fCode
&& form.SCode == sCode) // keep only the ones that I want
.Select(form => new Form() // and create a new Form object
{ // with the properties I plan to use
Id = form.Id,
Name = form.Name,
...
Products = form.Products // Fetch only the non-deleted products
.Where(product => !product.Deleted)
.ToList(),
}
.FirstOrDefault();
}
}
}
The only reason I need to create a new Product object is because I want to put it in a return value. If you don't need it in a return value, you can put your fetched properties into an anonymous object. This is usually more efficient, because you won't fetch data you won't used from the database.
For example the above example will assign Product.FormId. You won't need it, becasue you know that all thousand Products of this Form will have the same value FormId: namely the value of Form.Id.
The same query without fetching properties you don't use (anonymous types)
using (var dbContext = new MyDbContext(...))
{
return dBContext.Forms // from the collection of all Forms
.Where(form => form.Code == fCode
&& form.SCode == sCode) // keep only the ones that I want
.Select(form => new // and create a new Form object
{ // with the properties I plan to use
Id = form.Id,
Name = form.Name,
...
Products = form.Products // Fetch only the non-deleted products
.Where(product => !product.Deleted)
.Select(product => new
{ // Select only properties you plan to use
Name = product.Name,
Price = product.Price,
// not meaningful: you already know the value:
// FormId = product.FormId,
})
.ToList(),
}
.FirstOrDefault();
}
Update the database: remove the deleted products of the form
Although your function is called GetForms it seems you want to use it to remove the deleted products.
The easiest way is to use DbSet<Products>.RemoveRange:
using (var dbContext = new MyDbContext(...))
{
// remove all Deleted products of the form with Code equal to fCode
// and SCode equal to sCode
var productsToDelete = dbContext.Products
.Where(product => product.Deleted
&& product.Form.Code == fCode
&& product.Form.Scode == sCode);
// note: query is not executed yet!
dbContext.Products.RemoveRange(productsToDelete);
dbContext.SaveChanges();
}
It might be that you'll have to do RemoveRange(productsToDelete.ToList()), you'll have to check this.
Related
I have two tables. One of them is Customers which is in the database, the other one is ChangedCustomers, which comes from the user. I write the updated model, I guess there is something missing.
public async Task<int> UpdateCustomers (IENumerable<ChangedCustomers> changedCustomers
{
foreach(var item in changedCustomers)
{
_context.Customers.Updaate (new Customers()
{
CustomerName=item.CustomerName,
CustomerAddress=item.CustomerAddress
});
}
return await _context.SaveChangesAsync();
}
In Method I am not saying something like "update that row when Id values in Customers and ChangedCustomers tables are equal". I need this but I can't. How can I do it?
You are trying update the row by adding a new row? Thats what it looks like. If you want to update a specific row, you will need to fetch that row, then update the values.
foreach (var item in changedCustomers)
{
var customer = _context.Customers
.FirstOrDefault(x => x.CustomerId == item.CustomerId);
if (customer != null)
{
customer.CustomerName = item.CustomerName;
customer.CustomerAddress = item.CustomerAddress;
}
else
{
customer = new Customer
{
CustomerName = item.CustomerName,
CustomerAddress = item.CustomerAddress
}
_context.Add(customer);
}
}
_context.SaveChangesAsync();
FirstOrDefault() will retrieve the first value from _context.Customers that matches the expression, if it doesn't find any it will default to null. If it is not null then you can make the changes, if it is null, you could add a new value.
Here's general logic that you'd follow to update an existing record which could be placed within your loop:
//Find the entity already tracked based on table key
var entity = context.Customers.FirstOrDefault(item => item.YourTableID == id);
// Validate entity is not null
if (entity != null)
{
// Make changes to specific field
entity.Name = "Me";
// Update entity in it's entirety
entity = new Customers() { //Your logic here to build the updated entity }
// Save changes in database
context.SaveChanges();
}
Just mark the entities as modified and call SaveChanges.
Only UPDATE sql queries will be executed.
The code in #jaabh answer is very inefficient, since it pre-executes sql-queries SELECT, reading from the database those entities that we already have. This is unnecessary.
public async Task<int> UpdateCustomers(IEnumerable<ChangedCustomers> changedCustomers)
{
foreach (var item in changedCustomers)
{
_context.Entry(item).State = EntityState.Modified;
}
return await _context.SaveChangesAsync();
}
Let's say I modify entities from different DbSets within a single DbContext.
How can I tell Entity Framework, when calling SaveChanges(), to save changes only for a specific DbSet?
Ideally, what you would do is just modify the entities you want to change, then call SaveChanges() then modify the other entities. However, if that is not possible I would add an overload of SaveChanges() that looks like this.
public int SaveChanges<TEntity>() where TEntity : class
{
var original = this.ChangeTracker.Entries()
.Where(x => !typeof(TEntity).IsAssignableFrom(x.Entity.GetType()) && x.State != EntityState.Unchanged)
.GroupBy(x => x.State)
.ToList();
foreach(var entry in this.ChangeTracker.Entries().Where(x => !typeof(TEntity).IsAssignableFrom(x.Entity.GetType())))
{
entry.State = EntityState.Unchanged;
}
var rows = base.SaveChanges();
foreach(var state in original)
{
foreach(var entry in state)
{
entry.State = state.Key;
}
}
return rows;
}
Initially you find all entities whose state is not unchanged and save their entry. Then you set the state of every entity that isn't of your type TEntity and set their state to unchanged. Then call the base.SaveChanges() to save all changes to entities of your type.
Then, by looping through the saved entries, you can reset their original states back to them. This way you catch all adds/updates/deletes that aren't of your type, and restore them so that the next call to SaveChanges() doesn't lose their changes.
Before using the below code, you should know if you want to save only a specific set of entities, then it means you should use different instance of your db context for that unit of your work.
But using this code you can reject changes of all entities except YourSpecialEntity:
db.ChangeTracker.Entries()
.Where(x => x.State == EntityState.Modified &&
!typeof(YourSpecialEntity).IsAssignableFrom(x.Entity.GetType()))
.ToList()
.ForEach(entry => {
entry.CurrentValues.SetValues(entry.OriginalValues);
});
db.SaveChanges();
I suppose db is an instance of your dbcontext.
I am new to EF and I had the same problem. In my storage program i e.g sumit only articleId and OwnerId for a new stock because i see no need for hidden fields in my mvc-form. At first EF created new entries in article and customer database, but i only wanted to insert a new item in the stocks table.
So the solution is quite simple:
For all entities you know you don´t want to save them type this:
//prevent Owner to be saved (again)
db.Entry(stockItem.Owner).State = EntityState.Unchanged;
//prevent article to be saved (again)
db.Entry(stockItem.ArticleItem).State = EntityState.Unchanged;
db.Stocks.Add(stockItem);
//now just the properties (-> dbset of the stockItem get´s saved)
await db.SaveChangesAsync();
return RedirectToAction("Index");
I am having issues finding a good way to convert Entities with their children to DTO objects. For this post I've created pseudo code which is a simplified example that leaves out the database context, DTO objects. Assuming I have a parent Entity and child Entity:
public class Parent {
int Id;
string Name;
List<Child> Children;
}
public class Child {
int Id;
string Name;
Parent Parent;
int ParentId;
}
I've looked at two possibilities, but I haven't been able to find a good solution. Please have a look at the two examples and where I got stuck.
1. Example using select queries
To retreive all the parent entities as DTO's, I could then in a Controller do:
public IHttpActionResult GetParents()
{
var children = from c in _context.Children
select new ChildDTO()
{
Id = c.Id,
Name= c.Name
};
var parents = from p in _context.Parents
select new ParentDTO()
{
Id = p.Id,
Name = p.Name
Children = children.ToList(),
};
return parents;
}
This will return the parent DTO object with all its children as DTO objects. If I wanted to create a new function to get just Parent with id '1', I would at the moment have to duplicate the select statement to add a where clause:
public IHttpActionResult GetParent(int parentId)
{
var parents = from p in _context.Parents where p.id == parentId
...
And there might also be cases where I do not want the child objects back if I just want to display a list of parents. Which would mean that I would basically have to duplicate the code and change the select to this:
select new ParentDTO()
{
Id = p.Id,
Name = p.Name
//Removed the Children
//Children = children.ToList(),
};
In this example I do not see a good way to reuse code as much as possible, so that I don't end up writing the same basic select statements over and over.
2. Example using Expressions
I could also create Expressions for the parent and child, but I would not know
private static readonly Expression<Func<Child, ChildDTO>> AsChildDTO =
p => new ChildDTO()
{
Id = p.Id,
Name = p.Name
};
private static readonly Expression<Func<Parent, ParentDTO>> AsParentDTO =
p => new ParentDTO()
{
Id = p.Id,
Name = p.Name
};
To get the parents I could then in my controller do:
...
//Get list of parents
var parents = _context.Parents.Select(AsParentDTO);
//Or: Get only parent with Id
var specificParent= _context.Parents
.Select(AsParentDTO)
.Where(p => p.Id == 1);
return parents;
...
This solution seems good to me since I can reuse the Epressions and extend them if I want. I only do not seem to be able to Include the children to the parent this way:
...
var parents = _context.Parents
.Include(p => p.Children)
//I have no idea if it is possible to Invoke the child Expression here...
.Select(p => p.Children= AsChildDTO.Invoke()) //<-- this does not work
.Select(AsParentDTO)
...
As I wrote in the comment above; I have no idea if it is possible to somehow invoke the Child Expression here.
Outro
These are the two things I tried but got stuck with. But it could also be that I am missing a very obvious solution. My Question is how do I solve this issue in a way that I can reuse as much code as possible?
I think you are vastly over complicating it.
var results=_context.Parents
.Include(p=>p.Children);
will return your EF objects. That's what you should be working with. If you want to convert the EF objects to DTO objects, save that for the final projection (I rarely use DTO objects as the POCO objects from EF are usually just fine).
var parents=results.Select(p=>new ParentDTO
{ id=p.id,name=p.name,children=p.Children.ToList()}
);
If you just want parent 1, then:
var parent=results.Where(p=>p.id==1);
if you want it as parentDTO:
var parent=results.Where(p=>p.id==1).Select(p=>new ParentDTO {
{ id=p.id,name=p.name,children=p.Children.ToList()}
);
You could use things like AsParentDto, but doesn't that imply that you are going to be copying the entire Parent properties? (In your simple case -- id and name). And if you are copying the entire property list, why are you creating a new object with all the same properties as the EF object instead of just reusing the EF object? The only time I'd use a Dto object is if I wanted to pass around a parent that only has some of the properties and I wanted to save myself from retrieving the additional properties from the database, in which case, I'd still use the original database query and just project into it as the final step.
var slimparent=results.Where(p=>p.id==1).Select(p=>new SlimParentDto {
id=p.id });
Of course, if all I wanted was the parent id's then I'd just use an even simplier IQueryable<int> like:
var parentids=results.Where(p=>p.id==1).Select(p=>p.id);
--- TL;DR ---
Create a single method to retrieve your object will all the properties included. Everything then should use that as it's base, and attach further refinements in your controller to filter it down to just the data subset you want. Then as a last step, project the result into whatever DTO you want. Never use any methods to cause the IQueryable to be enumerated until you've done the projection. EF/LINQ will then generate an optimal query for you, just retrieving the properties required to fill your DTO.
So I have a little issue in sorting some data I have. In a Telerik Grid, I have a column called Requestor that displays the name of a person or Unit (group of people). The problem is, Requestor has two sources it can get it's data from. Here are the two sources.
1.) RequestorId: This is a foreign key to a table called Customer. Here, I store all the data for the user, including their full name. This field can be null btw.
2.) UnitId: This is another foreign key to a table called Units. Here, I store all the data for the Units, particularlly their names. This field can be null btw.
Here is the logic:
//Entity class that contains all the data for my grid
var purchaseOrders = _purchaseOrders.GetPurchaseOrders();
//Key is Id of PurchaseOrders, Value is name of requestor
var dictionary = new Dictionary<int, string>();
foreach (var purchaseOrder in purchaseOrders)
{
if (purchaseOrder.requestorId != null)
dictionary.add(purchaseOrder.Requestor.Fullname);
else
dictionary.add(purchaseOrder.unit.Fullname);
}
dictionary.orderby(x => x.value).ToDictionary(x => x.Key, x.Value);
var tempPurchaseOrders = new List<PurchaseOrder>();
foreach (var item in dictionary)
{
tempPurchaseOrders.Add(purchaseOrders.Where(x => x.Id == item.Key).FirstOrDefault());
}
purchaseOrders = tempPurchaseOrders.AsQueryable();
return purchaseOrders;
This logic returns an ordered list based on what I want to do, however, the problem is the amount of time it takes to process. It takes 1 minute to process. That's horrible obviously. Is there anyway to optimize this? I cut down the source after I return for the grid because there is no logical way to really cut it down beforehand.
Any help would be appreciated. Thanks.
Edit: I found out I no longer am required to use the RequestName field. That limits the data to two areas now. Still a minute to process though.
Did you try something like this:
return _purchaseOrders.GetPurchaseOrders().Select(i => new
{
OrderColumn = i.requestorId != null ? purchaseOrder.Requestor.Fullname : purchaseOrder.unit.Fullname,
// map other columns
})
.OrderBy(i => i.OrderColumn);
A bit like Sławomir Rosiek's solution (but entity framework won't accept that statement):
return _purchaseOrders.GetPurchaseOrders()
.OrderBy(o => o.unit.Fullname).ToList();
(since you don't use RequestName anymore).
Especially when GetPurchaseOrders() is an IQueryable from EF you delegate the sorting to the database engine because the sort expression becomes part of the SQL statement.
So I came up with my own solution. I first tried what both Sławomir Rosiek and Gert Arnold did. Unfortunately, like Gert mentioned, the first answer would not go through. The second one had similar issues.
In the end, I created a class to store the data from both Requestors and Units. It consisted of the following:
internal class RequestorData
{
public int entityId { get; set; }
public string Name { get; set; }
public bool isRequestorId { get; set; }
}
Next, I did the following.
//Entity class that contains all the data for my grid
var purchaseOrders = _purchaseOrders.GetPurchaseOrders();
var tempPurchaseOrders = new List<PurchaseOrder>();
var requestors = new List<RequestorData>();
var customers = purchaseOrders.Select(po => po.Requestor).Distinct().ToList();
var units = purchaseOrders.Select(po => po.Unit).Distinct().ToList();
foreach (var customer in customers)
{
if (customer != null)
requestors.Add(new RequestorData { entityId = customer.Id, Name = customer.FullName, isRequestorId = true });
}
foreach (var unit in units)
{
if (unit != null)
requestors.Add(new RequestorData { entityId = unit.Id, Name = unit.FullName, isRequestorId = false });
}
requestors = requestors.OrderBy(r => r.Name).ToList();
foreach (var requestor in requestors)
{
var id = requestor.entityId;
if (requestor.isRequestorId)
tempPurchaseOrders.AddRange(purchaseOrders.Where(po => po.RequestorId == id).ToList());
else
tempPurchaseOrders.AddRange(purchaseOrders.Where(po => po.UnitId == id).ToList());
}
purchaseOrders = tempPurchaseOrders.AsQueryable();
return purchaseOrders;
I ran this new rendition and have a 5-6 second time of wait. That's not perfect but much better than before. Thanks for all the help.
This is what I Have in my WCF service
public long Generic_Save(Product p, ObjectSet os)
{
if (p.Id == 0)
{
os.AddObject(p);
}
else
{
// UPDATE
Product original = os.Single<Project>(o => o.Id == p.Id);
original.Name = p.Name;
original.Items = p.Items; // doesn't work !
}
dataEntities.SaveChanges();
return p.Id;
}
Product p is an object from the WCF Call, with an EntityKey etc.. but it's not attached to the current dataEntities..
What I want to do is to save the object Product p directly, not to get the original from the ObjectSet os before and modify the values -> Product original = os.Single<Project>(o => o.Id == p.Id);
How can I do that?
[EDIT]
I have try this to add new items and it's working
foreach (Item item in p.Items)
{
try
{
dataEntities.Items.ApplyCurrentValues(item);
}
catch (Exception)
{
Items i = new Items();
// Set prop here or make a method CopyTo()
i.Prop = item.Prop;
dataEntities.AddToItems(i);
}
}
dataEntities.SaveChanges();
Badly. It is possible only with Product p itself (update detached entity) but it is really hard with items. The problem is that you must manually say EF exactly which item has changes, which is new and also which were deleted. The longer discussion of the problem is here. Common solutions are:
Do it exactly as you did at the moment but instead of assigning items, manually compare original and received items and modify loaded items accordingly. If you do not add or remove items on the client this should be just about calling ApplyCurrentValues for each item.
Use Self tracking entities (only for .NET clients).