Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/entity-framework/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 删除EF Core中具有并发性的实体_C#_Entity Framework_Entity Framework Core - Fatal编程技术网

C# 删除EF Core中具有并发性的实体

C# 删除EF Core中具有并发性的实体,c#,entity-framework,entity-framework-core,C#,Entity Framework,Entity Framework Core,我想删除EF Core中的一个实体,而不首先从数据库中加载它。我知道以前也有人问过类似的问题,但请耐心听我说,因为这个案例是不同的。除了通常的ID之外,实体还有一个行版本,这会导致问题 实体的定义如下: public int MyEntity { public int Id { get; set; } //Other irrelevant properties public byte[] RowVersion { get; set; } } 实体使用fluent API

我想删除EF Core中的一个实体,而不首先从数据库中加载它。我知道以前也有人问过类似的问题,但请耐心听我说,因为这个案例是不同的。除了通常的ID之外,实体还有一个行版本,这会导致问题

实体的定义如下:

public int MyEntity {
    public int Id { get; set; }
    //Other irrelevant properties
    public byte[] RowVersion { get; set; }
}
实体使用fluent API进行配置:

class MyEntityConfiguration : IEntityTypeConfiguration<MyEntity> {
    public void Configure( EntityTypeBuilder<MyEntity> builder ) {
        builder.Property( e => e.RowVersion )
            .IsRequired()
            .IsRowVersion();
    }
}
…并发检查落在我的脚上。我在DbUpdateConcurrencyException中收到此错误消息:

数据库操作预期影响1行,但实际影响0行。自加载实体后,数据可能已被修改或删除

原因是EF Core生成此查询以删除项目:

exec sp_executesql N'SET NOCOUNT ON;
DELETE FROM [MyEntity]
WHERE [Id] = @p57 AND [RowVersion] IS NULL;
SELECT @@ROWCOUNT',N'@p57 int',@p57=1 -- <--The ID of the item to delete
问题显然在中,[RowVersion]为空。这个条件永远不可能是真的,因为就像我在配置实体时明确告诉EF的那样,列是必需的,因此不能为NULL

当然,我没有在我想要删除的实体中添加行版本,实际上我也不想添加行版本,因为这意味着我必须从数据库中获取数据,这在这种情况下是不必要的。我甚至不介意在这里检查并发性,因为如果之前删除了该项,它不会有什么坏处


因此,问题是:有没有一种方法可以忽略此操作的并发检查,但不能忽略同一事务中的其他操作,或者以另一种方式执行删除操作,而不必首先从DB获取行版本。

您可以遵循下面我粘贴并解释的模式

1必须在模型和数据库上定义行版本字段

2对于删除,只需提供id和您在编辑更新或删除显示实体时已检索到的实体的行版本,您必须至少具有id

3如果没有行版本,则不必在意,默认为空

模式如下:

a检查是否在更改跟踪器上跟踪该实体,如果是,则仅将其删除

b如果实体未被跟踪,则它将模拟一个新的假实体,该实体的id和行版本为您拥有的id或null

啊哈!如果您没有提供行版本,则必须强制从数据库中提供有效版本,转到数据库a pick one use dbset Find method或

在我的例子中,我使用的是标量t-sql函数,通过使用db上下文执行:

最后,它断言删除已成功返回true 过后调用save changes时,dbcontext将保留更改或爆炸!!!,由您来处理错误

我将从通用存储库粘贴一些代码:

public async Task<bool> DeleteByIdAsync(TKey id, byte[] rowVersion = null, CancellationToken? token = null) 
{
    var entityTracked = _context.ChangeTracker
        .Entries<TEntity>()
        .Select((EntityEntry e) => (TEntity)e.Entity)
        .SingleOrDefault(e => e.Id.Equals(id));                                                             

    if (entityTracked != null)                                                                         
    {
        _context.Remove(entityTracked);                                                                     // If entity is tracked just delete it
    }
    else                                                                                                    // If not tracked, just mock up a new entity.
    {
        var entityMocked = new TEntity { Id = id, RowVersion = rowVersion };                                // (*) ValidateModelContext extension custom code will not validate for EntityState.Deleted to avoid moked entity fail validations. We are going to delete it anyway so has no sense to validate it before deleting it.

        if (rowVersion == null)                                                                             // DUDE!! Why you do not pass me a valid row version?. Then I forcelly must do a database trip to get the rowversion for this id. We do it executing an scalar function that returns me current row version value for this entity. 
            entityMocked.RowVersion = await GetRowVersionAsync(id.ToString()).ConfigureAwait(false);        // If the record do not exist on the database a null is returned. In such case, even if we already know that something went wrong, we let the dbconcurrency error occurs when saving changes since is really a concurency error, since the record not longer exists.

        _context.Remove(entityMocked);                                                                      // Just delete it it.
    }

    return true;
}
db上下文中映射这些UDF的代码如下:

