как связать SimpleMembership UserProfile с расширенным профилем с внешним ключом?

У меня закончились идеи о том, как заставить этот код работать так, как я хочу. У меня есть 3 модели, как указано ниже.

UserProfile and Player отношение один к одному

Сначала создается UserProfile, при регистрации электронная почта Подтверждение отправляется на указанную электронную почту пользователя. Как только они подтвердят свою учетную запись, они перенаправляются на свою первую страницу входа в систему, а в случае аутентификации перенаправляются на страницу «Создать нового игрока», чтобы предоставить дополнительную информацию о своем расширенном профиле. На странице создания я хочу, чтобы UserId вошедшего в систему пользователя было таким же, как и у создаваемого игрока. Когда я пробую код как есть, ответа нет, и ничего не добавляется и не сохраняется в базе данных.

Team and Player отношение "один ко многим"

Довольно много пояснений, игрок должен принадлежать команде. Это делает перенаправление на действие «Редактировать» невозможным, как Player.TeamId != null.

Профиль пользователя

[Table("UserProfile")]
public class UserProfile
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }

    public string UserName { get; set; }

    public string Email { get; set; }

    public virtual Player Player { get; set; }
}

Команда

    public class Team
    {
    [Key]
    [HiddenInput(DisplayValue = false)]
    public int TeamId { get; set; }

    [Display(Name = "Full Name:")]
    public string Name { get; set; }         

    public virtual ICollection<Player> Players { get; set; }        
}

Игрок

public class Player
{
    [Key]
    [ForeignKey("UserProfile")]
    [HiddenInput(DisplayValue = false)]
    public int UserId { get; set; }

    [Required]
    [Display(Name = "Full name")]
    public string FullName{ get; set; }        

    [Display(Name = "Team")]
    public int TeamId { get; set; }

    public virtual Team Team { get; set; }

    public virtual UserProfile UserProfile { get; set; }
 }

Сохранить проигрыватель

public void SavePlayer(Player player)
    {
        using (var context = new EFootballDb())
        {
            if (player.UserId == 0)
            {
                context.Players.Add(player);
            }
            else if (player.UserId > 0)
            {
                 var currentPlayer = context.Players                        
                    .Single(t => t.UserId == player.UserId);

                context.Entry(currentPlayer).CurrentValues.SetValues(player);                    
            }
            context.SaveChanges();
        }
    }

Создать действие

[HttpGet]
    public ActionResult Create()
    {
        PopulateTeamsDropDownList();
        return View();
    }

    [HttpPost]
    public ActionResult Create(Player model)
    {
       try
       {
           if (ModelState.IsValid)
           {
                _dataSource.SavePlayer(model);
                return RedirectToAction("Detail", "Team", new { id = model.TeamId });
            }
        }
        catch (Exception)
        {
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
        }
        PopulateTeamsDropDownList(model.TeamId);
        return View(model);
    }


private void PopulateTeamsDropDownList(object selectedTeams = null)
    {
        var teamsQuery = from d in _dataSource.Teams
                         orderby d.Name
                         select d;
        ViewBag.TeamID = new SelectList(teamsQuery, "TeamId", "Name", selectedTeams);
    }

Все прекрасно работает между Team and Player, но я просто не могу подключить UserProfile and Player к Creation. У кого-нибудь есть идеи или другой подход, который я должен попробовать? Заранее спасибо!


person Komengem    schedule 27.02.2013    source источник


Ответы (2)


ОБНОВЛЕНИЕ: я добавляю еще один способ сделать это. Это очень сложный способ, он работает, но в итоге вы этого не захотите.

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

Обратите внимание, что на этой странице есть две ошибки. 1) Используйте Add-Migration вместо Create-Migration 2) При обновлении UsersContext обязательно включите общий тип для DbSet. т.е.

public DbSet<Membership> Membership { get; set; }

Следуйте инструкциям по этой ссылке. Тогда вам нужно будет пройти еще немного.

Для моего решения я переместил все модели в библиотеку классов моделей, а UsersContext — в библиотеку классов DAL, но это не требуется.

Как только вы закончите миграцию, вы можете добавить базовый класс для ссылки на UserProfile следующим образом...

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

    public string FirstName { get; set; }

    public int UserProfileId { get; set; }

    [ForeignKey("UserProfileId")]
    public UserProfile UserProfile { get; set; }
}

Не забудьте обновить UsersContext. ПРИМЕЧАНИЕ. Вы можете переименовать UsersContext во что угодно.

public DbSet<User> Users { get; set; }

Последний шаг — добавить класс, который вызывается в вашем AccountController, вместо класса WebSecurity.CreateUserAndAccount(...)

public class CustomSimpleMembershipProvider
{
    private readonly UsersContext _db = new UsersContext();

