Làm cách nào để lưu trữ các phiên bản DataContext trong ứng dụng loại người tiêu dùng?


8

Chúng tôi có một ứng dụng sử dụng SDK do nhà cung cấp của chúng tôi cung cấp để tích hợp dễ dàng với chúng. SDK này kết nối với điểm cuối AMQP và chỉ cần phân phối, lưu trữ và chuyển đổi tin nhắn cho người tiêu dùng của chúng tôi. Trước đây, sự tích hợp này vượt qua HTTP với XML dưới dạng nguồn dữ liệu và tích hợp cũ có hai cách lưu trữ DataContext - theo yêu cầu web và mỗi id luồng được quản lý. (1)

Tuy nhiên, bây giờ, chúng tôi không tích hợp qua HTTP mà là AMQP minh bạch đối với chúng tôi vì SDK đang thực hiện tất cả logic kết nối và chúng tôi chỉ còn lại việc xác định người tiêu dùng của mình nên không có tùy chọn để lưu trữ DataContext "theo yêu cầu web" chỉ mỗi id chủ đề được quản lý là còn lại. Tôi đã triển khai chuỗi mẫu trách nhiệm, vì vậy khi có bản cập nhật cho chúng tôi, nó được đặt trong một hệ thống xử lý sử dụng DataContext để cập nhật cơ sở dữ liệu theo các bản cập nhật mới. Đây là cách phương thức gọi của đường ống trông như sau:

public Task Invoke(TInput entity)
{
    object currentInputArgument = entity;

    for (var i = 0; i < _pipeline.Count; ++i)
    {
        var action = _pipeline[i];
        if (action.Method.ReturnType.IsSubclassOf(typeof(Task)))
        {
            if (action.Method.ReturnType.IsConstructedGenericType)
            {
                dynamic tmp = action.DynamicInvoke(currentInputArgument);
                currentInputArgument = tmp.GetAwaiter().GetResult();
            }
            else
            {
                (action.DynamicInvoke(currentInputArgument) as Task).GetAwaiter().GetResult();
            }
        }
        else
        {
            currentInputArgument = action.DynamicInvoke(currentInputArgument);
        }
    }

    return Task.CompletedTask;
}

Vấn đề là (ít nhất là những gì tôi nghĩ là) chuỗi trách nhiệm này là chuỗi các phương thức trả về / bắt đầu các tác vụ mới, vì vậy khi một bản cập nhật cho thực thể A xuất hiện, nó được xử lý bởi chủ đề được quản lý id = 1 hãy nói và sau đó chỉ sau đó một lần nữa cùng một thực thể A chỉ được xử lý bởi id được quản lý id = 2 chẳng hạn . Điều này dẫn đến:

System.InvalidOperationException: 'Một đối tượng thực thể có thể được tham chiếu bởi nhiều phiên bản của IEntityChangeTracker.'

bởi vì DataContext từ chủ đề được quản lý id = 1 đã theo dõi thực thể A. (ít nhất đó là những gì tôi nghĩ)

Câu hỏi của tôi là làm thế nào tôi có thể lưu trữ DataContext trong trường hợp của tôi? Các bạn có cùng một vấn đề? Tôi đọc nàynày câu trả lời và từ những gì tôi hiểu bằng một tĩnh DataContext không phải là một lựa chọn cũng có. (2)

  1. Tuyên bố miễn trừ trách nhiệm: Đáng lẽ tôi phải nói rằng chúng tôi đã kế thừa ứng dụng và tôi không thể trả lời tại sao nó được triển khai như vậy.
  2. Tuyên bố miễn trừ trách nhiệm 2: Tôi có ít hoặc không có kinh nghiệm với EF.

