Update failed in generic repository with EF - entity-framework

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

Related

REST API - CreatedAtRoute method doesn't return a value

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:)

linq to entity on dbcontext.set<>

I have multiple databases, 1 common and n company db. I use code first with one migration for common and one for the companies db.
I have a base context this is inherit to 2 contexts (common, company). I try to use only the base context and remove the specified contexts, so far no problem.
My problem is following, if i try to use linq on context.Set<> then I get an InvalidOperationException "The entity type NOCompany is not part of the model for the current context".
using (NOContext db = new NOContext(connection)) {
var dbset = db.Set<NOCompany>()
.Where(company => (company.Deleted == null) || (company.Deleted == false));
foreach (var item in dbset) {
System.Diagnostics.Debug.WriteLine(item.Matchcode);
}
}
if I use this
using (NOContext db = new NOCommonContext(connection)) {
var dbset = db.Set<NOCompany>()
.Where(company => (company.Deleted == null) || (company.Deleted == false));
foreach (var item in dbset) {
System.Diagnostics.Debug.WriteLine(item.Matchcode);
}
}
then it works fine. Where is the problem?
Following excerpt from the classes
public class NOContext : DbContext, INOContext
{
public NOContext() { }
public NOContext(string connection) : base(connection) { }
#region - override DbContext -
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<EntityBase>()) {
DateTime currentDateTime = DateTime.Now;
var entity = entry.Entity;
if (entry.State == EntityState.Added) {
entity.CreateDate = currentDateTime;
entity.CreateId = NOEngine.SessionInfo.CurrentUserId;
} else if (entry.State == EntityState.Deleted) {
entry.State = EntityState.Modified;
entity.Deleted = true;
entity.DeletedDate = currentDateTime;
entity.DeletedId = NOEngine.SessionInfo.CurrentUserId;
}
entity.ModifiedDate = currentDateTime;
entity.ModifiedId = NOEngine.SessionInfo.CurrentUserId;
}
return base.SaveChanges();
}
#endregion
//database methods
}
Then I have 2 specified context
public class NOCommonContext : NOContext
{
public const string CommonCatalog = "NOCommonDb";
public NOCommonContext() { }
public NOCommonContext(string connection) : base(connection) { }
#region - DbSets -
public virtual DbSet<NOUser> Users { get; set; }
public virtual DbSet<NOCompany> Companies { get; set; }
public virtual DbSet<NOConfig> AppConfiguration { get; set; }
#endregion //DbSets
}
and
public partial class NOAppContext : NOContext
{
public NOAppContext() { }
public NOAppContext(string connection) : base(connection) { }
#region - DbSets -
public virtual DbSet<BPCard> BPCards { get; set; }
public virtual DbSet<BPContact> BPContacts { get; set; }
public virtual DbSet<HRCard> HRCards { get; set; }
#endregion //DbSets
}

EntityValidationException is thrown when saving my 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

entity-framework get entityreference value

