Entity Framework thay đổi kết nối trong thời gian chạy


80

Tôi có một dự án API web tham chiếu đến mô hình của tôi và các hội đồng DAL. Người dùng được hiển thị với một màn hình đăng nhập, nơi anh ta có thể chọn các cơ sở dữ liệu khác nhau.

Tôi xây dựng chuỗi kết nối như sau:

    public void Connect(Database database)
    {
        //Build an SQL connection string
        SqlConnectionStringBuilder sqlString = new SqlConnectionStringBuilder()
        {
            DataSource = database.Server,
            InitialCatalog = database.Catalog,
            UserID = database.Username,
            Password = database.Password,
        };

        //Build an entity framework connection string
        EntityConnectionStringBuilder entityString = new EntityConnectionStringBuilder()
        {
            Provider = database.Provider,
            Metadata = Settings.Default.Metadata,
            ProviderConnectionString = sqlString.ToString()
        };
    }

Trước hết, làm cách nào để thực sự thay đổi kết nối của ngữ cảnh dữ liệu?

Và thứ hai, vì đây là một dự án API web, chuỗi kết nối (được đặt ở lần đăng nhập ở trên) có liên tục trong suốt quá trình tương tác của người dùng hay nó phải được chuyển mỗi lần đến ngữ cảnh dữ liệu của tôi?


tôi đã thêm một chút thay thế trong trường hợp nó phù hợp với các yêu cầu về tư duy / hộp công cụ của bạn.
jim điện thoại viên

@ Ivan-Mark Bạn đã giải quyết phần này như thế nào Và thứ hai, vì đây là một dự án api web, nên các kết nối (được đặt ở lần đăng nhập ở trên) có liên tục trong suốt quá trình tương tác của người dùng hay nó phải được chuyển mọi lúc đến văn bản dữ liệu của tôi
Narendra Singh Rathore

@NarendraSinghRathore Các chuỗi kết nối được lưu trữ trong một tệp cấu hình với tên cơ sở dữ liệu (hoặc một cái gì đó khác) là khóa. Người dùng chọn cơ sở dữ liệu khi đăng nhập và cơ sở dữ liệu đó được lưu trữ trong bộ đệm ẩn nơi khóa có thể là tên người dùng. Người dùng đưa ra yêu cầu chuyển tên người dùng của mình làm tiêu đề và chuỗi kết nối được truy xuất và chuyển tới văn bản dữ liệu.
Ivan-Mark Debono,

@ Ivan-MarkDebono Bạn có thể giải thích bộ nhớ cache này không? Bạn đang sử dụng bộ nhớ cache hoặc phiên làm việc ở phần phụ trợ hay lưu trữ dưới dạng cookie ở giao diện người dùng. Cảm ơn!
Narendra Singh Rathore

1
@NarendraSinghRathore MemoryCache trong một singleton
Ivan-Mark Debono

Câu trả lời:


110

Câu trả lời này hơi muộn nhưng tôi nghĩ có một cách tiềm năng để làm điều này với một phương pháp mở rộng nhỏ gọn. Chúng ta có thể tận dụng quy ước EF qua cấu hình cộng với một vài lệnh gọi khuôn khổ nhỏ.

Dù sao, mã nhận xét và cách sử dụng ví dụ:

lớp phương thức mở rộng:

