Производительность .NET 6!

.NET 6 работает быстро. Очень быстро! Поскольку я ❤️ .NET, я собираюсь показать вам, насколько быстр .NET 6!

В этой короткой статье или, скажем так, примере (исходный код), я собираюсь показать вам, как вставить 1.5 миллиона записей, а затем получить, массировать и снова вставить за очень короткое время.

Прежде всего, я собираюсь создать простую таблицу в SQL Server 2019, как вы можете видеть ниже.

  • Имя базы данных : BenchMarkDB
  • Имя таблицы должно быть : StockMarket

Структуру таблицы вы можете увидеть на рисунке.

Сценарий таблицы также предоставляется.

CREATE TABLE [dbo].[StockMarket](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [StockShare] [int] NULL,
    [Name] [nvarchar](50) NULL,
    [Family] [nvarchar](50) NULL,
    [State] [nvarchar](50) NULL,
    [LogTime] [datetime] NULL,
 CONSTRAINT [PK_StockMarketNormal] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

Вход в полноэкранный режим Выход из полноэкранного режима

Поскольку я собираюсь использовать таблицу с оптимизацией памяти, вам следует преобразовать вашу таблицу в таблицу с оптимизацией памяти, чтобы получить наилучший результат производительности. Как это сделать? Следуйте рисунку и инструкции ниже.


Нажмите на уважаемый пункт, после чего вы увидите следующую страницу.

Если вы нажмете на Next, следующей страницей должна быть следующая страница, как я показал ниже.

Продолжайте нажимать на кнопку Далее

Как видно на скриншоте, SQL Server создаст новую таблицу и изменит имя текущей таблицы с суффиксом «_old», чтобы сохранить все, что вы уже создали, как есть.

Нажмите кнопку Далее, после чего вы увидите следующую страницу.

Работа с SQL Server почти закончена, нажмите кнопку Далее, чтобы увидеть следующую страницу.

Нажмите кнопку Migrate, и вы закончите работу с SQL Server.


Нажмите на кнопку OK, чтобы увидеть новую таблицу.

Это должен быть последний снимок экрана, касающийся создания оптимизированной таблицы In-Memory.

Как я уже сказал, SQL Server вполне достаточно. Я создал простое консольное приложение .NET 6 и назвал его «EF_6_BenchMark». Затем, как вы можете видеть, есть папка, которую я назвал «EF_6_Performance».

Все, что находится вне этой папки, кроме Program.cs, совершенно не имеет отношения к данному образцу и должно быть проигнорировано.

Прежде чем начать разговор о кодировании, давайте добавим все необходимое из Nuget. Прежде всего, пожалуйста, установите пакет Nuget, который я представил ниже.


Если вам сложно работать с картинками, вы можете скопировать и вставить название Nuget, как я указал ниже. Я не говорю установить его командой, я говорю, что вы можете скопировать название аддона из текста ниже, но если вы заинтересованы в добавлении командой, продолжайте, я уже предоставил то, что вам нужно.

Install-Package EFCore.BulkExtensions -Version 6.5.6
Вход в полноэкранный режим Выйти из полноэкранного режима

Следующим пакетом Nuget будет этот:

Вам нужен текст, он уже здесь:

Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 7.0.0-preview.6.22329.4
Войти в полноэкранный режим Выйти из полноэкранного режима

Я создал папку и назвал ее : Entity, поэтому давайте посмотрим, что происходит внутри этой папки. Здесь есть простая модель/сущность, связанная с моей таблицей. Взгляните:

 public class StockMarket
    {
        [Key]
        public int ID { get; set; }
        [Column(TypeName = "varchar")]
        public string Name { get; set; }
        [Column(TypeName = "varchar")]
        public string Family { get; set; }
        public int StockShare { get; set; }
        [Column(TypeName = "varchar")]
        public string State { get; set; }
        public DateTime LogTime { get; set; }
    }
Вход в полноэкранный режим Выход из полноэкранного режима

Следующая папка — : DbContext, которая содержит мой DbContext и его конфигурации, как показано ниже.

public partial class StockMarketContext : DbContext
    {
        public StockMarketContext()
        {
        }
        public StockMarketContext(DbContextOptions<StockMarketContext> options)
            : base(options)
        {

        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer("Server=ALIKOLAHDOOZAN;Database=BenchMarkDB;Trusted_Connection=True;",
                 options => options.EnableRetryOnFailure(
                maxRetryCount: 4,
                maxRetryDelay: TimeSpan.FromSeconds(1),
                errorNumbersToAdd: new int[] { }
            ));
            base.OnConfiguring(optionsBuilder);
        }

        public virtual DbSet<StockMarket> StockMarket { get; set; } = null!;
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<StockMarket>(entity =>
            {
                entity.ToTable("StockMarket");
                entity.Property(e => e.ID).HasColumnName("ID");
                entity.Property(e => e.Name)
                    .HasMaxLength(50)
                    .IsUnicode(false);
                entity.Property(e => e.Family)
                    .HasMaxLength(100)
                    .IsUnicode(false);
                entity.Property(e => e.StockShare)
                    .IsUnicode(false);
                entity.Property(e => e.State)
                    .HasMaxLength(10)
                    .IsUnicode(false);
            });
            OnModelCreatingPartial(modelBuilder);
        }
        partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
    }
