Entity Framework 6-GUID als Primärschlüssel: Der Wert NULL kann nicht in die Spalte 'Id', Tabelle 'FileStore' eingefügt werden. Spalte erlaubt keine Nullen

73

Ich habe eine Entität mit dem Primärschlüssel "Id", die Guid ist:

public class FileStore
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Path { get; set; }
}

Und einige Konfigurationen:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<FileStore>().Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    base.OnModelCreating(modelBuilder);
}

Wenn ich versuche, einen Datensatz einzufügen, wird folgende Fehlermeldung angezeigt:

Der Wert NULL kann nicht in die Spalte 'Id', Tabelle 'FileStore' eingefügt werden. Spalte erlaubt keine Nullen. INSERT schlägt fehl. \ R \ nDie Anweisung wurde beendet.

Ich möchte Guid nicht manuell generieren. Ich möchte nur einen Datensatz einfügen und Idvon SQL Server generiert werden. Wenn ich festlege .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity), Idist die Spalte in SQL Server keine Identitätsspalte.

Wie kann ich Entity Framework so konfigurieren, dass Guid in SQL Server automatisch generiert wird?

Algirdas
quelle
haben Sie versucht Anmerkung zu setzen , [DatabaseGenerated(DatabaseGeneratedOption.Identity)]bevor public Guid ID {get; set;}?
Haben Sie die Konfiguration nach dem ersten Erstellen der Tabelle hinzugefügt?
user3411327
Inanikian, ich denke, fließendes API wird bevorzugt, da OnModelCreatinges hier überschrieben wird.
Blaise
1
Ich sehe, Sie haben keine der Antworten akzeptiert. Warst du mit keinem zufrieden? Wenn ja, lass es mich wissen und ich werde einen anderen posten, das funktioniert. Ich fühle mich nur ein bisschen faul und möchte nicht tippen, wenn ich keinen Repräsentanten bekomme. :)
Konrad Viltersten

Antworten:

96

Zusätzlich zum Hinzufügen dieser Attribute zu Ihrer ID-Spalte:

[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }

In Ihrer Migration sollten Sie Ihre ändern CreateTable, um die defaultValueSQLEigenschaft zu Ihrer Spalte hinzuzufügen , dh:

Id = c.Guid(nullable: false, identity: true, defaultValueSql: "newsequentialid()"),

Dies verhindert, dass Sie Ihre Datenbank manuell berühren müssen, was Sie, wie Sie in den Kommentaren ausgeführt haben, mit Code First vermeiden möchten.

Lichtjahre
quelle
10
Diese Azure-Notiz hat mir sicher einige Zeit gespart. Vielen Dank.
Stanley.Goldman
3
newsequentialid () wird seit Azure SQL V12 msdn.microsoft.com/en-us/library/ms189786.aspx unterstützt .
DalSoft
1
Was ist das und wo verwenden wir es? " Id = c.Guid(nullable: false, identity: true, defaultValueSql: "newsequentialid()"),"
deadManN
Ich weiß, dass es eine Weile her ist, aber für zukünftige Leser wird die ID-Zuweisungsanweisung in die erste Code-Migrationsdatei aufgenommen.
Byrel Mitchell
18

Versuche dies :

public class FileStore
 {
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
   public Guid Id { get; set; }
   public string Name { get; set; }
   public string Path { get; set; }
 }

Sie können diesen SO-Beitrag überprüfen .

Gemeinschaft
quelle
1
Ich denke jedoch, dass Sie durch Überschreiben von OnModelCreating dieselbe Einstellung wie bei Verwendung dieses Attributs festlegen können. Ich werde den Beitrag lesen. Vielen Dank
Algirdas
9

Sie können den Standardwert Ihrer ID in Ihrer Datenbank auf newsequentialid () oder newid () setzen. Dann sollte die Identitätskonfiguration von EF funktionieren.

Murdock
quelle
1
Vielen Dank für die Antwort, aber ich verwende zuerst Code für das Projekt. Ich möchte also keine manuellen Schritte zum Erstellen einer Datenbank ausführen.
Algirdas
2
@Algirdas Warum gerade dann eine Guid.NewGuid () im Konstruktor jeder Entität haben
Murdock
Ich habe keine andere Lösung gefunden und daher Guid.NewGuid () zum Konstruktor hinzugefügt. Vielen Dank
Algirdas
1
@Algirdas Nur zu Ihrer Information, Guid.NewGuid () indiziert keinen Willen in der Datenbank. Sie sollten stattdessen eine sequentielle Guid verwenden.
Josh Mouch
6

