EF Core - obsługa współbieżności

Odpowiedz Nowy wątek
2018-06-07 18:19
0

Mam projekt, w którym w modelu dodałem:

        [Timestamp]
        public byte[] RowVersion { get; set; }

W bazie danych utworzona jest kolumna RowVersion, która przy każdej aktualizacji rekordu ulega zmianie.
Jeżeli teraz próbuję w dwóch odrębnych sesjach modyfikować ten sam rekord to zawsze zachowywana jest jego ostatnia wartość. Nigdy nie jest rzucany DBConcurrencyException bez względu nawet na to czy ten model zawiera RowVersion w zwracanym modelu.

Czy w EF Core obsługę współbieżności trzeba w jakikolwiek sposób konfigurować, żeby działała?

,ale czy takie zachowanie nie jest prawidłowe ? Jeżeli dwie osoby równocześnie modyfikują ten sam rekord to zachowany zostaje ostatni zapis - cw 2018-06-07 18:28
Jak przeczytałem na czym polega obsługa współbieżności to wydawało mi się, że jeżeli RowVersion zmieni się pomiędzy pobraniem i zapisaniem rekordu w drugiej sesji to aplikacja powinna rzucić DBConcurrencyException. - baroo 2018-06-07 18:31

Pozostało 580 znaków

2018-06-07 21:03
0

Pokaż jak zapisujesz

Pozostało 580 znaków

2018-06-07 21:33
0

Zachowuje się prawidłowo. Relacyjne Bazy Danych nie obsługują asychronicznego zapisu jak np. MongoDb. ORM powinien rzucić wyjątek ze względu na nie atomowość operacji. Czyli np. jeśli przy dużym latency dwa wątki z tą samą sesją będą chciały wykonać na raz tranzakcję zostanie rzucony wyjątek. Dlatego stosuje się TPL przy zapisie.

edytowany 1x, ostatnio: Enter Name, 2018-06-07 21:35
O czym Ty w ogóle piszesz? Co ma asynchroniczny zapis przez bazę do optymistic concurrency, które istnieje w ORMach od lat? - somekind 2018-06-07 23:11

Pozostało 580 znaków

2018-06-07 23:13
0
baroo napisał(a):

Czy w EF Core obsługę współbieżności trzeba w jakikolwiek sposób konfigurować, żeby działała?

Oczywiście. Musisz ORMowi powiedzieć, że dane property jest wersją rekordu.

https://docs.microsoft.com/en[...]concurrency-tokens-work-in-ef


"HUMAN BEINGS MAKE LIFE SO INTERESTING. DO YOU KNOW, THAT IN A UNIVERSE SO FULL OF WONDERS, THEY HAVE MANAGED TO INVENT BOREDOM."

Pozostało 580 znaków

2018-06-08 08:47
0

Jedynie adnotacja timestamp i propertis jest potrzebne do optymistycznego rozwiązywania konfliktów współbieżnych w EF core. Więc albo problem jest w Twoim kodzie, albo rozumowaniu jak to ma działać, tutaj przykład rzucający wyjątkiem:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;

namespace ConsoleApp10
{

    public class BloggingContext :DbContext
    {
        public DbSet<Blog> Blogs { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFGetStarted.ConsoleApp.NewDb;Trusted_Connection=True;");
        }
    }

    public class Blog
    {
        public int BlogId { get; set; }
        public string Url { get; set; }

        [Timestamp]
        public byte[] Timestamp { get; set; }
    }

    class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            using (var db = new BloggingContext())
            {
                db.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/adonet" });
                db.SaveChanges();               
            }

            Blog blog = null;

            // zaczynamy edycję bloga
            using (var db = new BloggingContext())
            {
                blog = await db.Blogs.FirstOrDefaultAsync();                
            }

