New Foreign Entities Aren't created with Update - c#

When I add a new item to a virtual collection property of my Entity it'll be added to the database.
For example
Loan.Notes.Add(new Note {Subject = "Test"})
This will create a new row in Notes table and showing the loanId for the link.
I tried to replicate this exact working functionality for another table. I set a breakpoint right before it hits save and I can see the collection has the NEW entity but it won't create. I'm clueless towards why it's working in one scenario and not the other.
Notes saves completely fine, status history doesn't.
public void Save()
{
foreach (var noteDTO in Notes.Where(x => x.NewRecord))
{
Loan.Notes.Add(new Note
{
Subject = noteDTO.Subject,
Comments = noteDTO.Comments,
Active = noteDTO.Active,
Loan = Loan,
Loan_Id = Loan.Id
});
}
foreach (var status in StatusHistory.Where(x => x.Id == 0))
{
Loan.StatusHistory.Add(
new LoanStatusHistory
{
Loan_Id = Loan.Id,
Active = status.Active,
IsCurrent = status.Current,
Date = status.StatusDate.Value.Date,
StatusReason = status.Reason,
LoanStatus_Id = status.Status_Id.Value,
Order = status.Order.Value,
Created = DateTime.Now,
CreatedBy = CurrentUser.GetInstance().MyUser.Id,
Modified = DateTime.Now,
LastModifiedBy = CurrentUser.GetInstance().MyUser.Id
});
}
}
UnitOfWork.LoanRepository.Update(Loan);
UnitOfWork.Save();
}
In Notes and Status models I have set there to be a virtual relationship to loan.
public virtual Loan Loan { get; set; }
In Loan Model I have set there to be a virtual relationship to Notes and Status
public virtual ICollection<LoanStatusHistory> StatusHistory { get; set; }
public virtual ICollection<Note> Notes { get; set; }
I was wondering if anyone could give me suggestions or tips to fix this please.

Related

Issue with data insert not finding an Id

In my project, I have in place a quoting system. This worked before, I have made changes to some of the things that are involved. The changes were made a while ago and I just had to do a quote and it is failing on the insert of Parts in the QuoteDetail table.
The error is that it cannot find Eparts_ProductMasterId. Eparts is a SQL view and I am not inserting it into this, it is read-only. I am pulling the info from the cart, but need it to find other info from this table. Which according to my locals, I have everything I need, all it has to do is insert it into the QuoteDetails table.
Also I am not inserting the ProductMasterId into the QuoteDetails table. It fails on the db.SaveChanges();
Before I go posting a bunch of code I will post the part that is failing and if you need to see more I will post what you need. I am not even calling a ProductMasterId and ProductMasterId is part of the SQL View.
Here is the part that fails to save. It passes the if and goes to the else and is not a complete part.
foreach (var item in CartItems)
{
var part = db.ExtParts.Where(a => a.Material == item.Material).FirstOrDefault();
var type = db.Parts.Where(a => a.Material == item.Material).FirstOrDefault();
if ((type.Class == "MACH") || (type.Class == "PRL"))
{
QuoteDetail qd = new QuoteDetail
{
Material = item.Material,
QuoteId = quote.QuoteId,
Quantity = item.Count,
UnitPrice = part.MachinedPrice,
IsDelete = false,
CreatedBy = UserData.FirstName,
CreatedDate = System.DateTime.Now
};
db.QuoteDetail.Add(qd);
}
else
{
if (part.Complete != true)
{
QuoteDetail qd = new QuoteDetail
{
Material = item.Material,
QuoteId = quote.QuoteId,
Quantity = item.Count,
UnitPrice = part.SellingPrice,
IsDelete = false,
CreatedBy = UserData.FirstName,
CreatedDate = System.DateTime.Now
};
db.QuoteDetail.Add(qd);
}
else
{
QuoteDetail qd = new QuoteDetail
{
Material = item.Material,
QuoteId = quote.QuoteId,
Quantity = item.Count,
UnitPrice = part.LastCost,
IsDelete = false,
CreatedBy = UserData.FirstName,
CreatedDate = System.DateTime.Now
};
db.QuoteDetail.Add(qd);
}
}
db.SaveChanges();
}
I have had this issue before putting a
public ExtParts EParts { get; set; }
in another model but this fixes that issue
public virtual ExtParts EParts { get; set; }
This is in the OrderDetails model or am I missing something?
Any help would be appreciated.
UPDATE:
I looked through my code again and found that I added the public virtual ExtParts EParts { get; set; } to the model and it was not needed. I already had it in the ViewModel so it was not needed in the actual model. The Model is used to save the records and when it was looking at the SQL View it was faulting out. Does not fault out on the others that are in the Model, but they are not SQL Views either, they are other related Models.
Removed
public virtual ExtParts EParts { get; set; }
from the Model. It was already in the ViewModel so it was not needed in the Model itself. When saving and using the Model I guess using an SQL View is not allowed. You cannot save to a SQL View it is for viewing purposes only. My mistake and I am not really sure when and why I added it to the Model. Removing it did not break any code.

