我正在尝试用ef4 ctp5实现存储库模式,我想出了一些东西,但我不是ef的专家所以我想知道我做的是不是很好。
这是我的数据库上下文
public class Db : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
}
和存储库:(简化)
public class Repo<T> : IRepo<T> where T : Entity, new()
{
private readonly DbContext context;
public Repo()
{
context = new Db();
}
public IEnumerable<T> GetAll()
{
return context.Set<T>().AsEnumerable();
}
public long Insert(T o)
{
context.Set<T>().Add(o);
context.SaveChanges();
return o.Id;
}
}
答案 0 :(得分:10)
您需要退一步思考存储库应该做什么。存储库用于检索记录,添加记录和更新记录。您创建的存储库几乎不处理第一种情况,处理第二种情况但不能有效处理,并且根本不处理第三种情况。
大多数通用存储库都有一个
行的接口public interface IRepository<T> where T : class
{
IQueryable<T> Get();
void Add(T item);
void Delete(T item);
void CommitChanges();
}
对于检索记录,您不能只使用AsEnumerable()
调用整个集合,因为这会将该表的每个数据库记录加载到内存中。如果您只想要用户名为username1
的用户,则不需要为数据库下载每个用户,因为这将是一个非常大的数据库性能命中,并且大客户端性能达不到任何好处。
相反,正如您在上面发布的界面中所看到的,您想要返回一个IQueryable<T>
对象。 IQuerable
允许调用存储库的任何类使用Linq并向数据库查询添加过滤器,并且一旦运行IQueryable,它就完全在数据库上运行,只检索所需的记录。数据库在排序和过滤数据方面要比你的系统好得多,所以最好尽可能多地在数据库上做。
现在关于插入数据,您有正确的想法,但您不想立即致电SaveChanges()
。原因是在所有数据库操作排队后最好调用Savechanges()
。例如,如果要在一个操作中创建用户及其配置文件,则无法通过您的方法,因为每次Insert
调用都会导致数据插入到数据库中。
相反,您想要的是将Savechanges()
调用分离为上面的CommitChanges
方法。
还需要处理数据库中的更新数据。为了更改实体的数据,实体框架会跟踪它收到的所有记录并监视它们以查看是否进行了任何更改。但是,您仍然必须告诉实体框架将所有已更改的数据发送到数据库。 context.SaveChanges()
电话会发生这种情况。因此,您需要将其作为单独的调用,以便您能够实际更新当前实现无法处理的已编辑数据。
<小时/> 修改: 你的评论让我意识到我看到的另一个问题。一个缺点是您正在存储库中创建数据上下文,这并不好。您真的应该让所有(或大多数)创建的存储库共享相同的数据上下文实例。
实体框架会跟踪实体所跟踪的上下文,如果您尝试在一个上下文中更新另一个实体,则会出现异常。当您开始编辑彼此相关的实体时,可能会出现这种情况。这也意味着您的SaveChanges()
调用不是事务性的,并且每个实体都在其自己的事务中更新/添加/删除,这可能会变得混乱。
我在我的存储库中的解决方案是,DbContext
被传递到构造函数中的存储库中。
答案 1 :(得分:6)
我可能会为此投票,但DbContext已经是一个存储库。当您将域模型公开为具体DbContext的集合属性时,EF CTP5会为您创建一个存储库。它提供了一个类似于界面的集合,用于访问域模型,同时允许您传递查询(作为linq或spec对象)以过滤结果。
如果您需要接口,CTP5不会为您提供接口。我已经将自己包装在DBContext中,并简单地从对象中公开了可公开获得的成员。它是可测试性和DI的适配器。
如果我所说的内容显然不明显,我会澄清澄清。