agrosner / DBFlow

A blazing fast, powerful, and very simple ORM android database library that writes database code for you.
MIT License
4.87k stars 598 forks source link

"onDelete = ForeignKeyAction.CASCADE" with no effects #216

Closed YSDC closed 8 years ago

YSDC commented 9 years ago

I try to use the "onDelete" with ForeignKeyAction.CASCADE but without success. I have the following annotation in a class that we will call MyClass:

    @Column(columnType = Column.FOREIGN_KEY, references = {@ForeignKeyReference(columnName = FIELD_MEDIA, columnType = Long.class, foreignColumnName = "id")}, onDelete = ForeignKeyAction.CASCADE, saveForeignKeyModel = false, uniqueGroups = 1)
    protected Media media;

But when i do this

        new Delete().from(MyClass.class).query();

The Media is not deleted. Any idea why?

agrosner commented 9 years ago

@YSDC do you have foreign key constraints turned on? @Database(foreignKeysSupported = true) ?

YSDC commented 9 years ago

Yes sure:

@Database(name = DatabaseHelper.DBNAME, version = BuildConfig.DATABASE_VERSION, foreignKeysSupported = true)
public class DatabaseHelper {
YSDC commented 9 years ago

Let me know if you need any more information to help me, it's a bit blocking this cascade, and I don't want to do an ugly loop to remove manually all items :(

What i tried already:

BTW for the ForeignKeyContainer you should add to the documentation that it's necessary to add the @ContainerAdapterannotation to make it works

YSDC commented 9 years ago

Some other information: I have enable low level log with FlowLog and have this message.

FlowLog﹕ Foreign Keys supported. Enabling foreign key features.

meaning that's it's supported.

I have also the right create sql command in my adapter


  @Override
  public String getCreationQuery() {
    return String.format("CREATE TABLE IF NOT EXISTS `NewsMedia`(`id` INTEGER PRIMARY KEY AUTOINCREMENT,  `news` TEXT,  `media` INTEGER, `main` INTEGER, UNIQUE (`news`, `media`) ON CONFLICT FAIL, FOREIGN KEY(`news`) REFERENCES `%1s` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`media`) REFERENCES `%1s` (`id`) ON UPDATE NO ACTION ON DELETE CASCADE );",FlowManager.getTableName(*******.News.class), FlowManager.getTableName(******.Media.class));
  }

The **\ are only here to hide package name

agrosner commented 9 years ago

What version of Android is this running on?

YSDC commented 9 years ago

Thanks for your answer. I tried on different phones that were 4.x I will give you a more precise version in the next 24 hours. I have created a sample project with the minimum of classes, I can send it to you if you want

dudadayan commented 9 years ago

Hey, can you please update if you solved this issue?

ydanneg commented 9 years ago

This should work in other way, if you do new Delete().from(Media.class).query(); then related MyClass records will be deleted.

ydanneg commented 9 years ago

After several failed attempts using foreign keys I found the way to implement One-To-One relationship using OneToMany annotation.

User.java

@Table(tableName = "Users", databaseName = "mydb")
@ModelContainer
public class User extends BaseModel {

    @Column(name = "id")
    @PrimaryKey(autoincrement = true)
    private long id;

    @Expose
    @Column(name = "userId")
    @NotNull
    @Unique(onUniqueConflict = ConflictAction.REPLACE)
    private String userId;

    @Expose
    // should be non-private due to the DBFlow compiler limitation
    /*private*/ UserProfile userProfile;

    @OneToMany(methods = {OneToMany.Method.ALL})
    public UserProfile getUserProfile() {
        if (userProfile == null) {
            userProfile = new Select().from(UserProfile.class)
                    .where("userId=?", userId).querySingle();
        }
        return userProfile;
    }

    @Override
    public void save() {
        if (userProfile != null) {
            userProfile.setUserId(userId);
        }
        super.save();
    }
}

UserProfile.java

@Table(tableName = "UserProfiles", databaseName = "mydb")
public class UserProfile extends BaseModel {
    @Column(name = "id")
    @PrimaryKey(autoincrement = true)
    private long id;

    @Expose
    @Column(name = "firstName")
    private String firstName;

    @Column(name = "userId")
    @NotNull
    @Unique(onUniqueConflict = ConflictAction.REPLACE)
    private String userId;
}

OneToManyValidator does not validate the filed type have to be a List. So that became possibly to user this annotation for non List variable :)

Example use:

    // create
    UserProfile profile = new UserProfile();
    User user = new User();
    user.setUserId("133456789");
    user.setUserProfile(profile);
    user.save(); // saves also a userProfile

    // load
    User user = new Select().from(User.class).querySingle();
    user.getUserProfile(); // not null, loaded from DB

    // modify
    user.getUserProfile().setFirstName("john");
    user.save(); // saves also a userProfile

    // delete
    user.delete(); // deletes user and associated userProfile

@agrosner Is it possible by default to use gettters and setters to get access to private memebrs in generated code? @agrosner The provided "OneToOne" solution still uses *ModelListTransaction (see OneToManyDefinition) to modify sub-models. Maybe there is a reason to add OneToOne annotation?

flywheelms commented 8 years ago

+1 OneToOne annotation

faken commented 7 years ago

I was wondering what's currently the preferred way of making a OneToOne-Relation work. I'm struggling with getting it to work when I'm following the current Document of DBFlow 4.0.4.