Nested existing entity is duplicated when saving parent - c#

I am having a problem where when I try to save a new entity that has existing entities nested. Instead of creating a relationship with existing entities it is duplicating them.
This is roughly my model:
public class Record
{
public int ID { get; set; }
public string RecordValue { get; set; }
public virtual ICollection<AddressLine> AddressLines { get; set; }
}
public class AddressLine
{
public int ID { get; set; }
public string AddressLineValue { get; set; }
public virtual ICollection<AddressLineType> AddressLineTypes { get; set; }
}
public class AddressLineType
{
public int ID { get; set; }
public string AddressLineTypeValue { get; set; }
}
I don't want any duplicate AddressLineTypes added so in my code I am doing something like this:
public void button1_Click(object sender, EventArgs e)
{
Record r = new Record();
r.RecordValue = "Record value";
AddressLine al = new AddressLine();
al.AddressLineValue = "Address line value";
AddressLineType alt;
using (var db = new MyDbContext())
{
alt = db.AddressLineTypes.Single(x => x.Value == "TypeValue");
}
al.AddressLineTypes.Add(alt);
r.AddressLines.Add(al);
SaveRecord(r);
}
public void SaveRecord(Record r)
{
using (var db = new MyDbContext())
{
db.Records.Add(r);
db.SaveChanges();
}
}
I have hit a breakpoint before db.SaveChanges() and the AddressLineType ID is populated but it creates new entries in the database as if ID == 0.
How do I stop the existing AddressLineTypes duplicating on save?

Try using a single Context:
...
using (var db = new MyDbContext())
{
alt = db.AddressLineTypes.Single(x => x.Value == "TypeValue");
al.AddressLineTypes.Add(alt);
r.AddressLines.Add(al);
SaveRecord(r, db);
}
}
public void SaveRecord(Record r, MyDbContext db)
{
db.Records.Add(r);
db.SaveChanges();
}

Related

One-to-many update in EF Core with value object

I have 3 classes as you can see :
public class car
{
public int Id { set; get; }
public string Name { set; get; }
public List<CarTest> CarTest { get; set; }
public car(string name, int id, List<CarTest> carTests)
{
Id = id;
Name = name;
CarTest = carTests;
}
public car()
{
}
}
public class CarTest
{
public OVTest OV { get; set; }
public string CarOV { get; set; }
}
public class OVTest
{
public string Name { get; set; }
}
I want to edit my value as you can see :
static void Main(string[] args)
{
myctx myc = new myctx();
var cartests = new List<CarTest>();
var cartest = new CarTest();
var OV = new OVTest();
OV.Name = "editedName";
cartest.OV = OV;
cartest.CarOV = "2OV";
cartests.Add(cartest);
var editcar = new car("ee5155fe",2, cartests);
myc.ChangeTracker.TrackGraph(editcar, e =>e.Entry.State = EntityState.Modified);
myc.SaveChanges();
Console.ReadLine();
}
But I get this error :
System.InvalidOperationException: 'The property 'CarTestId' on entity type 'OVTest' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.'
This is my config:
public class CarConfig : IEntityTypeConfiguration<car>
{
public void Configure(EntityTypeBuilder<car> builder)
{
builder.OwnsMany(u => u.CarTest);
builder.OwnsMany(u => u.CarTest).OwnsOne(c=>c.OV);
}
}

Automapper 8 Ignore Attribute doesn't work on child descendant

