Entity Framework DBSet Include without proxies - c#

I am trying to create a method that accesses my Entity Framework (database first) context using reflection for a REST web service. I have gotten as far as getting the table, and converting it to a list for returns, but I am running into trouble when I try to use Include to get some of the related tables.
I have a couple tables that I am testing with, they are Project and Person. Project has a reference to Person for the person who manages the project and a reference back from Person to Project for all the projects a person manages. In order to get the original return to work I added
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
to my context's class so that the referential loop is removed and the JSON serialization works correctly.
The problem I am facing is that I am now trying to explicitly get all the projects, and the person record related to the project, without including the list of the person's projects. But when I try to include people, I get the JSON serialization error because it is pulling back the circular reference. I currently have the following code:
Entities context = new Entities();
// Normally these will be a parameters to the calling method
string tableName = "Projects";
string includeTableName = "Person";
System.Reflection.PropertyInfo propertyInfo = context.GetType().GetProperty(tableName);
Type type = propertyInfo.PropertyType;
dynamic list = propertyInfo.GetValue(context);
var include = typeof(QueryableExtensions).GetMethod("Include", new[] { typeof(IQueryable), typeof(string) });
var toList = typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(type.GenericTypeArguments[0]);
list = include.Invoke(null, new object[] { list, includeTableName });
return toList.Invoke(null, new object[] { list });
The code executes properly but then I make the call I get the following error:
"Message":"An error has occurred.","ExceptionMessage":"Self referencing loop detected with type 'DDBAPI.EntityFramework.Project'. Path '[8].Person.Projects'."
Is there anyway with Include to stop it from loading the circular reference? I saw similar questions that referenced making DTOs to limit what gets pulled into the return but since normally I will not know what table I will be calling against, I will not know which DTO I would need as I am trying to avoid any logic based around the table names being passed in.

Removing the proxy creation doesn't solve the circular reference problem. It has nothing to do with it.
The proxies are simply created to handle the change tracking, but they have the same properties of the original entities.
Your problem is that there is a navigation property from table Person to Project and viceversa. That's the circular reference, and you could only break it if you removed the navigation property in one of the tables, i.e. the Person property in Project entity, or the Projects property in Person entity.
Most probably you don't want to do this. So, what you need to do is to instruct the serializer so that it can handle the circular references. I suppose you're using JSON.NET, which is the current JSON serializer by default. I do also suppose you're using Web API. If that's the case, you can find the JSON.NET serializer settings like this:
JsonSerializerSettings serializerSettings = GlobalConfiguration
.Configuration.Formatters.JsonFormatter.SerializerSettings;
Then you need to choose one of these two options:
ReferenceLoopHandling = ReferenceLoopHandling.Ignore. Docs here.
PreserveReferencesHandling = PreserveReferencesHandling.Objects Docs here.
There is still another solution: instead of removing one of the navigation properties responsible for the circular reference, you can instruct JSON.NET to not serialize it by decorating it with [JsonIgnore] attribute. Docs here.

Related

Problem with EF Core updating nested entities when using automapper

