如何使用Entity Framework处理可选表?

3
我正在使用Entity Framework 6访问数据库。随着时间推移,该数据库的模式存在几个略有不同的版本。当模式的某些部分可能“丢失”时,我如何使用EF访问数据库?
例如,某个时候向数据库添加了一个“Tank”表,因此我希望能够将其添加到我的上下文中。
public DbSet<Tank> Tanks { get; set; }

然后可以对其执行查询,如下所示:
var tanks = context.Tanks.Where( ... ).ToList()

但是很明显,当运行在没有这个表的版本的模式下时,这会引发异常。
除了捕获异常并检查适当的SQL错误代码之外,还有没有其他清晰处理此问题的方法?最好是让EF在运行查询之前检查表是否存在的某种方式?我在DbContext类上找不到任何有用的信息,在互联网搜索中也没有真正找到任何有用的东西。我想我可以降低原始SQL来完成它。
我应该指出的是,没有任何一个表上有“模式版本号”或类似的列,可以帮助确定正在访问哪个版本的数据库。也许这是个好主意(后见之明),但从未发生过,并且将这样的东西改装到现有安装中将是困难的。
此外,在我的特定场景中,我只是从现有数据库中读取数据:我不需要使用EF写入数据库,也不需要让EF为我构建模式。
编辑:我刚刚发现了这个SO问题:Entity Framework-如何检查表是否存在?,但答案基本上推荐执行SQL查询。虽然那是2011年的事情,但我想知道EF是否在以后的版本中引入了更清晰的内容。

1
为什么这个问题会被踩?这是一个解释清晰的问题,显示了提问者已经尝试过自己的努力。 - user1017882
2
OP,我不会将这个作为答案发布,因为我对使用EF时数据层方法的处理方式并不100%确定。但是,由于EF实际上是用于将数据映射到对象的,所以我认为它应该对其正在映射的数据具有“一致的理解”,而不是必须适应不断变化的模式。也就是说,如果您有一个更新的数据库(或没有),您需要考虑EF配置现在是否已经过时。我认为是这样的。为什么您的数据库会改变,但数据层不会呢? - user1017882
4
我赞同 @JayMee 的观点,如果在运行时不需要揭示这一点,我会选择条件编译。 - Mario The Spoon
2
@StevenRands - 也许你正在遭受 XY 问题的困扰?问问自己为什么需要处理变化。也许问题应该在架构的早期解决。此外,如果你的解决方案中有“坦克” - 现在怎么办?你不会只是忽略它,很可能你想对一个“坦克”对象做一些事情,但只有在它可用的地方。所以你的条件变得更加复杂,你更容易遇到一些噩梦般的维护问题。+1,这是一个好问题,我只是认为你应该重新考虑一下你的方法。 - user1017882
@JayMee 我们维护单个代码库的版本,然后编写数据层以应对各种安装所使用的不同模式。这种方法使某些事情变得更容易,而有些事情则更加困难。这是否是“正确”的方法实际上是一个观点问题,并且还需要我在问题中没有详细说明的领域知识。不过,我确实理解你提出的观点。 - Steven Rands
显示剩余4条评论
1个回答

1
实体框架似乎不支持这个功能,因此我认为有两种可能的方法:

使用原始SQL检查表是否存在

public static class DbContextExtensions
{
    public static bool HasTableNamed(
        this DbContext context, string table, string inSchema = "dbo")
    {
        string sql = @"SELECT CASE WHEN EXISTS
            (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
            WHERE TABLE_SCHEMA=@p0 AND TABLE_NAME=@p1) THEN 1 ELSE 0 END";

        return context.Database.SqlQuery<int>(sql, inSchema, table).Single() == 1;
    }
}

这样使用:

MyContext db = ...   // class derived from DbContext

...

if (db.HasTableNamed("MyOptionalTable"))
{
    // the table exists in the database
}
else
{
    // the table DOES NOT exist in the database
}

尝试运行EF查询,无论如何都要处理引发的异常。
public static class EntityCommandExecutionExceptionExtensions
{
    public static bool IsInvalidObjectNameError(
        this EntityCommandExecutionException exception)
    {
        const int InvalidObjectName = 208;

        var sqlException = exception.InnerException as SqlException;
        if (sqlException == null)
        {
            return false;
        }

        return sqlException.Errors.Cast<SqlError>().Any(
            error => error.Number == InvalidObjectName);
    }
}

使用方法如下:

try
{
    // run some EF query
}
catch (EntityCommandExecutionException exception)
{
    if (!exception.IsInvalidObjectNameError())
    {
        throw;
    }

    // one of the objects referenced by the query is missing
}

我很少看到人们经常检查SqlException.Number。大多数情况下省略这个检查是一个错误。我印象深刻。 - usr

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