I use AutoMapper 8.1.0 in a asp.net core project. I have an Automapper mapping that doesn't work as I expected. I reproduced the configuration so you can test it by yourself. So I have an ExpenseReport with a collection of ExpenseReportItem and this one with another collection. I have to keep the data of eTaxCollection after the mapping, but they are lost in the process.
So the question is why values of eTaxCollections are lost after calling _mapper.Map(vmodel, model) and how can I keep them?
The ignore attribute don't work. I also tried UseDestinationValue(). I lost 2 days trying to figure it out and I'm exhausted.
public void WeatherForecasts()
{
int[] excludeTaxes = new int[] { 2 };
var vmodel = new ExpenseReportCreateEditModel();
vmodel.Expenses.Add(new ExpenseReportItemModel()
{
ExcludeTaxIds = excludeTaxes,
Total = 12,
Id = 1
});
// fetch from bd
var model = new ExpenseReport();
// values will be lost after _mapper.Map...
var eTaxCollections = new HashSet<ExcludeExpenseReportItemTax>();
eTaxCollections.Add(new ExcludeExpenseReportItemTax()
{
TaxId = 1,
ExpenseReportItemId = 1
});
model.Items.Add(new ExpenseReportItem()
{
ExcludeTaxes = eTaxCollections,
ExpenseReportId = 1,
Id = 9
});
_mapper.Map(vmodel, model);
}
public class ExpenseReportCreateEditModelProfile : Profile
{
public ExpenseReportCreateEditModelProfile()
{
CreateMap<ExpenseReportCreateEditModel, ExpenseReport>()
.ForMember(d => d.Items, s => s.MapFrom(m => m.Expenses));
}
}
public class ExpenseReportItemModelProfile : Profile
{
public ExpenseReportItemModelProfile()
{
CreateMap<ExpenseReportItemModel, ExpenseReportItem>()
.ForMember(d => d.ExcludeTaxes, s => s.Ignore()); // <<<==== data are lost
}
}
public class ExpenseReportCreateEditModel
{
public int Id { get; set; }
public ICollection<ExpenseReportItemModel> Expenses { get; set; }
public ExpenseReportCreateEditModel()
{
Expenses = new HashSet<ExpenseReportItemModel>();
}
}
public class ExpenseReportItemModel
{
public int Id { get; set; }
public ICollection<int> ExcludeTaxIds { get; set; }
public decimal Total { get; set; }
public ExpenseReportItemModel()
{
ExcludeTaxIds = new HashSet<int>();
}
}
public class ExpenseReport
{
public int Id { get; set; }
public virtual ICollection<ExpenseReportItem> Items { get; set; }
public ExpenseReport()
{
Items = new HashSet<ExpenseReportItem>();
}
}
public class ExpenseReportItem
{
public int Id { get; set; }
public int ExpenseReportId { get; set; }
public virtual ICollection<ExcludeExpenseReportItemTax> ExcludeTaxes { get; set; }
public ExpenseReportItem()
{
ExcludeTaxes = new HashSet<ExcludeExpenseReportItemTax>();
}
}
public class ExcludeExpenseReportItemTax
{
public int ExpenseReportItemId { get; set; }
public virtual ExpenseReportItem ExpenseReportItem { get; set; }
public int TaxId { get; set; }
}
Thank you for any help
Edit
I execute the execution plan and perhaps this is the problem:
$typeMapDestination = ($dest ?? .New WebApplication1.Controllers.SampleDataController+ExpenseReportItem());
This is only way I can lost the values.
I have to find a solution now
Here the complete execution plan :
.If ($src == null) {
.Default(WebApplication1.Controllers.SampleDataController+ExpenseReportItem)
} .Else {
.Block() {
$typeMapDestination = ($dest ?? .New WebApplication1.Controllers.SampleDataController+ExpenseReportItem());
.Try {
.Block(System.Int32 $resolvedValue) {
.Block() {
$resolvedValue = .If (
$src == null || False
) {
.Default(System.Int32)
} .Else {
$src.Id
};
$typeMapDestination.Id = $resolvedValue
}
}
} .Catch (System.Exception $ex) {
.Block() {
.Throw .New AutoMapper.AutoMapperMappingException(
"Error mapping types.",
$ex,
.Constant<AutoMapper.TypePair>(AutoMapper.TypePair),
.Constant<AutoMapper.TypeMap>(AutoMapper.TypeMap),
.Constant<AutoMapper.PropertyMap>(AutoMapper.PropertyMap));
.Default(System.Int32)
}
};
$typeMapDestination
}
}

How to fill ObservableCollection from two tables from DataBase?

