The property 'ID" is part of the object's key on INSERT - c#

We have to transfer data from one database to another. So I tried to write a program, which reads tables from the old database, create Entities and store them afterwards in the new database. At the beginning it worked very good. I tried to read only one table and transfer it to the new one. Now i receive the following error:
"The property 'Id' is part of the object's key information and cannot
be modified.
No I dont get rid of that error. Even if I try to get back to the first implementation (which worked like a charm).Here I have the definition of the Table:
Table definition
And here the code:
class MappingUtility
{
public static IEnumerable<Nation> MapNation(DataTable table, IModelFactoryService service)
{
IEnumerable<DataRow> rows = table.AsEnumerable();
Nation nat = service.Create<Nation>();
foreach(var nation in rows)
{
nat.Id = (System.Guid)nation.ItemArray[0];
nat.HVCode = (string)nation.ItemArray[1];
nat.Kurzbezeichung = (string)nation.ItemArray[2];
nat.KFZ = (string)nation.ItemArray[3];
nat.iso_a2 = (string)nation.ItemArray[4];
nat.iso_a3 = (string)nation.ItemArray[5];
nat.iso_n3 = (string)nation.ItemArray[6];
nat.Vollbezeichung = (string)nation.ItemArray[7];
nat.Updated = DateTime.Now;
nat.Created = DateTime.Now;
yield return nat;
}
}
}
using (var da = new SqlDataAdapter("SELECT * FROM NATION", "....."))
{
var table = new DataTable();
da.Fill(table);
using (var context = new DAtenbankContext())
{
int i = 0;
foreach (var nation in MappingUtility.MapNation(table, ef6))
{
Debug.WriteLine(i++);
if (context.Nation.Where(p => p.Id == nation.Id).FirstOrDefault() == null)
{
try
{
context.Entry(nation).State = EntityState.Added;
context.SaveChanges();
}
catch(DbEntityValidationException ex)
{
Debug.WriteLine("");
}
catch (DbUpdateException ex)
{
Debug.WriteLine("There where some duplicate columns in the old table.");
Debug.WriteLine(ex.StackTrace);
}
}
}
}
}
Note: The id is not autogenerated. If I try to create only one Nation at a time i can insert it. Even with this for loop I insert one nation, at the second iteration I get the error.

I suspect that you're operating on the same instance of Nation with every iteration of the loop. It appears that you only ever create one instance and then modify it over time. Entity Framework is trying to track that instance, so modifying the key is confusing it.
Move the instantiation into the loop so that you're creating new instances:
IEnumerable<DataRow> rows = table.AsEnumerable();
foreach(var nation in rows)
{
Nation nat = service.Create<Nation>();
// ...
yield return nat;
}

Related

SaveChanges problem in N-Tier Architecture

Normally, with MVC I use db.savechanges() method after I do some processes. But check the below code when I use N-Tier Architecture in everyloop its gonna insert in this way but I dont want it. I have to check all the items first. If there is no problem then I have to insert it all together.
foreach (var item in mOrderList)
{
MOrder mOrder = new MOrder();
mOrder.StatusAdmin = false;
mOrder.Date = DateTime.Now;
mOrder.StatusMVendor = "Sipariş alındı.";
mOrder.HowMany = item.HowMany;
mOrder.MBasketId = item.MBasketId;
mOrder.MProductId = item.MProductId;
mOrder.MVendorId = item.MVendorId;
mOrder.WInvestorId = item.WInvestorId;
MProduct mprostock = _imProductService.GetMProductById(item.MProductId);
if (mprostock.Stock<=0)
{
return ReturnErrorAndSuccess(HttpStatusCode.NotFound, "MProduct", mprostock.Name + " ürününde stok kalmadığı için işlem tamamlanamadı.");
}
_imOrderService.InsertMOrder(mOrder);
}
all you have to do is:
first you should define a method that get list of mProductId and then return list of MProduct.
after that you should check if there is any record with Stock<=0 then return your error.
-also for your insert you should define a method that get list of MOrder and return appropriate datatype for example Boolean.
public List<MProduct> GetMProductByIds(List<MProductId> mProductId)
{
//getting record code
}
public bool AddMOrder(List<MOrder> mOrder)
{
//inserting record code
}

How to loop through MultiSelectList posted values and insert each into a new database row in ASP.Net MVC 5?