Войти в полноэкранный режим Выход из полноэкранного режима

Здесь находится моя строка подключения и все конфигурации, касающиеся моей сущности.

Следующая папка : Service. Внутри папки Service у меня есть две вложенные папки.

  • Интерфейс
  • Конкретный

Давайте посмотрим на содержимое интерфейса.

public interface IStockMarketService
    {
        Task BulkInsertAsync(int NumberOfRows, string state);
        void BulkUpdate(int NumberOfRows);
        void BulkDelete(int NumberOfRows);
        Task BulkInsertfromlist(IEnumerable<StockMarket> Data);
        List<StockMarket> LoadStockBasedOnState(string State);
        Task<IEnumerable<StockMarket>> LoadStockBasedOnStateByRawSQL(string State);
    }
Вход в полноэкранный режим Выход из полноэкранного режима

Как вы видите, я должен реализовать интерфейс, но прежде чем это сделать, давайте посмотрим на сценарий, который я собираюсь реализовать.

У моей сущности есть поле State. Это означает, что моя таблица в базе данных также имеет колонку State. Если вы посмотрите на метод BulkInsertAsync в моем интерфейсе, у метода есть два входных параметра. NumberOfRows и state, поэтому после реализации интерфейса я могу вставить столько записей, сколько захочу для каждого штата, используя входные параметры NumberOfRows и state (мой план — 150_000 строк для каждого штата), так что это означает, что, имея 10 различных штатов, я могу вставить 1,5 миллиона строк, затем я получу все данные, принадлежащие каждому штату, изменю значение сущности StockShare и затем снова вставлю строку в таблицу, так что в конце истории у меня будет 3 миллиона строк в моей таблице SQL. Как я уже пробовал, в .NET 6 это делается очень быстро! Давайте вернемся к коду и посмотрим, что происходит в реализации Service.

 public class StockMarketService : IStockMarketService
    {
        public async Task BulkInsertAsync(int NumberOfRows, string state)
        {
            var _StockMarketContext = new StockMarketContext();
            var stockMarkets = Enumerable.Range(0, NumberOfRows).Select(i => new StockMarket
            {
                Name = i.ToString(),
                Family = i.ToString(),
                State = state,
                StockShare = i,
                LogTime=DateTime.Now
            }).ToList();
            await _StockMarketContext.BulkInsertAsync(stockMarkets);
        }

        public async Task BulkInsertfromlist(IEnumerable<StockMarket> Data)
        {
            var _StockMarketContext = new StockMarketContext();
            await _StockMarketContext.BulkInsertAsync(Data.ToList());
        }

        public async Task<IEnumerable<StockMarket>> LoadStockBasedOnStateByRawSQL(string State)
        {
            var _StockMarketContext = new StockMarketContext();
            var data = await _StockMarketContext.StockMarket
                                           .FromSqlRaw("SELECT [ID],[StockShare],[Name],[Family],[State],[LogTime] FROM [dbo].[StockMarket] Where [State] = {0}", State)
                                           .AsNoTracking().ToArrayAsync();
            return data;
        }


        public List<StockMarket> LoadStockBasedOnState(string State)
        {
            var _StockMarketContext = new StockMarketContext();
            var data = _StockMarketContext.StockMarket
                .Where(x=>x.State==State)
                .AsNoTracking().ToList();
            return data;
        }

        public void BulkDelete(int NumberOfRows)
        {
            throw new NotImplementedException();
        }



        public void BulkUpdate(int NumberOfRows)
        {
            throw new NotImplementedException();
        }

        //public Array LoadStockBasedOnStateCompiledQuery(string State)
        //{
        //    using (var db = new StockMarketContext())
        //    {
        //        var Data = ListOfStockBasedOnStateByCompiledQuery(db, State);
        //        return Data;
        //    }
        //}


        //public List<StockMarket> LoadStockBasedOnStateByRawSQL(string State)
        //{
        //    StockMarketContext _StockMarketContext = new StockMarketContext();
        //    var data = _StockMarketContext.StockMarket.FromSqlRaw("SELECT [ID],[StockShare],[Name],[Family],[State] FROM[dbo].[StockMarket] Where [State] = {0}", State)
        //        .AsNoTracking()
        //        .ToList();
        //    return data;
        //}

        //private static Func<StockMarketContext, string, Array> ListOfStockBasedOnStateByCompiledQuery =
        //     EF.CompileQuery((StockMarketContext db, string State) => db.StockMarket
        //    .Where(x => x.State == State)
        //    .AsNoTracking().ToArray());

    }