Trying to populate an ObservableCollection from a database using the Entity Framework. Everything was fine until I started working with linked tables.
I created the DeviceCategory and DeviceComplexity model, and now in the WyeModel I try to integrate them into the DeviceCategoryViewModel. Further, in DeviceCategoryViewModel, I indicated a request for taking information from the database, but I ran into a problem. How to fill in ObservableCollection with this information? I tried different ways, but it didn’t lead to anything, I just got more confused.
DeviceCategoriesViewModel
class DeviceCategoryViewModel
{
TechDContext dc = new TechDContext();
public int Device_category_id { get; set; }
public string Device_category_name { get; set; }
public int Device_complexity_id { get; set; }
public string Device_complexity_name { get; set; }
public static DeviceCategoryViewModel DeviceCaterogyVM(DeviceCategory deviceCategory, DeviceComplexity deviceComplexity)
{
return new DeviceCategoryViewModel
{
Device_category_id = deviceCategory.Device_category_id,
Device_category_name = deviceCategory.Category_name,
Device_complexity_id = deviceCategory.Device_complexity_id,
Device_complexity_name = deviceComplexity.Device_complexity_name
};
}
public void FillDeviceDategories()
{
var q = from cat in dc.DeviceCategories
join com in dc.DeviceComplexities on cat.Device_complexity_id equals com.Device_complexity_id
select new
{
Device_category_id = cat.Device_category_id,
Category_name = cat.Category_name,
Device_complexity_id = com.Device_complexity_id,
Device_complexity_name = com.Device_complexity_name
};
items = q;
deviceCategories = Convert(items);
}
public ObservableCollection<DeviceCategoryViewModel>
Convert(IEnumerable<object> original)
{
return new ObservableCollection<DeviceCategoryViewModel>(original.Cast<DeviceCategoryViewModel>());
}
private IEnumerable<object> items;
public IEnumerable<object> Items
{
get
{
return items;
}
}
private ObservableCollection<DeviceCategoryViewModel> deviceCategories;
public ObservableCollection<DeviceCategoryViewModel> DeviceCategories
{
get
{
FillDeviceDategories();
return deviceCategories;
}
}
DeviceCategory Model
[Table("device_categories")]
public class DeviceCategory
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Device_category_id { get; set; }
public string Category_name { get; set; }
//[ForeignKey]
public int Device_complexity_id { get; set; }
public DeviceCategory()
{
}
public DeviceCategory(string name, int complexity_id)
{
Category_name = name;
Device_complexity_id = complexity_id;
}
}
DeviceCompexity Model
[Table("device_complexities")]
public class DeviceComplexity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Device_complexity_id { get; set; }
public string Device_complexity_name { get; set; }
public DeviceComplexity()
{
}
public DeviceComplexity(string name)
{
Device_complexity_name = name;
}
}
I now get an error in the conversion method
You'd try to cast your LINQ query result to ObservableCollection<DeviceCategoryViewModel> in separate Convert function.
Why not to directly collect your LINQ query result to ObservableCollection<DeviceCategoryViewModel>
Just use like this
var q = from cat in dc.DeviceCategories
join com in dc.DeviceComplexities on cat.Device_complexity_id equals com.Device_complexity_id
select new DeviceCategoryViewModel // <= Note This Line
{
Device_category_id = cat.Device_category_id,
Category_name = cat.Category_name,
Device_complexity_id = com.Device_complexity_id,
Device_complexity_name = com.Device_complexity_name
};
deviceCategories = new ObservableCollection<DeviceCategoryViewModel>(q);
OR if you want to get result after list then simply use q.ToList()
deviceCategories = new ObservableCollection<DeviceCategoryViewModel>(q.ToList());

Entity Framework saves too many child entities

