Update list in Entity Framework model - c#

I have an Entity Framework model:
public class Application
{
[Key]
public int ApplicationID { get; set; }
public int PatentID { get; set; }
...
//------------------------------------
public string ApplicationNumber { get; set; }
public string Priority { get; set; }
public List<ApplicationPayment> Payments { get; set; }
= new List<ApplicationPayment>();
}
and payment's model:
public class ApplicationPayment
{
[Key]
public int PaymentID { get; set; }
public string PaymentName { get; set; }
public float Amount { get; set; }
public int PayNumber { get; set; }
public DateTime Date { get; set; } = new DateTime(2017, 12, 1);
public float TopicPart { get; set; }
}
Entity Framework creates additional foreign keys for me in ApplicationPayment model Application_ApplicationID.
I add a new instance in ApplicationPayment table that has number of the existing Application:
But when I try to display this ApplicationPayment's table this returns the empty table.
I tried to add ApplicationPayment manually through SQL Server Management Studio and via fake-request. New line added but the list of ApplicationPayment is still empty.
Fake-request:
[HttpPut]
public void CreateApplicationPayment(int? id)
{
ApplicationPayment appPayment = new ApplicationPayment()
{
Amount = 80.0f,
Date = new DateTime(2017, 10, 25),
PaymentName = "payment",
PayNumber = 30,
TopicPart = 20
};
Application application = db.Applications.Find(id);
application.Payments.Add(appPayment);
db.SaveChanges();
}

Your collection property needs to be virtual if you want EF to automatically populate it:
public virtual List<ApplicationPayment> Payments { get; set; }
Also, if you're using EF 6 or previous, you'll need to make the type of that property ICollection<ApplicationPayment>, rather than List<ApplicationPayment>. I think EF Core relaxed this restriction, but I'm not sure. So, if you still have issues, change it there as well.
However, this is what's called lazy-loading, and it's not ideal in most scenarios. Additionally, if you're using EF Core, it still won't work, because currently EF Core does not support lazy loading. The better method is to eagerly load the relationship. This is done by using Include in your LINQ query:
Application application = db.Applications.Include(m => m.ApplicationPayments).SingleOrDefault(m => m.Id == id);
This will cause EF to do a join to bring in the related ApplicationPayments. You need to then use SingleOrDefault rather than Find, as Find doesn't work with Include. (Find looks up the object in the context first, before hitting the database, and as a result, cannot account for related items being available.)

Related

Entity Framework sets the wrong EntityState on Add [duplicate]

I try to add entity through the navigation property of collection, but the following message comes up:
"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."
The models are:
SuggestionGroupDb:
public class SuggestionGroupDb
{
public Guid Id { get; set; }
[Required]
public string UserId { get; set; }
[ForeignKey("UserId")]
public virtual TeguUserDb User { get; set; }
[Required(AllowEmptyStrings=false, ErrorMessage = "Required")]
[StringLength(30, MinimumLength = 1, ErrorMessage = "Invalid")]
public string Name { get; set; }
public int OrderNo { get; set; }
public virtual ICollection<SuggestionItemDb> Items { get; set; }
}
SuggestionItemDb:
public class SuggestionItemDb
{
public Guid Id { get; set; }
[Required]
public Guid SuggestionGroupId { get; set; }
[ForeignKey("SuggestionGroupId")]
public virtual SuggestionGroupDb SuggestionGroup { get; set; }
[Required(AllowEmptyStrings=false, ErrorMessage = "Required")]
[StringLength(30, MinimumLength = 1, ErrorMessage = "Invalid")]
public string Name { get; set; }
public int OrderNo { get; set; }
}
SuggestionGroup Repository Update function (simplified):
public async Task<SuggestionGroupRepositoryResult> UpdateAsync(string userid, SuggestionGroupDb suggestiongroup)
{
// Step 01 - Get the Entity
var dbSuggestionGroup = await GetAsync(userid, suggestiongroup.Id, suggestiongroup.Name);
// Step 02 - Update the items (just add one now)
foreach (var item in suggestiongroup.Items)
{
var sidb = new SuggestionItemDb() {Id = item.Id, Name = item.Name, OrderNo = item.OrderNo, SuggestionGroupId = item.SuggestionGroupId};
dbSuggestionGroup .Items.Add(sidb);
}
// Step 03 - Update the changes
try
{
var updated = context.AccSuggestionGroups.Update(dbSuggestionGroup);
await context.SaveChangesAsync();
return new SuggestionGroupRepositoryResult("Valid") /*{SuggestionGroup = updated.Entity}*/;
}
catch (Exception e)
{
context.Reset();
return new SuggestionGroupRepositoryResult("Failed", e.Message);
}
}
The problem is that SaveChanges throws and exception with the given message.
Is it possible to update the SuggestionItems through the SuggestionGroup?
I am using EF Core 3.0 preview 6.
Prior to EF Core 3.0, untracked entities discovered by the DetectChanges strategy (in your case, by adding an untracked entity to a collection) would automatically be in the Added state.
This is no longer the case. From Entity Framework Core 3.0 the entity will be automatically added in the Modified state.
Why
This change was made to make it easier and more consistent to work with disconnected entity graphs while using store-generated keys.
Source: EF Core 3.0 - Breaking Changes
You can force new untracked entities to be added in the Added state by configuring the key property to explicitly not use generated values.
For example:
public class SuggestionItemDb
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id { get; set; }
}
Or using the fluent API
modelBuilder
.Entity<SuggestionItemDb>()
.Property(e => e.Id)
.ValueGeneratedNever();

