EF Core6 RawQuery Update Table Column no matter conccurency - c#

I am having C# project using Entity Framework Core 6.0.6 I have the following scenario:
I have tournaments which have start date and end date. I have players wallets which have column TournamentId pointing to the tournament table. I want to set TournamentId = Null to all players after the tournament finishes ignoring any possible concurrency conflicts. What is the best practice to achieve this.
This is my Tournament definition:
public class Tournament
{
public int Id { get; set; }
public TournamentType Type { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public bool IsActive { get; set; }
public string Name { get; set; }
}
This is my Wallet definition:
public class Wallet
{
public int Id { get; set; }
public decimal Total { get; set; }
public decimal Available { get; set; }
public string UserId { get; set; }
public int? TournamentId { get; set; }
}
in the mean time Total and available could change because may continue playing.
my code to make the change is the following:
private async Task ClosePlayerTournaments()
{
var query = "Update Wallets Set TournamentId = null";
_context.Wallets.FromSqlRaw(query);
var saved = false;
while (!saved)
{
try
{
// Attempt to save changes to the database
await _context.SaveChangesAsync();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
// Log error
}
}
}

Use ExecuteSqlAsync
or ExecuteSqlRawAsync and don't call SaveChanges :
private async Task ClosePlayerTournaments()
{
var query = "Update Wallets Set TournamentId = null";
await _context.Database.ExecuteSqlRawAsync(query);
}
or
private async Task ClosePlayerTournaments()
{
await _context.Database.ExecuteSqlAsync($"Update Wallets Set TournamentId = null");
}
SaveChanges persists all changes made to tracked objects, ie objects loaded through a DbContext or attached to it. If it detects that the underlying data was changed by someone else, even another connection in the same application, it will raise a DbUpdateConcurrencyException
EF Core 7
In EF Core 7 the raw SQl query can be replaced by ExecuteUpdate :
await _context.Wallets.ExecuteUpdateAsync(
s => s.SetProperty(b => b.TournamentId , null);

Related

Entity Framework Core not filling object by foreign key on update

I see this error when updating an Application. The object has two external connections: ApplicationVisitors and ApplicationPlatforms. The properties in Application have been updated, but external connections was not be updated.
What am I doing wrong? How to update Application correctly?
Route
[Route("update")]
[HttpPut]
public async Task<IActionResult> UpdateApplication(ApplicationDTO applicationDTO)
{
if (!ModelState.IsValid)
return BadRequest("Модель не валидна!");
await applicationService.UpdateApplication(applicationDTO);
return Ok();
}
Service
public async Task UpdateApplication(ApplicationDTO applicationDTO)
{
var visitors = Mapper.ToVisitors(applicationDTO.ApplicationVisitors);
var visitorsToCreate = visitors.Where(w => w.Id == 0).ToList();
var createdVisitors = visitors.Where(w => w.Id > 0).ToList();
var resultCreateVisitors = await _wrapper.Visitor.CreateVisitorsAsync(visitorsToCreate);
createdVisitors.AddRange(resultCreateVisitors);
applicationDTO.ApplicationVisitors = Mapper.ToVisitors(createdVisitors);
await _wrapper.Application.UpdateAsync(Mapper.ToApplication(applicationDTO));
}
Repository method
public async Task UpdateAsync(Application application)
{
Update(application);
await SaveAsync();
}
BaseRepository
public void Update(T entity)
{
_repositoryContext.Set<T>().Attach(entity);
_repositoryContext.Entry(entity).State = EntityState.Modified;
}
public async Task SaveAsync()
{
await _repositoryContext.SaveChangesAsync();
}
I have not any exeption in debug. Application was filled by ID, but Platform and Visitor in collections ApplicationPlatforms and ApplicationVisitors does not filling by foreign key.
References is existing in classes.
view result Attach
Application
public class Application
{
public int Id { get; set; }
[Column(TypeName="date")]
public DateTime DateStart { get; set; }
[Column(TypeName = "date")]
public DateTime DateEnd { get; set; }
public int ApproverId { get; set; }
public User Approver { get; set; }
public int StatusId { get; set; }
public ApplicationStatus Status { get; set; }
public string VisitPurpose { get; set; }
public DateTime CreatedAt { get; set; }
public int AuthorId { get;set; }
public User Author { get; set; }
public IList<Draft> Drafts { get; set; }
public IList<ApplicationVisitor> ApplicationVisitors { get; set; }
public IList<ApplicationPlatform> ApplicationPlatforms { get; set; }
public IList<Pass> Passes { get; set; }
}
ApplicationVisitor
public class ApplicationVisitor
{
public int ApplicationId { get; set; }
[ForeignKey("ApplicationId")]
public Application Application { get; set; }
public int VisitorId { get; set; }
[ForeignKey("VisitorId")]
public Visitor Visitor { get; set; }
}
ApplicationPlatform
public class ApplicationPlatform
{
public int ApplicationId { get; set; }
[ForeignKey("ApplicationId")]
public Application Application { get; set; }
public int PlatformId { get; set; }
[ForeignKey("PlatformId")]
public Platform Platform { get; set; }
}
UPD: 22.10.2019. This is my solution what working for me on base selected answer!
I rewrited update method in ApplicationRepository
public async Task UpdateAsync(Application application)
{
var app = await GetAsync(application.Id);
app.DateStart = application.DateStart;
app.DateEnd = application.DateEnd;
app.ApproverId = application.ApproverId;
app.StatusId = application.StatusId;
app.VisitPurpose = application.VisitPurpose;
app.CreatedAt = application.CreatedAt;
app.AuthorId = application.AuthorId;
app.ApplicationVisitors = application.ApplicationVisitors;
app.ApplicationPlatforms = application.ApplicationPlatforms;
Update(app);
await SaveAsync();
}
And rewrided method in my BaseRepository
public void Update(T entity)
{
_repositoryContext.Set<T>().Update(entity);
}
Although you attached the Application object and set its state to Modified, all other objects references by it will be in Unchanged state, as stated here:
For entity types with generated keys if an entity has its primary key
value set then it will be tracked in the Unchanged state. If the
primary key value is not set then it will be tracked in the Added
state. This helps ensure only new entities will be inserted. An entity
is considered to have its primary key value set if the primary key
property is set to anything other than the CLR default for the
property type.
You can either manually set the state of all referenced objects or, A better approach in my option, is to load the objects from the database (and have EF track them for changes) and modify these objects. These way EF will know the exact state for each object when it comes to save it to the database.

Cannot insert value for identity in table when IDENTITY_INSERT is set to OFF

I am getting the error above when I call SaveChanges() using an instance of my Context. Now I understand what the error is pointing to, but I simply do not understand why it occurs for this particular situation.
The error occurs because I save an instance of the TestMember into the TestMember field in the Report class (which is the model for my table). Since TestMember is the foreign key this should not be a problem should it? For my own clarity I wrote raw SQL and explicitly put a valid int into the ForeignKey field and it worked fine. INSERT INTO [TestMemberReports].[dbo].[TestReports] (TestMemberId,Date,IsSuccess) values (3, '2019-05-09', 0).
However when done in code like shown below. It throws the SqlException: Cannot insert explicit value for identity column in table 'TestMembers' when IDENTITY_INSERT is set to OFF. error. For the purpose of the question please assume:
CreateReports() is called from main()
Code
public class TestReportService
{
private TestMemberReportContext Context { get; }
public void SaveChanges() => Context.SaveChanges();
public TestReportService(TestMemberReportContext context)
{
Context = context;
}
public void CreateReports()
{
var reports = AddReport();
Context.TestReports.Add(reports);
SaveChanges();
}
private TestReport AddReport()
{
return new TestReport { IsSuccess = 0, TestMember = GetTestMember("Member 1"), Date = DateTime.Now() });
}
public TestMember GetTestMember(string name)
{
return Context.TestMembers.Single(c => c.Name == name);
}
}
Models
public class TestReport
{
public int Id { get; set; }
public TestMember TestMember { get; set; }
public DateTime Date { get; set; }
public bool IsSuccess{ get; set; }
}
public class TestMember
{
public int Id { get; set; }
public string Name { get; set; }
public string FileName { get; set; }
public ICollection<TestRecipient> Recipients { get; set; }
}
For anyone who encounters this error in the future the fix is using a different kind of pattern to store foreign keys. Using SQL profiler I determined that when I stored a member object to be used as an FK in the Reports object, EF Core was infact running an INSERT query to my database (Hence the error message pointing to a table which is never called in the code).
Old Report Model
public class TestReport
{
public int Id { get; set; }
public TestMember TestMember { get; set; }
public DateTime Date { get; set; }
public bool IsSuccess{ get; set; }
}
New Report Model
public class TestReport
{
public int Id { get; set; }
public int? TestMemberId { get; set; }
[ForeignKey(nameof(TestMemberId))]
public virtual TestMember TestMember { get; set; }
public DateTime Date { get; set; }
public bool IsSuccess { get; set; }
}
This way whenever you want to store a TestMember object in a TestReport object you simply store the Id in the TestMemberId field.
And whenever you want to obtain a TestMember object from a TestReport object you simply use the Id as a predicate and use .Include(x => x.TestMember) in your LINQ (if you're using LINQ obviously)
I hope this helps you!

ASP.NET C# Class/Object with default values and database NOT NULL columns

I am using SQL server for database while developing in ASP.NET . In my database table, most of the columns are NOT NULL and have a set default value. I am also using Entity Framework.
My Model(class) currently looks like this:
public partial class INV
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public INV()
{
this.INVAttachments = new HashSet<INVAttachment>();
}
public int ID { get; set; }
public string SUPPROD { get; set; }
public string TYPE1 { get; set; }
public int MAXQTY { get; set; }
public decimal ROP { get; set; }
public int ROQ { get; set; }
public decimal SSL { get; set; }
public Nullable<System.DateTime> SYSDATE { get; set; }
public Nullable<System.DateTime> MAINT_DATE { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public ICollection<INVAttachment> INVAttachments { get; set; }
}
The user submits a form, and inside the form, most of fields are actually optional. But in the database, the columns are not null.
When I POST the form over to the server side, the INV object representation has null for the properties of the INV object. Below is a WebAPI function to handle the POST and save the INV object in the database.
[ResponseType(typeof(string))]
public async Task<string> PostINV(INV iNV)
{
try
{
if (!ModelState.IsValid)
{
//return BadRequest(ModelState);
return "badRequest";
}
db.INVs.Add(iNV);
await db.SaveChangesAsync();
//codes written here
}catch (Exception ex)
{
return "";
}
}
The above function is now returning system.data.entity.validation.dbvalidationerror for the INV entity because the properties for INV is required, but the object that I POST over contain NULL values.
My question is it possible to make sure the database columns remain NOT NULL but at the same time, I don't want my Model or Class to use Nullable types? Is it a matter of using a constructor for my class to set default values ?
This is just an answer to your question in a comment, and may not be the best solution
how does one make up values for DB ? Is it done by defining a
constructor or a method on the model?
You can do it a number of ways, however this is a very simple solution
public class Track
{
public Track()
{
LengthInMeters = 400;
}
public int LengthInMeters { get; set; }
}

EF Core 2 throws exception on SaveChanges, after first update for related entities

hi :) i ran in to a problem with editing/updating entries in db which has related entities. i am able to edit any entry once, and after that i am not able to update any dependent entity wich has the same principal entity, as the entity i already modified
i have spend last 5 days or so, trying to solve this. all the advice that i have found on the net, did not work :(
u can see the project #: https://github.com/nedimbih/WorkNotes
problematic part is this:
public partial class Work
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public virtual Worker Worker { get; set; }
public DateTime Date { get; set; }
public TimeSpan StartingTime { get; set; }
public float Duration { get; set; }
public string Note { get; set; }
}
and
public partial class Worker : IdentityUser
{
public Worker() => WorksDoneByWorker = new HashSet<Work>();
public virtual ICollection<Work> WorksDoneByWorker { get; set; }
}
In WorkRepository.cs i have this code
internal int Update(Work input)
{
Work workInDb = _context.WorkList
.Include(w => w.Worker)
.FirstOrDefault(w => w.Id == input.Id);
if (workInDb != null)
// v.1
// {workInDb = input;}
// v.2
{
workInDb.Note = input.Note;
workInDb.StartingTime = input.StartingTime;
workInDb.Duration = input.Duration;
workInDb.Date = input.Date;
workInDb.Worker = input.Worker;
}
int savedEntries;
try
{
savedEntries = _context.SaveChanges();
}
catch (Exception)
{
savedEntries = 0;
// used as a flag. Calling code acts upon it
}
return savedEntries;
}
i can make updates on work entries only once, for a given worker.
after i edit/update one work entry (savedEntries holds value 2), i can no longer update any entry with same worker as updated entry. i get exception on SaveChanges saying that Worker with same id is already being tracked.
if i turn of line workInDb.Worker=input.Worker then it saves, but that is not functionality that i need.
if i turn on v.1 code instead of v.2 i get no exception but SaveChanges does nothing.
count of modified entries (in context) is 0 in both cases.
thx :)
I don't think your model is right.
Try this:
public partial class Work
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int WorkerId { get; set; }
public Worker Worker { get; set; }
public DateTime Date { get; set; }
public TimeSpan StartingTime { get; set; }
public float Duration { get; set; }
public string Note { get; set; }
}
and the instead of setting the worker set its Id like this:
workInDb.WorkerId = input.Worker.Id;
This will prevent EF of trying to create and store a new Worker with the same Id, but instead in adding a relation to the existing worker.