Dies funktioniert für mich (kein Azure), SQL 2008 R2 auf dem Dev-Server oder localdb \ mssqllocaldb auf der lokalen Workstation. Hinweis: Die Entität fügt die Spalten Create, CreateBy, Modified, ModifiedBy und Version hinzu.

public class Carrier : Entity
{
    public Guid Id { get; set; }
    public string Code { get; set; }
    public string Name { get; set; }
}

Erstellen Sie dann eine Zuordnungskonfigurationsklasse

public class CarrierMap : EntityTypeConfiguration<Carrier>
{
    public CarrierMap()
    {
        HasKey(p => p.Id);

        Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

        Property(p => p.Code)
            .HasMaxLength(4)
            .IsRequired()
            .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute { IsClustered = true, IsUnique = true }));

        Property(p => p.Name).HasMaxLength(255).IsRequired();
        Property(p => p.Created).HasPrecision(7).IsRequired();
        Property(p => p.Modified)
            .HasColumnAnnotation("IX_Modified", new IndexAnnotation(new IndexAttribute()))
            .HasPrecision(7)
            .IsRequired();
        Property(p => p.CreatedBy).HasMaxLength(50).IsRequired();
        Property(p => p.ModifiedBy).HasMaxLength(50).IsRequired();
        Property(p => p.Version).IsRowVersion();
    }
}

Dadurch wird in der anfänglichen DbMigration eine Up-Methode erstellt, wenn Sie eine Add-Migration wie diese ausführen

        CreateTable(
            "scoFreightRate.Carrier",
            c => new
                {
                    Id = c.Guid(nullable: false, identity: true),
                    Code = c.String(nullable: false, maxLength: 4),
                    Name = c.String(nullable: false, maxLength: 255),
                    Created = c.DateTimeOffset(nullable: false, precision: 7),
                    CreatedBy = c.String(nullable: false, maxLength: 50),
                    Modified = c.DateTimeOffset(nullable: false, precision: 7,
                        annotations: new Dictionary<string, AnnotationValues>
                        {
                            { 
                                "IX_Modified",
                                new AnnotationValues(oldValue: null, newValue: "IndexAnnotation: { }")
                            },
                        }),
                    ModifiedBy = c.String(nullable: false, maxLength: 50),
                    Version = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
                })
            .PrimaryKey(t => t.Id)
            .Index(t => t.Code, unique: true, clustered: true);

Hinweis: Keine Sorge, dass die ID-Spalten keinen Standardwert erhalten

Führen Sie nun Update-Database aus, und Sie sollten eine Tabellendefinition in Ihrer Datenbank wie folgt erhalten:

CREATE TABLE [scoFreightRate].[Carrier] (
    [Id]         UNIQUEIDENTIFIER   DEFAULT (newsequentialid()) NOT NULL,
    [Code]       NVARCHAR (4)       NOT NULL,
    [Name]       NVARCHAR (255)     NOT NULL,
    [Created]    DATETIMEOFFSET (7) NOT NULL,
    [CreatedBy]  NVARCHAR (50)      NOT NULL,
    [Modified]   DATETIMEOFFSET (7) NOT NULL,
    [ModifiedBy] NVARCHAR (50)      NOT NULL,
    [Version]    ROWVERSION         NOT NULL,
    CONSTRAINT [PK_scoFreightRate.Carrier] PRIMARY KEY NONCLUSTERED ([Id] ASC)
);


GO
CREATE UNIQUE CLUSTERED INDEX [IX_Code]
    ON [scoFreightRate].[Carrier]([Code] ASC);

Hinweis: Wir haben den SqlServerMigrationSqlGenerator überschrieben, um sicherzustellen, dass der Primärschlüssel NICHT zu einem Clustered-Index wird, da wir unsere Entwickler dazu ermutigen, einen besseren Clustered-Index für Tabellen festzulegen