I'm working on a project that consists of multiple objects that I want to save to my database. I'm using a single context and a series of repository classes to access them.
When I try to save an entity, it seems to save all the virtual entities associated with it, even if that entity exists in the database already.
These are my classes:
public class Requirement
{
public int ID { get; set; }
public DateTime DateDue { get; set; }
public DateTime DateCompleted { get; set; }
public virtual Standard Standard { get; set; }
public virtual Project Project { get; set; }
}
public class Standard
{
public int ID { get; set; }
public int AgencyID { get; set; }
public string Name { get; set; }
public virtual Agency Agency { get; set; }
}
public class Project
{
public int ID { get; set; }
public bool Active { get; set; }
public virtual Agency Agency { get; set; }
public virtual Department Department { get; set; }
}
And this is the method I have for creating some data:
public class RequirementRepository
{
public static string CreateMockData()
{
StandardRepository stdRep = new StandardRepository();
ProjectRepository projRep = new ProjectRepository();
RequirementRepository reqRep = new RequirementRepository();
Project project = projRep.Find(1);
StringBuilder sb = new StringBuilder()
foreach (Standard s in stdRep.FindByAgencyID(project.Agency.ID))
{
Requirement r = new Requirement();
r.Project = project;
r.Standard = s;
r.DateCompleted = (DateTime)SqlDateTime.MaxValue;
r.DateDue = DateTime.Now.AddDays(90);
r = reqRep.Save(r);
sb.AppendLine(String.Format("Saved Requirement ID {0} with Project ID {1}<br>", r.ID, r.Project.ID));
}
return sb.ToString();
}
}
And here is associated repository code:
public class ProjectRepository
{
public Project Find(int id)
{
using (var db = new MyContext())
{
return db.Projects
.Include(p => p.Agency)
.Include(p => p.Department)
.First(p => p.ID.Equals(id));
}
}
}
public class StandardRepository
{
public List<Standard> FindByAgencyID(int agencyID)
{
using (var db = new MyContext())
{
return db.Standards.Where(r => r.AgencyID == agencyID).ToList();
}
}
}
public class RequirementRepository
{
public Requirement Save(Requirement requirement)
{
using (var db = new MyContext())
{
Requirement retVal = requirement;
if (requirement.ID.Equals(0))
{
retVal = db.Requirements.Add(requirement);
}
else
{
db.Entry(requirement).State = EntityState.Modified;
}
db.SaveChanges();
return retVal;
}
}
}
When I run this method, I expect it to insert a number of new Requirements into the database with the project ID of 1 and a standard ID of whatever standard it's on. Instead, it creates a whole new project and a whole new standard for every requirement it adds, then assigns those IDs to the requirement.
Each context keeps track of the entities loaded, modified and the entities added.
Your repositories need to look something like this....
public class StandardRepository
{
MyContext _context;
public StandardRepository(MyContext context)
{
_context = context;
}
public List<Standard> FindByAgencyID(int agencyID)
{
return _context.Standards.Where(r => r.AgencyID == agencyID).ToList();
}
}
public class RequirementRepository
{
MyContext _context;
public RequirementRepository(MyContext context)
{
_context = context;
}
public Requirement Save(Requirement requirement)
{
Requirement retVal = requirement;
if (requirement.ID.Equals(0))
{
retVal = _context.Requirements.Add(requirement);
}
else
{
_context.Entry(requirement).State = EntityState.Modified;
}
_context.SaveChanges();
return retVal;
}
}
public class RequirementRepository
{
public static string CreateMockData()
{
using(MyContext context = new MyContext())
{
StandardRepository stdRep = new StandardRepository(context);
ProjectRepository projRep = new ProjectRepository(context);
RequirementRepository reqRep = new RequirementRepository(context);
Project project = projRep.Find(1);
StringBuilder sb = new StringBuilder()
foreach (Standard s in stdRep.FindByAgencyID(project.Agency.ID))
{
Requirement r = new Requirement();
r.Project = project;
r.Standard = s;
r.DateCompleted = (DateTime)SqlDateTime.MaxValue;
r.DateDue = DateTime.Now.AddDays(90);
r = reqRep.Save(r);
sb.AppendLine(String.Format("Saved Requirement ID {0} with Project ID {1}<br>", r.ID, r.Project.ID));
}
}
return sb.ToString();
}
}
From my understanding, you shouldn't have to manually set the state of the object you have modified unless you have detached it from its context. EF keeps track of the object state.
I like to use something like this:
public abstract class EntityRepositoryBase<TEntity> : IDisposable, IEntityRepositoryBase<TEntity> where TEntity : class , IEntityWithId
{
protected EntityRepositoryBase()
{
Context = new SomeEntities();
}
public abstract ObjectSet<TEntity> EntityCollection { get; }
public SomeEntities Context { get; set; }
public TEntity GetById(int id)
{
return EntityCollection
.FirstOrDefault(x => x.Id == id);
}
public void Dispose()
{
Context.Dispose();
}
}
Then in the deriving repositories:
public class AnswerRepository : EntityRepositoryBase<AnswerEntity>, IAnswerRepository
{
public override ObjectSet<AnswerEntity> EntityCollection
{
get { return Context.AnswerEntities; }
}
}
I inject the Repositories into the relevant class using ninject but you should be able to get similar with:
using (var repo = new AnswerRepository())
{
// modifying via Context
var someEntity = repo.GetById(someId);
someEntity.Value = "1";
repo.Context.SaveChanges();
//modifying via repo
repo.Delete(anotherEntity);
}
and then doing what you need to do. The context is exposed via the interface IEntityRepositoryBase should you need to perform out-of-repository modifications and then SaveChanges() as well as any specific CRUD type methods in your repo. Once out of scope the object and the underlying connection will be closed.

entity framework saves first item in the loop but none other

