在使用EF(5.0 Beta Code First)作为我的ORM / DAL时,我一直在研究新项目的最佳实践。关键考虑因素是可测试性,松散耦合设计以及跨存储库的工作单元/事务支持。
据我所知,在EF DbContext中,UoW和DbSet是Repository,在服务层中,您可以在DbContexts SaveChanges()方法协调的多个存储库中进行更改。这是我的示例配置:
public interface IMyContext
{
IDbSet<Foo> Foos{ get; }
IDbSet<Bar> Bars { get; }
int SaveChanges();
void Dispose();
}
public class EFMyContext : DbContext, IMyContext
{
private IDbSet<Foo> _foos;
private IDbSet<Bar> _bars;
public EFMyContext()
: base("name=MyConnectionString")
{
Database.SetInitializer<EFMyContext>(null);
}
public IDbSet<Foo> Foos
{
get
{
if (_foos == null)
{
_foos = this.Set<Foo>();
}
return _foos;
}
}
public IDbSet<Bar> Bars
{
get
{
if (_bars == null)
{
_bars = this.Set<Bar>();
}
return _bars;
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new FooConfiguration());
modelBuilder.Configurations.Add(new BarConfiguration());
base.OnModelCreating(modelBuilder);
}
到目前为止一切都很好。如果我想使用这是一个像这样的上层
public class MyService : IMyService
{
private IMyContext _context;
public MyService(IMyContext context)
{
_context = context;
}
public void DoSomethingWithMyContext()
{
// fancy implementation code
}
}
这里我遇到了问题,因为我直接依赖EntityFramework.dll,因为MyContext暴露了IDBSet类型的属性。这似乎不对,与this问题非常相似。
所以我的问题是,如何抽象出对EF的直接依赖?我考虑引入自己的存储库和工作单元来传递实时上下文,但这会如何影响更改跟踪和所有其他整洁的EF功能?
我正在研究的另一件事是管理上下文生命周期的IoC机制。那时我可以取消UoW吗?
对于模糊不清,我对此处处于研究饱和点,需要在实施前开始确定。
答案 0 :(得分:2)
问自己一个问题:我的架构应该解决什么?除非你能提供防弹答案,否则为什么从服务程序集中引用EntityFramework.dll是错误的,你就是在浪费时间。
引用EntityFramework.dll实际上没有错。为什么会这样?您的服务不会暴露与EF相关的任何内容,因此它完全可以模拟。如果您还想对服务代码进行单元测试,则必须确保您的服务以抽象方式使用EF。在这种情况下,存储库可以是可行的,但它将是繁重的专用存储库 - 而不是通常提出的a generic nonsense。存储库将隐藏与EF相关的所有内容,您的服务将不知道您的应用程序中存在任何类似EF的内容。它还回答了有关EF功能的问题 - 如果您想让它可测试,您的服务必须完全独立于这些功能。例如,您不应该使您的服务代码依赖于LINQ-to-Entities或延迟加载。它只会导致troubles in testing。
即使您实现了存储库,如果它们与您的服务位于同一个程序集中,并且该程序集引用了EntityFramework.dll,那么仍然没有错。
答案 1 :(得分:1)
在服务层中,您实际上不需要使用IQueryable<T>
。
因为这样你就会与支持 LINQ 的 ORM 紧密结合。 所有 LINQ 查询都应封装在具体的 DAL 中。
更好的是,您可以在服务层中定义 DAL - 相关的存储库:
public interface IRepository<T>
{
T GetById(int id);
IEnumerable<T> GetAll();
// Filter is some DTO that will be mapped to
// concrete LINQ query inside your DAL
IEnumerable<T> GetFiltered(Filter<T> filter);
void Add(T item);
void Update(T item);
void Remove(T item);
}
更新:如果你爱上了LINQ,你可以为ObjectContext和ObjectSet定义自己的接口:
public interface IObjectContext : IUnitOfWork
{
IObjectSet<T> Set<T>() where T : class;
}
public interface IObjectSet<T> : IObjectQuery<T>
where T : class
{
void Remove(T entity);
void Add(T entity);
}
public interface IObjectQuery<out T> : IQueryable<T>, IQueryableWrapper
where T : class
{
IObjectQuery<T> Include(string path);
}
然后你可以为EF编写适配器。 您还应该将查询转换层添加到适配器,因为EF无法理解表达式树中的接口。 以下是QueryTranslator的示例:https://stackoverflow.com/a/1846030/1275399
答案 2 :(得分:0)
你很可能在IMyContext上没有DbSet属性,并且只在实现上有dbsets。 IMyContext应仅用于了解保存上下文(并将其丢弃)以及其他任何内容。 因此,在存储库中,您可以拥有一个知道如何与sql通信的策略,比如说使用ef并让它处理dbsets的一部分。只有当你真的计划搬出EF并在将来用别的东西替换它时,你才需要这样做。
您可以找到有关此here
的方法的更多详细信息