I'm trying to rename my Identity 2.0 tables to have my app name before them. So I've overridden OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<IdentityUser>().ToTable("appname_Users");
modelBuilder.Entity<IdentityRole>().ToTable("appname_Roles");
modelBuilder.Entity<IdentityUserClaim>().ToTable("appname_UserClaims");
modelBuilder.Entity<IdentityUserLogin>().ToTable("appname_UserLogins");
modelBuilder.Entity<IdentityUserRole>().ToTable("appname_UserRoles");
}
I deleted the Migrations folder, made sure these tables did not exist, and ran:
enable-migrations -Force
update-database
When I try to login to the site, it still says it can't find dbo.AspNetUsers.
When I check the migration script, I do see the following:
CreateTable(
"dbo.AspNetUsers",
c => new
{
Id = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.appname_Users", t => t.Id)
.Index(t => t.Id);
Where is it even getting the idea that it needs AspNetUsers? I've scoured documentation and can't find out what's going on. Any help would be appreciated.
Delete all your identity tables from database manually. Then in onModelCreating insert
modelBuilder.Entity<ApplicationUser>().ToTable("appname_Users");
modelBuilder.Entity<IdentityUserRole>().ToTable("appname_UserRoles");
modelBuilder.Entity<IdentityUserLogin>().ToTable("appname_UserLogins");
modelBuilder.Entity<IdentityUserClaim>().ToTable("appname_UserClaims");
modelBuilder.Entity<IdentityRole>().ToTable("appname_Roles");
if you create custom identity classes then use
modelBuilder.Entity<ApplicationUser>().ToTable("appname_Users");
modelBuilder.Entity<ApplicationRole>().HasKey<string>(r => r.Id).ToTable("appname_Roles");
modelBuilder.Entity<ApplicationUser>().HasMany<ApplicationUserRole>((ApplicationUser u) => u.UserRoles);
modelBuilder.Entity<ApplicationUserRole>().HasKey(r => new { UserId = r.UserId, RoleId = r.RoleId }).ToTable("appname_UserRoles");
This way works for me.........
Related
I try to apply migrations, first three of them to create table, the last one - insert data.
When i run php bin/console doctrine:migrations:migrate it gives me an error "There is no active transaction" after each migration and had stoped migration. So i had to run migrations:migrate 4 times.
Whats could be the problem?
This issue existed before, but now is visible after PHP 8 PDO.
I will quote a great explanation located at your
vendor/doctrine/migrations/docs/en/explanation/implicit-commits.rst:
Implicit commits
Since PHP8, if you are using some platforms with some drivers such as
MySQL with PDO, you may get an error that you did not get before when
using this library: There is no active transaction. It comes from
the fact that some platforms like MySQL or Oracle do not support DDL
statements (CREATE TABLE, ALTER TABLE, etc.) in transactions.
The issue existed before PHP 8 but is now made visible by e.g. PDO,
which now produces the above error message when this library attempts
to commit a transaction that has already been commited before.
Consider the following migration.
public function up(Schema $schema): void
{
$users = [
['name' => 'mike', 'id' => 1],
['name' => 'jwage', 'id' => 2],
['name' => 'ocramius', 'id' => 3],
];
foreach ($users as $user) {
$this->addSql('UPDATE user SET happy = true WHERE name = :name AND id = :id', $user);
}
$this->addSql('CREATE TABLE example_table (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
}
When you run that migration, what actually happens with some platforms
is you get the updates inside an implicitly commited transaction, then
the CREATE TABLE happens outside that transaction, and then there
is an attempt to commit an non-existent transaction.
In that sort of situation, if you still wish to get the DML statements
inside a transaction, we recommend you split the migration in 2
migrations, as follows.
final class Version20210401193057 extends AbstractMigration
{
public function up(Schema $schema): void
{
$users = [
['name' => 'mike', 'id' => 1],
['name' => 'jwage', 'id' => 2],
['name' => 'ocramius', 'id' => 3],
];
foreach ($users as $user) {
$this->addSql('UPDATE user SET happy = true WHERE name = :name AND id = :id', $user);
}
}
}
final class Version20210401193058 extends AbstractMigration
{
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE example_table (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))');
}
public function isTransactional(): bool
{
return false;
}
}
Please refer to the manual of your database platform to know if you
need to do this or not.
At the moment, this library checks if there is an active transaction
before commiting it, which means you should not encouter the error
described above. It will not be the case in the next major version
though, and you should prepare for that.
To help you deal with this issue, the library features a configuration
key called transactional. Setting it to false will cause new
migrations to be generated with the override method above, making new
migrations non-transactional by default.
Solution 1
Disable the parameter transactional in your migrations config file as explained here.
Solution 2
Disable transactional migration only this time by adding --all-or-nothing=0 in your php bin/console doctrine:migrations:migrate command.
If you use PHP 8.0, implements "isTransactional" in migration class and return false (See https://github.com/doctrine/DoctrineMigrationsBundle/issues/393)
For me following code worked
public function isTransactional(): bool
{
return false;
}
Been pulling my hair out over this for a day and exhausted my google foo. I have inherited a Silverstripe 3.4 site that we have upgraded to 4.4. But something odd has been going on with certain images after running MigrateFilesTask.
I think this is something to do with a file being attached to an unversioned objects that are accessed via ModelAdmin. But I have not been able to find a definitive solution.
Code for this object below. Problems experienced are under it.
<?php
use SilverStripe\Assets\Image;
use gorriecoe\Link\Models\Link;
use SilverStripe\Security\Member;
use SilverStripe\Control\Controller;
use SilverStripe\View\Parsers\URLSegmentFilter;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\FieldGroup;
use gorriecoe\LinkField\LinkField;
use SilverStripe\TagField\TagField;
use SilverStripe\ORM\DataObject;
use SilverStripe\SelectUpload\SelectUploadField;
class Person extends DataObject
{
private static $db = array(
'FirstName' => 'Varchar(128)',
'LastName' => 'Varchar(128)',
'Role' => 'Varchar(128)',
'DirectDialNumber' => 'Varchar(128)',
'Email' => 'Varchar(128)',
'CellphoneNumber' => 'Varchar(30)',
'DirectDial' => 'Varchar(30)',
'UrlSegment' => 'Varchar(255)',
'Blurb' => 'HTMLText',
'SortOrder' => 'Int'
);
private static $has_one = array(
'Image' => Image::class,
'Office' => 'Office',
'LinkedIn' => Link::class,
'Member' => Member::class
);
private static $many_many = array(
'Interests' => 'Section'
);
private static $belongs_many_many = array(
'ElementCollection' => 'ElementCollection'
);
static $sort_fields = array(
'FirstName' => 'First name',
'LastName' => 'Last name',
'Role' => 'Role'
);
private static $summary_fields = array(
'Name' => 'Name',
'Role' => 'Role',
'Office.Name' => 'Office'
);
private static $searchable_fields = array(
'FirstName',
'LastName',
'Role'
);
// For use with the ElementCollection
public static $templates = array(
'ElementPeople' => 'Default',
'ElementPeopleAlternative' => 'Alternative'
);
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->removeByName( ['SortOrder', 'ElementCollection', 'FirstName', 'LastName', 'Interests'] );
$firstname = TextField::create('FirstName', 'First name');
$lastname = TextField::create('LastName', 'Last name');
$fields->addFieldsToTab('Root.Main', FieldGroup::create($firstname, $lastname)->setTitle('Name')->setName('Name'), 'Role');
$image = UploadField::create('Image', 'Photo');
$image->setFolderName('Uploads/People');
$image->setCanSelectFolder(false);
$fields->addFieldToTab('Root.Main', $image);
$linkedin = LinkField::create('LinkedIn', 'LinkedIn', $this);
$fields->addFieldToTab('Root.Main', $linkedin);
$interests = TagField::create(
'Interests',
'Interests Tags',
Section::get(),
$this->Interests()
)->setShouldLazyLoad(true)
->setCanCreate(false);
$fields->addFieldToTab('Root.Main', $interests);
return $fields;
}
public function onBeforeWrite()
{
$count = 1;
$this->UrlSegment = $this->generateURLSegment();
while (!$this->validURLSegment()) {
$this->UrlSegment = preg_replace('/-[0-9]+$/', null, $this->UrlSegment) . '-' . $count;
$count++;
}
parent::onBeforeWrite();
}
}
Problem #1 is after running MigrateFileTask, ALL existing images attached to instances of this class get moved from /assets/Uploads/People to /assets/.protected/Uploads/People. The confusing part here is that there is one other class called Company that is structurally near identical, yet images for that remain in /assets/Uploads/Companies as expected.
Problem #2 is if I create a new Person object and attach an image, that image is in Draft, sitting in /assets/.protected/Uploads/People with no method of actually publishing it. Meanwhile, if I do the same with a Company object, the image is still in Draft, but I can see it in the CMS.
Can someone offer some guidance on the above? At this point I'd be happy to just be able for images to be published when the DO is and I'll manually go through every single Person record and hit save myself just to get this upgrade over the line.
You should be able to fix this issue by adding the image to your DataObejct's owns property. Basically add this:
private static $owns = [
'Image'
];
Basically owns tells a DataObject which objects to publish when it is saved:
More info in the docs: https://docs.silverstripe.org/en/4/developer_guides/model/versioning/#defining-ownership-between-related-versioned-dataobjects
The cause of issue #1 was found. Leaving this here in case it helps someone in future:
The database table File has a row for every File and Folder in the system. This table has a column called "CanViewType". It exists in both Silverstripe 3 and 4.
For the particular Folder that was causing trouble during the Migration process, I found it was the only one with that column set to "OnlyTheseUsers". The rest were set to "Inherit". This was the state of the table before the upgrade.
I'm unsure how or by what mechanism that row is ever changed, but the solution to problem #1 was to manually change that field to "Inherit" before running FileMigrationTask.
Issue #2 persists, but it looks like there are two very different issues here.
OK. So sorted problem #2 finally (see other answer for solution to #1), but it's put a massive dent in our confidence in Silverstripe and sparked a meeting here.
Code for future readers:
In your unversioned DataObject, add this. In this case, my file object is called "Image". If you had more than one file to publish on save, you would have to add one of these IF blocks for each.
public function onAfterWrite()
{
if ($this->Image()->exists() && !$this->Image()->isPublished()) {
$this->Image()->doPublish();
}
parent::onAfterWrite();
}
SIDENOTE:
This Object/File relationship really is a strange design choice. Are there actually any situations where you would want to attach a file or image to a data object and NOT publish that file at the same time as you save/publish the object/page? Developers even need to explicitly define that on Versioned objects using $owns - which I'm happy to bet that most developers have to add more times than NOT. Which should really tell a us something is around wrong way.
Adding an image to a CMS system shouldn't be hard. It should take reading basic docs at the most. Not Googling, deep API doc dives (which don't answer much) or posting on StackOverlfow (where no one really knows the answer) over three days. It's an image. A core function of the product.
I've been working with SS since v2.4 and seen all the hard lessons learned to get to v4. But this appears to be a textbook case of the simple being over-engineered.
I want to perform bulk update of users with a Approved users, the table
field_user_status_value
-----------------------
entity_type, entity_id, field_user_status_value
The entity_id is the user id which does not exist in the table, below is the custom module I wrote to update the table:
function bulkapprove_action_info() {
return array(
'bulkapprove_action_callback_name' => array(
'type' => 'user', // Can be file, term, user, etc.
'label' => t('Approve User'),
'configurable' => FALSE, // Doesn't need config form
'behavior' => array('view_property'), // Uses view access rights ,
'pass rows' => TRUE,
'triggers' => array('any'), // Works always
),
);
}
function bulkapprove_action_callback_name($entity, $context)
{
db_update('field_data_field_user_status')->fields(array('field_user_status_value' => 'Approved'))->condition('entity_id', $context->entity_id)->execute();
}
But it is not inserting the values in this table
In Drupal you do not want to update the database fields directly unless you created the table. Drupal's internal APIs provide a collection of tools to ensure you update the values correctly and that all supporting modules get notified of changes as needed through the hook system.
In this case the callback gets the actual entity to run your action against (in this case the user object). You want to take action on that entity and then save the entity.
function bulkapprove_action_callback_name($entity, $context)
{
$entity->status = 1;
entity_save('user', $entity);
}
Did I mess up the normal table creation by implementing my own mapping to the our existing user table? When running the application the normal AspNet tables are not generated in the database.
I was able to figure out how to map the AspNetUsers table to our existing table in our database using the following code:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>().ToTable("DBUser");
var entity = modelBuilder.Entity<ApplicationUser>();
entity.HasKey(x => x.UserID);
entity.Ignore(x => x.Id);
entity.Ignore(x => x.PhoneNumber);
entity.Ignore(x => x.PhoneNumberConfirmed);
entity.Ignore(x => x.EmailConfirmed);
entity.Ignore(x => x.LockoutEnabled);
entity.Ignore(x => x.LockoutEndDateUtc);
entity.Ignore(x => x.PhoneNumber);
entity.Ignore(x => x.PhoneNumberConfirmed);
entity.Ignore(x => x.SecurityStamp);
entity.Ignore(x => x.TwoFactorEnabled);
entity.Ignore(x => x.AccessFailedCount);
entity.Property(x => x.UserID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
entity.Property(x => x.PasswordHash).HasColumnName("Password");
}
However, when I run the application and register a user for the first time, the normally created AspNet prefixed tables like UserClaims, UserLogins, UserRoles, and Roles are not created for some reason, and it does insert a record into my DBUser table with the correct information.
Since I'm using an existing database, should I assume that I need to manually create these tables?
Any help with this would be greatly appreciated.
UserManager in Asp.Net Identity 2 prevents creation user with duplicate username through additional request to database to find possible duplicate. I think this is error prone and can cause concurrency errors. The correct mechanism should rely on on unique constraints or indexes. Am I wrong and do I miss something?
Links to source:
CreateAsync and ValidateUserName
No, you are not wrong. And Identity adds the unique index on Username column:
And the migration code for this table is:
CreateTable(
"dbo.AspNetUsers",
c => new
{
Id = c.String(nullable: false, maxLength: 128),
/* .... SNIP .... */
UserName = c.String(nullable: false, maxLength: 256),
})
.PrimaryKey(t => t.Id)
.Index(t => t.UserName, unique: true, name: "UserNameIndex");
Unique index is clearly set on the column.
p.s. you are looking on Identity v3 - it is not released. Current Identity v2.1 is not open source yet.