在Windows服务中使用SQL LocalDB

时间:2014-10-27 21:53:01

标签: sql sql-server localdb

我有一个非常小的测试应用程序,我在安装过程中尝试安装Windows服务并创建LocalDB数据库,然后在Windows服务运行时连接到该LocalDB数据库。

我遇到了从Windows服务连接到LocalDB实例的巨大问题。

我的安装过程完全如下:

  1. 执行安装程序.msi文件,该文件以NT AUTHORITY \ SYSTEM帐户运行msiexec进程。

  2. 使用以下命令运行自定义操作以执行SqlLocalDB.exe:

    • sqllocaldb.exe创建MYINSTANCE
    • sqllocaldb.exe分享MYINSTANCE MYINSTANCESHARE
    • sqllocaldb.exe启动MYINSTANCE
  3. 使用ADO.NET(System.Data.SqlConnection)运行自定义C#操作以执行以下操作:

    • 连接到以下连接字符串Data Source=(localdb)\MYINSTANCE; Integrated Security=true
    • CREATE DATABASE TestDB
    • USE TestDB
    • 创建表...
  4. 在安装程序完成之前启动Windows服务。

  5. Windows服务已安装到LocalSystem帐户,因此也作为NT AUTHORITY \ SYSTEM用户帐户运行。

  6. 服务尝试使用上面使用的相同连接字符串进行连接。

  7. 尝试从Windows服务中打开与上述连接字符串的连接时,我一直收到以下错误:

      

    System.Data.SqlClient.SqlException(0x80131904):与网络相关或   建立连接时发生特定于实例的错误   SQL Server。服务器未找到或无法访问。校验   实例名称正确且SQL Server配置为   允许远程连接。 (提供者:SQL网络接口,错误:50    - 发生本地数据库运行时错误。指定的LocalDB实例不存在。

    这很令人沮丧,因为msi安装程序自定义操作和Windows服务都在同一个Windows用户帐户下运行(我检查过,它们都是NT AUTHORITY \ System)。那么为什么第一部作品而第二部作品不在我之外。

    我尝试更改自定义操作和Windows服务中使用的连接字符串以使用共享名称(localdb)\.\MYINSTANCESHARE,并从Windows服务中获得完全相同的错误。

    我尝试将Windows服务登录的用户帐户更改为我的Windows用户帐户,只要我第一次运行命令将其添加到该实例的SQL Server登录帐户,该帐户就可以正常工作。

    我还尝试运行控制台应用程序并连接到共享名称连接字符串,这也很有用。

    我也尝试从SQL Server Management Studio连接到共享名称,并且也可以。

    然而,这些方法都没有真正解决我的问题。我需要Windows服务,因为它会在计算机启动后立即启动(即使没有用户登录),并且无论登录哪个用户帐户都会启动。

    Windows服务如何连接到LocalDB私有实例?

    我正在使用SQL Server 2014 Express LocalDB。

5 个答案:

答案 0 :(得分:13)

从对问题的评论中汲取经验,以下是一些需要关注的方面。其中一些已在这些评论中得到解答,但我在这里记录其他人,以防信息可能有用。


  • 正在使用什么版本的.Net?这里是4.5.1(好),但早期版本无法处理首选连接字符串(即@"(localdb)\InstanceName")。以下引用来自上述链接:

      

    如果您的应用程序在4.0.2之前使用.NET版本,则必须直接连接到LocalDB的命名管道。

    根据SqlConnection.ConnectionString的MSDN页面:

      

    从.NET Framework 4.5开始,您还可以按如下方式连接到LocalDB数据库:

         

    server=(localdb)\\myInstance

  • 路径:

    • 实例: C:\ Users {Windows登录} \ AppData \ Local \ Microsoft \ Microsoft SQL Server本地数据库\实例

    • 数据库:

      • 通过SSMS或直接连接创建: C:\ Users {Windows登录} \ Documents C:\ Users {Windows登录}
      • 通过Visual Studio创建: C:\ Users {Windows登录} \ AppData \ Local \ Microsoft \ VisualStudio \ SSDT


  • 初始问题
    • 症状:
      • 在预期位置创建的数据库文件(.mdf.ldf):
        C:\的Windows \ system32 \设置\ systemprofile
      • 在意外位置创建的实例文件:
        C:\ Users \ {当前用户} \ AppData \ Local \ Microsoft \ Microsoft SQL Server本地数据库\实例
    • 原因(注意来自" SqlLocalDB实用程序"上面链接的MSDN页面;强调我的):
        

      启动以外的操作只能在属于当前登录用户的实例上执行。


  • 要尝试的事情:

    • 指定数据库的连接字符串(如果错误与无法连接到实例有关,可能是一个长镜头):
    • "Server=(LocalDB)\MYINSTANCE; Integrated Security=true ;AttachDbFileName=C:\Windows\System32\config\systemprofile\TestDB.mdf"
    • "Server=(LocalDB)\.\MYINSTANCESHARE; Integrated Security=true ;AttachDbFileName=C:\Windows\System32\config\systemprofile\TestDB.mdf"

    • 服务正在运行吗?从命令提示符运行以下命令:
      TASKLIST /FI "IMAGENAME eq sqlservr.exe"
      它应该列在" Console"对于"会话名称"柱

    • 从命令提示符运行以下命令:
      sqllocaldb.exe info MYINSTANCE
      并验证"所有者"的价值。是正确的。是"共享名称"的值应该是什么?如果没有,文档说明:

        

      只有计算机上的管理员才能创建LocalDB的共享实例

    • 作为设置的一部分,将NT AUTHORITY\System帐户添加为系统登录,如果此帐户未显示为"所有者"实例:
      CREATE LOGIN [NT AUTHORITY\System] FROM WINDOWS; ALTER SERVER ROLE [sysadmin] ADD MEMBER [NT AUTHORITY\System];

    • 检查以下文件以获取线索/详细信息:
      C:\ Users {Windows登录} \ AppData \ Local \ Microsoft \ Microsoft SQL Server本地数据库\实例\ MYINSTANCE \ error.log

    • 最后,您可能需要创建一个实际帐户来创建和拥有实例和数据库,以及运行您的服务。 LocalDB真的是用户模式,让你的服务有自己的登录有什么缺点吗?而且你可能不需要在那时共享实例。

      事实上,正如Microsoft在SQL Server YYYY Express LocalDB MSDN页面上所指出的那样:

        

      由于Windows文件系统重定向,内置帐户(如NT AUTHORITY \ SYSTEM)拥有的 LocalDB 实例可能会出现可管理性问题;而是使用普通的Windows帐户作为所有者。

更新(2015-08-21)

根据OP的反馈,在某些环境中使用常规用户帐户可能会出现问题,并且请记住在%LOCALAPPDATA%文件夹中为运行安装程序的用户创建的LocalDB实例的原始问题(并且 NT AUTHORITY \ System %LOCALAPPDATA%文件夹,我找到了一个似乎与易于安装(无用户创建)一致的解决方案并且不需要额外的代码来加载SYSTEM配置文件。

尝试使用 LocalSystem帐户的两个内置帐户之一(不保留自己的注册信息。请使用以下任一帐户:

两者都有自己的个人资料文件夹: C:\ Windows \ ServiceProfiles

虽然我无法通过安装程序进行测试,但我确实通过将我的SQL Server Express 2014实例设置为以此帐户登录来测试登录为 NT AUTHORITY \ NetworkService 的服务,并且重新启动SQL Server服务。然后我运行了以下内容:

EXEC xp_cmdshell 'sqllocaldb c MyTestInstance -s';

并在以下位置创建了实例: C:\ Windows \ ServiceProfiles \ NetworkService \ AppData \ Local \ Microsoft \ Microsoft SQL Server本地数据库\实例

然后我运行了以下内容:

EXEC xp_cmdshell N'SQLCMD -S (localdb)\MyTestInstance -E -Q "CREATE DATABASE [MyTestDB];"';

它创建了数据库: C:\ Windows \ ServiceProfiles \ NetworkService

答案 1 :(得分:3)

我最近能够在我们的WiX安装程序中解决类似的问题。我们有一个在SYSTEM帐户下运行的Windows服务和一个安装程序,其中基于LocalDB的存储是数据库配置的选项之一。一段时间(实际上是几年)产品升级和服务工作得很好,没有与LocalDB相关的问题。我们使用默认的v11.0实例,它是在C:\ Windows \ System32 \ config树中的SYSTEM配置文件中创建的,以及通过在ALLUSERSPROFILE树中创建的AttachDbFileName指定的数据库。数据库提供程序配置为使用Windows身份验证。我们还在安装程序中有一个自定义操作,计划为延迟/非模拟,它运行数据库架构更新。

直到最近,所有这些工作都很顺利。经过另一堆数据库更新后,我们的新版本在升级前者后开始失败 - 服务无法启动,报告臭名昭着“与SQL Server建立连接时出现与网络相关或特定于实例的错误”(错误50) )错。

在调查此问题时,很明显问题在于WiX运行自定义操作。虽然非模拟CA在SYSTEM帐户下运行,但注册表配置文件和环境仍然是当前用户的(我怀疑WiX在附加到用户会话时自愿加载这些)。这导致从LOCALAPPDATA变量扩展的路径不正确 - 服务接收SYSTEM配置文件1,但架构更新CA与用户的配置一起工作。

所以这里有两种可能的解决方案。第一个很简单,但对用户的系统过于干扰 - 通过psexec启动cmd.exe,在SYSTEM帐户下重新创建破坏的实例。这不是我们的选择,因为用户可能在v11.0实例中创建了其他数据库,这是公共的。第二个选项假设了很多重构,但不会伤害任何东西。以下是使用WiX CA中的LocalDB正确运行数据库架构更新的方法:

  1. 将您的CA配置为延迟/非模仿(应在SYSTEM帐户下运行);
  2. 修复环境以指向SYSTEM配置文件路径:

    var systemRoot = Environment.GetEnvironmentVariable("SystemRoot");
    Environment.SetEnvironmentVariable("USERPROFILE", String.Format(@"{0}\System32\config\systemprofile", systemRoot));
    Environment.SetEnvironmentVariable("APPDATA", String.Format(@"{0}\System32\config\systemprofile\AppData\Roaming", systemRoot));
    Environment.SetEnvironmentVariable("LOCALAPPDATA", String.Format(@"{0}\System32\config\systemprofile\AppData\Local", systemRoot));
    Environment.SetEnvironmentVariable("HOMEPATH", String.Empty);
    Environment.SetEnvironmentVariable("USERNAME", Environment.UserName);
    
  3. 加载SYSTEM帐户个人资料。我使用了LogonUser / LoadUserProfile本机API方法,如下所示:

    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool LogonUser(
        string lpszUserName,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        ref IntPtr phToken);
    
    [StructLayout(LayoutKind.Sequential)]
    struct PROFILEINFO
    {
        public int dwSize; 
        public int dwFlags;
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpUserName; 
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpProfilePath; 
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpDefaultPath; 
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpServerName; 
        [MarshalAs(UnmanagedType.LPWStr)] 
        public String lpPolicyPath; 
        public IntPtr hProfile; 
    }
    
    [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool LoadUserProfile(IntPtr hToken, ref PROFILEINFO lpProfileInfo);
    
    var hToken = IntPtr.Zero;
    var hProfile = IntPtr.Zero;
    bool result = LogonUser("SYSTEM", "NT AUTHORITY", String.Empty, 3 /* LOGON32_LOGON_SERVICE */, 0 /* LOGON32_PROVIDER_DEFAULT */, ref token);
    if (result)
    {
        var profileInfo = new PROFILEINFO();
        profileInfo.dwSize = Marshal.SizeOf(profileInfo);
        profileInfo.lpUserName = @"NT AUTHORITY\SYSTEM";
        if (LoadUserProfile(token, ref profileInfo))
            hProfile = profileInfo.hProfile;
    }
    

    将其包含在IDisposable类中,并使用using语句构建上下文。

  4. 最重要的 - 重构代码以在子进程中执行必要的数据库更新。这可能是安装程序DLL或独立实用程序的简单exe包装程序(如果已有)。

  5. P.S。如果只允许Microsoft使用通过命令行选项选择创建LocalDB实例的位置,则可以避免所有这些困难。例如,像Postgres的initdb / pg_ctl实用程序一样。

答案 2 :(得分:0)

我建议您使用其他用户帐户,而不是使用系统帐户,执行以下操作: -

  • 在计算机上创建一个新帐户,并将其设置为帐户 Windows服务运行的位置。这不是一个好习惯 系统帐户只是为了运行一个应用程序,无论如何,作为 权限过多。
  • 确保将LocalDB文件的权限设置为允许所述用户帐户访问数据库(从而继续 使用集成安全性)
  • 通过运行sqlcmd或Management Studio尝试连接到同一用户帐户下的数据库(安装后),确保其正常工作 在所述用户的上下文下,然后连接到Integrated 安全性以确保其有效。

其他一些尝试/考虑的事情:

  • 您是否在Windows事件日志中检查了可能对诊断有用的任何事件?
  • 如果您有任何其他版本的SQL Server(尤其是2012年之前),请确保对于命令行工具,%PATH%不会设置为首先查找旧版工具。较旧的工具不支持LocalDB。
  • 也可以(作为替代方案)将LocalDB设置为与其他用户共享。这涉及共享实例,然后授予其他用户访问权限。请参阅"共享问题"本文中的部分:Troubleshoot SQL Server 2012 Express LocalDB

还有another SO article可能在其中的链接中包含一些更有用的信息(通过将pl-pl更改为en-us,将URL中的语言从波兰语更改为英语)。他的解决方案是使用SQL Server帐户,在您的情况下可能不太好。

这可能也很有用,因为它涉及被拒绝的安全权限,以及可能的解决方案:https://dba.stackexchange.com/questions/30383/cannot-start-sqllocaldb-instance-with-my-windows-account

答案 3 :(得分:0)

Trevor,你遇到的问题是MSI自定义操作。您必须使用“Impersonate = false”配置它们,否则自定义操作将在当前用户上下文中执行。

BTW您使用什么工具来创建安装程序? 根据您使用的工具,您能否提供自定义操作配置的屏幕截图或代码段?

本文中接受的答案将为您提供有关不同自定义操作执行备选方案的一些其他信息: Run ExeCommand in customAction as Administrator mode in Wix Installer

您可以在此处找到有关模拟的其他信息: http://blogs.msdn.com/b/rflaming/archive/2006/09/23/768248.aspx

答案 4 :(得分:0)

我不会在系统的localdb实例下创建数据库。我是在安装产品的当前用户下创建的。如果您需要删除或管理数据库,这将使生活更加轻松。他们可以通过sql management studio完成这项工作。否则,您必须使用psexc或其他方法在SYSTEM帐户下启动流程来管理它。

创建数据库后,使用您提到的共享选项。然后,SYSTEM帐户可以通过共享名称访问数据库。

sqllocaldb共享MSSqlLocalDb LOCAL_DB

分享时,我注意到您必须重新启动本地数据库实例才能通过共享名称实际访问数据库:

sqllocaldb停止MSSQLLocalDB

sqllocaldb启动MSSQLLocalDB

此外,您可能需要将SYSTEM帐户作为数据库读取器和写入器添加到数据库...

EXEC sp_addrolemember db_datareader,' NT AUTHORITY \ SYSTEM'