当我尝试向视图中添加对象时,它引发了异常unable to track an instance of type because it is a query type
。有办法解决这个问题吗?
答案 0 :(得分:2)
Query Types是只读的(对于所有数据库提供程序,不仅适用于内存):
- 永远不会跟踪DbContext上的更改,因此永远不会在数据库上对其进行插入,更新或删除。
但是,除了通常的
使用场景之外
- 映射到数据库视图。
- 映射到未定义主键的表。
他们允许
- 映射到模型中定义的查询。
或换句话说
- 可能会映射到定义查询-定义查询是模型中声明的辅助查询,它充当查询类型的数据源。
这是通过ToQuery流利的API实现的:
配置用于提供查询类型数据的查询。
因此,要在内存数据库中测试查询类型,应使用定义查询映射功能。
例如,在OnModelCreating
内,您可以添加如下内容:
if (Database.IsInMemory())
{
// In memory test query type mappings
modelBuilder.Query<MyQueryType>().ToQuery(() => LINQ_query);
// ... similar for other query types
}
else
{
// Database query type mappings
modelBuilder.Query<MyQueryType>().ToView("MyQueryTypeView");
// ...
}
其中LINQ_query
是访问上下文DbSet
和DbQuery
并投影到MyQueryType
的普通LINQ查询。
然后,测试将为涉及的实体提供数据,并且使用DbQuery
s进行的查询将从定义的查询中检索数据。
以上是推荐使用内存数据库测试视图的方法。
仅出于完整性考虑,可以通过创建某种查询存储库直接向DbQuery
s提供数据(基本上是模拟它们),但有以下限制-必须 shared (static
,因为当前EF Core除DbSet<T>
和DbQuery<T>
之外无法正确处理数据库上下文成员(如全局查询过滤器一样)。
类似这样的东西:
public static class FakeQueryProvider
{
static Dictionary<Type, IQueryable> queries = new Dictionary<Type, IQueryable>();
public static void SetQuery<T>(IQueryable<T> query)
{
lock (queries)
queries[typeof(T)] = query;
}
public static IQueryable<T> GetQuery<T>()
{
lock (queries)
return queries.TryGetValue(typeof(T), out var query) ? (IQueryable<T>)query : Enumerable.Empty<T>().AsQueryable();
}
public static QueryTypeBuilder<T> ToFakeQuery<T>(this QueryTypeBuilder<T> builder)
where T : class
{
return builder.ToQuery(() => GetQuery<T>());
}
}
然后代替
.ToQuery(() => LINQ_query);
您将使用
.ToFakeQuery();
并像这样将其喂入测试
List<MyQueryType> data = ...;
FakeQueryProvider.SetQuery(data.AsQueryable());
由于共享存储限制了并行运行MyQueryType
相关测试的能力,因此我仍然推荐第一种方法。
答案 1 :(得分:1)
我最终根据以下建议/建议重构了Ivan的扩展类代码。我在ToFakeQuery方法中添加了重载以引入字典。
public static class InMemoryQueryProviderExtensions
{
static Dictionary<Type, IQueryable> queries = new Dictionary<Type, IQueryable>();
public static void SetQuery<T>(IQueryable<T> query)
{
lock (queries)
queries[typeof(T)] = query;
}
public static IQueryable<T> GetQuery<T>()
{
lock (queries)
return queries.TryGetValue(typeof(T), out var query) ? (IQueryable<T>)query : Enumerable.Empty<T>().AsQueryable();
}
private static IQueryable<T> GetQuery<T>(Dictionary<Type, IQueryable> queryDictionary)
{
return queryDictionary.TryGetValue(typeof(T), out var query) ? (IQueryable<T>)query : Enumerable.Empty<T>().AsQueryable();
}
public static QueryTypeBuilder<T> ToFakeQuery<T>(this QueryTypeBuilder<T> builder)
where T : class
{
return builder.ToQuery(() => GetQuery<T>());
}
public static QueryTypeBuilder<T> ToFakeQuery<T>(this QueryTypeBuilder<T> builder, Dictionary<Type, IQueryable> queryDictionary)
where T : class
{
return builder.ToQuery(() => GetQuery<T>(queryDictionary));
}
}
然后,如下所示为我的DBContext创建一个新的派生类。基本上,使内存DBContext的派生实例可以维护字典。
public class TlInMemoryDbContext : TlDbContext
{
public TlInMemoryDbContext(DbContextOptions<TlDbContext> options)
: base(options)
{ }
Dictionary<Type, IQueryable> queries = new Dictionary<Type, IQueryable>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Query<EffectiveTimeEntry>().ToFakeQuery(queries);
}
public void SetQuery<T>(IQueryable<T> query)
{
lock (queries)
queries[typeof(T)] = query;
}
}
答案 2 :(得分:0)
我按照Ivan Stoev的建议使用了以上内容。
这是它的样子
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
if(Database.IsSqlServer())
{
modelBuilder.Query<ProviderRating>(entity =>
{
entity.ToView("vGetProviderRatingData", "dbo");
entity.Property(e => e.col1)
.HasMaxLength(10)
.IsUnicode(false);
entity.Property(e => e.col2)
.HasMaxLength(60)
.IsUnicode(false);
entity.Property(e => e.col3)
.HasMaxLength(10)
.IsUnicode(false);
});
}
else
{
modelBuilder.Query<ProviderRating>().ToQuery(() =>
ProviderRatingFake.Select(m => new ProviderRating()
{
col1 = m.col1,
col2 = m.col2,
col3 = m.col3,
}
));
}
}
ProviderRatingFake
类与ProviderRating
类完全一样
我还将此代码添加到了DbContext文件(ProviderQualityContext
)
public virtual DbSet<ProviderRatingFake> ProviderRatingFake { get; set; }
public virtual DbQuery<ProviderRating> ProviderRating { get; set; }
然后我像这样测试
[TestMethod]
public void TestingWithInMemoryDb()
{
var options = new DbContextOptionsBuilder<ProviderQualityContext>()
.UseInMemoryDatabase(databaseName: "Read_From_Database")
.Options;
var fakeProviderRating = new ProviderRatingFake
{
col1 = 1,
col2 = "Something",
col3 = "Something",
};
using (var context = new ProviderQualityContext(options))
{
context.ProviderRatingFake.Add(fakeProviderRating);
context.SaveChanges();
}
//use the newly created context and inject it into controller or repository
using (var context = new ProviderQualityContext(options))
{
//use the test context here and make assertions that you are returning the
//fake data
//Note that the actual code uses the Query like this
//This query will be populated with fake data using the else block
//in the method OnModelCreating
var returnedData = this.dbContext.Query<ProviderRating>().Where(m => m.col1 ==
"Something")
}
}
答案 3 :(得分:0)
public static class InMemoryQueryProviderExtensions
{
static Dictionary<Type, IQueryable> queries = new Dictionary<Type, IQueryable>();
public static void SetQuery<T>(IQueryable<T> query)
{
lock (queries)
queries[typeof(T)] = query;
}
public static IQueryable<T> GetQuery<T>()
{
lock (queries)
return queries.TryGetValue(typeof(T), out var query) ? (IQueryable<T>)query : Enumerable.Empty<T>().AsQueryable();
}
private static IQueryable<T> GetQuery<T>(Dictionary<Type, IQueryable> queryDictionary)
{
return queryDictionary.TryGetValue(typeof(T), out var query) ? (IQueryable<T>)query : Enumerable.Empty<T>().AsQueryable();
}
public static QueryTypeBuilder<T> ToFakeQuery<T>(this QueryTypeBuilder<T> builder)
where T : class
{
return builder.ToQuery(() => GetQuery<T>());
}
public static QueryTypeBuilder<T> ToFakeQuery<T>(this QueryTypeBuilder<T> builder, Dictionary<Type, IQueryable> queryDictionary)
where T : class
{
return builder.ToQuery(() => GetQuery<T>(queryDictionary));
}
}