简体   繁体   English

实体框架:删除子实体

[英]Entity Framework : deleting child entities

I am using EF, but I have lazy loading disabled. 我正在使用EF,但是我禁用了延迟加载。 Instead I am using eager loading, so I created my own service: 相反,我使用的是急切的加载,因此我创建了自己的服务:

/// <summary>
/// Generic service for entity framework
/// </summary>
/// <typeparam name="T">An entity model</typeparam>
public class Service<T> : IService<T> where T : class
{
    // Create our private properties
    private readonly DbContext _context;
    private readonly DbSet<T> _dbEntitySet;

    /// <summary>
    /// Default constructor
    /// </summary>
    /// <param name="context">The database context</param>
    public Service(DbContext context)
    {
        // Assign our context and entity set
        _context = context ?? throw new ArgumentNullException("context");
        _dbEntitySet = context.Set<T>();
    }

    /// <summary>
    /// Gets all the entities
    /// </summary>
    /// <param name="includes">Option includes for eager loading</param>
    /// <returns></returns>
    public IQueryable<T> List(params string[] includes)
    {

        // Create a query
        IQueryable<T> query = _dbEntitySet;

        // For each include, append to our query
        if (includes != null)
            foreach (var include in includes)
                query = query.Include(include);

        // Return our query
        return query;
    }

    /// <summary>
    /// Creates an entity
    /// </summary>
    /// <param name="model"></param>
    public void Create(T model) => _dbEntitySet.Add(model);

    /// <summary>
    /// Updates an entity
    /// </summary>
    /// <param name="model"></param>
    public void Update(T model) => _context.Entry<T>(model).State = EntityState.Modified;

    /// <summary>
    /// Removes an entity
    /// </summary>
    /// <param name="model"></param>
    public void Remove(T model) => _context.Entry<T>(model).State = EntityState.Deleted;

    /// <summary>
    /// Saves the database context changes
    /// </summary>
    /// <returns></returns>
    public async Task SaveChangesAsync()
    {
        try
        {
            // Save the changes to the database
            await _context.SaveChangesAsync();
        }
        catch (DbEntityValidationException ex)
        {

            // Retrieve the error messages as a list of strings.
            var errorMessages = ex.EntityValidationErrors.SelectMany(x => x.ValidationErrors).Select(x => x.ErrorMessage);

            // Join the list to a single string.
            var fullErrorMessage = string.Join("; ", errorMessages);

            // Combine the original exception message with the new one.
            var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);

            // Throw a new DbEntityValidationException with the improved exception message.
            throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
        }
        catch (DbUpdateException ex)
        {
            throw;
        }
    }

    /// <summary>
    /// Executes a stored procedure in sql
    /// </summary>
    /// <param name="procedure">The name of the sproc</param>
    /// <param name="parameters">the sql params for the sproc</param>
    /// <returns></returns>
    public DbRawSqlQuery<T> ExecuteProcedure(string procedure, List<SqlParameter> parameters)
    {
        var results = _context.Database.SqlQuery<T>($"exec {procedure} { CreateQueryStringFromParams(parameters) }");
        return results;
    }

    /// <summary>
    /// Dispose
    /// </summary>
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Creates the input string to run sprocs in sql with EF by converting the sql params into a nice string
    /// </summary>
    /// <param name="parameters"></param>
    /// <returns></returns>
    private static string CreateQueryStringFromParams(IEnumerable<SqlParameter> parameters)
    {
        var response = "";
        var list = parameters as IList<SqlParameter> ?? parameters.ToList();
        var length = list.Count;

        for (var i = 0; i < length; i++)
        {
            response += $"{list[i].ParameterName}=\"{list[i].Value}\"";
            if (i != length - 1)
                response += ", ";
        }

        return response;
    }

    /// <summary>
    /// Disposes of any attached resources
    /// </summary>
    /// <param name="disposing">A boolean indicating whether the object is being disposed</param>
    protected virtual void Dispose(bool disposing)
    {

        // If we are disposing, dispose of our context
        if (disposing)
            _context.Dispose();
    }
}

Each service then inherits this class: 然后,每个服务都将继承此类:

/// <summary>
/// Handles all Group related methods
/// </summary>
public class GroupService : Service<Group>, IGroupService
{

    /// <summary>
    /// The default constructor
    /// </summary>
    /// <param name="unitOfWork"></param>
    public GroupService(DbContext context) : base(context)
    {
    }