How to hide items, in an API reply, from a db query?

I'm currently using MVC with EF to have a small server with API querying a SQL database. But in the API reply I'm not able to hide some parameters.
The main object
public class AssetItem
{
[Key]
public Int32 AssetId { get; set; }
public String AssetName { get; set; }
public int OdForeignKey { get; set; }
[ForeignKey("OdForeignKey")]
public OperationalDataItem OperationalDataItem { get; set; }
}
The other one:
public class OperationalDataItem
{
[Key]
public Int32 OperationalDataId { get; set; }
public String Comunity { get; set; }
public List<AssetItem> AssetItems { get; set; }
}
From what I have read, this should be ok, I have also set the context:
public AssetContext(DbContextOptions<AssetContext> options) : base(options)
{}
public DbSet<AssetItem> AssetItems { get; set; }
public DbSet<OperationalDataItem> OperationalDataItems { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AssetItem>().HasOne(p =>
p.OperationalDataItem).WithMany(b => b.AssetItems).HasForeignKey(p =>
p.OdForeignKey);
}
And the seeding in program.cs
context.AssetItems.Add(
new AssetItem { AssetName = "Test test", OdForeignKey = 1,
OperationalDataItem =
new OperationalDataItem {Comunity = "Comunity1" }});
So calling the API this results in:
{ "assetId":3,
"assetName":"Test test",
"odForeignKey":1,
"operationalDataItem":null }
From what I read this is because of the lazy loading, how can I hide the result operationalDataItem?
In case is not possible i have of course try to query for it and give it back and it give something like:
{ "assetId":3,
"assetName":"Test test",
"odForeignKey":1,
"operationalDataItem":
{ "operationalDataId":1,
"comunity":"Comunity1",
"assetItems":[
But in this case I would like to hide "assetsItems" in the reply to the FE.
How can I hide those parameters?
The API is quite simple, just an example code:
var todoItem = await _context.AssetItems.FindAsync((Int32)id);
var item = _context.OperationalDataItems.Find((Int32)todoItem.OdForeignKey);
todoItem.OperationalDataItem = item;
return todoItem
If you want to fetch data from the database, but you only want to fetch some properties, use Select. Usually this is more efficient than using Find, because you'll only transfer the data that you actually plan to use.
To fetch some properties of the assetItem that has primary key assetItemId:
var result = dbContext.AssetItems
.Where(assetItem => assetItem.AssetItmId = assetItemId)
.Select(assetItem => new
{
// Select only the properties that you plan to use
Id = assetItem.AssertItemId,
Name = assetItem.Name,
OperationalData = new
{
// again, select only the properties that you plan to use
Id = assetItem.OperationalData.OperationalDataId,
Community = assetItem.OperationalData.Community,
},
})
.FirstOrDefault();
Or the other way round:
Fetch several properties of all (or some) OperationalDataItems, each with some properties of all (or some) of its AssetItems:
var result = dbContext.OperqationalDataItems
.Where(operationalDataItem => ...) // only if you don't want all
.Select(operationalDataItem => new
{
Id = operationalDataItem.Id,
Community = operationalDataItem.Community
AssetItems = operationalDataItem.AssetItems
.Where(assetItem => ...) // only if you don't want all its assetItems
.Select(assetItem => new
{
// Select only the properties you plan to use:
Id = assetItem.Id,
...
// not useful: you know the value of the foreign key:
// OperationalDataId = assetItem.OperationalDataId,
})
.ToList();
})
.ToList(); // or: FirstOrDefault if you expect only one element
Entity framework knows your one-to-many relation and is smart enough to know which (group-)join is needed for your query.
Some side remarks
You've declare your many-relation a List<AssetItem>. Are you sure that operationalDataItem.AssetItems[4] has a defined meaning? Wouldn't it be better to stick to the entity framework code first conventions? This would also eliminate the need for most attributes and / or fluent API
public class OperationalDataItem
{
public int Id { get; set; }
public String Comunity { get; set; }
...
// Every OperationalDataItem has zero or more AssetItems (one-to-many)
public virtual ICollection<AssetItem> AssetItems { get; set; }
}
public class AssetItem
{
public int Id { get; set; }
public String Name { get; set; }
...
// every AssetItem belongs to exactly one OperationalDataItem, using foreign key
public int OperationDataItemId { get; set; }
public virtual OperationalDataItem OperationalDataItem { get; set; }
}
In entity framework the columns of a table are represented by the non-virtual properties. The virtual properties represent the relations between the tables (one-to-many, many-to-many)
Because I stuck to the conventions, no attributes nor fluent API is needed. Entity framework is able to detect the one-to-many relation and the primary and foreign keys. Only if I am not satisfied with the names or the types of the columns I would need fluent API.

C# EntityFramework .NETCore 2.0 Many-to-Many

.. Hello I know there's been already plenty of questions on this, but still can't get it right no matter what.
I need a many-to-many with custom join table, but every time I try to fetch collection it's still empty.
First class:
public class Test1 {
[Key]
public int id { get; set; }
public virtual ICollection<TestToTest> others { get; set; }
public Test1() {
others = new HashSet<TestToTest>();
}
}
Second one:
public class Test2 {
[Key]
public int id { get; set; }
public virtual ICollection<TestToTest> others { get; set; }
public Test2() {
others = new HashSet<TestToTest>();
}
}
And the join table:
public class TestToTest {
[Key]
public int id { get; set; }
[ForeignKey("Test1")]
public int test1Id { get; set; }
[ForeignKey("Test2")]
public int test2Id { get; set; }
public virtual Test1 test1 { get; set; }
public virtual Test2 test2 { get; set; }
}
But still when I try to get one of them with query like:
var cont = new MyContext(); //DbContext
Test1 t1 = cont.test1.Find(1); // Fetches first class ok
var tt = t1.others; // Empty array
I really have no idea what more I'm missing there in other to make it work.
If I add a new one to context then it's ok... as long as it's cached -> and it does write row into db. But after restart (without any cache in context), field 'others' is always empty.
Thanks for any help in advance.
It's not loading as child relationship are setup for Eager Loading. Eager loading is the process whereby a query for one type of entity also loads related entities as part of the query, so that we don't need to execute a separate query for related entities. Eager loading is achieved using the Include() method. So if the relationship entity is not loaded using include then it would NOT be loaded.
Change the code as
Test1 t1 = cont.test1.Include(t => t.others).SingleOrDefault(t => t.id == 1);
You can read about Eager Loading at this Microsoft document.

Pattern required for adding/updating entities in EF5 including relationships

I am using VS 2010 with Entity Framework 5 code first and C# and have a web application (hence disconnected entities). I am used to working with SQL queries directly but am very new to EF and code first.
I have two classes:
public class User
{
public int UserID {get; set;}
public string UserName { get; set; }
public bool IsSuspended { get; set; }
public int UnitID { get; set; }
public virtual MyTrust MyTrusts { get; set; }
}
public class MyTrust
{
public int MyTrustID { get; set; }
public string MyTrustName { get; set; }
public string Region { get; set; }
public bool DoNotUse { get; set; }
}
and my DbContext class contains:
public DbSet<MyTrust> MyTrust { get; set; }
public DbSet<User> Users { get; set; }
modelBuilder.Entity<User>()
.HasRequired(m => m.MyTrust);
The MyTrust entity will not be changed
There are three scenarios I am interested in:
Adding a user with an existing MyTrust
Updating a user with no change to the trust
Updating a user with a change to the trust
When the website returns the data the MyTrust object has only the MyTrustID set. When I update/add the user the MyTrust record is also updated.
CLARIFICATION The relationship in the User object is NOT updated; the actual MyTrust object is updated with the data returned from the website; as most fields are empty this is corrupting the object AND not achieving the required update of the User record.
In fact, the problem seems to boil down to the fact that the wrong end of the relationship is being updated.
I have looked at some many examples I cannot see a simple solution.
Can anyone please suggest a straightforward pattern for this (it was so easy in the SQL days).
UPDATE
I resolved this by adding specific keys to the User and MyTrust classes.
public int NHSTrustID { get; set; }
and a matching key in the MyTrust class.
In retrospect the question was wrong. I wasn't after patterns but the solution to a specific problem.
I've given some examples below - I've done them from memory but hopefully will give you a good starting point:
Adding a user with an existing MyTrust
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Added
context.Entry(myUser.MyTrusts).State = EntityState.Modified;
context.Entry(myUser.MyTrusts).Property(x => x.MyTrustName).IsModified = false;
context.Entry(myUser.MyTrusts).Property(x => x.Region).IsModified = false;
context.Entry(myUser.MyTrusts).Property(x => x.DoNotUse).IsModified = false;
context.SaveChanges();
}
Updating a user with no change to trusts:
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Modified
context.Entry(myUser.MyTrusts).State = EntityState.Unchanged;
context.SaveChanges();
}
Updating a user with a change to trusts:
using(var context = new MyDbContext()){
context.Entry(myUser).State = EntityState.Modified
context.Entry(myUser.MyTrusts).State = EntityState.Modified;
context.SaveChanges();
}

Recursive Entity Update

I have an entity which holds a list of entities (same as root entity) to represent a Folder structure:
public class SopFolder
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime? LastUpdated { get; set; }
public int Status { get; set; }
public virtual ICollection<SopField> SopFields { get; set; }
public virtual ICollection<SopFolder> SopFolderChildrens { get; set; }
public virtual ICollection<SopBlock> Blocks { get; set; }
public virtual ICollection<SopReview> Reviews { get; set; }
}
This entity is stored in my DB using Code-First Approach which is working fine. I then print the entity to a KendoUI Treeview, let the user modify it and on "save" post it back to the Server to an Action as IEnumerable<TreeViewItemModel> items.
I then look for the ROOT entity with all it's children (there is only one root) and convert it back into an SopFolder object.
To get the full object updated in the database I do the following:
List<SopFolder> sopfolderlist = ConvertTree(items.First());
SopFolder sopfolder = sopfolderlist[0];
if (ModelState.IsValid)
{
SopFolder startFolder = new SopFolder { Id = sopfolder.Id };
//db.SopFolders.Attach(startFolder);
// db.SopFolders.Attach(sopfolder);
startFolder.Name = sopfolder.Name;
startFolder.LastUpdated = sopfolder.LastUpdated;
startFolder.SopFields = sopfolder.SopFields;
startFolder.SopFolderChildrens = sopfolder.SopFolderChildrens;
startFolder.Status = sopfolder.Status;
db.Entry(startFolder).State = EntityState.Modified;
db.SaveChanges();
return Content("true");
}
However this is not working. The model is not updated at all. If I shift the "entityState.Modified" before the modifications, it just creates a complete fresh duplicate of my data in the database (modified of course).
Is my approach correct or do I have to go a different path? What am I missing here? I guess there is another "hidden" id which lets the EF map the entities to the db entries but I am not sure about this. Thanks for help!
UPDATE:
Instead of creatinga new instance of SopFolder I also tried db.SopFolders.Find(sopfolder.Id) and this works for entries with no children. If I have entities with children, it creates a duplicate.
Regards,
Marcus
This is typical Disconnected Graph scenario. Please see this question for possible solutions:
Disconnected Behavior of Entity Framework when Updating Object Graph
You have already figure out the first solution - that is: update entities separately. Actually, what you should do is to fetch the original data from database and then do comparison of what have changed. There are some generic ways of doing that, some of them are described in "Programming EF DbContext" book by J.Lerman, which I strongly recommend to you before doing more coding using EF.
P.S. IMHO this is the worse downside of EF.
Replace SopFolder startFolder = new SopFolder { Id = sopfolder.Id }; with
SopFolder startFolder = db.SopFolders.FirstOrDefault(s=>s.Id.Equals(sopfolder.Id));
// then validate if startFolder != null
I recommend you to create your entity model with ParentId, not children object list. When you need treeview model collect it with recursive function from database.
public class SopFolder
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime? LastUpdated { get; set; }
public int Status { get; set; }
public virtual ICollection<SopField> SopFields { get; set; }
//public virtual ICollection<SopFolder> SopFolderChildrens { get; set; }
public int? ParentFolderId { get; set; }
public virtual ICollection<SopBlock> Blocks { get; set; }
public virtual ICollection<SopReview> Reviews { get; set; }
}
When you create children folders, select it's parent, so collect your data. In childrens case try this :
List<SopFolder> sopfolderlist = ConvertTree(items.First());
SopFolder sopfolder = sopfolderlist[0];
if (ModelState.IsValid)
{
SopFolder startFolder = new SopFolder { Id = sopfolder.Id };
//db.SopFolders.Attach(startFolder);
// db.SopFolders.Attach(sopfolder);
startFolder.Name = sopfolder.Name;
startFolder.LastUpdated = sopfolder.LastUpdated;
startFolder.SopFields = sopfolder.SopFields;
startFolder.SopFolderChildrens = sopfolder.SopFolderChildrens;
foreach (var child in sopfolder.SopFolderChildrens)
{
db.SopFolders.CurrentValues.SetValues(child);
db.SaveChanges();
}
startFolder.Status = sopfolder.Status;
db.Entry(startFolder).State = EntityState.Modified;
db.SaveChanges();
return Content("true");
}

Categories

Resources