EF4 CTP5 的存储库模式
我正在尝试使用 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;
}
}
I'm trying to implement the repository pattern with ef4 ctp5, I came up with something but I'm no expert in ef so I want to know if what I did is good.
this is my db context
public class Db : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
}
and the repository: (simplified)
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;
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您需要退一步思考存储库应该做什么。存储库用于检索记录、添加记录和更新记录。您创建的存储库几乎无法处理第一种情况,处理第二种情况但效率不高,并且根本不处理第三种情况。
大多数通用存储库都有一个类似于“
要检索记录”的接口,您不能只使用
AsEnumerable()
调用整个集合,因为这会将该表的每个数据库记录加载到内存中。如果您只需要用户名为username1
的用户,则无需下载数据库的每个用户,因为这将严重影响数据库性能,并且会严重影响客户端性能,但没有任何好处根本不。相反,正如您从我上面发布的界面中看到的那样,您希望返回一个
IQueryable
对象。 IQuerable 允许调用存储库的任何类使用 Linq 并向数据库查询添加过滤器,并且一旦 IQueryable 运行,它就完全在数据库上运行,只检索您想要的记录。数据库在排序和过滤数据方面比您的系统要好得多,因此最好在数据库上尽可能多地进行操作。现在,关于插入数据,您的想法是正确的,但您不想立即调用
SaveChanges()
。原因是最好在所有数据库操作排队后调用Savechanges()
。例如,如果您想在一个操作中创建一个用户及其个人资料,则不能通过您的方法,因为每个Insert
调用都会导致数据插入到数据库中。相反,您想要的是将
Savechanges()
调用分离到我上面的CommitChanges
方法中。这也需要处理数据库中的数据更新。为了更改实体的数据,实体框架会跟踪它收到的所有记录并监视它们以查看是否进行了任何更改。但是,您仍然需要告诉实体框架将所有更改的数据发送到数据库。这是通过
context.SaveChanges()
调用发生的。因此,您需要将其作为单独的调用,以便能够实际更新已编辑的数据,而当前的实现无法处理这些数据。Edit:
Your comment made me realize another issue that I see. One downfall is that you are creating a data context inside of the repository, and this isn't good. You really should have all (or most) of your created repositories sharing the same instance of your data context.
实体框架会跟踪在哪个上下文中跟踪实体,并且如果您尝试使用一个上下文中的实体更新另一个上下文中的实体,则会出现异常。当您开始编辑彼此相关的实体时,这种情况可能会发生。这也意味着您的 SaveChanges() 调用不是事务性的,每个实体都是在其自己的事务中更新/添加/删除的,这可能会变得混乱。
我在存储库中对此的解决方案是将 DbContext 传递到构造函数中的存储库中。
You need to step back and think about what the repository should be doing. A repository is used for retrieving records, adding records, and updating records. The repository you created barely handles the first case, handles the second case but not efficiently, and doesn't at all handle the 3rd case.
Most generic repositories have an interface along the lines of
For retrieving records, you can't just call the whole set with
AsEnumerable()
because that will load every database record for that table into memory. If you only want Users with the username ofusername1
, you don't need to download every user for the database as that will be a very large database performance hit, and a large client performance hit for no benefit at all.Instead, as you will see from the interface I posted above, you want to return an
IQueryable<T>
object.IQuerable
s allow whatever class that calls the repository to use Linq and add filters to the database query, and once the IQueryable is run, it's completely run on the database, only retrieving the records you want. The database is much better at sorting and filtering data then your systems, so it's best to do as much on the DB as you can.Now in regards to inserting data, you have the right idea but you don't want to call
SaveChanges()
immediately. The reason is that it's best to callSavechanges()
after all your db operations have been queued. For example, If you want to create a user and his profile in one action, you can't via your method, because eachInsert
call will cause the data to be inserted into the database then.Instead what you want is to separate out the
Savechanges()
call into theCommitChanges
method I have above.This is also needed to handle updating data in your database. In order to change an Entity's data, Entity Framework keeps track of all records it has received and watches them to see if any changes have been made. However, you still have to tell the Entity Framework to send all changed data up to the database. This happenes with the
context.SaveChanges()
call. Therefore, you need this to be a separate call so you are able to actually update edited data, which your current implementation does not handle.Edit:
Your comment made me realize another issue that I see. One downfall is that you are creating a data context inside of the repository, and this isn't good. You really should have all (or most) of your created repositories sharing the same instance of your data context.
Entity Framework keeps track of what context an entity is tracked in, and will exception if you attempt to update an entity in one context with another. This can occur in your situation when you start editing entities related to one another. It also means that your
SaveChanges()
call is not transactional, and each entity is updated/added/deleted in it's own transaction, which can get messy.My solution to this in my Repositories, is that the
DbContext
is passed into the repository in the constructor.我可能会因此被否决,但 DbContext 已经是一个存储库了。当您将域模型公开为具体 DbContext 的集合属性时,EF CTP5 会为您创建一个存储库。它提供了一个类似于集合的接口,用于访问域模型,同时允许您传递查询(作为 linq 或规范对象)以过滤结果。
如果您需要接口,CTP5 不会为您提供接口。我已经将自己的 DBContext 包装起来,并简单地公开了对象中公开可用的成员。它是可测试性和 DI 的适配器。
如果我说的不是很明显,我会发表评论以进行澄清。
I may get voted down for this, but DbContext already is a repository. When you expose your domain models as collection properties of your concrete DbContext, then EF CTP5 creates a repository for you. It presents a collection like interface for access to domain models whilst allowing you to pass queries (as linq, or spec objects) for filtering of results.
If you need an interface, CTP5 doesn't provide one for you. I've wrapped my own around the DBContext and simply exposed the publicly available members from the object. It's an adapter for testability and DI.
I'll comment for clarification if what I said isn't apparently obvious.