public static class ConnectionTools
{
    // all params are optional
    public static void ChangeDatabase(
        this DbContext source,
        string initialCatalog = "",
        string dataSource = "",
        string userId = "",
        string password = "",
        bool integratedSecuity = true,
        string configConnectionStringName = "") 
        /* this would be used if the
        *  connectionString name varied from 
        *  the base EF class name */
    {
        try
        {
            // use the const name if it's not null, otherwise
            // using the convention of connection string = EF contextname
            // grab the type name and we're done
            var configNameEf = string.IsNullOrEmpty(configConnectionStringName)
                ? source.GetType().Name 
                : configConnectionStringName;

            // add a reference to System.Configuration
            var entityCnxStringBuilder = new EntityConnectionStringBuilder
                (System.Configuration.ConfigurationManager
                    .ConnectionStrings[configNameEf].ConnectionString);

            // init the sqlbuilder with the full EF connectionstring cargo
            var sqlCnxStringBuilder = new SqlConnectionStringBuilder
                (entityCnxStringBuilder.ProviderConnectionString);

            // only populate parameters with values if added
            if (!string.IsNullOrEmpty(initialCatalog))
                sqlCnxStringBuilder.InitialCatalog = initialCatalog;
            if (!string.IsNullOrEmpty(dataSource))
                sqlCnxStringBuilder.DataSource = dataSource;
            if (!string.IsNullOrEmpty(userId))
                sqlCnxStringBuilder.UserID = userId;
            if (!string.IsNullOrEmpty(password))
                sqlCnxStringBuilder.Password = password;

            // set the integrated security status
            sqlCnxStringBuilder.IntegratedSecurity = integratedSecuity;

            // now flip the properties that were changed
            source.Database.Connection.ConnectionString 
                = sqlCnxStringBuilder.ConnectionString;
        }
        catch (Exception ex)
        {
            // set log item if required
        }
    }
}

cách sử dụng cơ bản:

// assumes a connectionString name in .config of MyDbEntities
var selectedDb = new MyDbEntities();
// so only reference the changed properties
// using the object parameters by name
selectedDb.ChangeDatabase
    (
        initialCatalog: "name-of-another-initialcatalog",
        userId: "jackthelady",
        password: "nomoresecrets",
        dataSource: @".\sqlexpress" // could be ip address 120.273.435.167 etc
    );

Tôi biết bạn đã có sẵn chức năng cơ bản, nhưng nghĩ rằng điều này sẽ thêm một chút đa dạng.


6
Điều này là rất tốt, cảm ơn bạn! Tôi có thể sử dụng điều này trên một dự án nhiều người thuê cùng với một phần mở rộng Controllersẽ luôn đặt 'db' của bộ điều khiển thành db cụ thể của khách hàng của họ. Điều này cũng giải phóng tôi (hoặc bất kỳ quản trị viên / nhà phát triển nào trong tương lai) khỏi việc phải tạo một chuỗi kết nối mới cho mọi ứng dụng khách được thêm vào.
LukeP

3
vâng, tôi đã vật lộn trong nhiều ngày theo đúng nghĩa đen để tìm ra một giải pháp mạnh mẽ khả thi cho vấn đề này và phương pháp mở rộng đơn giản này đã giải đáp các vấn đề của tôi. kể từ khi tạo nó trở lại vào tháng 11 năm ngoái, tôi đã không phải thực hiện bất kỳ thay đổi nào đối với nó, vì vậy tôi nghĩ rằng nó đã được thử nghiệm trên đường tốt như vậy :). Dù sao, rất vui vì nó đánh dấu được một vài ô ... tốt để nói chuyện.
jim số điện thoại

5
Tôi gặp lỗi này System.ArgumentException: Từ khóa không được hỗ trợ: 'nguồn dữ liệu' trong EF 4
sheshadri

2
@ user1234 Tôi cũng gặp lỗi: Từ khóa không được hỗ trợ 'nguồn dữ liệu'. Để giải quyết vấn đề này, tôi đã phải thay đổi phần mã này của anh ấy: // add a reference to System.Configuration var entityCnxStringBuilder = new EntityConnectionStringBuilder { ProviderConnectionString = new SqlConnectionStringBuilder(System.Configuration.ConfigurationManager .ConnectionStrings[configNameEf].ConnectionString).ConnectionString };
A.Ima

2
@jimtollan Mỗi khi tôi tạo phiên bản mới, nó được tạo từ chuỗi kết nối cũ được lưu trong app.config !!
Abdulsalam Elsharif

62

DbContextcó quá tải phương thức khởi tạo chấp nhận tên của một chuỗi kết nối hoặc chính một chuỗi kết nối. Triển khai phiên bản của riêng bạn và chuyển nó đến hàm tạo cơ sở:

public class MyDbContext : DbContext
{
    public MyDbContext( string nameOrConnectionString ) 
        : base( nameOrConnectionString )
    {
    }
}

Sau đó, chỉ cần chuyển tên của một chuỗi kết nối đã định cấu hình hoặc chính một chuỗi kết nối khi bạn khởi tạo DbContext

