Find dynamic dbset in dbcontext dynamically

I know that this question has already been asked, but I could not find the answer that satisfied me. I am trying to get a specific DbSet<T> based on its type name.

I have the following:

 [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; } } 

I have no problem getting the type itself, whether through a simple switch or reflection. However, I need to return the type as dynamic, since I do not know what type of DbSet it will be. Then, elsewhere in the same assembly, I use it as follows:

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

At this point, model contains an instance of type InternalDbSet<ModelA> . From there, any use that I use with the model object, I get a RunTimeBinderException: "Microsoft.Data.Entity.Internal.InternalDbSet" does not contain a definition for "FirstOrDefault"

Researching on the Internet I found a blog post explaining that (dixit his blog):

the reason the call to FirstOrDefault () fails is because the type of model information is not available at run time. The reason for this is not accessible because anonymous types are not publicly available. When a method returns an instance of this anonymous type, it returns a System.Object that refers to an instance of the anonymous type — a type whose information is not available to the main program.

And then he indicates that the solution:

The solution is actually quite simple. All we need to do is open the AssemplyInfo.cs of the ClassLibrary1 project and add the following line to it: [assembly:InternalsVisibleTo("assembly-name")]

I tried this solution on my code, but it does not work. For information, I have an asp.net 5 solution with two assemblies running on dnx dotnet46. Application and DLL containing all my models and DbContext. However, all calls related to this are on the dll.

Does this solution have the ability to work? Am I missing something? Any pointers would be greatly appreciated?

Thank you in advance

[EDIT]

I tried to return an IQueryable<dynamic> rather than dynamic , and I could execute the main query model.FirstOrDefault(); , but first of all, I would like to filter the field as well:

 var record = model.FirstOrDefault(item => item.MyProperty == true); 
+5
source share
2 answers

* Disclaimer: This answer does not give a strict answer to my question. It is rather a different approach to solving my own problem. I know this is a concrete example for a specific situation that will not work for everyone. I post this approach in the hope that it will help someone, but will not mark it as an answer, since I still hope for a real solution.

To begin with, let me accept the fact that the only useful information we can get from the current code is whether a record exists or not. Any attempt at dynamic queries after this will throw a RuntimeBinderException.

Then we continue with another fact; DbContext.Add (object) and DbContext.Update (object) are not template-based, so we can use them to save our models (instead of db.A.Add () or db.A.Update ())

In my own situation, it is no longer necessary to develop a procedure

  • Define models a little differently

First I need a field that can be restored in all my models, which obviously should be a way to identify a unique record.

 // 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. Reuse dynamic queries to do something we know.

I did not find a way to smartly query dynamically, so instead I know that I can reliably identify the record and know if it exists or not.

 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 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. A little plumbing to give meaning to my situation.

Further, what I needed to do with these dynamic queries was a way to replicate data from the server to my client. (To simplify this example, I skipped most of the architecture)

 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. Connection bits.

Now, here is the procedure that connects the bits

 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: I'm still interested in the real answer, so please continue to post for another answer, if you have a real one to share.

0
source

So, how did I do this when at compile time I didn’t know <T> .

First you need to get the type, because the DbContext.Set method returns a non-shared instance of DbSet for accessing objects of this type in the context and in the underlying storage.

 public virtual DbSet Set(Type entityType) 

Note. Here, the argument is the type of object for which the collection should be returned. And for this type of object, this is the return value.

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

now that i have this type

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

Or one liner will be

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

Source: https://habr.com/ru/post/1236909/


All Articles