Сущность не может быть создана в запросе LINQ to Entities

389

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

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

Код ниже выдает следующую ошибку:

«Объект или комплексный тип Shop.Product не могут быть созданы в запросе LINQ to Entities»

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

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

Как я могу предварительно создать пользовательский раздел выбора?

Гути Фаранджи
источник
System.NotSupportedException: «Сущность или сложный тип« StudentInfoAjax.Models.Student »нельзя создать в запросе LINQ to Entities».
Вахид

Ответы:

390

Вы не можете (и не должны иметь возможность) проецировать на сопоставленную сущность. Однако вы можете проецировать на анонимный тип или 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();
}
Yakimych
источник
152
Я не понимаю, почему я не должен быть в состоянии сделать это ... Это было бы очень полезно ...
Jonx
118
Хорошо, отображенные сущности в EF в основном представляют таблицы базы данных. Если вы проецируете на сопоставленную сущность, то, что вы в основном делаете, это частично загружаете сущность, которая не является допустимым состоянием. EF не будет иметь ни малейшего представления о том, как, например, обрабатывать обновление такого объекта в будущем (поведение по умолчанию, вероятно, будет перезаписывать не загруженные поля пустыми значениями или тем, что у вас будет в вашем объекте). Это было бы опасной операцией, так как вы рискуете потерять некоторые из ваших данных в БД, поэтому не разрешается частично загружать сущности (или проецировать на сопоставленные сущности) в EF.
Якимыч
26
@Yakimych, который имеет смысл, за исключением случаев, когда у вас есть какая-то совокупная сущность, которую вы генерируете / создаете с помощью запроса и, следовательно, полностью осведомлены / намереваетесь создать совершенно новую сущность, которую вы затем будете манипулировать, а потом добавлять. В этом случае вам нужно либо принудительно выполнить запрос, либо нажать в dto и обратно в сущность, чтобы добавить - что расстраивает
Cargowire
16
@Cargowire - я согласен, что такой сценарий существует, и он расстраивает, когда вы знаете, что делаете, но не имеете права делать это из-за ограничений. Однако, если бы это было разрешено, многие разочарованные разработчики будут жаловаться на потерю своих данных, например, при попытке сохранить частично загруженные объекты. IMO, ошибка, которая взрывается с большим шумом (выдает исключение и т. Д.), Лучше, чем поведение, которое может привести к скрытым ошибкам, которые трудно отследить и объяснить (все хорошо работает, прежде чем вы начнете замечать пропущенные данные).
Якимыч
276

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

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 () был опущен, он может выдать то же исключение, что и сейчас, чтобы предупредить нас о том, что нам нужно быть достаточно конкретными относительно результата, который мы хотим.

Горан
источник
3
Это самое чистое решение, когда вам не нужно / не нужно заботиться о состоянии выбранного объекта, который вы хотите проецировать.
Марио Мейреллес
2
И когда вам все равно, если вы возвращаете IEnumerable или IQueryable;). Но, тем не менее, вы получите мое одобрение, потому что это решение работает для меня сейчас.
Майкл Бренн
10
технически проекция на тип модели происходит вне запроса, и я считаю, что также требуется дополнительная итерация по списку. Я не буду использовать это решение для своего кода, но это решение вопроса. Всплеск.
1c1cle
4
Я предпочитаю это принятому решению DTO - гораздо более элегантному и чистому
Адам Эй,
7
За исключением того, что, на самом деле, это не ответ на вопрос. Это ответ о том, как сделать проекцию Linq To Objects, а не проекцию запроса Linq to Entities. Таким образом, опция DTO является единственной опцией re: Linq to Entities.
Ризм
78

Есть еще один способ, которым я нашел работы: вы должны создать класс, производный от вашего класса 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};
}

Не уверен, что это «разрешено», но это работает.