var context = new MyDbContext( "..." );

Tôi không nhận ra rằng hàm đã tồn tại trong lớp dẫn xuất DbContext của mình nên tôi chỉ sử dụng hàm đó.
Brian Leeming

2
Tôi nghĩ câu trả lời này nên được đánh dấu là câu trả lời được chấp thuận.
vô ích

2
Câu trả lời này rất tuyệt, nhưng như @eMeL giải thích. Lớp này được tạo tự động, vì vậy thay vào đó bạn nên tạo một lớp khác dựa trên lớp này để không bị ghi đè nếu bạn cập nhật mô hình.
Juan Carlos Oropeza

4
@JuanCarlosOropeza: EF khéo léo đánh dấu các lớp đã tạo (bot hcontext và các thực thể) là một phần, vì vậy bạn có thể tạo tệp của riêng mình, khai báo lại DbContext trong đó (như một phần) và thêm các chức năng tùy chỉnh của bạn vào đó.
dotNET

14

Câu trả lời của Jim Tollan hoạt động tuyệt vời, nhưng tôi gặp Lỗi: Từ khóa không được hỗ trợ 'nguồn dữ liệu'. Để giải quyết vấn đề này, tôi đã phải thay đổi phần mã này của anh ấy:

// add a reference to System.Configuration
var entityCnxStringBuilder = new EntityConnectionStringBuilder
    (System.Configuration.ConfigurationManager
            .ConnectionStrings[configNameEf].ConnectionString);

đến điều này:

// add a reference to System.Configuration
var entityCnxStringBuilder = new EntityConnectionStringBuilder
{
    ProviderConnectionString = new  SqlConnectionStringBuilder(System.Configuration.ConfigurationManager
               .ConnectionStrings[configNameEf].ConnectionString).ConnectionString
};