I have an entity object which is connected to another entities.
I want to loop through all entity properties , if the property is String then do something with the value.
If the property is EntityReference, I want to get it's value (it has only one), and do something with the value as well.
I was able to determine if the property is string or EntityReference.
I get the String value by -
value = typeof(entity).GetProperty(property.Name).GetValue(request, null);
but how do I get the value of an entityreference ?
Just trace the property tree.
You have the first step. repeat for lower properties.
var TopLevelProp = poco.GetType().GetProperty(property.Name).GetValue(poco, null);
var LowerProp = TopLevelProp.GetType().GetProperty("aLowerPropName").GetValue(TopLevelProp, null);
although you tagged this EF. What did you mean by entity reference ?
edit: in the hope i have covered the entity and its key question
Here is a sample Repository covering EF Context and entity access. See the Entity field and Entity KEY field methods...
public class Repository<T> : IRepositoryEF<T> where T : BaseObject {
public RepositoryEF(DbContext context) { Context = context; }
public DbEntityEntry<T> Entry(T entity) { return Context.Entry(entity); }
public DbSet<T> EntityDbSet() { return Context.Set<T>(); }
public ObjectContext ObjectContext { get { return ((IObjectContextAdapter) this.Context).ObjectContext; } }
public DbContext Context { get; protected set; }
public EntityState GetEntityState(object entity) { return Context.Entry(entity).State; }
public ObjectSet<T> GetObjectSet() { return ObjectContext.CreateObjectSet<T>(); }
public IList<string> GetEntityFields() {
var entityFields = GetObjectSet().EntitySet.ElementType.Properties;
return entityFields.Select(e => e.Name).ToList();
}
public string[] GetEntityKeyFields() { return GetObjectSet().EntitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray(); }
public EntityKey GetEntityKey(T entity) {
if (entity == null) {
return null;
}
return ObjectContext.CreateEntityKey(GetObjectSet().EntitySet.Name, entity);
}
public string GetEntityKeyAsString(T entity) {
if (entity == null) {
return string.Empty;
}
var eK = GetEntityKey(entity);
var keyAsString = eK.EntityKeyValues.Aggregate("", (current, keyField) => current + keyField.Key + "=" + keyField.Value + ",");
return keyAsString;
}
}
If you want to get all the metadata in the Context:
ObjectContext objContext = ((IObjectContextAdapter)context).ObjectContext;
MetadataWorkspace workspace = objContext.MetadataWorkspace;
IEnumerable<EntityType> managedTypes = workspace.GetItems<EntityType>(DataSpace.OSpace);
You can go to town on the meta data. see all enums values in DataSpace to get at various parts of the model

Entity Framework Generic Repository

I am writing a generic repository to be used for my every model CRUD operation using entity framework CTP5 as following:
public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : BaseEntity
{
public DbContext Context { get; set; }
public void Insert(TEntity entity)
{
if (Context.Entry<TEntity>(entity).State == EntityState.Detached)
{
Context.Set<TEntity>().Attach(entity);
}
Context.Set<TEntity>().Add(entity);
Context.SaveChanges();
}
public void Delete(int id)
{
TEntity entity = Context.Set<TEntity>().Find(id);
if (Context.Entry<TEntity>(entity).State == EntityState.Detached)
{
Context.Set<TEntity>().Attach(entity);
}
Context.Set<TEntity>().Remove(entity);
Context.SaveChanges();
}
public void Delete(TEntity entity)
{
Context.Set<TEntity>().Remove(entity);
Context.SaveChanges();
}
public void Update(TEntity entity)
{
TEntity status = Context.Set<TEntity>().Find(entity.Id);
status = entity;
Context.SaveChanges();
}
public TEntity GetFirst()
{
var entity = Context.Set<TEntity>().FirstOrDefault();
if (entity == null) return null;
return entity;
}
public TEntity GetNext(int id)
{
var entity = (from u in Context.Set<TEntity>()
where u.Id > id
select u).FirstOrDefault();
if (entity == null) return null;
return entity;
}
public TEntity GetPrevoius(int id)
{
var entity = (from u in Context.Set<TEntity>()
where u.Id < id
orderby u.Id descending
select u).FirstOrDefault();
if (entity == null) return GetFirst();
return entity;
}
public TEntity GetLast()
{
var entity = (Context.Set<TEntity>().OrderByDescending(u => u.Id)).FirstOrDefault();
if (entity == null) return null;
return entity;
}
public TEntity GetById(int id)
{
return Context.Set<TEntity>().Find(id);
}
public int GetMaxId()
{
var max = Context.Set<TEntity>().Count()+ 1;
return max;
}
}
everything works fine but Update method which nither doesnt generate any error nor save any changes back to database.
Can anybody guid me how to solve this issue?
You can use CurrentValues.SetValues:
public void Update(TEntity entity)
{
TEntity status = Context.Set<TEntity>().Find(entity.Id);
Context.Entry(status).CurrentValues.SetValues(entity);
Context.SaveChanges();
}
It updates scalar and complex properties but not navigation properties.
You're overwriting the variable status with a totally new object, taking the one from the database out of scope, but not actually modifying the object that is attached to the context, which is what you'll want to do.
The only way I can think off the top of my head is to use reflection to read all the properties of the type, and assign the values to the original object based on the new one, something like:
foreach (var prop in typeof(TEntity).GetProperties())
{
prop.SetValue(status, prop.GetValue(entity, null), null);
}