Entity Framework Core 6 detaches newly added entity - c#

Hello dear StackOverflow community,
i have recently begun working on a project with Entity Framework Core 6. It's a very neat framework and after few little hiccups i got used to it quickly.
However i've found out that Entity Framework secretly detaches newly created objects sometimes.
I'm working on a software which connects to cameras with integrated license plate recognition software, loads the license plates and additional data and later checks, how long a car remained on the parking lot. I store them in a table for currently parked cars as well as a table for statistics. Once i processed all license plates, i write them to the database to be later checked for parking violations.
The cars make it to the database fine, however some of the statistics are not written in there. According to the EF logs, Entity Framework is missing a reference to the parking lot or the camera the car got scanned with and thus EF detaches my statistics object. However Entity Framework does not throw an error and according to the debugger, the camera and parking lot objects are present and the statistics object got the reference.
You can find the code below.
Thank you in advance for your time to help me :)
List<Cars> cars = new List<Cars>();
foreach (Camera cam in _parkingLot.Cameras)
{
ICamera cameraObject = ICamera.GetCameraObjekt(cam);
//Loading all license plates from the camera
List<Cars> tempCars = cameraObject.GetCars();
cars.AddRange(tempCars);
}
//Adds new cars to current context
_context.AddRange(cars);
foreach (Cars car in cars)
{
//Saving some additional information
//since i do not look immediately for
//potential violations and want to make
//sure that i do not apply rules retroactively
ParkTimes parkTimes = GetParkTimes(car);
ParkTimes rentTimes = GetRentTimes(car);
if (parkTimes != null)
{
car.ParkStartTime = parkTimes.StartTime;
car.ParkEndTime = parkTimes.EndTime;
}
if (rentTimes != null)
{
car.RentStartTime = rentTimes.StartTime;
car.RentEndTime = rentTimes.EndTime;
}
//Here's where i save my statistics
ParkStatistics stats = new ParkStatistics();
stats.Direction = Convert.ToInt32(car.Direction);
stats.Car = car;
stats.Timestamp = car.Timestamp;
//The camera is attached to the car after creating the car object
//after i downloaded the data from the camera. This one is allegedly missing.
stats.Camera = car.Camera;
stats.CameraName = car.Camera.Name;
//We're in a class here and the _parkingLot property is
//a global property. This one is also allegedly missing.
stats.ParkingLot = _parkingLot;
stats.HasRentingService = _parkingLot.HasRentingService;
stats.HasParkingMeter = _parkingLot.HasParkingMeter;
stats.MaxParkingDuration = _parkingLot.MaxParkingDuration;
if (parkTimes != null)
{
stats.ParkStartTime = parkTimes.StartTime;
stats.ParkEndTime = parkTimes.EndTime;
}
if (rentTimes != null)
{
stats.RentStartTime = rentTimes.StartTime;
stats.RentEndTime = rentTimes.EndTime;
}
//When i add the statistics object to the context,
//the object gets detached. I do not get an exception
//here. I only realized that the object is missing
//after looking at the database and reading
//the EF logs.
_context.Add(stats);
}
_parkingLot.LastUpdate = DateTime.Now;
_context.SaveChanges();

Related

Erasing AutoCAD drawing objects after exploding them, using C#