Tôi thực sự xin lỗi. Tôi biết rằng tôi không nên sử dụng câu trả lời để trả lời các câu trả lời khác, nhưng câu trả lời của tôi quá dài cho một nhận xét :(


6

Lớp được tạo là 'một phần'!

public partial class Database1Entities1 : DbContext
{
    public Database1Entities1()
        : base("name=Database1Entities1")
    {
    }

... và bạn gọi nó như thế này:

using (var ctx = new Database1Entities1())
      {
        #if DEBUG
        ctx.Database.Log = Console.Write;
        #endif

vì vậy, bạn chỉ cần tạo một tệp lớp riêng cho lớp được tạo tự động ban đầu (có cùng tên lớp!) và thêm một hàm tạo mới với tham số chuỗi kết nối, giống như câu trả lời của Moho trước đây.

Sau khi nó, bạn có thể sử dụng hàm tạo tham số so với nguyên bản. :-)

thí dụ:

using (var ctx = new Database1Entities1(myOwnConnectionString))
      {
        #if DEBUG
        ctx.Database.Log = Console.Write;
        #endif

Giải pháp trên đang làm việc cho tôi. Bạn có thể biết thêm thông tin chi tiết từ liên kết
Kartik Goyal,

0

Thêm nhiều chuỗi kết nối trong web.config hoặc app.config của bạn.

Sau đó, bạn có thể lấy chúng dưới dạng một chuỗi như:

System.Configuration.ConfigurationManager.
    ConnectionStrings["entityFrameworkConnection"].ConnectionString;

Sau đó, sử dụng chuỗi để đặt:

Provider
Metadata
ProviderConnectionString

Nó được giải thích tốt hơn ở đây:

Đọc chuỗi kết nối từ web.config


các chuỗi kết nối được lưu trữ trong một cơ sở dữ liệu máy chủ sql riêng biệt và một danh sách được hiển thị cho người dùng.
Ivan-Mark Debono

0
string _connString = "metadata=res://*/Model.csdl|res://*/Model.ssdl|res://*/Model.msl;provider=System.Data.SqlClient;provider connection string="data source=localhost;initial catalog=DATABASE;persist security info=True;user id=sa;password=YourPassword;multipleactiveresultsets=True;App=EntityFramework"";

EntityConnectionStringBuilder ecsb = new EntityConnectionStringBuilder(_connString);
ctx = new Entities(_connString);

Bạn có thể lấy chuỗi kết nối từ web.config và chỉ cần đặt chuỗi đó trong phương thức khởi tạo EntityConnectionStringBuilder và sử dụng EntityConnectionStringBuilder làm đối số trong phương thức khởi tạo cho ngữ cảnh.

Lưu vào bộ đệm chuỗi kết nối theo tên người dùng. Ví dụ đơn giản bằng cách sử dụng một vài phương pháp chung để xử lý việc thêm / truy xuất từ ​​bộ nhớ cache.

private static readonly ObjectCache cache = MemoryCache.Default;

// add to cache
AddToCache<string>(username, value);

// get from cache

 string value = GetFromCache<string>(username);
 if (value != null)
 {
     // got item, do something with it.
 }
 else
 {
    // item does not exist in cache.
 }


public void AddToCache<T>(string token, T item)
    {
        cache.Add(token, item, DateTime.Now.AddMinutes(1));
    }

public T GetFromCache<T>(string cacheKey) where T : class
    {
        try
        {
            return (T)cache[cacheKey];
        }
        catch
        {
            return null;
        }
    }

Có nhưng chuỗi kết nối mới có cần phải được chuyển tới dbcontext mỗi khi người dùng gọi hành động của bộ điều khiển không?
Ivan-Mark Debono

Bạn có thể sẽ loại bỏ ngữ cảnh sau mỗi cuộc gọi, vì vậy có. Bối cảnh chỉ nên tồn tại cho một yêu cầu (Đơn vị công việc). Giải thích
scheien

Vậy làm cách nào và ở đâu tôi sẽ lưu trữ các kết nối của người dùng trong suốt phiên của anh ta? (Nhiều người dùng có thể kết nối với các dự án web api và có thể có connectionStrings khác nhau)
Ivan-Mark Debono

Làm thế nào về bộ nhớ đệm nó và truy xuất nó bằng tên người dùng hoặc một số khóa khác.
scheien

0

Trong trường hợp của tôi, tôi đang sử dụng ObjectContext trái ngược với DbContext vì vậy tôi đã chỉnh sửa mã trong câu trả lời được chấp nhận cho mục đích đó.

public static class ConnectionTools
{
    public static void ChangeDatabase(
        this ObjectContext source,
        string initialCatalog = "",
        string dataSource = "",
        string userId = "",
        string password = "",
        bool integratedSecuity = true,
        string configConnectionStringName = "")
    {
        try
        {
            // use the const name if it's not null, otherwise
            // using the convention of connection string = EF contextname
            // grab the type name and we're done
            var configNameEf = string.IsNullOrEmpty(configConnectionStringName)
                ? Source.GetType().Name
                : configConnectionStringName;

            // add a reference to System.Configuration
            var entityCnxStringBuilder = new EntityConnectionStringBuilder
                (System.Configuration.ConfigurationManager
                    .ConnectionStrings[configNameEf].ConnectionString);

            // init the sqlbuilder with the full EF connectionstring cargo
            var sqlCnxStringBuilder = new SqlConnectionStringBuilder
                (entityCnxStringBuilder.ProviderConnectionString);

            // only populate parameters with values if added
            if (!string.IsNullOrEmpty(initialCatalog))
                sqlCnxStringBuilder.InitialCatalog = initialCatalog;
            if (!string.IsNullOrEmpty(dataSource))
                sqlCnxStringBuilder.DataSource = dataSource;
            if (!string.IsNullOrEmpty(userId))
                sqlCnxStringBuilder.UserID = userId;
            if (!string.IsNullOrEmpty(password))
                sqlCnxStringBuilder.Password = password;

            // set the integrated security status
            sqlCnxStringBuilder.IntegratedSecurity = integratedSecuity;

            // now flip the properties that were changed
            source.Connection.ConnectionString
                = sqlCnxStringBuilder.ConnectionString;
        }
        catch (Exception ex)
        {
            // set log item if required
        }
    }
}

Tôi gặp lỗi này Từ khóa không được hỗ trợ: 'data source'. Tôi đang sử dụng EF 4
sheshadri

0

Tôi muốn có nhiều nguồn dữ liệu trong cấu hình ứng dụng. Vì vậy, sau khi thiết lập một phần trong app.config, tôi đã hoán đổi nguồn dữ liệu và sau đó chuyển nó vào dbcontext dưới dạng chuỗi kết nối.

//Get the key/value connection string from app config  
var sect = (NameValueCollection)ConfigurationManager.GetSection("section");  
var val = sect["New DataSource"].ToString();

//Get the original connection string with the full payload  
var entityCnxStringBuilder = new EntityConnectionStringBuilder(ConfigurationManager.ConnectionStrings["OriginalStringBuiltByADO.Net"].ConnectionString);     

//Swap out the provider specific connection string  
entityCnxStringBuilder.ProviderConnectionString = val;

//Return the payload with the change in connection string.   
return entityCnxStringBuilder.ConnectionString;

Điều này khiến tôi mất một chút thời gian để tìm ra. Tôi hy vọng nó sẽ giúp ai đó ra ngoài. Tôi đã làm cho nó quá phức tạp. trước đây.


0

Tôi có hai phương thức mở rộng để chuyển đổi chuỗi kết nối bình thường sang định dạng Entity Framework. Phiên bản này hoạt động tốt với các dự án thư viện lớp mà không cần sao chép các chuỗi kết nối từ tệp app.config sang dự án chính. Đây là VB.Net nhưng dễ chuyển đổi sang C #.

Public Module Extensions

    <Extension>
    Public Function ToEntityConnectionString(ByRef sqlClientConnStr As String, ByVal modelFileName As String, Optional ByVal multipleActiceResultSet As Boolean = True)
        Dim sqlb As New SqlConnectionStringBuilder(sqlClientConnStr)
        Return ToEntityConnectionString(sqlb, modelFileName, multipleActiceResultSet)
    End Function

    <Extension>
    Public Function ToEntityConnectionString(ByRef sqlClientConnStrBldr As SqlConnectionStringBuilder, ByVal modelFileName As String, Optional ByVal multipleActiceResultSet As Boolean = True)
        sqlClientConnStrBldr.MultipleActiveResultSets = multipleActiceResultSet
        sqlClientConnStrBldr.ApplicationName = "EntityFramework"

        Dim metaData As String = "metadata=res://*/{0}.csdl|res://*/{0}.ssdl|res://*/{0}.msl;provider=System.Data.SqlClient;provider connection string='{1}'"
        Return String.Format(metaData, modelFileName, sqlClientConnStrBldr.ConnectionString)
    End Function

End Module

Sau đó, tôi tạo một lớp một phần cho DbContext:

Partial Public Class DlmsDataContext

    Public Shared Property ModelFileName As String = "AvrEntities" ' (AvrEntities.edmx)

    Public Sub New(ByVal avrConnectionString As String)
        MyBase.New(CStr(avrConnectionString.ToEntityConnectionString(ModelFileName, True)))
    End Sub

End Class

Tạo truy vấn:

Dim newConnectionString As String = "Data Source=.\SQLEXPRESS;Initial Catalog=DB;Persist Security Info=True;User ID=sa;Password=pass"

Using ctx As New DlmsDataContext(newConnectionString)
    ' ...
    ctx.SaveChanges()
End Using

0

Đối với cả Cơ sở dữ liệu SQL Server và SQLite, hãy sử dụng:

_sqlServerDBsContext = new SqlServerDBsContext(new DbContextOptionsBuilder<SqlServerDBsContext>().UseSqlServer("Connection String to SQL DB").Options);

Đối với SQLite, hãy đảm bảo đã Microsoft.EntityFrameworkCore.Sqliteđược cài đặt, khi đó chuỗi kết nối chỉ đơn giản là "'DataSource =' + tên tệp".

_sqliteDBsContext = new SqliteDBsContext(new DbContextOptionsBuilder<SqliteDBsContext>().UseSqlite("Connection String to SQLite DB").Options);

-6
Linq2SQLDataClassesDataContext db = new Linq2SQLDataClassesDataContext();

var query = from p in db.SyncAudits orderby p.SyncTime descending select p;
Console.WriteLine(query.ToString());

hãy thử mã này ...

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.