public async Task<byte[]> GetRowVersionAsync<TEntity>(string id = null)
{
    switch (typeof(TEntity))
    {
        case var type when type == typeof(Department):
            return await Department.Select(e => GetDepartmentRowVersion(Convert.ToInt32(id))).FirstOrDefaultAsync();

        case var type when type == typeof(Player):
            return await (id == null ? Player.Select(e => GetPlayerRowVersion(Guid.Empty)) : Player.Select(e => GetPlayerRowVersion(new Guid(id)))).FirstOrDefaultAsync();

        case var type when type == typeof(Address):
            return await (id == null ? Address.Select(e => GetAddressRowVersion(Guid.Empty)) : Address.Select(e => GetAddressRowVersion(new Guid(id)))).FirstOrDefaultAsync();

        default:
            return new byte[] { };
    }
}

public static byte[] GetPlayerRowVersion(Guid id) => null;     // Scalar function mappings.
public static byte[] GetAddressRowVersion(Guid id) => null;    // When using an in memory database since SQL UDF functions are not mapped to any Server, so we return a null value instead of throwing an error.
public static byte[] GetDepartmentRowVersion(int id) => null;

我希望你保留关键概念。。BR和happy codding

不确定EF Core,但我对EF 6使用了以下解决方法:我创建了额外的NoConcurencyDbContext,它继承自main,override on ModelCreating并将所有RowVersion属性配置为ConcurencyMode。无

publicclass NoConcurencyDbContext : MainDbContext {

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<EntityWithRowVersio>().Property(t => t.RowVersion).IsConcurrencyToken(false);

    }
}
您可以编写T4模板,并从EF模型自动生成此类上下文。
因此,我们的想法是简单地修改模型配置,其中包含用于特定操作的子dbcontext。希望它也能在EF Core中完成

如果你有ID,为什么不也有rowversion?因为我是从外键获得ID的,这是我只能拥有ID的众多原因之一。我不确定是否有办法在单个操作中禁用它。您可能需要运行一些特殊的SQL。正如其他人所指出的,where子句是必要的。唯一缺失的部分应给出不同的错误,异常消息明确说明数据可能已更改或修改。您可以很容易地查看日志,找出正在运行的查询,并得出缺少RowVersion的结论。至于忽略它的能力,用户有很多方法可以编写错误代码。尝试识别所有案例,并向用户提供一条漂亮的消息是一项复杂的任务,没有什么价值。这种情况与用户提供的不正确值的唯一区别是,EF Core可以执行空检查。如果添加这样的检查,那么性能将急剧下降,而数据库的更新无论如何都会失败。
public async Task<byte[]> GetRowVersionAsync<TEntity>(string id = null)
{
    switch (typeof(TEntity))
    {
        case var type when type == typeof(Department):
            return await Department.Select(e => GetDepartmentRowVersion(Convert.ToInt32(id))).FirstOrDefaultAsync();

        case var type when type == typeof(Player):
            return await (id == null ? Player.Select(e => GetPlayerRowVersion(Guid.Empty)) : Player.Select(e => GetPlayerRowVersion(new Guid(id)))).FirstOrDefaultAsync();

        case var type when type == typeof(Address):
            return await (id == null ? Address.Select(e => GetAddressRowVersion(Guid.Empty)) : Address.Select(e => GetAddressRowVersion(new Guid(id)))).FirstOrDefaultAsync();

        default:
            return new byte[] { };
    }
}

public static byte[] GetPlayerRowVersion(Guid id) => null;     // Scalar function mappings.
public static byte[] GetAddressRowVersion(Guid id) => null;    // When using an in memory database since SQL UDF functions are not mapped to any Server, so we return a null value instead of throwing an error.
public static byte[] GetDepartmentRowVersion(int id) => null;
    [DbContext(typeof(DataContext))]
    [Migration(nameof(DataContext) + nameof(GetDepartmentRowVersion))]
    public class GetDepartmentRowVersion : Migration
    {
        protected override void Up(MigrationBuilder builder)
        {
            var sp = $@"
CREATE FUNCTION {nameof(DataContext.GetDepartmentRowVersion)} (@Id INT) RETURNS BINARY(8) AS BEGIN
DECLARE @rowVersion AS BINARY(8)
IF @Id = 0 SELECT @rowVersion = MAX([{nameof(Department.RowVersion)}]) FROM {nameof(DataContext.Department)} ELSE SELECT @rowVersion = [{nameof(Department.RowVersion)}] FROM {nameof(DataContext.Department)} WHERE {nameof(Department.Id)} = @Id
RETURN @rowVersion
END";

            if (builder.ActiveProvider.Split('.').Last() == StorageProvider.SqlServer.ToString()) builder.Sql(sp);
        }

        protected override void Down(MigrationBuilder builder)
        {
            var sp = $@"IF OBJECT_ID('{nameof(DataContext.GetDepartmentRowVersion)}') IS NOT NULL DROP FUNCTION {nameof(DataContext.GetDepartmentRowVersion)}";

            if (builder.ActiveProvider.Split('.').Last() == StorageProvider.SqlServer.ToString()) builder.Sql(sp);
        }
    }
publicclass NoConcurencyDbContext : MainDbContext {

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<EntityWithRowVersio>().Property(t => t.RowVersion).IsConcurrencyToken(false);

    }
}