簡體   English   中英

如何使用Entity Framework Core模擬SQL Server?

[英]How to do SQL Server impersonation with Entity Framework Core?

我需要在現有的EF Core項目中進行SQL Server模擬。 我得到了這個工作(有點)。 當前,每當訪問DbContext任何屬性時, DbContext顯式調用一個函數,該函數檢查上下文的當前連接狀態,並在必要時嘗試連接。

我的想法是,每當通過此方法打開連接時,我都會還原任何模擬(我認為既然共享了連接,那么這很有必要嗎?)。 為此,我只需發送帶有REVERT作為其命令文本的DbCommand 這本身就很好。

REVERT之后,我進行檢查以查看當前請求是否需要模擬。 如果是的話,我以EXECUTE AS USER = @disguise 這樣也可以。

在同一請求中沒有問題,並且似乎進一步的查詢確實在使用模擬的USER。 但是在下一個請求的第一個查詢中,大多數時間(並非總是?)出現(似乎與查詢的內容無關),我得到以下錯誤:

由於會話處於終止狀態,因此無法繼續執行。
當前命令發生嚴重錯誤。 結果(如果有的話)應丟棄。

當我通過在REVERT之前先運行EXECUTE AS...語句來顛倒順序時,根本不會發生任何錯誤(盡管當然,實際上沒有語句使用我需要的模擬上下文)。 因此,我不認為僅冒充是一個問題。 我能想到的唯一區別是,在中斷的情況下,我允許EF在模擬的上下文中執行所有其自身的后台操作。

有誰知道為什么會發生此錯誤? 我最好的猜測是,當將連接釋放回連接池或從連接池恢復連接時,EF在模擬環境中表現不佳嗎?

更直接地,我正在尋找一種使EF在模擬方面表現出色的方法,否則我將采取更多的故障排除步驟來進行進一步調查。

編輯:在這里添加我的DbContext類以供參考

namespace CM.App.Models
{
    public class AppDataContext : DbContext
    {
        private DataContextUser dataContextUser;
        private bool impersonationSet = false;

        public bool HasAdminAccess() => Execute("SELECT IS_ROLEMEMBER('CM_Admin')", (row) => row.GetInt32(0) == 1).Single();

        private DbSet<Report> reports;

        public DbSet<Report> Reports
        {
            get
            {
                OpenConnection();
                return reports;
            }
            set
            {
                this.reports = value;
            }
        }

        private DbSet<Action> actions;
        public DbSet<Action> Actions
        {
            get
            {
                OpenConnection();
                return actions;
            }
            set
            {
                this.actions = value;
            }
        }

        private DbSet<UserSettings> userSettings;
        public DbSet<UserSettings> UserSettings
        {
            get
            {
                OpenConnection();
                return userSettings;
            }
            set
            {
                this.userSettings = value;
            }
        }

        private DbSet<UserStaticReportConfiguration> userStaticReportConfigurations;
        public DbSet<UserStaticReportConfiguration> UserStaticReportConfigurations
        {
            get
            {
                OpenConnection();
                return userStaticReportConfigurations;
            }
            set
            {
                this.userStaticReportConfigurations = value;
            }
        }

        public AppDataContext() { }
        public AppDataContext(DbContextOptions<AppDataContext> options) : base(options) { }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<UserStaticReportConfiguration>()
                .HasIndex(usrc => new { usrc.DbUserName, usrc.Key }).IsUnique();
        }

        public void SetDataContextUser(DataContextUser dataContextUser)
        {
            this.dataContextUser = dataContextUser;
            impersonationSet = false;
            OpenConnection();
        }

        private DataContextException getDataContextException(SqlException sqlException)
        {
            string message = string.Empty;

            foreach (object objErr in sqlException.Errors)
            {
                SqlError err = objErr as SqlError;
                if (message.Length > 0)
                    message += "\n";
                message += err.Message;
            }

            if (string.IsNullOrEmpty(message))
                message = sqlException.Message;

            return DataContextException.GetDataContextException(message);
        }

        public async Task OpenConnectionAsync()
        {
            switch (Database.GetDbConnection().State)
            {
                case ConnectionState.Closed:
                case ConnectionState.Broken:
                case ConnectionState.Connecting:
                    impersonationSet = false;
                    try
                    {
                        await Database.OpenConnectionAsync();
                    }
                    catch (SqlException sqlException)
                    {
                        throw getDataContextException(sqlException);
                    }
                    break;
                default:
                    return;
            }

            if (!impersonationSet && dataContextUser != null && dataContextUser.IsFacade)
            {
                impersonationSet = true;
                await ExecuteAsync("EXECUTE AS USER = @disguise", new Dictionary<string, object> { { "@disguise", dataContextUser.DbUsername } });
            }

            return;
        }