Томаш Иневич
источник
3
Умная! Попробовал это сейчас, и это работает. Я уверен, что это как-то обожжет меня.
Даниил
5
Кстати, это вас укусит, если вы попытаетесь сохранить результаты GetProducts (), поскольку EF не может найти сопоставление для PseudoProduct, например «System.InvalidOperationException: не удалось найти информацию о сопоставлении и метаданных для EntityType« blah.PseudoProduct »».
sming
4
Лучший ответ и единственный, который отвечает в рамках параметров вопроса. Все остальные ответы меняют тип возвращаемого значения или преждевременно выполняют IQueryable и используют linq для объектов
rdans,
2
100% в шоке это работает ... в EF 6.1 это работает.
TravisWhidden
2
@mejobloggs Попробуйте атрибут [NotMapped] в производном классе или .Ignore <T>, если вы используете свободный API.
Дунк
37

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

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;
}
Боян Хрнкас
источник
23

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

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;
}
Сорен
источник
хороший момент, я только что узнал кое-что IQueryable с вашим хорошим ответом. Хотя было бы неплохо, если бы вы объяснили, ПОЧЕМУ он бесполезен после ToList (), и причина в том, что вы не можете использовать общие списки в запросе LINQ-to-SQL. Поэтому, если вы знаете, что вызывающий всегда будет помещать результаты в другой запрос, то, безусловно, имеет смысл быть IQueryable. Но если нет ... если вы собираетесь использовать его как общий список после, то используйте ToList () внутри метода, чтобы вы не делали ToList () для IQueryable при каждом вызове этого метода.
PositiveGuy
Вы полностью в порядке, мой друг. Я просто имитирую сигнатуру метода вопроса, из-за чего я конвертирую ее в Query-способную ...;)
Сорен
1
Это работает, productList становится недоступным для редактирования после ToList (). Как я могу сделать это редактируемым?
doncadavona
Если вы вводите .ToListзапрос, он выполняется и получает данные с сервера, тогда какой смысл делать это снова AsQueryable?
Моши
1
@Moshii просто для того, чтобы удовлетворить сигнатуру возвращаемого типа метода (как я уже сказал в ответе, это больше не полезно).
Сорен
4

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

db.Products
    .where(x=>x.CategoryID == categoryID).ToList()
    .select(x=>new Product { Name = p.Name}).ToList(); 
Мохамед Адам
источник
3
Однако это все равно будет делать «SELECT * FROM [..]», а не «SELECT name FROM [..]»
Timo Hermans
1

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

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 ()».

JollyBrackets
источник
1

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

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

С DTO вы можете:

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

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

Jelman
источник
0

Если вы используете Entity Framework, попробуйте удалить свойство из DbContext, которое использует вашу сложную модель как Entity. У меня возникла та же проблема при отображении нескольких моделей в модель представления с именем Entity.

public DbSet<Entity> Entities { get; set; }

Удаление записи из DbContext исправило мою ошибку.

Викас Сухаг
источник
0

если вы выполняете, Linq to Entityвы не можете использовать ClassTypeс newв selectзакрытии запросаonly anonymous types are allowed (new without type)

взгляните на этот фрагмент моего проекта

//...
var dbQuery = context.Set<Letter>()
                .Include(letter => letter.LetterStatus)
                .Select(l => new {Title =l.Title,ID = l.ID, LastModificationDate = l.LastModificationDate, DateCreated = l.DateCreated,LetterStatus = new {ID = l.LetterStatusID.Value,NameInArabic = l.LetterStatus.NameInArabic,NameInEnglish = l.LetterStatus.NameInEnglish} })
                               ^^ without type__________________________________________________________________________________________________________^^ without type

вы добавили new keywordзакрытие в Select даже на complex propertiesэту ошибку

так removeчто ClassTypes from newключевое слово по Linq to Entityзапросам ,,

потому что он будет преобразован в оператор SQL и выполняется на SqlServer

так когда я могу использовать new with typesна selectзакрытие?

Вы можете использовать его, если вы имеете дело с LINQ to Object (in memory collection)

//opecations in tempList , LINQ to Entities; so we can not use class types in select only anonymous types are allowed
var tempList = dbQuery.Skip(10).Take(10).ToList();// this is list of <anonymous type> so we have to convert it so list of <letter>

//opecations in list , LINQ to Object; so we can use class types in select
list = tempList.Select(l => new Letter{ Title = l.Title, ID = l.ID, LastModificationDate = l.LastModificationDate, DateCreated = l.DateCreated, LetterStatus = new LetterStatus{ ID = l.LetterStatus.ID, NameInArabic = l.LetterStatus.NameInArabic, NameInEnglish = l.LetterStatus.NameInEnglish } }).ToList();
                                ^^^^^^ with type 

после того, как я выполнил ToListзапрос, стало in memory collection так, что мы можем использовать new ClassTypesв select

