DbSet.Attach() only updates single table but not referencing ones - database

I have two tables: Word and Adjective, both with some properties. Primary key of both tables is ID, Adjective.ID also references Word.ID as foreign key so there is a 1-1 relationship.
I also have a repository for any kind of table with an Update function.
public void Update(T entity) {
var entry = DatabaseContext.Entry(entity);
DatabaseSet.Attach(entity);
entry.State = EntityState.Modified;
}
I take a value from the database, convert it into a ViewModel looking like this (of course it's actually a little more complex):
public class WordModel {
public int ID { get; set; }
public string OriginalWord { get; set; }
}
public class AdjectiveModel : WordModel {
public string Translation { get; set; }
}
Then I alter the values of properties Word and Translation, convert and write it back. After conversion I have an object like this:
Word = {
ID = 1
OriginalWord = y
Adjective = {
ID = 1
Translation = z
}
}
Upon updating however, only one table gets updated.
Database.Words.Update(Word) only updates the OriginalWord value in the Word table,
Database.Adjectives.Update(Word.Adjective) only updates the Translation value in the Adjective table.
When running the updates for both tables sequentially I get an InvalidOperationException: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Creating a new database entry works perfectly.
I cannot believe I have to update both tables on their own and then save the context for each. I have created the database Repository via a Tutorial that obviously didn't explain well enough what's going on with the DbSet and the DbContext, which leaves me a little helpless here.
Sadly I have no link (it is quite a while ago I created the database project)
So, what am I doing wrong here?

You entity Word contains an entity Adjective, it is then the root of the object graph. Now generally here's what you should keep in mind in the following situations :
All objects in the graph are new (new word and new adjective)
use myDbContext.Words.Add(myNewWordObjectGraph); to have the correct state you want.
Only root is new (new word and a pre-existing non modified adjective)
use myDbContext.Entry(myNewWord).state = EntityState.Added; to have the correct state you want.
Root is modified and some nodes are modified (word and adjective both exist in the DB and both have been modified)
use myDbContext.Entry(myWord).State = EntityState.Modified; and myDbContext.Entry(myAdjective).State = EntityState.Modified; to have the correct state you want. i.e. call myDbContext.Entry(myObject).State = EntityState.Modified; for each modified object in the graph whether it's the root or some other node.
Root is unchanged and/or Modified and some nodes are added, others are also unchanged and/or modified
use myDbContext.MyRootObjectDbSet.Add(myRootObject); ; this will mark all the objects in the graph as EntityState.Added including the unchanged and/or modified objects. so the next call should be for each unchanged and/or modified object in order to correct its state : myDbContext.Entry(myObject).State = ThisObjectSCorrectState;.
I Hope that helps
EDIT
Calling DbSet.Attach(...) just adds the object to the objects tracked by EF. If you modify an object before calling DbSet.Attach(...), the modifications won't be persisted to DB when you call SaveChages(), so attaching an object as is before modification, calling DbSet.Attach(...) and then modifying the object is the way to make EF aware of the modifications.
Based on the way your update method's defined I would assume your repository looks something like this maybe?
//Not threadsafe as it contains a transient object 'DbContext'.
public class Repository<T> : IRespository<T> where T : class
{
private readonly MyDbContext context;
public Repository(MtDbContext context)
{
this.context = context
}
//...
public void Update(T entity) {... }
public void Commit() { context.SaveChanges(); }
}
I would suggest changing the update method to the following :
public void Update(T entity)
{
context.Entry(entity).State = EntityState.Modified;
}
And this update method would be called for each object you updated in the graph using the same instance of the repository enclosing the DbContext.

Related

Get subset of rows from table and filter after the query is executed

I am using entity framework core with models for all the tables in the SQL database.
I have a linq query that pulls rows from a table - let's call it facilities.
Then, I iterate the results of the query using fornext() (don't ask) :)
Within the loop we pull data from various other tables that are related to facilities.
Obviously this is a huge performance issue since there can be up to 100 rows in facilities which means the DB gets queried every time the loop iterates for every additional table we are pulling from. Note that some of the tables are from another database and you cannot join between contexts. Already tried that.
So, I thought to myself, let's pull all the rows from the related tables before we process the loop. That way, we only make those db calls one time for each associated table.
var pracloc = _ODSContext.AllPractitionerLocations
.Where(l => l.AllPractitionerLocationID != 0);
And, that works just fine.
Next step, let's simplify the code and pull some of those db calls out into private methods within the class.
For example:
Here's where I call the method (this replaces the line above).
var pracloc = GetAllPractitionerLocationsDTO();
Here's the method.
private AllPractitionerLocationsDTO GetAllPractitionerLocationsDTO()
{
AllPractitionerLocationsDTO dto = new();
dto.MyList = new List<AllPractitionerLocationDTO>();
var myo = _ODSContext.AllPractitionerLocations
.Where(s => s.AllPractitionerLocationID != 0)
.Select(g => new AllPractitionerLocationDTO()
{
AllPractitionerLocationID = g.AllPractitionerLocationID
});
dto.MyList = myo.ToList();
return dto;
}
Here's the subsequent filter (which is unchanged between the two data queries above):
var PracLocation = pracloc
.Where(a => a.LocationID = provider.LocationID)
.FirstOrDefault();
And, this works fine as long as I pull the data by querying the DB directly as in the first line above.
When I try to pull the data in the method, the line above throws:
'AllPractitionerLocationsDTO' does not contain a definition for 'Where' and no accessible extension method 'Where' accepting a first argument of type 'AllPractitionerLocationsDTO' could be found (are you missing a using directive or an assembly reference?)
AllPractitionerLocationsDTO is a model class with a subset of the rows in the "real" model:
public class AllPractitionerLocationDTO
{
public int SRCAllPractitionerLocationID { get; set; }
public int AllPractitionerLocationID { get; set; }
}
public class AllPractitionerLocationsDTO
{
public List<AllPractitionerLocationDTO> MyList;
}
Since that is identical in structure to the actual DB table, why won't the where clause work? Or, how can I implement my own where within the model class?
I even tried adding the dbset<> to the context. Still didn't work
public virtual DbSet<AllPractitionerLocationDTO> AllPractitionerLocationDTOs { get; set; }
Help me please.
You have to return IQueryable from your method. Only in this case you can reuse it later and filter effectively:
private IQueryable<AllPractitionerLocationDTO> GetAllPractitionerLocationsDTO()
{
var query = _ODSContext.AllPractitionerLocations
.Where(s => s.AllPractitionerLocationID != 0)
.Select(g => new AllPractitionerLocationDTO
{
AllPractitionerLocationID = g.AllPractitionerLocationID
});
return query;
}

Spring data JPA inserting null into column error even though I POST a value?

I want to save both child and parent entities whenever a POST call is made. I have an Item entity with a one to one mapping to a parent table Attribute:
#Entity
#Table(name="Item")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Item
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name="attr_id")
private Attribute attribute;
#OneToMany(mappedBy = "item", cascade = CascadeType.ALL, orphanRemoval=true)
private List<ValueSet> valueSets = new ArrayList<>();
// Other fields, getters, setters, overriding equals and hashcode using Objects.equals() and Objects.hashCode() for all the fields, helper for adding and removing ValueSet
}
The Attribute entity looks like this:
#Entity
#Table(name="Attribute")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Attribute
{
#Id
#Column(name="id")
private Long id;
// Other fields, getters, setters, NOT overriding equals hashCode
}
Whenever an Item gets saved I need the Attribute to get saved as well. I've my postman sending JSON POST data as follows:
{
"attribute":{
"id":"6"
},
"valueSets":[
{
"value":"basic"
}
]
}
My handler looks like this:
#PostMapping("/item")
public void postItems(#RequestBody Item item)
{
itemRepository.save(item);
}
ItemRepository is just a one liner with #Repository annotation:
public interface ItemRepository extends CrudRepository<Item, Long>
When I try to save the Item I run into - Cannot insert the value NULL into column 'attr_id', table 'Item'; column does not allow nulls. INSERT fails.
I can't figure out why is it unable to take the id value of 6 that I am supplying as part of my POST invocation. The id value 6 already exists on the Attribute table. I have also tried making the relationship bi-directional using mappedBy and CASCADE.ALL but still get the same error.
Any thoughts/suggestions on what I'm messing/missing? Also, is there a better approach to handle nested entities? Should I try to save each of them individually? In that case can the #RequestBody still be the parent entity?
I have built an example project, and try to replicate your situation, not successful. I am able to insert the "item" without any issue.
I placed the project under this repository https://github.com/hepoiko/user-5483731-1
Hope this help you to troubleshooting further or let me know If I miss anything in there.

Why am I getting DbUpdateException: OptimisticConcurrencyException?

I have a Category class:
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
}
I also have a Subcategory class:
public class Subcategory
{
public int SubcategoryId { get; set; }
public Category Category { get; set; }
public string SubcategoryName { get; set; }
}
And a Flavor class:
public class Flavor
{
public int FlavorId { get; set; }
public Subcategory Subcategory { get; set; }
public string FlavorName { get; set; }
}
Then I also have Filling and Frosting classes just like the Flavor class that also have Category and Subcategory navigation properties.
I have a Product class that has a Flavor navigation property.
An OrderItem class represents each row in an order:
public class OrderItem
{
public int OrderItemId { get; set; }
public string OrderNo { get; set; }
public Product Product { get; set; }
public Frosting Frosting { get; set; }
public Filling Filling { get; set; }
public int Quantity { get; set; }
}
I'm having issues when trying to save an OrderItem object. I keep getting DbUpdateException: An error occurred while saving entities that do not expose foreign key properties for their relationships. with the Inner Exception being OptimisticConcurrencyException: Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. I've stepped through my code several times and I can't find anything that modifies or deletes any entities loaded from the database. I've been able to save the OrderItem, but it creates duplicate entries of Product, Flavor, Subcategory and Category items in the DB. I changed the EntityState of the OrderItem to Modified, but that throws the above exception. I thought it might have been the fact that I have Product, Frosting and Filling objects all referencing the same Subcategory and Category objects, so I tried Detaching Frosting and Filling, saving, attaching, changing OrderItem entity state to Modified and saving again, but that also throws the above exception.
The following statement creates duplicates in the database:
db.OrderItems.Add(orderItem);
Adding any of the following statements after the above line all cause db.SaveChanges(); to throw the mentioned exception (both Modified and Detached states):
db.Entry(item).State = EntityState.Modified;
db.Entry(item.Product.Flavor.Subcategory.Category).State = EntityState.Modified;
db.Entry(item.Product.Flavor.Subcategory).State = EntityState.Modified;
db.Entry(item.Product.Flavor).State = EntityState.Modified;
db.Entry(item.Product).State = EntityState.Modified;
Can someone please give me some insight? Are my classes badly designed?
The first thing to check would be how the entity relationships are mapped. Generally the navigation properties should be marked as virtual to ensure EF can proxy them. One other optimization is that if the entities reference SubCategory then since SubCats reference a Category, those entities do not need both. You would only need both if sub categories are optional. Having both won't necessarily cause issues, but it can lead to scenarios where a Frosting's Category does not match the category of the Frosting's SubCategory. (Seen more than enough bugs like this depending on whether the code went frosting.CategoryId vs. frosting.SubCategory.CategoryId) Your Flavor definition seemed to only use SubCategory which is good, just something to be cautious of.
The error detail seems to point at EF knowing about the entities but not being told about their relationships. You'll want to ensure that you have mapping details to tell EF about how Frosting and SubCategory are related. EF can deduce some of these automatically but my preference is always to be explicit. (I hate surprises!)
public class FrostingConfiguration : EntityTypeConfiguration<Frosting>
{
public FlavorConfiguration()
{
ToTable("Flavors");
HasKey(x => x.FlavorId)
.Property(x => x.FlavorId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasRequired(x => x.SubCategory)
.WithMany()
.Map(x => x.MapKey("SubCategoryId");
}
}
Given your Flavor entity didn't appear to have a property for the SubCategoryId, it helps to tell EF about it. EF may be able to deduce this, but with IDs and the automatic naming conventions it looks for, I don't bother trying to remember what works automagically.
Now if this is EF Core, you can replace the .Map() statement with:
.ForeignKey("SubCategoryId");
which will set up a shadow property for the FK.
If SubCats are optional, then replace HasRequired with HasOptional. The WithMany() just denotes that while a Flavor references a sub category, SubCategory does not maintain a list of flavours.
The next point of caution is passing entities outside of the scope of the DBContext that they were loaded. While EF does support detaching entities from one context and reattaching them to another, I would argue that this practice is almost always far more trouble than it is worth. Mapping entities to POCO ViewModels/DTOs, then loading them on demand again when performing updates is simpler, and less error-prone then attempting to reattach them. Data state may have changed between the time they were initially loaded and when you go to re-attach them, so fail-safe code needs to handle that scenario anyways. It also saves the hassle of messing around with modified state in the entity sets. While it may seem efficient to not load the entities a second time, by adopting view models you can optimize reads far more efficiently by only pulling back and transporting the meaningful data rather than entire entity graphs. (Systems generally read far more than they update) Even for update-heavy operations you can utilize bounded contexts to represent large tables as smaller, simple entities to load and update a few key fields more efficiently.

Dapper can't ignore nested objects for parameter?

I am beginning to use Dapper and love it so far. However as i venture further into complexity, i have ran into a big issue with it. The fact that you can pass an entire custom object as a parameter is great. However, when i add another custom object a a property, it no longer works as it tries to map the object as a SQL parameter. Is there any way to have it ignore custom objects that are properties of the main object being passed thru? Example below
public class CarMaker
{
public string Name { get; set; }
public Car Mycar { get; set; }
}
propery Name maps fine but property MyCar fails because it is a custom object. I will have to restructure my entire project if Dapper can't handle this which...well blows haha
Dapper extensions has a way to create custom maps, which allows you to ignore properties:
public class MyModelMapper : ClassMapper<MyModel>
{
public MyModelMapper()
{
//use a custom schema
Schema("not_dbo_schema");
//have a custom primary key
Map(x => x.ThePrimaryKey).Key(KeyType.Assigned);
//Use a different name property from database column
Map(x=> x.Foo).Column("Bar");
//Ignore this property entirely
Map(x=> x.SecretDataMan).Ignore();
//optional, map all other columns
AutoMap();
}
}
Here is a link
There is a much simpler solution to this problem.
If the property MyCar is not in the database, and it is probably not, then simple remove the {get;set;} and the "property" becomes a field and is automatically ignored by DapperExtensions. If you are actually storing this information in a database and it is a multi-valued property that is not serialized into a JSON or similar format, I think you are probably asking for complexity that you don't want. There is no sql equivalent of the object "Car", and the properties in your model must map to something that sql recognizes.
UPDATE:
If "Car" is part of a table in your database, then you can read it into the CarMaker object using Dapper's QueryMultiple.
I use it in this fashion:
dynamic reader = dbConnection.QueryMultiple("Request_s", param: new { id = id }, commandType: CommandType.StoredProcedure);
if (reader != null)
{
result = reader.Read<Models.Request>()[0] as Models.Request;
result.reviews = reader.Read<Models.Review>() as IEnumerable<Models.Review>;
}
The Request Class has a field as such:
public IEnumerable<Models.Review> reviews;
The stored procedure looks like this:
ALTER PROCEDURE [dbo].[Request_s]
(
#id int = null
)
AS
BEGIN
SELECT *
FROM [biospecimen].requests as bn
where bn.id=coalesce(#id, bn.id)
order by bn.id desc;
if #id is not null
begin
SELECT
*
FROM [biospecimen].reviews as bn
where bn.request_id = #id;
end
END
In the first read, Dapper ignores the field reviews, and in the second read, Dapper loads the information into the field. If a null set is returned, Dapper will load the field with a null set just like it will load the parent class with null contents.
The second select statement then reads the collection needed to complete the object, and Dapper stores the output as shown.
I have been implementing this in my Repository classes in situations where a target parent class has several child classes that are being displayed at the same time.
This prevents multiple trips to the database.
You can also use this approach when the target class is a child class and you need information about the parent class it is related to.

Breeze Delete Parent with Guid PK and Children are Modified

I am trying to delete both the parent and the children entities in an Angular/Breeze application.
The backing store is a code-first Entity Framework.
The entities are as follows:
public class Ingredient
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public virtual ICollection<IngredientDescription> Descriptions { get; set; }
}
public class IngredientDescription
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public Guid IngredientId { get; set; }
[ForeignKey("IngredientId")]
public virtual Ingredient Ingredient { get; set; }
public string Description { get; set; }
[Required]
public int Culture { get; set; }
}
When I delete the Ingredient, I need to delete the IngredientDescription. I have tried doing it both ways, where I delete either first (children, then parent or parent, then children).
Whenever I delete the Ingredient (parent), Breeze is setting the children IngredientDescription.IngredientId to Guid.Empty or {0000-0000-...}. This is causing the children entities to a state of Modified (as opposed to the Deleted I had already set them).
I have tried everything I can think of to get this to work from the clientside code. I have gotten it to work by previewing the changes in the controller and re-marking it as Deleted. But, I'd like to just get it to work from the clientside.
I don't have to have cascading deletes, just if I delete it, to prevent Breeze from modifying it from Deleted to Modified.
My clientside function is as below:
function removeIngredient(ingredient) {
var descriptions = ingredient.descriptions;
for (var d = 0; d < ingredient.descriptions.length; d++) {
var thisDescription = ingredient.descriptions[d];
thisDescription.entityAspect.setDeleted();
}
ingredient.entityAspect.setDeleted();
}
Any thoughts on how I can keep Breeze from marking the child objects as Modified after I have already marked them as Deleted?
Below is an image of where the child's EntityState is being set to modified from the removeFromRelations call.
I believe the problem was in the loop that deletes the children
for (var d = 0; d < ingredient.descriptions.length; d++) {
var thisDescription = ingredient.descriptions[d];
thisDescription.entityAspect.setDeleted();
}
By setting the record to Deleted via setDeleted(), the record is being removed from the array. So, records were getting skipped for deletion and then when attempting to save the parent the FK violation was coming up.
I changed the function as below with a while length > 0
function removeIngredient(ingredient) {
while (ingredient.descriptions.length > 0) {
ingredient.descriptions[0].entityAspect.setDeleted();
}
ingredient.entityAspect.setDeleted();
}
Ok, I've just retested Breeze's deletion logic ( on breeze version 1.5.0) to try to confirm your issue, and have been unable to repro the behavior you see. Just to recap, the order in which you perform deletions does matter.
Delete parent then delete children
This will mark the parent as deleted and will force the update of the children's foreign keys to either null or the default value of the key depending on whether the foreign key property is nullable or not. The one exception to this rule is that breeze will never attempt to modify a primary key, so if the foreign key of the child is also part of the primary key then breeze will not attempt to modify it.
All child navigation properties will now return either empty arrays or null depending on whether the navigation property is scalar or not. At this point each of the children will marked as modified, as a result of the foreign key change.
Then the children will each be marked deleted.
Delete children then parent ( recommended)
The children will each be marked deleted. Foreign keys will NOT change but the corresponding navigation property will return null, instead of the returning the parent. At this point all of the parent's navigation properties that previously returned these children will now return either null or an empty array.
The parent is marked deleted. No change is made to any of its children ( because it has none at this point).
I have been unable to repro a case where an entity is set to an entity state of modified after it has been deleted. However, this may have been a bug in an earlier version of Breeze, so please try your test with the latest version.

Resources