I am maintaining an application which uses EF Core to persist data to a SQL database.
I am trying to implement a new feature which requires me to retrieve an object from the database (Lets pretend its an order) manipulate it and some of the order lines which are attached to it and save it back into the database. Which wouldn't be a problem but I have inherited some of this code so need to try to stick to the existing way of doing things.
The basic process for data access is :
UI -> API -> Service -> Repository -> DataContext
The methods in the repo follow this pattern (Though I have simplified it for the purposes of this question)
public Order GetOrder(int id)
{
return _context.Orders.Include(o=>o.OrderLines).FirstOrDefault(x=>x.Id == id);
}
The service is where business logic and mapping to DTOs are applied, this is what the GetOrder method would look like :
public OrderDTO GetOrder(int id)
{
var ord = _repo.GetOrder(id);
return _mapper.Map<OrderDto>(ord);
}
So to retrieve and manipulate an order my code would look something like this
public void ManipulateAnOrder()
{
// Get the order DTO from the service
var order = _service.GetOrder(3);
// Manipulate the order
order.UpdatedBy = "Daneel Olivaw";
order.OrderLines.ForEach(ol=>ol.UpdatedBy = "Daneel Olivaw");
_service.SaveOrder(order);
}
And the method in the service which allows this to be saved back to the DB would look something like this:
public void SaveOrder(OrderDTO order)
{
// Get the original item from the database
var original = _repo.GetOrder(order.Id);
// Merge the original and the new DTO together
_mapper.Map(order, original);
_repo.Save(original);
}
Finally the repositories save method looks like this
public void Save(Order order){
_context.Update(order)
_context.SaveChanges();
}
The problem that I am encountering is using this method of mapping the Entities from the context into DTOs and back again causes the nested objects (in this instance the OrderLines) to be changed (or recreated) by AutoMapper in such a way that EF no longer recognises them as being the entities that it has just given to us.
This results in errors when updating along the lines of
InvalidOperationException the instance of ProductLine cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.
Now to me, its not that there is ANOTHER instance of the object being tracked, its the same one, but I understand that the mapping process has broken that link and EF can no longer determine that they are the same object.
So, I have been looking for ways to rectify this, There are two ways that have jumped out at me as being promising,
the answer mentioned here EF & Automapper. Update nested collections
Automapper.Collection
Automapper.collection seems to be the better route, but I cant find a good working example of it in use, and the implementation that I have done doesn't seem to work.
So, I'm looking for advice from anyone who has either used automapper collections before successfully or anyone that has any suggestions as to how best to approach this.
Edit, I have knocked up a quick console app as an example, Note that when I say quick I mean... Horrible there is no DI or anything like that, I have done away with the repositories and services to keep it simple.
I have also left in a commented out mapper profile which does work, but isn't ideal.. You will see what I mean when you look at it.
Repo is here https://github.com/DavidDBD/AutomapperExample
Ok, after examining every scenario and counting on the fact that i did what you're trying to do in my previous project and it worked out of the box.
Updating your EntityFramework Core nuget packages to the latest stable version (3.1.8) solved the issue without modifying your code.
AutoMapper in fact "has broken that link" and the mapped entities you are trying to save are a set of new objects, not previously tracked by your DbContext. If the mapped entities were the same objects, you wouldn't have get this error.
In fact, it has nothing to do with AutoMapper and the mapping process, but how the DbContext is being used and how the entity states are being managed.
In your ManipulateAnOrder method after getting the mapped entities -
var order = _service.GetOrder(3);
your DbContext instance is still alive and at the repository layer it is tracking the entities you just retrieved, while you are modifying the mapped entities -
order.UpdatedBy = "Daneel Olivaw";
order.OrderLines.ForEach(ol=>ol.UpdatedBy = "Daneel Olivaw");
Then, when you are trying to save the modified entities -
_service.SaveOrder(order);
this mapped entities reach the repository layer and DbContext tries to add them to its tracking list, but finds that it already has entities of same type with same Ids in the list (the previously fetched ones). EF can track only one instance of a specific type with a specific key. Hence, the complaining message.
One way to solve this, is when fetching the Order, tell EF not to track it, like at your repository layer -
public Order GetOrder(int id, bool tracking = true) // optional parameter
{
if(!tracking)
{
return _context.Orders.Include(o=>o.OrderLines).AsNoTracking().FirstOrDefault(x=>x.Id == id);
}
return _context.Orders.Include(o=>o.OrderLines).FirstOrDefault(x=>x.Id == id);
}
(or you can add a separate method for handling NoTracking calls) and then at your Service layer -
var order = _repo.GetOrder(id, false); // for this operation tracking is false

.net Core EF - Dynamically Add to DbSet by string name or type

Suppose I have a thousand classes that all inherit one base class, and each of these classes (not the base) has a table, each of which has the same required columns. How can I use reflection or generics to add or update a row in the correct table? I'll know the name of the specific entity at runtime.
There are a handful of stackoverflow posts about getting a dbset with reflection, but the answer results in an IQueryable, which you cannot add a new item to. Those posters seem content with that because they're just fetching data I suppose. I need a DbSet so I can add and update. I can get the fully qualified entity name and type, but how do I get a DbSet with that? I don't want to write a 1000 line switch statement :(
I ended up using magic strings to add all the properties I knew would be present in the base class. Although not ideal, it did add to the correct table which was the ultimate goal.
var entityType = Assembly.GetAssembly(typeof(BaseTypeEntity)).GetType(namespacePrefix + typeName);
// create an instance of that type
object instance = Activator.CreateInstance(entityType);
// Get a property on the type that is stored in the
// property string
PropertyInfo prop = entityType.GetProperty("Active");
prop.SetValue(instance, true, null);
// .... more properties
_context.Add(instance);
You can do it by dynamically creating a new type that inherits from DbContext and the use Reflection Emit to add the DbSets to this type.
It's a bit of a long solution to try and post into a response here, but here's a GitHub link to a working demo of how it can be done - EF Core Dynamic Db Context

Duplicate Entity Record [duplicate]

My model looks something like this:
Company
-Locations
Locations
-Stores
Stores
-Products
So I want to make a copy of a Company, and all of its associations should also be copied and saved to the database.
How can I do this if I have the Company loaded in memory?
Company company = DbContext.Companies.Find(123);
If it is tricky, I can loop through each association and then call create a new object. The Id's will be different but everything else should be the same.
I am using EF 6.
Cloning object graphs with EF is a piece of cake:
var company = DbContext.Companies.AsNoTracking()
.Include(c => c.Locations
.Select(l => l.Stores
.Select(s => s.Products)))
.Where(c => c.Id == 123)
.FirstOrDefault();
DbContext.Companies.Add(company);
DbContext.SaveChanges();
A few things to note here.
AsNoTracking() is vital, because the objects you add to the context shouldn't be tracked already.
Now if you Add() the company, all entities in its object graph will be marked as Added as well.
I assume that the database generates new primary key values (identity columns). If so, EF will ignore the current values from the existing objects in the database. If not, you'll have to traverse the object graph and assign new values yourself.
One caveat: this only works well if the associations are 1:0..n. If there is a n:m association, identical entities may get inserted multiple times. If, for example, Store-Product is n:m and product A occurs at store 1 and store 2, product A will be inserted twice. If you want to prevent this, you should fetch the objects by one context, with tracking (i.e. without AsNoTracking), and Add() them in a new context. By enabling tracking, EF keeps track of identical entities and won't duplicate them. In this case, proxy creation should be disabled, otherwise the entities keep a reference to the context they came from.
More details here: Merge identical databases into one
I would add a method to each model that needs to be cloneable this way, I'd recommend an interface for it also.
It could be done something like this:
//Company.cs
Company DeepClone()
{
Company clone = new Company();
clone.Name = this.name;
//...more properties (be careful when copying reference types)
clone.Locations = new List<Location>(this.Locations.Select(l => l.DeepClone()));
return clone;
}
You should repeat this basic pattern for every class and "child" class that needs to be copiable. This way each object is aware of how to create a deep clone of its self, and passes responsibility for child objects off to the child class, neatly encapsulating everything.
It could be used this way:
Company copyOfCompany123 = DbContext.Companies.Find(123).DeepClone;
My apologies if there are any errors in the above code; I don't have Visual Studio available at the moment to verify everything, I'm working from memory.
One other really simple and code efficient way to deeply clone an object using serialization can be found in this post How do you do a deep copy an object in .Net (C# specifically)?
public static T DeepClone<T>(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T) formatter.Deserialize(ms);
}
}
Just be aware that this can have some pretty serious resource and performance issues depending on your object structure. Every class that you want to use it on must also be marked with the [Serializable] attribute.

