我想将值传递给DbContext的ctor,然后让该值对相关的DbSets强制执行“过滤”。这可能......还是有更好的方法?
代码可能如下所示:
class Contact {
int ContactId { get; set; }
int CompanyId { get; set; }
string Name { get; set; }
}
class ContactContext : DbContext {
public ContactContext(int companyId) {...}
public DbSet<Contact> Contacts { get; set; }
}
using (var cc = new ContactContext(123)) {
// Would only return contacts where CompanyId = 123
var all = (from i in cc.Contacts select i);
// Would automatically set the CompanyId to 123
var contact = new Contact { Name = "Doug" };
cc.Contacts.Add(contact);
cc.SaveChanges();
// Would throw custom exception
contact.CompanyId = 456;
cc.SaveChanges;
}
答案 0 :(得分:47)
我决定实现一个自定义IDbSet来处理这个问题。要使用此类,您需要传入DbContext,过滤器表达式和(可选)Action来初始化新实体,以使它们符合过滤条件。
我测试过枚举集合并使用Count聚合函数。它们都修改了生成的SQL,因此它们应该比在客户端上过滤更有效。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
namespace MakeMyPledge.Data
{
class FilteredDbSet<TEntity> : IDbSet<TEntity>, IOrderedQueryable<TEntity>, IOrderedQueryable, IQueryable<TEntity>, IQueryable, IEnumerable<TEntity>, IEnumerable, IListSource
where TEntity : class
{
private readonly DbSet<TEntity> Set;
private readonly IQueryable<TEntity> FilteredSet;
private readonly Action<TEntity> InitializeEntity;
public FilteredDbSet(DbContext context)
: this(context.Set<TEntity>(), i => true, null)
{
}
public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter)
: this(context.Set<TEntity>(), filter, null)
{
}
public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity)
: this(context.Set<TEntity>(), filter, initializeEntity)
{
}
private FilteredDbSet(DbSet<TEntity> set, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity)
{
Set = set;
FilteredSet = set.Where(filter);
MatchesFilter = filter.Compile();
InitializeEntity = initializeEntity;
}
public Func<TEntity, bool> MatchesFilter { get; private set; }
public void ThrowIfEntityDoesNotMatchFilter(TEntity entity)
{
if (!MatchesFilter(entity))
throw new ArgumentOutOfRangeException();
}
public TEntity Add(TEntity entity)
{
DoInitializeEntity(entity);
ThrowIfEntityDoesNotMatchFilter(entity);
return Set.Add(entity);
}
public TEntity Attach(TEntity entity)
{
ThrowIfEntityDoesNotMatchFilter(entity);
return Set.Attach(entity);
}
public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, TEntity
{
var entity = Set.Create<TDerivedEntity>();
DoInitializeEntity(entity);
return (TDerivedEntity)entity;
}
public TEntity Create()
{
var entity = Set.Create();
DoInitializeEntity(entity);
return entity;
}
public TEntity Find(params object[] keyValues)
{
var entity = Set.Find(keyValues);
if (entity == null)
return null;
// If the user queried an item outside the filter, then we throw an error.
// If IDbSet had a Detach method we would use it...sadly, we have to be ok with the item being in the Set.
ThrowIfEntityDoesNotMatchFilter(entity);
return entity;
}
public TEntity Remove(TEntity entity)
{
ThrowIfEntityDoesNotMatchFilter(entity);
return Set.Remove(entity);
}
/// <summary>
/// Returns the items in the local cache
/// </summary>
/// <remarks>
/// It is possible to add/remove entities via this property that do NOT match the filter.
/// Use the <see cref="ThrowIfEntityDoesNotMatchFilter"/> method before adding/removing an item from this collection.
/// </remarks>
public ObservableCollection<TEntity> Local { get { return Set.Local; } }
IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator() { return FilteredSet.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return FilteredSet.GetEnumerator(); }
Type IQueryable.ElementType { get { return typeof(TEntity); } }
Expression IQueryable.Expression { get { return FilteredSet.Expression; } }
IQueryProvider IQueryable.Provider { get { return FilteredSet.Provider; } }
bool IListSource.ContainsListCollection { get { return false; } }
IList IListSource.GetList() { throw new InvalidOperationException(); }
void DoInitializeEntity(TEntity entity)
{
if (InitializeEntity != null)
InitializeEntity(entity);
}
}
}
答案 1 :(得分:5)
EF没有任何“过滤”功能。你可以尝试通过继承自定义DbSet
来实现这样的目标,但我认为它仍然会有问题。例如,DbSet直接实现IQueryable,因此可能无法包含自定义条件。
这将需要一些能够处理这些要求的包装器(可以是存储库):
Where
条件SaveChanges
并使用context.ChangeTracker
来获取所有更新的实体。然后,您可以检查实体是否已被修改。通过包装器我不是指自定义DbSet
实现 - 这太复杂了:
public class MyDal
{
private DbSet<MyEntity> _set;
public MyDal(DbContext context)
{
_set = context.Set<MyEntity>();
}
public IQueryable<MyEntity> GetQuery()
{
return _set.Where(e => ...);
}
// Attach, Insert, Delete
}