I am working on a Task in which the code automatically opens a drawing selected by the user [in a UI] and selects all the objects in the drawing and starts to explode all the of them till they cant be exploded anymore. While doing this I face a problem, the original (un-exploded 3D object) is still present in the drawing, super imposed by the Exploded object. Every recursive call of the Explode function creates a new exploded 3D object of that object.
Here is a snippet of the code I working on:
PromptSelectionResult ss = ed.SelectAll();
using (DocumentLock acLckDoc = doc.LockDocument())
{
using (Transaction tr = db.TransactionManager.StartTransaction())
{
objs = new DBObjectCollection();
foreach (SelectedObject so in ss.Value)
{
Entity ent = (Entity)tr.GetObject(so.ObjectId, OpenMode.ForWrite);
if (!(ent is Solid3d))
{
ent.Explode(objs);
ent.UpgradeOpen();
ent.Erase();
}
}
tr.Commit();
}
}
As soon as the control comes on to the ent.Erase() statement - it throws an exception, eCannotBeErasedByCaller. I cant figure out why? I have unlocked all layers, opened the entity for Write, CommandFlags have been set to Session and UsePickSet (shuffled through all).
Anybody got any suggestions?
Looking at your description, you probably need a recursive explode. Sometime ago I did a code around this, for other type of entities, but you can adjust it.
private List<DBObject> FullExplode(Entity ent)
{
// final result
List<DBObject> fullList = new List<DBObject>();
// explode the entity
DBObjectCollection explodedObjects = new DBObjectCollection();
ent.Explode(explodedObjects);
foreach (Entity explodedObj in explodedObjects)
{
// if the exploded entity is a blockref or mtext
// then explode again
if (explodedObj.GetType() == typeof(BlockReference) ||
explodedObj.GetType() == typeof(MText))
{
fullList.AddRange(FullExplode(explodedObj));
}
else
fullList.Add(explodedObj);
}
return fullList;
}
source: http://adndevblog.typepad.com/infrastructure/2013/04/get-cogopoint-label-text.html
I finally found out the reason why the Original objects werent getting erased.
In the earlier part of the code, a AutoCAD Plant3D dwg is exported to AutoCAD (ExporttoAutoCAD / Saveas), this was creating Proxy items. These cant be deleted manually or via code.
Only way is to explode the PipeLines and Inline assets before exporting the file. This happens automatically if you export the file, but if you use saveas, you will have to explode the Pipe components before you export the file.
Wasted a lot of time understanding the cause, but finally got it!

Get related entity statecode value to use as a check

I need to be able to only execute my code upon the condition that the related opportunity has a statecode of 1
In my code I am able to use the GenerateSalesOrderFromOpportunityRequest class provided by the Microsoft Dynamics SDK to create a new sales order when a opportunityclose activity is created.
The drawback of this approach is that an opportunityclose activity is created by the system when an opportunity is closed as won(1) or lost(2). Also, there are no attributes on the opportunityclose activity that say if it was won or lost. So the only way to find it out is to get that attribute from the related opportunity.
In my code I'm able to get other attributes from the related opportunity, like name, but I have not been able to get any other value for statecode other that 0.
Here is my code:
Entity postImageEntity = (context.PostEntityImages != null && context.PostEntityImages.Contains(this.postImageAlias)) ? context.PostEntityImages[this.postImageAlias] : null;
if (postImageEntity.LogicalName == "opportunityclose" && postImageEntity.Attributes.Contains("opportunityid") && postImageEntity.Attributes["opportunityid"] != null)
{
// Create an entity reference for the related opportunity to get the id for the GenerateSalesOrderFromOpportunityRequest class
EntityReference entityRef = (EntityReference)postImageEntity.Attributes["opportunityid"];
// Retrieve the opportunity that the closed opportunity activity was created for.
Entity RelatedEntityRef = service.Retrieve("opportunity", entityRef.Id, new ColumnSet( new String[] {"statecode","statuscode", "name"}));
OptionSetValue StateCode = (OptionSetValue)RelatedEntityRef.Attributes["statecode"];
OptionSetValue StatusCode = (OptionSetValue)RelatedEntityRef.Attributes["statuscode"];
string OppName = (string)RelatedEntityRef.Attributes["name"];
if (entityRef.LogicalName == "opportunity" && StateCode.Value == 1)
{
try
{
GenerateSalesOrderFromOpportunityRequest req = new GenerateSalesOrderFromOpportunityRequest();
req.OpportunityId = entityRef.Id;
req.ColumnSet = new ColumnSet(true);
GenerateSalesOrderFromOpportunityResponse resp = (GenerateSalesOrderFromOpportunityResponse)service.Execute(req);
}
catch (FaultException ex)
{
throw new InvalidPluginExecutionException("An error occurred in the plug-in.", ex);
}
}
}
Recap: For this to work I just need to be able to get the actual statecode value of the opportunity related to the opportunityclose. Currently I have only been able to get 0 even if I know that the state code of the opportunity is 1.
Other Info:
This is for Microsoft Dynamics Online 2013/2015(works with both)
Using SKD v6.1.1
Plugin works, but fires whether the opportunity is won or lost. (not intended)
Can't you view the opportunity to see if the status is won or lost. I
You can retrieve the OpportunityClose and change the status if you need to
https://msdn.microsoft.com/en-us/library/gg334301.aspx
I assume it's setting the opportunityclose to 1 because you are executing GenerateSalesOrderFromOpportunityRequest which you would only do if you won the opportunity (e.g. you wouldn't progress a lost opportunity).

