MVC AddObject Create multiple records - c#

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.

Related

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

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

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;
}

How to build List by using data from another List using LINQ

i have some List that hold 1 or more Guid.
var getMyfirstListGuids = (from dlist in db.MyTable
where dlist.id == theid
select dlist).ToList();
List<MyfirstList> myfirstList = new List<MyfirstList>();
foreach (var item in myLandingPageList)
{
Guid theguid = new Guid(item.guid);
MyfirstList addnewRow = new MyfirstList();
addnewRow.LpGuid = new Guid(theguid);
myfirstList.Add(addnewRow);
}
Now i have a list with 1 or more guids.
my next step is to make list with data from SQL by the first list guids.
In the SQL could be 1 row or more for each guid. with one row result i can guess what to do. but if there is many results i dont have an idea.
Okay so you wanna do it like this:
List<SecondListGUID> secondListGUID = new List<SecondListGUID>();
foreach (var item in myfirstList)
{
for(int i = 0; i<_yourDBEntity.GUIDs.Count(); i++)
{
if(item.LpGuid == _yourDBEntity.GUIDs[i].GUID)
secondListGUID.add(
new SecondListGUID() {
// add the corresponding GUID's here
});
}
}
Basically you have to do a foreach through your first List and then do a for loop (or foreach - whichever you prefer) through your DB table (entity in this case if you're using Entity framework) and simply compare the GUID's from your DB table and if they match you'll wanna add the item to your third list.
P.S. I've worked with the informations that you've provided, you can change the 2nd list type into the one you need, and change your entity framwork data model name into the one you actually use :)
You can try this
List<Guid>getMyfirstListGuids = new List<Guid>();
getMyfirstListGuids.addRange(from dlist in db.MyTable
where dlist.id == theid
select dlist).ToList());
List<MySecList> mySecList = new List<MySecList>();
mySecList.AddRange(_db.myLandingPageList.Where(p => p.Guid.Any(x => getMyfirstListGuids.Contains(x));

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.

Trying to get all entity fields from a CRM record. C#

Straight to the point, I am trying to make an application so that the user can search through their CRM system without going onto CRM. I have managed to retrieve the record but when i try to put the record into a list using the records attributes I am only recieving the Keys.
Here is the code:
EntityCollection retrieved = _service.RetrieveMultiple(query);
foreach (var c in retrieved.Entities)
{
foreach (KeyValuePair<String, Object> attribute in c.Attributes)
{
lstRecordDetails.Items.Add(string.Format(attribute.Key + ": " + attribute.Value));
}
}
This only displays the recordID and the Record Name, I understand these are both the primary keys oh the record and i know i could use c.Attributes["description"] for the description but is there a way that i can get all the fields from the record and display them in the list?
Edit: Details on the query
QueryByAttribute query = new QueryByAttribute(entity);
query.ColumnSet = new ColumnSet(field);
query.Attributes.AddRange(field);
query.Values.AddRange(selected);
The way you can retrieve all the columns of an entity is
query.ColumnSet = new ColumnSet(true);
However, be careful with this, because query all columns has an impact on the system (you should always retrieve explicitly the columns you need).
RetrieveMultipleRequest retrieve = new RetrieveMultipleRequest();
retrieve.Query = query;
retrieve.ReturnDynamicEntities = true;
retrieved = (RetrieveMultipleResponse)Service.Execute(retrieve);
foreach(var dynEntity in retrieved.BusinessEntityCollection)
{
foreach (var prop in dynEntity.Properties)
{
lstRecordDetails.Items.Add(string.Format("{0}:{1}", prop.Name, prop.Value);
}
}

Categories

Resources