как сериализовать сущности, которые имеют отношение «один ко многим» и разбивку на страницы в entity Framework?

#entity-framework #asp.net-web-api #configuration #foreign-keys #lazy-loading

#entity-framework #asp.net-web-api #конфигурация #внешние ключи #отложенная загрузка

Вопрос:

У меня есть две модели для двух таблиц, имеющих отношение к внешнему ключу. Я должен получить запись из обеих таблиц в одном запросе.

Структура модели выглядит следующим образом:-

  1. EmployeeRecord.cs
 public partial class EmployeeRecord
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public tblEmployeeRecord()
        {
            this.countries = new HashSet<tblCountry>();
        }

        public int EmployeeId { get; set; }
        public string Name { get; set; }
        public string userName { get; set; }
        public string userRole { get; set; }
        public string id { get; set; }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual ICollection<Country> countries { get; set; }

    }
  

2.Country.cs

  public partial class Country
    {
        public int CountryId { get; set; }
        public int EmployeeId { get; set; }
        public string CountryName { get; set; }

       public virtual EmployeeRecord employeeRecord { get; set; }
    }
  
  1. EmployeeController.cs
  public class EmployeeRecordsController : ApiController
    {
        private EstorageEntitiesforCombineView db = new EstorageEntitiesforCombineView();

        // GET: api/EmployeeRecords
        public IEnumerable<EmployeeRecord> GetEmployeeRecords([FromUri]PagingParameterModel pagingparametermodel)
        {
             var source= db.EmployeeRecords.OrderBy(a => a.EmployeeId);

            // Get's No of Rows Count 
             if (pagingparametermodel.userRole == "HR")
            {
                 source= (from a in db.EmployeeRecords
                        where a.userRole == "HR"
                        select a).OrderBy(a => a.EmployeeId);
            }
            else if(pagingparametermodel.userRole == "eStorage Admin")
            {
                source = (from a in db.EmployeeRecords
                          where a.userRole == "eStorage Admin"
                          select a).OrderBy(a => a.EmployeeId);

            } else if(pagingparametermodel.userRole == "SharedService")
            {
                source = (from a in db.EmployeeRecords
                          where a.userRole == "SharedService"
                          select a).OrderBy(a => a.EmployeeId);

            }
             if(pagingparametermodel.toSearch == 1 amp;amp; pagingparametermodel.userRole == "ALL")
            {
                source = (from b in db.EmployeeRecords
                          where b.Name.StartsWith(pagingparametermodel.name) 
                          select b).OrderBy(a => a.EmployeeId);

            }else if (pagingparametermodel.toSearch==1) {
                source= (from b in db.EmployeeRecords
                        where b.Name.StartsWith(pagingparametermodel.name) amp;amp; b.userRole==pagingparametermodel.userRole
                        select b).OrderBy(a => a.EmployeeId);

            }
            int count = source.Count();

            // Parameter is passed from Query string if it is null then it default Value will be pageNumber:1  
            int CurrentPage = pagingparametermodel.pageNumber;

            // Parameter is passed from Query string if it is null then it default Value will be pageSize:20  
            int PageSize = pagingparametermodel.pageSize;

            // Display TotalCount to Records to User  
            int TotalCount = count;

            // Calculating Totalpage by Dividing (No of Records / Pagesize)  
            int TotalPages = (int)Math.Ceiling(count / (double)PageSize);

            // Returns List of Customer after applying Paging   
            var items = source.Skip((CurrentPage - 1) * PageSize).Take(PageSize).ToList();

            // if CurrentPage is greater than 1 means it has previousPage  
            var previousPage = CurrentPage > 1 ? "Yes" : "No";

            // if TotalPages is greater than CurrentPage means it has nextPage  
            var nextPage = CurrentPage < TotalPages ? "Yes" : "No";

            // Object which we are going to send in header   
            var paginationMetadata = new
            {
                totalCount = TotalCount,
                pageSize = PageSize,
                currentPage = CurrentPage,
                totalPages = TotalPages,
                previousPage,
                nextPage
            };

            // Setting Header  
            System.Web.HttpContext.Current.Response.Headers.Add("Paging-Headers", Newtonsoft.Json.JsonConvert.SerializeObject(paginationMetadata));
            // Returing List of Customers Collections  
            return items;

        }
}
  

Я хочу получить следующий ответ :-

 [{
        "tblCountries": [
            {
                "CountryId": 265,
                "EmployeeId": 350,
                "CountryName": "INWEST"
            }
        ],
        "EmployeeId": 350,
        "Name": "ABC",
        "userName": "abc@mail.com",
        "userRole": "HR",
        "id": nbh546652n45
 }]
  

Но ответ следующий:-

 [{
        "tblCountries": [],
        "EmployeeId": 350,
        "Name": "ABC",
        "userName": "abc@mail.com",
        "userRole": "HR",
        "id": nbh546652n45
 }]
  

Я также пробовал, Include(a => a.countries) следующим образом:-

 source= (from a in db.EmployeeRecords
            where a.userRole == "HR"
           select a).Include(a => a.countries).OrderBy(a => a.EmployeeId);
  

Но я получаю следующую ошибку:-

 <ExceptionMessage>
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
</ExceptionMessage>
<ExceptionType>System.InvalidOperationException</ExceptionType>
<StackTrace/>
<InnerException>
<Message>An error has occurred.</Message>
<ExceptionMessage>
Object graph for type 'System.Collections.Generic.HashSet`1[[eStorageApi.Models.tblCountry, eStorageApi, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' contains cycles and cannot be serialized if reference tracking is disabled.
</ExceptionMessage>
<ExceptionType>
System.Runtime.Serialization.SerializationException
</ExceptionType>
  

Пытался включить эти LOCS в WebApiConfig.cs:-

 config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling= Newtonsoft.Json.ReferenceLoopHandling.Ignore;
config.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.None;
  

Все еще получаю ту же ошибку.

Комментарии:

1. Сущности представляют данные. То, что возвращает ваш API, должно быть определено и выбрано из объекта, а не пытаться передать объект напрямую. (да, многие примеры там делают это, все еще очень плохая / ленивая практика) Определите структуру и поля, которые вы хотите вернуть как класс POCO DTO, и используйте .Select() или AutoMapper .ProjectTo() для его заполнения. Не рекомендуется предоставлять клиенту доступ к базе данных, даже для выполнения ВЫБОРА * ИЗ WHERE для таблицы, что и означает возврат объектов.

Ответ №1:

Спроецируйте свою модель домена на что-то другое, прежде чем возвращать ее из вашего api. Это лучше контролирует формат данных ответа, и если добавляется свойство (например, ssn), оно не раскрывается случайно. Это также имеет дополнительное преимущество в исправлении ваших циклических ссылок.

 return items.Select(i => new { 
    i.Name,
    i.Username,
    ...
    Countries = i.Countries.Select(c => new {
            c.CountryId,
            c.CountryName,
            ...
        }
};
  

Ответ №2:

Вам нужно установить связь между employeeRecord и странами. Попробуйте с этим:

  source= db.EmployeeRecords.Include(e => e.countries).Select(c => c.employeeRecord)
           .Where(a => a.userRole == "HR");
  

И когда вы выполняете запрос, вам нужно указать текст: Application / Json

Комментарии:

1. Привет @mlvictor, который я использую, как показано ниже, дает тот же результат :

2. var source = db.tblEmployeeRecords. Включить (e => e.tblCountries). OrderBy(a => a.EmployeeID);