    /// <summary>
    /// Lists groups by category
    /// </summary>
    /// <param name="categoryId">The id of the category</param>
    /// <param name="includes"></param>
    /// <returns></returns>
    public IQueryable<Group> List(int categoryId, params string[] includes) => List(includes).Where(m => m.CategoryId == categoryId);

    /// <summary>
    /// Gets a single Group by id
    /// </summary>
    /// <param name="id">The id of the Group</param>
    /// <returns></returns>
    public async Task<Group> GetAsync(int id, params string[] includes) => await List(includes).Where(model => model.Id == id).SingleOrDefaultAsync();
}

Each "entity" has a class similar to this GroupService . 每个“实体”都有一个与此GroupService相似的类。 I also have providers for each entity type too and here is my delete method: 我也为每种实体类型提供了提供程序 ,这是我的删除方法:

/// <summary>
/// Delete a Group
/// </summary>
/// <param name="id">The Group id</param>
/// <returns></returns>
public async Task<bool> DeleteAsync(int id)
{

    // Get our model
    var model = await _service.GetAsync(id, "Questions");

    // For each question, remove from the database
    if (model.Questions != null)
        foreach (var question in model.Questions.ToList())
            if (!await _questionProvider.Value.DeleteAsync(question.Id, false))
                throw new Exception("Failed to delete the questions");

    // Update our Questions
    model.Questions = null;

    // Save our model
    _service.Remove(model);

    // Save the database changes
    await _service.SaveChangesAsync();

    // Return true
    return true;
}

As you can see, I just pull back the questions. 如您所见,我只是撤回了问题。

If there are some questions, then I invoke the questionProvider's delete method, which is very similar: 如果有问题,那么我将调用questionProvider的 delete方法,该方法非常相似:

/// <summary>
/// Delete a question
/// </summary>
/// <param name="id">The question id</param>
/// <param name="saveChanges">Saves the changes to the database after the delete (default true)</param>
/// <returns></returns>
public async Task<bool> DeleteAsync(int id, bool saveChanges = true)
{

    // Get our model
    var model = await _service.GetAsync(id, "Answers");

    // For each answer, delete from the database
    if (model.Answers != null)
        foreach (var answer in model.Answers.ToList())
            if (!await _answerProvider.Value.DeleteAsync(answer.Id, false))
                throw new Exception("Failed to delete the answers");

    // Update our Answers
    model.Answers = null;

    // Save our model
    _service.Remove(model);

    // Save the database changes
    if (saveChanges) await _service.SaveChangesAsync();

    // Return true
    return true;
}

As you can see, I do not save the context changes until all children have been removed. 如您所见,在删除所有子级之前,我不会保存上下文更改。 Now I must point out that I am not removing the child directly from the parent. 现在,我必须指出,我不是要直接从父母那里移除孩子。 Instead I am removing the entity from it's own collection and then setting the property to null. 相反,我是从其自己的集合中删除该实体,然后将该属性设置为null。 After it is all done I save the changes, but I am getting this error: 完成所有操作后,我保存更改,但出现此错误:

The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. 操作失败:由于一个或多个外键属性不可为空,因此无法更改该关系。 When a change is made to a relationship, the related foreign-key property is set to a null value. 对关系进行更改时,相关的外键属性将设置为空值。 If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted. 如果外键不支持空值,则必须定义新的关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。

Does anyone know how I can delete the entities in a method similar to what I am trying to achieve? 有谁知道我可以用与我尝试实现的方法类似的方法删除实体?

This was rather easy to fix after reading some other people with similar issues. 在阅读了其他一些有类似问题的人之后,这个问题很容易解决。 I just changed one line of code in my Service.cs from: 我只是从以下位置更改了Service.cs中的一行代码:

public void Remove(T model) => _context.Entry<T>(model).State = EntityState.Deleted;

to: 至:

public void Remove(T model) => _dbEntitySet.Remove(model);

And that worked. 那行得通。


Update 更新

I also found that the eager load needs to include all the navigation properties that will be affected. 我还发现,急切的负载需要包括将受到影响的所有导航属性。 So if I was to delete a Question which has Answers and Answers have Formulas , the Get invocation would have to be: 所以,如果我要删除的有答案答案问题中有公式 ,在获取调用必须是:

var model = await _service.GetAsync(id, "Answers.Formulas");

If you don't include that, you will get an error. 如果不包括在内,则会出现错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM