i try to serialize a class into xml.
after the serialization , i would like to update the statusid.
I'm able to find the first order and alter the statusId , when goes to the 2nd orderid,here is where i encountered the ambiguous match found error.
here is the main method:
using (var Context = new Context())
{
var orderRepo = new OrderRepository(Context);
foreach (var orderId in orderIds)
{
var order = orderRepo.Find(orderId);
order.orderStatusID = 5;
}
orderRepo.Save();
}
in the OrderRepository.cs
public Order Find(int id)
{
return _context.Orders.Find(id);
}
public void Save()
{
try
{
_context.SaveChanges();
}
catch (Exception ex)
{
_logger.Error(ex);
}
}
order.cs:
[XmlRoot("Orders")]
[NotMapped]
public class OrderCollection
{
public OrderCollection() { Orders = new List<Order>(); }
[XmlElement("Order")]
[NotMapped]
public List<Order> Orders { get; set; }
}
[Serializable()]
public class Order
{
public int id { get; set; }
[XmlElement("date")]
public DateTime createdDate
{
get
{
return (_createdDate == default(DateTime))
? DateTime.Now
: _createdDate;
}
set { _createdDate = value; }
}
private DateTime _createdDate = default(DateTime);
public string firstName { get; set; }
public string lastName { get; set; }
[XmlIgnore]
public int orderStatusID { get; set; }
}
turn out to be an entity had two members with the same name and different casing. One was a field from the database, and one was a navigation property. Just renaming one of the two solved the problem.
Related
I have an Invoice object that has a list of items and other properties. Whenever I execute the service that edits an invoice, each item get doubled for some reason.
For example, here's a snapshot of the items table after creating an invoice with two items:
Items table
And here's a snapshot of it after executing EditInvoice service: Items table after editing
Data Model
Invoice
public class Invoice
{
public string InvoiceId { get; set; } = GenerateID.GenerateInvoiceID();
public string Description { get; set; }
public List<InvoiceItem> Items { get; set; }
public DateTime InvoiceDate { get; set; }
public string PaymentTerms { get; set; }
public DateTime PaymentDue { get; set; }
public int TotalPrice { get; set; }
public string Status { get; set; } = "pending";
public Client Client { get; set; }
public string ClientId { get; set; }
public string BillFromAddress { get; set; }
public string BillFromCity { get; set; }
public string BillFromCountry { get; set; }
public string BillFromPostal { get; set; }
}
InvoiceItem
public class InvoiceItem
{
public string InvoiceItemId { get; set; } = GenerateID.GenerateItemID();
public string InvoiceId { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int Quantity { get; set; }
[Required]
public int Price { get; set; }
public InvoiceItem()
{
}
public InvoiceItem(string itemName, int quantity, int price)
{
Name = itemName;
Quantity = quantity;
Price = price;
}
}
My InputModel
public class InputModel
{
[Required]
public string Description { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime InvoiceDate { get; set; }
public string PaymentTerms { get; set; }
public DateTime PaymentDue { get; set; }
public Client Client { get; set; }
public List<InvoiceItem> Items { get; set; } = new List<InvoiceItem>(16);
[Required]
public string BillFromAddress { get; set; }
[Required]
public string BillFromCity { get; set; }
[Required]
public string BillFromCountry { get; set; }
[Required]
public string BillFromPostal { get; set; }
public void PopulateItems()
{
for (int i = 0; i < Items.Capacity; i++)
{
Items.Add(new InvoiceItem());
}
}
}
My Page Models
CreateInvoiceModel
public class CreateInvoiceModel : PageModel
{
public readonly InvoiceService _service;
[BindProperty]
public InputModel Input { get; set; }
public CreateInvoiceModel(InvoiceService service)
{
_service = service;
}
public void OnGet()
{
Input = new InputModel();
Input.PopulateItems();
}
public async Task<IActionResult> OnPost()
{
if (ModelState.IsValid)
{
_service.AddInvoice(Input);
return RedirectToPage("/Index");
}
return Page();
}
}
EditInvoiceModel
public class EditInvoiceModel : PageModel
{
public readonly InvoiceService _service;
[BindProperty]
public InputModel Input { get; set; }
public string InvoiceId { get; set; }
public EditInvoiceModel(InvoiceService service)
{
_service = service;
}
public async void OnGet(string id)
{
Invoice invoice = await _service.GetInvoice(id);
InvoiceId = invoice.InvoiceId;
Input = new InputModel();
Input.Items = invoice.Items;
Input.BillFromAddress = invoice.BillFromAddress;
Input.BillFromCity = invoice.BillFromCity;
Input.BillFromPostal = invoice.BillFromPostal;
Input.BillFromCountry = invoice.BillFromCountry;
Input.Client = invoice.Client;
Input.InvoiceDate = invoice.InvoiceDate;
Input.PaymentTerms = invoice.PaymentTerms;
Input.Description = invoice.Description;
}
public async Task<IActionResult> OnPost(string id)
{
if(ModelState.IsValid)
{
_service.EditInvoice(Input, id);
return RedirectToPage("/ViewInvoice", new { id = id });
}
return Page();
}
}
Services
AddInvoice
public async void AddInvoice(InputModel input)
{
Invoice invoice = new Invoice();
invoice.Description = input.Description;
invoice.Items = input.Items;
invoice.InvoiceDate = input.InvoiceDate;
invoice.PaymentTerms = input.PaymentTerms;
invoice.Client = input.Client;
invoice.BillFromAddress = input.BillFromAddress;
invoice.BillFromCity = input.BillFromCity;
invoice.BillFromCountry = input.BillFromCountry;
invoice.BillFromPostal = input.BillFromPostal;
//Attaching the invoice id to each item in the invoice
foreach (var item in invoice.Items)
{
item.InvoiceId = invoice.InvoiceId;
}
//IndexModel.invoices.Add(invoice);
_context.Add(invoice);
await _context.SaveChangesAsync();
}
EditInvoice
public async void EditInvoice(InputModel input, string id)
{
var invoice = await _context.Invoices.FindAsync(id);
if (invoice == null) { throw new Exception("Unable to find the invoice"); }
invoice.Items = input.Items;
invoice.Description = input.Description;
invoice.InvoiceDate = input.InvoiceDate;
invoice.PaymentTerms = input.PaymentTerms;
invoice.Client = input.Client;
invoice.BillFromAddress = input.BillFromAddress;
invoice.BillFromCity = input.BillFromCity;
invoice.BillFromCountry = input.BillFromCountry;
invoice.BillFromPostal = input.BillFromPostal;
await _context.SaveChangesAsync();
}
Your code has multiple problems:
First of all in your InvoiceItem you have the following line
public string InvoiceItemId { get; set; } = GenerateID.GenerateItemID();
which means that whenever n InvoiceItem is instanciated a new Id is generated which is not correct because there is a difference between actually creating an InvoiceItem and just creating an instance of the class InvoiceItem. A new Id should only be generated if a new InvoiceItem is created but not for example if an existing InvoiceItem is loaded from the database (in both cases an instance of the class InvoiceItem is created but only in the first one an actual InvoiceItem shall be created). So removing the id generation from the property declaration and only performing is when an InvoiceItem shall actually be created will fix this part of the problem.
The second problem is in EditInvoice where you call
var invoice = await _context.Invoices.FindAsync(id);
// ...
invoice.Items = input.Items;
in the first line you are loading the invoice from the database but you are not including the invocies and therefore they do not get loaded and EF does not even know they exist.
So when you are calling invoice.Items = input.Items you are assigning the list with the new InvoiceItems with the newly generated ids (as explained above) and they will therefore be added to the database and hence duplicated.
So instead of replacing the whole list you should be editing existing items and only adding InvoiceItems which have actually been created to the list.
Instead of
invoice.Items = input.Items;
You can work with:
For adding a new InvoiceItem record, presume the invoice item doesn't have InvoiceId (foreign key value), use .Add() as adding a new record.
For the existing InvoiceItem record, presume the invoice item has InvoiceId (foreign key value), set the entity, and modify its state as Modified. Make sure that the entity's Id (primary key (value)) matches with the record in the database table. Reference: Attaching an existing but modified entity to the context
public async void EditInvoice(InputModel input, string id)
{
var invoice = await _context.Invoices.FindAsync(id);
if (invoice == null) { throw new Exception("Unable to find the invoice"); }
foreach (var item in input.Items)
{
if (String.IsNullOrEmpty(item.InvoiceId))
{
invoice.Add(item);
}
else
{
context.Entry(item).State = EntityState.Modified;
}
}
invoice.Description = input.Description;
invoice.InvoiceDate = input.InvoiceDate;
invoice.PaymentTerms = input.PaymentTerms;
invoice.Client = input.Client;
invoice.BillFromAddress = input.BillFromAddress;
invoice.BillFromCity = input.BillFromCity;
invoice.BillFromCountry = input.BillFromCountry;
invoice.BillFromPostal = input.BillFromPostal;
await _context.SaveChangesAsync();
}
Or you can work as:
foreach (var item in input.Items)
{
if (String.IsNullOrEmpty(item.InvoiceId))
{
item.InvoiceId = invoice .InvoiceId;
context.Entry(item).State = EntityState.Added;
}
else
{
context.Entry(item).State = EntityState.Modified;
}
}
Reference: Insert or update pattern
Although the provided documentation is regarding Entity Framework 6, it supports in Entity Framework Core.
How to correctly handle computed properties in EF model?
My try bellow will fail because of "The entity or complex type 'Invoice' cannot be constructed in a LINQ to Entities query."
Consider method "GetInvoice" as WebApi method with allowed querystring.
static void Main(string[] args)
{
var invs = GetInvoice();
invs.FirstOrDefault();
}
public static IQueryable<Invoice> GetInvoice()
{
var model = new Model();
IQueryable<Invoice> inv = model.Invocies.Include(t => t.Items).SelectInvoiceData();
return inv;
}
public static class ExtHelper
{
public static IQueryable<Invoice> SelectInvoiceData(this IQueryable<Invoice> item)
{
return item.Select(c => new Invoice
{
LatestItemName = c.Items.FirstOrDefault().Name
});
}
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreatedAt { get; set; }
}
public class Invoice
{
public int Id { get; set; }
public DateTime CreatedAt { get; set; }
public string Issuer { get; set; }
[NotMapped]
public string LatestItemName { get; set; }
private ICollection<Item> _items;
public virtual ICollection<Item> Items
{
get { return _items ?? (_items = new Collection<Item>()); }
set { _items = value; }
}
}
EntityFramework 6 does not support creating partial entities like this. Either use anonymous type:
return item.Select(c => new
{
LatestItemName = c.Items.FirstOrDefault().Name
});
Or some DTO class that does not belong to context:
return item.Select(c => new InvoiceDTO
{
LatestItemName = c.Items.FirstOrDefault().Name
});
However in EF Core it is possible to create entities like in your example.
I have a VS project that uses Entity Framework to store the information into a database.
I have this class:
DIET_DietHeaders:
using System;
using System.Collections.Generic;
public partial class DIET_DietHeaders
{
public DIET_DietHeaders()
{
this.DIET_DietDetails = new HashSet<DIET_DietDetails>();
}
public int ID { get; set; }
public string CodicePersoneFisiche { get; set; }
public System.DateTime Data { get; set; }
public string Name { get; set; }
public virtual ANAG_Assistiti ANAG_Assistiti { get; set; }
public virtual ICollection<DIET_DietDetails> DIET_DietDetails { get; set; }
}
ANAG_Assistiti:
using System;
using System.Collections.Generic;
public partial class ANAG_Assistiti
{
public ANAG_Assistiti()
{
this.DIET_FoodsBlack = new HashSet<DIET_FoodsBlack>();
this.DIET_DietHeaders = new HashSet<DIET_DietHeaders>();
}
public string CodicePersoneFisiche { get; set; }
public string GruppoSanguigno { get; set; }
public string FattoreRH { get; set; }
public virtual ICollection<DIET_FoodsBlack> DIET_FoodsBlack { get; set; }
public virtual ANAG_PersoneFisiche ANAG_PersoneFisiche { get; set; }
public virtual ICollection<DIET_DietHeaders> DIET_DietHeaders { get; set; }
}
}
Now in my controller, I'm building this code to save the new record into the DIET_DietHeaders table when I receive a request from Json.
[Route("Diet/UpdateDiet")]
[HttpPost]
public HttpResponseMessage UpdateDiet(PatientDietDTO upa)
{
try
{
if (upa != null)
{
UserDTO u = new UserDTO(upa.UserName, upa.Password);
RMessage LoginStatus = Login(u);
if (!login)
{
return Request.CreateResponse(HttpStatusCode.OK, LoginStatus);
}
else
{
string patientA = (assistitoExistsByCF(upa.Patient) ? upa.Patient : getCFAssistoByUsername(upa.Patient));
int res = CreateDiet(upa);
if (upa.ID == null)
{
// effettua insert
var diet = new DIET_DietHeaders
{
CodicePersoneFisiche= upa.Patient
,ANAG_Assistiti = db_data.ANAG_Assistiti.First(c => c.CodicePersoneFisiche == upa.Patient)
, Data=upa.Data
,Name=upa.Name
,Calories=upa.Calories
,WaterCount=upa.WaterCount
,CaloriesTarget=upa.CaloriesTarget
,ProteinTarget=upa.ProteinTarget
,FatTarget=upa.Fat
};
diet.CodicePersoneFisiche = "CFPALUMBO22";
db_data.DIET_DietHeaders.Add(diet);
db_data.SaveChanges();
log.Debug("save done");
int id = diet.ID;
}
return Request.CreateResponse(HttpStatusCode.Created, (new RMessage((short)status_code.Success, "Diet created")));
}
}
else
{
return Request.CreateResponse(HttpStatusCode.BadRequest, (new RMessage((short)status_code.Failure, "Not well formed JSON")));
}
}
catch (Exception e)
{
e = e.GetBaseException();
log.Error(string.Format("{0} {1}", e.Message, e.StackTrace));
return Request.CreateResponse(HttpStatusCode.InternalServerError, new RMessage((short)status_code.Exception, e.Message));
}
}
If I try to execute a request I get this strange error:
Cannot to insert null into column CodiceFiscaleAssistito of table DIET_DietHeaders
but this field is correctly populated.
Where is my error?
I have a situation where the code I've arrived at doesn't match any examples I find so I wonder if I'm missing something.
Basically, I want an EF code first Entity that contains a collection of Entities participating in a many-to-many relationship.
Then, I'd like to be able to:
Add to collection at the same time as creating an entity
Not get a warning about accessing a virtual member from constructor
Here's what I have:
public class NotificationUser
{
private ICollection<NotificationUserGroup> _userGroups = new HashSet<NotificationUserGroup>();
public int UserId { get; set; }
public string UserName { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<NotificationUserGroup> UserGroups
{
get { return _userGroups; }
set { _userGroups = value; }
}
}
Is there a better/different way to accomplish my goal?
This example might help
public class NotificationUser
{
public NotificationUser()
{
UserGroups = new HashSet<NotificationUserGroup>();
}
public int NotificationUserId { get; set; }
public string UserName { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<NotificationUserGroup> UserGroups { get; set; }
}
public class NotificationUserGroup
{
public int NotificationUserGroupId { get; set; }
public string GroupName { get; set; }
}
public class Context : DbContext
{
public Context()
: base()
{
}
public DbSet<NotificationUser> NotificationUsers { get; set; }
public DbSet<NotificationUserGroup> NotificationUserGroup { get; set; }
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
using (var ctx = new Context())
{
var user = new NotificationUser() { UserName = "Name1" };
user.UserGroups.Add(new NotificationUserGroup() { GroupName = "Group1" });
user.UserGroups.Add(new NotificationUserGroup() { GroupName = "Group2" });
ctx.NotificationUsers.Add(user);
ctx.SaveChanges();
}
using (var ctx = new Context())
{
foreach (var user in ctx.NotificationUsers)
{
foreach (var group in user.UserGroups)
Console.WriteLine("Group Id: {0}, Group Name: {1}, UserName: {2}", group.NotificationUserGroupId, group.GroupName,user.UserName);
}
foreach (var group in ctx.NotificationUserGroup)
{
Console.WriteLine("Group Id: {0}, Group Name: {1}", group.NotificationUserGroupId, group.GroupName);
}
}
Console.ReadKey();
}
}
EDIT: See the bottom of this question for the working code.
I have two tables, Patients and Drugs, that I am updating with a data feed. I get a current list of patients, then iterate through and update or insert records as appropriate. This works without issue.
The trouble comes when I iterate through that patient's current medications. I end up getting multiple copies of the original patient. Drug records are transferred as expected (the records themselves don't change so new records are inserted and existing records ignored). I end up with the original patient record (inserted from UpdatePatients() below) and then one additional patient record for each medication record. Each medication record ends up with a distinct PatientId.
Class definitions:
public class Patient
{
public int PatientId { get; set; }
[Required]
public int FacilityNumber { get; set; }
[Required]
public int PatNo { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int Age { get; set; }
[Required]
public string Gender { get; set; }
[Required]
public DateTime VentStart { get; set; }
[Required]
public DateTime VentEnd { get; set; }
[Required]
public DateTime AdmitDate { get; set; }
public DateTime? DischargeDate { get; set; }
}
public class Drug
{
public int DrugId { get; set; }
[Required]
public int DrugDDI { get; set; }
[Required]
public int OrderId { get; set; }
[Required]
public string DrugName { get; set; }
[Required]
public DateTime DispenseDate { get; set; }
[Required]
public double UnitsDispensed { get; set; }
[ForeignKey("Patient")]
public int PatientId { get; set; }
public virtual Patient Patient { get; set; }
}
Offending code:
private static void UpdatePatients()
{
var Patients = DB2Patient.GetPatients();
foreach (Patient p in Patients)
{
using (var PatientContext = new VAEContext())
{
var ExistingPatientRecord = PatientContext.Patients.FirstOrDefault(
ep => ep.PatNo == p.PatNo
);
if (ExistingPatientRecord != null)
{
ExistingPatientRecord.VentEnd = p.VentEnd;
ExistingPatientRecord.DischargeDate = p.DischargeDate;
PatientContext.SaveChanges();
}
else
{
PatientContext.Patients.Add(p);
PatientContext.SaveChanges();
}
}
UpdateDrugs(p);
}
}
private static void UpdateDrugs(Patient p)
{
var Drugs = DB2Drug.GetDrugs(p.PatNo);
foreach (Drug d in Drugs)
{
using (var DrugContext = new VAEContext())
{
var ExistingDrugRecord = DrugContext.Drugs.FirstOrDefault(
ed => ed.DrugDDI == d.DrugDDI &&
ed.DispenseDate == d.DispenseDate &&
ed.OrderId == d.OrderId
);
if (ExistingDrugRecord == null)
{
d.Patient = p;
DrugContext.Drugs.Add(d);
DrugContext.SaveChanges();
}
}
}
}
EDIT: Working code:
private static void UpdatePatients()
{
var Patients = DB2Patient.GetPatients();
using (var db = new VAEContext())
{
foreach (Patient p in Patients)
{
var ExistingPatientRecord = db.Patients.FirstOrDefault(
ep => ep.PatNo == p.PatNo
);
if (ExistingPatientRecord != null)
{
ExistingPatientRecord.VentEnd = p.VentEnd;
ExistingPatientRecord.DischargeDate = p.DischargeDate;
}
else
{
db.Patients.Add(p);
}
UpdateDrugs(p, db);
}
db.SaveChanges();
}
}
private static void UpdateDrugs(Patient p, VAEContext ctx)
{
var Drugs = DB2Drug.GetDrugs(p.PatNo);
foreach (Drug d in Drugs)
{
var ExistingDrugRecord = ctx.Drugs.FirstOrDefault(
ed => ed.DrugDDI == d.DrugDDI &&
ed.DispenseDate == d.DispenseDate &&
ed.OrderId == d.OrderId
);
if (ExistingDrugRecord == null)
{
d.Patient = p;
ctx.Drugs.Add(d);
}
}
}
Why new context every time something needs to be inserted? Both methods UpdatePatients and UpdateDrugs are private, you can use the same context for all linked operations and I'm sure you won't get the duplicates:
private static void UpdateDrugs(Patient p, VAEContext context)
...
Also there's probably no need to save on every drug, doing so likely decreases performance and doesn't do much in terms of data integrity. Consider saving the context changes once per linked updates (say after UpdateDrugs is called in UpdatePatients)
Other than that you can check out the ObjectContext.Attach and related methods on how to link the Patient object to your newly created Drugs context instance
http://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.attach.aspx