The instance of entity type 'x' cannot be tracked because another instance with the same key value for {'a', 'b'} is already being tracked - entity-framework

I got the error in the title while editing relational table between many-to-many relationship. It will not let duplicate on table, so I try to remove rows and then create new one but it didn't work.
public void Update(ThermoformProduct entity, int[] thermoformCategoryIds)
{
using (var context = new ShopContext())
{
var product = context.ThermoformProducts
.Include(i => i.ThermoformProductCategories)
.FirstOrDefault(i => i.ProductId == entity.ProductId);
if (product != null)
{
product.Code = entity.Code;
product.Culture = entity.Culture;
product.Renk = entity.Renk;
product.UstGenislik = entity.UstGenislik;
product.UstCap = entity.UstCap;
product.AltCap = entity.AltCap;
product.TbCap = entity.TbCap;
product.Yukseklik = entity.Yukseklik;
product.Hacim = entity.Hacim;
product.TamHacim = entity.TamHacim;
product.Baski = entity.Baski;
product.SosisIciAdet = entity.SosisIciAdet;
product.KoliIciAdet = entity.KoliIciAdet;
product.ImageUrl = entity.ImageUrl;
product.ThermoformProductCategories.RemoveAll(s=>s.ProductId == product.ProductId);
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId,
ThermoformProduct = product,
CategoryId = catid,
ThermoformCategory = context.ThermoformCategories.Where(i => i.CategoryId == catid).FirstOrDefault()
}).ToList();
context.SaveChanges();
}
}
}

EF cannot track two different instance of an entity type with same primary key. You have included the related ThermoformProductCategory entities, so they are being tracked by the context. When you remove them, they are cleared from the ThermoformProductCategories property of that product, but they are not removed from the context, and are still being tracked. Finally when you create the new list of ThermoformProductCategory, some of the new ones' primary key are matching the previous ones' (which already exist in the context)
Since you are creating the entire list again, you don't need to fetch the related entities in the first place. Simply assign a new list and EF will replace the entire list of related entities -
var product = context.ThermoformProducts.FirstOrDefault(i => i.ProductId == entity.ProductId);
if (product != null)
{
// set all the properties
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId,
CategoryId = catid
}).ToList();
context.SaveChanges();
}

Two things:
The filter is redundant - the ThermoformProductCategories navigation property should already be filtered.
product.ThermoformProductCategories.RemoveAll(s=>s.ProductId == product.ProductId);
instead do this:
product.ThermoformProductCategories.RemoveAll(); // Or .Clear()
Don't set navigation properties in this case, just the foreign key values - this should resolve your issue:
Instead of:
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId,
ThermoformProduct = product,
CategoryId = catid,
ThermoformCategory = context.ThermoformCategories.Where(i => i.CategoryId == catid).FirstOrDefault()
}).ToList();
Do:
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId, // Even this might be redundant since you're adding to the product navigation property list.
CategoryId = catid
}).ToList();

both answers was solution here is my final form if anyone gets trouble
public void Update(ThermoformProduct entity, int[] thermoformCategoryIds)
{
using (var context = new ShopContext())
{
var product = context.ThermoformProducts
.FirstOrDefault(i => i.ProductId == entity.ProductId);
if (product != null)
{
product.Code = entity.Code;
product.Culture = entity.Culture;
product.Renk = entity.Renk;
product.UstGenislik = entity.UstGenislik;
product.UstCap = entity.UstCap;
product.AltCap = entity.AltCap;
product.TbCap = entity.TbCap;
product.Yukseklik = entity.Yukseklik;
product.Hacim = entity.Hacim;
product.TamHacim = entity.TamHacim;
product.Baski = entity.Baski;
product.SosisIciAdet = entity.SosisIciAdet;
product.KoliIciAdet = entity.KoliIciAdet;
product.ImageUrl = entity.ImageUrl;
var cmd = "delete from ThermoformProductCategory where ProductId=#p0";
context.Database.ExecuteSqlRaw(cmd, product.ProductId);
product.ThermoformProductCategories = thermoformCategoryIds.Select(catid => new ThermoformProductCategory()
{
ProductId = product.ProductId,
CategoryId = catid,
ThermoformCategory = context.ThermoformCategories.Where(i => i.CategoryId == catid).FirstOrDefault()
}).ToList();
context.Entry(product).State = EntityState.Modified;
context.SaveChanges();
}
}
}

Related

How to optimize the query - Ef core

