动态地在DbContext中查找泛型DbSet

时间:2015-11-26 13:56:02

标签: c# .net asp.net-mvc-5 entity-framework-core

我知道这个问题已经被问到了,但我找不到让我满意的答案。我要做的是根据类型的名称检索特定的DbSet<T>

我有以下内容:

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyDllAssemblyName")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyCallingAssemblyName")]

class MyDbContext : DbContext {

    public DbSet<ModelA> A { get; set; }
    public DbSet<ModelB> B { get; set; }

    public dynamic GetByName_SwitchTest(string name) {
        switch (name) {
            case "A": return A;
            case "B": return B;
        }
    }

    public dynamic GetByName_ReflectionTest(string fullname)
    {
        Type targetType = Type.GetType(fullname);
        var model = GetType()
            .GetRuntimeProperties()
            .Where(o => 
                o.PropertyType.IsGenericType &&
                o.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>) &&
                o.PropertyType.GenericTypeArguments.Contains(targetType))
            .FirstOrDefault();
        if (null != model)
            return model.GetValue(this);
        return null;
    }
}

无论是通过简单的开关还是反射,我都可以轻松获取类型。然而,我需要将类型作为动态返回,因为我不知道它将是什么类型的DbSet。 然后在同一个组件中的其他地方,我用这种方式:

// MyDbContext MyDbContextInstance..
var model = MyDbContextInstance.GetByName_SwitchTest("A");
var record1 = model.FirstOrDefault(); // It crashes here with RunTimeBinderException

此时model包含InternalDbSet<ModelA>类型的实例。从那里,我对model对象的任何使用我得到一个RunTimeBinderException:     'Microsoft.Data.Entity.Internal.InternalDbSet'不包含'FirstOrDefault'的定义

在网上调查,我发现blog post解释了(dixit他的博客):

  

调用FirstOrDefault()失败的原因是该类型   模型信息在运行时不可用。原因不是   可用是因为匿名类型不公开。当方法   正在返回该匿名类型的实例,它返回一个   System.Object,它引用匿名类型的实例 - a   类型,其信息不可用于主程序。

然后他指出了一个解决方案:

  

解决方案实际上非常简单。我们所要做的就是开放   ClassLibrary1项目的AssemplyInfo.cs并添加以下内容   行到它:[assembly:InternalsVisibleTo("assembly-name")]

我确实在我的代码上尝试了这个解决方案,但它不起作用。有关信息,我有一个asp.net 5解决方案,在dnx dotnet46上运行两个程序集。一个app和一个包含我所有模型和DbContext的dll。我所做的所有相关电话都位于dll上。

此解决方案是否有机会工作? 我错过了什么吗? 任何指针都会非常感激吗?

提前致谢

[编辑]

我试图返回IQueryable<dynamic>而不是dynamic我可以执行基本查询model.FirstOrDefault(); 以上我希望能够过滤字段:

var record = model.FirstOrDefault(item => item.MyProperty == true);

2 个答案:

答案 0 :(得分:0)

*免责声明:此回复并未对我的问题给出严格的回答。这是解决我自己问题的另一种方法。我知道这是一个特定情况的具体例子,对每个人都不适用。我发布这种方法是希望它可以帮助某人,但不会将其标记为答案,因为我仍然希望有一个真正的解决方案。

首先,让我们接受这样一个事实:我们可以从当前代码中获得的唯一有用信息是记录是否存在。之后任何动态查询的尝试都会产生RuntimeBinderException。

然后让我们继续另一个事实; DbContext.Add(object)和DbContext.Update(object)不是基于模板的,因此我们可以使用它们来保存我们的模型(而不是db.A.Add()或db.A.Update())

