SQLite.NET - System.NotSupportedException: Cannot compile: Parameter

8
我正在一个Xamarin iOS项目中使用SQLite.NET Async(http://www.nuget.org/packages/SQLite.Net.Async-PCL/),但是在使用表谓词查询时遇到了问题。
每当我使用下面的Get方法(非常简单)时,我都会收到一个异常,指出表达式无法编译,System.NotSupportedException:无法编译:Parameter。
但是,如果我使用低级别查询,如GetQuery方法,它就可以正常工作。是否在定义我的表或该方法中做错了什么,导致sqlite.net无法编译表达式?
public interface IDataModel
{
    [PrimaryKey, AutoIncrement]
    int Id { get; set; }
}

public class BaseDataModel : IDataModel
{
    [PrimaryKey]
    public virtual int Id { get; set; }
}

[Table("Event")]
public class EventDataModel : BaseDataModel
{
    public string Name { get; set; }
    public int OrganizationId { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime? EndDate { get; set; }
    public bool Active { get; set; }
}

public class DataService<T> : IDataService<T> where T : IDataModel, new()
{

    public virtual async Task<T> Get(int id)
    {
        var connection = await GetConnection();

        return await connection.Table<T>()
                .Where(item => item.Id == id)
                .FirstOrDefaultAsync();
    }

    public virtual async Task<T> GetQuery(int id)
    {
        var connection = await GetConnection();

        return (await connection.QueryAsync<T>("SELECT * FROM Event WHERE Id = ?", id))
             .FirstOrDefault();
    }
}

编辑#1: 问题似乎与我的方法是通用的有关。如果我将它们更改为特定于模型“connection.Table<EventDataModel>.Where(...”的方法,则可以工作。通用方法不起作用吗?
编辑#2: 我添加了一个“类”约束到T上,与现有的“IDataModel,new()”约束一起使用,这似乎已解决了该问题...这有意义吗?

1
一定有什么东西丢失了 - 当前的代码无法编译,因为在类型 T 上未定义 Id - Jean Hominal
是的,我省略了一些代码,因为实际代码要复杂得多。我已经更新了。 - Zach Green
是的,这个限制非常有意义。您可以通过使用不包含Id字段的类运行GetQuery函数来尝试此操作。基于LINQ的函数是强类型的,而查询则不是(where子句部分)。在桌面/服务器应用程序中,您可以使用动态参数而不是通用参数,但就像查询一样,您将冒着运行时异常的风险。 - SKall
关于查询的另一件事是,如果您想使其通用,就不能使用硬编码的“FROM Event”,而必须将其替换为TableAttribute的值([Table(“Event”)])。 - SKall
2
“添加类约束”是什么意思? - NorCalKnockOut
3个回答

13

加入 class 约束条件确实能解决这个问题。

当你写下:

public virtual async Task<T> Get(int id)
    where T : IDataModel, new()
{
    var connection = await GetConnection();

    return await connection.Table<T>()
            .Where(item => item.Id == id)
            .FirstOrDefaultAsync();
}

你看不到它,但编译器会在itemitem.Id之间插入一个强制类型转换。

也就是说,编译器实际上写的是:

public virtual async Task<T> Get(int id)
    where T : IDataModel, new()
{
    var connection = await GetConnection();

    return await connection.Table<T>()
            .Where(item => ((IDataModel)item).Id == id)
            .FirstOrDefaultAsync();
}

如果T是值类型,则需要插入该转换。

可以想象,SQLite.net的查询提供程序无法正确处理插入的转换,因为这样做并不容易。

添加class约束可以使编译器避免插入该转换,从而得到一个更简单的表达式,SQLite.net查询提供程序显然可以正确地翻译它。


public T GetItem<T>(int id) where T : BL.Contracts.IBusinessEntity, new(){ lock (locker) { return database.Table<T>().FirstOrDefault(x => x.ID == id); } } 我似乎无法修复我的 - NorCalKnockOut
@NorCalKnockOut:你在T上添加了class约束吗?如果这样做没有解决问题,请提出另一个问题。 - Jean Hominal

4
我想问题在于编译器不知道该项具有Id属性。
return await connection.Table<T>()
        .Where(item => **item.Id** == id)
        .FirstOrDefaultAsync();

您可以创建一个带有Id字段的接口,并使用where T:约束来实现。
public interface ITable
{
    int Id { get; set; }
}

    public virtual async Task<T> Get(int id) where T : ITable
    {
       ... 

再次强调,您可能应该只使用FindAsync:
public virtual async Task<T> Get(int id)
{
    var connection = await GetConnection();

    return await connection.FindAsync<T>(id);
}

我加入了更多的上下文来更新代码。我的数据模型正在实现一个具有Id字段的接口。FindAsync在这种简单情况下可以工作,但是在其他复杂情况下会失败。 - Zach Green
我遇到了与原帖作者类似的问题,于是我修改了我的 LoadByIdAsync() 方法,改为调用 Find<T>() 而不是 FirstOrDefaultAsync,现在一切都正常了。 - dub stylee

0
对于那些想知道 T 上的 class 约束是什么意思的人,以下是方法签名: Task<T> GetAsync<T>(int id) where T : class, IDbModel, new();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接