构建 Entity Framework 通用存储库 2 连接






4.79/5 (7投票s)
继续使用 Entity Framework 通用存储库,在本例中是为有状态应用程序连接。
引言
几周前,我们回顾了第一篇关于 断开连接存储库 的文章,在这篇文章中,我们将完成另一块拼图,即连接的通用存储库。在这种新的存储库类型中,出现了一个非常重要的全新角色,它就是 ObservableCollection<T>
,这将是连接通用存储库不可分割的朋友。
目录
- Entity Framework 通用存储库 已连接
- Set<TEntity> DbContext 方法
- 示例类
- 构建 Entity Framework 通用存储库 已连接
- 提取接口
- WPF 示例
- 扩展 ConGeneriRepository<TEntitiy>
- 测试项目
- 历史
Entity Framework 通用存储库 已连接
Entity Framework 通用存储库已连接,用于 WPF、Silverlight、Windows Forms、控制台应用程序等状态处理。
此存储库处理组更改,并已修复到 ItemsControls
。此通用存储库类型可直接处理 DataGrid
的修改。
其主要特点是
- 应通过依赖注入接收
DbContext
- 应实现
IDisposable
接口以释放非托管资源 - 应有一个
DbContext
受保护的属性。此属性将在通用存储库的生命周期内保持活动状态,仅在Dispose
方法中终止。 DbContext
属性将监听存储库的所有更改。- 它没有方法(添加、删除和更新),因为这些操作是通过 Local
DbSet
属性执行的。Local 是 ObservableCollection<TEntity> (INotifyCollectionChanged),通常会直接链接到(ListBox、ListView、DataGrid 等)。 - 已连接的存储库有一个
SaveChanged
方法,用于将所有更改发送到数据库。
它有一个 SaveChanged
方法。
我们必须考虑连接存储库的使用,因为它对数据库连接消耗有很大影响。此过程为每个用户和加载的屏幕消耗一个连接。
ObservableCollection<T>
是 Repository
的关键,它是用户/机器交互与 DbSet
/DbContext
之间的中间件。ObservableCollection<T>
通过查询接收数据库中的数据,并通过其事件 CollectionChanged
监听 insert
/delete
更改,并通过模型的事件 INotifiedPropertyChanged
监听修改。
Set<TEntity> DbContext 方法
Set<TEntity>
与 断开连接的存储库 中的一样非常重要,但在这种情况下,我们的已连接存储库将其引用保存在一个 protected
字段中,因为它必须在存储库的整个生命周期内可用。
有关更多信息,请阅读 断开连接的存储库 中 DbSet<T>
部分。
示例类
这是示例类
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 通用存储库 已断开连接
第一步,我们将创建通用的 ConGenericRepository
类
public class ConGenericRepository<TEntity> : IDisposable where TEntity : class
{
protected internal readonly DbContext _dbContext;
protected internal readonly DbSet<TEntity> _dbSet;
public ConGenericRepository(DbContext dbContext)
{
if (dbContext == null) throw new ArgumentNullException(nameof(dbContext),
$"The parameter dbContext can not be null");
_dbContext = dbContext;
_dbSet = _dbContext.Set<TEntity>();
}
public void Dispose()
{
if (_dbContext != null) _dbContext.Dispose();
}
}
首先,我们将注入 DbContext
对象,查询 DbSet
并将其保存在 dbset
字段中。
该类应实现 IDisposible
接口以释放非托管资源。
该类具有引用类型的泛型约束。
有些方法的描述与断开连接的非常相似,但在实现上有所不同。
让我们开始构建所有方法。
All / AllAsync
All
/AllAsync
方法返回 All
表数据。
public ObservableCollection<TEntity> All()
{
_dbSet.Load();
var result = _dbSet.Local;
return result;
}
public Task<ObservableCollection<TEntity>> AllAsync()
{
return Task.Run(() =>
{
return All();
});
}
使用中
[TestMethod]
public void All_OK()
{
ObservableCollection<FootballClub> result = instance.All();
Assert.IsNotNull(result);
Assert.IsTrue(result.Count > 0);
}
方法 All
/Async
将整个表加载到 Local (ObservableCollection
) 属性中并返回该集合。Local 属性会持续监听更改。
Find / FindAsync
Find
/FindAsync
方法与 All
/AllAsync
方法非常相似,但 Find
/FindAsync
通过 PK
搜索单个行。PK
可以是简单的或复杂的。始终返回一行。
public TEntity Find(params object[] pks)
{
if (pks == null) throw new ArgumentNullException(nameof(pks),
$"The parameter pks can not be null");
var result = _dbSet.Find(pks);
return result;
}
public Task<TEntity> FindAsync(object[] pks)
{
return _dbSet.FindAsync(pks);
}
param pks
的行为与 断开连接 相同,有关更多信息,请参阅 断开连接文章 的此部分。
使用中
[TestMethod]
public void Find_OK()
{
object[] pks = new object[] { 1 };
FootballClub result = instance.Find(pks);
Assert.AreEqual(result.Id, 1);
}
GetData / GetDataAsync
与 Find
/FindAsync
一样,GetData
/GetDataAsync
方法与 All
/AllAsync
非常相似,不同之处在于,GetData
有一个 Expression<Func<TEntity,bool>>
参数用于过滤查询,而 Find
/FindAsync
只返回一个项目,GetData
/GetDataAsync
则始终返回一个集合,即使该集合只有一个项目。
public ObservableCollection<TEntity> GetData(Expression<Func<TEntity, bool>> filter)
{
if (filter == null) throw new ArgumentNullException(nameof(filter),
$"The parameter filter can not be null");
_dbSet.Where(filter).Load();
var filterFunc = filter.Compile();
var result = new ObservableCollection<TEntity>(_dbSet.Local.Where(filterFunc));
RelinkObservableCollection(result);
return result;
}
public Task<ObservableCollection<TEntity>>
GetDataAsync(Expression<Func<TEntity, bool>> filter)
{
return Task.Run(() =>
{
return GetData(filter);
});
}
请注意,我们使用了一个 private
方法 RelinkObservableCollection
private void RelinkObservableCollection(ObservableCollection<TEntity> result)
{
result.CollectionChanged += (sender, e) =>
{
switch (e.Action)
{
case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
_dbSet.Add((TEntity)e.NewItems[0]);
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
_dbSet.Remove((TEntity)e.OldItems[0]);
break;
default:
break;
}
};
}
此方法是必需的,因为我们应该只返回 DbSet
属性 Local 信息的一部分。为此,我们创建一个新的 ObservableCollection
包含过滤后的数据,此时 Local 属性会解除与其的链接。RelinkObservableCollection
会重新将 ObservableCollection
的更改与 DbSet
链接起来。
使用中
[TestMethod]
public void GetData_OK()
{
Expression<Func<FootballClub, bool>> filter = a => a.Name == "Real Madrid C. F.";
ObservableCollection<FootballClub> result = instance.GetData(filter);
Assert.IsNotNull(result);
Assert.IsTrue(result.Count == 1);
}
SaveChanges / SaveChangesAsync
SaveChanges
/SaveChangesAsync
方法会将 ObservableCollection Local
属性保存在数据库中。
public int SaveChanges()
{
var result = _dbContext.SaveChanges();
return result;
}
public Task<int> SaveChangesAsync()
{
return _dbContext.SaveChangesAsync();
}
实际运行
[TestMethod]
public void SaveChanges_OK()
{
ObservableCollection<FootballClub> data = instance.All();
data.Add(new FootballClub
{
CityId = 1,
Name = "New Team",
Members = 0,
Stadium = "New Stadium",
FundationDate = DateTime.Today
});
int result = instance.SaveChanges();
int expected = 1;
RemovedInsertRecords();
Assert.AreEqual(expected, result);
}
HasChanges / HasChangesAsync
HasChanges
/HasChangesAsync
方法验证 DbSet
属性是否已被修改。在 WPF 应用程序中,这与 Commands 结合使用非常实用,可以启用或禁用保存按钮。
public bool HasChanges()
{
var result = _dbContext.ChangeTracker.Entries<TEntity>()
.Any(a => a.State == EntityState.Added
|| a.State == EntityState.Deleted
|| a.State == EntityState.Modified);
return result;
}
public Task<bool> HasChangesAsync()
{
return Task.Run(() =>
{
return HasChanges();
});
}
该方法验证 ChangeTracker
属性,查找状态为:Added
、Deleted
或 Modified
的行。
实际运行
[TestMethod]
public void HasChanges_OK()
{
ObservableCollection<FootballClub> data = instance.All();
data.Add(new FootballClub
{
CityId = 1,
Name = "New Team",
Members = 0,
Stadium = "New Stadium",
FundationDate = DateTime.Today
});
bool result = instance.HasChanges();
Assert.IsTrue(result);
}
提取接口
完成此操作后,我们将提取接口。
结果
public interface IConGenericRepository<TEntity> : IDisposable where TEntity : class
{
ObservableCollection<TEntity> All();
Task<ObservableCollection<TEntity>> AllAsync();
ObservableCollection<TEntity> GetData(Expression<Func<TEntity, bool>> filter);
Task<ObservableCollection<TEntity>> GetDataAsync(Expression<Func<TEntity, bool>> filter);
TEntity Find(params object[] pks);
Task<TEntity> FindAsync(object[] pks);
int SaveChanges();
Task<int> SaveChangesAsync();
bool HasChanges();
Task<bool> HasChangesAsync();
}
我们将 IDisposable
实现翻译到了这个接口。
WPF 示例
示例的创建与断开连接存储库相同,您可以在 断开连接文章 中看到它。
考虑到我们的连接通用存储库,MainViewModel
是最重要的类。在我们的示例中,我们直接与 datagrid
进行交互,进行三个操作:
Insert
- 要插入新行,我们将填充最后一个空白的datagridrow
。Update
- 要更新行,我们将点击datagridcell
进入编辑模式,然后修改数据。Delete
- 要删除行,我们将选择datagridrow
并按“supr
”键。
实际应用
接下来,我们将展示在 WPF 项目中使用连接通用存储库的类(ViewModels
)。
public class MainViewModel : ViewModelBase, IDisposable
{
private readonly IConGenericRepository<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(IConGenericRepository<FootballClub> repository)
{
_repository = repository;
Data = _repository.All();
}
public void Dispose()
{
_repository.Dispose();
}
public RelayCommand SaveCommand => new RelayCommand(SaveExecute, SaveCanExecute);
private bool SaveCanExecute()
{
var result = _repository.HasChanges();
return result;
}
private void SaveExecute()
{
Action callback = () =>
{
var changes = _repository.SaveChanges();
Messenger.Default.Send(new PopupMessage
($"It has been realized {changes} change(s) in Database." ));
CollectionViewSource.GetDefaultView(Data).Refresh();
};
Messenger.Default.Send(new PopupMessage
("Do you want to make changes in DataBase ?", callback));
}
}
MainViewModel
类通过注入接收 IConGenericRepository<FootballClub>
接口。在其构造函数中,填充同名的注入字段,并使用 All 通用存储库方法填充 ObservableCollection
。此类有一个名为 SaveCommand
的 RelayCommand
,该命令使用两个方法:Execute
和 CanExecute
。对于 CanExecute
方法,我们将使用 HasChanges
存储库方法,这将使“保存”按钮启用或禁用,并确保在没有更改的情况下保存。SaveExecuted
方法向视图发送消息以显示 messagebox,并等待 messagebox
的答复,以便通过 SaveChanges
存储库方法将数据保存到数据库。
扩展 ConGenericRepository
连接的世界可能会令人困惑。在上一个示例中,我们可以在 datagrid
中进行多次更改,当我们保存更改时,我们不知道哪些行被插入,哪些行被更新或删除。因此,我们将创建一个新的 datagrid
列来提供此信息。该列将是行的状态。
在 Entity Framework 模型类中,我们将创建一个新的 NotMapped
属性
private string _state;
[NotMapped]
public string State
{
get { return _state; }
set
{
if (_state != value)
{
_state = value;
OnPropertyChanged();
}
}
}
此属性将包含所有属性的通用状态。在 Entity Framework 的初始版本中,所有生成的实体类都包含此属性。
我们将添加新的特定连接通用存储库,FutballClubConRepository
public class FootballClubConRepository :
ConGenericRepository<FootballClub>, IFootballClubConRepository
{
public FootballClubConRepository(DbContext dbContext) : base(dbContext) { }
public string GetState(FootballClub entity)
{
var stateEntity = _dbContext.Entry(entity).State;
return stateEntity.ToString();
}
}
FutballClubConRepository
应 inherits ConGenericRepository<TEntity>
并实现一个基构造函数。添加 GetState
方法以告知 Entity Framework 的内部状态。
我们将更新 MainViewModel
public class MainViewModel : ViewModelBase, IDisposable
{
private readonly IFootballClubConRepository _repository;
//private readonly IConGenericRepository<FootballClub> _repository;
public ObservableCollection<FootballClub> Data { get; set; }
//public MainViewModel(IConGenericRepository<FootballClub> repository)
public MainViewModel(IFootballClubConRepository repository)
{
_repository = repository;
Data = _repository.All();
ListenerChangeState(Data, _repository);
}
private void ListenerChangeState(ObservableCollection<FootballClub> data,
IFootballClubConRepository repository)
{
data.ToList().ForEach(a => ChangeStateRegister(a, repository));
data.CollectionChanged += (sender, e) =>
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
var entity = e.NewItems[0] as FootballClub;
entity.State = "Added";
}
};
}
private void ChangeStateRegister
(FootballClub entity, IFootballClubConRepository repository)
{
entity.PropertyChanged += (sender, e) =>
{
if (e.PropertyName != "State")
{
entity.State = repository.GetState(entity);
}
};
}
public void Dispose()
{
_repository.Dispose();
}
public RelayCommand SaveCommand => new RelayCommand(SaveExecute, SaveCanExecute);
private bool SaveCanExecute()
{
var result = _repository.HasChanges();
return result;
}
private void SaveExecute()
{
Action callback = () =>
{
var changes = _repository.SaveChanges();
Messenger.Default.Send(new PopupMessage
($"It has been realized {changes} change(s) in Database." ));
CollectionViewSource.GetDefaultView(Data).Refresh();
ResetDataStates(Data);
};
Messenger.Default.Send(new PopupMessage
("Has you make the changes in DataBase ?", callback));
}
private void ResetDataStates(ObservableCollection<FootballClub> data)
{
data.ToList().ForEach(a => a.State = null);
}
}
我们添加了两个 private
方法来注册更改:ListenerChangedState
,用于注册 insert
更改;ChangeStateRegister
,用于注册修改后的更改。
最后,我们将回顾转换类
public class StateConverter : IMultiValueConverter
{
public ImageBrush _imgInsert;
public ImageBrush _imgUpdate;
public StateConverter()
{
_imgInsert = Application.Current.FindResource("Inserted") as ImageBrush;
_imgUpdate = Application.Current.FindResource("Edited") as ImageBrush;
}
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
if (values[0] == null) return null;
var valueStr = values[0].ToString();
switch (valueStr)
{
case "Added" : return _imgInsert;
case "Modified": return _imgUpdate;
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
此类将状态描述转换为图像描述。
实际运行
测试项目
测试项目的结构与上一篇文章 Generic Repository Disconnected 相同。我们添加了一个新的 WPF 项目 BuildingEFGRepository.WPF_Con
来实现新示例。
您还将看到项目 BuildingEFGRepository.WPF_Con
的 connectionstring
发生了变化。
历史
- 2017 年 12 月 18 日:初始版本