【问题标题】:Remove an entity without fetching it in the generic repository pattern entity framework在通用存储库模式实体框架中删除实体而不获取它
【发布时间】:2019-09-10 12:14:08
【问题描述】:

我正在尝试使用通用存储库模式从包含不同表(如 Employee、Project、Skills)的数据库中删除 Employee 实体。

namespace Information.Repository
{
    public class IRepositoy<TEntity> : IRepository<TEntity> where TEntity : class
    {
        private readonly ApplicationDbContext _dbContext;

        public IRepositoy(ApplicationDbContext dbContext)
        {
            _dbContext = dbContext;
        }
        public void Remove(int id)
        {
            TEntity element = _dbContext.Set<TEntity>().Find(id);
            _dbContext.Set<TEntity>().Remove(element);
        }
    }
}

当上述 Remove 方法被调用时,它会进行两次数据库调用

  1. 用于获取实体。

  2. 秒删。

我找到了类似下面的查询,它使用单个 SQL 查询执行 当实体类型(员工或项目或技能)已知时

 public void Remove(int id)
        {
            Employee employee = new Employee { EmployeeId = id };
            _dbContext.Entry(employee).State = EntityState.Deleted;
        }

任何人都可以建议我如何使用类似于上述示例的通用存储库模式来删除实体而不获取它。

【问题讨论】:

  • 如果您使用.Net Core,Global Query Filters 中解释的软删除将对您有所帮助。
  • 看看通用存储库的陷阱解释here

标签: c# sql entity-framework generics repository-pattern


【解决方案1】:

使用原始 SQL

实体框架不允许您删除尚未加载(或附加)的对象。这也扩展到条件删除(例如删除所有名为 John 的用户),因为它要求您在删除用户之前加载用户。

您可以通过执行原始 SQL 来解决此问题。这并不理想,因为您倾向于使用 EF,因此您不必编写 SQL,但缺乏体面的删除行为(不加载)使得这是一个可以接受的解决方案。

类似的东西:

using (var context = new FooContext())
{
    var command = "DELETE * FROM dbo.Foos WHERE Id = 1";

    context
        .Database
        .ExecuteSqlCommand(command);
} 

在相关的地方,不要忘记 SQL 注入保护。但是,对于简单的删除,这通常不是问题,因为 FK 通常是 GUID 或 int,不会让您受到注入攻击。


使其通用

您发布的示例也可以,但您可能没有使用它,因为它不容易被通用化。

在我的所有 EF 项目中,我倾向于为我的所有实体创建一个(抽象)基类,类似于:

public class BaseEntity
{
    public int Id { get; set; }

    public DateTime CreatedOn { get; set; }
    public string CreatedBy { get; set; }

    public DateTime? UpdatedOn { get; set; }
    public string UpdatedBy { get; set; }
}

接口也可以,我只是更喜欢这里的基类。

审计字段不是这个答案的一部分,但它们确实展示了拥有基类的好处。

当您的所有实体都继承自同一个基类时,您可以在您的存储库上放置一个泛型类型约束,以确保泛型类型具有Id 属性:

public class IRepositoy<TEntity> : IRepository<TEntity> where TEntity : BaseEntity

此时您可以普遍实施您提出的解决方案:

public void Remove(TEntity obj)
{
    dbContext.Entry(obj).State = EntityState.Deleted;
}

你也可以指定一个无参数的构造函数类型约束:

where TEntity : BaseEntity, new()

这也使您能够实例化您的泛型类型:

public void Remove(int id)
{
    TEntity obj = new TEntity() { Id = id };
    dbContext.Entry(obj).State = EntityState.Deleted;
}

注意
还有一个通用的原始 SQL 解决方案,但我省略了它,因为它更复杂,因为它要求您根据实体类型检索表名。
原始 SQL 变体仅在您想要执行条件删除的情况下才有价值(例如,删除所有 id 为偶数的实体)。

但是,由于大多数条件删除都是特定于实体的,这意味着您通常不需要将它们设为通用,这使得原始 SQL 方法更可行,因为您只需在特定存储库中实现它,而不是通用的。

【讨论】:

  • 非常好的答案,有详细的解释和提到的陷阱。不相关,但我通常不鼓励使用具有丰富 ORM 的通用存储库,如 EF/NH。我已经讨论过了here
【解决方案2】:

您仍然需要获取它。 Entity Framework 缓存您的 dbSet,因此通常非常快。像这样使用相同的上下文:

public virtual void Delete(object id)
    {
        TEntity entityToDelete = dbSet.Find(id);
        Delete(entityToDelete);
    }

    public virtual void Delete(TEntity entityToDelete)
    {
        if (context.Entry(entityToDelete).State == EntityState.Detached)
        {
            dbSet.Attach(entityToDelete);
        }
        dbSet.Remove(entityToDelete);
    }

其中 dbSet =

context.Set<TEntity>();

【讨论】:

  • 这里它也对数据库进行两次调用,一次用于获取实体,另一个用于删除它。当您执行我发布的第二个示例时,它正在执行单个查询来删除实体。查询是:DELETE [dbo].[Employees] WHERE ([EmployeeId] = 21)
  • @Rana:这个答案并没有声称只在一次通话中完成,而是向您展示了 EF 打算如何使用它(实际上是两次通话,而不是一次)。
  • @Rana:看我的回答。
【解决方案3】:

实体框架的当前限制是,为了更新或删除实体,您必须先将其检索到内存中。但是,删除特定记录的方法很少。

您可以尝试ExecuteSqlCommand删除特定记录

_dbContext.Database.ExecuteSqlCommand("Delete Employee where EmployeeId = {0}", id );

或尝试使用EntityFramework.Extended Library 删除特定记录

_dbContext.Settings.Where(s=> s.EmployeeId == id).Delete();

【讨论】:

  • 请注意,链接库已经 4 年不支持,删除功能现在被锁定在付费许可证后面。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多