MVC 5 Create Validation error but valid ModelState

I am trying to create within MVC 5 and am getting a validation error even though the ModelState is coming back valid.
Error message
Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
and when I look at the message, it shows....
The name 'e' does not exist in the current context
When I look at the POST data, the model that was created has all required fields filled in. I did notice that the model ID was assigned 0. I'm not sure if that is the error or if it is supposed to pass a zero for the ID.
What might the problem be?
WosController.cs
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "id,woNumber,woDescription,dueDate,qty,item_id,releaseDate,parentWO_id,wip_id")] Wo wo)
{
if (ModelState.IsValid)
{
db.Wos.Add(wo);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(wo);
}
Wo.cs
public partial class Wo
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Wo()
{
this.WoParts = new HashSet<WoPart>();
this.WoStatuses = new HashSet<WoStatus>();
}
public int id { get; set; }
public string woNumber { get; set; }
public string woDescription { get; set; }
public Nullable<System.DateTime> dueDate { get; set; }
public string qty { get; set; }
public Nullable<int> item_id { get; set; }
public Nullable<System.DateTime> releaseDate { get; set; }
public string status { get; set; }
public Nullable<int> parentWO_id { get; set; }
public int wip_id { get; set; }
public Nullable<int> part_id { get; set; }
public virtual Item Item { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<WoPart> WoParts { get; set; }
public virtual Wo woParentWO { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<WoStatus> WoStatuses { get; set; }
public virtual Part Part { get; set; }
public virtual Wip Wip { get; set; }
}
Wrap your call to SaveChangesAsync in a try...catch like so:
try
{
await db.SaveChangesAsync();
}
catch (DbEntityValidationException e)
{
var errorMessages = e.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
var fullErrorMessage = string.Join("; ", errorMessages);
var exceptionMessage = string.Concat(e.Message, " The validation errors are: ", fullErrorMessage);
throw new DbEntityValidationException(exceptionMessage, e.EntityValidationErrors);
}
That will show you the actual properties causing the validation issues. Then, update your question with the results, if you still need assistance.
Likely, your database is out of sync with your entities. The status property is not required on your entity, and by default properties of type string are nullable. That would explain why you're passing validation on post, but failing on actually saving the entity.
Generally, it's best not to rely on the database setting a default value in the first place. Instead, have the property itself have a default value, and then it will always be fine, regardless of what's going on at the database level:
private string _status;
public string status
{
get { return _status ?? "Default Value"; }
set { _status = value;
}
Short of that, if status is truly not required, then you should ensure that the status column on your table is nullable.

Categories