I am writing a crawler that should go to a website, extract some data from there and then store it into a db, the thing is the crawler should also update the data that has already been found in a prior run.
The ParseDataPage returns the information parsed from the site in a EF POCO, one of its properties is a unique identifier (which also is the primary key in the db table), how can I tell EF to insert/add the object?
class Program
{
static void Main()
{
var context = (adCreatorEntities) DbContextFactory.GetInstance().GetDbContext<adCreatorEntities>();
var crawler = new DataCrawler();
crawler.Login();
var propertyIds = crawler.GetPropertyIds();
foreach (var id in propertyIds)
{
var poco = crawler.ParseDataPage(id);
context.Properties.Add(poco); //<-- How can I tell EF to update if the record exists or to insert it otherwise??
context.SaveChanges();
}
context.SaveChanges();
if (crawler.LoggedIn)
crawler.Logout();
}
}
You can set the entity state to Modified or Add the entity to the DbSet based on the key's value.
if(entity.propertyId <= 0)
{
context.Properties.Add(poco);
}
else
{
context.Entry(poco).State = EntityState.Modified;
}
This is code for EF5, EF4 is slightly different for setting object state
context.ObjectStateManager.ChangeObjectState(poco, EntityState.Modified);
you can check whether record exists or not using following code,
var entity= dataContext.Properties.Find(b => b.UniqueId == poco.UniqueId);
if (entity== null)
{
dataContext.Properties.Add(poco);
}
else
{
dataContext.Entry(entity).State = EntityState.Modified;
}
dataContext.SaveChanges();
Related
When cloning/copying child records, I use a foreach loop and then create a record with all its attributes. I wrote similar code in another project and worked fine for me.
There are multiple articles/questions based on the same Error. Now my issue is how should I create child records with all its attributes.
foreach (var packingList in oPEntityCollection.Entities)
{
packingList.Attributes.Remove("statuscode");
packingList.Attributes.Remove("statecode");
packingList.Id=Guid.Empty;
orgService.Create(packingList);
}
Another strange issue
An entry with the same key already exists
Code:
Entity parentEntity = orgService.Retrieve(context.PrimaryEntityName, context.PrimaryEntityId, new ColumnSet(true));
parentEntity.Id = Guid.empty;
orgService.Create(parentEntity);
Even if I create a new object and copy parentEntity just like below I get this error.
Entity costcalEntity = new Entity();
costcalEntity = parentEntity;
costcalEntity.Id = Guid.Empty;
orgService.Create(costcalEntity);
So I end up creating a record with primary name and once the record is created, I update the same record with old record attributes.
Entity costcalEntity = new Entity();
costcalEntity.LogicalName = parentEntity.LogicalName;
costcalEntity["name"] = parentQuotationEntity.GetAttributeValue<string>("name");
costcalEntity.Id = Guid.Empty;
Guid newGuid = orgService.Create(costcalEntity);
if (newGuid != Guid.Empty)
{
costcalEntity = parentEntity;
costcalEntity.Id = newGuid;
orgService.Update(costcalEntity);
}
and this works fine.
In both cases you have the same issue, with it's root cause being the Id stored in the attribute collection of the entity. If you look at the early bound generation, you can access the Id by the entity.Id property, as well as the attribute collection as shown in the definition for the id in the primary id:
public System.Nullable<System.Guid> AccountId
{
[System.Diagnostics.DebuggerNonUserCode()]
get
{
return this.GetAttributeValue<System.Nullable<System.Guid>>("accountid");
}
[System.Diagnostics.DebuggerNonUserCode()]
set
{
this.OnPropertyChanging("AccountId");
this.SetAttributeValue("accountid", value);
if (value.HasValue)
{
base.Id = value.Value;
}
else
{
base.Id = System.Guid.Empty;
}
this.OnPropertyChanged("AccountId");
}
}
So when you are retrieving an existing entity, both the Property Id, which you have handled, as well as the attribute collection, which you haven't handled, have been populated by the CRM SDK. So in order to be able to duplicate it, you'll need to clear the id in both places.
Here is how I solved it
foreach (Entity packingList in oPEntityCollection.Entities)
{
Entity newpackingList = new Entity()
{
LogicalName = packingList.LogicalName,
};
newpackingList.Attributes.AddRange(packingList.Attributes);
newpackingList.Attributes.Remove("primaryGuid");
Guid newOpGuid = orgService.Create(newpackingList);
tracingService.Trace($"OP record created sucessfully with guid {newOpGuid}");
}
So the Trick, issue was rather I was trying to assign packingList directly to newpackingList. This caused to assign packingList metadata attributes as well such. This was not acceptable with crm
But rather I should add it's attribute. This worked and created all child records.
Same worked for parent record as well
Entity parentEntity = orgService.Retrieve(context.PrimaryEntityName, context.PrimaryEntityId, new ColumnSet(true));
Entity newParentEntity = new Entity()
{
LogicalName = parentEntity.LogicalName,
};
newParentEntity.Attributes.AddRange(parentEntity.Attributes);
newParentEntity.Attributes.Remove("primaryGuid");
orgService.Create(newParentEntity);
If your question is "How do I duplicate an Entity retrieved from CRM?", your answer can be simplified.
var entity = orgService.Retrieve(context.PrimaryEntityName, context.PrimaryEntityId, new ColumnSet(true));
entity.Id = Guid.Empty;
entity.Attributes.Remove("primaryGuid");
orgService.Create(entity);
I have a case where I am trying to Delete one item from a table but before doing that I want to copy it to another table.
Here is my Delete method:
public ActionResult Delete(int id, Car car)
{
try
{
using (BookCarDBEntities db = new BookCarDBEntities())
{
var carToDelete = db.Cars.FirstOrDefault(c => c.Id == id);
var book = CreateNewBooking(carToDelete);
db.Bookings.Add(book);
db.Cars.Remove(carToDelete);
db.SaveChanges();
return View(book);
}
catch (Exception ex)
{
return View(ex + "error");
}
}
And here is a method which does the conversion from 'Car' table to 'Booking' table:
private object CreateNewBooking(Car car)
{
var bookingCreated = new Booking
{
id = car.Id,
model = car.model,
make = car.make,
price = car.price,
location = car.location
};
return bookingCreated;
}
The problem is that I get an error:
'System.InvalidOperationException': The entity type Booking is not part of the model for the current context.
How can I solve this?
You managed to fix the error (in the comments) but I'll post the solution here for others.
The error:
'System.InvalidOperationException': The entity type Booking is not part of the model for the current context.
can be caused for a couple of reasons.
The entity has become detached or has somehow lost its reference
within the entity framewowrk. This can be resolved by refreshing the
entities. Go to the edmx file, right-click on the designer surface
and selecting Update model from database. If that still fails,
delete the entity (or all entities) in the designer and then update
model from database.
If the table/entity does not exist. In this case create the table/entity and
update the entities.
Then Rebuild after you have updated from database.
In my mvc application I have a lot of related data, I need to write a method to allow the edit/update of that related data, at the moment my data is handled using a ViewModel. I have attempted to write a method to do this but I can't get it to work correctly and I'm not sure my approach is quite right and so I wanted to get some guidance.
Here is my code:
Controller Code
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(FullVesselViewModel model)
{
var vessel = new tbl_vessels
{
vessel_idx = model.vessel_idx,
vessel_name = model.vessel_name
};
var vessel_spec = new tbl_vessel_spec
{
spec_idx = model.spec_idx,
bhp = model.bhp
}
using (var context = new dataEntities())
{
context.Entry(vessel).State = EntityState.Modified;
context.Entry(vessel_spec).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
}
My initial though was to declare my viewmodel and then break down the two related entities and store them in variables, comitting those variables as modified and then saving my changes. My above approach returns concurrency errors. I have stored the indexes of the entities in a couple of hidden for fields to see if that would help but I've had no luck.
View
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.vessel_idx)
#Html.HiddenFor(model => model.spec_idx)
...input fields etc ...
}
Is my thinking correct for this sort of scenario or have I over engineered something that can be done simpler?
The error that I get is as follows and is triggered at SaveChanges():
Entity Framework: “Store update, insert, or delete statement affected an unexpected number of rows (0).”
After analyzing your code & found context.SaveChanges() as source of EF exception, you may choose one of these solutions that suitable for your issue.
Change EntityState to Added before saving changes:
using (var context = new dataEntities())
{
context.Entry(vessel).State = EntityState.Added;
context.Entry(vessel_spec).State = EntityState.Added;
context.SaveChanges();
return RedirectToAction("Index");
}
or use TryUpdateModel inside your controller action after finding data matches, in case you need data update without changing primary key:
var foundvessel = context.tbl_vessels.Where(x => x.vessel_idx == model.vessel_idx).FirstOrDefault();
var foundvspec = context.tbl_vessel_spec.Where(x => x.spec_idx == model.spec_idx).FirstOrDefault();
if (foundvessel != null && foundvspec != null)
{
var vessel = new tbl_vessels
{
vessel_idx = model.vessel_idx,
vessel_name = model.vessel_name
};
var vessel_spec = new tbl_vessel_spec
{
spec_idx = model.spec_idx,
bhp = model.bhp
}
using (var context = new dataEntities())
{
this.TryUpdateModel(vessel);
this.TryUpdateModel(vessel_spec);
context.SaveChanges();
return RedirectToAction("Index");
}
}
EF's generated error indicates an optimistic concurrency exception where you trying to update primary key value of some table(s), i.e. if vessel_idx & spec_idx are primary key from each target table EF treats modifying them as adding new row(s), rather than updates against existing data.
Thus, you need to perform:
EntityState.Added if new primary key has added
EntityState.Modified if existing primary key left unchanged
Any improvements or suggestions welcome.
Additional reference: Entity Framework: "Store update, insert, or delete statement affected an unexpected number of rows (0)."
Please, help me to handle this situation:
I meaningly switched off AutoDetectChangesEnabled and I load my
entities AsNoTracked() meaningly either.
And I can't update many-to-many relationship in this case:
Here is the code of Update method:
public void Update(User user)
{
var userRoleIds = user.Roles.Select(x => x.Id);
var updated = _users.Find(user.Id);
if (updated == null)
{
throw new InvalidOperationException("Can't update user that doesn't exists in database");
}
updated.Name = user.Name;
updated.LastName = user.LastName;
updated.Login = user.Login;
updated.Password = user.Password;
updated.State = user.State;
var newRoles = _roles.Where(r => userRoleIds.Contains(r.Id)).ToList();
updated.Roles.Clear();
foreach (var newRole in newRoles)
{
updated.Roles.Add(newRole);
}
_context.Entry(updated).State = EntityState.Modified;
}
All simple fields, like Name, LastName updated. But the set
of Roles for User doesn't get updated - it stays the same.
I tried loading Roles using
_context.Entry(updated).Collection("Roles").Load();
But I can't update this loaded set in any way.
I searched for similar items but failed to find the answer, thought it definitely already exists.
I'm really sorry for possible dublicate.
PS. I want to add that I don't want to delete or update child entities at all.
A lot of existing answers suggest manually delete / add child entities to database in whole, but it is not suitable for me.
Roles are independent entities, any other user can use them.
I just want to update User_Role table in database, but I can't.
How I can do just this ( a.myFavorits.Add()) without pulling the all object to var a , because a has a lot of data, and I don't want to pull all a object, but I can't find a way do do it.
I want to do the lambada and the linq without return something but linq is always return something
public static void addFavorits(long f,long idUser)
{
using (var db = dataBase())
{
// here i pull object user from users table
var a = db.users.Where(c => c.id == idUser).SingleOrDefault();
// here i adding to the object field myFavorits new value
//myFavorits is also a table of entitys that connected to user object
a.myFavorits.Add(new BE.FavoritsUsersLong { myLong = f });
db.SaveChanges();
}
}
I thought to do something like this but i dont know how to set the field users_TableId that is the key that connect the 2 tables
public static void addFavorits(long favoritId,long idUser)
{
using (var db = dataBase())
{
db.favoritsUsersLong.Add(new BE.FavoritsUsersLong {myLong = favoritId}
/*,users_TableId =idUser*/);
db.SaveChanges();
}
}
Here's a concrete example that does what you want. In this example, only the Name of a Company is modified and saved. Or an item is added to one of its collections.
var cmp = new Company{ CmpId = 1, Name = "Cmp1" }; // CmpId is the primary key
db.Companies.Attach(cmp);
db.Entry(cmp).Property(c => c.Name).IsModified = true;
// Or add an entity to a collection:
cmp.Users = new[] {new User { Name = "a1", PassWord = "a1" } };
try
{
db.Configuration.ValidateOnSaveEnabled = false;
db.SaveChanges();
}
finally
{
db.Configuration.ValidateOnSaveEnabled = true;
}
Result in SQL:
DECLARE #0 VarChar(30) = 'Cmp1'
DECLARE #1 Int = 1
UPDATE [dbo].[Company]
SET [Name] = #0
WHERE ([CmpId] = #1)
There are a few things to note here:
Obviously you need to know the Id of the entity you want to modify.
The object you create is called a stub entity, which is an incomplete entity. When you try to save such an entity, EF is very likely to complain about null values in required properties. That's why almost certain you'd have to disable validation (temporarily, or, better, dispose the context immediately).
If you want to add an item to a collection, you should leave validation enabled, because you'd want to know for sure that the new entity is valid. So you shouldn't mix these two ways to use a stub entity.
If you often need roughly the same small part of your entity you may consider table splitting.
I'm guessing this is what you want? I don't see you 'editting' I only see you adding.
using (var db = dataBase())
{
var a = new user();
....
//set properties etc..
...
a.myFavorits.Add(new BE.FavoritsUsersLong { myLong = f });
db.users.Add(a);
db.SaveChanges();
}