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;
}
Related
I'm strugglish with adding feature for my controller. While adding new item, receving the error like: "An error occurred while updating the entries. See the inner exception for details."
I debugged it, and understood ProductDetailIs is null and here is the issue. But, can not figure out how to mend the problem.
Here is the DTO models:
public class WishlistItemDto
{
public int Id { get; set; }
public string CustomerId { get; set; }
public ProductDetailsDtoWithPrimaryImage ProductDetails { get; set; }
public int Quantity { get; set; }
}
public class WishListItemCreationDto
{
public string CustomerId { get; set; }
public int ProductDetailId { get; set; }
public int Quantity { get; set; }
}
Controller:
[HttpPost]
public async Task<IActionResult> Add(WishListItemCreationDto wishListItemDto)
{
var itemAdd = _mapper.Map<WishlistItemDto>(wishListItemDto);
var itemCreated = await _wishListItemService.AddAsync(itemAdd);
return CreatedAtAction(nameof(GetId), new { id = itemCreated.Id }, wishListItemDto);
}
Service:
public async Task<WishlistItemDto> AddAsync(WishlistItemDto item)
{
var entity = _mapper.Map<WishlistItem>(item);
await _wishListItemRepository.AddAsync(entity);
return _mapper.Map<WishlistItemDto>(entity);
}
Repository:
public async Task<WishlistItem> AddAsync(WishlistItem item)
{
await _context.Set<WishlistItem>().AddAsync(item);
await _context.SaveChangesAsync();
return item;
}
This line here:
var itemAdd = _mapper.Map<WishlistItemDto>(wishListItemDto);
your "wishListItemDto" is passed in as a 'WishListItemCreationDto' which contains only a ProductDetailsId. Automapper will have no way of knowing how to convert that into a ProductDetailsDtoWithPrimaryImage.
Typically for something like this where you pass an reference ID you would compose your entity by either populating a FK or loading the referenced entity. Your existing service and repository patterns will complicate your final solution. From what I can see from your example I'd look at creating an AddAsync method that accepts the WishListItemCreationDto:
public async Task<WishlistItemCreationDto> AddAsync(WishlistItemCreationDto item)
{
var entity = _mapper.Map<WishlistItem>(item);
var productDetails = _productDetailsRepository.GetById(item.ProductDetailsId);
entity.ProductDetails = productDetails;
await _wishListItemRepository.AddAsync(entity);
return _mapper.Map<WishlistItemDto>(entity);
}
Without the added abstraction complexity of the Service and Repository the add operation can be a whole lot simpler:
[HttpPost]
public async Task<IActionResult> Add(WishListItemCreationDto wishListItemDto)
{
// or better, use an injected dependency to the Context...
// TODO: add applicable exception handling.
using(var context = new AppDbContext())
{
var item = _mapper.Map<WishlistItem>(wishListItemDto);
var productDetails = context.ProductDetails.Single(x => x.ProductDetailsId == wishListItemDto.ProductDetailsId);
item.ProductDetails = productDetails;
context.SaveChanges();
return CreatedAtAction(nameof(GetId), new { id = itemCreated.Id }, wishListItemDto);
}
}
I'm building some REST API server in .NET Core and using Postman software to test it. I have a problem with POST method which doesn't return me any value ("Could not get any response") when I try to perform second Add operation on my DBContext class inside CreateUser method. My code :
UsersController :
[Produces("application/json")]
[Route("api/[controller]")]
public class UsersController : Controller
{
private readonly DBContext _context;
#region CONSTRUCTOR
public UsersController(DBContext context)
{
_context = context;
}
#endregion
#region HTTP GET
// GET: api/users || api/users?cardnr=xxx
[HttpGet]
public async Task<IActionResult> GetUsers(string cardNr)
{
if (String.IsNullOrEmpty(cardNr))
{
try
{
var users = await _context.Users.ToListAsync();
if (users.Any())
{
return Json(users);
}
else
{
return NotFound();
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
else
{
try
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.Cards.Any(c => c.CardNumber.Equals(cardNr)));
if (user == null)
{
return NotFound();
}
else
{
return new ObjectResult(user);
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
}
//GET: api/users/1
[HttpGet("{id}", Name = "GetUserByID")]
public async Task<IActionResult> GetUserByID(Int32 id)
{
try
{
var user = await _context.Users.FirstOrDefaultAsync(u => u.IDUser == id);
if (user == null)
{
return NotFound();
}
else
{
return new ObjectResult(user);
}
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
#endregion
#region HTTP POST
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] Models.User userToCreate, string userGroupID)
{
if (userToCreate == null)
{
return BadRequest();
}
else
{
try
{
_context.Users.Add(userToCreate);
int parsingResult;
// if user passed userGroupID
if (userGroupID != null)
{
// parsing if userGroupID is a number
if (!int.TryParse(userGroupID, out parsingResult))
{
return BadRequest();
}
else
{
// if client want to assign a new user to some group
if (parsingResult > 0)
{
// creating new record in UserGroup table - assigning a user to group
var userGroup = new Models.UserGroup();
_context.Entry(userGroup).Property("IDGroup").CurrentValue = parsingResult;
_context.Entry(userGroup).Property("IDUser").CurrentValue = userToCreate.IDUser;
_context.UserGroups.Add(userGroup); // NOTE HERE
}
}
}
await _context.SaveChangesAsync();
return CreatedAtRoute("GetUserByID", new { id = userToCreate.IDUser }, userToCreate);
}
catch (Exception ex)
{
Helpers.ExceptionLogger.LogException(ex);
return StatusCode(500);
}
}
}
#endregion
}
User model :
public class User
{
[Key]
public int IDUser { get; set; }
[Required]
public string Name { get; set; }
public List<UserGroup> UsersGroups { get; set; }
}
UserGroup model :
public class UserGroup
{
public Group Group { get; set; }
public User User { get; set; }
}
DBContext class :
public class DBContext : DbContext
{
public DBContext(DbContextOptions<DBContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// shadow property - foreign key
modelBuilder.Entity<UserGroup>()
.Property<int>("IDUser");
// shadow property - foreign key
modelBuilder.Entity<UserGroup>()
.Property<int>("IDGroup");
modelBuilder.Entity<UserGroup>()
.HasKey( new string[]{ "IDUser", "IDGroup" });
modelBuilder.Entity<UserGroup>()
.HasOne(ug => ug.Group)
.WithMany(g => g.UsersGroups)
.HasForeignKey("IDGroup");
modelBuilder.Entity<UserGroup>()
.HasOne(ug => ug.User)
.WithMany(u => u.UsersGroups)
.HasForeignKey("IDUser");
base.OnModelCreating(modelBuilder);
}
public DbSet<Group> Groups { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<UserGroup> UserGroups { get; set; }
}
The problem lies in HttpPost method in UsersController.
When I do "normal" POST and pass JSON object which contain a user to add without assigning it to group (empty userGroupID parameter) everything is ok - user gets added to the DataBase and Postman returns me a user with its ID.
screen :
and when I try to add a new user but with adding it to specific group I always get an error :
screen :
Even despite that error new user gets properly added to DB and associated with its group (record gets added to UserGroup table; UserGroup is join table between Users and Groups table). So I have proper data in my DB but I always get this error and I can't return new added user to client who called API and can't get his ID. Am I doing something wrong in my CreateUser method ?
UPDATE :
I have added a comment line in "NOTE HERE" in CreateUser method in UsersController. If I comment whole this line I don't get an error from Postman but obviously I don't get my User associated with its group (I don't get new record added to UserGroup join table). So it seems like another Add method on context object causing an error ... Does it make sense ?
Did you try to debug it?
Set a breakpoint on the row:
if (userToCreate == null)
Send again the request with Postman and debug your app. There you can see what and where it goes wrong.
Please let me know how it is going so I know how can I help you:)
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; }
}
I've created my generic repository based on this codeproject sample . Update() method isn't working (without any error). when I add this line of code:
this.context.Entry(entity).State = EntityState.Modified;
this error occured:
Attaching an entity of type 'MySolution.DAL.Event' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
what should I do?
All of my codes are here:
context:
public abstract class BaseEntity
{
public Int64 ID { get; set; }
public DateTime AddedDate { get; set; }
public DateTime ModifiedDate { get; set; }
public string IP { get; set; }
}
public class MyContext:DbContext
{
public MyContext()
: base("MyConnectionString")
{
base.Configuration.LazyLoadingEnabled = false;
base.Configuration.ProxyCreationEnabled = false;
base.Configuration.ValidateOnSaveEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
.Where(type => !String.IsNullOrEmpty(type.Namespace))
.Where(type => type.BaseType != null && type.BaseType.IsGenericType
&& type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));
foreach (var type in typesToRegister)
{
dynamic configurationInstance = Activator.CreateInstance(type);
modelBuilder.Configurations.Add(configurationInstance);
}
base.OnModelCreating(modelBuilder);
}
public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity
{
return base.Set<TEntity>();
}
//public DbSet<Event> Events { get; set; }
}
Repository:
public class Repository<T> where T : BaseEntity
{
private readonly MyContext context;
private IDbSet<T> entities;
string errorMessage = string.Empty;
public Repository(MyContext context)
{
this.context = context;
}
public T GetById(object id)
{
return this.Entities.Find(id);
}
public T Insert(T entity)
{
try
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
var savedEntity= this.Entities.Add(entity);
this.context.SaveChanges();
return savedEntity;
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
errorMessage += string.Format("Property: {0} Error: {1}",
validationError.PropertyName, validationError.ErrorMessage) + Environment.NewLine;
}
}
throw new Exception(errorMessage, dbEx);
}
}
public void Update(T entity)
{
try
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
//this.context.Entry(entity).State = EntityState.Modified;not worked
//this.context.Entry(entity).CurrentValues.SetValues(entity);not worked
this.context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
errorMessage += Environment.NewLine + string.Format("Property: {0} Error: {1}",
validationError.PropertyName, validationError.ErrorMessage);
}
}
throw new Exception(errorMessage, dbEx);
}
}
public void Delete(T entity)
{
try
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
this.Entities.Remove(entity);
this.context.SaveChanges();
}
catch (DbEntityValidationException dbEx)
{
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
errorMessage += Environment.NewLine + string.Format("Property: {0} Error: {1}",
validationError.PropertyName, validationError.ErrorMessage);
}
}
throw new Exception(errorMessage, dbEx);
}
}
public virtual IQueryable<T> Table
{
get
{
return this.Entities;
}
}
private IDbSet<T> Entities
{
get
{
if (entities == null)
{
entities = context.Set<T>();
}
return entities;
}
}
}
You can use
this.context.Entry(entity).State = EntityState.Modified
then
when you update an entity you can use
public void Edit(Post post)
{
var postInDb=DBReadWrite<Post>().GetById(post.ID);
postInDb.ModifiedProp=post.ModifiedProp;
DBReadWrite<Post>().Update(postInDb);
}
if you dont want this you can use reflectionType yuo,,ou can catch keyattribute in repository and find entity in db and modified.
your code not works because EF is thinking you add an entity and it says I have same entity
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