Preserving DataContext, long running operation within Iterating - c#

Following actions needs to be performed:
get a view from the database of users that needs to recieve an e-mail
send this email
update the records of the usres with a timestamp the email was send.
This code does as described above but in the wrong order (rough sketch/needs refactoring):
public class MailMessageController
{
public static IEnumerable<Invitation> GetInvitations()
{
using (boDataContext context = new boDataContext())
{
// the table where the timestamps have to be set
Table<Computer> computer = context.GetTable<Computer>();
// the view that contains the users email addresses
Table<Invitation> report = context.GetTable<Invitation>();
// get the view from the database
IEnumerable<Invitation> items = report.AsEnumerable<Invitation>();
foreach (Invitation item in items)
{
// update the timestamp (not good, the e-mail hasn't been sent)
Computer c = computer.FirstOrDefault(
i => i.ComputerID == item.ComputerID
);
if (c != null)
{
c.DateInvited = DateTime.Now;
}
yield return item;
}
context.SubmitChanges(); // <-- this should commit the changes
}
}
I send e-mails from this collections via:
foreach(Invitation item in MailMessageController.GetInvitations())
{
if (SendMessage(item)) // <<-- bool if message was sent successfully
{
// here I want to update the database with the timestamp
}
}
So I need to update the database with a timestamp after the e-mail was send succesfully.
But it seems that I can't get around the fact that I need to create a context for every instance I need to update.
So this is more of a design issue. How can I get the view from the database, send emails and update the timestamp in the most fluent and less-cost manner?

How about updating the timestamps outside the loop?
var timeStamps = new List<Tuple<DateTime, int>>();
foreach(Invitation item in MailMessageController.GetInvitations())
{
if (SendMessage(item)) // <<-- bool if message was sent successfully
{
timeStamps.Add(new Tuple<DateTime, int>(DateTime.Now, item.ID);
}
}
UpdateTimeStamps(timeStamps);

Related

Best way to update data after an API call gets a record

I am currently working on an API where a record should only be allowed to be pulled once. It's basically a queue where once a client pulls the record, the Retrieved field on the record is marked true. The Get calls only pull records where the Retrieved field is false.
Controller:
[HttpGet]
public virtual IActionResult GetAll([FromQuery] int? limit)
{
try
{
return Ok(_repository.Get(limit));
}
catch
{
return new StatusCodeResult(StatusCodes.Status500InternalServerError);
}
}
Repository:
public IQueryable<Report> Get(int? limit)
{
IQueryable<Report> reports;
if (limit == null)
{
reports = _context.Reports.Where(r => r.Retrieved == false);
}
else
{
reports = _context.Reports.Where(r => r.Retrieved == false).Take((int)limit);
}
return reports;
}
What would be the best way to modify the records that have been pulled by the Get call? If I do the modification before returning results from the repository code, then when the controller actually converts the IQueryable to real data, the field has changed and it won't pull any results, but the Controller seems like the wrong place to be doing this sort of modification to the database.
I would split this functionality away from the retrieval. Let the caller/client indicate that the report has been successfully retrieved and read with a second call. It is a little more overhead but it adds resilience. Example: if there is a failure in the retrieval after the server call (maybe in the network on browser or client app) then the client has another opportunity to retrieve the data.
Controller:
[HttpPut]
public virtual async Task<IActionResult> MarkAsRetrieved(IEnumerable<int> reportIds, CancellationToken token)
{
await _repository.MarkRetrievedAsync(reportIds, token).ConfigureAwait(true);
return Ok();
}
Repository:
public Task MarkRetrievedAsync([FromBody]IEnumerable<int> reportIds, CancellationToken token)
{
foreach (Report report in reportIds.Select(x => new Report{ReportId = x, Retrieved = false}))
{
_context.Reports.Attach(report);
report.Retrieved = true;
}
return _context.SaveChangesAsync(token);
}
Notes
It is only necessary to send over the identifier for a Report instance. You can then attach an empty instance with that same identifier and update the Retrieved property to true, just that will be sent in the corresponding store update statement.
I would changed the Retrieved bit in the database to a handle of some kind-- Guid or record id to another table that records the fetch, or some other unique value. Then I would determine the handle, update the records I am about to fetch with that that handle, then retrieve the records that match that handle. At any point if you fail, you can set the retrieved handle back to NULL for the handle value you started.

Stop AspNet Identity storing email address after validation error

I have a serivce that fetches a list of users from a legacy system and synchronises my AspNet Identity database. I’ve a problem when updating a user’s email address with UserManager.SetEmail(string userId, string email) and the validation fails. The user object in the UserStore retains the value of the invalid email address. I stop processing that user and skip to the next user in my list. Later when my service finds a new user to create, I use UserManager.Create(ApplicationUser user) and the database is updated with all outstanding changes including the invalid email address of the existing user.
Is there a way to stop the invalid email address being persisted? Is this a bug or am I just not using it correctly? Should I just manually take a backup of every object before any update and revert all values if the IdentityResult has an error?
//get LegacyUsers
foreach (AppUser appUser in LegacyUsers){
var user = UserManager.FindByName(appUser.userName);
if (user != null){
If (!user.Email.Equals(appUser.Email)){
var result = UserManager.setEmail(user.Id, appUser.Email)
if (!result.Succeeded){
//user object still has new value of email despite error, but not yet persisted to DB.
Log.Error(…);
continue;
}
}
}
else
{
ApplicationUser newUser = new ApplicationUser{
UserName = appUser.userName,
//etc
}
var result = UserManager.Create(newUser); //DB updates first user with new email aswell as inserting this new user
if (!result.Succeeded){
Log.Error(…);
continue;
}
}
}
I'm using version 2.2.1.40403 of Microsoft.AspNet.Identity.Core and Microsoft.AspNet.Identity.EntityFramework
This is happening because EF keeps track of models and updates all of the modified objects when SaveChanges() method is called by UserManager.Create() method. You could easily detach the user which has invalid email from the DbContext like this:
// first get DbContext from the Owin.
var context = HttpContext.GetOwinContext().Get<ApplicationDbContext>();
foreach (AppUser appUser in LegacyUsers){
var user = UserManager.FindByName(appUser.userName);
if (user != null){
If (!user.Email.Equals(appUser.Email)){
var result = UserManager.setEmail(user.Id, appUser.Email)
if (!result.Succeeded){
Log.Error(…);
// detach the user then proceed to the next one
context.Entry(user).State = EntityState.Detached;
continue;
}
}
}
else{
// rest of the code
}
}

linq issue with creating relationships

Seems I have a slight problem with my linq, I have a datacontract for Groups and I have a seperate datacontract for messages. Messages can be part of a Group. However when I update a message record its not reflected when I list the group information, the message is still the same for that group. But yet the update is reflected when I directly list messages?
This is how I add a message to a group:
//lists for reference:
List<Group> Groups = new List<Group>();
List<Message> messages = new List<Message>();
//not sure the below method is correct for adding a message to a group
public void AddMessagetoGroup(string group, string messageID, string message)
{
var result = Groups.Where(n => String.Equals(n.GroupName, group)).FirstOrDefault();
var result1 = messages.Where(n => String.Equals(n.MessageID, messageID)).FirstOrDefault();
if (result != null)
{
result.GroupMessages.Add(new Message() { MessageID = messageID, GroupMessage = message });
}
if (result1 != null)
{
result1.MessageGroup.Add(new Group() { GroupName = group });
}
}
Seriously dont understand whats going on, if I add the message to the group any changes I make to the message should be reflected. The only thing I can think of is its adding a new instance of the already existing message, which means my update method is only copying the message, but where is this new copyed record even held. If its to difficult to fix how could I update the message that has been copyedTo/added to the group instead (cheap workaround)?
Assuming that a Group can have Messages and a Message can have Groups, you are trying to maintain 5 things:
The list of all Groups List<Group> Groups = ...
The list of all Messages List<Message> messages = ...
The messages for each Group List<Message> GroupMessages... in Group
The groups for each message List<Group> MessageGroup... in Message
The actual message to send to the group updated in several places
From what I can see it's the last one that is not being maintained correctly:
In AddMessagetoGroup you associate a 'new' Message to the Group.GroupMessages. This is a new instance of a class and won't be updated automatically when you update other Message instances. Just because two messages have the same MessageId doesn't mean they are the same instance.
In UpdateMessage you change a particular message but only in the messages list. This doesn't point to the same message in the group list.
All in all, you need a refactor to really get your code to what you want it to do. The way I see it is that you want to keep groups and messages separate, and reference once from the other rather than create copies.
First, the master list:
List<Group> Groups = new List<Group>();
List<Message> Messages = new List<Message>();
Secondly, creating or updating a message (you don't have the create part yet):
public Message CreateOrUpdateMessage(string messageID, string groupMessage)
{
var findmessage = Messages.Where(s => s.MessageID == messageID).FirstOrDefault();
if (findmessage != null)
{
findmessage.GroupMessage = groupMessage;
}
else
{
findmessage = new Message() { MessageID = messageID, GroupMessage = groupMessage};
Messages.Add(findmessage);
}
return findmessage;
}
Note how this takes care of adding this message to the Messages list. This is the only function that should add or change messages.
And finally adding messages to a group (note I don't worry about adding groups here):
public void AddMessagetoGroup(string group, string messageID, string message)
{
var findmessage = CreateOrUpdateMessage(messageID, message);
var findgroup = Groups.Where(n => String.Equals(n.GroupName, group)).FirstOrDefault();
if (findgroup != null)
{
if (findgroup.GroupMessages.Where(m => m.MessageID == messageID).Count() == 0)
{
findgroup.GroupMessages.Add(findmessage);
findmessage.MessageGroup.Add(findgroup);
}
}
}
Note that this function will also create the message, and ensure there are no duplicates in either Messages in total or Messages for any particular group.

Save changes back to database using entity framework

I have simple query that loads data from two tables into GUI. I'm saving loaded data to widely available object Clients currentlySelectedClient.
using (var context = new EntityBazaCRM())
{
currentlySelectedClient = context.Kliencis.Include("Podmioty").FirstOrDefault(d => d.KlienciID == klientId);
if (currentlySelectedClient != null)
{
textImie.Text = currentlySelectedClient.Podmioty.PodmiotOsobaImie;
textNazwisko.Text = currentlySelectedClient.Podmioty.PodmiotOsobaNazwisko;
}
else
{
textNazwa.Text = currentlySelectedClient.Podmioty.PodmiotFirmaNazwa;
}
}
So now if I would like to:
1) Save changes made by user how do I do it? Will I have to prepare something on database side? How do I handle modifying multiple tables (some data goes here, some there)? My current code seems to write .KlienciHaslo just fine, but it doesn't affect Podmioty at all. I tried different combinations but no luck.
2) Add new client to database (and save information to related tables as well)?
currentClient.Podmioty.PodmiotOsobaImie = textImie.Text; // not saved
currentClient.Podmioty.PodmiotOsobaNazwisko = textNazwisko.Text; // not saved
currentClient.KlienciHaslo = "TEST111"; // saved
using (var context = new EntityBazaCRM())
{
var objectInDB = context.Kliencis.SingleOrDefault(t => t.KlienciID == currentClient.KlienciID);
if (objectInDB != null)
{
// context.ObjectStateManager.ChangeObjectState(currentClient.Podmioty, EntityState.Modified);
//context.Podmioties.Attach(currentClient.Podmioty);
context.Kliencis.ApplyCurrentValues(currentClient); // update current client
//context.ApplyCurrentValues("Podmioty", currentClient.Podmioty); // update current client
}
else
{
context.Kliencis.AddObject(currentClient); // save new Client
}
context.SaveChanges();
}
How can I achieve both?
Edit for an answer (doesn't save anything ):
currentClient.Podmioty.PodmiotOsobaImie = textImie.Text; // no save
currentClient.Podmioty.PodmiotOsobaNazwisko = textNazwisko.Text; // no save
currentClient.KlienciHaslo = "TEST1134"; // no save
using (var context = new EntityBazaCRM())
{
if (context.Kliencis.Any(t => t.KlienciID == currentClient.KlienciID))
{
context.Kliencis.Attach(currentClient); // update current client
}
else
{
context.Kliencis.AddObject(currentClient); // save new Client
}
context.SaveChanges();
}
Apparently ApplyCurrentValues only works with scalar properties.
If you attach the currentClient then associated objects should also attach, which means they'll be updated when you SaveChanges()
But you'll get an Object with the key exists exception because you are already loading the object from the database into the objectInDB variable. The context can only contain one copy of a Entity, and it knows that currentClient is the same as objectInDB so throws an exception.
Try this pattern instead
if (context.Kliencis.Any(t => t.KlienciID == currentClient.KlienciID))
{
context.Kliencis.Attach(currentClient); // update current client
}
else
{
context.Kliencis.AddObject(currentClient); // save new Client
}
or if you're using an identity as the ID, then
// if the ID is != 0 then it's an existing database record
if (currentClient.KlienciID != 0)
{
context.Kliencis.Attach(currentClient); // update current client
}
else // the ID is 0; it's a new record
{
context.Kliencis.AddObject(currentClient); // save new Client
}
After some work and help from Kirk about the ObjectStateManager error that I was getting I managed to fix this. This code allows me to save both changes to both tables.
currentClient.Podmioty.PodmiotOsobaImie = textImie.Text;
currentClient.Podmioty.PodmiotOsobaNazwisko = textNazwisko.Text;
currentClient.KlienciHaslo = "TEST1134";
using (var context = new EntityBazaCRM()) {
if (context.Kliencis.Any(t => t.KlienciID == currentClient.KlienciID)) {
context.Podmioties.Attach(currentClient.Podmioty);
context.Kliencis.Attach(currentClient);
context.ObjectStateManager.ChangeObjectState(currentClient.Podmioty, EntityState.Modified);
context.ObjectStateManager.ChangeObjectState(currentClient, EntityState.Modified);
} else {
context.Kliencis.AddObject(currentClient); // save new Client
}
context.SaveChanges();
}

Solving Optimistic Concurrency Update problem with Entity Framework

How should I solve simulation update, when one user updates already updated entery by another user?
First user request 'Category' entityobject, second user does the same.
Second user updates this object and first user updates.
I have field timestamp field in database that wa set to Concurrency Mode - Fixed.
This is how I update:
public class CategoriesRepository : BaseCategoryRepository
{
public void Update(....)
{
try
{
Category catToUpdate = (from c in Contentctx.Categories where c.CategoryID == categoryId select c).FirstOrDefault();
catToUpdate.SectionReference.EntityKey = new System.Data.EntityKey("ContentModel.Sections", "SectionID", sectionId);
if (catToUpdate != null)
{
//Set fields here....
Contentctx.SaveChanges();
}
base.PurgeCacheItems(CacheKey);
}
catch (OptimisticConcurrencyException ex)
{
}
}
}
//Contentctx comes from base class: Contentctx:
private ContentModel _contenttx;
public ContentModel Contentctx
{
get
{
if ((_contenttx == null))
{
_contenttx = new ContentModel();
}
return _contenttx;
}
set { _contenttx = value; }
}
//on Business layer:
using (CategoriesRepository categoriesRepository = new CategoriesRepository())
{
categoriesRepository.UpdateCategory(.....);
}
Exception never jumps...
How should I handle this?
Are you sure that sequence of calls is performed as you described? Anyway the main problem here is that you should not use the timestamp returned by the query in Upate method. You should use timestamp received when user get initial data for update. It should be:
User requests data from update - all fields and timestamp are received from DB
Timestamp is stored on client (hidden field in web app)
User modifies data and pushes Save button - all data including old timestamp are send to processin
Update method loads current entity
All updated fields are merged with old entity. Timestamp of entity is set to timestamp received in the first step.
SaveChanges is called
The reason for this is that it can pass a lot of time between first call and update method call so there can be several changes already processed. You have to use initial timestamp to avoid silent overwritting other changes.
Edit:
// You are updating category - update probably means that you had to load category
// first from database to show actual values. When you loaded the category it had some
// timestamp value. If you don't use that value, any change between that load and c
// calling this method will be silently overwritten.
public void Update(Category category)
{
try
{
Category catToUpdate = (from c in Contentctx.Categories where c.CategoryID == categoryId select c).FirstOrDefault();
...
// Now categoryToUpdate contains actual timestamp. But it is timestamp of
// actual values loaded now. So if you use this timestamp to deal with
// concurrency, it will only fire exception if somebody modifies data
// before you call SaveChanges few lines of code bellow.
if (catToUpdate != null)
{
//Set fields here....
// To ensure concurrency check from your initial load, you must
// use the initial timestamp.
catToUpdate.Timestamp = category.Timestamp;
Contentctx.SaveChanges();
}
...
}
catch (OptimisticConcurrencyException ex)
{
...
}
}

Categories