Как добавить нового пользователя и запросить в одной транзакции с использованием удостоверения ASP.NET Core 2.1? - PullRequest
0 голосов
/ 14 октября 2019

Я пытаюсь добавить нового пользователя и некоторые другие связанные объекты, включая заявку в качестве одной транзакции. Мои занятия в основном определены ниже. Обратите внимание, что я использую int первичные ключи в своем классе User и это столбец идентификаторов (значение предоставляется базой данных при вставке).

public class User : IdentityUser<int>
{
    // custom props here
}

public class UserClaim : IdentityUserClaim<int>
{
    // i actually haven't added anything to this class, it's mainly here so 
    // i don't have to keep specifying the user's primary key type
}

public class OtherEntity 
{
    public int UserId { get; set; }

    [ForeignKey(nameof(UserId))]
    public User User { get; set; }

    // other stuff
}

Затем я хочу добавить пользователяи т.д. с базой данных примерно так:

User user = new User(){ /* set props */ };

OtherEntity other = new OtherEntity()
{
    User = user
};

UserClaim claim = new UserClaim()
{
    /* this is where the problem is */
    UserId = user.Id,
    ClaimType = "my-claim-type",
    ClaimValue = "my-claim-value"
};

context.AddRange(user, other, claim);
context.SaveChanges();

Я могу легко связать User с OtherEntity, потому что я настроил свойство навигации, поэтому я могу просто добавить к нему Userи структура сущности заботится о заполнении столбца UserId. Я не могу сделать это с UserClaim, потому что у него нет свойства навигации. Я мог бы позвонить context.SaveChanges() после добавления User, и объектная структура получит User.Id, созданный для меня базой данных, которую я мог бы использовать для установки UserId на UserClaim, но это означало бы две транзакции.

Я попытался добавить свойство навигации в мое определение UserClaim следующим образом:

public class UserClaim : IdentityUserClaim<int>
{
    [ForeignKey(nameof(UserId))]
    public User User { get; set; }
}

Но я получаю следующую ошибку времени выполнения:

InvalidOperationException:Отношение «UserClaim.User» к «Пользователю» со свойствами внешнего ключа {'UserId': int} не может быть нацелено на первичный ключ {'Id': int}, поскольку оно несовместимо. Настройте основной ключ или набор совместимых свойств внешнего ключа для этого отношения.

Существует ли способ создания пользователя и заявки в одной транзакции?

Ответы [ 2 ]

0 голосов
/ 15 октября 2019

Мой вопрос должен был звучать так: «Как добавить свойства навигации между классами удостоверений ASP.NET?»

Если бы я искал ответ на этот вопрос, я бы нашел документы для Microsoft объясняя, как это сделать!

Связанные выше документы рассказывают, как добавить свойство навигации User.UserClaims:

public class User : IdentityUser<int>
{
    public virtual ICollection<UserClaim> Claims { get; set; }
}

public class DataContext : IdentityDbContext</* Identity classes */>
{
    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<User>(e =>
        {
            e.HasMany(u => u.Claims)
             .WithOne()
             .HasForeignKey(c => c.UserId) 
             .IsRequired();
        });
    }
}

Но это не показывает, как сделать обратное свойство навигации UserClaim.User. Я понял, что это можно сделать следующим образом:

public class UserClaim : IdentityUserClaim<int>
{
    public virtual User User { get; set; }
}

public class DataContext : IdentityDbContext</* Identity classes */>
{
    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<User>(e =>
        {
            e.HasMany(u => u.Claims)
             .WithOne(c => c.User) // <- this line is different
             .HasForeignKey(c => c.UserId) 
             .IsRequired();
        });
    }
}

Затем вы можете создать нового пользователя и заявить права одновременно на мой вопрос:

User user = new User(){ /* set props */ };

UserClaim claim = new UserClaim()
{
    User = user, // <- this is the bit you can't do without the nav. property
    ClaimType = "my-claim-type",
    ClaimValue = "my-claim-value"
};

context.AddRange(user, claim);
context.SaveChanges();

Я думаю, этоздравый смысл, хотя я не осознавал, пока не проверил фактический SQL, попавший в базу данных, но для этого все равно потребуется две поездки в базу данных, даже если мы вызываем .SaveChanges() только один раз! Entity Framework сначала сохранит User и получит идентификатор для вставленной строки, который затем будет использовать при вставке UserClaim.

0 голосов
/ 14 октября 2019

Вставка связанных данных документирована в Entity Framework: https://docs.microsoft.com/pl-pl/ef/core/saving/related-data И это также хорошо описано в других темах, например: Вставка внешнего ключа Entity Framework с Auto-Id

Каждый из вас должен правильно установить отношения для отношений (внешние ключи) в своих моделях (без них EF не знает, что делать), и когда вы добавляете его, вам нужно начинать с начала, поэтому UserClaimдолжен быть установлен из вашей сущности User, например

    var user = new User(){
    //properites
    OtherEntity = new OtherEntity()
    {
        Id = 0, /*will be set automatically*/
        UserId = 0 /*will be set automatically*/
        /* other properites */
    };
    Claim = new UserClaim(){
        Id = 0, /*will be set automatically*/
        UserId = 0 /*will be set automatically*/
        /* other properites */
    }
}

ontext.Add(user);
context.SaveChanges();

Вы не предоставили всю информацию о ваших отношениях, я только что предположил это из вашего кода.

PS. AddRange имеет только один параметр.

РЕДАКТИРОВАТЬ: Чтобы уточнить мой ответ, чтобы все работало, необходимо вызвать AddRange / Add с вашей базовой сущностью и отношениями внутри, в деревеманера. Но сначала вам нужно настроить ваши модели, что-то в этом роде.

public class User : IdentityUser<int>
{
    [ForeignKey("UserClaimId")]
    public virtual UserClaim Claim {get;set;}
}

public class UserClaim : IdentityUserClaim<int>
{
    [ForeignKey("UserId")]
    public virtual User User {get;set;}
}

public class OtherEntity 
{
    public int UserId { get; set; }

    [ForeignKey("UserId")]
    public User User { get; set; }

    // other stuff
}

Вы также можете использовать OnModelCreating для настройки Entite, как описано в документации: https://docs.microsoft.com/en-us/ef/core/modeling/relationships

Entity Framework прямо сейчасничего не знаю об отношениях в базе данных, типах данных и т. д.

Обновлен пример кода

Изменить, недостаточно репутации, чтобы добавить комментарий кваш ответ

Ну, EF все еще нужно отслеживать сущности. Мне нужно написать и запустить несколько примеров, чтобы проверить, есть ли способ улучшить поездки в базу данных. Может быть, кто-то мог бы рассказать вам больше о механизме добавления связанных данных и о том, есть ли способ его улучшить. Есть несколько библиотек, которые помогают улучшить производительность сохранения изменений в базе данных, например: https://entityframework -extensions.net Вы можете попробовать это.

...