I've followed the documentation here, and I'm using SQL Server 2016.(https://ef.readthedocs.io/en/latest/modeling/concurrency.html)
I've added a timestamp column to my table named VersionCol, and then when I run Scaffold-DbContext it places the following property on my table entity in my DbContext.
entity.Property(e => e.VersionCol)
.IsRequired()
.HasColumnType("timestamp")
.ValueGeneratedOnAddOrUpdate()
It's missing .IsConcurrencyToken() so I've added that on myself, but still no exceptions are thrown in situations that should suffer concurrency issues. It instead simply over-writes the data.
Is there something I am missing?
Edit:
I'm using a Database-first approach (so no [Timestamp] or any other annotations), and my DbContext is being injected into a service, configured with services.AddScoped<IPoRepository, PoRepository>() in Startup.cs
It generates a public byte[] VersionCol { get; set; } field in my model, which is right I believe.
In my PoRepository I'm trying to update my Po with the following:
public void SavePo(PoListing poListing) {
Po po;
try {
po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
} catch ( ArgumentNullException ) {
throw new ArgumentNullException( "The PO does not exist." );
}
po.AssignedUserId = poListing.AssignedUserId;
po.VersionCol = poListing.VersionCol;
_context.Entry( po ).State = EntityState.Modified;
_context.SaveChanges();
}
The PoListing is essentially just a part of a Po, so It only has some of it's columns (it's not a table in the database), and it has the VersionCol of the Po when it is first generated. If the PoListing has an older VersionCol than the Po it's based off, then it should give an exception.
Edit2:
This works, but I can't figure out how to make it work without needing to make this second context, and just use the injected context.
public void SavePo(PoListing poListing) {
DbContextOptionsBuilder<TMS_1000Context> options = new DbContextOptionsBuilder<TMS_1000Context>();
options.UseSqlServer( "Server=DEVSQL16;Database=TMS_1000_Dev;Trusted_Connection=True;MultipleActiveResultSets=true" );
TMS_1000Context context1;
try {
po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
} catch ( ArgumentNullException ) {
throw new ArgumentNullException( "The PO does not exist." );
}
using ( context1 = new TMS_1000Context( options.Options ) ) {
po.AssignedUserId = poListing.AssignedUserId;
po.VersionCol = poListing.VersionCol;
context1.Update( po );
context1.SaveChanges();
}
}
Edit3:
This is currently working. Is there another way?
public void SavePo(PoListing poListing) {
Po po;
try {
po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
} catch ( ArgumentNullException ) {
throw new ArgumentNullException( "The PO does not exist." );
}
po.AssignedUserId = poListing.AssignedUserId;
_context.Entry( po ).Property( u => u.VersionCol ).OriginalValue = poListing.VersionCol;
_context.Update( po );
_context.SaveChanges();
}
The reason I believe this is happening is because EF Core tracking cares only if the original values are the same as what's currently in the database, and if they aren't then that's when a concurrency exception is thrown.
Here are 3 fixes I've found.
Change the original value so that it is now different than what exists in the database.
public void SavePo(PoListing poListing) {
Po po;
try {
po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
} catch ( ArgumentNullException ) {
throw new ArgumentNullException( "The PO does not exist." );
}
po.AssignedUserId = poListing.AssignedUserId;
_context.Entry( po ).Property( u => u.VersionCol ).OriginalValue = poListing.VersionCol;
_context.Update( po );
_context.SaveChanges();
}
Get the entity with AsNoTracking(). Now EF Core won't just compare the original values.
public void SavePo(PoListing poListing) {
Po po;
try {
po = _context.Po.AsNoTracking().Where( p => p.Poid == poListing.PoId ).First();
} catch ( ArgumentNullException ) {
throw new ArgumentNullException( "The PO does not exist." );
}
po.AssignedUserId = poListing.AssignedUserId;
po.VersionCol = poListing.VersionCol;
_context.Update( po );
_context.SaveChanges();
}
Detach the entity from the context. Similar function as fix #2
public void SavePo(PoListing poListing) {
Po po;
try {
po = _context.Po.Where( p => p.Poid == poListing.PoId ).First();
} catch ( ArgumentNullException ) {
throw new ArgumentNullException( "The PO does not exist." );
}
po.AssignedUserId = poListing.AssignedUserId;
po.VersionCol = poListing.VersionCol;
_context.Entry( po ).State = EntityState.Detached;
_context.Update( po );
_context.SaveChanges();
}
Related
I am having a problem with repeating previous operations when there is an error in the SaveChanges method of Entity Framework.
Below is the code block
public static int SaveChangesTask(this DbContext db)
{
int result = -1;int countLoop = 0;
bool continueLoop = true;
var modifiedOrAddedEntities = db.ChangeTracker.Entries().Where(a => a.State != EntityState.Detached
&& a.State != EntityState.Unchanged).ToList();
while (continueLoop && countLoop<3)
{
try
{
result= db.SaveChanges();
continueLoop = false;
}
catch(Exception ex)
{
string error = ex.ToSystemException();
if(error.ToLowerInvariant().Contains("ORA-00060".ToLowerInvariant()) || error.ToLowerInvariant().Contains("deadlock"))
{
foreach (var item in modifiedOrAddedEntities)
{
db.Entry(item).State = item.State;
}
countLoop++;
Random rnd = new Random();
System.Threading.Thread.Sleep(rnd.Next(1, 5)* 1000);
}
else
{
throw ex;
}
}
}
return result;
}
But when I want to add old tracking objects to context, Entity Framework Throws Exception like that
"The entity type DbEntityEntry is not part of the model for the current context"
I have created two new fields named "Price" for quote and quote product and I want to update the second every time I update the first.
Here is my code:
protected void ExecutePostAccountUpdateContacts(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
string oldPrice = "";
string newPrice = "";
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
var ServiceContext = new OrganizationServiceContext(service);
ITracingService tracingService = localContext.TracingService;
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
Entity preImageEntity = (context.PreEntityImages != null && context.PreEntityImages.Contains(this.preImageAlias)) ? context.PreEntityImages[this.preImageAlias] : null;
// get the post entity image
Entity postImageEntity = (context.PostEntityImages != null && context.PostEntityImages.Contains(this.postImageAlias)) ? context.PostEntityImages[this.postImageAlias] : null;
if (preImageEntity.Attributes.Contains("Price"))
{
oldPrice = (string)preImageEntity.Attributes["Price"];
}
if (postImageEntity.Attributes.Contains("Price"))
{
newPrice = (string)postImageEntity.Attributes["Price"];
}
if (newPrice != oldPrice)
{
try
{
//Create query to get the related contacts
var res = from c in ServiceContext.CreateQuery("Products")
where c["parentQuoteid"].Equals(entity.Id)
select c;
foreach (var c in res)
{
Entity e = (Entity)c;
e["Price"] = newPrice;
ServiceContext.UpdateObject(e);
}
ServiceContext.SaveChanges();
}
catch (FaultException ex)
{
throw new InvalidPluginExecutionException("An error occurred in the plug-in.", ex);
}
}
}
}
Although you haven't asked a question, your query isn't quite right. So I am assuming your plugin fails when querying for product with a parentquoteid.
Not all linq operators are implemented, also , pass the entity logical name to the create query as a parameter, so instead of Products, just product. There is no out of the box field called parentquoteid, are you missing your custom attribute prefix?
var res = from c in ServiceContext.CreateQuery("product")
where c.GetAttributeValue<Guid>("new_parentquoteid") == entity.Id
select c;
I wrote this simple code to update my database column.
using (HRMSEntities context = new HRMSEntities())
{
TBL_EMPLOYEE dataTicketInsert = new TBL_EMPLOYEE();
dataTicketInsert = context.TBL_EMPLOYEE.Where(x => x.Id == inputEmployeeID).FirstOrDefault();
dataTicketInsert.Ticket = ticketT;
context.SaveChanges();
}
Error Message:
An exception of type 'System.Data.Entity.Validation.DbEntityValidationException' occurred in EntityFramework.dll but was not handled in user code
Additional information: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.
How can I resolve the problem?
Add the following code to your DbContext class, then in the validation error message, you will be able to see the details of the validation problem:
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException ex)
{
var errorMessages = ex.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
var fullErrorMessage = string.Join("; ", errorMessages);
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
}
Reference: https://stackoverflow.com/a/15820506/1845408
I am developing an MVC app.
When I try to updated record it showing error of DBEntityValidation exception,
( beacuse its trying to add record in DB. This is my code)
public JsonResult SavePassword(int EmpId, string Password)
{
try
{
Employee e1 = db.Employees.First(i => i.Id == EmpId);
db.Entry(e1).State = EntityState.Modified;
e1.Password = Password;
db.SaveChanges();
return Json(EmpId);
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"", ve.PropertyName, ve.ErrorMessage);
}
}
throw;
}
}
In exception, it shows that validation msgs, which I have checked while adding new record.
So , I think its trying to add in DB insted of updating.
I've recently discovered AutoMapper for bridging ViewModels and my actual DB objects. I use it in the way decribed here: http://automapper.codeplex.com/wikipage?title=Projection&referringTitle=Home
I've discovered Emit Mapper to :), but I can't find anytning similar to (where I can specify custom projecting rules):
.ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.EventDate.Date))
Thanks in advance!
For the Record this is the best solution that I came across on how to do it:
http://emitmapper.codeplex.com/discussions/259655
Check the solution on the last post. It works really well.
Update: The code for future reference:
public class ExtDefaultMapConfig<TSrc, TDst> : DefaultMapConfig
{
private readonly Dictionary<string, Func<TSrc, object>> _properties = new Dictionary<string, Func<TSrc, object>>();
public ExtDefaultMapConfig<TSrc, TDst> ForMember(string property, Func<TSrc, object> func)
{
if (!_properties.ContainsKey(property))
_properties.Add(property, func);
return this;
}
public ExtDefaultMapConfig<TSrc, TDst> ForMember(Expression<Func<TDst, object>> dstMember, Func<TSrc, object> func)
{
var prop = ReflectionHelper.FindProperty(dstMember);
return ForMember(prop.Name, func);
}
public ExtDefaultMapConfig<TSrc, TDst> Ignore(Expression<Func<TDst, object>> dstMember)
{
var prop = ReflectionHelper.FindProperty(dstMember);
IgnoreMembers<TSrc, TDst>(new[] { prop.Name });
return this;
}
public override IMappingOperation[] GetMappingOperations(Type from, Type to)
{
var list = new List<IMappingOperation>();
list.AddRange(base.GetMappingOperations(from, to));
list.AddRange(
FilterOperations(
from,
to,
ReflectionUtils.GetPublicFieldsAndProperties(to)
.Where(f => _properties.ContainsKey(f.Name))
.Select(
m =>
(IMappingOperation)new DestWriteOperation
{
Destination = new MemberDescriptor(m),
Getter =
(ValueGetter<object>)
(
(value, state) =>
{
Debug.WriteLine(string.Format("Mapper: getting value of field or property {0}", m.Name));
return ValueToWrite<object>.ReturnValue(_properties[m.Name]((TSrc) value));
}
)
}
)
)
);
return list.ToArray();
}
}
class ReflectionHelper
{
public static MemberInfo FindProperty(LambdaExpression lambdaExpression)
{
Expression expression = lambdaExpression;
bool flag = false;
while (!flag)
{
switch (expression.NodeType)
{
case ExpressionType.Convert:
expression = ((UnaryExpression)expression).Operand;
break;
case ExpressionType.Lambda:
expression = ((LambdaExpression)expression).Body;
break;
case ExpressionType.MemberAccess:
MemberExpression memberExpression = (MemberExpression)expression;
if (memberExpression.Expression.NodeType != ExpressionType.Parameter && memberExpression.Expression.NodeType != ExpressionType.Convert)
throw new ArgumentException(string.Format("Expression '{0}' must resolve to top-level member.", lambdaExpression), "lambdaExpression");
return memberExpression.Member;
default:
flag = true;
break;
}
}
return null;
}
public static object GetValue(string property, object obj)
{
PropertyInfo pi = obj.GetType().GetProperty(property);
return pi.GetValue(obj, null);
}
}