public class OurMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
    protected override void Generate(AddPrimaryKeyOperation addPrimaryKeyOperation)
    {
        if (addPrimaryKeyOperation == null) throw new ArgumentNullException("addPrimaryKeyOperation");
        if (!addPrimaryKeyOperation.Table.Contains("__MigrationHistory"))
            addPrimaryKeyOperation.IsClustered = false;
        base.Generate(addPrimaryKeyOperation);
    }

    protected override void Generate(CreateTableOperation createTableOperation)
    {
        if (createTableOperation == null) throw new ArgumentNullException("createTableOperation");
        if (!createTableOperation.Name.Contains("__MigrationHistory"))
            createTableOperation.PrimaryKey.IsClustered = false;
        base.Generate(createTableOperation);
    }

    protected override void Generate(MoveTableOperation moveTableOperation)
    {
        if (moveTableOperation == null) throw new ArgumentNullException("moveTableOperation");
        if (!moveTableOperation.CreateTableOperation.Name.Contains("__MigrationHistory")) moveTableOperation.CreateTableOperation.PrimaryKey.IsClustered = false;
        base.Generate(moveTableOperation);
    }
}
Allan
quelle
5

Es ist mir schon mal passiert.

Wenn die Tabelle erstellt und .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)später hinzugefügt wurde , konnte die Codemigration der Guid-Spalte keinen Standardwert zuweisen.

Die Reparatur:

Alles, was wir brauchen, ist, in die Datenbank zu gehen, die Spalte ID auszuwählen und newsequentialid()manuell hinzuzufügen Default Value or Binding.

Die dbo .__ MigrationHistory-Tabelle muss nicht aktualisiert werden.

Ich hoffe es hilft.


Die Lösung der Zugabe New Guid()wird im allgemeinen nicht bevorzugt, weil in der Theorie gibt ist die Möglichkeit , dass Sie ein Duplikat aus Versehen bekommen könnten.


Und Sie sollten sich keine Gedanken über die direkte Bearbeitung in der Datenbank machen. Alles, was Entity Framework tut, ist die Automatisierung eines Teils unserer Datenbankarbeit.

Übersetzen

.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)

in

[Id] [uniqueidentifier] NOT NULL DEFAULT newsequentialid(),

Wenn unsere EF eine Sache irgendwie übersehen hat und den Standardwert für uns nicht hinzugefügt hat, fügen Sie ihn einfach manuell hinzu.

Blaise
quelle
1
Wenn dies der Primary KeyFall ist , ist es unmöglich, einen doppelten Guid-Schlüssel in diesem Feld zu haben. Weil Primary Keywird eine eindeutige Einschränkung haben. Der Datenbankserver lehnt doppelte Primärschlüssel ab.
AechoLiu
4

Demnach wird DatabaseGeneratedOption.Identity von einer bestimmten Migration nicht erkannt, wenn sie nach dem Erstellen der Tabelle hinzugefügt wird. Dies ist der Fall, auf den ich stoße. Also habe ich die Datenbank und diese spezifische Migration gelöscht und eine neue Migration hinzugefügt, schließlich die Datenbank aktualisiert, dann funktioniert alles wie erwartet. Ich verwende EF 6.1, SQL2014 und VS2013.

stt106
quelle
Nur zur Information: In ef7 RC1 funktioniert [Key, DatabaseGenerated (DatabaseGeneratedOption.Identity)] gut ... Aber auch nur mit neuer Datenbank getestet ...
Miroslav Siska
4

Entity Framework - Verwenden Sie eine Guid als Primärschlüssel

Die Verwendung eines Guid als Primärschlüssel für Ihre Tabellen erfordert bei Verwendung von Entity Framework etwas mehr Aufwand als bei Verwendung einer Ganzzahl. Der Einrichtungsprozess ist unkompliziert, nachdem Sie gelesen / gezeigt haben, wie es geht.

Der Prozess unterscheidet sich geringfügig für die Ansätze Code First und Database First. Dieser Beitrag beschreibt beide Techniken.

Geben Sie hier die Bildbeschreibung ein

Code zuerst

Die Verwendung eines Guid als Primärschlüssel bei der ersten Code-Vorgehensweise ist einfach. Fügen Sie beim Erstellen Ihrer Entität das DatabaseGenerated-Attribut wie unten gezeigt zu Ihrer Primärschlüsseleigenschaft hinzu.

[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }

