Sta*_*ajs 27 .net c# entity-framework
我们有一个Windows服务,可以将一堆插件(程序集)加载到自己的AppDomain中.每个插件都与SOA意义上的"服务边界"对齐,因此负责访问自己的数据库.我们注意到,在单独的AppDomain中,EF的速度要慢3到5倍.
我知道EF第一次创建DbContext并点击数据库时,它必须做一些必须按AppDomain重复的设置工作(即不在AppDomains上缓存).考虑到EF代码完全是自包含的插件(因此自包含到AppDomain),我原本期望时间可以与父AppDomain的时间相媲美.他们为什么不同?
尝试过针对.NET 4/EF 4.4和.NET 4.5/EF 5.
class Program
{
static void Main(string[] args)
{
var watch = Stopwatch.StartNew();
var context = new Plugin.MyContext();
watch.Stop();
Console.WriteLine("outside plugin - new MyContext() : " + watch.ElapsedMilliseconds);
watch = Stopwatch.StartNew();
var posts = context.Posts.FirstOrDefault();
watch.Stop();
Console.WriteLine("outside plugin - FirstOrDefault(): " + watch.ElapsedMilliseconds);
var pluginDll = Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory + @"..\..\..\EF.Plugin\bin\Debug\EF.Plugin.dll");
var domain = AppDomain.CreateDomain("other");
var plugin = (IPlugin) domain.CreateInstanceFromAndUnwrap(pluginDll, "EF.Plugin.SamplePlugin");
plugin.FirstPost();
Console.ReadLine();
}
}
Run Code Online (Sandbox Code Playgroud)
public interface IPlugin
{
void FirstPost();
}
Run Code Online (Sandbox Code Playgroud)
public class MyContext : DbContext
{
public IDbSet<Post> Posts { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
public class Post
{
public int Id { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
public class SamplePlugin : MarshalByRefObject, IPlugin
{
public void FirstPost()
{
var watch = Stopwatch.StartNew();
var context = new MyContext();
watch.Stop();
Console.WriteLine(" inside plugin - new MyContext() : " + watch.ElapsedMilliseconds);
watch = Stopwatch.StartNew();
var posts = context.Posts.FirstOrDefault();
watch.Stop();
Console.WriteLine(" inside plugin - FirstOrDefault(): " + watch.ElapsedMilliseconds);
}
}
Run Code Online (Sandbox Code Playgroud)
笔记:
outside plugin - new MyContext() : 55
outside plugin - FirstOrDefault(): 783
inside plugin - new MyContext() : 352
inside plugin - FirstOrDefault(): 2675
outside plugin - new MyContext() : 53
outside plugin - FirstOrDefault(): 798
inside plugin - new MyContext() : 355
inside plugin - FirstOrDefault(): 2687
outside plugin - new MyContext() : 45
outside plugin - FirstOrDefault(): 778
inside plugin - new MyContext() : 355
inside plugin - FirstOrDefault(): 2683
在对AppDomains的成本进行进一步研究之后,似乎有人建议后续AppDomain必须重新JIT系统DLL,因此在创建AppDomain时存在固有的启动成本.那是怎么回事?我原本以为JIT-ing会在AppDomain上创建,但是在调用它时可能是EF JIT-ing?
重新JIT的参考:http: //msdn.microsoft.com/en-us/magazine/cc163655.aspx#S8
Timings听起来很相似,但不确定是否相关: 在新的AppDomain中建立的第一个WCF连接非常慢
基于@ Yasser建议在AppDomains上进行EF通信,我试图进一步隔离它.我不相信这是事实.
我已经完全删除了EF.csproj中的任何EF引用.我现在有足够的代表发布图像,所以这是解决方案结构:

如您所见,只有插件才能引用Entity Framework.我还验证了只有插件有一个带有EntityFramework.dll的bin文件夹.
我添加了一个帮助程序来验证是否已在AppDomain中加载EF程序集.我还验证了(未示出)在调用数据库之后,还加载了额外的EF程序集(例如动态代理).
因此,检查EF是否已在各个点加载:
...产生:
Main - IsEFLoaded: False Plugin - IsEFLoaded: True Plugin - new MyContext() : 367 Plugin - FirstOrDefault(): 2693 Plugin - IsEFLoaded: True Main - IsEFLoaded: False
因此,似乎AppDomains完全隔离(如预期的那样),并且插件内的时序相同.
class Program
{
static void Main(string[] args)
{
var dir = Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory + @"..\..\..\EF.Plugin\bin\Debug");
var evidence = new Evidence();
var setup = new AppDomainSetup { ApplicationBase = dir };
var domain = AppDomain.CreateDomain("other", evidence, setup);
var pluginDll = Path.Combine(dir, "EF.Plugin.dll");
var plugin = (IPlugin) domain.CreateInstanceFromAndUnwrap(pluginDll, "EF.Plugin.SamplePlugin");
Console.WriteLine("Main - IsEFLoaded: " + Helper.IsEFLoaded());
plugin.FirstPost();
Console.WriteLine("Main - IsEFLoaded: " + Helper.IsEFLoaded());
Console.ReadLine();
}
}
Run Code Online (Sandbox Code Playgroud)
(是的,我不会为此添加另一个项目...)
public static class Helper
{
public static bool IsEFLoaded()
{
return AppDomain.CurrentDomain
.GetAssemblies()
.Any(a => a.FullName.StartsWith("EntityFramework"));
}
}
Run Code Online (Sandbox Code Playgroud)
public class SamplePlugin : MarshalByRefObject, IPlugin
{
public void FirstPost()
{
Console.WriteLine("Plugin - IsEFLoaded: " + Helper.IsEFLoaded());
var watch = Stopwatch.StartNew();
var context = new MyContext();
watch.Stop();
Console.WriteLine("Plugin - new MyContext() : " + watch.ElapsedMilliseconds);
watch = Stopwatch.StartNew();
var posts = context.Posts.FirstOrDefault();
watch.Stop();
Console.WriteLine("Plugin - FirstOrDefault(): " + watch.ElapsedMilliseconds);
Console.WriteLine("Plugin - IsEFLoaded: " + Helper.IsEFLoaded());
}
}
Run Code Online (Sandbox Code Playgroud)
@Yasser:只有在命中数据库后才会将System.Data.Entity加载到插件中.最初只在插件中加载了EntityFramework.dll,但是也加载了后数据库的其他EF程序集:

拉链解决方案.该网站仅保留文件30天.随意建议一个更好的文件共享网站.
此外,我有兴趣知道您是否可以通过在主项目中引用EF来验证我的发现,并查看原始样本的时间模式是否可重现.
需要说明的是,这是我有兴趣分析的第一个呼叫时间,其中包括EF启动.在第一次调用时,从父AppDomain中的~800ms到子AppDomain中的~2700ms是非常明显的.在随后的呼叫中,从约1ms到约3ms几乎不可察觉.为什么第一次呼叫(包括EF启动)在AppDomains内部更加昂贵?
我已经更新了样本,专注FirstOrDefault()于减少噪音的呼叫.在父AppDomain中运行并在3个子AppDomain中运行的一些计时:
EF.vshost.exe|0|FirstOrDefault(): 768 EF.vshost.exe|1|FirstOrDefault(): 1 EF.vshost.exe|2|FirstOrDefault(): 1 AppDomain0|0|FirstOrDefault(): 2623 AppDomain0|1|FirstOrDefault(): 2 AppDomain0|2|FirstOrDefault(): 1 AppDomain1|0|FirstOrDefault(): 2669 AppDomain1|1|FirstOrDefault(): 2 AppDomain1|2|FirstOrDefault(): 1 AppDomain2|0|FirstOrDefault(): 2760 AppDomain2|1|FirstOrDefault(): 3 AppDomain2|2|FirstOrDefault(): 1
static void Main(string[] args)
{
var mainPlugin = new SamplePlugin();
for (var i = 0; i < 3; i++)
mainPlugin.Do(i);
Console.WriteLine();
for (var i = 0; i < 3; i++)
{
var plugin = CreatePluginForAppDomain("AppDomain" + i);
for (var j = 0; j < 3; j++)
plugin.Do(j);
Console.WriteLine();
}
Console.ReadLine();
}
private static IPlugin CreatePluginForAppDomain(string appDomainName)
{
var dir = Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory + @"..\..\..\EF.Plugin\bin\Debug");
var evidence = new Evidence();
var setup = new AppDomainSetup { ApplicationBase = dir };
var domain = AppDomain.CreateDomain(appDomainName, evidence, setup);
var pluginDll = Path.Combine(dir, "EF.Plugin.dll");
return (IPlugin) domain.CreateInstanceFromAndUnwrap(pluginDll, "EF.Plugin.SamplePlugin");
}
public class SamplePlugin : MarshalByRefObject, IPlugin
{
public void Do(int i)
{
var context = new MyContext();
var watch = Stopwatch.StartNew();
var posts = context.Posts.FirstOrDefault();
watch.Stop();
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName + "|" + i + "|FirstOrDefault(): " + watch.ElapsedMilliseconds);
}
}
Run Code Online (Sandbox Code Playgroud)
拉链解决方案.该网站仅保留文件30天.随意建议一个更好的文件共享网站.
这似乎只是子 AppDomain 的成本。一篇相当古老的帖子(可能不再相关)表明,除了必须 JIT 编译每个子 AppDomain 之外,还可能存在其他考虑因素,例如评估安全策略。
实体框架确实具有相对较高的启动成本,因此效果被放大,但相比之下,调用 System.Data 的其他部分(例如直接SqlDataReader)同样可怕:
EF.vshost.exe|0|SqlDataReader: 67 EF.vshost.exe|1|SqlDataReader: 0 EF.vshost.exe|2|SqlDataReader: 0 应用程序域0|0|SqlDataReader:313 应用程序域0|1|SqlDataReader:2 应用程序域0|2|SqlDataReader: 0 应用程序域1|0|SqlDataReader:290 应用程序域1|1|SqlDataReader:3 应用程序域1|2|SqlDataReader:0 应用程序域2|0|SqlDataReader:316 应用程序域2|1|SqlDataReader:2 应用程序域2|2|SqlDataReader:0
public class SamplePlugin : MarshalByRefObject, IPlugin
{
public void Do(int i)
{
var watch = Stopwatch.StartNew();
using (var connection = new SqlConnection("Data Source=.\\sqlexpress;Initial Catalog=EF.Plugin.MyContext;Integrated Security=true"))
{
var command = new SqlCommand("SELECT * from Posts;", connection);
connection.Open();
var reader = command.ExecuteReader();
reader.Close();
}
watch.Stop();
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName + "|" + i + "|SqlDataReader: " + watch.ElapsedMilliseconds);
}
}
Run Code Online (Sandbox Code Playgroud)
即使是新晋的谦虚者也DataTable被夸大了:
EF.vshost.exe|0|数据表:0 EF.vshost.exe|1|数据表:0 EF.vshost.exe|2|数据表:0 应用程序域0|0|数据表:12 应用程序域0|1|数据表:0 应用程序域0|2|数据表:0 应用程序域1|0|数据表:11 应用程序域1|1|数据表:0 应用程序域1|2|数据表:0 应用程序域2|0|数据表:10 应用程序域2|1|数据表:0 应用程序域2|2|数据表:0
public class SamplePlugin : MarshalByRefObject, IPlugin
{
public void Do(int i)
{
var watch = Stopwatch.StartNew();
var table = new DataTable("");
watch.Stop();
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName + "|" + i + "|DataTable: " + watch.ElapsedMilliseconds);
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1926 次 |
| 最近记录: |