2010-09-04 6 views
6

I suivre par exemple l'entité-cadre:colonne d'identité dans EF 4

http://msdn.microsoft.com/en-us/library/bb399182.aspx

et j'ai problème avec des colonnes d'identité.

Voici une partie de code de base de données création:

CREATE TABLE [dbo].[Person](
    [PersonID] [int] IDENTITY(1,1) NOT NULL, 
    [LastName] [nvarchar](50) NOT NULL, 
    [FirstName] [nvarchar](50) NOT NULL, 
    [HireDate] [datetime] NULL, 
    [EnrollmentDate] [datetime] NULL, 
CONSTRAINT [PK_School.Student] PRIMARY KEY CLUSTERED 
(
    [PersonID] ASC 
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY] 
) ON [PRIMARY] 
END 
GO 

Dans VS 2010 je construis .edmx et au modèle que je vois que personne StoreGeneratedPattern est définie à l'identité.

Mais quand je veux créer personne, par: alt text

Pourquoi je dois mettre id, si cette colonne est autoincrement?

EDITŁ

Je pensais que je trouve le moyen de résoudre mon problème par:

var schoolContext = new SchoolEntities(); 

      schoolContext.AddToPeople(new Person() { LastName = "Gates", FirstName = "Bil" }); 

      schoolContext.SaveChanges(); 

parce qu'il a ajouté Bill à des personnes, mais ... parce que PersonID n'est pas annulable, et il lui inséré avec id 0. Quand j'ai essayé d'ajouter une autre personne de la même façon bien sûr, je reçois l'erreur au sujet de clé primaire :)

donc encore rien ...

Toutes les idées?

Répondre

6

Je ne peux pas vous dire pourquoi l'équipe EF a choisi de le faire de cette façon - ma seule supposition serait que la génération de code qui crée la méthode CreatePerson ne vérifie pas si l'ID est un auto-incrémentation une méthode qui fonctionnera dans n'importe quelle circonstance - si l'identification est autoincrement ou pas.

Si cela vous dérange vraiment, vous pouvez également profiter du fait que la classe d'entité générée Person est définie comme une classe partielle , de sorte que vous pouvez l'étendre facilement. Créez un nouveau fichier de classe appelé par ex. PersonEx.cs et étendre cette classe partielle:

public partial class Person 
{ 
    public static Person CreatePerson(string lastName, string firstName) 
    { 
     return CreatePerson(-1, lastName, firstName); 
    } 
} 

Maintenant, vous pouvez facilement créer vos Person entités sans spécifier un identifiant, et les ajouter à vos données:

using(SchoolEntities context = new SchoolEntities()) 
{ 
    Person newPerson = Person.CreatePerson("Gates", "Bill"); 
    context.AddToPeople(newPerson); 

    context.SaveChanges(); 

    int newID = newPerson.PersonID; 
} 

Ce n'est pas une solution parfaite - mais devrait fonctionner très bien (du moins c'est pour moi).

+0

Merci Marc! C'est la prochaine fois que vous économiserez beaucoup de temps – user278618

+0

Joli motif mais je pense que vous seriez plus en sécurité en utilisant le 0 par défaut pour l'ID .. -1 échouerait aux vérifications par défaut et on pourrait supposer que vous ne créeriez pas une table avec une identité commençant à zéro. –

+0

@daveL: les nombres négatifs fonctionnent très bien pour moi .... –

7

