PLINQO 和事务问题

发布于 2024-11-05 11:58:54 字数 895 浏览 3 评论 0原文

我刚刚开始使用 PLINQO 在我的 n 层分布式系统中实现存储库层和数据层。

数据层由以下几层组成:存储库、数据提供者、数据服务

存储库层负责从数据库获取数据并在数据库中设置数据。

数据提供者层是存储库和服务层之间的门户。

数据服务层保存所有业务逻辑和规则。

使用事务时,我在此架构中遇到问题:我经常收到 InvalidOperationException 错误消息:“无法附加已存在的实体。”

究其原因,是因为repository层的设计。所有存储库方法中的常见步骤是:

MyDataContext context;
if(InTransaction == true)
    context = GetExistingContext();
else
    context = GetNewContext();

//Do some database operation for example:
var user = context.User.GetByKey("username");

//Detach from context
user.Detach();

if(InTransaction == false)
    context.Dispose();

InTransaction 为 true 并且我调用对同一实体进行操作的两个方法时,会发生 InvalidOperationException。由于 InTransaction 为 true,因此这两个方法使用相同的数据上下文。第一个方法附加实体并最终分离,第二个方法也尝试附加,然后发生异常。

我做错了什么以及如何防止这种情况发生?

谢谢,

科比

I just started working with PLINQO to implement the repository layer and the data tier in my n-tier distributed system.

The data tier consists of the following layers: repository, data provider, data service

The repository layer responsible for getting data from database and setting data in the database.

The data provider layer is the gate between the repository and the service layer

The data service layer holds all the business logic and rules.

I am having a problem in this architecture when using transactions: I am getting InvalidOperationException with error message: "Cannot attach an entity that already exists." very often.

The reason is because the desing of the repository layer. The common steps in all the repository methods are:

MyDataContext context;
if(InTransaction == true)
    context = GetExistingContext();
else
    context = GetNewContext();

//Do some database operation for example:
var user = context.User.GetByKey("username");

//Detach from context
user.Detach();

if(InTransaction == false)
    context.Dispose();

The InvalidOperationException happens when InTransaction is true and I am calling to two methods that operates on the same entity. Becuase the InTransaction is true the two methods uses the same datacontext. the first method attach the entity and in the end detach and the second method also tries to attach and then the exception occurs.

What am I doing wrong and how can I prevent this from happen ?

Thanks,

Koby

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

瞄了个咪的 2024-11-12 11:58:54

我找到了解决问题的方法。

当我尝试附加已附加到数据上下文的实体时,发生了异常,这是因为我的 Update() 方法中没有指示该实体是否已附加到该方法使用的数据上下文。

当我删除附件时,我没有得到异常,但实体有时没有更新(如果数据上下文是新的)。

所以我想如何在这两种冲突的情况之间架起桥梁,解决方案是 TransactionScope。

我创建了 TransactionScope 类,它允许方法加入和离开范围。

Join() 方法创建新的数据上下文并初始化使用计数器,因此对此方法的任何连续调用都会使该计数器加一。

Leave() 方法会递减使用计数器,当它达到零时,数据上下文将被释放。

还有返回数据上下文的属性(而不是每个方法都尝试获取自己的上下文)。

我将需要数据上下文的方法从问题中描述的方法更改为:

_myTranscationScope.Join();

try
{
  var context = _myTransactionScope.Context;

  //Do some database operation for example:
  context.User.GetByKey("username");

}
finally
{
    _myTranscationScope.Leave();
}

此外,我重写了数据上下文的 Dispose 方法,并将实体的分离移到那里,而不是在每个方法中执行此操作。

我需要确保的是我有正确的事务范围,并确保每次调用 join 也调用离开(即使在异常情况下)

这现在使我的代码可以完美地适应所有场景(包括单个数据库操作、复杂事务) ,使用序列化对象)。

这是 TransactionScope 类的代码(我删除了取决于我正在处理的项目的行代码,但它仍然是完全有效的代码):

using System;
using CSG.Games.Data.SqlRepository.Model;

namespace CSG.Games.Data.SqlRepository
{
    /// <summary>
    /// Defines a transaction scope
    /// </summary>
    public class TransactionScope :IDisposable
    {
        private int _contextUsageCounter; // the number of usages in the context
        private readonly object _contextLocker; // to make access to _context thread safe

        private bool _disposed; // Indicates if the object is disposed

        internal TransactionScope()
        {
            Context = null;
            _contextLocker = new object();
            _contextUsageCounter = 0;
            _disposed = false;
        }

