how to update easily an entity directly from a wcf call? - c#

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).

Related

one2many relation, get parent with child

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.

Problems with DataContext not loading updated entity

I've stumbled upon a strange problem which I can't wrap my head around.
I have two DataContexts in two different forms. I created two "Customer" entities (Customer A and Customer Z) in one form and saved them through the form's context. I then went back to the other form, where I manage orders. In this form, I can create an order and search for a customer (or his address).
public void CustomerAddressSearched()
{
// Get all delivery addresses matching keyword
List<Address> addressList = customerModel.GetMatchingDeliveryAddresses(view.OrderSearchKeyword);
// If there is no matching address
if (addressList.Count == 0)
{
MessageBox.Show(Properties.Resources.SearchNoResult);
}
// If there is a single matching address
else if (addressList.Count == 1)
{
activeOrder.Address = addressList[0];
PopulateOrderAddressControls(addressList[0]);
activeOrderIsSaved = false;
}
// If there is more than one matching address, open search result view for user to pick address
else
{
using (var form = new CustomerSearchView(addressList))
{
if (form.ShowDialog() == DialogResult.OK)
{
CustomerViewObject selectedCustomer = (CustomerViewObject)form.selectedCustomer;
activeOrder.Address = addressList.Where(a => a.Id == selectedCustomer.AddressId).FirstOrDefault();
activeOrder.AddressId = selectedCustomer.AddressId;
PopulateOrderAddressControls(activeOrder.Address);
activeOrderIsSaved = false;
}
}
}
EnableOrDisableControls();
}
I searched for Customer and got a list with both customers. I went back to the customer form and changed the name of Customer A to Customer B, saved, then went back and searched again. Unfortunately, I got Customer A and Z as a result, not Customer B. Checked the database, Customer B is in it. I then picked Customer A for the order I created and saved it. Created a new one, searched again, and this time I got Customer B instead of A. I somehow need to use the "old" version once, otherwise the new one doesn't show. If this makes any sense.
So I checked
List<Address> addressList = customerModel.GetMatchingDeliveryAddresses(view.OrderSearchKeyword);
to see if there was something wrong with the way I retrieve the latest customer data.
public List<Address> GetMatchingDeliveryAddresses(string keyword)
{
List<Address> addressList = new List<Address>();
foreach (var c in context.Customers)
{
if (c.DateDeleted == null)
{
var result = c.Address.Where(a => (a.LastName.Contains(keyword) || a.Company.Contains(keyword)) && a.IsDeliveryAddress == true)
.OrderByDescending(a => a.DateEffective)
.FirstOrDefault();
if (result != null)
{
addressList.Add(result);
}
}
}
return addressList;
}
Sure enough, when debugging and stopping at the foreach loop, I noticed the latest version (Customer B) was not loaded (if I didn't use it at least once or restarted the program/created a new context).
I put
var list = context.Addresses.ToList();
right before the foreach loop just to check if he wouldn't load the new customer address at all, but all of a sudden it worked, the foreach loop loaded the newest customer version. I removed the line again and it stopped working. Put it back in, worked again. I guess my question is, why is the line above somehow refreshing my context, or whatever is happening?
Cheers!
Edit
I started went to the customer directory where I had two customers (Customer E and Customer 3). I renamed them to Customer ASD and Customer 666. Went back to the order form and searched for Customer. It ran the following code:
public List<Address> GetMatchingDeliveryAddresses(string keyword)
{
List<Address> addressList = new List<Address>();
foreach (var c in context.Customers)
{
var result = c.Address.Where(a => (a.LastName.Contains(keyword) || a.Company.Contains(keyword)) && a.IsDeliveryAddress == true)
.OrderByDescending(a => a.DateEffective)
.FirstOrDefault();
System.Console.WriteLine("PRE Name: " + result.LastName);
}
var list = context.Addresses.ToList();
foreach (var c in context.Customers)
{
var result = c.Address.Where(a => (a.LastName.Contains(keyword) || a.Company.Contains(keyword)) && a.IsDeliveryAddress == true)
.OrderByDescending(a => a.DateEffective)
.FirstOrDefault();
System.Console.WriteLine("POST Name: " + result.LastName);
}
With the following output:
PRE Name: Customer E
PRE Name: Customer 3
POST Name: Customer ASD
POST Name: Customer 666
I don't get it.
The question is getting too many comments, so I'm gonna post an answer here, cause I'm pretty sure that's the reason why you have this.. problem.
From what you've wrote in the comments: The context used to retrieve the customers is created when the form is initialized. I'm doing a context per form. it's getting clear that you are creating the context along with the form itself (probably in the constructor) and this is bad for many reasons. There are a lot of articles why you should NOT do that, but here we are talking for one certain problem and that's obviously the fact that your context is not changing until you close the form.
Now for your last comment - Turns out you were right, the code never made it to the dispose call for the customer directory context. But I still don't understand why it triggers such strange behaviour and how the line I added "fixed" it.
The reason is that DbContext is keeping track on all the changes that you make until the current context is alive, but it's just an expression tree (you could check what an expression tree is if you like) and in order to have some real data from that you need to execute certain calls. One of which is ToList() which forces the execution of the changes, but the quotes are really in place here, due to the fact that it's not how you are supposed to do it.
To keep it short. If you are not going to use any sort of data access layer, and just gonna use the DbContext directly inside your forms you should remove the context creation from the constructor (or where it is right now) and create new context each time (most probably for each method) that need to use. And in order to get rid of this problem you should wrap up tour code in using statement like so :
public List<Address> GetMatchingDeliveryAddresses(string keyword)
{
using( var context = new MyEntitiesContext())
{
List<Address> addressList = new List<Address>();
foreach (var c in context.Customers)
{
var result = c.Address.Where(a => (a.LastName.Contains(keyword) || a.Company.Contains(keyword)) && a.IsDeliveryAddress == true)
.OrderByDescending(a => a.DateEffective)
.FirstOrDefault();
System.Console.WriteLine("PRE Name: " + result.LastName);
}
foreach (var c in context.Customers)
{
var result = c.Address.Where(a => (a.LastName.Contains(keyword) || a.Company.Contains(keyword)) && a.IsDeliveryAddress == true)
.OrderByDescending(a => a.DateEffective)
.FirstOrDefault();
System.Console.WriteLine("POST Name: " + result.LastName);
}
}
And that should be all.

Clean method in order to update collection in entity framework

i'm sorry if this question has already been asked, but i'm in trouble with my method of updating collection in Entity Framework.
Let me explain the situation :
- I have for example one model CUSTOMER with some properties and a collection of ORDERS (for example).
- Let's imagine we have an admin page on wich we can edit all the ORDERS for a customer, and when we submit the form, it will send us back the object CUSTOMERS with updated ORDERS (some added, some updated and some deleted).
For the moment i use something like this in order to compare old collection and new collection and determine which object i need to delete/update/add
var toRemove = new List<ORDERS>();
var toAdd = new List<ORDERS>();
foreach (
var order in
oldList.Where(
order =>
newList.FirstOrDefault(t => t.link_id == order.link_id) == null))
{
toRemove.Add(order);
}
foreach (
var order in
newList.Where(
order =>
oldList.FirstOrDefault(t => t.link_id == order.link_id) == null))
{
toAdd.Add(order);
}
foreach (var ORDERSe in toRemove)
{
bdd.ORDERS.Remove(ORDERSe);
}
foreach (var ORDERSe in toAdd)
{
ORDERSe.pjt_id = project_id;
bdd.ORDERS.Add(ORDERSe);
}
foreach (
var order in
newList.Where(
order =>
oldList.FirstOrDefault(t => t.link_id == order.link_id) != null))
{
var child = oldList.FirstOrDefault(t => t.link_id == order.link_id);
bdd.Entry(child).CurrentValues.SetValues(order);
}
But i'm unconfortable with this, because in my mind, entity framework should be able to do the work for me !
I was hoping something like :
customer.orders = newOrders;
Did i missed anything about entity framework or ?
Because when i do this, it just duplicate my orders.
Thanks in advance for your answer.
You can certainly make it cleaner using .Except() and .Intersect(), but the concept doesn't really change, AFAIK you still have to individually remove, update & add the entries in loops...
var oldList = new List<ORDERS>();
var newList= new List<ORDERS>();
var IdsToRemove = oldList.Select(t => t.link_id).Except(newList.Select(t => t.link_id));
var IdsToAdd = newList.Select(t => t.link_id).Except(oldList.Select(t => t.link_id));
var IdsToUpdate = newList.Select(t => t.link_id).Intersect(oldList.Select(t => t.link_id));
//remove
bdd.orders.where(x => IdsToRemove.Contains(x.link_id)).ForEach(x => bdd.Remove(x));
//add
foreach(var order in newList.Where(x -> IdsToAdd.Contains(x.link_id))
{
bdd.Orders.Attach(order);
bdd.Entries(order).EntityState = EntityState.Added;
}
//update
foreach(var order in newList.Where(x -> IdsToUpdate .Contains(x.link_id))
{
bdd.Orders.Attach(order);
bdd.Entries(order).EntityState = EntityState.Modified;
}
bdd.SaveChanges();
But i'm unconfortable with this, because in my mind, entity framework
should be able to do the work for me !
In fact, EF does the Work for you. Using the data context SaveChanges method EF should be able to save all your changes at once:
DbContext.SaveChanges()
For your convinience you can still override this method. Internally you should use something like this:
public override int SaveChanges()
{
var changeSet = ChangeTracker.Entries<IAuditable>();
if (changeSet != null)
{
foreach (var entry in changeSet.Where(c => c.State != EntityState.Unchanged))
{
entry.Entity.ModifiedDate = DateProvider.GetCurrentDate();
entry.Entity.ModifiedBy = UserName;
}
}
return base.SaveChanges();
}

Pivot List of entities using LINQ in C#

What I have is a list of entities coming back from a database that I want to pivot so that I end up with a new list of entities with the duplicates removed and the pivoted items attached to the new entity.
Currently I have a simple solution like this:
IQueryable<Entity> results // being passed in from calling method.
List<Entity> pivotedEntities = new List<Entity>();
foreach (Entity entity in results)
{
if (pivotedEntities.Contains(entity))
{
Entity matchedEntity = pivotedEntities.Find(e => e.Id == entity.Id);
matchedEntity.RelatedEntities.Add(entity.RelatedEntity);
}
else
{
pivotedEntities.Add(new Entity());
}
}
return pivotedEntities.AsQueryable();
This works fine however I want to be able to achieve the same thing with a LINQ query on the IQueryable results variable so that it maintains its deffered execution rather than executing as soon as I enter the foreach.
I have other methods that get called around this code that also alter the IQueryable and I then want to execute the call to the database once all filters have been applied.
Hope that all makes sense.
Maybe this will reduce few loops.
List<Entity> pivotedEntities = new List<Entity>();
int index = 0;
foreach (Entity entity in results)
{
index = pivotedEntities.IndexOf(e => e.Id == entity.Id);
if (index>-1)
{
pivotedEntities[index].RelatedEntities.Add(entity.RelatedEntity);
}
else
{
pivotedEntities.Add(new Entity());
}
}
return pivotedEntities.AsQueryable();

EF many-to-many madness

I have a method that updates a ReportRecipient object in EF. The primitives work fine; the headache comes in when trying to manage a M2M relationship with the RecipientGroups objects.
Please take a look at this code:
public IReportRecipient ModifyRecipientWithGroupAssignments(IEnumerable<Guid> groupIds, IReportRecipient recipient)
{
var entity = Context.ReportRecipients
.Include("RecipientGroups")
.FirstOrDefault(e => e.ReportRecipientId == recipient.ReportRecipientId)
.FromIReportRecipient(recipient);
var toRemove = entity.RecipientGroups
.Where(e => !groupIds.Contains(e.GroupId))
.ToList();
//remove group assignments that no longer apply
foreach (var group in toRemove)
{
if (group != null)
{
entity.RecipientGroups.Attach(group);
entity.RecipientGroups.Remove(group);
}
}
var toAdd = entity.RecipientGroups
.Where(e => groupIds.Contains(e.GroupId))
.ToList();
//add new groups that weren't there before
foreach (var group in toAdd)
{
if (group != null)
{
entity.RecipientGroups.Attach(group);
}
}
return entity;
}
... my problem is on the var ToAdd... line. Even if I have a collection of Guids in groupIds that match Guids representing RecipientGroup objects in the database, toAdd always evaluates to an empty collection. I would think the Contains() function would work for this scenario; can someone please explain if I am doing something wrong?
You should load the RecipientGroups you want to add from the database (Context.RecipientGroups I guess), not from the collection you want to add them to (entity.RecipientGroups in the code sample).

Categories

Resources