    public bool CreateUser(string username, string password, string firstName)
    {
        var userCreated = false;

        WebSecurity.CreateUserAndAccount(username, password);

        var userProfile = _db.UserProfiles.FirstOrDefault(u => u.UserName == username);

        // Check if user already exists
        if (userProfile != null)
        {
            var member = new Model.User { FirstName = firstName, UserProfile = userProfile };

            try
            {
                _db.Users.Add(member);
                _db.SaveChanges();

                userCreated = true;
            }
            catch (Exception)
            {
                //Add logging or throw whatever exceptions needed
                throw;
            }

        }
        return userCreated;
    }
}

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

person JabberwockyDecompiler    schedule 28.02.2013
comment
Это не сработало для меня. Я пробовал несколько способов, но пока не повезло. Только добавляет и сохраняет профиль пользователя и ничего больше - person Komengem; 04.03.2013
comment
Komenge, Когда UserProfile сохраняется и вы смотрите на структуру базы данных, правильно ли она выглядит с взаимосвязью между вашим UserProfile и связанной таблицей? Кроме того, при попытке сохранить связанный объект вы получаете исключение? - person JabberwockyDecompiler; 06.03.2013
comment
Пока что я могу сказать, что UserProfile и другие связанные таблицы членства обновлены. У меня нет столбца в UserProfile для хранения данных для Player, поскольку я получаю UserId UserProfile и передаю его Player в качестве внешнего ключа. Я думаю, что WebSecurity.CurrentUserId или любые связанные с ним вызовы обнуляются. Я думаю, что это как-то связано с контекстом, который я использую, который отличается от того, что использует WebSecurity. - person Komengem; 06.03.2013
comment
Никаких исключений не выдается, и у меня есть исключения, если данные не сохраняются. - person Komengem; 06.03.2013
comment
Извините за задержку с ответом. Ваши данные игрока должны быть добавлены отдельно. Используйте класс CustomSimpleMembershipProvider, который я описал выше, вместо User, вы добавите данные таблицы Player. - person JabberwockyDecompiler; 27.03.2013

Гораздо более простой способ сделать это — просто создать класс с виртуальным внешним ключом для UserProfile. Виртуальный для ленивой загрузки. Причина, по которой я сделал это таким образом, заключается в том, что как только я перешел к OAuth, все стало работать намного проще.

   public class User
    {
        public int Id { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public int UserProfileId { get; set; }

        [ForeignKey("UserProfileId")]
        public virtual UserProfile UserProfile { get; set; }
    }

Затем переместите свой UsersContext из AccountModels.cs в свой собственный контекст следующим образом.

public class OAuthNoMigrateContext : DbContext
{
    public OAuthNoMigrateContext()
        : base("DefaultConnection")
    {
    }

    public DbSet<User> Users { get; set; }
    public DbSet<UserProfile> UserProfiles { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {

    }
}

Создайте аналогичный CustomSimpleMembershipProvider, как в моем другом ответе.

public class CustomSimpleMembershipProvider
{
    private readonly OAuthNoMigrateContext _db = new OAuthNoMigrateContext();

    public bool CreateUser(string username, string password, User user)
    {
        var userCreated = false;

        WebSecurity.CreateUserAndAccount(username, password);

        var userProfile = _db.UserProfiles.FirstOrDefault(u => u.UserName == username);

        // Check if user exists
        if (userProfile != null)
        {
            user.UserProfile = userProfile;

            try
            {
                _db.Users.Add(user);
                _db.SaveChanges();

                userCreated = true;
            }
            catch (Exception)
            {
                //Add logging or throw whatever exceptions needed
                throw;
            }

        }
        return userCreated;
    }
}

Добавьте это в Application_Start

Database.SetInitializer(new OAuthNoMigrateInitializer());

Примечание. Мне нравится создавать метод, который проверяет наличие пользователей и вызывается из файла Application_Start. Инициализация запускается при первом вызове БД, так и происходит.

private static void ViewDb()
    {
        using (var context = new OAuthNoMigrateContext())
        {
            var user = context.Users.Where(u => u.FirstName.StartsWith("T"));
        }
    }

Это вызывает этот инициализатор

public class OAuthNoMigrateInitializer : DropCreateDatabaseAlways<OAuthNoMigrateContext>
{
    private readonly CustomSimpleMembershipProvider _provider = new CustomSimpleMembershipProvider();

    protected override sealed void Seed(OAuthNoMigrateContext context)
    {
        //Initialize UserProfile
        WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);

        AddUsers(context);
    }

    private void AddUsers(OAuthNoMigrateContext context)
    {
        if (WebSecurity.UserExists("username")) return;

        var user = new User
        {
            FirstName = "FName",
            LastName = "LName"
        };

        _provider.CreateUser("username", "password", user);
    }
}
person JabberwockyDecompiler    schedule 27.03.2013
comment
Не забудьте закомментировать или удалить [InitializeSimpleMembership] в AccountController.cs, так как вы уже выполняете инициализацию в семени. - person JabberwockyDecompiler; 27.03.2013