I got this message when debugged in catch (Exception e). When User fill in all the information, Address and Payment View will get the SalesOrderID and redirect to Complete View. But it didn't show the Complete when it done.
[HttpPost]
public ActionResult AddressAndPayment(SalesOrderHeader order,Customer customer, Address address ,FormCollection values)
{
ViewBag.PersonType = new SelectList(new[] { "EM", "SC", "VC", "IN" } // work
.Select(x => new { value = x, text = x }),
"value", "text");
try
{
if (string.Equals(values["PromoCode"], PromoCode, StringComparison.OrdinalIgnoreCase) == false)
{
return View(order);
}
else
{
order.AccountNumber = User.Identity.Name;
order.OrderDate = DateTime.Now;
address.ModifiedDate = DateTime.Now; // maybe this error
order.Address.PostalCode = "12345";
//Save Order
BikeDBs.SalesOrderHeaders.Add(order);
try
{
BikeDBs.SaveChanges();
}
catch (DbEntityValidationException e)
{
foreach (var entityValidationErrors in e.EntityValidationErrors)
{
foreach (var validationError in entityValidationErrors.ValidationErrors)
{
Console.WriteLine("Properties: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
}
//Process Order
var cart = ShoppingCart.GetCart(this.HttpContext);
cart.CreateOrder(order);
//cart.CreateOrder(order1);
return RedirectToAction("Complete", new { id = order.SalesOrderID });
}
}
catch (Exception exception)
{
//Invalid - redisplay with errors
return View(order);
}
All I want is when the Order is saved, it will redirect to Complete. But in this case, it's not. And here is Address model:
public partial class Address
{
public Address()
{
this.SalesOrderHeaders = new HashSet<SalesOrderHeader>();
this.SalesOrderHeaders1 = new HashSet<SalesOrderHeader>();
}
public int AddressID { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public int StateProvinceID { get; set; }
public string PostalCode { get; set; }
public System.Guid rowguid { get; set; }
[Required()]
public Nullable<System.DateTime> ModifiedDate { get; set; }
public virtual StateProvince StateProvince { get; set; }
public virtual ICollection<SalesOrderHeader> SalesOrderHeaders { get; set; }
public virtual ICollection<SalesOrderHeader> SalesOrderHeaders1 { get; set; }
}
What's a solution and how to fix it?
You can do this pretty easily by using the ModelState, it should catch it. If it doesn't I added code into your catch block to catch it and display the page again with the errors using ModelState.AddModelError.
[HttpPost]
public ActionResult AddressAndPayment(SalesOrderHeader order,Customer customer, Address address ,FormCollection values)
{
ViewBag.PersonType = new SelectList(new[] { "EM", "SC", "VC", "IN" } // work
.Select(x => new { value = x, text = x }),
"value", "text");
if(ModelState.IsValid)
{
try
{
if (string.Equals(values["PromoCode"], PromoCode, StringComparison.OrdinalIgnoreCase) == false)
{
return View(order);
}
else
{
order.AccountNumber = User.Identity.Name;
order.OrderDate = DateTime.Now;
order.Address.PostalCode = values["PostalCode"];
//Save Order
BikeDBs.SalesOrderHeaders.Add(order);
try
{
BikeDBs.SaveChanges();
}
catch (DbEntityValidationException e)
{
foreach (var entityValidationErrors in e.EntityValidationErrors)
{
foreach (var validationError in entityValidationErrors.ValidationErrors)
{
// If this far add errors to model errors and show view again.
ModelState.AddModelError(validationError.PropertyName, validationError.ErrorMessage);
Console.WriteLine("Properties: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
}
}
return View(order);
}
//Process Order
var cart = ShoppingCart.GetCart(this.HttpContext);
cart.CreateOrder(order);
//cart.CreateOrder(order1);
return RedirectToAction("Complete", new { id = order.SalesOrderID });
}
}
catch (Exception exception)
{
//Invalid - redisplay with errors
return View(order);
}
}
return View(order);
}
For my answer I assume that the properties PostalCode and PersonType are of type string and are defined as not nullable.
I think the error messages you get clearly say what the problem is. The properties PostalCode and PersonType are required, that means they need to have a value other than null.
So when you do not set the properties to a value other than null and you try to save your entity you will get the error messages.
To fix it you will net to set the properties to some values (maybe a default value) or you have to change your EntityModel to specify that these properties are nullable
Related
I'm trying to get the Audit:NET EntityFramework.Core extension to write an AuditLog entry per changed property.
For this purpose I've overidden the EntityFrameworkDataProvider.InsertEvent with a custom DataProvider.
The problem is, using DbContextHelper.Core.CreateAuditEvent to create a new EntityFrameworkEvent returns null.
The reason seems to be, at this point in the code execution DbContextHelper.GetModifiedEntries determines all EF Entries have State.Unmodified, even if they are clearly included in the EventEntry changes.
I'm trying to circumvent CreateAuditEvent by manually creating the contents is impossible due to private/internal properties.
Maybe there is an alternative solution to this problem I'm not seeing, i'm open to all suggestions.
Audit entity class
public class AuditLog
{
public int Id { get; set; }
public string Description { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
public string PropertyName { get; set; }
public DateTime AuditDateTime { get; set; }
public Guid? AuditIssuerUserId { get; set; }
public string AuditAction { get; set; }
public string TableName { get; set; }
public int TablePK { get; set; }
}
Startup configuration
Audit.Core.Configuration.Setup()
.UseCustomProvider(new CustomEntityFrameworkDataProvider(x => x
.AuditEntityAction<AuditLog>((ev, ent, auditEntity) =>
{
auditEntity.AuditDateTime = DateTime.Now;
auditEntity.AuditAction = ent.Action;
foreach(var change in ent.Changes)
{
auditEntity.OldValue = change.OriginalValue.ToString();
auditEntity.NewValue = change.NewValue.ToString();
auditEntity.PropertyName = change.ColumnName;
}
}
Custom data provider class
public class CustomEntityFrameworkDataProvider : EntityFrameworkDataProvider
{
public override object InsertEvent(AuditEvent auditEvent)
{
var auditEventEf = auditEvent as AuditEventEntityFramework;
if (auditEventEf == null)
return null;
object result = null;
foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
{
if (entry.Changes == null || entry.Changes.Count == 0)
continue;
foreach (var change in entry.Changes)
{
var contextHelper = new DbContextHelper();
var newEfEvent = contextHelper.CreateAuditEvent((IAuditDbContext)auditEventEf.EntityFrameworkEvent.GetDbContext());
if (newEfEvent == null)
continue;
newEfEvent.Entries = new List<EventEntry>() { entry };
entry.Changes = new List<EventEntryChange> { change };
auditEventEf.EntityFrameworkEvent = newEfEvent;
result = base.InsertEvent(auditEvent);
}
}
return result;
}
}
Check my answer here https://github.com/thepirat000/Audit.NET/issues/323#issuecomment-673007204
You don't need to call CreateAuditEvent() you should be able to iterate over the Changes list on the original event and call base.InsertEvent() for each change, like this:
public override object InsertEvent(AuditEvent auditEvent)
{
var auditEventEf = auditEvent as AuditEventEntityFramework;
if (auditEventEf == null)
return null;
object result = null;
foreach (var entry in auditEventEf.EntityFrameworkEvent.Entries)
{
if (entry.Changes == null || entry.Changes.Count == 0)
continue;
// Call base.InsertEvent for each change
var originalChanges = entry.Changes;
foreach (var change in originalChanges)
{
entry.Changes = new List<EventEntryChange>() { change };
result = base.InsertEvent(auditEvent);
}
entry.Changes = originalChanges;
}
return result;
}
Notes:
This could impact performance, since it will trigger an insert to the database for each column change.
If you plan to use async calls to DbContext.SaveChangesAsync, you should also implement the InsertEventAsync method on your CustomDataProvider
The Changes property is only available for Updates, so if you also want to audit Inserts and Deletes, you'll need to add the logic to get the column values from the ColumnValues property on the event
I'm trying to detect duplicate a key insertion error in my code written by means of C# and MongoDB.Driver.
Is it correct error handling for this case? (there is a unique index on EntityId column)
public class Entity
{
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
public string EntityId { get; set; }
}
...
public async Task<string> CreateEntityAsync(string entityId)
{
var entity = new Entity
{
EntityId = entityId,
};
try
{
await Collection.InsertOneAsync(peer);
}
//according to https://docs.mongodb.com/manual/core/index-unique error 11000 should be raised.
catch (MongoWriteException ex) when (GetErrorCode(ex) == 11000)
{
//custom error handling
}
return entity.Id;
}
private int GetErrorCode(MongoWriteException ex)
{
return (ex.InnerException as MongoBulkWriteException)?.WriteErrors.FirstOrDefault()?.Code ?? 0;
}
I need keep the same original Id (GUID) after save data because is a replication job. (SQL -> SQL remote). Then, the model can not be changed. After SaveChanges() EF insert a new random Guid as Id, then this changes my original object, and do not want that. A compact sample:
class EFInsertTest
{
public void InsertTest()
{
var id = new Guid("D75C887D-BF25-E611-943B-080027BA87E8"); // dummy
var entity = new Something { Id = id, Name = "ELENOR" };
using (var db = new SomethingContext())
{
db.Things.Add(entity);
db.SaveChanges();
// TEST
if (db.Things.Find(id) != null)
{
Console.WriteLine($"Great! Expected behavior");
}
else
{// run this:
Console.WriteLine($"Failed! Id has another value");
}
Console.ReadKey();
// SQL hard code (works fine)
db.Database.ExecuteSqlCommand($"INSERT INTO [Something] VALUES('{id}', '{entity.Name}')");
db.SaveChanges();
// TEST
if (db.Things.Find(id) != null)
{
Console.WriteLine($"Great! Expected behavior");
}
else
{
Console.WriteLine($"Failed! Id has another value");
}
Console.ReadKey();
}
}
}
public class SomethingContext : DbContext
{
public virtual DbSet<Something> Things { get; set; }
}
public class Something
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Name { get; set; }
}
On escalado, throws the exception. It throws with or wihtout Include.
static void Main(string[] args)
{
try
{
using (var context = new CKContext())
{
var servReprosWithIncludes = context.ServicioRepro
.Include(p => p.Categoria)
.ToList();
var escalado = context.EscaladoPrecio
//.Include(p => p.Servicio)
.ToList();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
InvalidOperationException: The value of a property that is part of an object's key does not match the corresponding property value stored in the ObjectContext. This can occur if properties that are part of the key return inconsistent or incorrect values or if DetectChanges is not called after changes are made to a property that is part of the key.
The mapping of EscaladoPrecio:
public class EscaladoPrecioMapping : EntityTypeConfiguration<EscaladoPrecio>
{
public EscaladoPrecioMapping()
{
base.HasKey(p => new { p.Desde, p.Hasta, p.ServicioReproId });
base.HasRequired(p => p.Servicio)
.WithMany()
.HasForeignKey(p => p.ServicioReproId);
base.ToTable("PreciosServicioReprografia");
}
}
The entity ServicioRepro is a part from TPT hierarchy. Looks like:
public class ServicioRepro : Producto
{
public bool IncluirPrecioClick { get; set; }
public bool IncluirPrecioPapel { get; set; }
public bool HayPapel { get; set; }
public bool HayImpresion { get; set; }
public bool PrecioPorVolumen { get; set; }
//public virtual ICollection<EscaladoPrecio> EscaladoPrecio { get; set; }
public virtual CategoriaServicioRepro Categoria { get; set; }
public virtual ServicioReproFacturacionType ServicioReproFacturacionType { get; set; }
}
On this entity you can't see the key, because the base entity Producto have it.
The entity EscaladoPrecio have 3 PK: desde, hasta and Servicio. Servicio is PK and FK.
The entity looks like (methods, overrides and members have been removed to reduce the code):
public class EscaladoPrecio : IComparable<EscaladoPrecio>, IComparable<int>, IComparable, IEntity
{
#region Declarations
private int _desde;
private int _hasta;
private double _precio;
private int _cada;
#endregion Declarations
#region Constructor
public EscaladoPrecio()
: this(1, 1, 0, 0)
{ }
public EscaladoPrecio(int desde, int hasta, double precio)
: this(desde, hasta, precio, 0)
{ }
public EscaladoPrecio(int desde, int hasta, double precio, int cada)
{
_desde = desde;
_hasta = hasta;
_precio = precio;
_cada = cada;
}
#endregion Constructor
#region Properties
public int Desde
{
get
{
return _desde;
}
set
{
_desde = value;
}
}
public int Hasta
{
get
{
return _hasta;
}
set
{
_hasta = value;
}
}
public double Precio
{
get
{
return _precio;
}
set
{
_precio = value;
}
}
public int Cada
{
get
{
return _cada;
}
set
{
_cada = value;
}
}
#endregion Properties
private int _ServicioReproId;
public int ServicioReproId
{
get
{
if (Servicio != null)
{
_ServicioReproId = Servicio.Id;
return Servicio.Id;
}
else
return 0;
}
set
{
_ServicioReproId = value;
}
}
public virtual ServicioRepro Servicio { get; set; }
}
Why throws the exception?
Why are you doing this:
public int ServicioReproId
{
get
{
if (Servicio != null)
{
_ServicioReproId = Servicio.Id;
return Servicio.Id;
}
else
return 0;
}
set
{
_ServicioReproId = value;
}
}
Your part of the key property ServicioReproId is returning 0 here potentially although it has been loaded (and stored in the context) with a value != 0 (probably). I think this part of the exception is refering to this problem: "This can occur if properties that are part of the key return inconsistent or incorrect values."
Better leave it an automatic property:
public int ServicioReproId { get; set; }
try to initialice his virtual property in the constructor of the class EscaladoPrecio()
I have a demo class "User" like the following:
public partial class User {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[StringLength(30)]
[Required]
public string LoginName { get; set; }
[StringLength(120)]
[Required]
[DataType(DataType.Password)]
public string Pwd { get; set; }
[StringLength(50)]
public string Phone { get; set; }
[StringLength(100)]
public string WebSite { get; set; }
...
...
}
As you can see, "LoginName" and "Pwd" are "Required".
Some time , I only want to update user's "WebSite" , So I do like this:
public void UpdateUser(User user , params string[] properties) {
this.rep.DB.Users.Attach(user);
this.rep.DB.Configuration.ValidateOnSaveEnabled = false;
var entry = this.rep.DB.Entry(user);
foreach(var prop in properties) {
var entProp = entry.Property(prop);
//var vas = entProp.GetValidationErrors();
entProp.IsModified = true;
}
this.rep.DB.SaveChanges();
this.rep.DB.Configuration.ValidateOnSaveEnabled = true;
}
Parameter "user" like this:
new User(){
ID = 1,
WebSite = "http://www.stackoverflow.com"
}
Notice , I don't specify "LoginName" and "Pwd"
This function can work fine , but I wouldn't set ValidateOnSaveEnabled to false.
Is there any way only validate "WebSite" when ValidateOnSaveEnabled is true?
Thanks.
As I know validation executed in SaveChanges always validates the whole entity. The trick to get selective validation for property is commented in your code but it is not part of the SaveChanges operation.
I get a solution.
First define PartialValidationManager:
public class PartialValidationManager {
private IDictionary<DbEntityEntry , string[]> dics = new Dictionary<DbEntityEntry , string[]>();
public void Register(DbEntityEntry entry , string[] properties) {
if(dics.ContainsKey(entry)) {
dics[entry] = properties;
} else {
dics.Add(entry , properties);
}
}
public void Remove(DbEntityEntry entry) {
dics.Remove(entry);
}
public bool IsResponsibleFor(DbEntityEntry entry) {
return dics.ContainsKey(entry);
}
public void ValidateEntity(DbEntityValidationResult result) {
var entry = result.Entry;
foreach(var prop in dics[entry]){
var errs = entry.Property(prop).GetValidationErrors();
foreach(var err in errs) {
result.ValidationErrors.Add(err);
}
}
}
}
2, Add this Manager to My DbContext:
public class XmjDB : DbContext {
public Lazy<PartialValidationManager> PartialValidation = new Lazy<PartialValidationManager>();
protected override System.Data.Entity.Validation.DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry , IDictionary<object , object> items) {
if(this.PartialValidation.Value.IsResponsibleFor(entityEntry)) {
var result = new DbEntityValidationResult(entityEntry , new List<DbValidationError>());
this.PartialValidation.Value.ValidateEntity(result);
return result;
} else
return base.ValidateEntity(entityEntry , items);
}
...
...
Update Method :
public void UpateSpecifyProperties(T t, params string[] properties) {
this.DB.Set<T>().Attach(t);
var entry = this.DB.Entry<T>(t);
this.DB.PartialValidation.Value.Register(entry , properties);
foreach(var prop in properties) {
entry.Property(prop).IsModified = true;
}
this.DB.SaveChanges();
this.DB.PartialValidation.Value.Remove(entry);
}
Ok, it work fine.