EntityFramework 6 Serialize

I have searched in google for 3 days without any success.
I am using the Database First approach then generated the classes. What I need to do now is to Serialize my entity into Json and then either save to a file or send to another party using Web API (certainly they will be deserialized and consumed later)
The problem here is that EF6 trying to includes all the Navigation Properties, and makes the serialization / deserialization extremely difficults. Setting the properties below doesn't work at all.
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
Is there an easy way telling EF6 to just ignore those Navigation Properties all together when serializing? Since I am using the DB first approaches, I have access to all related tables with their foreign keys.
Thanks in Advance.
Try using Newtonsoft.Json; nuget for the serialization.
To serialize:
string json = JsonConvert.SerializeObject(MyObjects, Formatting.Indented);
Inside your class MyObject, you just add a [JsonIgnore] annotation above properties you want to skip (Navigation props among others)

How can I "transport" an entity object from a WCF service to a client application with Entity Framework?

I am completely new to Entitfy Framework. I am using a WCF service to pass data to an ASP.NET Web Forms application. I have classes that were generated according to the database schema I have.
First, I tried to get a simple result, executing an operation contract, named 'GetPublciations', which returns all the records, stored in my Publciation table, as an array of Publication objects. Everything compiled fine, but I got a run-time error. I fixed that by following the instructions in this question and its answer.
Basically, I added a line, like the one below in my method:
yourContextObject.Configuration.ProxyCreationEnabled = false;
Now, I have a new problem. My Publication table has a foreign key CreationUserId, that references another table, named User. But when I try to get the value of some of the properties of the User object, it turns out that it actually has a value of NULL, which is imposible because in my database this foreign key column is NOT NULL. I tested this in a Console Application, without setting the ProxyCreationEnabled to false and then all of my "child" objects were created appropriately (meaning I had a User object inside my Publication object, which was not NULL).
I guess I can overcome this problem simply by creating a view in my database, for example, DetailedPublciation, then creating an appropriate auto-generated class with Entity Framework and then passing an object of this DetailedPublciation through my WCF service. Meaning, the newly created class will have only properties of primitive types like string, int, long, etc. and I won't have to reference a 'child' object.
So, my question is how I can fix that and whether creating a view like the mentioned above is the correct way to fix this problem.
Edit 1:
I tried with this code inside my GetPublications method:
SomeEntities someEntities = new SomeEntities();
someEntities.Configuration.ProxyCreationEnabled = false;
return someEntities.Publications.Include("User").ToList();
I get a big exception 'tree', but the inner exception states:
An existing connection was forcibly closed by the remote host
Edit 2:
I tried the following code in both - a Console Application and a WCF Service.
SomeEntities someEntities = new SomeEntities();
someEntities.Configuration.ProxyCreationEnabled = false;
return someEntities.Publications.Include(p => p.User).ToList();
In the Console Application everything works fine but in the service, I get a StackOverflow Exception.
ProxyCreationEnabled essentially turns off lazy loading which means you need to manually specify any extra objects you want retrieved from your database. Using Include for example, if your query looks like this:
var publications = from p in context.Publications
where p.Column = someValue
select p;
Then you should change it to this:
var publications = (from p in context.Publications
where p.Column = someValue
select p)
.Include(p => p.CreationUser);
The Include function will force Entity Framework to (eagerly) load the CreationUser object.

Categories