我确实使用nsubstitue为Entity Famework Core 1.1.2创建了DbSet的模拟
class FakeDbSet<TEntity> : DbSet<TEntity>, IQueryable<TEntity>, IAsyncEnumerable<TEntity> where TEntity : class
它有一个内部列表,用于保存数据以模拟添加,查找和删除方法。如何模拟.Include和.ThenInclude方法来使连接工作?
我目前的FakeDbSet实现:
/// <summary>
/// FakeDbSet holds entries in interal list to fake add and delete methods
/// Mocking DbSet normaly would only work for getter not for setter
/// </summary>
/// <typeparam name="TEntity"></typeparam>
class FakeDbSet<TEntity> : DbSet<TEntity>, IQueryable<TEntity>, IAsyncEnumerable<TEntity> where TEntity : class
{
/// <summary>
/// Static constructor. Determines the which properties are key properties
/// </summary>
static FakeDbSet()
{
var type = typeof(TEntity);
foreach (var property in type
.GetProperties()
.Where(v => v.GetCustomAttributes(false).OfType<KeyAttribute>().Any()))
{
Keys.Add(property);
}
}
/// <summary>
/// Contains PropertyInfo objects for each of the key properties
/// </summary>
private static readonly List<PropertyInfo> Keys = new List<PropertyInfo>();
/// <summary>
/// The data we will query against in a List object
/// </summary>
private readonly IList<TEntity> _data;
/// <summary>
/// The data we will query against in a IQueryable object
/// </summary>
private readonly IQueryable<TEntity> _queryable;
/// <summary>
/// A dictionary to look up the current status of an object
/// </summary>
private readonly Dictionary<TEntity, EntityStatus> _entityStatus =
new Dictionary<TEntity, EntityStatus>();
/// <summary>
/// Observable collection of data
/// </summary>
/// <summary>
/// Constructor. Expects an IList of entity type
/// that becomes the data store
/// </summary>
/// <param name="data"></param>
public FakeDbSet(IList<TEntity> data)
{
_data = data;
_entityStatus.Clear();
foreach (var item in data)
{
_entityStatus[item] = EntityStatus.Normal;
}
_queryable = data.AsQueryable();
// The fake provider wraps the real provider (for "List<TEntity")
// so that it can log activities
Provider = new FakeAsyncQueryProvider<TEntity>(_queryable.Provider);
}
/// <inheritdoc />
public override EntityEntry<TEntity> Add(TEntity entity)
{
_data.Add(entity);
_entityStatus[entity] = EntityStatus.Added;
return null;
}
/// <inheritdoc />
public override async Task<EntityEntry<TEntity>> AddAsync(TEntity entity, CancellationToken cancellationToken = new CancellationToken())
{
return await Task.FromResult(Add(entity));
}
/// <inheritdoc />
public override Task AddRangeAsync(params TEntity[] entities)
{
throw new NotImplementedException();
}
/// <summary>
/// Implements the Find function of IdbSet.
/// Depends on the keys collection being
/// set to the key types of this entity
/// </summary>
/// <param name="keyValues"></param>
/// <returns></returns>
public override TEntity Find(params object[] keyValues)
{
if (keyValues.Length != Keys.Count)
{
throw new ArgumentException(
string.Format("Must supply {0} key values", Keys.Count),
"keyValues"
);
}
var query = _queryable;
var parameterExpression = Expression.Parameter(typeof(TEntity), "v");
for (int i = 0; i < Keys.Count; i++)
{
var equalsExpression = Expression.Equal(
// key property
Expression.Property(parameterExpression, Keys[i]),
// key value
Expression.Constant(keyValues[i], Keys[i].PropertyType)
);
var whereClause = (Expression<Func<TEntity, bool>>) Expression.Lambda(
equalsExpression,
new ParameterExpression[] {parameterExpression}
);
query = query.Where(whereClause);
}
var result = query.ToList();
return result.SingleOrDefault();
}
public override async Task<TEntity> FindAsync(params object[] keyValues)
{
return await new Task<TEntity>(() => Find(keyValues));
}
/// <summary>
/// Implements the Remove function of IDbSet
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
public override EntityEntry<TEntity> Remove(TEntity entity)
{
_data.Remove(entity);
_entityStatus[entity] = EntityStatus.Deleted;
return null;
}
public IEnumerator<TEntity> GetEnumerator()
{
return _queryable.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _queryable.GetEnumerator();
}
public Type ElementType => _queryable.ElementType;
public Expression Expression => _queryable.Expression;
public IQueryProvider Provider { get; }
public enum EntityStatus
{
None,
Added,
Deleted,
Normal
}
/// <inheritdoc />
IAsyncEnumerator<TEntity> IAsyncEnumerable<TEntity>.GetEnumerator()
{
return new FakeAsyncEnumerator<TEntity>(_queryable.GetEnumerator());
}
}
答案 0 :(得分:0)
有一种方法可以在.NET Core中执行此操作。我意识到你的问题有点陈旧,我希望你已经找到了答案,但以防万一,这是我采用的策略。一般的想法是创建自己的同名扩展方法,它们覆盖EFCore扩展方法并使用可配置的功能(通过公开的静态属性)。
QueryableExtensions.cs - 您的重写扩展方法。
public static class QueryableExtensions
{
public static IIncluder Includer = null;
public static IIncludableQueryable<T, TProperty> Include<T, TProperty>(
this IQueryable<T> source,
Expression<Func<T, TProperty>> path
)
where T : class
{
return Includer.Include(source, path);
}
}
DbIncluder.cs - 默认功能的包装。
using EFCore = Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions;
public class DbIncluder : IIncluder
{
public IIncludableQueryable<T, TProperty> Include<T, TProperty>(
IQueryable<T> source,
Expression<Func<T, TProperty>> path
)
where T : class
{
return EFCore.Include(source, path);
}
}
IIncluder.cs
public interface IIncluder
{
IIncludableQueryable<T, TProperty> Include<T, TProperty>(
IQueryable<T> source,
Expression<Func<T, TProperty>> path
) where T : class;
}
然后,在您的Repository.cs(例如)中,您可以替换Mock IIncluder:
public class Repository : IRepository
{
static Repository()
{
QueryableExtensions.Includer
= QueryableExtensions.Includer ?? new DbIncluder();
}
// ...
}
只要您在使用Repo之前设置了QueryableExtensions.Includer = [您的模拟],它就应该使用模拟。请注意,此模式也可用于任何其他EntityFrameworkCore扩展方法。
这是我在以下网站找到的解决方案的修改版本: