Android 房间 - 如何在插入之前检查是否已存在同名实体?

Android Room - How can I check if an entity with the same name already exists before inserting?

我正在使用 mvvm 模式和 android 空间创建一个应用程序,但我 运行 在验证用户输入时遇到了一些麻烦。当用户想要向应用程序添加成分时,他们需要输入该成分的名称。如果该名称已被使用,我希望该应用程序通知用户。我已经使用 Transformations.Map() 函数尝试了一些东西,但没有成功。

我对 mvvm 模式和 LiveData 还很陌生,我已经坚持了很长一段时间,所以任何建议都将不胜感激。

这是成分实体:

@Entity(tableName = "ingredient")
public class BaseIngredient {

@PrimaryKey(autoGenerate = true)
private int id;

private String name;

private String category;

@ColumnInfo(name = "cooking_time")
private int cookingTime;

@Ignore
public BaseIngredient() {
}

public BaseIngredient(int id, @NonNull String name, @NonNull String category, int cookingTime)
        throws InvalidValueException {
    this.id = id;
    setName(name);
    setCookingTime(cookingTime);
    setCategory(category);
}

public void setName(String name) throws InvalidNameException {
    if (name == null || name.isEmpty())
        throw new InvalidNameException("Name is empty");
    if (!name.matches("[A-z0-9]+( [A-z0-9]+)*"))
        throw new InvalidNameException("Name contains invalid tokens");

    this.name = name;
}

public void setCategory(String category) throws InvalidCategoryException {
    if (category == null || category.isEmpty())
        throw new InvalidCategoryException("Category is empty");
    if (!category.matches("[A-z0-9]+"))
        throw new InvalidCategoryException("Category contains invalid tokens");

    this.category = category;
}

public void setCookingTime(int cookingTime) throws InvalidCookingTimeException {
    if (cookingTime < 1)
        throw new InvalidCookingTimeException("Time must be positive");

    this.cookingTime = cookingTime;
}

/* getters */

public boolean isValid() {
    return name != null && category != null && cookingTime != 0;
}

这是我正在使用的 IngredientRepository:

private IngredientDao ingredientDao;

private LiveData<List<BaseIngredient>> ingredients;

public IngredientRepository(Application application) {
    LmcfyDatabase database = LmcfyDatabase.getDatabase(application.getApplicationContext());
    ingredientDao = database.ingredientDao();
    ingredients = ingredientDao.getAllIngredients();
}

public LiveData<List<BaseIngredient>> getAllIngredients() {
    return ingredients;
}

public LiveData<List<BaseIngredient>> getIngredientsWithQuery(String query) {
    return ingredientDao.getIngredientsWithQuery("%" + query + "%");
}

public void insert(BaseIngredient ingredient) {
    LmcfyDatabase.databaseWriteExecutor.execute(() -> {
        ingredientDao.insert(ingredient);
    });
}

public LiveData<Integer> getIngredientsWithNameCount(String name) {
    return ingredientDao.getIngredientsWithNameCount(name);
}

IngredientDao:

@Insert(onConflict = OnConflictStrategy.IGNORE, entity = BaseIngredient.class)
long insert(BaseIngredient ingredient);

@Delete(entity = BaseIngredient.class)
void delete(BaseIngredient ingredient);

@Query("SELECT * FROM ingredient")
LiveData<List<BaseIngredient>> getAllIngredients();

@Query("SELECT * FROM ingredient WHERE name LIKE :query")
LiveData<List<BaseIngredient>> getIngredientsWithQuery(String query);

@Query("SELECT COUNT(id) FROM ingredient WHERE name LIKE :name")
LiveData<Integer> getIngredientsWithNameCount(String name);

最后是用于创建成分的 ViewModel

    private final IngredientRepository repository;

private final BaseIngredient ingredient;

private final MutableLiveData<String> nameError;

private final MutableLiveData<String> categoryError;

private final MutableLiveData<String> cookingTimeError;

private final MutableLiveData<Boolean> ingredientValidStatus;

public AddIngredientViewModel(@NonNull Application application) {
    super(application);
    repository = new IngredientRepository(application);
    ingredient = new BaseIngredient();

    nameError = new MutableLiveData<>();
    categoryError = new MutableLiveData<>();
    cookingTimeError = new MutableLiveData<>();
    ingredientValidStatus = new MutableLiveData<>();
}

public void onNameEntered(String name) {
    try {
        ingredient.setName(name);
        nameError.setValue(null);
    } catch (InvalidNameException e) {
        nameError.setValue(e.getMessage());
    } finally {
        updateIngredientValid();
    }
}

public void onCategoryEntered(String category) {
    try {
        ingredient.setCategory(category);
        categoryError.setValue(null);
    } catch (InvalidCategoryException e) {
        categoryError.setValue(e.getMessage());
    } finally {
        updateIngredientValid();
    }
}

public void onCookingTimeEntered(int cookingTime) {
    try {
        ingredient.setCookingTime(cookingTime);
        cookingTimeError.setValue(null);
    } catch (InvalidCookingTimeException e) {
        cookingTimeError.setValue(e.getMessage());
    } finally {
        updateIngredientValid();
    }
}

private void updateIngredientValid() {
    ingredientValidStatus.setValue(ingredient.isValid());
}

public boolean saveIngredient() {
    if (ingredient.isValid()) {
        Log.d(getClass().getName(), "saveIngredient: Ingredient is valid");
        repository.insert(ingredient);
        return true;
    } else {
        Log.d(getClass().getName(), "saveIngredient: Ingredient is invalid");
        return false;
    }
}

视图模型中的 onXXEntered() 函数链接到片段中的文本视图,按下保存按钮时调用 saveIngredient() 函数。 XXError LiveData 用于向用户显示错误。

真正的问题在于 LiveData 是异步的,用户可以在 LiveData 包含来自数据库的结果之前更改他们的输入并单击保存按钮。如果我想在保存时检查输入,'add activity' 在检查完成之前就已经完成了。

再一次,我们将不胜感激。

我不得不在我最近的一个项目中做类似的事情。我所做的是:

  1. Room 无法使用 SQLite Unique 约束创建列(如果它不是 PrimaryKey - 这是您的情况)。所以不要使用 Room 在您的应用程序代码中初始化数据库。相反,在您的应用程序外部创建一个数据库文件。在 'name' 列上添加 Unique 约束。然后在项目中的 assets 文件夹下添加数据库文件。 (例如,在 assets 中创建一个子文件夹 - 'db_files' - 并将您预先创建的数据库文件复制到该文件夹​​下)

  2. 我猜你为你的@DataBase 使用了单例模式class。将您的 'getInstance()' 方法替换为以下内容:

public static MyDB getInstance(final Context context) {
        if(INSTANCE == null) {
            synchronized (AVListDB.class) {
                INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                        MyDB.class,"myDB.db")
                        .createFromAsset( "db/myDB.db")
                        .build();

            }
        }
        return INSTANCE;
    }

这会在应用程序数据库文件路径下创建预打包数据库文件的副本。

  1. 有了唯一约束,你的 @Insert@Update 注释方法将遵守约束条件,并抛出一个 SQLiteConstraintException 如果您尝试插入以前使用过的名称。您可以捕获此异常,并根据需要将其传递给您的 View 或 ViewModel(我实现了一个简单的集中式事件发布者组件)。

希望对您有所帮助。