65.9K
CodeProject 正在变化。 阅读更多。
Home

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

2017 年 11 月 26 日

CPOL

7分钟阅读

viewsIcon

21742

downloadIcon

378

逐步创建 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 通用存储库中一个非常重要的方法。此方法返回 DbContextTEntity 类型的 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 RelayCommandInsertExecute 方法中调用 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 RelayCommandUpdateExecute 方法中调用 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 RelayCommandDeleteExecute 方法中调用 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 方法。

测试项目

测试项目包含五个项目

  1. BuildingEFGRepository.DAL - 包含通用存储库逻辑
  2. BuildingEFGRepository.DataBase - 包含 Entity Framework 类、POCO 数据库类和自定义存储库
  3. BuildingEFGRepository.DataBase.Tests - 包含 BuildingEFGRepository.DataBase 的测试
  4. BuildingEFGRepository.MVC - 包含 Web ASP.NET MVC 应用程序
  5. BuildingEFGRepository.WPF_DesCon - 包含 WPF 应用程序

您需要为 3 个配置文件更改连接字符串。

我们将更改原始路径,替换为我们机器的路径

示例

C:\TFS\PakkkoTFS\Blog\C#\BuildingEFGRepository\BuildingEFGRepository.DataBase\MyDb.mdf

For

C:\YourSolutionPath\BuildingEFGRepository\BuildingEFGRepository.DataBase\MyDb.mdf

要更改的配置文件

  1. BuildingEFGRepository.DAL.Tests\App.Config
  2. BuildingEFGRepository.MVC\Web.Config
  3. BuildingEFGRepository.WPF_DesCon\App.Config
© . All rights reserved.