Câu hỏi thường gặp:

  1. Phiên bản nào của EF chúng tôi đang sử dụng? 5.0
  2. Tại sao các thực thể sống lâu hơn bối cảnh? - Họ không nhưng có lẽ bạn đang hỏi tại sao các thực thể cần sống lâu hơn bối cảnh. Tôi sử dụng các kho lưu trữ sử dụng DataContext được lưu trong bộ nhớ cache để lấy các thực thể từ cơ sở dữ liệu để lưu trữ chúng trong bộ sưu tập trong bộ nhớ mà tôi sử dụng làm bộ đệm.

Đây là cách các thực thể được "trích xuất", trong đó DatabaseDataContextDataContext được lưu trong bộ nhớ cache (BLOB với toàn bộ bộ cơ sở dữ liệu bên trong)

protected IQueryable<T> Get<TProperty>(params Expression<Func<T, TProperty>>[] includes)
{
    var query = DatabaseDataContext.Set<T>().AsQueryable();

    if (includes != null && includes.Length > 0)
    {
        foreach (var item in includes)
        {
            query = query.Include(item);
        }
    }

    return query;
}

Sau đó, bất cứ khi nào ứng dụng khách hàng của tôi nhận được tin nhắn AMQP, chuỗi mẫu trách nhiệm của tôi bắt đầu kiểm tra xem tin nhắn này và dữ liệu của nó tôi đã xử lý chưa. Vì vậy, tôi có phương pháp trông như thế:

public async Task<TEntity> Handle<TEntity>(TEntity sportEvent)
            where TEntity : ISportEvent
{
    ... some unimportant business logic

    //save the sport
    if (sport.SportID > 0) // <-- this here basically checks if so called 
                           // sport is found in cache or not
                           // if its found then we update the entity in the db
                           // and update the cache after that
    {
        _sportRepository.Update(sport); /* 
                                         * because message update for the same sport can come
                                         * and since DataContext is cached by threadId like I said
                                         * and Update can be executed from different threads
                                         * this is where aforementioned exception is thrown
                                        */

    }
    else                   // if not simply insert the entity in the db and the caches
    {
        _sportRepository.Insert(sport);
    }

    _sportRepository.SaveDbChanges();

    ... updating caches logic
}

Tôi nghĩ rằng việc lấy các thực thể từ cơ sở dữ liệu bằng AsNoTracking()phương thức hoặc tách rời các thực thể mỗi khi tôi "cập nhật" hoặc "chèn" thực thể sẽ giải quyết điều này, nhưng nó đã không làm được.


Không phải là tôi chưa có câu trả lời, bạn có thể cho tôi biết bạn đang sử dụng phiên bản nào của EF không
Simon Price

cũng có thể, hãy nhìn vào này và xem nếu điều này giúp bạn ở tất cả stackoverflow.com/questions/41346635/...
Simon Giá

@Simonprice, 5.0
kuskmen

Bạn có thể mở khóa thực thể A sau khi cập nhật nó. Nhưng điều này sẽ không xử lý vấn đề tương tranh của bạn, chỉ giảm thiểu sự cố xảy ra
ilkerkaran

@ilkerkaran, nhưng nếu tôi mở khóa sau khi cập nhật / chèn không có nghĩa là tôi sẽ không thể lưu nó vào db sau này? Tôi về cơ bản đang gọi cập nhật hoặc chèn dựa trên các tiêu chí và sau đó ngay lập tức theo sau SaveChanges.
kuskmen

Câu trả lời:


2

Trong khi có một chi phí nhất định để tạo DbContext và sử dụng DI để chia sẻ một thể hiện của DbContext trong một yêu cầu web có thể lưu một số chi phí này, các thao tác CRUD đơn giản có thể tạo ra một DbContext mới cho mỗi hành động.

Nhìn vào mã mà bạn đã đăng cho đến nay, tôi có thể có một phiên bản riêng của DbContext được tạo mới trong hàm tạo Kho lưu trữ, và sau đó mới tạo một Kho lưu trữ cho mỗi phương thức.

Sau đó, phương pháp của bạn sẽ trông giống như thế này:

public async Task<TEntity> Handle<TEntity>(TEntity sportEvent)
        where TEntity : ISportEvent
{
        var sportsRepository = new SportsRepository()

        ... some unimportant business logic

        //save the sport
        if (sport.SportID > 0) 
        {
            _sportRepository.Update(sport);
        }
        else
        {
            _sportRepository.Insert(sport);
        }

        _sportRepository.SaveDbChanges();

}

public class SportsRepository
{
    private DbContext _dbContext;

    public SportsRepository()
    {
        _dbContext = new DbContext();
    }

}

Bạn cũng có thể muốn xem xét việc sử dụng các Thực thể gốc như một cách để chia sẻ DbContext với các lớp kho lưu trữ khác.


Có, thật không may, dự án datalayer được sử dụng bởi cả dịch vụ mới và ứng dụng trang web cũ của chúng tôi và nó không phải là một chủ đề thay đổi. : / Tôi đã kết thúc bằng cách sử dụng một dbcontext cho tất cả các luồng của mình và tôi cho rằng tôi sẽ nghĩ đến việc thay đổi điều này sau khi chúng tôi thực hiện đường ống đa luồng
kuskmen

0

Vì đây là về một số ứng dụng kinh doanh hiện tại, tôi sẽ tập trung vào các ý tưởng có thể giúp giải quyết vấn đề thay vì giảng về các thực tiễn tốt nhất hoặc đề xuất thay đổi kiến ​​trúc.

Tôi biết điều này là rõ ràng nhưng đôi khi viết lại thông báo lỗi giúp chúng tôi hiểu rõ hơn những gì đang xảy ra vì vậy hãy kiên nhẫn với tôi.

Thông báo lỗi cho biết các thực thể đang được sử dụng bởi nhiều bối cảnh dữ liệu cho biết rằng có nhiều phiên bản dbcontext và các thực thể được tham chiếu bởi nhiều hơn một trong các trường hợp đó.

Sau đó, câu hỏi nêu rõ bối cảnh dữ liệu trên mỗi luồng được sử dụng cho mỗi yêu cầu http và các thực thể đó được lưu trữ.

Vì vậy, có vẻ an toàn khi giả định các thực thể đọc từ ngữ cảnh db khi bộ nhớ cache bị lỗi và được trả về từ bộ đệm khi truy cập. Việc cố cập nhật các thực thể được tải từ một cá thể bối cảnh db bằng cách sử dụng một cá thể bối cảnh db thứ hai gây ra lỗi. Chúng ta có thể kết luận rằng trong trường hợp này, một thực thể chính xác giống nhau đã được sử dụng trong cả hai hoạt động và không có sự tuần tự hóa / giải tuần tự hóa nào được áp dụng để truy cập bộ đệm.

Các phiên bản DbContext tự lưu trữ thực thể thông qua cơ chế theo dõi thay đổi bên trong của chúng và lỗi này là một biện pháp bảo vệ tính toàn vẹn của nó. Vì ý tưởng là có một quy trình chạy dài xử lý các yêu cầu đồng thời thông qua nhiều bối cảnh db (một trên mỗi luồng) cộng với bộ đệm thực thể được chia sẻ, nên sẽ rất có ích về hiệu năng và trí nhớ (theo dõi thay đổi có thể sẽ tăng thời gian sử dụng bộ nhớ ) để cố gắng thay đổi db bối cảnh vòng đời thành mỗi tin nhắn hoặc làm trống trình theo dõi thay đổi của chúng sau khi mỗi tin nhắn được xử lý.

Tất nhiên, để xử lý các cập nhật thực thể, chúng cần phải được gắn vào bối cảnh db hiện tại ngay sau khi lấy nó từ bộ đệm và trước khi bất kỳ thay đổi nào được áp dụng cho chúng.


Cảm ơn những hiểu biết, tôi đồng ý với bạn, tuy nhiên vấn đề ở đây vẫn còn. Cách giải quyết hiện tại của tôi sẽ là bỏ qua "kho lưu trữ" và làm việc trực tiếp với bối cảnh dữ liệu ...
kuskmen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.