In my controller I'm looping through items and saving them to my db. The problem is that it saves the first item, but none of the others. I put a breakpoint on the "SaveItem()" line in the loop and it hits it every time, but what seems odd to me is that it only goes through to the method for the 1st item.
What am I doing wrong?
public void SubmitItem(Cart cart, ShippingDetails shippingDetails, ProcessedItems processedItem, string orderID)
{
var cartItems = cart.Lines;
//CartIndexViewModel cartIndex = new CartIndexViewModel();
//var customID = cartIndex.OrderID;
foreach(var item in cartItems)
{
processedItem.OrderID = orderID;
processedItem.ProductID = item.Product.ProductID;
processedItem.Name = item.Product.Name;
processedItem.Description = item.Product.Description;
processedItem.Price = item.Product.Price;
processedItem.Category = item.Product.Category;
processedItem.ImageName = item.Product.ImageName;
processedItem.Image2Name = item.Product.Image2Name;
processedItem.Image3Name = item.Product.Image3Name;
processedItem.BuyerName = shippingDetails.Name;
processedItem.Line1 = shippingDetails.Line1;
processedItem.Line2 = shippingDetails.Line2;
processedItem.Line3 = shippingDetails.Line3;
processedItem.City = shippingDetails.City;
processedItem.State = shippingDetails.State;
processedItem.Zip = shippingDetails.Zip;
processedItem.Country = shippingDetails.Country;
processedItem.Status = "Submitted";
processedItems.SaveItem(processedItem);
}
}
public class EFProcessedItemsRepository : IProcessedItems
{
private EFDbContext context = new EFDbContext();
public IQueryable<ProcessedItems> ProcessedItem
{
get { return context.ProcessedItems; }
}
public void SaveItem(ProcessedItems processedItem)
{
if(processedItem.ProcessedID == 0)
{
try
{
context.ProcessedItems.Add(processedItem);
context.SaveChanges();
}
catch (Exception)
{
throw;
}
}
else
{
context.Entry(processedItem).State = EntityState.Modified;
}
}
public void DeleteItem(ProcessedItems processedItem)
{
context.ProcessedItems.Remove(processedItem);
context.SaveChanges();
}
}
here is the class for the processedItem:
public class ProcessedItems
{
[Key]
public int ProcessedID { get; set; }
public string OrderID { get; set; }
public int ProductID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
public string ImageName { get; set; }
public string Image2Name { get; set; }
public string Image3Name { get; set; }
public string Status { get; set; }
//shipping
public string BuyerName { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string Line3 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public string Country { get; set; }
}
Interface:
public interface IProcessedItems
{
IQueryable<ProcessedItems> ProcessedItem { get; }
void SaveItem(ProcessedItems processedItem);
void DeleteItem(ProcessedItems processedItem);
}
try calling context.SaveChanges() after adding all of the items, I think it should persist them all in one go.
Another thing to try:
Refactor your code so that SaveItem accepts only one item to save, Add it and call SaveChanges()
Loop through the cart items outside the method and call the method with one item to save at a time.
// set orderID, shippingDetails above
foreach(var item in cartItems)
{
ProcessedItems processedItem = new ProcessedItems();
processedItem.OrderID = orderID;
processedItem.ProductID = item.Product.ProductID;
processedItem.Name = item.Product.Name;
processedItem.Description = item.Product.Description;
processedItem.Price = item.Product.Price;
processedItem.Category = item.Product.Category;
processedItem.ImageName = item.Product.ImageName;
processedItem.Image2Name = item.Product.Image2Name;
processedItem.Image3Name = item.Product.Image3Name;
processedItem.BuyerName = shippingDetails.Name;
processedItem.Line1 = shippingDetails.Line1;
processedItem.Line2 = shippingDetails.Line2;
processedItem.Line3 = shippingDetails.Line3;
processedItem.City = shippingDetails.City;
processedItem.State = shippingDetails.State;
processedItem.Zip = shippingDetails.Zip;
processedItem.Country = shippingDetails.Country;
SubmitItem(processedItem);
}
public void SubmitItem(ProcessedItems processedItem)
{
processedItem.Status = "Submitted";
processedItems.SaveItem(processedItem);
}
I think it is because processedItem is the same instance for each loop iteration. So after it has been through SaveItem once, it has its ProcessedID set and therefore won't get processed again.
My first guess is that you always store one entity, which is stored in processedItem, which is a input parameter. Try to create new Entity on each loop and then save it. In other words, you assign values to input parameter
processedItem.OrderID = orderID;
and then store same entity each time, but with changed fields
processedItems.SaveItem(processedItem);

Categories

Resources