Подтвердить что ты не робот

Entity Framework и многопоточность

У нас возникли проблемы с разработкой нашего многопоточного приложения Entity Framework и хотелось бы получить некоторые рекомендации. Мы создаем объекты в разных потоках, объекты добавляются в коллекции, которые затем привязываются к различным элементам управления wpf. Класс ObjectContext не является потокобезопасным, поэтому для управления этим мы имеем по существу 2 решения:

Решение 1 имеет один контекст и тщательно использует блокировку, чтобы гарантировать, что 2 потока не обращаются к нему в одно и то же время. Это было бы относительно просто реализовать, но потребовало бы, чтобы контекст был живым на протяжении всего приложения. Это плохая идея, чтобы открыть один экземпляр контекста таким образом?

Решение 2 - создавать объекты контекста по требованию, а затем немедленно отделять объекты, а затем удерживать их в наших собственных коллекциях, а затем повторно присоединять их для любого обновления. У этого есть некоторые серьезные проблемы для использования, хотя, как при отсоединении объектов они теряют ссылки на объекты свойств навигации. Также есть проблема, что 2 потока могут пытаться получить доступ к одному объекту, и оба пытаются связать() с контекстом. Кроме того, нам нужно будет предоставлять новый контекст каждый раз, когда мы хотим получить доступ к свойствам навигации сущностей.

Q: Являются ли какие-либо из двух решений действительными, если не так, как вы порекомендовали нам это?

4b9b3361

Ответ 1

Во-первых, я предполагаю, что вы прочитали эту статью в MSDN.

Решение №1 почти наверняка является самым безопасным с точки зрения потоков, поскольку вы гарантируете, что только один поток взаимодействует с контекстом при любом времени. Нет ничего неотъемлемо неправильного в том, чтобы поддерживать контекст вокруг - он не поддерживает открытия базы данных за кулисами, поэтому это просто накладные расходы памяти. Разумеется, это может привести к проблемам с производительностью, если вы столкнулись с этим потоком, и все приложение написано с допущениями с одним db-потоком.

Решение №2 кажется мне неработоспособным - у вас появятся тонкие ошибки во всем приложении, где люди забудут повторно присоединить (или отсоединить) объекты.

Одно из решений - не использовать объекты сущности на уровне пользовательского интерфейса приложения. Я бы порекомендовал это в любом случае - скорее всего, структура/компоновка объектов сущности не является оптимальной для того, как вы хотите отображать вещи в своем пользовательском интерфейсе (это является причиной для семейства MVC). Ваш DAL должен иметь методы, специфичные для бизнес-логики (например, UpdateCustomer), и он должен сам решать, создавать ли новый Контекст или использовать сохраненный. Вы можете начать с единого хранимого контекста, а затем, если у вас возникнут проблемы с узким местом, у вас есть ограниченная площадь поверхности, где вам нужно внести изменения.

Недостатком является то, что вам нужно написать намного больше кода - у вас будут ваши объекты EF, но у вас также есть бизнес-сущности, которые имеют повторяющиеся свойства и потенциально различную мощность многих объектов EF. Чтобы уменьшить это, вы можете использовать фреймворки, такие как AutoMapper, чтобы упростить копирование свойств из объектов EF в бизнес-объекты и обратно.

Ответ 2

У меня есть дюжина потоков stackoverflow, касающихся EF и многопоточности. У всех из них есть ответы, которые подробно объясняют проблему, но на самом деле не показывают, как ее исправить.

EF не является потокобезопасным, мы все это знаем. Но, по моему опыту, единственный риск - это контекстные творения/манипуляции. На самом деле существует очень простое исправление, в котором вы можете хранить свою ленивую загрузку.

Допустим, у вас есть приложение WPF и веб-сайт MVC. где приложение WPF использует многопоточность. Вы просто располагаете контекст db в многопоточности и сохраняете его, когда нет. Пример веб-сайта MVC, контекст будет автоматически размещен после представления представления.

На уровне приложения WPF вы используете это:

ProductBLL productBLL = new ProductBLL(true);

В прикладном уровне MVC вы используете это:

ProductBLL productBLL = new ProductBLL();

Как выглядит ваш бизнес-логический уровень продукта:

public class ProductBLL : IProductBLL
{
    private ProductDAO productDAO; //Your DB layer