        public void OpenConnection() =>
            OpenConnectionAsync().Wait();
        public void CloseConnection() =>
            Database.CloseConnection();

        public async Task<int> ExecuteAsync(string commandText, Dictionary<string, object> parameters = null, Action<DbCommand> prep = null)
        {
            if (commandText == null)
                throw new ArgumentNullException(nameof(commandText));

            using (DbCommand cmd = Database.GetDbConnection().CreateCommand())
            {
                cmd.CommandText = commandText;
                if (parameters != null)
                {
                    foreach (KeyValuePair<string, object> kvp in parameters)
                    {
                        DbParameter param = cmd.CreateParameter();
                        param.ParameterName = kvp.Key;
                        param.Value = kvp.Value ?? DBNull.Value;
                        cmd.Parameters.Add(param);
                    }
                }
                if (prep != null)
                    prep(cmd);
                await OpenConnectionAsync();
                try
                {
                    return await cmd.ExecuteNonQueryAsync();
                }
                catch (SqlException sqlException)
                {
                    throw getDataContextException(sqlException);
                }
            }
        }
        public async Task<List<T>> ExecuteAsync<T>(string commandText, Func<DbDataReader, T> rowReader, Dictionary<string, object> parameters = null, Action<DbCommand> prep = null)
        {
            if (commandText == null)
                throw new ArgumentNullException(nameof(commandText));
            if (rowReader == null)
                throw new ArgumentNullException(nameof(rowReader));
            List<T> ret = null;
            using (DbCommand cmd = Database.GetDbConnection().CreateCommand())
            {
                cmd.CommandText = commandText;
                if (parameters != null)
                {
                    foreach (KeyValuePair<string, object> kvp in parameters)
                    {
                        DbParameter param = cmd.CreateParameter();
                        param.ParameterName = kvp.Key;
                        param.Value = kvp.Value ?? DBNull.Value;
                        cmd.Parameters.Add(param);
                    }
                }
                if (prep != null)
                    prep(cmd);

                await OpenConnectionAsync();
                try
                {
                    using (DbDataReader reader = await cmd.ExecuteReaderAsync())
                    {
                        ret = new List<T>();
                        while (reader.Read())
                        {
                            ret.Add(rowReader(reader));
                        }
                    }
                }
                catch (SqlException sqlException)
                {
                    throw getDataContextException(sqlException);
                }
            }

            return ret;
        }
        public int Execute(string commandText, Dictionary<string, object> parameters = null, Action<DbCommand> prep = null)
        {
            try
            {
                return ExecuteAsync(commandText, parameters, prep: prep).Result;
            }
            catch (AggregateException ex)
            {
                if (ex.InnerExceptions.FirstOrDefault() is DataContextException && !ex.InnerExceptions.Skip(1).Any())
                    throw ex.InnerExceptions.Single();
                throw ex;
            }
        }
        public List<T> Execute<T>(string commandText, Func<DbDataReader, T> rowReader, Dictionary<string, object> parameters = null, Action<DbCommand> prep = null)
        {
            try
            {
                return ExecuteAsync(commandText, rowReader, parameters, prep).Result;
            }
            catch (AggregateException ex)
            {
                if (ex.InnerExceptions.FirstOrDefault() is DataContextException && !ex.InnerExceptions.Skip(1).Any())
                    throw ex.InnerExceptions.Single();
                throw ex;
            }
        }

    }
}

編輯2:其他上下文

我們的用戶有用戶名/密碼登錄到Web服務,然后使用該用戶名/密碼作為與后端數據庫的實際連接憑據。 技術用戶可以根據需要直接連接到數據庫,但是服務器提供了對數據更非技術友好的視圖。 模擬是用於需要幫助用戶了解他們為什么看到他們看到的數據的管理員的。 已有RLS限制了每個用戶可以看到的數據,因此我們認為SQL Server級模擬將是其他用戶看到的最真實的視圖。

好的,我發現,盡管我不喜歡它,但是一種解決方案是保持所有使用模擬的連接不被合並。 我沒有意識到這是可能的,我在EFCore源代碼中找到了它。

基本上我只是添加了SqlConnection.ClearPool((SqlConnection)Database.GetDbConnection()); 在我的EXECUTE AS ...語句之前並刪除了REVERT (由於連接不再共享,因此不能在錯誤的上下文中前進)。

對此我唯一不滿意的是,我從來沒有發現EF(或基礎提供者)的真實基礎問題與我的模擬連接狀態有關。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM