构建 Entity Framework 断开连接的通用存储库






4.89/5 (9投票s)
逐步创建 Entity Framework 通用存储库
引言
存储库模式将检索数据模型的映射逻辑与业务逻辑分离。我们将为应用程序中的每个实体类创建一个存储库类。所有这些类通常都有一组主要的相等方法,例如
全部
GetData
查找
Add
移除
更新
- 等等。
所有这些类都包含非常相似的代码,并且有非常相似的测试。
创建通用存储库可以节省时间和代码。
这是它的主要优点
- 代码量减少
- 测试量减少(您只需测试存储库本身或派生存储库类中的新代码)
- 提高测试覆盖率
- 减少开发时间
- 改进维护
本文将尝试一步一步地从头开始解释如何构建一个通用存储库。
目录
- 通用存储库类型
Set<TEntity> DbContext
方法- 示例类
- Entity Framework 通用存储库 离线
- 构建 Entity Framework 通用存储库 离线
All
/AllAsync
Find
/FindAsync
GetData
/GetDataAsync
Add
/AddAsync
Remove
/RemoveAsync
Update
/UpdateAsync
- 提取接口
- MVC 示例
- WPF 示例
- 扩展
DisconGeneriRepository<TEntitiy>
- 测试项目
通用存储库类型
这种通用存储库类型专注于 Entity Framework 技术。根据其特性,这些存储库可以是连接的或离线的。
篇幅有限,无法讨论两种类型,本文将介绍 Disconnected
类型,而 Connected
类型将留待以后介绍。
Set<TEntity> DbContext 方法
这是构建 Entity Framework 通用存储库中一个非常重要的方法。此方法返回 DbContext
中 TEntity
类型的 DbSet
的引用。
换句话说,Set<TEntity>
方法使我们能够从单个 DbContext
访问 TEntity
类型的 DbSet
,而无需知道 DbSet
属性的名称,也无需知道具体的 DbContext
类型。
更多信息请 点击此处 查看。
我将尝试用代码解释
我们有一个简单的 DbContext GeneralEntities
,其中包含一个简单的 DbSet Customers
,类型为 Customer
。
public partial class GeneralEntities : DbContext
{
public GeneralEntities() : base("name=GeneralEntities") { }
public DbSet<Customer> Customers { get; set; }
/// More Code
}
我们创建了一个简单的方法来访问 Customers DbSet
。
public void Do(GeneralEntities context)
{
/// We know the DbContext Type (GeneralEntities).
/// The DbSet type is static.
/// We know de DbSet name --> Customers.
DbSet<Customer> myDbSet = context.Customers;
}
在这种非常简单的情况下,因为我知道 DbContext
类型、DbSet
名称,并且 DbSet
是静态类型的。
接下来的方法包含通用动态实例化 DbSet
public void Do(DbContext context)
{
/// We don't know the DbContext Type, I know the base class.
/// The DbSet type is static (Customer)
/// We don't know the DbSet name --> Customers.
DbSet<Customer> myDbSet = context.Set<Customer>();
}
public void Do<TEntity>(DbContext context) where TEntity : class
{
/// We don't know the DbContext Type, I know the base class.
/// The DbSet type is generic (TEntity)
/// We don't know the DbSet name --> Customers.
DbSet<TEntity> myDbSet = context.Set<TEntity>();
}
方法参数是 DbContext
类型(基类),无法直接访问 DbSet<Customer>
属性。
图形比较
示例类
这些是示例类
public partial class MyDBEntities : DbContext
{
public MyDBEntities()
: base("name=MyDBEntities")
{
}
public virtual DbSet<City> Cities { get; set; }
public virtual DbSet<FootballClub> FootballClubs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<City>()
.Property(e => e.Name)
.IsUnicode(false);
modelBuilder.Entity<FootballClub>()
.Property(e => e.Name)
.IsUnicode(false);
modelBuilder.Entity<FootballClub>()
.Property(e => e.Members)
.HasPrecision(18, 0);
}
}
public partial class City
{
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Column(TypeName = "numeric")]
public decimal? People { get; set; }
[Column(TypeName = "numeric")]
public decimal? Surface { get; set; }
public ICollection<FootballClub> FootballClubs { get; set; }
}
public partial class FootballClub
{
public int Id { get; set; }
public int CityId { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Column(TypeName = "numeric")]
public decimal Members { get; set; }
[Required]
[StringLength(50)]
public string Stadium { get; set; }
[Column(TypeName = "date")]
public DateTime? FundationDate { get; set; }
public string Logo { get; set; }
}
Entity Framework 通用存储库 离线
Entity Framework 通用存储库 离线模式用于无状态流程,如 ASP.NET MVC、WebAPI、WPF/Forms 离线模式、批量处理等。
这些存储库进行一对一的更改,通常与编辑弹出窗口或新的编辑表单配合使用。
其主要特点是
- 应通过依赖注入接收
Func<DbContext>
,因为它将在每次方法执行时创建一个新的DbContext
。 - 它不需要具有
DbContext
属性,或者为了上述相同的原因实现了IDisposable
。 - 不需要
ObservableCollection<TEntity>
,因为我们将直接附加DbSet
。 - 它没有
SaveChanged
方法,因为在所有方法中,数据都会被保存。 - 如果有许多客户端同时打开,它将消耗很少的资源,因为它只在进行更改时进行交互。
构建 Entity Framework 通用存储库 离线
在第一步,我们将创建通用的 DesconGenericRepository
类
public class DisconGenericRepository<TEntity> where TEntity : class
{
protected readonly Func<DbContext> _dbContextCreator;
public DesconGenericRepository(Func<DbContext> dbContextCreator)
{
if (dbContextCreator == null) throw new ArgumentNullException(nameof(dbContextCreator),
$"The parameter dbContextCreator can not be null");
_dbContextCreator = dbContextCreator;
}
}
DisconGenericRepository
类有一个构造函数,其中注入了一个 Func<DbContext>
参数用于依赖注入,并对应一个只读字段。
该类对引用类型具有泛型约束。
让我们来构建所有方法。
ALL / ALLASYNC
All
/AllAsync
方法返回所有表数据。
public IEnumerable<TEntity> All()
{
var result = Enumerable.Empty<TEntity>();
using(var context = _dbContextCreator())
{
var dbSet = context.Set<TEntity>();
result = dbSet.ToList();
}
return result;
}
public Task<IEnumerable<TEntity>> AllAsync()
{
return Task.Run(() =>
{
return All();
});
}
正如我们所见,我们将使用 using 语句来实例一个 DbContext
,其中包含我们的 Func<DbContext>
字段。这将是离线通用存储库类中所有方法的常量。我们恢复 DbSet
实例并调用其 LinQ to Entities 方法 ToList
,以便在此时执行 select
。
使用中
[TestMethod]
public void All_OK()
{
Func<DbContext> contextCreator = () => new MyDBEntities() as DbContext;
instance = new DisconGenericRepository<FootballClub>(dbContextCreator: contextCreator);
IEnumerable<FootballClub> result = instance.All();
Assert.IsNotNull(result);
Assert.IsTrue(result.Count() > 0);
}
FIND / FINDASYNC
Find
/FindAsync
方法与 All
/AllAsync
方法非常相似,但 Find
/FindAsync
搜索主键 (PK) 的单个行。主键可以是简单的或复杂的。始终返回一行。
Find
/FindAsync
方法与 All
/AllAsync
方法非常相似,但 Find
/FindAsync
搜索主键 (PK) 的单个行。主键可以是简单的或复杂的。始终返回一行。
public TEntity Find(params object[] pks)
{
if (pks == null) throw new ArgumentNullException(nameof(pks), $"The parameter pks can not be null");
TEntity result = null;
using (var context = _dbContextCreator())
{
var dbSet = context.Set<TEntity>();
result = dbSet.Find(pks);
}
return result;
}
public Task<TEntity> FindAsync(params object[] pks)
{
return Task.Run(() =>
{
return Find(pks);
});
}
参数 pks
是一个 params
参数,因此可以接受用于复杂主键的值组。
简单主键的使用示例
[TestMethod]
public void Find_OK2()
{
Func<DbContext> contextCreator = () => new MyDBEntities() as DbContext;
instance = new DisconGenericRepository<FootballClub>(dbContextCreator: contextCreator);
FootballClub result = instance.Find(1);
Assert.AreEqual(result.Id, 1);
}
复杂主键的使用示例。
表定义
[TestMethod]
public void Find_OK2()
{
Func<DbContext> contextCreator = () => new MyDBEntities() as DbContext;
instance = new DisconGenericRepository<FootballClub>(dbContextCreator: contextCreator);
string propertyPk1 = "pk1";
int propertyPk2 = 15;
DateTime propertyPk3 = DateTime.Today;
FootballClub result = instance.Find(propertyPk1, propertyPk2, propertyPk3);
Assert.AreEqual(result.Id, 1);
}
GETDATA / GETDATAASYNC
与 Find
/FindAsync
一样,GetData
/GetDataAsync
方法与 All
/AllAsync
非常相似,不同之处在于 GetData
有一个 Expression<Func<TEntity,bool>>
参数用于过滤查询。
public IEnumerable<TEntity> GetData(Expression<Func<TEntity, bool>> filter)
{
if (filter == null) throw new ArgumentNullException(nameof(filter),
$"The parameter filter can not be null");
var result = Enumerable.Empty<TEntity>();
using (var context = _dbContextCreator())
{
var dbSet = context.Set<TEntity>();
result = dbSet.Where(filter).ToList();
}
return result;
}
public Task<IEnumerable<TEntity>> GetDataAsync(Expression<Func<TEntity, bool>> filter)
{
return Task.Run(() =>
{
return GetData(filter);
});
}
使用中
[TestMethod]
public void GetData_OK()
{
Func<DbContext> contextCreator = () => new MyDBEntities() as DbContext;
instance = new DisconGenericRepository<FootballClub>(dbContextCreator: contextCreator);
Expression<Func<FootballClub, bool>> filter = a => a.Name == "Real Madrid C. F.";
IEnumerable<FootballClub> result = instance.GetData(filter);
Assert.IsNotNull(result);
Assert.IsTrue(result.Count() == 1);
}
ADD / ADDASYNC
顾名思义,Add
/AddAsync
方法在数据库中插入元素。
public int Add(TEntity newEntity)
{
if (newEntity == null) throw new ArgumentNullException(nameof(newEntity),
$"The parameter newEntity can not be null");
var result = 0;
using (var context = _dbContextCreator())
{
var dbSet = context.Set<TEntity>();
dbSet.Add(newEntity);
result = context.SaveChanges();
}
return result;
}
public Task<int> AddAsync(TEntity newEntity)
{
return Task.Run(() =>
{
return Add(newEntity);
});
}
public int Add(IEnumerable<TEntity> newEntities)
{
if (newEntities == null) throw new ArgumentNullException(nameof(newEntities),
$"The parameter newEntities can not be null");
var result = 0;
using (var context = _dbContextCreator())
{
var dbSet = context.Set<TEntity>();
dbSet.AddRange(newEntities);
result = context.SaveChanges();
}
return result;
}
public Task<int> AddAsync(IEnumerable<TEntity> newEntities)
{
return Task.Run(() =>
{
return Add(newEntities);
});
}
它有两个重载,分别用于单个实体或实体集合,都返回插入到数据库的元素数量。
使用中
[TestMethod]
public void Add_SimpleItem_OK()
{
Func<DbContext> contextCreator = () => new MyDBEntities() as DbContext;
instance = new DisconGenericRepository<FootballClub>(dbContextCreator: contextCreator);
FootballClub newEntity = new FootballClub
{
IdCity = 1,
Name = "New Team",
Members = 0,
Stadium = "New Stadium",
FundationDate = DateTime.Today
};
int result = instance.Add(newEntity);
int expected = 1;
Assert.AreEqual(expected, result);
}
[TestMethod]
public void Add_MultiItems_OK()
{
Func<DbContext> contextCreator = () => new MyDBEntities() as DbContext;
instance = new DisconGenericRepository<FootballClub>(dbContextCreator: contextCreator);
IEnumerable<FootballClub> newEntities = new List<FootballClub>
{
new FootballClub
{
IdCity = 1,
Name = "New Team",
Members = 0,
Stadium = "New Stadium",
FundationDate = DateTime.Today
},
new FootballClub
{
IdCity = 1,
Name = "New Team 2",
Members = 0,
Stadium = "New Stadium 2",
FundationDate = DateTime.Today
}
};
int result = instance.Add(newEntities);
int expected = 2;
Assert.AreEqual(expected, result);
}
REMOVE / REMOVEASYNC
这些方法有更多的重载,并分为两组
- 移除实体
- 移除主键
/// For Object (TEntity)
public int Remove(TEntity removeEntity)
{
if (removeEntity == null) throw new ArgumentNullException(nameof(removeEntity),
$"The parameter removeEntity can not be null");
var result = 0;
using (var context = _dbContextCreator())
{
var dbSet = context.Set<TEntity>();
dbSet.Attach(removeEntity);
context.Entry(removeEntity).State = EntityState.Deleted;
result = context.SaveChanges();
}
return result;
}
public Task<int> RemoveAsync(TEntity removeEntity)
{
return Task.Run(() =>
{
return Remove(removeEntity);
});
}
public int Remove(IEnumerable<TEntity> removeEntities)
{
if (removeEntities == null) throw new ArgumentNullException(nameof(removeEntities),
$"The parameter removeEntities can not be null");
var result = 0;
using (var context = _dbContextCreator())
{
var dbSet = context.Set<TEntity>();
foreach (var removeEntity in removeEntities)
{
dbSet.Attach(removeEntity);
context.Entry(removeEntity).State = EntityState.Deleted;
}
dbSet.RemoveRange(removeEntities);
result = context.SaveChanges();
}
return result;
}
public Task<int> RemoveAsync(IEnumerable<TEntity> removeEntities)
{
return Task.Run(() =>
{
return Remove(removeEntities);
});
}
/// For PKs
public int Remove(params object[] pks)
{
if (pks == null) throw new ArgumentNullException(nameof(pks),
$"The parameter removeEntity can not be null");
var result = 0;
using (var context = _dbContextCreator())
{
var dbSet = context.Set<TEntity>();
var entity = Find(pks);
dbSet.Attach(entity);
context.Entry(entity).State = EntityState.Deleted;
result = context.SaveChanges();
}
return result;
}
public Task<int> RemoveAsync(params object[] pks)
{
return Task.Run(() =>
{
return Remove(pks);
});
}
对于移除方法,我们使用了 2 个重要的 Entity Framework 方法
DbSet.Attach
- 此DbSet
类方法将实体对象附加到DbSet
属性,状态为 unchanged。这是必需的,因为如果我们使用了DbSet.Remove
方法,它会引发一个异常,因为不在上下文(DbContext
)中的实体无法被移除。DbContext.Entry(obj).State
- 它会查询ChangeTracker DbContext
属性,并将其状态修改为 deleted。
使用中
[TestMethod]
public void Remove_SimpleItem_forEntity_OK()
{
/// changed pk for tests
Func<DbContext> contextCreator = () => new MyDBEntities() as DbContext;
instance = new DisconGenericRepository<FootballClub>(dbContextCreator: contextCreator);
var removeEntity = instance.Find(99);
int result = instance.Remove(removeEntity);
int expected = 0;
Assert.AreEqual(expected, result);
}
[TestMethod]
public void Remove_MultiItems_forEntity_OK()
{
/// changed pk for tests
Func<DbContext> contextCreator = () => new MyDBEntities() as DbContext;
instance = new DisconGenericRepository<FootballClub>(dbContextCreator: contextCreator);
IEnumerable<FootballClub> removeEntities = new List<FootballClub>
{
new FootballClub
{
Id = 9999,
CityId = 1,
Name = "New Team",
Members = 0,
Stadium = "New Stadium",
FundationDate = DateTime.Today
},
new FootballClub
{
Id = 100,
CityId = 1,
Name = "New Team 2",
Members = 0,
Stadium = "New Stadium 2",
FundationDate = DateTime.Today
}
};
int result = instance.Remove(removeEntities);
int expected = 0;
Assert.AreEqual(expected, result);
}
[TestMethod]
public void Remove_SimpleItem_forPK_OK()
{
/// changed pk for tests
Func<DbContext> contextCreator = () => new MyDBEntities() as DbContext;
instance = new DisconGenericRepository<FootballClub>(dbContextCreator: contextCreator);
int result = instance.Remove(pks: 9999);
int expected = 0;
Assert.AreEqual(expected, result);
}
UPDATE / UPDATEASYNC
数据库中的更新值与 Remove
方法非常相似,但更简单,因为它没有用于主键或集合的更新。
public int Update(TEntity updateEntity)
{
if (updateEntity == null) throw new ArgumentNullException(nameof(updateEntity),
$"The parameter updateEntity can not be null");
var result = 0;
using (var context = _dbContextCreator())
{
var dbSet = context.Set<TEntity>();
dbSet.Attach(updateEntity);
context.Entry(updateEntity).State = EntityState.Modified;
result = context.SaveChanges();
}
return result;
}
public Task<int> UpdateAsync(TEntity updateEntity)
{
return Task.Run(() =>
{
return Update(updateEntity);
});
}
使用中
[TestMethod]
public void Update_OK()
{
/// changed values for tests
Func<DbContext> contextCreator = () => new MyDBEntities() as DbContext;
instance = new DisconGenericRepository<FootballClub>(dbContextCreator: contextCreator);
FootballClub updateEntity = new FootballClub
{
Id = 9999,
CityId = 1,
Name = "New Team 3",
Members = 10,
Stadium = "New Stadium 3",
FundationDate = DateTime.Today
};
int result = instance.Update(updateEntity);
int expected = 0;
Assert.AreEqual(expected, result);
}
提取接口
完成这些之后,我们将提取接口。
结果
public interface IDisconGenericRepository<TEntity> where TEntity : class
{
IEnumerable<TEntity> All();
Task<IEnumerable<TEntity>> AllAsync();
TEntity Find(params object[] pks);
Task<TEntity> FindAsync(params object[] pks);
IEnumerable<TEntity> GetData(Expression<Func<TEntity, bool>> filter);
Task<IEnumerable<TEntity>> GetDataAsync(Expression<Func<TEntity, bool>> filter);
int Add(TEntity newEntity);
Task<int> AddAsync(TEntity newEntity);
int Add(IEnumerable<TEntity> newEntities);
Task<int> AddAsync(IEnumerable<TEntity> newEntities);
int Remove(TEntity removeEntity);
Task<int> RemoveAsync(TEntity removeEntity);
int Remove(IEnumerable<TEntity> removeEntities);
Task<int> RemoveAsync(IEnumerable<TEntity> removeEntities);
int Remove(params object[] pks);
Task<int> RemoveAsync(params object[] pks);
int Update(TEntity updateEntity);
Task<int> UpdateAsync(TEntity updateEntity);
}
MVC 示例
让我们尝试在一个“真实应用程序”中使用我们的通用存储库,在本例中是 ASP.NET MVC Web 应用程序。向我们的解决方案添加一个 MVC 项目。
我们将安装 Autofac.MVC
用于依赖注入 (IoC)。
我们将解释 Autofac.MVC
的抽象概念,有关更多信息,请参阅 此链接。
在 Globalasax.cs 类中,我们将添加 RegisterAutofac()
方法,并在 Application_Start()
方法的第一行调用它。
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
/// Add call
RegisterAutofac();
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
private void RegisterAutofac()
{
var builder = new ContainerBuilder();
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.RegisterSource(new ViewRegistrationSource());
// manual registration of types;
IDisconGenericRepository<FootballClub> footbalRepository =
new DisconGenericRepository<FootballClub>(() => new MyDBEntities());
builder.Register<IDisconGenericRepository<FootballClub>>(a => footbalRepository);
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}
添加一个新的完整控制器:FootballClubsController
,并生成所有操作及其视图。
我们将查看 Controller
类
public class FootballClubsController : Controller
{
private readonly IDisconGenericRepository<FootballClub> _repository;
public FootballClubsController(IDisconGenericRepository<FootballClub> repository)
{
_repository = repository;
}
}
我们的离线通用存储库的依赖注入。
这些是数据库操作
// GET: FootballClubs
public ActionResult Index()
{
var model = _repository.All();
return View(model);
}
对于 Index
操作,我们将使用 All
存储库方法。
// GET: FootballClubs/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
FootballClub footballClub = _repository.Find(id);
if (footballClub == null)
{
return HttpNotFound();
}
return View(footballClub);
}
对于 Details
操作,我们将使用 Find
存储库方法按 id 选择行。
// POST: FootballClubs/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,
CityId,Name,Members,Stadium,FundationDate,Logo")] FootballClub footballClub)
{
if (ModelState.IsValid)
{
_repository.Add(footballClub);
return RedirectToAction("Index");
}
return View(footballClub);
}
对于 Create POST 操作,我们将使用 Add
存储库方法创建一个新的 FootballClub
数据库行。
// POST: FootballClubs/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,CityId,Name,
Members,Stadium,FundationDate,Logo")] FootballClub footballClub)
{
if (ModelState.IsValid)
{
_repository.Update(footballClub);
return RedirectToAction("Index");
}
return View(footballClub);
}
对于 Edit POST 操作,我们将使用 Update
存储库方法更新 FootballClub
数据库行的所有属性。
// POST: FootballClubs/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
_repository.Remove(id);
return RedirectToAction("Index");
}
对于 DeleteConfirmed
POST 操作,我们将使用 Remove
存储库方法删除 FootballClub
数据库行(按 id)。请记住,离线通用存储库有一个用于完整 FootballClub
对象的 Remove
方法。
有关 FootballClubController
的更多信息,请下载项目。
实际运行
https://www.youtube.com/watch?v=n_3mXMkYyw0&feature=youtu.be
WPF 示例
尽管 WPF 支持有状态应用程序约定(已连接),但我们可以使用无状态技术并释放数据库连接和资源。
WPF 应用程序仅在执行任何操作时连接到数据库服务器,并且仅在数据库操作期间连接,而不是在整个应用程序生命周期中连接。
我们使用 MVVM 模式实现了 WPF 项目。我们使用了以下出色的工具包(更多信息)
- MVVM Light
- MahApps
接下来,我们将展示我们在 WPF 项目中使用离线通用存储库的类(ViewModels
)。
InsertViewModel
:
public class InsertViewModel : ViewModelBase
{
private FootballClub _model;
public FootballClub Model
{
get { return _model; }
set { Set(nameof(Model), ref _model, value); }
}
private readonly IDisconGenericRepository<FootballClub> _repository;
public InsertViewModel(FootballClub model, IDisconGenericRepository<FootballClub> repository)
{
Model = model;
_repository = repository;
}
public RelayCommand InsertCommand => new RelayCommand(InsertExecute);
private void InsertExecute()
{
_repository.Add(Model);
Messenger.Default.Send(new NotificationMessage("Inserted"));
}
// ... Another code
}
我们将在 InsertCommand RelayCommand
的 InsertExecute
方法中调用 Add
方法。
EditViewModel
:
public class EditViewModel : ViewModelBase
{
private FootballClub _model;
public FootballClub Model
{
get { return _model; }
set { Set(nameof(Model), ref _model, value); }
}
private readonly IDisconGenericRepository<FootballClub> _repository;
public EditViewModel(FootballClub model, IDisconGenericRepository<FootballClub> repository)
{
Model = model;
_repository = repository;
}
public RelayCommand AceptChangesCommand => new RelayCommand(AceptChangesExecute);
private void AceptChangesExecute()
{
_repository.Update(Model);
Messenger.Default.Send(new NotificationMessage("Updated"));
}
public RelayCommand CancelCommand => new RelayCommand(CancelExecute);
private void CancelExecute()
{
Messenger.Default.Send(new NotificationMessage("Cancel"));
}
// ... Another code
}
我们将在 UpdateCommand RelayCommand
的 UpdateExecute
方法中调用 Update
方法。
MainViewModel
:
public class MainViewModel : ViewModelBase
{
private readonly IDisconGenericRepository<FootballClub> _repository;
public ObservableCollection<FootballClub> Data { get; set; }
private FootballClub _selectedItem;
public FootballClub SelectedItem
{
get { return _selectedItem; }
set { Set(nameof(SelectedItem), ref _selectedItem, value); }
}
public MainViewModel(IDisconGenericRepository<FootballClub> repository)
{
_repository = repository;
Data = new ObservableCollection<FootballClub>(_repository.All());
}
// ... Another code
public RelayCommand DeleteCommand => new RelayCommand(DeleteExecute, () => SelectedItem != null);
private void DeleteExecute()
{
_repository.Remove(SelectedItem);
Data.Remove(SelectedItem);
}
// ... Another code
}
我们将在 DeleteCommand RelayCommand
的 DeleteExecute
方法中调用 Remove
方法。
https://www.youtube.com/watch?v=fyTYS6NgbVg&feature=youtu.be
扩展 DisconGenericRepository<TEntity>
DisconGenericRepository
有一些有趣的方法,但我们可能需要用新方法来扩展其功能。
以满足我们的需求。
最好的方法是继承主类并在派生类中创建新方法。
public class FootballClubRepository : DisconGenericRepository<FootballClub>, IFootballClubRepository
{
public FootballClubRepository(Func<DbContext> dbContextCreator) : base(dbContextCreator) { }
public int UpdateRangeLow(IEnumerable<FootballClub> entities)
{
int result = 0;
foreach (var entity in entities)
{
/// Is low, because create a connection foreach entity
/// we use this case for didactic reasons
result += base.Update(entity);
}
return result;
}
public int UpdateRangeFast(IEnumerable<FootballClub> entities)
{
int result = 0;
using(var context = base._dbContextCreator())
{
entities.ToList().ForEach(e => UpdateEntity(e, context));
result = context.SaveChanges();
}
return result;
}
private void UpdateEntity(FootballClub entity, DbContext context)
{
var dbSet = context.Set<FootballClub>();
dbSet.Attach(entity);
context.Entry(entity).State = EntityState.Modified;
}
}
批量更新并不常见,但我们决定添加此方法,因为它将很有用。
出于教学原因,我们插入了两个方法:Update
,第一个是一个低级方法,因为它将为每次更新创建一个新的数据库连接。第二个方法性能更好,因为它在同一个数据库上下文中执行更新查询。为了重构原因,还存在另一个 private update
方法。
测试项目
测试项目包含五个项目
BuildingEFGRepository.DAL
- 包含通用存储库逻辑BuildingEFGRepository.DataBase
- 包含 Entity Framework 类、POCO 数据库类和自定义存储库BuildingEFGRepository.DataBase.Tests
- 包含BuildingEFGRepository.DataBase
的测试BuildingEFGRepository.MVC
- 包含 Web ASP.NET MVC 应用程序BuildingEFGRepository.WPF_DesCon
- 包含 WPF 应用程序
您需要为 3 个配置文件更改连接字符串。
我们将更改原始路径,替换为我们机器的路径
示例
C:\TFS\PakkkoTFS\Blog\C#\BuildingEFGRepository\BuildingEFGRepository.DataBase\MyDb.mdf
For
C:\YourSolutionPath\BuildingEFGRepository\BuildingEFGRepository.DataBase\MyDb.mdf
要更改的配置文件
- BuildingEFGRepository.DAL.Tests\App.Config
- BuildingEFGRepository.MVC\Web.Config
- BuildingEFGRepository.WPF_DesCon\App.Config