Вход в полноэкранный режим Выход из полноэкранного режима

Я собираюсь использовать сервис внутри program.cs для вставки, массажа и повторной вставки строк для каждого состояния. Поскольку мне нужно наблюдать за производительностью и тем, что происходит во время выполнения, я создал папку Helper, которая содержит очень простой пользовательский логгер, как показано ниже:

public static class CustomLogger
    {
        static Dictionary<DateTime, string> ListOfTimes = new();
        public static void SaveTime(string Message)
        {
            Console.WriteLine($"{DateTime.Now:HH:mm:ss.fffffff} : {Message}");
            ListOfTimes.Add(DateTime.Now,Message);
        }

        public static void SaveToFile()
        {
            var sb = new StringBuilder();
            ListOfTimes.ToList().ForEach(kvp => sb.AppendLine($"{kvp.Key:HH:mm:ss.fffffff} : {kvp.Value}"));
            var LogWriter = System.IO.File.CreateText(@"C:tempBenchMarkLogs.txt");
            LogWriter.Write(sb.ToString());
            LogWriter.Close();
        }

    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Далее представлен мой файл program.cs ! Прежде всего, напомню, что мы будем иметь дело с 10 состояниями!

string[] States = {
    "Alabama",
    "Alaska",
    "Arizona",
    "Arkansas",
    "California",
    "Colorado",
    "Connecticut",
    "Delaware",
    "Florida",
    "Georgia"
};
Вход в полноэкранный режим Выход из полноэкранного режима

Это вся моя программа.cs

using EF_6_BenchMark.EF_6_Performance.Entity;
using EF_6_BenchMark.EF_6_Performance.Helper;
using EF_6_BenchMark.EF_6_Performance.Service.Concrete;
using System.Diagnostics;



//Start Time
CustomLogger.SaveTime("Main Thread: Application has just been Started...");

Stopwatch stw = new Stopwatch();
IEnumerable<StockMarket> data;
StockMarketService Sr = new StockMarketService();
const int Row_Numbers = 150_000;


string[] States = {
    "Alabama",
    "Alaska",
    "Arizona",
    "Arkansas",
    "California",
    "Colorado",
    "Connecticut",
    "Delaware",
    "Florida",
    "Georgia"
};



stw.Start();
CustomLogger.SaveTime("Main Thread: StopWatch has just been Started...");



await Parallel.ForEachAsync(States, (state, tmp) =>
{
    CustomLogger.SaveTime($"Thread of State {state} : Iteration has just been Started");

    Parallel.Invoke(
            async () =>
             {
                 CustomLogger.SaveTime($"Thread of operation for State:{state} - Iteration has just been Started");

                 await Sr.BulkInsertAsync(Row_Numbers, state);
                 //
                 data = await Sr.LoadStockBasedOnStateByRawSQL(state);
                 Parallel.ForEach(data, x => x.StockShare += 10);
                 await Sr.BulkInsertfromlist(data);

                 CustomLogger.SaveTime($"Thread of operation for State:{state} - Iteration has just been Finished");
             }
           );

    CustomLogger.SaveTime($"Thread of State {state} : Iteration has just been Finished");
    return ValueTask.CompletedTask;
});

stw.Stop();
CustomLogger.SaveTime("Main Thread: StopWatch has just been FINISHED");

Thread.Sleep(10000);

CustomLogger.SaveTime("Main Thread: Application has just been FINISHED");
CustomLogger.SaveToFile();

Console.ReadKey();
Вход в полноэкранный режим Выход из полноэкранного режима

Вы можете видеть, что я собираюсь работать с 150 000 строк для каждого штата:
const int Row_Numbers = 150_000;

Вся моя программа.cs состоит из протоколирования и Console.Write или использования StopWatch. Наиболее важной частью является использование метода BulkInsertAsync для вставки 150,000 строк для каждого штата, а затем получение данных с помощью LoadStockBasedOnStateByRawSQL для каждого штата, массирование данных с помощью :

Parallel.ForEach(data, x => x.StockShare += 10);
Войти в полноэкранный режим Выход из полноэкранного режима

и затем, я должен BulkInsert весь список с помощью : BulkInsertfromlist и готово!

Это означает, что наиболее важной частью кода является :

await Parallel.ForEachAsync(States, (state, tmp) =>
{
    CustomLogger.SaveTime($"Thread of State {state} : Iteration has just been Started");

    Parallel.Invoke(
            async () =>
             {
                 CustomLogger.SaveTime($"Thread of operation for State:{state} - Iteration has just been Started");

                 await Sr.BulkInsertAsync(Row_Numbers, state);
                 //
                 data = await Sr.LoadStockBasedOnStateByRawSQL(state);
                 Parallel.ForEach(data, x => x.StockShare += 10);
                 await Sr.BulkInsertfromlist(data);

                 CustomLogger.SaveTime($"Thread of operation for State:{state} - Iteration has just been Finished");
             }
           );

    CustomLogger.SaveTime($"Thread of State {state} : Iteration has just been Finished");
    return ValueTask.CompletedTask;
});
Вход в полноэкранный режим Выход из полноэкранного режима

Не забудьте изменить строку подключения в методе OnConfiguring внутри StockMarketContext.cs, затем вы можете запустить код и посмотреть результат.


Я также собрал результат в текстовый файл, поэтому вы должны держать консоль открытой, чтобы иметь возможность просмотреть результат. Кроме того, вы можете увидеть, сколько времени .NET 6 и SQL Server требуется для вставки первой строки, а затем последней строки после получения и изменения значения StockShare в таблице SQL в миллисекундах.

Используя эти 3 T-SQL Statement, вы можете просто увидеть результат.

Select Top 100 * from [BenchMarkDB].[dbo].[StockMarket] Where State='Alabama'
Order By ID Desc


Select FORMAT(Count(*),'N0') 
From [BenchMarkDB].[dbo].[StockMarket]

Select DATEDIFF(MILLISECOND,Min(LogTime),MAX(LogTime)) As TimeDiff 
From [BenchMarkDB].[dbo].[StockMarket]
Войти в полноэкранный режим Выход из полноэкранного режима

Я знаю, что вы ищете репозиторий GitHub: GitHub repository

Если ссылка по какой-то причине не работает, найдите ссылку ниже:

https://github.com/AliCharper/DOTNET_6_Performance
Войти в полноэкранный режим Выход из полноэкранного режима

Если вы хотите узнать спецификацию моего оборудования, посмотрите на картинку ниже!

Я желаю, чтобы всем понравился контент, и на этом я заканчиваю.

Наслаждайтесь и продолжайте кодить на .NET!
Али Колахдоозан
Я ❤️ .NET
KUL, Малайзия

Оцените статью
devanswers.ru
Добавить комментарий