I am working a asp .net core web API with EF core.
I wrote this query. But this take 20-30seconds to execute.
Anyone have idea to improve this query.
var hotels = await _context.Hotels
.Where(i => (i.DestinationCode == request.Destination))
.Select(i => new HotelListHotelVm
{
Item1 = i.Item1,
Item2 = i.Item2,
Item3 = i.Item3,
Item4Code = i.Item4Code,
Item4Description = i.Item4.TypeDescription,
Item5 = i.Item5.Select(x => new HotelListHotelVm.HotelListItem5Vm
{
Code = x.Item5Code,
Description = x.Item5.Description,
}).Where(x =>(incomingItem5s.Length > 0 ) ? (incomingItem5s.Contains(x.Code)) : (x.Code != "")),
Item6 = i.Item6.Select(x => new HotelListHotelVm.HotelListHotelItem6Vm
{
Id = x.Id,
Item6TypeCode = x.Item6TypeCode,
Order = x.Order,
Path = x.Path,
VisualOrder = x.VisualOrder,
}).Take(3),
HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm {
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode
}),
})
.Where( i => ((incomingItem4.Length > 0 ) ? (incomingItem4.Contains(i.Item4Code)) : (i.Item4Code != "")) )
.OrderByDescending(i => i.Code)
.PaginatedListAsync(request.PageNumber, request.PageSize);
foreach( var item in hotels.Items){
foreach(var facility in item.HotelFacilities){
foreach( var fac in _context.Facilities){
if(facility.FacilityCode == fac.Code){
facility.HotelFacilityDescription = fac.Description;
}
}
}
}
I f I remove those foreach code, The query takes 8-10s to execute.
But I need those foreach codes. Because I need the HotelFacilityDescription
Any suggestion for optimize the query ?
Edit The i.Facilities - model
public class HotelFacility
{
// removed some
public int FacilityCode { get; set; }
public int FacilityGroupCode { get; set; }
public FacilityGroup FacilityGroup { get; set; }
public int HotelCode { get; set; }
public Hotel Hotel { get; set; }
}
}
_context.Facilities will be enumerated (i.e. database will be called) for every iteration of previous loops. The quick fix is to call it ones and store results in variable:
var facilities = _context.Facilities.ToList();
foreach( var item in hotels.Items){
foreach(var facility in item.HotelFacilities){
foreach(var fac in facilities){
if(facility.FacilityCode == fac.Code){
facility.HotelFacilityDescription = fac.Description;
}
}
}
}
Next improvement can be converting facilities into Dictionary for searching purposes.
Even better approach can be writing query joining with _context.Facilities on database side (but here more info needed).
I've read this a couple times, but it looks like the relationship for Hotel.Facilities is a Facility, so could you not just do:
HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm {
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = x.Description
}),
If for some reason Hotel.Facilities is not pointing at a Facility, but is a Many-to-Many HotelFacilityGroup entity to a FacilityGroup, that also contains a FacilityCode, if the associated FacilityGroup has access to a set of Facilities beneath it you could leverage that:
Edit: It sounds like multiple Facilities share the same Code where some may have a null description. Provided that the facilities matching the code would be within the same facility group and not consider the same Code within different facility groups. If you need to match the code across all facilities then there probably isn't much of an alternative to loading the entire set of facility codes & descriptions.
HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm {
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = x.FacilityGroup.Facilities.Where(f => f.Code == x.FacilityCode && f.Description != null).Select(f => f.Description).FirstOrDefault()
}),
That would avoid the need to go load all of the facilities to resolve that code. Otherwise, if you do need to fetch across all facilities, pre-loading them would be the way to go, but rather than fetching the entire Facility entity I would recommend just the values you need, the Code and the Description. This cuts down on the amount of memory needed and potentially be a faster query:
var facilities = _context.Facilities
.Select(f => new
{
f.Code,
f.Description
}).ToList();
Edit:
From there, finding a match using:
foreach( var facility in hotels.Items.SelectMany(x => x.HotelFacilities)
{
facility.HotelFaciltyDescription = facilities
.Where(x => x.Code == facility.FacilityCode
&& !string.IsNullOrEmpty(x.Description)
.Select(x => x.Description)
.FirstOrDefault();
}
I would recommend an OrderBy clause to ensure the selection of the facility is predictable as it sounds like there could be multiple matches on a code with a non-null description.
The loop can be eliminated by projecting the value in the LINQ to Entities query.
It would have been quite easy if you had relationship and navigation property like other *Code fields. But as clarified in the comments, there is no such relationship, so you have to resort to old good manual left other join to emulate what navigation property provide automatically, e.g.
HotelFacilities = i.Facilities.ToList().Distinct().Take(6)
// left outer join with Facilities
.SelectMany(x => _context.Facilities
.Where(f => x.FacilityCode == f.Code).DefaultIfEmpty(),
(x, x_Facility) => new HotelListHotelVm.HotelListFacilityVm
{
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = x_Facility.Description // <--
}),
Here x_Facility emulates optional reference navigation property x.Facility if existed.
In case you need just single property from the related table, instead of a left join you could also use the original query with single value returning correlated subquery inside the projection, e.g.
HotelFacilities = i.Facilities.ToList().Distinct().Take(6)
.Select(x => new HotelListHotelVm.HotelListFacilityVm
{
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = _context.Facilities
.Where(f => x.FacilityCode == f.Code)
.Select(f => f.Description)
.FirstOrDefault() // <--
}),
or even
HotelFacilityDescription = _context.Facilities
.FirstOrDefault(f => x.FacilityCode == f.Code).Description
All these will eliminate the need of the post loop executiong additional database queries. You can test them and take the one with best performance (#2 and #3 produce one and the same SQL, so it's a matter of taste - the choice is between #1 and #2/3).

UpdateRange method of Entity Framework Core does not work

The UpdateRange method of Entity Framework Core is used here to update multiple records but it is not working.
My code is:
var dept1 = new Department()
{
Id = 8,
Name = "New Designing"
};
var dept2 = new Department()
{
Id = 9,
Name = "New Research"
};
var dept3 = new Department()
{
Id = 102,
Name = "New HR"
};
List<Department> modifiedDept = new List<Department>() { dept1, dept2, dept3 };
using (var context = new CompanyContext())
{
context.UpdateRange(modifiedDept);
await context.SaveChangesAsync();
}
And the error I get is:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 'Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.'
What should be done in this case?
You are supposed to get data from database and modify data. Not creating new class.
using (var context = new JobContext())
{
var depts = context.Department.Where(x => x.Id > 1).AsQueryable();
depts.Where(x => x.Id == 2).FirstOrDefault().Name = "New Designing";
depts.Where(x => x.Id == 3).FirstOrDefault().Name = "New Research";
depts.Where(x => x.Id == 4).FirstOrDefault().Name = "New HR";
context.UpdateRange(depts);
context.SaveChanges();
}
Before
After

Prevent sort result of union in entity framework

In SQL server union, result is sorted based on primary key column. I want to prevent this behavior in entity framework.
In this post, #praveen has explained how to do this in pure sql. But I want to do this in entity framework.
My code:
public virtual ActionResult Search(string keyword)
{
var products = _db.Products
.Where(x => x.IsActive)
.AsQueryable();
var productExactlyTitle = products.Where(x => x.Title == keyword);
var productStartTitle = products.Where(x => x.Title.StartsWith(keyword));
var productContainsTitle = products.Where(x => x.Title.Contains(keyword)
|| x.Title.Contains(keyword)
|| x.SubTitle.Contains(keyword)
|| x.OtherName.Contains(keyword));
var productList = productExactlyTitle.Union(productStartTitle)
.Union(productContainsTitle)
.Take(10)
.AsEnumerable()
.Select(x => new ProductItemViewModel()
{
Id = x.Id,
Title = x.Title,
Price = x.Price.ToPrice(),
Image = x.Images.FirstOrDefault(y => y.IsCoverPhoto)?.ImageUrl
});
// some code ...
}
I want to show records with below order:
First: records of productExactlyTitle
Second: records of productStartTitle
Third: records of productContainsTitle
But result is sorted with Id column! and I don't want this.
Is there a way for do this?
In SQL all queries without an order by explicitly set is considered unordered. (and EF queries a translated into SQL). So if you want a specific order after your union just specify it.
var result = q1.Union(q2).OrderBy(x => x.?);
For your specific case:
var p1 = productExactlyTitle.Select(x => new { Item = x, Order = 1 });
var p2 = productStartTitle.Select(x => new { Item = x, Order = 2 });
var p3 = productContainsTitle.Select(x => new { Item = x, Order = 3 });
var productList = p1.Union(p2)
.Union(p3)
.OrderBy(x => x.Order)
.Select(x => x.Item)
.Take(10);

Single database call pulling data from multiple tables in EF Core

The following code currently opens a connection three times to my database, to pull out each object.
Is there a better way to craft the query so the database is only hit once and pulls back all the objects I'm looking for?
var metadataResult = new MetadataViewModel
{
Milestones = goalsContext.Milestones.Select(m => new MilestoneViewModel
{
Id = m.Id,
Name = m.Name,
Year = m.Year,
Date = m.Date
}),
Aggregates = goalsContext.Aggregates.Select(a => new AggregateViewModel
{
Id = a.Id,
Name = a.Name
}),
Metrics = goalsContext.Metrics.Select(m => new MetricViewModel
{
Id = m.Id,
Name = m.Name,
Description = m.Description
})
};
If your view models are a fairly similar shape then you should be able to use Union to get everything in one query and then transform the rows into appropriate ViewModel instances afterwards. Something like the following -
var combinedResults =
context.Products.Select(p => new
{
Type = "Product",
ID = p.ProductID,
Name = p.ProductName,
SupplierName = p.Supplier.CompanyName
})
.Union(
context.Categories.Select(c => new
{
Type = "Category",
ID = c.CategoryID,
Name = c.CategoryName,
SupplierName = (string)null
})
)
.ToList();
var viewModel = new ViewModel
{
Products = combinedResults
.Where(x => x.Type == "Product")
.Select(x => new ProductViewModel
{
ID = x.ID,
Name = x.Name,
SupplierName = x.SupplierName
}),
Categories = combinedResults
.Where(x => x.Type == "Category")
.Select(x => new CategoryViewModel
{
ID = x.ID,
Name = x.Name
})
};

Entity Framework - how to save entity without saving related objects

In my Entity Framework, I have three related entities 'Client', 'ClientAddress' and 'LookupAddressType'. "LookupAddressType" is a master class specifying the type of available address type, like business address, residential address etc. ClientAddress depend on LookupAddresstype and Client. While saving a Client entity with relevant ClientAddress data, i'm getting following error.
"Violation of PRIMARY KEY constraint 'PK_LookupAddressType'. Cannot
insert duplicate key in object 'dbo.LookupAddressType'. The statement
has been terminated.
I do not need LookupAddressType to be inserted. Here I just need the relevant lookupAddressTypeId to be inserted in clientAddress entity.
The Saving code is like this:
Add(Client);
_objectContext.SaveChanges();
how can i do this?
The Load Code is below:
private void LoadClientDetails(EFEntities.Client _Client)
{
EFEntities.LookupClientStatu clientStatus;
var clientAddressList = new List<ClientAddress>();
if (_Client == null)
{
return;
}
//Assign data to client object
_Client.ClientName = rtxtName.Text;
_Client.Alias = rtxtAlias.Text;
_Client.ClientCode =Int32.Parse(rtxtClientCode.Text);
_Client.TaxPayerID = rtxtTaxPayerId.Text;
if (rcboStatus.SelectedIndex != 0)
{
clientStatus = new EFEntities.LookupClientStatu
{
ClientStatusID = (Guid) (rcboStatus.SelectedValue),
ClientStatusDescription = rcboStatus.Text
};
_Client.LookupClientStatu = clientStatus;
}
//_Client.Modified = EnvironmentClass.ModifiedUserInstance.Id;
_Client.EffectiveDate = rdtEffectiveDate.Value;
if (rdtExpDate.Value != rdtExpDate.MinDate)
{
_Client.ExpirationDate = rdtExpDate.Value;
}
else
{
_Client.ExpirationDate = null;
}
_Client.StartDate = DateTime.Now;
EFEntities.ClientAddress clientAddress = null;
// Iesi.Collections.Generic.ISet<ClientAddress> clientAddress = new HashedSet<ClientAddress>();
foreach (var cAddress in _clientController.client.ClientAddresses)
{
clientAddress = cAddress;
break;
}
if (clientAddress == null)
{
clientAddress = new EFEntities.ClientAddress();
}
clientAddress.Address1 = rtxtClientAdd1.Text;
clientAddress.Address2 = rtxtClientAdd2.Text;
clientAddress.Address3 = rtxtClientAdd3.Text;
// Address type details
if (rcboClientAddType.SelectedIndex != -1)
{
clientAddress.LookupAddressType = new EFEntities.LookupAddressType
{
AddressTypeID = (Guid) (rcboClientAddType.SelectedValue),
AddressTypeDescription = rcboClientAddType.Text
};
//clientAddress.AddressType.Id = Convert.ToByte(rcboClientAddType.SelectedValue);
}
clientAddress.City = rtxtClientCity.Text;
clientAddress.Client = _Client;
\
_Client.ClientAddresses.Add(clientAddress);
}
Well I did this to the following lines of code to make it work.
if (rcboClientAddType.SelectedIndex != -1)
{
clientAddress.LookupAddressType = new EFEntities.LookupAddressType
{
AddressTypeID = (Guid) (rcboClientAddType.SelectedValue),
AddressTypeDescription = rcboClientAddType.Text
};
//clientAddress.AddressType.Id = Convert.ToByte(rcboClientAddType.SelectedValue);
}
I changed the above code into this
if (rcboClientAddType.SelectedIndex != -1)
{
//clientAddress.LookupAddressType = new EFEntities.LookupAddressType
// {
// AddressTypeID = (Guid) (rcboClientAddType.SelectedValue),
// AddressTypeDescription = rcboClientAddType.Text
// };
clientAddress.AddressTypeID = (Guid)(rcboClientAddType.SelectedValue);
}