        internal MainDataContext Context { get; private set; }

        internal void Join()
        {
            // block the access to the context
            lock (_contextLocker)
            {
                CheckDisposed();

                // Increment the context usage counter
                _contextUsageCounter++;

                // If there is no context, create new
                if (Context == null)
                    Context = new MainDataContext();
            }
        }

        internal void Leave()
        {
            // block the access to the context
            lock (_contextLocker)
            {
                CheckDisposed();
                // If no one using the context, leave...
                if(_contextUsageCounter == 0)
                     return;

                // Decrement the context usage counter
                _contextUsageCounter--;

                // If the context is in use, leave...
                if (_contextUsageCounter > 0)
                    return;

                // If the context can be disposed, dispose it
                if (Context.Transaction != null)
                    Context.Dispose();

                // Reset the context of this scope becuase the transaction scope ended
                Context = null;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            lock (_contextLocker)
            {
                if (_disposed) return;

                if (disposing)
                {
                    if (Context != null && Context.Transaction != null)
                        Context.Dispose();

                    _disposed = true;
                }
            }
        }        

        private void CheckDisposed()
        {
            if (_disposed)
                throw new ObjectDisposedException("The TransactionScope is disposed");
        }

    }
 }

I found a solution to my problem.

The exception occurred when I tried to attach an entity that is already attached to a datacontext and this happened because I didn't had an indication in my Update() method if the entity is already attached to the data context that the method uses.

When I removed the attach I didn't got the exception but the entity sometimes was not udpated (in cases the datacontext is new).

So I thought how to bridge between those 2 conflicting situation and the solution is TransactionScope.

I created TransactionScope class that allows methods to Join and Leave the scope.

The Join() method creates new datacontext and initialize usage counter so any consecutive call to this method increase this counter by one.

The Leave() method decrements the usage counter and when it reaches to zero the datacontext is disposed.

There is also property that returns the data context (instead of each method to try and get its own context).

I changed the methods that need data context from what described in my question to:

_myTranscationScope.Join();

try
{
  var context = _myTransactionScope.Context;

  //Do some database operation for example:
  context.User.GetByKey("username");

}
finally
{
    _myTranscationScope.Leave();
}

In addition I overrided the Dispose method of the datacontext and moved the detach of the entities to there instead of doing this in each method.

All I need to make sure is that I have the correct transaction scope and make sure each call to join has also call to leave (even in exception)

This now make my code works perfect with all the scenarios (including single database operations, complex transactions, working with serialized objects).

Here is the code of the TransactionScope class (I removed line codes that depends on the project I am working on, but still it is fully working code):

using System;
using CSG.Games.Data.SqlRepository.Model;

namespace CSG.Games.Data.SqlRepository
{
    /// <summary>
    /// Defines a transaction scope
    /// </summary>
    public class TransactionScope :IDisposable
    {
        private int _contextUsageCounter; // the number of usages in the context
        private readonly object _contextLocker; // to make access to _context thread safe

        private bool _disposed; // Indicates if the object is disposed

        internal TransactionScope()
        {
            Context = null;
            _contextLocker = new object();
            _contextUsageCounter = 0;
            _disposed = false;
        }

        internal MainDataContext Context { get; private set; }

        internal void Join()
        {
            // block the access to the context
            lock (_contextLocker)
            {
                CheckDisposed();

                // Increment the context usage counter
                _contextUsageCounter++;

                // If there is no context, create new
                if (Context == null)
                    Context = new MainDataContext();
            }
        }

        internal void Leave()
        {
            // block the access to the context
            lock (_contextLocker)
            {
                CheckDisposed();
                // If no one using the context, leave...
                if(_contextUsageCounter == 0)
                     return;

                // Decrement the context usage counter
                _contextUsageCounter--;

                // If the context is in use, leave...
                if (_contextUsageCounter > 0)
                    return;

                // If the context can be disposed, dispose it
                if (Context.Transaction != null)
                    Context.Dispose();

                // Reset the context of this scope becuase the transaction scope ended
                Context = null;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            lock (_contextLocker)
            {
                if (_disposed) return;

                if (disposing)
                {
                    if (Context != null && Context.Transaction != null)
                        Context.Dispose();

                    _disposed = true;
                }
            }
        }        

        private void CheckDisposed()
        {
            if (_disposed)
                throw new ObjectDisposedException("The TransactionScope is disposed");
        }

    }
 }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文