    public ProductBLL(): this(false)
    {

    }
    public ProductBLL(bool multiThreaded)
    {
        productDAO = new ProductDAO(multiThreaded);
    }
    public IEnumerable<Product> GetAll()
    {
        return productDAO.GetAll();
    }
    public Product GetById(int id)
    {
        return productDAO.GetById(id);
    }
    public Product Create(Product entity)
    {
        return productDAO.Create(entity);
    }
    //etc...
}

Как выглядит ваш логический уровень вашей базы данных:

public class ProductDAO : IProductDAO
{
    private YOURDBCONTEXT db = new YOURDBCONTEXT ();
    private bool _MultiThreaded = false;

    public ProductDAO(bool multiThreaded)
    {
        _MultiThreaded = multiThreaded;
    }
    public IEnumerable<Product> GetAll()
    {
        if (_MultiThreaded)
        {
            using (YOURDBCONTEXT  db = new YOURDBCONTEXT ())
            {
                return db.Product.ToList(); //USE .Include() For extra stuff
            }
        }
        else
        {
            return db.Product.ToList();
        }                  
    }

    public Product GetById(int id)
    {
        if (_MultiThreaded)
        {
            using (YOURDBCONTEXT  db = new YOURDBCONTEXT ())
            {
                return db.Product.SingleOrDefault(x => x.ID == id); //USE .Include() For extra stuff
            }
        }
        else
        {
            return db.Product.SingleOrDefault(x => x.ID == id);
        }          
    }

    public Product Create(Product entity)
    {
        if (_MultiThreaded)
        {
            using (YOURDBCONTEXT  db = new YOURDBCONTEXT ())
            {
                db.Product.Add(entity);
                db.SaveChanges();
                return entity;
            }
        }
        else
        {
            db.Product.Add(entity);
            db.SaveChanges();
            return entity;
        }
    }

    //etc...
}

Ответ 3

Вы не хотите долго жить. В идеале они должны быть для жизни операции запроса/данных.

При работе с подобными проблемами я завершил реализацию репозитория, который кэшировал объекты PK для данного типа и разрешил "LoadFromDetached", который мог бы найти сущность в базе данных, и "скопировать" все скалярные свойства, кроме PK over к вновь прикрепленному объекту.

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

Ответ 4

Это было когда-то с вопроса, но я недавно столкнулся с аналогичной проблемой и в итоге сделал ниже, что помогло нам соответствовать критериям эффективности.

В основном вы разбиваете свой список на куски и обрабатываете их в спящих потоках многопоточным способом. Каждый новый поток также инициирует свой собственный uow, который требует привязки ваших объектов.

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

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

    private void PersistProductChangesInParallel(List<Product> products, 
        Action<Product, string> productOperationFunc, 
        string updatedBy)
    {
        var productsInChunks = products.ChunkBy(20);

        Parallel.ForEach(
            productsInChunks,
            new ParallelOptions { MaxDegreeOfParallelism = 20 },
            productsChunk =>
                {
                    try
                    {
                        using (var transactionScope = new TransactionScope(
                                TransactionScopeOption.Required,
                                new TransactionOptions { IsolationLevel = IsolationLevel.Snapshot }))
                        {
                            var dbContext = dbContextFactory.CreatedbContext();
                            foreach (var Product in productsChunk)
                            {
                                dbContext.products.Attach(Product);
                                productOperationFunc(Product, updatedBy);
                            }
                            dbContext.SaveChanges();
                            transactionScope.Complete();
                        }
                    }
                    catch (Exception e)
                    {
                        Log.Error(e);
                        throw new ApplicationException("Some products might not be updated", e);
                    }
                });
    }

Ответ 5

У меня только что был проект, где пытались использовать EF с многопоточными причинами ошибок.

Я пробовал

using (var context = new entFLP(entity_connection))            
{
    context.Product.Add(entity);
    context.SaveChanges();
    return entity;
}

но он просто изменил тип ошибки от ошибки datareader до множественной ошибки потока.

Простым решением было использование хранимых процедур с импортом функций EF

using (var context = new entFLP(entity_connection))
{
    context.fi_ProductAdd(params etc);
}

Ключ должен перейти к источнику данных и избежать модели данных.