IMO l'ID est nécessaire même s'il est généré. Supposons que vous utilisez une association de clé étrangère (comportement différent de l'association indépendante). Cela signifie que les entités enfants associées utilisent la clé primaire de l'entité parent pour créer une relation. Supposons maintenant que vous ajoutez plusieurs entités parentes avec des entités apparentées dans une seule unité de travail. Vous devez spécifier un identifiant unique (temporaire) pour chaque entité parente, sinon vous ne configurerez jamais votre graphe d'objet. Donc, le générateur de code fait probablement cela par défaut.

Edit:

Je suis surpris que je suis downvoted pour réponse fondée sur des faits corrects.Alors laissez-moi clarifier ma réponse:

Il existe deux types de relations disponibles dans EF 4.0. Association indépendante et association de clé étrangère. La différence est que le dernier ajoute des propriétés de clé étrangère aux entités. Cela vous permet de travailler avec les relations de la même manière que dans la base de données - simplement en définissant des clés. Vous pouvez lire à propos de ces différences dans MSDN.

Supposons maintenant un exemple simple. J'ai un simple modèle EDMX avec MyContext. Le modèle se compose de deux entités Order et OrderLine. Lorsque j'ai ajouté des tables Orders et OrderLines au modèle, j'ai épaissi les colonnes Inclure les clés étrangères dans le modèle. J'utilise donc des associations de clés étrangères au lieu d'associations indépendantes.

L'ordre a stocké l'ID généré en tant que clé et CustomerName en tant que propriété. La ligne de commande a l'ID généré en magasin en tant que clé, ProductTitle en tant que propriété et OrderId en tant que clé étrangère. Je veux ajouter deux commandes en simple unité de travail:

using (var context = new MyContext()) 
{ 
    var ox = Order.CreateOrder(0, "CustomerX"); 
    var oy = Order.CreateOrder(0, "CustomerY"); 

    // Building relationship in the same way as in database 
    var olx = OrderLine.CreateOrderLine(0, ox.Id, "ProductX"); 
    var oly = OrderLine.CreateOrderLine(0, oy.Id, "ProductY"); 

    context.Orders.AddObject(ox); 
    context.Orders.AddObject(oy); 
    context.OrderLines.AddObject(olx); 
    context.OrderLines.AddObject(oly); 
    context.SaveChanges(); // UpdateException: Unable determine principal end of Model.FK_OrderLine_Order relationship. Multiple added entities have the same primary key. 
} 

L'erreur que je l'ai fait dans mon code met en identification des deux nouvelles commandes à 0. Même si cela est temporaire Id il doit encore être unique si vous vouloir l'utiliser pour les clés étrangères. Vous pouvez probablement demander à l'instant pourquoi le contexte ne gère pas Id par lui-même? Simple parce que ça ne peut pas. Le contexte sait que l'ID est temporaire et sera régénéré en magasin mais le contexte ne connaît pas les détails de la configuration du magasin. Lorsque vous définissez Identity dans la base de données, vous configurez également la graine et l'incrémentation. Ces deux valeurs n'étant pas connues du contexte, le contexte n'est pas en mesure de dériver un identifiant temporaire unique valide qui n'est pas déjà utilisé par le magasin. Supposons que ce contexte crée à tort un identifiant temporaire et après que vous chargiez l'entité qui utilise déjà ce problème Id =.

Si je mets simplement à jour mon code pour utiliser un identifiant temporaire unique (je sais comment le magasin est configuré) cela fonctionnera. C'est l'une des raisons pour lesquelles je dois fournir des méthodes d'identification temporaire pour créer.

+0

Je ne pense pas qu'il y aura un problème si vous avez plusieurs objets parents avec 'ID = -1' avec des objets enfants. Il va simplement insérer parent pour parent, et utiliser '@@ IDENTITY' pour lier les enfants. La propriété 'Id' n'est pas utilisée pour créer le graphe d'objet. –

+0

Ce que vous avez décrit fonctionne pour une association indépendante. J'ai décrit l'association de clé étrangère. –

2

Vous devriez pouvoir personnaliser le modèle t4 qui génère vos classes pour supprimer la propriété de la méthode d'usine. Voir Danny Simmons' blog post pour plus d'informations sur la façon de créer le t4. Je n'ai pas testé la méthode d'usine résultante, mais je ne vois pas pourquoi cela ne fonctionnerait pas.

modifier cette méthode:

bool IncludePropertyInFactoryMethod(StructuralType factoryType, EdmProperty edmProperty) 
{ 
    if (edmProperty.Nullable) 
    { 
     return false; 
    } 

    if (edmProperty.DefaultValue != null) 
    { 
     return false; 
    } 

    if ((Accessibility.ForReadOnlyProperty(edmProperty) != "public" && Accessibility.ForWriteOnlyProperty(edmProperty) != "public") || 
     (factoryType != edmProperty.DeclaringType && Accessibility.ForWriteOnlyProperty(edmProperty) == "private") 
     ) 
    { 
     // There is no public part to the property. 
     return false; 
    } 

    /********* 
    * This was added: 
    */ 

    var identity = edmProperty.TypeUsage.Facets 
     .Where(f => f.Name == "StoreGeneratedPattern").FirstOrDefault(); 

    if (identity != null && identity.Value.ToString() == "Identity") 
    { 
     // property is "Identity" generated, so skip from the factory method. 
     return false; 
    } 

    /********* 
    * end of the custom code 
    */ 

    return true; 
} 
+2

cela fonctionnera pour le StorageModel. Si vous voulez utiliser le modèle conceptuel, vous devrez utiliser MetadataProperties. c'est-à-dire: property.MetadataProperties.TryGetValue (annotationNamespace + ": StoreGeneratedPattern", false, out storeGeneratedPatternProperty); –

+0

@SimonFrancesco, pouvez-vous s'il vous plaît élaborer plus sur la façon de faire, qu'est-ce que annotationNamespace? – Shimmy

+0

@Shimmy, Dans votre template T4 vous pouvez faire quelque chose comme: 'String annotationNamespace =" http://schemas.microsoft.com/ado/2009/02/edm/annotation "; \t \t \t MetadataProperty storeGeneratedPatternProperty = null; \t \t \t propriété.MetadataProperties.TryGetValue (annotationNamespace + ": StoreGeneratedPattern", false, out storeGeneratedPatternProperty); 'Si vous avez besoin d'une explication plus longue serait heureux de poster réponse :) –

Questions connexes