动态切换数据库(EF框架)

         文章简略:本文测试项目为Silverlight+EF+RIA Service动态切换数据库的问题


通常,Ado.net EntityFramework的数据库连接字符串ConnectionString是存在实体框架所在的类库项目中的配置文件中(.config)的,类似这样:

<connectionStrings>
     <add name="{EFName}Entities" connectionString="metadata=res://*/Model1.csdl|
     res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;
     provider connection string=&quot;data source={IP};
     initial catalog=Northwind;persist security info=True;user id=sa;
     password={PWD};multipleactiveresultsets=True;App=EntityFramework&quot;" 
     providerName="System.Data.EntityClient" />
</connectionStrings>

EntityConnection的ConnectionString和以前的Ado.net的ConnectionString是有一些的区别的。有2个新元素,解释下:

             metatata: 指定Entity Framework数据模型的路径,包括CSDL(概念架构定义语言)、SSDL(存储架构定义语言)、MSL(映射规范语言)的路径

             provider: 数据库provider,例如SQLServer的默认Ado.net:System.Data.SqlClient,也可以是其它的数据库例如Oracle、SQLite、MySql等

需要注意的是:EF框架默认寻找的数据库连接数据库字符串不一定是当期类库或者项目,严谨的说法是,解决方案下的启动项目所在的配置文件中寻找连接

这是用配置的方式指定ConnectionString,也可以用代码动态指定Entity framework的数据库连接,用SqlConnectionStringBuilder手动创建:

public partial class EFNameEntities
 {
         partial void OnContextCreated()
         {
             SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder
             ((EntityConnection)Connection).StoreConnection.ConnectionString);
             builder.IntegratedSecurity = false;
             builder.UserID = "sa";
             builder.Password = "";
             builder.InitialCatalog = "";
             ((EntityConnection)Connection).StoreConnection.ConnectionString = builder.ConnectionString;
         }
 }

这样ConnectionString就写死了,不能满足实际的开发需求,所以还是要用Web.config配置的方式。
下面我们做一个测试,我们在Web.config添加一个数据库连接方式用来测试,关键词叫“MyTestDBConn”

<connectionStrings>
     <add name="EFNameEntities" connectionString="metadata=res://*/Model1.csdl|res:
    //*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;
    provider connection string=&quot;data source=192.168.1.88;
    initial catalog=Northwind;persist security info=True;user id=sa;password={};
    multipleactiveresultsets=True;App=EntityFramework&quot;" 
    providerName="System.Data.EntityClient" />
     <add name="MyTestDBConn" connectionString="metadata=res://*/Model1.csdl|res:
    //*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;
    provider connection string=&quot;data source=192.168.1.100;
    initial catalog=Northwind_AAA;persist security info=True;
    user id=sa;password={PWD};multipleactiveresultsets=True;App=EntityFramework&quot;" 
    providerName="System.Data.EntityClient" />
</connectionStrings>
在Domain Service中改变EntityFramework的ConnectionString,这儿指定用"MyTestDBConn":
using System.Configuration;
 
[EnableClientAccess()]
public class TestDomainService : LinqToEntitiesDomainService<TestEntities>
{
  protected override TestEntities CreateObjectContext()
  {
    string connection = ConfigurationManager.ConnectionStrings["MyTestDBConn"].ConnectionString;
    return new TestEntities(connection);
  }
}

这样的确可以在DomainService切换Entity Framework数据库连接ConnectionString,但我如果有好多DomainService怎么办,岂不是每一个都要这样来一下子?第二 个问题是,如何把"MyTestDBConn"作为一个参数传进来?
先解决第一个问题,我们写一个DomainService的基类,继承原来的基类:LinqToEntitiesDomainService,然后让每一个DomainService继承这个新的基类即可

using System;
 using System.Data.Objects;
 using System.ServiceModel.DomainServices.EntityFramework;
 using System.ServiceModel.DomainServices.Server;
 using System.Configuration;
 
 public abstract class DomainServiceBase<TContext> : LinqToEntitiesDomainService<TContext>
       where TContext : ObjectContext, new()
     {
         protected override TContext CreateObjectContext()
         {
             string connection = ConfigurationManager.ConnectionStrings["MyTestDBConn"].ConnectionString;
 
             Type contextType = typeof(TContext);
             object objContext = Activator.CreateInstance(contextType, connection);
 
             return objContext as TContext;
         }
 
         protected override void OnError(DomainServiceErrorInfo errorInfo)
         {
             // Deal with errors
         }
 }
 //接着,让你的DomainService继承这个基类,不要继承LinqToEntitiesDomainService了
 EnableClientAccess()]
 public class TestDomainService : DomainServiceBase<TestEntities>
 {
             //...
 }

