I would ask about how to get current date in workflows.?
For example:-
I have field called (Inspection Date) I would like to compare this filed if it equal current date then go into the process.
I will create Wait Condition waiting until current date = Inspection date then will do my process .
How to get current date in workflow.?
To answer the OP's question about how to get the current date in the workflow, the short answer is that you can't. As Henk van Boeijen has described, it is possible to get the current date in some of the individual steps however.
Where I work we have implemented a very basic custom workflow activity that simply returns the current date and time.
public class CurrentDateWorkflow : CodeActivity
{
protected override void Execute(CodeActivityContext executionContext)
{
try
{
// Create the context and tracing service
IExecutionContext context = executionContext.GetExtension<IExecutionContext>();
IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
ITracingService tracingService = executionContext.GetExtension<ITracingService>();
if (tracingService == null)
throw new InvalidPluginExecutionException("Failed to retrieve tracing service.");
tracingService.Trace("Entered CurrentDateWorkflow.Execute(), Activity Instance Id: {0}, Workflow Instance Id: {1}",
executionContext.ActivityInstanceId,
executionContext.WorkflowInstanceId);
var DatePartOnly = InputDatePartOnly.Get(executionContext);
// Set output parameters
if (DatePartOnly)
CurrentDate.Set(executionContext, DateTime.UtcNow.Date);
else
CurrentDate.Set(executionContext, DateTime.UtcNow);
// All done
tracingService.Trace("CurrentDateWorkflow.Execute() Complete. Activity Instance Id: {0}, Workflow Instance Id: {1}",
executionContext.ActivityInstanceId,
executionContext.WorkflowInstanceId);
}
catch (Exception ex)
{
throw new InvalidPluginExecutionException(String.Format("An error occurred in the {0} plug-in.",
this.GetType().ToString()),
ex);
}
}
[Output("Current Date")]
public OutArgument<DateTime> CurrentDate { get; set; }
[Input("Date Only")]
[Default("False")]
public InArgument<bool> InputDatePartOnly { get; set; }
}
In window "Specify Condition" select field "Inspection Date" with condition "Is Greater Than or Equal To". In the Form Assistant expand picklist "Look for", select under "Local Values" the option "Process". In the box below the picklist "Execution Time" is displayed. Select it and you are done.
Related
I have a problem In association with validation property! I use .net core 2.1. I checked unique fields in OnModelCreating method in DbContext class and it's working fine. now I want to display an error message if the user enters the same BirthCertificate value (already exists in the database) in the input field like that Display & Required & MaxLength & ... attributes and send (bind) it to ModelState to check it. i also use jquery.validate.js in the client and show all errors and it's working fine. how I should do this:
Public Class Person
{
[Display(Name = "Enter BirthCertificate")]
[Required(ErrorMessage = "Please enter {0}")]
[MaxLength(10, ErrorMessage = "Max lenght is {0}")]
public string BirthCertificate { get; set; }
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<DomainClasses.Person.Person>(entity =>
{
entity.HasIndex(e => e.BirthCertificate).IsUnique(); // it's working fine
});
}
thanks
You can use either one of these.
1) Create new unique key for BirthCertificate in SQL Server. Use catch (Exception ex) and return to ajax to show error.
2) Do checking code
if (db.Person.Where(x => x.BirthCertificate.Contains(birthcert)).Any())
{
//return to ajax to show error
}
I am working on a Dynamics CRM CWA that updates the "Modified By" field based on a text field called "Prepared By". I currently have 3 errors that I need some help debugging (see below). They may be pretty easy fixes but I am fairly new to coding. Any help de-bugging would be greatly appreciated. Thanks!
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Threading.Tasks;
using System.Activities;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
using System.Runtime.Serialization;
namespace KED365.Workflows
{
/// </summary>
public class ModifiedBy : WorkFlowActivityBase
{
private Guid contactid;
[Input("User Full Name")]
public InArgument<string> UserFullName { get; set; }
/// <summary>
/// Executes the WorkFlow.
/// </summary>
/// <param name="crmWorkflowContext">The <see cref="LocalWorkflowContext"/> which contains the
/// <param name="executionContext" > <see cref="CodeActivityContext"/>
/// </param>
/// <remarks>
/// For improved performance, Microsoft Dynamics 365 caches WorkFlow instances.
/// The WorkFlow's Execute method should be written to be stateless as the constructor
/// is not called for every invocation of the WorkFlow. Also, multiple system threads
/// could execute the WorkFlow at the same time. All per invocation state information
/// is stored in the context. This means that you should not use global variables in WorkFlows.
/// </remarks>
protected override void Execute(CodeActivityContext activityContext, IWorkflowContext workflowContext, IOrganizationService orgService, ITracingService tracingService)
{
//get entity record for which plugin was fired
Entity _target = (Entity)workflowContext.InputParameters["Target"];
//check if portaluser name is to be obtained from custom createby or from custom modifiedby
if (workflowContext.MessageName.ToUpper() == "CREATE")
{
contactid = _target.Attributes.Contains("new_createdby") ? _target.GetAttributeValue<EntityReference>("new_createdby").Id : Guid.Empty;
}
else
{
contactid = _target.Attributes.Contains("new_modifiedby") ? _target.GetAttributeValue<EntityReference>("new_modifiedby").Id : Guid.Empty;
}
//retrieve contact fullname from contactid
var _contact = activityContext.CreateQuery("contact").Where(c => c.GetAttributeValue<Guid>("contactid").Equals(contactid)).FirstOrDefault();
if (_contact != null)
{
if (_contact.Attributes.Contains("fullname"))
{
fullname = _contact.GetAttributeValue<string>("fullname");
}
//retrieve Systemuser that has same name as that of new_portalcreatedby/ //new_portalmodifiedby
Entity _user = context.CreateQuery("systemuser").Where(e => e.GetAttributeValue<string>("fullname").Equals(fullname)).FirstOrDefault();
if (_user != null)
{
//check if we need to update createdby or modifiedby
if (workflowContext.MessageName.ToUpper() == "CREATE")
{
_target["createdby"] = _user.ToEntityReference();
}
else
{
_target["modifiedby"] = _user.ToEntityReference();
}
//assign new target to plugin executioncontext
workflowContext.InputParameters["Target"] = _target;
}
}
}
}
}
Error 1 :
Severity Code Description Project File Line Suppression State
Error CS1061 'CodeActivityContext' does not contain a definition for 'CreateQuery' and no extension method 'CreateQuery' accepting a first argument of type 'CodeActivityContext' could be found (are you missing a using directive or an assembly reference?) Workflows C:\Users\tgiard\Downloads\GetUserByName-master\GetUserByName-master\Workflows\ModifiedBy.cs 68 Active
Error 2 :
Severity Code Description Project File Line Suppression State
Error CS0103 The name 'fullname' does not exist in the current context Workflows C:\Users\tgiard\Downloads\GetUserByName-master\GetUserByName-master\Workflows\ModifiedBy.cs 75 Active
Error 3 :
Severity Code Description Project File Line Suppression State
Error CS0103 The name 'context' does not exist in the current context Workflows C:\Users\tgiard\Downloads\GetUserByName-master\GetUserByName-master\Workflows\ModifiedBy.cs 79 Active
Here is some feedback on your issues:
Error 1 - 'CodeActivityContext' does not contain a definition for 'CreateQuery'
This issue is related to the lines:
var _contact = activityContext.CreateQuery("contact").Where(c => c.GetAttributeValue<Guid>("contactid").Equals(contactid)).FirstOrDefault();
and
Entity _user = context.CreateQuery("systemuser").Where(e => e.GetAttributeValue<string>("fullname").Equals(fullname)).FirstOrDefault();
I don't know what that method is but you have better options; for the contact you already have the guid, so you can simply use a Retrieve():
var _contact = orgService.Retrieve("contact", contactid, new ColumnSet("fullname"));
And for the system user write a QueryExpression filtering by fullname:
var query = new QueryExpression("systemuser"):
query.Criteria.AddCondition("fullname", ConditionOperator.Equal, fullname);
var _user = orgService.RetrieveMultiple(query).Entities.FirstOrDefault();
Error 2: The name 'fullname' does not exist in the current contex
This is basic C#, you must instantiate your variable before you use it:
string fullname;
Error 3: The name 'context' does not exist in the current context
Ironic and true. This should be activityContext, but we have already fixed this issue in the change we made for Error 1.
Entity _user = context.CreateQuery("systemuser").Where(e => e.GetAttributeValue<string>("fullname").Equals(fullname)).FirstOrDefault();
As Zach Mast correctly pointed out, using a Pre-Operation is recommended. It also seems an odd case where you retrieve a contacts name and match it with a user. Instead, You could change the type of the field to a user reference, add a user field to the contact you retrieve or add a code to match the Contact to the User. This way, you won't have issues with users with the same name or typo's.
Please find below your workflow activity converted to a Pre-Operation plugin.
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Linq;
namespace KED365.Plugins
{
public class CreateUpdateContact : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = factory.CreateOrganizationService(context.UserId);
tracingService.Trace("Start plugin");
tracingService.Trace("Validate Target");
if (!context.InputParameters.Contains("Target") || !(context.InputParameters["Target"] is Entity))
return;
tracingService.Trace("Retrieve Target");
var target = (Entity)context.InputParameters["Target"];
String message = context.MessageName.ToLower();
SetCreatedByAndModifiedBy(tracingService, service, target, message);
}
private void SetCreatedByAndModifiedBy(ITracingService tracingService, IOrganizationService service, Entity target, string message)
{
tracingService.Trace("Start SetPriceList");
tracingService.Trace("Validate Message is Create or Update");
if (!message.Equals("create", StringComparison.OrdinalIgnoreCase) && !message.Equals("update", StringComparison.OrdinalIgnoreCase))
return;
tracingService.Trace("Retrieve Attributes");
var createdByReference = target.GetAttributeValue<EntityReference>("new_createdby");
var modifiedByReference = target.GetAttributeValue<EntityReference>("new_modifiedby");
tracingService.Trace("Retrieve And Set User for Created By");
RetrieveAndSetUser(tracingService, service, target, createdByReference, "createdby");
tracingService.Trace("Retrieve And Set User for Modified By");
RetrieveAndSetUser(tracingService, service, target, modifiedByReference, "modifiedby");
}
private void RetrieveAndSetUser(ITracingService tracingService, IOrganizationService service, Entity target, EntityReference reference, string targetAttribute)
{
tracingService.Trace("Validating Reference");
if (reference == null)
return;
tracingService.Trace("Retrieving and Validating User");
var user = RetrieveUserByName(service, reference.Name, new ColumnSet(false));
if (user == null)
return;
tracingService.Trace("Setting Target Attribute");
target[targetAttribute] = user.ToEntityReference();
}
private Entity RetrieveUserByName(IOrganizationService service, string name, ColumnSet columns)
{
var query = new QueryExpression
{
EntityName = "systemuser",
ColumnSet = columns,
Criteria = new FilterExpression
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression
{
AttributeName = "fullname",
Operator = ConditionOperator.Equal,
Values = { name }
}
}
}
};
var retrieveResponse = service.RetrieveMultiple(query);
if (retrieveResponse.Entities.Count == 1)
{
return retrieveResponse.Entities.FirstOrDefault();
}
else
{
// Alternatively you can thrown an error as you have unexpectedly multiple matches
return null;
}
}
}
}
I'm creating a chatbot using LUIS template and I have an intent called StartTest with utterances like these:
Begin [TestNameEntity] on [GearEntity] and [RPMEntity]
Begin [TestNameEntity] on [GearEntity]
Begin [TestNameEntity]
If the user input matches the first utterance, I have all entities I need. However, if its input matches the second utterance, I have to ask him the RPM. And for the third utterance I have to ask the gear and the RPM.
But for the second and third utterances I need to know what the user has said for the [TestNameEntity], like this:
User: Begin test 1 on second gear
Bot: What RPM?
User: 2500
How can I achieve this?
once your Intent method is called you can use this logic
result.Entities[0].Type to get entity name
so you can iterate result.Entities to get all the entities user has entered. If any of the three entities is missing you can ask for a prompt. This is a sample code of how to achieve it.
//global variables
public string CurrentGear { get; set; }
public string CurrentRpm { get; set; }
[LuisIntent("StartTest")]
public async Task StartTestIntent(IDialogContext context, LuisResult result)
{
if (result.Entities != null && result.Entities.Count >0)
{
//sample you will get selected entity type
//var ent1=result.Entities[0].Type;
var userValues=GetUserEntities(result);
if(userValues["GearEntity"]==null)
showGearPrompt(context);
if(userValues["RPMEntity"]==null)
showRpmPrompt(context);
}
}
private string[] GetUserEntities(LuisResult result)
{
//your logic here
//return list of entities;
}
private async Task showGearPrompt(IDialogContext context)
{
PromptDialog.Text(
context: context,
resume: OnGearOptionReceivedAsync,
prompt: "please enter Gear Value",
retry: "Sorry, I didn't understand that. Please try again."
);
}
public virtual async Task OnGearOptionReceivedAsync(IDialogContext context, IAwaitable<string> gear)
{
string response = await gear;
CurrentGear = response;
}
private async Task showRpmPrompt(IDialogContext context)
{
PromptDialog.Text(
context: context,
resume: OnRpmOptionReceivedAsync,
prompt: "please enter RPM Value",
retry: "Sorry, I didn't understand that. Please try again."
);
}
public virtual async Task OnRpmOptionReceivedAsync(IDialogContext context, IAwaitable<string> rpm)
{
string response = await rpm;
CurrentRpm = response;
}
Link to how to use prompts
I'm still fairly new to ASP.NET and MVC and despite days of googling and experimenting, I'm drawing a blank on the best way to solve this problem.
I wrote a BirthdayAttribute that I want to work similar to the EmailAddressAttribute. The birthday attribute sets the UI hint so that the birthday DateTime will be rendered using an editor template that has 3 dropdown lists. The attribute can also be used to set some additional meta data that tells the year dropdown how many years it should display.
I know I could use jQuery's date picker, but in the case of a birthday I find the 3 dropdowns much more usable.
#model DateTime
#using System;
#using System.Web.Mvc;
#{
UInt16 numberOfVisibleYears = 100;
if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("NumberOfVisibleYears"))
{
numberOfVisibleYears = Convert.ToUInt16(ViewData.ModelMetadata.AdditionalValues["NumberOfVisibleYears"]);
}
var now = DateTime.Now;
var years = Enumerable.Range(0, numberOfVisibleYears).Select(x => new SelectListItem { Value = (now.Year - x).ToString(), Text = (now.Year - x).ToString() });
var months = Enumerable.Range(1, 12).Select(x => new SelectListItem{ Text = new DateTime( now.Year, x, 1).ToString("MMMM"), Value = x.ToString() });
var days = Enumerable.Range(1, 31).Select(x => new SelectListItem { Value = x.ToString("00"), Text = x.ToString() });
}
#Html.DropDownList("Year", years, "<Year>") /
#Html.DropDownList("Month", months, "<Month>") /
#Html.DropDownList("Day", days, "<Day>")
I also have a ModelBinder to rebuild my date afterwards. I've removed the content of my helper functions for brevity, but everything works great up to this point. Normal, valid dates, work just fine for creating or editing my members.
public class DateSelector_DropdownListBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext == null)
throw new ArgumentNullException("controllerContext");
if (bindingContext == null)
throw new ArgumentNullException("bindingContext");
if (IsDropdownListBound(bindingContext))
{
int year = GetData(bindingContext, "Year");
int month = GetData(bindingContext, "Month");
int day = GetData(bindingContext, "Day");
DateTime result;
if (!DateTime.TryParse(string.Format("{0}/{1}/{2}", year, month, day), out result))
{
//TODO: SOMETHING MORE USEFUL???
bindingContext.ModelState.AddModelError("", string.Format("Not a valid date."));
}
return result;
}
else
{
return base.BindModel(controllerContext, bindingContext);
}
}
private int GetData(ModelBindingContext bindingContext, string propertyName)
{
// parse the int using the correct value provider
}
private bool IsDropdownListBound(ModelBindingContext bindingContext)
{
//check model meta data UI hint for above editor template
}
}
Now that I'm looking at it, I should probably be using a nullable DateTime, but that's neither here nor there.
The problem I'm having is with very basic validation of invalid dates such as February 30th, or September 31st. The validation itself works great, but the invalid dates aren't ever saved and persisted when the form is reloaded.
What I'd like is to remember the invalid date of February 30th and redisplay it with the validation message instead of resetting the dropdowns to their default value. Other fields, like the email address (decorated with the EmailAddressAttribute) preserve invalid entries just fine out of the box.
At the moment I am just trying to get the server side validation working. To be honest, I haven't even started thinking about the client side validation yet.
I know there is lots I could do with javascript and ajax to make this problem a moot point, but I would still rather have the proper server side validation in place to fall back on.
I finally managed to solve my problem, so I wanted to share my solution.
DISCLAIMER:
Although I used to be great with .NET 2.0 back in the day, I'm only now updating my skills to the latest versions of C#, ASP.NET, MVC, and Entity Framework. If there are better ways to do anything I've done below please I'm always open to feedback.
TODO:
Implement client side validation for invalid dates such as February 30th. Client side validation for [Required] attribute is already built in.
Add support for cultures so that the date shows up in desired format
The solution came to me when I realized that the problem I was having is that DateTime will not allow itself to be constructed with an invalid date such as February 30th. It simply throws an exception. If my date wouldn't construct, I knew of no way to pass my invalid data back through the binder to the ViewModel.
To solve this problem, I had to do away with the DateTime in my view model and replace it with my own custom Date class. The solution below will provide fully functioning server side validation in the event that Javascript is disabled. In the event of a validation error the invalid selections will persist after the validation message is displayed allowing the user to easily fix their mistake.
It should be easy enough to map this view-ish Date class to the DateTime in your date model.
Date.cs
public class Date
{
public Date() : this( System.DateTime.MinValue ) {}
public Date(DateTime date)
{
Year = date.Year;
Month = date.Month;
Day = date.Day;
}
[Required]
public int Year { get; set; }
[Required, Range(1, 12)]
public int Month { get; set; }
[Required, Range(1, 31)]
public int Day { get; set; }
public DateTime? DateTime
{
get
{
DateTime date;
if (!System.DateTime.TryParseExact(string.Format("{0}/{1}/{2}", Year, Month, Day), "yyyy/M/d", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
return null;
else
return date;
}
}
}
This is just a basic date class that you can construct from a DateTime. The class has properties for Year, Month, and Day as well as a DateTime getter that can try to retrieve you a DateTime class assuming you have a valid date. Otherwise it returns null.
When the built in DefaultModelBinder is mapping your form back to this Date object, it will take care of the Required and Range validation for you. However, we will need a new ValidationAtribute to make sure that invalid dates such as February 30th aren't allowed.
DateValidationAttribute.cs
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public sealed class DateValidationAttribute : ValidationAttribute
{
public DateValidationAttribute(string classKey, string resourceKey) :
base(HttpContext.GetGlobalResourceObject(classKey, resourceKey).ToString()) { }
public override bool IsValid(object value)
{
bool result = false;
if (value == null)
throw new ArgumentNullException("value");
Date toValidate = value as Date;
if (toValidate == null)
throw new ArgumentException("value is an invalid or is an unexpected type");
//DateTime returns null when date cannot be constructed
if (toValidate.DateTime != null)
{
result = (toValidate.DateTime != DateTime.MinValue) && (toValidate.DateTime != DateTime.MaxValue);
}
return result;
}
}
This is a ValidationAttribute that you can put on your Date fields and properties. If you pass in the resource file class and the resource key it will search the corresponding resource file in your "App_GlobalResources" folder for the error message.
Inside the IsValid method, once we're sure we're validating a Date we check it's DateTime property to see if it's not null to confirm that it's valid. I throw in a check for DateTime.MinValue and MaxValue for good measure.
So that's about it really. With this Date class, I managed to do away completely with the custom ModelBinder. This solution relies completely on the DefaultModelBinder, which means all of the validation works right out of the box. It apparently even checks my new DateValidationAttribute, which I was super excited about. I stressed forever thinking I might have to muck with validators in a custom binder. This feels a lot cleaner.
Here is the complete code for the partial view I'm using.
DateSelector_DropdownList.cshtml
#model Date
#{
UInt16 numberOfVisibleYears = 100;
if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("NumberOfVisibleYears"))
{
numberOfVisibleYears = Convert.ToUInt16(ViewData.ModelMetadata.AdditionalValues["NumberOfVisibleYears"]);
}
var now = DateTime.Now;
var years = Enumerable.Range(0, numberOfVisibleYears).Select(x => new SelectListItem { Value = (now.Year - x).ToString(), Text = (now.Year - x).ToString() });
var months = Enumerable.Range(1, 12).Select(x => new SelectListItem { Text = new DateTime(now.Year, x, 1).ToString("MMMM"), Value = x.ToString() });
var days = Enumerable.Range(1, 31).Select(x => new SelectListItem { Value = x.ToString(), Text = x.ToString() });
}
#Html.DropDownList("Year", years, "<Year>") /
#Html.DropDownList("Month", months, "<Month>") /
#Html.DropDownList("Day", days, "<Day>")
I'll also include the attribute I use that sets up the template hint and the number of visible years to show.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class DateSelector_DropdownListAttribute : DataTypeAttribute, IMetadataAware
{
public DateSelector_DropdownListAttribute() : base(DataType.Date) { }
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues.Add("NumberOfVisibleYears", NumberOfVisibleYears);
metadata.TemplateHint = TemplateHint;
}
public string TemplateHint { get; set; }
public int NumberOfVisibleYears { get; set; }
}
I think the solution turned out a lot cleaner than I expected it to. It solves all of my problems in the exact way that I was hoping to. I do wish that I was somehow able to keep the DateTime, but this is the only way I could figure out how to maintain an invalid selection using only server side code.
Are there any improvements you would make?
I building my first MVC project and having problem with handling concurrency.
Web application was working fine (adding, editing and deleting) before I decided to add concurrency handling.
//Entity class Line
public class Line
{
[Key]
[Required(ErrorMessage = "Please enter a line name")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter a Business Unit name")]
public string BU { get; set; }
[Required(ErrorMessage = "Please enter a Department name")]
public string Department { get; set; }
[Required(ErrorMessage = "Please enter a location of the line")]
public string Location { get; set; }
[Required(ErrorMessage = "Please enter a target output")]
[Range(0.01, double.MaxValue, ErrorMessage = "Please enter a positive target output")]
public int TargetOutput { get; set; }
[Required(ErrorMessage = "Please enter a target yiel")]
[Range(0.01, 1.00, ErrorMessage = "Please enter a positive number in range of 0.01 to 1.00")]
public Double TargetYield { get; set; }
[Timestamp]
public Byte[] Timestamp { get; set; }
}
code from Controller:
// Saves changes to the Line
[HttpPost]
public ActionResult Edit(Line line)
{
if (ModelState.IsValid)
{
repository.SaveLine(line);
TempData["message"] = string.Format("{0} has been saved", line.Name);
return RedirectToAction("Index");
}
else
{
// return to lines list if there is something wrong with the data
return View(line);
}
}
code from Entity Framework repository class ( this part of code is not competed yet, at the moment when concurrency occurred it supposed to just replace timestamp with the one from db and save it again) :
// Save changes to the line or create new one if not exists
public void SaveLine(Line line)
{
// Checking if line with the same name already exists
//Line found = Lines.FirstOrDefault(l => l.Name == line.Name);
string found = Lines
.Select(l => l.Name)
.Where( n => n == line.Name)
.SingleOrDefault();
if (found == null)
{
context.Lines.Add(line);
}
else
{
context.Entry(line).State = System.Data.EntityState.Modified;
}
//context.SaveChanges();
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var dbvalues = (Line)entry.GetDatabaseValues().ToObject();
line.Timestamp = dbvalues.Timestamp;
context.SaveChanges();
}
}
When I run application in only one tab and trying to edit line entity I'm getting this error:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
I have debug and check the timestamp value, it is null. But in database is 0x0000000000000814. When in next step timestamp is replaced with the one from database and trying to save again it throws the same exception.
I have not idea where I have made mistake, any help is much appreciated
Excuse me for my English.
Thank you
You need to write your timestamp out to the view to ensure it comes back into your entity.
Html.HiddenFor(o=>o.Timestamp)
It works when I run single instance of application, but when I run two of them for testing concurrency. I get exception which I have handled and replaced timestamp with the one from the db, but same exception raised again even when both of the timestamps are the same:
From immediate window:
?line.Timestamp
{byte[8]}
[0]: 0
[1]: 0
[2]: 0
[3]: 0
[4]: 0
[5]: 0
[6]: 70
[7]: 81
?dbValues.Timestamp
{byte[8]}
[0]: 0
[1]: 0
[2]: 0
[3]: 0
[4]: 0
[5]: 0
[6]: 70
[7]: 81
Why is that? Should it not only raise exception when timestamps are different?
Resolved: It looks like when I try to save again in code it actually still holding old object with old timestamp. I have moved try catch statement to controller and changed to replace timestamp and return Edit View when concurrency occured. Now when user press Save again exception is not throwed and data is saved.