Many-to-Many EF Core already being tracked - C# Discord Bot

So I'm using Entity Framework Core to build a database of Guilds (Another name for Discord Servers) and Users, with the Discord.NET Library. Each Guild has many users, and each user can be in many guilds. First time using EF and I'm having some teething issues. The two classes are:
public class Guild
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public ulong Snowflake { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public string Name { get; set; }
public ICollection<User> Users { get; set; }
}
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public ulong Snowflake { get; set; }
public string Username { get; set; }
public ushort DiscriminatorValue { get; set; }
public string AvatarId { get; set; }
public ICollection<Guild> Guilds { get; set; }
public DateTimeOffset CreatedAt { get; set; }
}
With the goal of having 3 tables: Guild, Users, and GuildUsers. This is my current function for getting the guilds:
using var context = new AutomataContext();
var discordGuilds = this.client.Guilds.ToList();
var dbGuilds = context.Guilds;
List<Guild> internalGuilds = discordGuilds.Select(g => new Guild
{
Snowflake = g.Id,
Name = g.Name,
CreatedAt = g.CreatedAt,
Users = g.Users.Select(gu => new User
{
Id = context.Users.AsNoTracking().FirstOrDefault(u => u.Snowflake == gu.Id)?.Id ?? default(int),
}).ToList(),
}).ToList();
// Upsert Guilds to db set.
foreach (var guild in internalGuilds)
{
var existingDbGuild = dbGuilds.AsNoTracking().FirstOrDefault(g => g.Snowflake == guild.Snowflake);
if (existingDbGuild != null)
{
guild.Id = existingDbGuild.Id;
dbGuilds.Update(guild); // Hits the second Update here and crashes
}
else
{
dbGuilds.Add(guild);
}
}
await context.SaveChangesAsync();
I should note, a 'snowflake' is a unique ID that discord uses, but I wanted to keep my own unique ID for each table.
High level overview, guilds are collected into Discord.NET models. These are then transformed into internalGuilds (my guild class, which includes the list of users). Each of these is looped through and upserted to the database.
The issue arises in the second guild loop, where an error is thrown in the "Update" that a User ID is already being tracked (Inside the guild). So the nested ID is already being tracked? Not sure what's going on here, any help would be appreciated. Thanks.
This exception is most likely occurring because you are loading Users without tracking then looping through and potentially trying to update or insert guilds /w the same user reference, especially using the Update method.
I would suggest removing the use of AsNoTracking. Working with detached entity references via AsNoTracking is more of a performance tweak for when reading large amounts of data. You can pre-fetch all of the User references by their snowflake:
using (var context = new AutomataContext())
{
var discordGuilds = this.client.Guilds.ToList();
// Get the user snowflakes from the guilds, and pre-fetch them.
var userSnowflakes = discordGuilds.SelectMany(g => g.Users.Select(u => u.Id)).ToList();
var users = await context.Users
.Where(x => userSnowflakes.Contains(x.Snowflake))
.ToListAsync();
// We need to add references for any New user snowflakes.
var existingSnowflakes = users.Select(x => x.Snowflake).ToList();
// If more detail is needed for new user records, it will need to be fetched from the passed in Guild.User.
var newUsers = userSnowflakes.Except(existingSnowFlakes)
.Select(x => new User { SnowflakeId = x }).ToList();
if(newUsers.Any())
users.AddRange(newUsers);
List<Guild> internalGuilds = discordGuilds.Select(g => new Guild
{
Snowflake = g.Id,
Name = g.Name,
CreatedAt = g.CreatedAt,
Users = g.Users
.Select(gu => users.Single(u => u.Snowflake == gu.Id))
.ToList(),
}).ToList(),
// Upsert Guilds to db set.
foreach (var guild in internalGuilds)
{
var existingGuildId = context.Guilds
.Where(x => x.Snowflake == guild.Snowflake)
.Select(x => x.Id)
.SingleOrDefault();
if (existingGuildId != 0)
{
guild.Id = existingGuildId;
dbGuilds.Update(guild);
}
else
{
dbGuilds.Add(guild);
}
}
await context.SaveChangesAsync();
This should help ensure that the User references for existing users are pointing at the same instances, whether existing users or new user references that will be associated to the DbContext when first referenced.
Ultimately I don't recommend using Update for "Upsert" scenarios, instead since the Db Record needs to be fetched anyways, updating values on the fetched instance or inserting a new one. Update will want to send all fields from an entity to the database each time, rather than just sending what has changed. It means enforcing a bit more control over what can possibly be changed vs. what should not be.

Find method not assigning values to object

I have a web project that everything is working and this below line works for other models except for this one. I'm just needing some info on where to start looking for the solution at.
When I debug it I see that it is getting all the new data that has been edited but it does not assign the new data to EditedtimeEntry. The EditedtimeEntry var has the old data not the new data that was edited. I looked at the timeEntry.Id and it has the new edit its just not being assigned to the EditedtimeEntry. There is no exception or build errors it just does not save the changes, and it looks like the reason it is not save the changes is because the EditedtimeEntry var is not getting the new data assigned to it for some reason. Can anyone point me in the right direction?
TimeEntry EditedtimeEntry = db.TimeEntries.Find(timeEntry.Id);
Here is the Full method with the problem:
public ActionResult Edit( [Bind(Include = "Id,Description,Rate,Paid,Tech,Company")] TimeEntry timeEntry)
{
if (ModelState.IsValid)
{
TimeEntry EditedtimeEntry = db.TimeEntries.Find(timeEntry.Id);
Technician tech = db.Technician.Single(m => m.PhoneNumber == timeEntry.Tech.PhoneNumber);
EditedtimeEntry.Tech = tech;
Company comp = db.Companies.Single(m => m.Name == timeEntry.Company.Name);
EditedtimeEntry.Company = comp;
db.Entry(EditedtimeEntry).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(timeEntry);
}
I have other methods for other models that are identical to this one and it works. Here is alsos the model
public class TimeEntry
{
[Key]
public Guid Id { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public string Description { get; set; }
public Rate Rate { get; set; }
public Technician Tech { get; set; }
public bool Paid { get; set; }
public Company Company { get; set; }
}
public enum Rate { Desktop, Network, Remote, Phone }
Thanks =)
Basically, what you are doing is exactly the contrary of what you want to do :) when you recover the item from the database, you are simply getting the item unchanged that is still in database. What you want is update the database item and then save changes (with EntityState.Modified then SaveChanges())
You simply want to edit timeEntry so that all changes done in the UI are translated into DB :
public ActionResult Edit( [Bind(Include = "Id,Description,Rate,Paid,Tech,Company")] TimeEntry timeEntry)
{
if (ModelState.IsValid)
{
Technician tech = db.Technician.Single(m => m.PhoneNumber == timeEntry.Tech.PhoneNumber);
timeEntry.Tech = tech;
Company comp = db.Companies.Single(m => m.Name == timeEntry.Company.Name);
timeEntry.Company = comp;
db.Entry(timeEntry).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(timeEntry);
}

Creating two linked objects before linked field has been generated by database - Entity Framework

I would like to know if there is a way to add two linked objects to a database through entity framework before the linked field has been generated by the database.
I am still learning EF and I'm not exactly sure how to ask this question clearly so here is an example of what I am trying to achieve:
I have two classes:
class Sale
{
public int Id { get; set; } // generated by SQL Server
public string Foo { get; set; }
public string Bar { get; set; }
public virtual ICollection<SalesComment> Comments { get; set; }
}
class SalesComment
{
public int Id { get; set; }
public int SaleId { get; set; }
public string Comment {get; set; }
}
Using fluent api in my 'dbcontext' class I link the two objects like so:
modelBuilder.Entity<Sale>().HasMany(s => s.Comments).WithRequired().HasForeignKey(c => c.SaleId);
This works great, I can retrieve a Sale object from the database and I can loop through the comments linked to it by SaleId.
What I want to do is when creating a new Sale, add a Comment as I am creating it, with EF realising this is happening and adding the SaleId to the Comments table once it is generated by the database, something like:
using (MyDatabase db = new MyDatabase())
{
var sale = db.Set<Sale>();
sale.Add(new Sale
{
Foo = "Test",
Bar = "Test",
Comment.Add(new SalesComment .... //this is the line i'm not sure about
});
db.SaveChanges();
}
Is something like this possible? Or would I have to first save the Sale object to the database so that a SaleId is generated, then retrieve that object again so I can read the SaleId to use for a new SalesComment.
Try this.
using (MyDatabase db = new MyDatabase())
{
var sale = db.Set<Sale>();
var saleComment = new SalesComment{Comment="Hello"};
var saleToAdd = new Sale
{
Foo = "Test",
Bar = "Test",
Comments = new List<SalesComment> {saleComment}
});
sale.Add(saleToAdd);
db.SaveChanges();
// After saving changes, these 2 values will be populated and are equal
var saleID = saleToAdd.Id;
var saleCommentSaleId = saleComment.saleId;
}
You don't have to retrieve the object again if you cache it properly before adding it to the DbSet.

Entity Framework 6 update a table and insert into foreign key related tables

Not quiet sure how to word the title for this one so feel free to edit if it isn't accurate.
Using an example, what I am trying to accomplish is update a record in table foo, and then create new records in a subsequent table that has the foo tables PK as foreign key, think One-to-Many relationship.
How do I update a table that has foreign key constraints and create a new related record(s) in these subsequent table(s)?
Currently I am using Entity Framework 6 to .Add and .Attach entities to the context and save them to the database.
Edit
To clarify further what I am trying to achieve, the below object is a cut down made up example what I am trying to save to the context. If I try to .Add intObj after "Billy Bob" has already been created because he has bought a new car, another service, or his tyres have changed it will create a new Billy Bob record (duplicate) and the corresponding related tables.
intObj.FirstName = "Billy";
intObj.Lastname = "Bob";
intObj.Important = 100;
intObj.LastSeen = DateTime.Now.Date;
intObj.Cars = new List<Car>{
new Car{
Model = "Commodore",
Make = "Holden",
YearMade = DateTime.Today.Date,
Odometer = 15000,
EmailWordCount = 500,
TyreStatuss = new List<TyreStatus>{
new TyreStatus{
Tyre1 = "Good",
Tyre2 = "Good",
Tyre3 = "Okay",
Tyre4 = "Okay"
}
},
Services = new List<Service>{
new Service{
Cost = "$500",
Time = "2 Days",
Date = DateTime.Today
}
},
}
};
Thanks
In the following snippets you have Employee class, which references two more entities: a collection of Assignment and a single Country.
ORMs like EF , NHibernate, etc... have a feature known as Transitive Persistence, that is, if an object (Assignment and Country) is referenced by a persistent one (Employee), then Assignments and Country will eventually become persistent too when, in your EF case, SaveChanges method gets invoked in the Context, without you explicitly save them.
public class Employee
{
public virtual Guid Id { get; protected set; }
public virtual string EmployeeNumber { get; set; }
public virtual Country BirthCountry { get; set; }
private ICollection<Assignment> _assignment = new List<Assignment>();
public virtual ICollection<Assignment> Assignments
{
get
{
return _assignment;
}
set
{
_assignment= value;
}
}
}
public class Assignment
{
public virtual Guid Id { get; protected set; }
public virtual DateTime BeginTime { get; set; }
public virtual DateTime EndTime { get; set; }
public virtual string Description{ get; set; }
}
public class Country
{
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
}
//Somewhere in your program
private void SaveAllChanges()
{
_db = new EFContext();
//Creating a new employee here, but it can be one loaded from db
var emp = new Employee { FirstName = "Emp Name",
LastName = "Emp Last", EmployeeNumber = "XO1500"
};
emp.BirthCountry = new Country { Name = "Country1" };
emp.Assignment.Add(new Assignment{ BeginTime = DateTime.Now,EndTime=DateTime.Now.AddHours(1) });
//Only employee is explicitly added to the context
_db.Employees.Add(emp);
//All the objects in the employee graph will be saved (inserted in this case) in the db.
_db.SaveChanges();
}
}
EDIT:
That is very similar to my code above, once "Billy Bob" is created you only need to update it, and that include any new service he buy;
Pseudo code:
var bob = _db.Clients.SingleOrDefault(c=> c.Id = "Bob Row Id")
//Bob buy a car:
bob.Cars.Add(new Car()...)
//...and change tire 1 from an old car
var car = bob.Cars.SingleOrDefault(c=> c.Id = "Car Row Id")
car.TireStatus.Tire1 = "New"
....
//Persist all changes
//Existing objects will be updated..., and the new ones created in this process will be inserted
_db.SaveChanges()
Let me know if this clarify your ideas

Categories