I'm wondering, if it's even possible in the first place, how I would go about querying the database (using EF) using an ID and a table name.
For example, writing a function as:
QueryDynamicData(string tableName, long entityID){return GetItem(tableName, entityID);}
And could be called like:
var entry = QueryDynamicData("Person", 143);
To clarify, this is for a MVC ASP.Net project using Entity Frameworks.
Thanks in advance!
EDIT:
Following the example from #JPVenson, I came up with the following code. Note that it returns a list of Dictionaries, even though Id is unique, since I'm thinking ahead to when we may want to get all results for a dynamic table instead of just by Id. (This is only proof of concept level)
public List<Dictionary<string, object>> QueryDynamicData(string table, int entityID)
{
try
{
//Get the table desired based on the table name passed
PropertyInfo dbSetInfo = DBContext.GetType().GetProperties().FirstOrDefault(p => p.Name.ToLower().Equals(table.ToLower()));
//Return all results from the table into IQueryable set where Id = entityID passed
IQueryable anyDbSet = ((IQueryable)dbSetInfo.GetValue(DBContext)).Where("Id=" + entityID);
List<Dictionary<string,object>> listObjects = new List<Dictionary<String, Object>>();
//Iterate through results
foreach (Object entity in anyDbSet)
{
//Create dictionary of Field Name, Field Value from results
Dictionary<string, object> listDBValues = entity.GetType().GetProperties().ToDictionary(propertyInfo => propertyInfo.Name, propertyInfo => propertyInfo.GetValue(entity));
//Add dictionary to list of dictionaries - useful for returning list of found results instead of just one
listObjects.Add(listDBValues);
}
//Return list of dictionaries
return listObjects;
}
catch (Exception e) { }
return null;
}
Yes you can. There is a blog from ScottGu
https://weblogs.asp.net/scottgu/dynamic-linq-part-1-using-the-linq-dynamic-query-library
(MS Version of DynamicLinq https://github.com/kahanu/System.Linq.Dynamic/wiki)
that contains the wiki for a lib called DynamicLinq. I'm using it currently in a Project and it will fit your approach.
You still have to wrap it and use some Reflection to build a proper IQueryable but it does a lot of work for you
Edit Code Example
With some reflection you can access your dbSet like this (Untested Pseudocode!):
public object[] QueryDynamicData(string table, int entityId) {
//Your DbContext that contains all of your
var dbContext = new FooBaa()
//Get the DbSet in your DbContext that matches the "Table" name.
//You are searching for the generic parameter of the DbSet
var dbSetInfo = dbContext.GetType().GetProperties().FirstOrDefault(e => e.GetGenericArguments().Any(f => f.Name.Equals(table));
//Now get the DbSet from the DbContext and cast it to an IQueryabe
IQueryable anyDbSet = (IQueryable)dbSetInfo.GetValue(dbContext);
//Use Dynamic Linq to create a Query that selects an ID
//warning SQL-Injection possible checkout the 2nd argument of type IDictionary
return anyDbSet.Where("Id=" + entityId).ToArray();
}
I had a few minutes to work on this between meetings, and came up with this (add it to your DbContext class:
public dynamic FindEntity(string table, long Id)
{
PropertyInfo prop = this.GetType().GetProperty(table, BindingFlags.Instance | BindingFlags.Public);
dynamic dbSet = prop.GetValue(this, null);
return dbSet.Find(Id);
}
It uses some reflection to find the property on the DbContext with the name of the table, and grabs a reference to it. Then it calls Find on that DbSet<T> to find the object with the specified primary key. Since you aren't sending any actual Type information along, everything has to be dynamically typed.
You can call it like this:
using (var db = new MyContext())
{
var e = db.FindEntity("Animals", 1);
}
I can't vouch for how useful it will be do you, but it does return (dynamically typed) data in my test setup.
I've been struggling for a while with a problem that consists on auditing generically database entities when they're saved. I have a project that uses EF 6 and it was required to me to create a "non-invasive" method to audit entities when they're added, modified or deleted. I have to store a JSON of the inserted entity, modified entity or deleted entity without interfering with the normal flow. The project has a Database First implementation.
My solution was simple, add a partial class of any entity that the rest of the programmers want to audit implementing IAudit which is basically an empty interface to get all changes from entities that implement it.
public interface IAudit {}
I have a Currencies entity that just implement it without any other code (I could do something else in the future but I don't need it)
public partial class Currencies : IAudit
I override the SaveChanges method to look for entities to audit
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
// This linq looks for new entities that were marked for audit
CreateAuditLog(System.Data.Entity.EntityState.Added);
CreateAuditLog(System.Data.Entity.EntityState.Modified);
CreateAuditLog(System.Data.Entity.EntityState.Deleted);
return base.SaveChanges();
}
The solution calls 3 times the CreateAuditLog because in the near future I need to implement a configuration to audit whatever the user decides, might be from a database configuration that is activated/deactivated by users.
Everything worked perfectly, I was able to get saved entities in the specified state:
private void CreateAuditLog(System.Data.Entity.EntityState state)
{
var auditedEntities = ChangeTracker.Entries<IAudit>()
.Where(p => p.State == state)
.Select(p => p.Entity);
... some code that do something else
foreach (var auditedEntity in auditedEntities)
{
... some information I required to add
strJSON = JsonConvert.SerializeObject(auditedEntity, new EFNavigationPropertyConverter());
... some code to save audit information
}
}
The problem is I lose every value in the Deleted state, I only get the ID, there's no information in the properties except the ID and there is no any possibility of extract it in any way. I looked for every single solution in StackOverflow and other websites and there is nothing to recover the original information.
How can I get the previous deleted values to store them in the same way I'm storing Added and Modified entities?
It took me a couple of days to figure it out. Might be the solution is a bit complex but I tried several less complex options with not a good result.
First, as I'm just auditing Delete in a different way I separated Deleted state from Added and Modified that work well with no change. Deleted state is a particular case and I treat it like that.
First, I needed to obtain the original values from the database. In the Deleted state they're gone, there's not any possibility of recovering them from the entity. It's possible to obtain them with the following code:
var databaseValues = this.Entry(auditedEntity).GetDatabaseValues();
The result is just a collection of DB property values (DbPropertyValues). If I can get the original values I set the original values from the deleted entity:
dbEntityEntry.OriginalValues.SetValues(databaseValues);
This line just fills the entity original values, it doesn't modify the current value at all. It's useful to do it that way because it takes some code to check every property and set it ourselves, it's an interesting shortcut.
Now, the problem is I don't have the entity to serialize, so I need a new one which in my case I create by reflection because I don't know the type (I receive entities that implement IAudit)
Type type = auditedEntity.GetType();
var auditDeletedEntity = Activator.CreateInstance(type);
This is the entity I will serialize to store the audit later.
Now, the complex part, I need to get the entity properties and fill them by reflection from the original values set in the entity:
foreach (var propertyInfo in type.GetProperties())
{
if (!propertyInfo.PropertyType.IsArray && !propertyInfo.PropertyType.IsGenericType)
{
var propertyValue = originalValues.GetValue<object>(propertyInfo.Name);
auditDeletedEntity.GetType().InvokeMember(propertyInfo.Name,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
Type.DefaultBinder, auditDeletedEntity, new[] { propertyValue });
}
}
I had to check generic and array types to avoid following EF relations that are not going to work with this method and I also don't need (I need the object not the whole tree)
After that I simply need to serialize the audited deleted entity:
strJSON = JsonConvert.SerializeObject(auditDeletedEntity, new EFNavigationPropertyConverter());
The code looks like this:
string strJSON = string.Empty;
if (state == System.Data.Entity.EntityState.Deleted)
{
var databaseValues = this.Entry(auditedEntity).GetDatabaseValues();
// Get original values from the database (the only option, in the delete method they're lost)
DbEntityEntry dbEntityEntry = this.Entry(auditedEntity);
if (databaseValues != null)
{
dbEntityEntry.OriginalValues.SetValues(databaseValues);
var originalValues = this.Entry(auditedEntity).OriginalValues;
Type type = auditedEntity.GetType();
var auditDeletedEntity = Activator.CreateInstance(type);
// Get properties by reflection
foreach (var propertyInfo in type.GetProperties())
{
if (!propertyInfo.PropertyType.IsArray && !propertyInfo.PropertyType.IsGenericType)
{
var propertyValue = originalValues.GetValue<object>(propertyInfo.Name);
auditDeletedEntity.GetType().InvokeMember(propertyInfo.Name,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty,
Type.DefaultBinder, auditDeletedEntity, new[] { propertyValue });
}
}
strJSON = JsonConvert.SerializeObject(auditDeletedEntity, new EFNavigationPropertyConverter());
}
}
else
{
strJSON = JsonConvert.SerializeObject(auditedEntity, new EFNavigationPropertyConverter());
}
Might be there's a better way but I seriously spent a good amount of time looking for options and I couldn't find anything better.
Any suggestion or optimization is appreciated.
My Scenario
I have a table of items, each of which has a priority and some other information.
I also have a database view, which selects * from that table, but replaces the priorities based on some other attributes of the item
Both the view and the table contain exactly the same fields, with only the content of the priorities changing.
In my code -- names changed to protect the innocent :) -- I have:
[Table("schema.Items")] //The table
public class Item
{
//...all of the fields that exist in both the table and the view.
}
Question
Given this scenario, is there a way for me to pull from the view instead of the table but map it to the same model naturally? If so, how do I do that?
Clarifications:
The "view" I mention is a database view that reads from the table but interprets the priority differently.
So, in this situation, we'd like to just pull from the view.
The database view and database table have the same fields.
I'm trying to find a way to avoid having ClassA and ClassB, both with the same properties, just so I can pull one from the view and the other from the table.
I am referring only to reading data. There is no expectation of an update here.
If I understand you correctly, you can use SqlQuery to load from a query that is generated from view.
var db = ...; // instance of context
var q = db.Set<ViewModel>().Where(...).ToString();
var result = db.Database.SqlQuery<TableModel>(q);
PS
If the view name has a pattern like view_TableName, you can just use TableModel to generate the query then replace the table name with view name.
Here is an extension method that could achieve that.
public static string GetViewSql<T>(this DbContext db, IQueryable<T> q)
where T : class
{
const string prefix = "view_";
var tableName = Regex.Match(
db.Set<T>().ToString(),
#"FROM (\[.*\]\.\[.*\]) AS \[Extent1\]").Groups[1].Value;
var viewName = Regex.Replace(
tableName,
#"\[.*\]\.\[(.*)\]",
m => m.Groups[0].Value.Replace(
m.Groups[1].Value, prefix + m.Groups[1].Value));
var sql = q.ToString().Replace(tableName, viewName);
return sql;
}
Usage:
var query = db.Set<TableModel>().Where(...);
var sql = db.GetViewSql(query);
var result = db.Database.SqlQuery<TableModel>(sql);
I have annotated my Entity Framework Code First objects with some extra metadata, such as the DataTypeAttribute or possibly new, custom attributes. A version of this code (from http://www.minddriven.de/index.php/technology/dot-net/web/asp-net-mvc/check-data-annotations-from-code) works well to read the attributes once I have the EF Code First POCO object's Type object.
However, I cannot figure out how to go from the MetadataWorkspace, where I find all the entities:
ObjectContext objContext = ((IObjectContextAdapter)this).ObjectContext;
MetadataWorkspace mw = objContext.MetadataWorkspace;
var entities = mw.GetItems<EntityType>(DataSpace.OSpace);
to the POCO class Types I need to reflect on the Attributes.
How do I get from an EntityType to the POCO object or its proxy? Or alternatively, how can I find all the POCO objects in the context without GetItems()?
Relevant Links:
ASP.NET MVC Quick Tip: Check Data Annotations from code
How to read Custom Attributes using reflection set by Fluent API in EF 4.1
MSDN link about getting entities from proxies
There might be a direct way to do this but you can get the type from the full name
var types = from entity in entities
select Type.GetType(entity.FullName);
If I'm correct what you want - this should help as it describes a similar problem I posted a while ago:
How check by unit test that properties mark as computed in ORM model?
Also check this other post which kind of summarizes it all:
Get Model schema to programmatically create database using a provider that doesn't support CreateDatabase
(all are earlier posts of mine)
In short, I managed to read most of the information - but it's not
perfect (if I remember right, I cannot check right now) and has some
issues for certain situations, depends on what you need exactly).
If it helps anyone else, this my test harness code for dumping my DbContext POCO proxies from my EF setup (specifically an Effort CSV database):
MyContext ctx = new MyContext();
var metadata = ((IObjectContextAdapter)ctx).ObjectContext.MetadataWorkspace;
var schema = metadata.GetItems(DataSpace.SSpace).ToList();
Dictionary<string, List<string>> tables = new Dictionary<string, List<string>>();
Dictionary<string, System.Data.Entity.Core.Metadata.Edm.EntityType> types = new Dictionary<string, System.Data.Entity.Core.Metadata.Edm.EntityType>();
Dictionary<string, string> table_names = new Dictionary<string, string>();
foreach (var item in schema)
{
if (item.ToString().Contains("CodeFirstDatabaseSchema") && !item.ToString().Contains("_"))
{
string[] tableItem = item.ToString().Split('.');
string name = tableItem[1];
Debug.WriteLine("table_name: " + name);
if (!tables.ContainsKey(name))
{
System.Data.Entity.Core.Metadata.Edm.EntityType entity_type = item as System.Data.Entity.Core.Metadata.Edm.EntityType;
if (entity_type != null)
{
List<string> columns = new List<string>();
var members = entity_type.DeclaredMembers;
foreach (var member in members)
{
columns.Add(member.ToString());
}
Debug.WriteLine("columns:\n" + string.Join(",", columns));
tables.Add(name, columns);
types.Add(name, entity_type);
table_names.Add(name, item.MetadataProperties["TableName"].Value.ToString());
}
}
}
}
foreach (var table_name in tables.Keys)
{
List<string> columns = tables[table_name];
System.Data.Entity.Core.Metadata.Edm.EntityType entity = types[table_name];
string table_csv_name = table_names[table_name] + ".csv";
Debug.WriteLine("table_csv_name: " + table_csv_name);
var assembly_name = AssemblyName.GetAssemblyName("MyDLL.dll");
var proxy = metadata.GetItems<System.Data.Entity.Core.Metadata.Edm.EntityType>(DataSpace.OSpace).Where(v => v.Name == table_name).FirstOrDefault();
Type proxy_type = Type.GetType(proxy.FullName + ", " + assembly_name.FullName);
if (proxy_type != null)
{
var set = ctx.Set(proxy_type);
foreach (var row in set)
{
Debug.WriteLine("row: " + row);
}
}
}
I've then got code that dumps the DbSet objects back out to csv via reflection on the POCO properties. Note the CodeFirstDatabaseSchema literal above is from Effort I think, YMMV
I'm using entity framework to connect with the database. I've one little problem:
I've one table which have one varbinary(MAX) column(with filestream).
I'm using SQL request to manage the "Data" part, but EF for the rest(metadata of the file).
I've one code which has to get all files id, filename, guid, modification date, ... of a file. This doesn't need at all the "Data" field.
Is there a way to retrieve a List but without this column filled?
Something like
context.Files.Where(f=>f.xyz).Exclude(f=>f.Data).ToList();
??
I know I can create anonymous objects, but I need to transmit the result to a method, so no anonymous methods. And I don't want to put this in a list of anonymous type, and then create a list of my non-anonymous type(File).
The goal is to avoid this:
using(RsSolutionsEntities context = new RsSolutionsEntities())
{
var file = context.Files
.Where(f => f.Id == idFile)
.Select(f => new {
f.Id, f.MimeType, f.Size, f.FileName, f.DataType,
f.DateModification, f.FileId
}).FirstOrDefault();
return new File() {
DataType = file.DataType, DateModification = file.DateModification,
FileId = file.FileId, FileName = file.FileName, Id = file.Id,
MimeType = file.MimeType, Size = file.Size
};
}
(I'm using here the anonymous type because otherwise you will get a NotSupportedException: The entity or complex type 'ProjectName.File' cannot be constructed in a LINQ to Entities query.)
(e.g. this code throw the previous exception:
File file2 = context.Files.Where(f => f.Id == idFile)
.Select(f => new File() {Id = f.Id, DataType = f.DataType}).FirstOrDefault();
and "File" is the type I get with a context.Files.ToList(). This is the good class:
using File = MyProjectNamespace.Common.Data.DataModel.File;
File is a known class of my EF datacontext:
public ObjectSet<File> Files
{
get { return _files ?? (_files = CreateObjectSet<File>("Files")); }
}
private ObjectSet<File> _files;
Is there a way to retrieve a List but without this column filled?
Not without projection which you want to avoid. If the column is mapped it is natural part of your entity. Entity without this column is not complete - it is different data set = projection.
I'm using here the anonymous type because otherwise you will get a
NotSupportedException: The entity or complex type 'ProjectName.File'
cannot be constructed in a LINQ to Entities query.
As exception says you cannot project to mapped entity. I mentioned reason above - projection make different data set and EF don't like "partial entities".
Error 16 Error 3023: Problem in mapping fragments starting at line
2717:Column Files.Data in table Files must be mapped: It has no
default value and is not nullable.
It is not enough to delete property from designer. You must open EDMX as XML and delete column from SSDL as well which will make your model very fragile (each update from database will put your column back). If you don't want to map the column you should use database view without the column and map the view instead of the table but you will not be able to insert data.
As a workaround to all your problems use table splitting and separate the problematic binary column to another entity with 1 : 1 relation to your main File entity.
I'd do something like this:
var result = from thing in dbContext.Things
select new Thing {
PropertyA = thing.PropertyA,
Another = thing.Another
// and so on, skipping the VarBinary(MAX) property
};
Where Thing is your entity that EF knows how to materialize. The resulting SQL statement shouldn't include the large column in its result set, since it's not needed in the query.
EDIT: From your edits, you get the error NotSupportedException: The entity or complex type 'ProjectName.File' cannot be constructed in a LINQ to Entities query. because you haven't mapped that class as an entity. You can't include objects in LINQ to Entities queries that EF doesn't know about and expect it to generate appropriate SQL statements.
You can map another type that excludes the VarBinary(MAX) column in its definition or use the code above.
you can do this:
var files = dbContext.Database.SqlQuery<File>("select FileId, DataType, MimeType from Files");
or this:
var files = objectContext.ExecuteStoreQuery<File>("select FileId, DataType, MimeType from Files");
depending on your version of EF
I had this requirement because I have a Document entity which has a Content field with the content of the file, i.e. some 100MB in size, and I have a Search function that I wanted to return the rest of the columns.
I chose to use projection:
IQueryable<Document> results = dbContext.Documents.Include(o => o.UploadedBy).Select(o => new {
Content = (string)null,
ContentType = o.ContentType,
DocumentTypeId = o.DocumentTypeId,
FileName = o.FileName,
Id = o.Id,
// etc. even with related entities here like:
UploadedBy = o.UploadedBy
});
Then my WebApi controller passes this results object to a common Pagination function, which applies a .Skip, .Take and a .ToList.
This means that when the query gets executed, it doesn't access the Content column, so the 100MB data is not being touched, and the query is as fast as you'd want/expect it to be.
Next, I cast it back to my DTO class, which in this case is pretty much exactly the same as the entity class, so this might not be a step you need to implement, but it's follows my typical WebApi coding pattern, so:
var dtos = paginated.Select(o => new DocumentDTO
{
Content = o.Content,
ContentType = o.ContentType,
DocumentTypeId = o.DocumentTypeId,
FileName = o.FileName,
Id = o.Id,
UploadedBy = o.UploadedBy == null ? null : ModelFactory.Create(o.UploadedBy)
});
Then I return the DTO list:
return Ok(dtos);
So it uses projection, which might not fit the original poster's requirements, but if you're using DTO classes, you're converting anyway. You could just as easily do the following to return them as your actual entities:
var dtos = paginated.Select(o => new Document
{
Content = o.Content,
ContentType = o.ContentType,
DocumentTypeId = o.DocumentTypeId,
//...
Just a few extra steps but this is working nicely for me.
For EF Core 2
I implemented a solution like this:
var files = context.Files.AsNoTracking()
.IgnoreProperty(f => f.Report)
.ToList();
The base idea is to turn for example this query:
SELECT [f].[Id], [f].[Report], [f].[CreationDate]
FROM [File] AS [f]
into this:
SELECT [f].[Id], '' as [Report], [f].[CreationDate]
FROM [File] AS [f]
you can see the full source code in here:
https://github.com/aspnet/EntityFrameworkCore/issues/1387#issuecomment-495630292
I'd like to share my attempts to workaround this problem in case somebody else is in the same situation.
I started with what Jeremy Danyow suggested, which to me is the less painful option.
// You need to include all fields in the query, just make null the ones you don't want.
var results = context.Database.SqlQuery<myEntity>("SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName");
In my case, I needed a IQueryable<> result object so I added AsQueryable() at the end. This of course let me add calls to .Where, .Take, and the other commands we all know, and they worked fine. But there's a caveat:
The normal code (basically context.myEntity.AsQueryable()) returned a System.Data.Entity.DbSet<Data.DataModel.myEntity>, while this approach returned System.Linq.EnumerableQuery<Data.DataModel.myEntity>.
Apparently this means that my custom query gets executed "as is" as soon as needed and the filtering I added later is done afterwards and not in the database.
Therefore I tried to mimic Entity Framework's object by using the exact query EF creates, even with those [Extent1] aliases, but it didn't work. When analyzing the resulting object, its query ended like
FROM [dbo].[TableName] AS [Extent1].Where(c => ...
instead of the expected
FROM [dbo].[TableName] AS [Extent1] WHERE ([Extent1]...
Anyway, this works, and as long as the table is not huge, this method will be fast enough. Otherwise you have no option than to manually add the conditions by concatenating strings, like classic dynamic SQL. A very basic example in case you don't know what I'm talking about:
string query = "SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName";
if (parameterId.HasValue)
query += " WHERE Field1 = " + parameterId.Value.ToString();
var results = context.Database.SqlQuery<myEntity>(query);
In case your method sometimes needs this field, you can add a bool parameter and then do something like this:
IQueryable<myEntity> results;
if (excludeBigData)
results = context.Database.SqlQuery<myEntity>("SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName").AsQueryable();
else
results = context.myEntity.AsQueryable();
If anyone manages to make the Linq extensions work properly like if it was the original EF object, please comment so I can update the answer.
I'm using here the anonymous type because otherwise you will get a
NotSupportedException: The entity or complex type 'ProjectName.File'
cannot be constructed in a LINQ to Entities query.
var file = context.Files
.Where(f => f.Id == idFile)
.FirstOrDefault() // You need to exeucte the query if you want to reuse the type
.Select(f => new {
f.Id, f.MimeType, f.Size, f.FileName, f.DataType,
f.DateModification, f.FileId
}).FirstOrDefault();
And also its not a bad practice to de-normalize the table into further, i.e one with metadata and one with payload to avoid projection. Projection would work, the only issue is, need to edit any time a new column is added to the table.
I tried this:
From the edmx diagram (EF 6), I clicked the column I wanted to hide from EF and on their properties you can set their getter and setter to private. That way, for me it works.
I return some data which includes a User reference, so I wanted to hide the Password field even though it's encrypted and salted, I just didn't want it on my json, and I didn't want to do a:
Select(col => new {})
because that's a pain to create and maintain, especially for big tables with a lot of relationships.
The downside with this method is that if you ever regenerate your model, you would need to modify their getter and setter again.
Using Entity Framework Power Tools you can do the following in efpt.config.json:
"Tables": [
{
"ExcludedColumns": [
"FileData"
],
"Name": "[dbo].[Attachment]",
"ObjectType": 0
}
]