【问题标题】:Find a generic DbSet in a DbContext dynamically在 DbContext 中动态查找通用 DbSet
【发布时间】:2016-02-29 15:49:54
【问题描述】:

我知道已经有人问过这个问题,但我找不到令我满意的答案。我要做的是根据类型名称检索特定的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&lt;ModelA&gt; 类型的实例。从那里,我对 model 对象的任何使用都会得到 RunTimeBinderException : “Microsoft.Data.Entity.Internal.InternalDbSet”不包含“FirstOrDefault”的定义

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

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

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

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

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

这个解决方案有机会工作吗? 我错过了什么吗? 任何指针将不胜感激?

提前致谢

[编辑]

我尝试返回IQueryable&lt;dynamic&gt; 而不是dynamic,我可以执行基本查询model.FirstOrDefault(); 但最重要的是我也希望能够过滤一个字段:

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

【问题讨论】:

  • 如何将动态更改为 IEnumerable ?
  • 我确实尝试过类似的事情(更改为 IQueryable ),即使我可以做一些像 model.FirstOrDefault() 这样的查询,它也不允许我做像 model.FirstOrDefault(item =&gt; item.MyProperty == true) 这样的事情。这会降低事物的实用性。
  • 我也遇到了同样的问题。你有没有达成决议的机会?
  • 嗨@Asryael,不幸的是我还没有找到合适的解决方案,由于时间不够,我不得不想出一个在我的案例场景中可以正常工作的替代解决方案(发布在下面)。希望这可以帮助你。
  • 这不起作用var mydbset= this.Set(typeof(SomeEntity));

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


【解决方案1】:

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

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

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

在我自己的情况下,不再需要制定程序

  1. 定义模型略有不同

首先,我需要一个可在我的所有模型中检索的字段,这显然应该是一种识别唯一记录的方法。

// 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. 重新调整动态查询的用途,以做我们知道可行的事情

我还没有找到动态进行智能查询的方法,所以我知道我可以可靠地识别一条记录并知道它是否存在。

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. 一点管道让我了解我的情况

接下来,我需要对动态查询做的是一种将数据从服务器复制到客户端的方法。 (为了简化这个例子,我省略了大部分架构)

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. 连接位。

现在,这是连接位的例程

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:我仍然对真实答案感兴趣,因此如果您有真实答案要分享,请继续发布其他答案。

【讨论】:

    【解决方案2】:

    那么当我在编译期间不知道&lt;T&gt; 时,我是怎么做的呢?

    首先需要获取类型,因为 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>"));
    

    【讨论】:

    • 感谢您的回答。但是 - public virtual DbSet Set(Type entityType) - Entity Framework 7 似乎不再存在。最重要的是,DbSet 不允许我对模型进行流畅的 API 查询。
    猜你喜欢
    • 1970-01-01
    • 2017-12-10
    • 1970-01-01
    • 2012-11-11
    • 1970-01-01
    • 2016-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多