WebAPI Controller werden oftmals direkt von Entity-Framework-Klassen erstellt. Meist funktioniert dies auch ohne Probleme. Sobald in der Datenstruktur ein Self-Join enthalten ist, funktioniert das Serialisieren der Objekte nicht mehr. Bei einem Self-Join zeigt der aktuelle Datensatz auf einen anderen Datensatz in der selben Tabelle. Dies wird oftmals zur Abbildung von rekursiven Daten in einer Datenbank genutzt. Meist sind es Stücklisten. In der Northwind Datenbank ist ein Self-Join in der Employees Tabelle enthalten. Das Feld “ReportsTo” zeigt zu einem anderen Datensatz in der selben Tabelle.
Der Controller, der von der WebAPI-Vorlage erstellt wird sieht so aus:
1 private NorthwindEntities db = new NorthwindEntities();
2
3 // GET: api/Employees
4 public IQueryable<Employee> GetEmployees()
5 {
6 return db.Employees;
7 }
Sobald dieser aufgerufen wird kommt es zu Fehlermeldungen. Im Fall eines Aufrufes mit einem Accept-Header für “application/xml” kommt es zu dieser Fehlermeldung:
<InnerException>
<Message>An error has occurred.</Message>
<ExceptionMessage>Der Typ 'System.Data.Entity.DynamicProxies.Employee_B1E79F3DA3457CF8D8ED9D5552D7D5BFDED8339AF7872876BE7D42B811832EB6' mit dem Datenvertragsnamen 'Employee_B1E79F3DA3457CF8D8ED9D5552D7D5BFDED8339AF7872876BE7D42B811832EB6:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' wird nicht erwartet. Verwenden Sie ggf. einen DataContractResolver, wenn Sie DataContractSerializer verwenden, oder fügen Sie alle unbekannten Typen statisch der Liste der bekannten Typen hinzu, beispielsweise mithilfe des KnownTypeAttribute-Attributs oder indem Sie sie zur Liste der bekannten Typen hinzufügen, die an das Serialisierungsprogramm übergeben wird.</ExceptionMessage>
<ExceptionType>System.Runtime.Serialization.SerializationException</ExceptionType>
<StackTrace>….
“
Beim Aufruf mit application/json ist es nicht besser:
"InnerException": {
"Message": "An error has occurred.",
"ExceptionMessage": "Self referencing loop detected for property 'Employee' with type 'System.Data.Entity.DynamicProxies.Employee_B1E79F3DA3457CF8D8ED9D5552D7D5BFDED8339AF7872876BE7D42B811832EB6'. Path '[0].Orders[0]'.",
"ExceptionType": "Newtonsoft.Json.JsonSerializationException",
…..
In beiden Fällen gibt es Probleme beim Serialisieren. Eine mögliche Lösung besteht darin, nicht direkt die vom Entity Framework generierten Objekte aus dem WebApi Controller zurückzugeben, sondern eigens erstellte “Data Transfer Objects“ zu nutzen. Diese enthalten nur jene Attribute die auch benötigt werden. Das Erstellen dieser DTO-Klassen ist allerdings recht viel Coding Aufwand, da Properties von der EF Klasse zur DTO Klasse kopiert werden müssen.
Hier setzt das Tool “AutoMapper” an. Mit diesem ist es mit wenigen Codezeilen möglich die DTO Klassen mit Werten aus den EF Klassen zu befüllen.
Zunächst muss das NuGet Package “AutoMapper” hinzugefügt werden. Danach wird die EmployeeDTO Klasse implementiert. Alle Properties die an den Client übergeben werden soll, sind hier zu definieren:
1 public class EmployeeDto
2 {
3 public int EmployeeID { get; set; }
4 public string LastName { get; set; }
5 public string FirstName { get; set; }
6 public string Title { get; set; }
7 public string TitleOfCourtesy { get; set; }
8 public Nullable<System.DateTime> BirthDate { get; set; }
9 public Nullable<System.DateTime> HireDate { get; set; }
10 public string Address { get; set; }
11 public string City { get; set; }
12 public string Region { get; set; }
13 public string PostalCode { get; set; }
14 public string Country { get; set; }
15 public string HomePhone { get; set; }
16 public string Extension { get; set; }
17 public string Notes { get; set; }
18 public Nullable<int> ReportsTo { get; set; }
19 }
In der Application_Start()_Methode, welche in der Datei Global.asax zu finden ist, muss eine Zeile eingefügt werden:
1 Mapper.Initialize(c => c.CreateMap<Employee, EmployeeDto>());
Danach wird die GET-Methode im Controller noch angepasst, sodass ein IEnumerable von EmployeeDto-Objekten zurückgegeben wird. In der Methode selbst, wird vom Mapper die Methode Map verwendet um die vom EF gefundenen Objekte in DTO Objekte zu kopieren:
1 public IEnumerable<EmployeeDto> GetEmployees()
2 {
3 var emp = db.Employees;
4 return Mapper.Map<IEnumerable<Employee>, IEnumerable<EmployeeDto>>(emp);
5 }
Schon hat man mehr Kontrolle darüber, welche Properties an die Clients übergeben werden ohne eine Zeile Code für das Kopieren der Daten geschrieben zu haben.
Wer mehr über den AutoMapper lesen möchte, wird unter http://automapper.org/ fündig.