I've visited every search result I could find but I'm stuck.
I'm working on a "dvd store" website using ASP.Net MVC 5, and I'm having some difficulty with inserting MultiSelectList values into the database. The database structure has a many-to-many table which stores the movie Id, genre Id, and a primary key. There's also a movie table with fields like movie title, cost, image path, director, rating, etc.
My insert logic works for putting data into the movies table, but I have a multiselectlist in my Create view which is populated from a list of movie genres in the database. When I select one list item, the ID inserts into the moviegenre table just fine. When I select more than one, only one ID is inserted. I'd like to insert a new row for each selection, with the movie ID and the genre ID (i.e. if 3 genres are selected, create 3 new rows with the same movie id but different genre id's for each row).
How can I iterate through the posted MultiSelectList data and insert a new row for each value?
This is the code in my View:
#Html.ListBoxFor(r => r.CMovie.GenreId, new MultiSelectList(Model.CGenreList, "Id", "Description"), new { #class = "form-control" })
My Controller:
[HttpPost]
public ActionResult Create(MovieGenresDirectorsRatings mgdr) // The ViewModel
{
try
{
mgdr.CMovie.Insert();
return RedirectToAction("Index");
}
catch (Exception ex)
{
throw ex;
return View(mgdr);
}
}
The ViewModel:
public class MovieGenresDirectorsRatings
{
public IEnumerable<int> GenreId { get; set; }
public CGenreList CGenreList { get; set; }
public CDirectorList CDirectorList{ get; set; }
public CFormatList CFormatList { get; set; }
public CRatingList CRatingList { get; set; }
public CGenre CGenre { get; set; }
public CMovie CMovie { get; set; }
}
And my Insert logic in the Model:
public void Insert()
{
using (myEntities dc = new myEntities())
{
try
{
tblMovie movie = new tblMovie();
// Add movie to tblMovie
movie.Id = 1;
if (dc.tblMovies.Any())
movie.Id = dc.tblMovies.Max(p => p.Id) + 1;
this.Id = movie.Id;
movie.Title = this.Title;
movie.Description = this.Description;
movie.ImagePath = this.ImagePath;
movie.Cost = this.Cost;
movie.RatingId = this.RatingId;
movie.FormatId = this.FormatId;
movie.DirectorId = this.DirectorId;
try
{
tblMovieGenre genre = new tblMovieGenre();
genre.Id = 1;
if (dc.tblMovieGenres.Any())
genre.Id = dc.tblMovieGenres.Max(p => p.Id) + 1;
// THIS IS THE PART that I'm struggling with.
// I think the data is there, I'm just not sure how to access it
foreach (var GenreId in GenreId) // This line is probably wrong, but I'm not sure how to access the data
{
genre.GenreId = this.GenreId.FirstOrDefault();
genre.MovieId = movie.Id;
dc.tblMovieGenres.Add(genre);
}
}
catch (Exception ex)
{
throw ex;
}
dc.tblMovies.Add(movie);
// Commit changes
dc.SaveChanges();
}
catch (Exception ex)
{
throw ex;
}
}
}
I've tried foreach loops and for loops and I can't get it to work. What am I doing wrong?
Edit #1: After making a few changes, here is my (current and non-working) complete Insert logic in the CMovie class. When I only choose one "genre" from the MultiSelectList, it works fine and inserts into both tables properly. However, when I select two or more "genres" from the MultiSelectList, I get a "Value cannot be null, parameter name: items" error.
public void Insert()
{
using (dbEntities2 oDc = new dbEntities2())
{
try
{
tblMovie movie = new tblMovie();
// Add movie to tblMovie
movie.Id = 1;
if (oDc.tblMovies.Any()) // If table is not empty
movie.Id = oDc.tblMovies.Max(p => p.Id) + 1;
this.Id = movie.Id;
movie.Title = this.Title;
movie.Description = this.Description;
movie.ImagePath = this.ImagePath;
movie.Cost = this.Cost;
movie.RatingId = this.RatingId;
movie.FormatId = this.FormatId;
movie.DirectorId = this.DirectorId;
try
{
foreach (var GenreId in GenreIds)
{
tblMovieGenre genre = new tblMovieGenre();
genre.Id = 1;
if (oDc.tblMovieGenres.Any())
{
genre.Id = oDc.tblMovieGenres.Max(p => p.Id) + 1; // genre.Id is set to the highest id in the table, +1
}
genre.Id = this.Id;
genre.GenreId = GenreId;
genre.MovieId = movie.Id;
oDc.tblMovieGenres.Add(genre);
}
}
catch (Exception ex)
{
throw ex;
}
oDc.tblMovies.Add(movie);
// Commit changes
oDc.SaveChanges();
}
catch (Exception ex)
{
throw ex;
}
}
}
}`
Edit 2: I've found a solution to the problem. Hopefully this helps someone else having the same issue. I changed the create to use a dropdownlist instead of a multiselectlist, and modified the edit method to allow for updating of multiple genres.
Inside the CMovie Model, I created two new methods, AddGenre and DeleteGenre. In the controller, I added four new IEnumerable<int> variables: oldGenreIds, newGenreIds, adds, and deletes.
I then made a list from the IEnumerable deletes and adds:
IEnumerable<int> deletes = oldGenreIds.Except(newGenreIds);
IEnumerable<int> adds = newGenreIds.Except(oldGenreIds);
deletes.ToList().Foreach(a => mgdr.CMovie.DeleteGenre(id, a));
adds.ToList().Foreach(a => mgdr.CMovie.AddGenre(id, a));
The update method is then called, which sets the changed values (including the movie title, description, image path, etc):
mgdr.CMovie.Update();
By moving the ForEach logic into the controller, I was able to call the AddGenre method several times - which I wasn't able to do when calling it directly inside the Insert method.
Your post method should accept array rather than single object.
[HttpPost]
public ActionResult Create(MovieGenresDirectorsRatings[] mgdr) // The ViewModel
{
foreach(var genr in mgdr){
try
{
genr.CMovie.Insert(); //inserting each object received from view.
return RedirectToAction("Index");
}
catch (Exception ex)
{
throw ex;
return View(mgdr);
}
}
}
The idea is receive all objects from view. Even if your view is posting all the items from multiselect list, you need array like structure in your controller to get data from view. Once you have that data in controller, you loop through all of them inserting one by one.
Here is the problem (an overlook actually):
tblMovieGenre genre = new tblMovieGenre();
// code...
foreach (var GenreId in GenreId)
{
genre.GenreId = this.GenreId.FirstOrDefault();
// code
dc.tblMovieGenres.Add(genre);
}
So see in the above code you create one tblMovieGenre and then in your loop you keep adding the same instance of tblMovieGenres over and over again. So essentially you add a single tblMovieGenres with values from the last iteration in your loop.
Fix
To fix the issue move the instantiation inside the loop:
foreach (var GenreId in GenreId)
{
tblMovieGenre genre = new tblMovieGenre();
// code...
dc.tblMovieGenres.Add(genre);
}
Other Suggestions
1
Hungarian notation is not encouraged in .NET so prefixing your database tables with tbl is not only a notation problem but makes your code harder to read, especially when you use an ORM. Therefore, if you remove the tbl from table names, your code will be:
MovieGenere instead of tblMovieGenre.
2
Also, if I look at one line of code and I can figure out the type of the object, I always use var instead. Like this:
tblMovieGenre genre = new tblMovieGenre();
var genre = new tblMovieGenre();
That is a personal preference (less typing).
But if I cannot figure the type from reading the single line, then I do not use var:
tblMovieGenre genre = GetMovie();
3
If you make your table primary key columns identity columns starting at 1, then you will not need such code:
movie.Id = 1;
if (dc.tblMovies.Any())
movie.Id = dc.tblMovies.Max(p => p.Id) + 1;
Whenever you create a new object in code, it will have an ID of 0 and when you add it to the db, EF will treat it like a new record and generate a new identity for it. This takes the responsibility of managing IDs away from you, which means less coding.

Adding to or updating an entity in a foreach loop takes too long time before calling SaveChanges()?

I have this method that saves an entity with its related items (many-to-many relationship),
private static void Save<T>(TbCommonHistoryLog log, List<T> lstDetails) where T : IHasSerial
{
foreach (var item in lstDetails.OrderBy(x => x.Serial))
{
var ser = SerializeObject(item);
var record = oContext.TbHistoryLog_Lists.FirstOrDefault(x => x.ListObjectJson == ser);
if (record == null) //add new list item
{
TbCommonHistoryLog_Lists listObject = new TbCommonHistoryLog_Lists()
{
ListObjectJson = SerializeObject(item)
};
var details = new TbCommonHistoryLogDetails { TbHistoryLog = log, TbHistoryLog_Lists = listObject };
oContext.TbHistoryLogDetails.Add(details);
}
else //attach an existing list item
{
var o = oContext.TbHistoryLog_Lists.Find(record.Id);
oContext.TbHistoryLog_Lists.Attach(o);
var details = new TbCommonHistoryLogDetails { TbHistoryLog = log, TbHistoryLog_Lists = o };
oContext.TbHistoryLogDetails.Add(details);
}
}
oContext.BulkSaveChanges();
}
I have two tables: TbCommonHistoryLog, TbCommonHistoryLog_Lists, that are in many to many relationship, the joining table is TbCommonHistoryLogDetails,
What I'm doing here is an auditing for master-detail models, all audits are serialized to JSON in DB, I save the head object in the TbCommonHistoryLog table, and every list item in the TbHistoryLog_Lists table, in the mthod above I check if the list item is already exists in the database or not to avoid duplicating.
but this process takes more than 15 seconds which is a very long time, I can't figure out what am I doing wrong here.. please help?
For every single item in collection you're querying database. My suggestion is to save records in var, then ask the variable if the item is in database.
var databaseRecords = oContext.TbHistoryLog_Lists.ToList();
Then in the loop:
var record = databaseRecords.FirstOrDefault(x => x.ListObjectJson == ser);

insert multiple records one by one using LINQ

I'm trying to copy ProductStatisticsTemp table data to ProductStatistics table,
var str = from a in db.ProductStatisticsTemp select a;
ProductStatistics ls = new ProductStatistics();
foreach (var val in str.ToList())
{
ls.Product_ID = val.Product_ID;
ls.ProductNameEn = val.ProductNameEn;
ls.ProductNameAr = val.ProductNameAr;
db.ProductStatistics.Add(ls);
db.SaveChanges();
}
first record can insert but once its try to insert 2nd one getting following error
The property 'Product_ID' is part of the object's key information and
cannot be modified.
It's because you have one instance of an object and try to add already added object twice.
You need to create new object of ProductStatistics in the loop.
Also you can save changes just once after the loop to improve performance by trigger DB communication just once:
var str = from a in db.ProductStatisticsTemp select a;
foreach (var val in str.ToList())
{
ProductStatistics ls = new ProductStatistics
{
Product_ID = val.Product_ID,
ProductNameEn = val.ProductNameEn,
ProductNameAr = val.ProductNameAr
};
db.ProductStatistics.Add(ls);
}
db.SaveChanges();
Here is a slightly different method.
var products = db.ProductStatisticsTemp.Select(t => new ProductStatistics
{
Product_ID = t.Product_ID,
ProductNameEn = t.ProductNameEn,
ProductNameAr = t.ProductNameAr
}).ToList()
db.ProductStatistics.AddRange(products);
db.SaveChanges();
IMHO Inspired from #Vadim Martynov
If the Product_ID is your primary key, and your set to increment
the key from database . Do not do this Product_ID = val.Product_ID.
The key should be generated from the database. You will get the id
after save changes is invoked.
try
{
var str = from a in db.ProductStatisticsTemp select a;
//This will improve some performance
db.Configuration.AutoDetectChangesEnabled = false;
foreach (var val in str.ToList())
{
ProductStatistics ls = new ProductStatistics
{
Product_ID = val.Product_ID,
ProductNameEn = val.ProductNameEn,
ProductNameAr = val.ProductNameAr
};
//use AddRange or Add based on your EF Version.
db.ProductStatistics.Add(ls);
}
db.SaveChanges();
}
finally
{
db.Configuration.AutoDetectChangesEnabled = true;
}
If you are using AddRange you could omit db.Configuration.AutoDetectChangesEnabled = false
For more info about DetectChanges available here
AddRange() method only support from EF6 see documentation
db.ProductStatistics.AddRange(products);
What AddRange will do for you is
if AutoDetectChangesEnabled is set to true (which is the default), then DetectChanges will be called once before adding any entities and will not be called again.
This means that in some situations AddRange may perform significantly
better than calling Add multiple times would do.
Note that entities that are already in the context in some other state will have their state set to Added. AddRange is a no-op for entities that are already in the context in the Added state.

MVC AddObject Create multiple records

I have a list which contains multiple records. I like to use the AddObject to create those records but what is happening is that it creates just the last record in the list.
Here is the code
foreach (var item in invlist) {
invmodel.tblrec.FirstName = item.FirstName;
invmodel.tblrec.LastName = item.LastName;
db.tblRec.AddObject(invmodel.tblrec);
}
db.SaveChanges();
I would start with this very simple modification of your code:
foreach (var item in invlist) {
var tblRec = new TblRec();
tblRec.FirstName = item.FirstName;
tblRec.LastName = item.LastName;
db.tblRec.AddObject(tblRec);
}
db.SaveChanges();
Why? Because your code is repeatedly adding the same instance and for EF it is still the same object - it will either result in exception or only the last item will be inserted to database.

Categories

Resources