2

我正在使用 S#harp 架构,有没有人找到使用它访问 SQL Azure 联合的方法?

我知道以下命令必须在事务之外执行,因为 SQL Azure 不允许在事务中“使用联合”语句。

use Federation CustomerFederation (CustomerID=2) with reset, filtering=on 
GO
<some sql statement...>
GO

这里还有一篇文章展示了一个创建自定义 NHibernate Session 类的示例,但是如何使用 S#arp 架构来完成/扩展呢?

我还知道 SQL Azure 联合还有其他分片选项,例如 NHibernate.Shards 或多租户 S#arp 架构扩展,但请继续回答这个问题,而不是提供其他选项。

我知道我不是唯一使用 S#arp Architecture 和 SQL Azure Federations 的人,而且 Google 没有提供太多,所以如果其他人找到了解决方案,请分享。

4

1 回答 1

2

由于还没有人回复我的帖子,所以经过几天的研究,我正在回复它。我能够与具有 1 个接口和 3 个类的 S#harp 集成(我希望它们是一个开箱即用的解决方案?)。

下面提供的代码可以复制并粘贴到任何应用程序中,它应该可以正常工作。唯一的例外是 FederationSessionHelper 类。这是特定于每个应用程序的,因为您获得的信息可能会发生变化。我的 web.config 中有一个应用程序设置部分,其中包含联合名称等。此外,当用户进行身份验证时,我会解析他们来自的根 url,然后查询联合根以找出它们是什么租户(我有一个我创建的自定义租户表)。然后,我将租户 ID 放在密钥“FederationKeyValue_Key”下的会话中,然后将在 FederationSession 类中使用它来构建 Use Federation 语句。

/// <summary>
/// Interface used to retrieve app specific info about your federation.
/// </summary>
public interface IFederationSessionHelper
{
    string ConnectionString { get; }
    string FederationName { get; }
    string DistributionName { get; }
    string FederationKeyValue { get; }
}

/// <summary>
/// This is were you would get things specific for your application. I have 3 items in the web.config file and 1 stored in session. You could easily change this to get them all from the repository or wherever meets the needs of your application.
/// </summary>
public class FederationSessionHelper : IFederationSessionHelper
{
    private const string ConnectionStringKey = "ConnectionString_Key";
    private const string FederationNameKey = "FederationName_Key";
    private const string DistributionNameKey = "DistributionName_Key";
    private const string FederationKeyValueKey = "FederationKeyValue_Key";

    public string ConnectionString { get { return ConfigurationManager.ConnectionStrings[ConnectionStringKey].ConnectionString; } }
    public string FederationName { get { return ConfigurationManager.AppSettings[FederationNameKey]; } }
    public string DistributionName { get { return ConfigurationManager.AppSettings[DistributionNameKey]; } }

    //When user authenitcates, retrieve key value and store in session. This will allow to retrieve here.
    public string FederationKeyValue { get { return Session[FederationKeyValueKey]; } }       
}

/// <summary>
/// This is were the magic begins and where the integration with S#arp occurs. It manually creates a Sql Connections and adds it the S#arps storage.  It then runs the Use Federation command and leaves the connection open. So now when you use an NhibernateSession.Current it will work with Sql Azure Federation.
/// </summary>
public class FederationSession : IDisposable
{
    private SqlConnection _sqlConnection;

    public void Init(string factoryKey,
                       string federationName,
                       string distributionName,
                       string federationKeyValue,
                       bool doesFilter,
                       string connectionString)
    {
        var sql = string.Format("USE FEDERATION {0}({1} = '{2}') WITH RESET, FILTERING = {3};", federationName, distributionName, federationKeyValue, (doesFilter) ? "ON" : "OFF");
        _sqlConnection = new SqlConnection(connectionString);

        _sqlConnection.Open();
        var session = NHibernateSession.GetSessionFactoryFor(factoryKey).OpenSession(_sqlConnection);
        NHibernateSession.Storage.SetSessionForKey(factoryKey, session);

        var query = NHibernateSession.Current.CreateSQLQuery(sql);
        query.UniqueResult();
    }

    public void Dispose()
    {
        if (_sqlConnection != null && _sqlConnection.State != ConnectionState.Closed)
            _sqlConnection.Close();
    }
}

/// <summary>
/// This was just icing on the cake.  It inherits from S#arps TransactionAttribute and calls the FederationSession helper to open a connection.  That way all you need to do in decorate your controller with the newly created [FederationTransaction] attribute and thats it.
/// </summary>
public class FederationTransactionAttribute : TransactionAttribute
{
    private readonly string _factoryKey = string.Empty;
    private bool _doesFilter = true;

    /// <summary>
    ///     When used, assumes the <see cref = "factoryKey" /> to be NHibernateSession.DefaultFactoryKey
    /// </summary>
    public FederationTransactionAttribute()
    { }

    /// <summary>
    ///     Overrides the default <see cref = "factoryKey" /> with a specific factory key
    /// </summary>
    public FederationTransactionAttribute(string factoryKey = "", bool doesFilter = true)
        : base(factoryKey)
    {
        _factoryKey = factoryKey;
        _doesFilter = doesFilter;
    }        

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var federationSessionHelper = ServiceLocator.Current.GetInstance<IFederationSessionHelper>();
        var factoryKey = GetEffectiveFactoryKey();
        new FederationSession().Init(factoryKey,
                                        federationSessionHelper.FederationName,
                                        federationSessionHelper.DistributionName,
                                        federationSessionHelper.FederationKeyValue,
                                        _doesFilter,
                                        federationSessionHelper.ConnectionString);

        NHibernateSession.CurrentFor(factoryKey).BeginTransaction();
    }

    private string GetEffectiveFactoryKey()
    {
        return String.IsNullOrEmpty(_factoryKey) ? SessionFactoryKeyHelper.GetKey() : _factoryKey;
    }        
}

现在我可以用新创建的 [FederationTransaction] 替换 S#arp 的 [Transaction] 属性,如下所示:

[HttpGet] 
[FederationTransaction]
public ActionResult Index()
{
   var viewModel = NHibernateSession.Current.QueryOver<SomeDemoModel>().List()
   return View(viewModel);            
}

控制器中的任何代码都不需要知道它使用 Sql Azure 联合。这一切都应该正常工作。

有什么想法吗?有人找到更好的解决方案吗?请分享。

于 2012-07-05T20:04:21.710 回答