dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.72k stars 3.17k forks source link

Migration for linq To sql with updateCheck #32045

Open Cestbienmoi opened 1 year ago

Cestbienmoi commented 1 year ago

We will migrate a application that use linq to sql updatecheck = WhenChanged for concurrency for all column except some always. See https://learn.microsoft.com/fr-fr/dotnet/api/system.data.linq.mapping.columnattribute.updatecheck?view=netframework-4.8.1 I do not seen any solution to do this in EF core (except always => ConcurrencyCheck).

Example a record with with fields : Id, A, B Each user 1/ 2/ get the record, 1/ update the field A, and save, 2/ update the field B (A is not touched), it can save too without conflict. But if 2/ change A too => it detects a conflict.

My only idea for now, it is to mark all field with concurrencycheck, if there is an conflict, check field that other user has changed and change the current/original value and resave but i think it is some "barbare" way. ((if changed too by user => show user conflict to accept or to cancel))

ajcvickers commented 1 year ago

Note for triage: looks like L2S has a special concurrency check: "WhenChanged: Use this column only when the member has been changed by the application."

FabriceAv commented 5 months ago

I find a "bad" workaround

using Microsoft.EntityFrameworkCore.SqlServer.Update.Internal;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Update;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;

#pragma warning disable EF1001 // Internal EF Core API usage.

namespace Test
{
    public class InlSqlServerModificationCommandBatchFactory : IModificationCommandBatchFactory
    {
        private const int DefaultMaxBatchSize = 42;
        private const int MaxMaxBatchSize = 1000;
        private readonly int _maxBatchSize;

        public InlSqlServerModificationCommandBatchFactory(
            ModificationCommandBatchFactoryDependencies dependencies,
            IDbContextOptions options)
        {
            Dependencies = dependencies;

            _maxBatchSize = Math.Min(
                options.Extensions.OfType<SqlServerOptionsExtension>().FirstOrDefault()?.MaxBatchSize ?? DefaultMaxBatchSize,
                MaxMaxBatchSize);

            if (_maxBatchSize <= 0)
            {
                throw new ArgumentOutOfRangeException(
                    nameof(RelationalOptionsExtension.MaxBatchSize), RelationalStrings.InvalidMaxBatchSize(_maxBatchSize));
            }
        }

        /// <summary>
        ///     Relational provider-specific dependencies for this service.
        /// </summary>
        protected virtual ModificationCommandBatchFactoryDependencies Dependencies { get; }
        public virtual ModificationCommandBatch Create()
        {
            return new InlSqlServerModificationCommandBatch(Dependencies, _maxBatchSize);
        }
    }

    public class InlSqlServerModificationCommandBatch : SqlServerModificationCommandBatch
    {
        public InlSqlServerModificationCommandBatch(ModificationCommandBatchFactoryDependencies dependencies,
            int maxBatchSize) : base(dependencies, maxBatchSize)
        {
        }

        protected override void AddCommand(IReadOnlyModificationCommand modificationCommand)
        {
            if (modificationCommand.EntityState == EntityState.Modified)
            {
                foreach (var columnModification in modificationCommand.ColumnModifications)
                {
                    if (columnModification.IsWrite)
                    {
                        columnModification.IsCondition = true;
                    }
                }
            }
            base.AddCommand(modificationCommand);
        }
    }
}

and i use in options .ReplaceService<IModificationCommandBatchFactory, InlSqlServerModificationCommandBatchFactory>()