Удалите объект, не извлекая его в общей структуре объекта шаблона репозитория
Я пытаюсь удалить объект Employee из базы данных, которая содержит различные таблицы, такие как Employee, Project, Skills, используя общий шаблон репозитория.
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, он выполняет два вызова базы данных
Один для получения сущности.
Второй для его удаления.
Я нашел запрос, подобный приведенному ниже, который выполняется с одним запросом SQL, когда известен тип объекта (сотрудник, проект или навык).
public void Remove(int id)
{
Employee employee = new Employee { EmployeeId = id };
_dbContext.Entry(employee).State = EntityState.Deleted;
}
Может ли кто-нибудь предложить мне, как удалить объект, не извлекая его, используя общий шаблон репозитория, аналогичный приведенному выше примеру.
3 ответа
Использование сырого SQL
Entity Framework не позволяет удалить объект, который вы не загрузили (или не прикрепили). Это также распространяется на условное удаление (например, удаление всех пользователей с именем 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 полезен только в тех случаях, когда вы хотите выполнить условное удаление (например, удаление всех объектов, идентификатор которых является четным числом).Однако, поскольку большинство условных удалений относятся к конкретным объектам, это означает, что вам обычно не нужно делать их универсальными, что делает необработанный подход SQL более жизнеспособным, поскольку вам нужно только реализовать его в конкретном репозитории, а не в общем.
Вам все еще нужно его принести. Entity Framework кэширует ваши dbSets, поэтому обычно это происходит довольно быстро. Используйте тот же контекст, например:
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>();
Текущее ограничение Entity Framework состоит в том, что для обновления или удаления сущности необходимо сначала получить ее в память. Однако есть несколько альтернатив для удаления определенной записи.
Можешь попробовать ExecuteSqlCommand
удалить конкретную запись
_dbContext.Database.ExecuteSqlCommand("Delete Employee where EmployeeId = {0}", id );
или попробуйте использовать https://github.com/zzzprojects/EntityFramework.Extended Library для удаления определенной записи
_dbContext.Settings.Where(s=> s.EmployeeId == id).Delete();