Башир Аль-Момани
источник
Конечно, вы можете использовать анонимные типы, но вы не можете создать сущность в запросе LINQ, даже для установки анонимного члена, потому что LINQ-to-Entities по-прежнему вызывает то же исключение.
Suncat2000
0

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

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

IEnumerable<object> list = dataContext.Table.Select(e => new { MyRequiredField = e.MyRequiredField}).AsEnumerable();
Стерлинг Диас
источник
0

Это не позволит вам перейти обратно на Product, так как это ваш стол, который вы запрашиваете. Вам нужна анонимная функция, затем вы можете добавить ее в ViewModel и добавить каждую ViewModel вList<MyViewModel> и вернуть их. Это небольшое отступление, но я включаю предостережения по поводу обработки необнуляемых дат, потому что с ними трудно справиться, на всякий случай. Вот как я справился с этим.

Надеюсь, у вас есть ProductViewModel:

public class ProductViewModel
{
    [Key]
    public string ID { get; set; }
    public string Name { get; set; }
}

У меня есть структура внедрения / репозитория зависимостей, где я вызываю функцию для получения моих данных. Используя ваш пост в качестве примера, при вызове функции Controller это будет выглядеть так:

int categoryID = 1;
var prods = repository.GetProducts(categoryID);

В классе репозитория:

public IEnumerable<ProductViewModel> GetProducts(int categoryID)
{
   List<ProductViewModel> lstPVM = new List<ProductViewModel>();

   var anonymousObjResult = from p in db.Products
                            where p.CategoryID == categoryID 
                            select new
                            {
                                CatID = p.CategoryID,
                                Name = p.Name
                            };

        // NOTE: If you have any dates that are nullable and null, you'll need to
        // take care of that:  ClosedDate = (DateTime?)p.ClosedDate ?? DateTime.Now

        // If you want a particular date, you have to define a DateTime variable,
        // assign your value to it, then replace DateTime.Now with that variable. You
        // cannot call a DateTime.Parse there, unfortunately. 
        // Using 
        //    new Date("1","1","1800"); 
        // works, though. (I add a particular date so I can edit it out later.)

        // I do this foreach below so I can return a List<ProductViewModel>. 
        // You could do: return anonymousObjResult.ToList(); here
        // but it's not as clean and is an anonymous type instead of defined
        // by a ViewModel where you can control the individual field types

        foreach (var a in anonymousObjResult)
        {                
            ProductViewModel pvm = new ProductViewModel();
            pvm.ID = a.CatID;  
            pvm.Name = a.Name;
            lstPVM.Add(rvm);
        }

        // Obviously you will just have ONE item there, but I built it 
        // like this so you could bring back the whole table, if you wanted
        // to remove your Where clause, above.

        return lstPVM;
    }

Вернувшись в контроллер, вы делаете:

 List<ProductViewModel> lstProd = new List<ProductViewModel>();

 if (prods != null) 
 {
    // For setting the dates back to nulls, I'm looking for this value:
    // DateTime stdDate = DateTime.Parse("01/01/1800");

    foreach (var a in prods)
    {
        ProductViewModel o_prod = new ReportViewModel();
        o_prod.ID = a.ID;
        o_prod.Name = a.Name;
       // o_prod.ClosedDate = a.ClosedDate == stdDate ? null : a.ClosedDate;
        lstProd.Add(o_prod);
    }
}
return View(lstProd);  // use this in your View as:   @model IEnumerable<ProductViewModel>
vapcguy
источник
-1

только добавить AsEnumerable ():

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products.AsEnumerable()
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}
Hamidreza
источник
8
НИКОГДА не делай этого! Это приведет к получению всех данных из БД, а затем сделает выбор.
Gh61
1
Именно поэтому в некоторых компаниях Linq запрещено использовать.
Hakan
-2

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

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products.AsEnumerable()
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}
Hamidreza
источник
Почему это плохой ответ, хотя он работает .... AsEnumerable завершает linq для сущностей. Предложение Where и все остальное обрабатывается за пределами linq для Entities. т. е. каждый продукт извлекается, а затем фильтруется linq для объектов. Помимо этого, он почти такой же, как и ответ .ToList выше. stackoverflow.com/questions/5311034/…
KenF
1
Проблема в том, что выполняется только выбор * из ..., а не выбор нового продукта {Имя = имя_папки}, поскольку вы также получите циклическую ссылку. И вы хотите только имя.
Стерлинг Диас,