web-dev-qa-db-fra.com

L'entité ne peut pas être construite dans une requête LINQ to Entities

Il existe un type d'entité appelé produit qui est généré par le cadre de l'entité. J'ai écrit cette requête

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

Le code ci-dessous lève l'erreur suivante:

"L'entité ou le type de complexe Shop.Product ne peut pas être construit dans une requête LINQ to Entities"

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

Mais quand j'utilise select p au lieu de select new Product { Name = p.Name};, cela fonctionne correctement.

Comment puis-je préformer une section de sélection personnalisée?

362
Ghooti Farangi

Vous ne pouvez pas (et ne devriez pas pouvoir) projeter sur une entité mappée. Vous pouvez toutefois projeter sur un type anonyme ou sur un DTO :

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

Et votre méthode retournera une liste de DTO.

public List<ProductDTO> GetProducts(int categoryID)
{
    return (from p in db.Products
            where p.CategoryID == categoryID
            select new ProductDTO { Name = p.Name }).ToList();
}
368
Yakimych

Vous pouvez projeter en type anonyme, puis en type de modèle

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 });
}

Edit: Je vais être un peu plus précis car cette question a attiré beaucoup d'attention.

Vous ne pouvez pas projeter directement dans le type de modèle (restriction EF), il n'y a donc aucune solution. Le seul moyen consiste à projeter dans un type anonyme (1ère itération), puis à modéliser un type (2ème itération).

Sachez également que lorsque vous chargez partiellement des entités de cette manière, elles ne peuvent pas être mises à jour. Elles doivent donc rester détachées telles quelles.

Je n'ai jamais complètement compris pourquoi ce n'est pas possible, et les réponses sur ce fil ne donnent pas de raison forte de le contrer (principalement en ce qui concerne les données partiellement chargées). Il est correct que dans une entité d'état partiellement chargée ne puisse pas être mise à jour, cette entité étant alors détachée, des tentatives accidentelles de sauvegarde ne seraient pas possibles.

Considérez la méthode que j'ai utilisée ci-dessus: nous avons toujours une entité de modèle partiellement chargée en conséquence. Cette entité est détachée.

Considérez ce code possible (souhait-exister):

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

Cela pourrait également donner une liste d'entités détachées, nous n'aurions donc pas besoin de faire deux itérations. Un compilateur serait bien avisé de voir que AsNoTracking () a été utilisé, ce qui entraînera des entités détachées, ce qui pourrait nous permettre de le faire. Si, toutefois, AsNoTracking () était omis, il pourrait émettre la même exception qu’il le fait actuellement, pour nous avertir que nous devons être suffisamment précis sur le résultat souhaité.

259
Goran

J'ai trouvé une autre méthode qui fonctionne: vous devez créer une classe dérivée de votre classe Product et l'utiliser. Par exemple:

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};
}

Vous ne savez pas si cela est "autorisé", mais cela fonctionne.

74
Tomasz Iniewicz

Voici un moyen de le faire sans déclarer de classe supplémentaire:

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;
}

Toutefois, cela ne doit être utilisé que si vous souhaitez combiner plusieurs entités en une seule entité. La fonctionnalité ci-dessus (mappage simple de produit à produit) est réalisée comme suit:

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;
}
36
Bojan Hrnkas

Un autre moyen simple :)

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;
}
22
Soren

Vous pouvez utiliser ceci et il devrait fonctionner -> Vous devez utiliser toList avant de créer la nouvelle liste en utilisant select:

db.Products
    .where(x=>x.CategoryID == categoryID).ToList()
    .select(x=>new Product { Name = p.Name}).ToList(); 
3
Mohamed Adam

Vous pouvez résoudre ce problème en utilisant des objets de transfert de données (DTO).

Ce sont un peu les modèles de vue dans lesquels vous insérez les propriétés dont vous avez besoin et que vous pouvez mapper manuellement dans votre contrôleur ou en utilisant des solutions tierces telles qu'AutoMapper.

Avec DTO, vous pouvez:

  • Rendre les données sérialisables (Json)
  • Se débarrasser des références circulaires
  • Réduisez le trafic réseau en laissant des propriétés inutiles (viewmodelwise)
  • Utilisez objectflattening

J'ai appris cela à l'école cette année et c'est un outil très utile.

1
Jelman

En réponse à l’autre question qui était marquée comme une copie ( voir ici ), j’ai trouvé une solution simple et rapide basée sur la réponse de Soren:

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();

Remarque: cette solution ne fonctionne que si vous avez une propriété de navigation (clé étrangère) sur la classe Task (appelée ici "Incident"). Si vous n'en avez pas, vous pouvez simplement utiliser l'une des autres solutions publiées avec "AsQueryable ()".

1
JollyBrackets

ajoutez seulement AsEnumerable ():

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

si vous exécutez Linq to Entity, vous ne pouvez pas utiliser la ClassType avec new dans la select fermeture de la requête only anonymous types are allowed (new without type)

regardez cet extrait de mon projet

//...
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

de vous avez ajouté le new keyword dans Sélectionner fermeture même sur le complex properties vous aurez cette erreur

so remove le ClassTypes from new mot-clé sur Linq to Entity requêtes,

car il sera transformé en instruction SQL et exécuté sur SqlServer

alors quand puis-je utiliser new with types sur select fermeture?

vous pouvez l'utiliser si vous avez affaire à 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 

après avoir exécuté ToList sur une requête, il est devenu in memory collection afin que nous puissions utiliser new ClassTypes dans la sélection

0

Si vous utilisez la structure Entity, essayez de supprimer la propriété de DbContext qui utilise votre modèle complexe en tant qu'entité J'ai rencontré le même problème lors du mappage de plusieurs modèles dans un modèle de vue nommé Entity.

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

Supprimer l’entrée de DbContext a corrigé mon erreur.

0
vikas suhag

Dans de nombreux cas, la transformation n'est pas nécessaire. Pensez à la raison pour laquelle vous souhaitez que le type soit fortement typé, et déterminez si vous souhaitez uniquement les données, par exemple dans un service Web ou pour les afficher. Peu importe le type. Vous avez juste besoin de savoir comment le lire et de vérifier qu'il est identique aux propriétés définies dans le type anonyme que vous avez défini. C'est le scénario optimun, car vous n'avez pas besoin de tous les champs d'une entité et c'est la raison pour laquelle le type anonyme existe.

Un moyen simple est de faire ceci:

IEnumerable<object> list = dataContext.Table.Select(e => new { MyRequiredField = e.MyRequiredField}).AsEnumerable();
0
Sterling Diaz