Das Entitätsframework erstellt die Spalte wie erwartet mit einem Primärschlüssel und einem Datentyp mit eindeutiger Kennung.

codefirst-defaultvalue

Beachten Sie auch sehr wichtig, dass der Standardwert für die Spalte auf festgelegt wurde (newsequentialid()). Dies erzeugt eine neue sequentielle (kontinuierliche) Guid für jede Zeile. Wenn Sie so geneigt newid()wären , könnten Sie dies in ) ändern , was zu einer völlig zufälligen Guid für jede neue Zeile führen würde. Dies wird jedes Mal gelöscht, wenn Ihre Datenbank gelöscht und neu erstellt wird. Dies funktioniert also besser, wenn Sie den Ansatz "Database First" verwenden.

Datenbank zuerst

Der erste Datenbankansatz folgt einer ähnlichen Linie wie der erste Code-Ansatz, Sie müssen Ihr Modell jedoch manuell bearbeiten, damit es funktioniert.

Stellen Sie sicher, dass Sie die Primärschlüsselspalte bearbeiten und die Funktion (newsequentialid ()) oder (newid ()) als Standardwert hinzufügen, bevor Sie etwas tun.

Geben Sie hier die Bildbeschreibung ein

Öffnen Sie als Nächstes Ihr EDMX-Diagramm, wählen Sie die entsprechende Eigenschaft aus und öffnen Sie das Eigenschaftenfenster. Stellen Sie sicher, dass StoreGeneratedPattern auf Identität festgelegt ist.

databasefirst-model

Sie müssen Ihrer Entität keine ID in Ihrem Code geben, die automatisch für Sie ausgefüllt wird, nachdem die Entität in die Datenbank übernommen wurde.

using (ApplicationDbContext context = new ApplicationDbContext())
{
    var person = new Person
                     {
                         FirstName = "Random",
                         LastName = "Person";
                     };

    context.People.Add(person);
    context.SaveChanges();
    Console.WriteLine(person.Id);
}

Wichtiger Hinweis: Ihr Guid-Feld MUSS ein Primärschlüssel sein, sonst funktioniert dies nicht. Entity Framework gibt Ihnen eine ziemlich kryptische Fehlermeldung!

Zusammenfassung

Guid (Global Unique Identifiers) kann problemlos als Primärschlüssel in Entity Framework verwendet werden. Je nachdem, welchen Ansatz Sie wählen, ist ein wenig zusätzlicher Aufwand erforderlich. Wenn Sie den Code First-Ansatz verwenden, fügen Sie Ihrem Schlüsselfeld das Attribut DatabaseGenerated hinzu. Setzen Sie beim Ansatz "Database First" das StoredGeneratedPattern in Ihrem Modell explizit auf "Identität".

[1]: https://i.stack.imgur.com/IxGdd.png
[2]: https://i.stack.imgur.com/Qssea.png
Vinu Das
quelle
2

Wenn Sie Code-First ausführen und bereits eine Datenbank haben:

public override void Up()
{
    AlterColumn("dbo.MyTable","Id", c =>  c.Guid(nullable: false, identity: true, defaultValueSql: "newsequentialid()"));
}
Jemand OnEarth
quelle
1

Du kannst nicht. Sie werden / tun eine Menge Dinge zu brechen. Wie Beziehungen. Dies hängt davon ab, dass die Nummer zurückgezogen wird, was EF nicht so tun kann, wie Sie es eingerichtet haben. Der Preis für das Brechen jedes Musters, das es gibt.

Generieren Sie die GUID in der C # -Ebene, damit die Beziehungen weiter funktionieren können.

TomTom
quelle
0

Und was ist so etwas?

public class Carrier : Entity
{
    public Carrier()
    {
         this.Id = Guid.NewGuid();
    }  
    public Guid Id { get; set; }
    public string Code { get; set; }
    public string Name { get; set; }
}
Miroslav Siska
quelle
2
Dies schafft Fragmentierung, es ist kein Problem für kleine DB, aber große DB sollte sequentielle Guid verwenden
Calimero100582
1
@Miroslav, ich gehe davon aus, dass Sie über meine Antwort oben sprechen. Wenn ja, fügen Sie den Konstruktor nicht hinzu. Wenn die Entität in der Datenbank gespeichert wird, wird von der Datenbank eine sequentielle Guid erstellt.
Allan