Multiple instances of IEntityChangeTracker Error

I've read through at least a dozen other questions just like this one, but I am having trouble grasping some of this stuff.
I'm used to developing ASP.NET MVC3 with repositories and code-first entities linking to the entity framework.
I've recently switched to database-first ADO.NET with services development.
I find this to be very clean since I can access stuff through my foreign keys.
Anyway, my old save methods seem to be broken since I constantly get this error
An entity object cannot be referenced by multiple instances of
IEntityChangeTracker
So here's a look at my save action and my service:
Action:
[HttpPost]
public ActionResult AddReview(Review review, int id)
{
User loggedInUser = userService.GetUserByusername(User.Identity.Name);
review.WriterId = loggedInUser.UserId;
review.ProductId = id;
if (ModelState.IsValid)
{
reviewService.Save(review);
Product product = productService.GetProduct(id);
if(product.Quantity>=1)
product.Quantity--;
product.TimesBought++;
productService.UpdateRating(product, reviewService);
loggedInUser.GoldCoins -= product.Price;
Session["goldCoins"] = loggedInUser.GoldCoins;
userService.Save(loggedInUser);
productService.Save(product);
}
else
{
return View(review);
}
return RedirectToAction("Index", "Answers", new { reviewId = review.ReviewId });
Service:
public class ReviewService : Service<Review, CapstoneEntities>
{
...
public void Save(Review review)
{
using (var db = new CapstoneEntities())
{
if (review.ReviewId == 0)
{
db.Reviews.Add(review);
db.Entry(review).State = EntityState.Added;
}
else
{
db.Entry(review).State = EntityState.Modified;
}
db.SaveChanges();
}
}
}
My suspicion is with this line of code: using (var db = new CapstoneEntities()) but I'm not sure how else to do this. Again, this worked perfectly with my old way of doing things but now I get errors on just about ever CRUD operation.
Thank you.
It looks like this is being caused by having an entity belong to multiple DataContexts. Whatever code that is calling that action should use the same DataContext to create the entity as the one used to persist it to the datastore.
In most instances you should only keep one instance of the DataContext. You can use a DI framework like Castle to define/store a dependency (in this case the DataContext) as Transient or PerWebRequest and inject it into the service and controller, so you'll always have a reference to the same instance of the DataContext.
I am new to MVC & Entity frame work. I got same problem after fighting a lot
This solution worked for me. Hope it can be useful for you guys.
var mediaItem = db.MediaItems.FirstOrDefault(x => x.Id == mediaItemViewModel.Id);
mediaItem.Name = mediaItemViewModel.Name;
mediaItem.Description = mediaItemViewModel.Description;
mediaItem.ModifiedDate = DateTime.Now;
mediaItem.FileName = mediaItem.FileName;
mediaItem.Size = KBToMBConversion(mediaItemViewModel.Size);
mediaItem.Type = mediaItem.Type;
//db.Entry(mediaItem).State = EntityState.Modified;// coment This line
db.SaveChanges();
Cause you are reading the the whole object from db and holding it in the current context and when you try to modify the entity state its tells you already one entity attached to the current context. just call save changes it will save it.

Implementing Audit Log / Change History with MVC & Entity Framework

I am building in a Change History / Audit Log to my MVC app which is using the Entity Framework.
So specifically in the edit method public ActionResult Edit(ViewModel vm), we find the object we are trying to update, and then use TryUpdateModel(object) to transpose the values from the form on to the object that we are trying to update.
I want to log a change when any field of that object changes. So basically what I need is a copy of the object before it is edited and then compare it after the TryUpdateModel(object) has done its work. i.e.
[HttpPost]
public ActionResult Edit(ViewModel vm)
{
//Need to take the copy here
var object = EntityFramework.Object.Single(x=>x.ID = vm.ID);
if (ModelState.IsValid)
{
//Form the un edited view model
var uneditedVM = BuildViewModel(vm.ID); //this line seems to confuse the EntityFramework (BuildViewModel() is used to build the model when originally displaying the form)
//Compare with old view model
WriteChanges(uneditedVM, vm);
...
TryUpdateModel(object);
}
...
}
But the problem is when the code retrieves the "unedited vm", this is causing some unexpected changes in the EntityFramework - so that TryUpdateModel(object); throws an UpdateException.
So the question is - in this situation - how do I create a copy of the object outside of EntityFramework to compare for change/audit history, so that it does not affect or change the
EntityFramework at all
edit: Do not want to use triggers. Need to log the username who did it.
edit1: Using EFv4, not too sure how to go about overriding SaveChanges() but it may be an option
This route seems to be going nowhere, for such a simple requirement! I finally got it to override properly, but now I get an exception with that code:
public partial class Entities
{
public override int SaveChanges(SaveOptions options)
{
DetectChanges();
var modifiedEntities = ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
foreach (var entry in modifiedEntities)
{
var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry).GetModifiedProperties(); //This line throws exception The ObjectStateManager does not contain an ObjectStateEntry with a reference to an object of type 'System.Data.Objects.EntityEntry'.
var currentValues = ObjectStateManager.GetObjectStateEntry(entry).CurrentValues;
foreach (var propName in modifiedProps)
{
var newValue = currentValues[propName];
//log changes
}
}
//return base.SaveChanges();
return base.SaveChanges(options);
}
}
IF you are using EF 4 you can subscribe to the SavingChanges event.
Since Entities is a partial class you can add additional functionality in a separate file. So create a new file named Entities and there implement the partial method OnContextCreated to hook up the event
public partial class Entities
{
partial void OnContextCreated()
{
SavingChanges += OnSavingChanges;
}
void OnSavingChanges(object sender, EventArgs e)
{
var modifiedEntities = ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
foreach (var entry in modifiedEntities)
{
var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).GetModifiedProperties();
var currentValues = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).CurrentValues;
foreach (var propName in modifiedProps)
{
var newValue = currentValues[propName];
//log changes
}
}
}
}
If you are using EF 4.1 you can go through this article to extract changes
See FrameLog, an Entity Framework logging library that I wrote for this purpose. It is open-source, including for commercial use.
I know that you would rather just see a code snippet showing how to do this, but to properly handle all the cases for logging, including relationship changes and many-to-many changes, the code gets quite large. Hopefully the library will be a good fit for your needs, but if not you can freely adapt the code.
FrameLog can log changes to all scalar and navigation properties, and also allows you to specify a subset that you are interested in logging.
There is an article with high rating here at the codeproject: Implementing Audit Trail using Entity Framework . It seems to do what you want. I have started to use this solution in a project. I first wrote triggers in T-SQL in the database but it was too hard to maintain them with changes in the object model happening all the time.