在我自己的情况下,不需要更多的程序

  1. 稍微不同地定义模型
  2. 首先,我需要一个可以在我的所有模型中检索的字段,这显然是一种识别唯一记录的方法。

    // IModel give me a reliable common field to all my models ( Fits my DB design maybe not yours though )
    interface IModel { Guid Id { get; set; } }
    
    // ModelA inherit IModel so that I always have access to an 'Id'
    class ModelA : IModel {
        public Guid Id { get; set; }
        public int OtherField { get; set; }
    }
    
    // ModelB inherit IModel so that I always have access to an 'Id'
    class ModelB : IModel {
        public Guid Id { get; set; }
        public string WhateverOtherField { get; set; }
    }
    
    1. 重新调整动态查询以执行我们知道的工作
    2. 我还没有找到动态进行智能查询的方法,所以相反我知道我可以可靠地识别记录并知道它是否存在。

      class MyDbContext : DbContext {
      
          public DbSet<ModelA> A { get; set; }
          public DbSet<ModelB> B { get; set; }
      
          // In my case, this method help me to know the next action I need to do
          // The switch/case option is not pretty but might have better performance 
          // than Reflection. Anyhow, this is one's choice.
          public bool HasRecord_SwitchTest(string name) {
              switch (name) {
                  case "A": return A.AsNoTracking().Any(o => o.Id == id);
                  case "B": return B.AsNoTracking().Any(o => o.Id == id);
              }
              return false;
          }
      
          // In my case, this method help me to know the next action I need to do
          public bool HasRecord_ReflectionTest(string fullname)
          {
              Type targetType = Type.GetType(fullname);
              var model = GetType()
                  .GetRuntimeProperties()
                  .Where(o => 
                      o.PropertyType.IsGenericType &&
                      o.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>) &&
                      o.PropertyType.GenericTypeArguments.Contains(targetType))
                  .FirstOrDefault();
              if (null != model)
                  return (bool)model.GetValue(this).AsNoTracking().Any(o => o.Id == id);
              return false;
          }
      
          // Update and save immediately - simplified for example
          public async Task<bool> UpdateDynamic(object content)
          {
              EntityEntry entry = Update(content, GraphBehavior.SingleObject);
              return 1 == await SaveChangesAsync(true);
          }
      
          // Insert and save immediately - simplified for example
          public async Task<bool> InsertDynamic(object content)
          {
              EntityEntry entry = Add(content, GraphBehavior.SingleObject);
              return 1 == await SaveChangesAsync(true);
          }
      }
      
      1. 一点点管道以了解我的情况
      2. 接下来,我需要对动态查询执行的操作是将数据从服务器复制到客户端的方法。 (我省略了一大块架构来简化这个例子)

        class ReplicationItem
        {
            public ReplicationAction Action { get; set; } // = Create, Update, Delete
            public string ModelName { get; set; } // Model name
            public Guid Id { get; set; } // Unique identified across whole platform
        }
        
        1. 连接位。
        2. 现在,这是连接位

          的例程
          public async void ProcessReplicationItem(ReplicationItem replicationItem)
          {
              using (var db = new MyDbContext())
              {
                  // Custom method that attempts to get remote value by Model Name and Id
                  // This is where I get the strongly typed object 
                  var remoteRecord = await TryGetAsync(replicationItem.ModelName, replicationItem.Id);
                  bool hasRemoteRecord = remoteRecord.Content != null;
          
                  // Get to know if a local copy of this record exists.
                  bool hasLocalRecord = db.HasRecord_ReflectionTest(replicationItem.ModelName, replicationItem.Id);
          
                  // Ensure response is valid whether it is a successful get or error is meaningful ( ie. NotFound )
                  if (remoteRecord.Success || remoteRecord.ResponseCode == System.Net.HttpStatusCode.NotFound)
                  {
                      switch (replicationItem.Action)
                      {
                          case ReplicationAction.Create:
                          {
                              if (hasRemoteRecord)
                              {
                                  if (hasLocalRecord)
                                      await db.UpdateDynamic(remoteRecord.Content);
                                  else
                                      await db.InsertDynamic(remoteRecord.Content);
                              }
                              // else - Do nothing
                              break;
                          }
                          case ReplicationAction.Update:
                              [etc...]
                      }
                  }
              }
          }
          
          // Get record from server and with 'response.Content.ReadAsAsync' type it 
          // already to the appropriately
          public static async Task<Response> TryGetAsync(ReplicationItem item)
          {
              if (string.IsNullOrWhiteSpace(item.ModelName))
              {
                  throw new ArgumentException("Missing a model name", nameof(item));
              }
          
              if (item.Id == Guid.Empty)
              {
                  throw new ArgumentException("Missing a primary key", nameof(item));
              }
          
              // This black box, just extrapolate a uri based on model name and id
              // typically "api/ModelA/{the-guid}"
              string uri = GetPathFromMessage(item);
          
              using (var client = new HttpClient())
              {
                  client.BaseAddress = new Uri("http://localhost:12345");
          
                  HttpResponseMessage response = await client.GetAsync(uri);
                  if (response.IsSuccessStatusCode)
                  {
                      return new Response()
                      {
                          Content = await response.Content.ReadAsAsync(Type.GetType(item.ModelName)),
                          Success = true,
                          ResponseCode = response.StatusCode
                      };
                  }
                  else
                  {
                      return new Response()
                      {
                          Success = false,
                          ResponseCode = response.StatusCode
                      };
                  }
              }
          }
          
          public class Response
          {
              public object Content { get; set; }
              public bool Success { get; set; }
              public HttpStatusCode ResponseCode { get; set; }
          }
          

          ps:我仍然对真正的答案感兴趣,所以如果您有真正的答案,请继续发帖以获得其他答案。

答案 1 :(得分:0)

所以当我在编译期间不知道<T>时,我是怎么做到的。

首先需要获取类型 DbContext.Set 方法返回一个非泛型DbSet实例,以访问上下文和底层商店中给定类型的实体。

public virtual DbSet Set(Type entityType)

注意这里参数是应该返回一个集合的实体的类型。为给定的实体类型设置的是返回值。

var type = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(t => t.Name == <Pass your table name>);

现在有了这种类型

if(type != null)
{
DbSet context = context.Set(type);
}

或者一个班轮就是

DbSet mySet = context.Set(Type.GetType("<Your Entity Name>"));