Сущность не может быть создана в запросе LINQ to Entities - PullRequest
357 голосов
/ 16 марта 2011

Существует тип сущности, называемый продуктом, который создается структурой сущности. Я написал этот запрос

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}

Приведенный ниже код вызывает следующую ошибку:

"Сущность или комплексный тип Shop.Продукт не может быть построен в Запрос LINQ to Entities "

var products = productRepository.GetProducts(1).Tolist();

Но когда я использую select p вместо select new Product { Name = p.Name};, он работает правильно.

Как мне предварительно сформировать секцию произвольного выбора?

Ответы [ 13 ]

363 голосов
/ 16 марта 2011

Вы не можете (и не должны) проецировать на сопоставленную сущность.Однако вы можете проецировать на анонимный тип или DTO :

public class ProductDTO
{
    public string Name { get; set; }
    // Other field you may need from the Product entity
}

И ваш метод вернет список DTO.

public List<ProductDTO> GetProducts(int categoryID)
{
    return (from p in db.Products
            where p.CategoryID == categoryID
            select new ProductDTO { Name = p.Name }).ToList();
}
257 голосов
/ 21 сентября 2012

Вы можете проецировать в анонимный тип, а затем из него в тип модели

public IEnumerable<Product> GetProducts(int categoryID)
{
    return (from p in Context.Set<Product>()
            where p.CategoryID == categoryID
            select new { Name = p.Name }).ToList()
           .Select(x => new Product { Name = x.Name });
}

Редактировать : Я собираюсь быть более конкретным, так как этот вопрос привлек много внимания.

Вы не можете напрямую проецировать на тип модели (ограничение EF), поэтому нет никакого способа обойти это. Единственный способ - проецировать на анонимный тип (1-я итерация), а затем моделировать тип (2-я итерация).

Также имейте в виду, что когда вы частично загружаете объекты таким образом, они не могут быть обновлены, поэтому они должны оставаться отсоединенными, как они есть.

Я никогда полностью не понимал, почему это невозможно, и ответы в этой теме не дают веских оснований для этого (в основном речь идет о частично загруженных данных). Правильно, что в частично загруженном состоянии сущность не может быть обновлена, но тогда эта сущность будет отсоединена, поэтому случайные попытки сохранить их будут невозможны.

Рассмотрим метод, который я использовал выше: в результате у нас все еще есть частично загруженный объект модели. Эта сущность обособлена.

Считайте этот (желательный существующий) возможный код:

return (from p in Context.Set<Product>()
        where p.CategoryID == categoryID
        select new Product { Name = p.Name }).AsNoTracking().ToList();

Это также может привести к списку отдельных объектов, поэтому нам не нужно делать две итерации. Компилятор был бы разумным, чтобы увидеть, что AsNoTracking () был использован, что приведет к отсоединению сущностей, так что это может позволить нам сделать это. Однако, если AsNoTracking () был опущен, он может выдать то же исключение, что и сейчас, чтобы предупредить нас о том, что нам нужно быть достаточно конкретными относительно результата, который мы хотим.

73 голосов
/ 28 апреля 2012

Есть еще один способ, которым я нашел работы: вы должны создать класс, производный от вашего класса Product, и использовать его. Например:

public class PseudoProduct : Product { }

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products
           where p.CategoryID== categoryID
           select new PseudoProduct() { Name = p.Name};
}

Не уверен, разрешено ли это, но это работает.

36 голосов
/ 08 мая 2012

Вот один из способов сделать это без объявления дополнительного класса:

public List<Product> GetProducts(int categoryID)
{
    var query = from p in db.Products
            where p.CategoryID == categoryID
            select new { Name = p.Name };
    var products = query.ToList().Select(r => new Product
    {
        Name = r.Name;
    }).ToList();

    return products;
}

Тем не менее, это будет использоваться, только если вы хотите объединить несколько объектов в один объект. Вышеуказанная функциональность (простое сопоставление продуктов) выполняется следующим образом:

public List<Product> GetProducts(int categoryID)
{
    var query = from p in db.Products
            where p.CategoryID == categoryID
            select p;
    var products = query.ToList();

    return products;
}
22 голосов
/ 06 апреля 2011

Еще один простой способ:)

public IQueryable<Product> GetProducts(int categoryID)
{
    var productList = db.Products
        .Where(p => p.CategoryID == categoryID)
        .Select(item => 
            new Product
            {
                Name = item.Name
            })
        .ToList()
        .AsQueryable(); // actually it's not useful after "ToList()" :D

    return productList;
}
3 голосов
/ 20 сентября 2016

Вы можете использовать это, и оно должно работать -> Вы должны использовать toList, прежде чем создавать новый список, используя select:

db.Products
    .where(x=>x.CategoryID == categoryID).ToList()
    .select(x=>new Product { Name = p.Name}).ToList(); 
1 голос
/ 01 декабря 2016

Это можно решить с помощью объектов передачи данных (DTO).

Это немного похоже на модели представления, в которые вы вводите нужные свойства, и вы можете сопоставить их вручную в контроллере или с помощью сторонних решений, таких как AutoMapper.

С DTO вы можете:

  • Сделать данные сериализуемыми (Json)
  • Избавиться от циклических ссылок
  • Сократите сетевой трафик, оставив ненужные свойства (viewmodelwise)
  • Использовать объектное расширение

В этом году я изучал это в школе, и это очень полезный инструмент.

1 голос
/ 22 сентября 2015

В ответ на другой вопрос, который был отмечен как дубликат ( см. Здесь ), я нашел быстрое и простое решение, основанное на ответе Сорена:

data.Tasks.AddRange(
    data.Task.AsEnumerable().Select(t => new Task{
        creator_id   = t.ID,
        start_date   = t.Incident.DateOpened,
        end_date     = t.Incident.DateCLosed,
        product_code = t.Incident.ProductCode
        // so on...
    })
);
data.SaveChanges();

Примечание: Это решение работает, только если у вас есть свойство навигации (внешний ключ) в классе Task (здесь он называется «Инцидент»). Если у вас его нет, вы можете просто использовать одно из других опубликованных решений с «AsQueryable ()».

0 голосов
/ 07 марта 2018

Во многих случаях преобразование не требуется.Подумайте, по какой причине вам нужен тип List, и оцените, хотите ли вы просто получить данные, например, в веб-сервисе или для их отображения.Неважно, тип.Вам просто нужно знать, как его прочитать и убедиться, что он идентичен свойствам, определенным в анонимном типе, который вы определили.Это оптимальный сценарий, потому что вам не нужны все поля сущности, и именно поэтому существует анонимный тип.

Простой способ сделать это:

IEnumerable<object> list = dataContext.Table.Select(e => new { MyRequiredField = e.MyRequiredField}).AsEnumerable();
0 голосов
/ 07 августа 2017

вы можете добавить AsEnumerable в свою коллекцию следующим образом:

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products.AsEnumerable()
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...