            /// O nie ktoś nam za plecami majstruje z naszą encją! 
            using (var db = new BloggingContext())
            {
                var blog2 = await db.Blogs.FirstOrDefaultAsync();
                blog2.Url = DateTime.UtcNow.ToString();
                db.SaveChanges();
            }

            // kończymy edycję bloga
            using (var db = new BloggingContext())
            {
                db.Blogs.Attach(blog);
                blog.Url += "+";

                try
                {
                    db.SaveChanges();
                } catch (Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException ex)
                {
                    Console.WriteLine("DBConcurrencyException");
                }
            }
        }
    }
}

It's easy to hate code you didn't write, without an understanding of the context in which it was written.

Pozostało 580 znaków

2018-06-08 09:09
Wesoły Terrorysta
0

Ok, dzięki. Chyba już wiem gdzie robię błąd w swoim kodzie.

Pozostało 580 znaków

2018-06-08 19:15
0

Czy taki kod operacji update zapewni poprawne śledzenie danej encji?

        // PUT api/OneTimeServiceItems/5
        [HttpPut("{id}")]
        public async Task<IActionResult> Put(int id, [FromBody]OneTimeServiceItemDto updatedOneTimeServiceItemDto)
        {
            if (!ModelState.IsValid || updatedOneTimeServiceItemDto.Id != id)
                return BadRequest();

            var oneTimeServiceItems = await _context.ServiceItems.OfType<OneTimeServiceItem>().Where(g => g.Id == id).ToListAsync();

            if (!oneTimeServiceItems.Any())
                return NotFound();

            var updatedOneTimeServiceItem = oneTimeServiceItems.First();
            Mapper.Map(updatedOneTimeServiceItemDto, updatedOneTimeServiceItem);

            try
            {
                _context.ServiceItems.Update(updatedOneTimeServiceItem);
                await _context.SaveChangesAsync();
                _cache.Remove(IMemoryCacheKeys.customersCacheKey);
            }
            catch (Exception exception)
            {
                BadRequest(exception);
            }

            return new NoContentResult();
        }

Pozostało 580 znaków

2018-06-08 23:01
0

O czym Ty w ogóle piszesz? Co ma asynchroniczny zapis przez bazę do optymistic concurrency, które istnieje w ORMach od lat?

Wydawało mi się, że autor bije do tego jak się zachowuje ORM podczas współbieżności a nie jak działa optimistic lock.

Poza tym EF nie powinno automatycznie robić Dirty Cheking?
Typu :

Set Name WHERE Id=1 AND Name="name"

Ja tam po prostu robie Flush przed Commitem i umnie to po prostu działa. ;)

Pozostało 580 znaków

2018-06-09 11:35
0

Jak sobie debuguję tą akcje kontrolera to teoretycznie RowVersion jest brane pod uwagę:

UPDATE [ServiceItems] SET [CreatedAt] = @p0, [CreatedById] = @p1, [GrossValueAdded] = @p2, [IsArchived] = @p3, [IsBlocked] = @p4, [IsManual] = @p5, [IsSubNamePrinted] = @p6, [IsSuspended] = @p7, [IsValueVariable] = @p8, [Name] = @p9, [NetValue] = @p10, [Notes] = @p11, [Quantity] = @p12, [RemoteSystemServiceCode] = @p13, [ServiceCategoryType] = @p14, [ServiceItemCustomerSpecificTag] = @p15, [SpecificLocation] = @p16, [SubName] = @p17, [UpdatedAt] = @p18, [UpdatedById] = @p19, [VATRate] = @p20, [InstallationDate] = @p21, [IsInvoiced] = @p22, [ServiceItemsSetId] = @p23
WHERE [Id] = @p24 AND [RowVersion] = @p25;
SELECT [RowVersion]
FROM [ServiceItems]
WHERE @@ROWCOUNT = 1 AND [Id] = @p24;

ale nijak mi to wyjątku nie rzuca.

edytowany 1x, ostatnio: baroo, 2018-06-09 11:35

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0