第二个问题是如何把"MyTestDBConn"这个ConnectionString关键词作为参数传进来?这个问题有点难。上面说的都是在 服务器端指定连接到哪个数据库服务器,如何实现在客户端用户登录的时候,有个下拉框选择数据库服务器,然后在Silverlight客户端把这个参数传递 到服务器端DomainService并连接到指定的数据库服务器?
如果用上面的思路,就是要在DomainService的构造函数上动手,加一个参数string databaseKey,但是这个有点罗嗦。为什么?因为DomainService不是一般的class,它是WCF Service,在DomainService里面的每一个方法就是一个定义了request和response的contract的WCF (Web Service),只不过WCF Ria Services隐藏了这些调用WCF的复杂性,让你感觉在客户端就可以简单的调用DomainService。所以,如果你要给 DomainService的构造函数传一个参数,就要研究一下如何给WCF Service的构造函数传递一个参数的方法,可以用IInstanceProvider和ServiceBehavior来实现,代码见这篇文章。
这里我用另外一个Session的方法来实现更简单,每个客户端session不同,所以在客户端用户登录的时候,有个下拉框选择数据库服务 器,然后在Silverlight客户端把这个参数传递到服务器端DomainService并让Entity framework连接到指定的数据库服务器是可以用Session来实现的。具体方法是在DomainService里加一个SetDatabase函 数设置数据库,客户端把参数传给服务端,并保存到Session。然后重载CreateObjectContext ()函数并根据Session的值连接到指定的数据库。最后在SetDatabase这个函数的Callback时读取真正的数据。代码:
在DomainService里面加个SetDatabase()方法:

using System.Web;
 
 [EnableClientAccess()]
 public class TestDomainService : LinqToEntitiesDomainService<TestEntities>
 {
    public void SetDatabase(string Database)
    {
            HttpContext.Current.Session["UserDatabase"] = Database;
    }
 }
 
在DomainService里面重载CreateObjectContext()方法:
using System.Web;

[EnableClientAccess()]
public class TestDomainService : LinqToEntitiesDomainService<TestEntities>
{
   protected override TestEntities CreateObjectContext()
   {
        if (HttpContext.Current.Session["UserDatabase"] != null)
        {
                string connection = ConfigurationManager.ConnectionStrings[HttpContext.Current.Session["UserDatabase"].ToString()].ConnectionString;
                return new TestEntities(connection);
        }
        else
             return base.CreateObjectContext();
   }
}
 
前台调用xaml.cs:
public partial class MainPage : UserControl
{
        private DomainService1 s = new DomainService1();

        public MainPage()
        {
            InitializeComponent();

            //使用MyTestDBConn作为ConnectionString,加载数据到DataGrid
            LoadData("MyTestDBConn"); 
        }

         private void button1_Click(object sender, RoutedEventArgs e)
         {
             //使用Web.config中另外一个ConnectionString,加载数据到DataGrid
             LoadData("YourEntities");
         }
 
         //EF连接指定的ConnectionString
         //Param: database - web.config中ConnectionString的Name关键词
         private void LoadData(string database)
         {
             this.busyIndicator1.IsBusy = true;
             //****SetDatabase callback的时候读取数据*********
             s.SetDatabase(database, (InvokeOperation) =>
             {
                 //GetCompanies改为你自己的方法
                 s.Load(s.GetCompaniesQuery(), (t) =>
                 {
                     dataGrid1.ItemsSource = t.Entities;
                     this.busyIndicator1.IsBusy = false;
                 }, true);
             }, null);
         }
  }

可以另外按照上面DomainServiceBase的方法写一个基类避免每个DomainService重写一遍。
结束
不知道大家有没有更好的方法实现客户端传参到服务端动态切换数据库呢?另:由于Entity framework的模型是由链接到本地数据库映射生成的,所以不提供源码下载了,按照本文的步骤一步步做就能运行。如果对 Silverlight+Wcf Ria service+EF还不明白的,可以看博客园其他的博文。

参考文章:http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/8f18aed8-8e34-48ea-b8be-6c29ac3b4f41

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。