ArcGis Engine, how to select objects?

I'm trying to create a standalone application, which loads a ArcGis map, selects a few objects in one layer and zooms to them.
Loading and displaying the map does work, using something like this:
AxMapControl _mapControl;
// in constructor:
_mapControl = new AxMapControl();
// in loading
_mapControl.LoadMxFile(#"C:\Users\me\Documents\TestProject.mxd");
This does work nicely and does display the map as full extent (of course the AxMapControl is embedded into a WindowsFormsHost, but this shouldn't be a problem).
But now I need to select one or more objects and zoom to them. I tried to select in one layer for testing, but this does not work at all:
IFeatureSelection features = _mapControl.Map.Layer[0] as IFeatureSelection;
if (features != null)
{
QueryFilter qf = new QueryFilterClass();
qf.WhereClause = "[Name]='FS4711000'";
features.SelectFeatures(qf, esriSelectionResultEnum.esriSelectionResultNew, false);
}
on the SelectFeatures call I get an COM error 80004005 (E_Fail) in ESRI.ArcGIS.Carto, without much more explanation. Probably I'm doing it all wrong.
Maybe someone has a sample how to select objects in a layer?
I think your issue is as simple as the [square brackets] around your field name in the query string.
This works:
IFeatureSelection features = _currentLayer as IFeatureSelection;
if (features != null)
{
QueryFilter qf = new QueryFilter();
qf.WhereClause = "Type='1'";
features.SelectFeatures(qf, esriSelectionResultEnum.esriSelectionResultNew, false);
}
_axMapControl.Refresh();
Whereas this fails with COM-error E_FAIL:
IFeatureSelection features = _currentLayer as IFeatureSelection;
if (features != null)
{
QueryFilter qf = new QueryFilter();
qf.WhereClause = "[Type]='1'";
features.SelectFeatures(qf, esriSelectionResultEnum.esriSelectionResultNew, false);
}
_axMapControl.Refresh();
Also, notice that the map (or at least the IActiveView returned by AxMapControl.ActiveView) needs to be manually refreshed, or the selection is not displayed before the map is redrawn.

Categories

Resources