3
votes

I have a Report class in my web api project:

[KnownType(typeof(Employee))]
[DataContract]
public class Report
{
    [DataMember]
    public int Id { get; set; }

    public string ManagerId { get; set; }
    public string EmployeeId { get; set; }

    [DataMember]
    public virtual Employee Manager { get; set; }

    [DataMember]
    public virtual Employee Employee { get; set; }
}

If virtual is in the method signature I get the following exception:

Type 'System.Data.Entity.DynamicProxies.Report_1FFC700B8A805A61BF97A4B9A18D60F99AAA83EE08F4CA2E2454BADA9737B476' with data contract name 'Report_1FFC700B8A805A61BF97A4B9A18D60F99AAA83EE08F4CA2E2454BADA9737B476:http://schemas.datacontract.org/2004/07/System.Data.Entity.DynamicProxies' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.

If it isn't everything works. What am I missing?


Edited to include object retrieval code

ApiController

[ResponseType(typeof(IEnumerable<Report>))]
public IHttpActionResult GetLogs([FromUri] string id)
{
    return Ok(_loggingService.GetReportsByEmployeeId(id));
}

Service

public IQueryable<Report> GetReportsByEmployeeId(string employeeId)
{
    return _reports.GetAll().Where(x => x.ManagerId.Equals(employeeId) || x.EmployeeId.Equals(employeeId));
}

Repository

public IQueryable<T> GetAll()
{
    return _dbSet;
}
1

1 Answers

6
votes

Presumably you are retrieving this object using entity framework or a similar ORM.

When you don't have any virtual properties the ORM is safe to use an instance of your Type. However, when you have virtual properties the ORM has to use its own Type in order to support lazy loading which is why you're seeing the serializer complain that it can't serialize the Type 'DynamicProxies'.

To avoid this, separate your domain model from your serialization model and in your call to Select return an instance of your serialization model.

Alternatively, turn off lazy loading in the ORM (not recommended)

Edit: Adding an example as discussed in the comments

As mentioned, your Domain Model (the object representation of your data that you use to interact with the database) and your Business Model (the object representation of your data that you manipulate for business logic) should ideally be separated. Further, nothing outside of the service should be touching domain models directly as doing so risks triggering an unexpected lazy load, blurring of responsibilities across your codebase or other unsavory outcomes.

That separation, incidentally, will avoid the issues that you are having. Example below, of course feel free to change the namespaces and Type names as you see fit.

Business Model

namespace MyCompany.MyProject.BusinessModels
{
    [KnownType(typeof(Employee))]
    [DataContract]
    public class Report
    {
        [DataMember]
        public int Id { get; set; }

        [DataMember]
        public virtual Employee Manager { get; set; }

        [DataMember]
        public virtual Employee Employee { get; set; }
    }
}

Domain Model

namespace MyCompany.MyProject.DomainModels
{
    // this separation may seem like overkill, but when you come to
    // want different business models than your domain models, or to decorate
    // this model with Entity Framework specific attributes, you'll be glad
    // for the separation.
    public class Report
    {
        public int Id { get; set; }

        public virtual Employee Manager { get; set; }

        public virtual Employee Employee { get; set; }
    }
}

Service

public IQueryable<BusinessModels.Report> GetReportsByEmployeeId(string employeeId)
{
    return _reports // _reports is a collection of Type DomainModels.Report
            .GetAll()
            .Where(x => 
                 x.ManagerId.Equals(employeeId) 
                 || x.EmployeeId.Equals(employeeId))
            .Select(s =>
               new BusinessModels.Report
               {
                    Id = s.Id,
                    Employee = s.Employee,
                    Manager = s.Manager
               })
            .ToList(); 
            // We don't want database access happening outside of the service. 
            // ToList() executes the SQL *now* rather than waiting until 
            // the first time you enumerate the result.
}