Make My Entity Framework Code more Efficient - c#

We have a Repository class. While this class has some custom behavior, it wraps a standard ObjectContext object and so it's primarily standard Entity Framework.
Currently, we have the following code block, which works but performs horribly. (target is a CostingLineItem. The code changes the AddedByEmployee reference for this CostingLineItem.)
if (target.AddedByEmployee != null)
target.AddedByEmployee.CostingLineItems.Remove(target);
byEmployee.CostingLineItems.Add(target);
target.AddedByEmployee = byEmployee;
Because we have lazy loading enabled, this bit of code loads both the target.AddedByEmployee.CostingLineItems and byEmployee.CostingLineItems collections, which could be many thousands of rows.
Unfortunately, I can't change the lazy loading setting for this code. I need to find a way to make it more efficient, but nothing seems to work for me.
Here's what I've tried so far.
Method 1:
target.AddedByEmployeeId = byEmployee.Id;
Has no effect. The AddedByEmployeeId column still contains the original value. Apparently, the original employee reference is still there and takes priority.
Method 2:
target.AddedByEmployee = byEmployee;
Throws the following exception. Again, seems the original employee reference is still there.
Multiplicity constraint violated. The role 'Employees' of the relationship 'Leo.Domain.FK_CostingLineItem_Employees' has multiplicity 1 or 0..1.
Method 3:
Repository.Detach(target.AddedByEmployee);
target.AddedByEmployee = byEmployee;
Detach() (which in turn calls ObjectContext.Detach()) throws the following exception:
The object cannot be detached because it is not attached to the ObjectStateManager.
Method 4:
target.AddedByEmployee.CostingLineItems.Remove(target);
target.AddedByEmployee = byEmployee;
This works! But it's still loading target.AddedByEmployee.CostingLineItems, which I would like to avoid.
I realize I have not shown all of our code here (that's just not possible). But the code is using a standard ObjectContext underneath. What I would really like is someone who has some insights into Entity Framework and can offer some ideas for what else I can try, or what else I can check.
Note: We are currently running Visual Studio 2012. I will see if we can update to 2015. I would love to know if anything has changes since the version we are using that could make some of the methods above work where they didn't before.

Have you tried removing the target directly from the DbSet<> rather that removing it from the attached CostingLineItems.
context.Set<CostingLineItems>().Remove(target)
This should remove the target without loading the collection.

After some extensive research and testing, I think I determined what my issues were.
The problem was that the entity in question had just been cloned in the database. The cloning code follows a bunch of rules that dictate if related items are also cloned or only the references are cloned.
Either way, the AddedByEmployee had already been set. And so trying to set it a second time, or simply trying to set the ID has problems because of the conflict with what was already set.

Related

Random InvalidCastException using ToListAsync on subclass objects

Context
I am using Entity Framework Core, SQLite and its spellfix1 extension.
There are two classes: MetaMovie and FuzzyMetaMovie
FuzzyMetaMovie is a subclass of MetaMovie
Description
At first, I get an IQueryable<MetaMovie> correspondingMovies using db.MetaMovies.Where(...). If no result, then I use db.FuzzyMetaMovies.FromRawSql(...) that is assigned to the same variable of type IQueryable<MetaMovie>. To finish, I call List<MetaMovie> tempList = await correspondingMovies.ToListAsync();.
Issue
The issue is coming from that last line. I am sometimes, rarelly, getting an InvalidCastException: Unable to cast object of type 'Castle.Proxies.MetaMovieProxy' to type 'com.cyberinternauts.all.MediaRecognizer.Models.Metas.FuzzyMetaMovie'.
I, then, tried calling the code with one of those specifically failing. No issue!! Any of the ones failing tried alone aren't creating the issue. So, I mean here it goes exactly through the same code path and the exception is not thrown.
I sincerely have no idea why is this happening. I know, you have no working code here. But, I am not able to circumvent the issue, thus no easy way to reproduce it.
Any idea?
Edit #1
I wrote a patch solution that does the trick (see my solution below). Though, I will explain how to reproduce it:
Context
Having two classes one is the child of the other: A being the parent of B.
The classes share the same table with a Discriminator column.
AsNoTracking() is not used.
Steps
Make a query that loads an object A with Id == 1
Make another query that loads an object B with Id ==1
Underlying reason of the crash
It is caused by EntityFramework that tries to load an entity of type B that has already been cached as type A. Thus, the downcasting failure from A to B.
The issue was coming from a previous call that loads at least one of the movies that is fetched by the raw query.
To fix it, I used (so no cache is used):
List<MetaMovie> tempList = await correspondingMovies.AsNoTracking().ToListAsync();
I opened an issue in GitHub for the efcore project: https://github.com/dotnet/efcore/issues/27802

Getting an EntityCollection to talk to the database

I believe I have found what I want in terms of a main class for operations in Entity, but do not know how exactly to wire it up to the database, and MSDN is precious short on documentation (Google is even less useful).
Again, pardon my ignorance, as I am new to this, and only recently coming up to speed, but how would I get this to connect or reflect this change with the database? I am just not seeing a path between this, and say DbContext. Do I craft a query by hand? Is there some special class I am missing that provides some functionality I've been missing over the last three days that I've been trying to coax this framework into doing just anything minimally useful? Thanks to anyone who accepts this arduous task.
EntityCollection<T> newCollection = new EntityCollection<T>();
newCollection.Add(objectValue);
I mean, is EntityCollection just a List / Array for Entities? Or does it have some back-end hooks to the database? Can it perform updates / deletes when its methods are called, and are these magical in nature, or is there some prep work that makes the magic work?
As I understand, this class behaves just like the List<T> class - i.e. it's an in-memory collection.
Look at this answer: ObjectSet.AddObject() vs. EntityCollection.Add()
But if you'll look at the MSDN documentation about its Add method so you'll see that it could be related to the ObjectContext and eventually to the data-source(Database):
The Add method adds an object to an EntityCollection and
creates a relationship between the two objects. When the source object
is attached to an ObjectContext instance, the Add method also adds the
object to the ObjectContext. This operation is translated into an
insert operation in the data source when SaveChanges is called. For
more information, see Adding, Modifying, and Deleting Objects (Entity
Framework).

Internal access for entities in Entity Framework makes simple linq where query crash

I am developing a library which use EF for database access. To avoid expose the entities outside the library, I have set the access of all tables to internal (I have also set to internal the Entity Container Access). The problem is that now, inside the library, when I try to get an entity by its id, the Where query throws an exception, I do not why.
The internal access has been set in the diagram (.edmx file), and in a static class that I am using as a factory inside the library I have something like this:
var id = 1234;
var mec = new MyEntitiesContainer();
var myEntity = mec.MyEntities.Where(e => e.MyEntitiesId == id).FirstOrDefault();
Simple where query to get a concrete entity (row) from the database by its id.
When all entities class access are public, there is no problem, but when I have set them to internal this exception is thrown:
System.ArgumentNullException: Value cannot be null. Parameter name: source
at System.Linq.Queryable.Where[TSource](IQueryable`1 source, Expression`1 predicate)
Any suggestions?
Update! Actually found a more direct working solution to this problem:
Open the edmx in the designer
Set "Entity Container Access" to Internal
Be sure all Entities have "Access" set to Public
Expand the edmx and edit the entity t4 (NOT the *.Context.tt)
In the t4 search for Accessibility.ForType(entity) and replace it with "Internal" (or "Friend" for VB)
Save/Re-run the T4 and that's it.
After following those steps no part of your EF items should be visible outside the Assembly and everything should "just work".
If you still experience the "Value cannot be null" exception see the "gotcha" at the bottom of this post.
This is already answered here: c# DbSet - Internal object cannot be got
The gist is that if you set an entityset/dbset property as internal it will not be automatically instantiated by EF. But you can manually instantiate it as described in the accepted answer on that page.
Another gotcha to look out for:
There is a bug in VS2012 (version 11.0.60610.01 Update 3 is my current environment) where the designer won't properly set items back to Public in the underlying edmx file even though it reports as though it does.
So even after setting items back to public, re-running the context t4 and rebuilding you'll still receive that exception.
The only workaround I've found is to open the edmx file and edit it manually, setting the internals back to public.
How is code from another namespace meant to set the properties in objects from your namespaces? If you think about how EF lives in a different namespace to your code yet populates properties in it, I'd venture that public is somewhat necessary.

EF4 Relationships Not Updating Correctly

Ok, I'm working on a project and was just handed a bug that I'm having a bit of trouble with. The code is written in a "different" manner and I think the way the original developers approached this project set it up for some problems, one of which I'm dealing with today. Basically, we have something like this:
Review_Comment comment = commentContext.Review_Comment.First(c => c.CommentID == commentID);
commentContext.DeleteObject(comment);
commentContext.SaveChanges();
review.Review_Comment.Clear();
review.Review_Comment.Load(System.Data.Objects.MergeOption.OverwriteChanges);
context.SaveChanges();
Let me explain a few things and then I'll explain the problem:
"review" is an instance of the Review class, which is the parent of a set of "Review_Comments" (i.e. Review_Comments belong to a single Review).
The function above is to delete a comment.
The comments, for better or worse, use their own EF4 context (separate from the context that the "review" variable is attached to. This is important.
What the original developer tried to do, I think was load the comment in a separate context, delete it, then update the EntityCollection of Review_Comments in the separate "Review" class manually.
However, when context.SaveChanges() is called, we get the following error:
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
I've seen this error described when people are trying to delete say, an Order object and the related OrderItems are not deleted correctly but this case is different. We're trying to delete a single child object and then update the EntityCollection on another entity using a separate context.
Hope that all makes sense, let me know if I can help clarify anything. Any thoughts?
EDIT: I should mention I was able to get this problem to go away by using the same context that the rest of the page uses. However, in this case, because of several dependencies previous developers have introduced, I have to keep the second context or else I have to rewrite a ton of code to remove the dependencies on the second context just to fix this bug. I'm hoping to find a solution that doesn't involve that much time. The goal is to delete the comment, then to reload a separate entity's Review_Comment EntityCollection and be able to call SaveChanges() without this error.
Your problem is that the .Clear() causes the second context to disassociate the Review_Comments from its Review, it never realizes that the Review_Comment was actually deleted.
You should do this instead
context.Refresh(RefreshMode.StoreWins, review.Review_Comment );
context.SaveChanges();
If you watch the entity state of the comment in "review.Review_Comment" you should see that after the Refresh, its state becomes "Detached" rather than "Modified"

LINQ to SQL Basic insert throws: Attach or Add not new entity related exception

I am trying to insert a record. This code worked but has stopped working I don't know why. Here is the code:
using (SAASDataContext dc = new SAASDataContext())
{
tblAssessment a2 = new tblAssessment();
a2.AssessmentCentreId = centreId;
a2.AttemptNumber = 1;
dc.tblAssessments.InsertOnSubmit(a2);
dc.SubmitChanges();
CurrentAssessmentId = a2.AssessmentId;
}
The code compiles but throws the exception below on the dc.SubmitChanges(); line.
Exception thrown:
An attempt has been made to Attach or Add an entity that is not new,
perhaps having been loaded from another DataContext. This is not
supported.
Notes:
AssessmentCentreId is a foreign key on tblCentre, centreId is a valid existing centre id,
AssessmentCentreId and AttemptNumber are the only not null fields all other columns allow nulls.
I have googled but all the results seem to pertain to people trying to attach entities pulled from other disconnected DataContext's I'm not doing that so I'm stumped.
UPDATE:
Adding
dc.DeferredLoadingEnabled = false;
at the top of the using block makes it work, but I'd like to know why coz I have no idea at the moment sufficiently advanced technology being indistinguishable from magic right now :)
This was bugging me as well. I did some searching and found a lot of confusion and some sloppy work-arounds regarding detached entities. I then found a nice solution on codeplex that has solved the immediate issue and has greatly extended linq2sql functionality. Its a small class thats really easy to implement and forms an EntityBase for your projects dbml's.
Here is the offical description and link.
LINQ to SQL Entity Base is a simple base class that is primarily designed to support LINQ to SQL in a disconnected way, which is one of the shortcomings of the LINQ to SQL technology at the present time. This is highly useful in an n-Tier, distributed or ASP.NET environment where disconnected functionality is relavent.
http://linq2sqleb.codeplex.com/
Hope this helps anyone who is having similar.
Jerome Vernon
The issue is that the Centre object doesn't exist in the context. It's in the db, but not "in your hand" with a2. Try this:
a2.AssessmentCentre = dc.AssessmentCentres.SingleOrDefault(
d=>d.AssessmentCentreId.Equals(centreId));
The AssessmentCentre object will then exist in the context, which means there will be no problem with attaching it to a2.
If these are the only non-null columns, where is the primary key? LINQ to SQL requires a primary key to insert data. If you're using AssessmentCentreId on its own or along with AttemptNumber as a composite, are you sure you're not trying to insert a duplicate key?

Categories

Resources