I need to create a generic method to fetch some data related to another entity.
This is how my code looks like at the moment:
public static void InvalidateData<TEntity>(string foreignKey, int valueFK)
{
try
{
var key = typeof(TEntity).Name;
var adapter = (IObjectContextAdapter)EntityModelDataProvider.Database;
var objectContext = adapter.ObjectContext;
var container = objectContext.MetadataWorkspace.GetEntityContainer(
objectContext.DefaultContainerName, System.Data.Entity.Core.Metadata.Edm.DataSpace.CSpace);
var name = container.BaseEntitySets.Where((s) => s.ElementType.Name.Equals(key)).FirstOrDefault().Name;
string sqlQuery = String.Format(#"SELECT VALUE entity FROM [VW_{0}] WHERE entity.{1} = #foreignkey", name, foreignKey);
var query = objectContext.CreateQuery<TEntity>(sqlQuery, new ObjectParameter("foreignkey", valueFK));
var tmpResult = query.ToList();
}
catch (Exception)
{
throw;
}
}
And the error:
'entity.Average_FK' could not be resolved in the current scope or context. Make sure that all referenced variables are in scope, that required schemas are loaded, and that namespaces are referenced correctly. Near member access expression, line 1, column 58.
Generated query:
SELECT VALUE entity FROM [VW_Average_Client] WHERE entity.Average_FK = #foreignkey
I checked the Properties of the Entity and I do have one called "Average_FK".
Any idea how to achieve my goal?
UPDATE
I am trying to implement a generic where like this:
Expression<Func<TEntity, bool>> customFilter = entity => ComparePropertyValue<TEntity>(entity, foreignKey, valueFK);
var query = objectContext.CreateQuery<TEntity>("[" + name + "]").Where(customFilter);
....
private static bool ComparePropertyValue<TEntity>(TEntity entity, string property, object value)
{
try
{
PropertyInfo propertyInfo = typeof(TEntity).GetProperty(property);
return propertyInfo.GetValue(entity) == value;
}
catch (Exception)
{
throw;
}
}
And this exception:
LINQ to Entities does not recognize the method 'Boolean ComparePropertyValue[VW_Average_Client](XXX.EntityModel.VW_Average_Client, System.String, System.Object)' method, and this method cannot be translated into a store expression.
Finally I found a solution.
(In case someone wants to post a better/cleaner answer I will set yours as the valid one)
public static void InvalidateDataGeneric<TEntity>(string foreignKey, int valueFK)
{
try
{
var key = typeof(TEntity).Name;
var adapter = (IObjectContextAdapter)EntityModelDataProvider.Database;
var objectContext = adapter.ObjectContext;
var container = objectContext.MetadataWorkspace.GetEntityContainer(
objectContext.DefaultContainerName, System.Data.Entity.Core.Metadata.Edm.DataSpace.CSpace);
var name = container.BaseEntitySets.Where((s) => s.ElementType.Name.Equals(key)).FirstOrDefault().Name;
Func<TEntity, bool> filter = algo => ComparePropertyValue<TEntity>(algo, foreignKey, valueFK);
var query = objectContext.CreateQuery<TEntity>("[" + name + "]").Where(filter);
foreach (var element in query)
{
// whatever
}
objectContext.SaveChanges();
}
catch (Exception)
{
throw;
}
}
private static bool ComparePropertyValue<TEntity>(TEntity entity, string property, object value)
{
try
{
PropertyInfo propertyInfo = typeof(TEntity).GetProperty(property);
return propertyInfo.GetValue(entity).Equals(value);
}
catch (Exception)
{
throw;
}
}
The place where entity is used is wrong, it should be:
string sqlQuery = String.Format(#"SELECT VALUE FROM [VW_{0}] entity WHERE entity.{1} = #foreignkey", name, foreignKey);
or even omit entity:
string sqlQuery = String.Format(#"SELECT VALUE FROM [VW_{0}] WHERE {1} = #foreignkey", name, foreignKey);
(note - I agree with the comment above that for several reasons this is not a recommended approach; there are better & cleaner solutions to achieve the same. But that would not answer the question...)
Related
I'm new to redis. I was doing get all keys and iterating key and get the value.
Here is the code:
var keys = Connection.GetServer("localhost", 6379).Keys();
foreach (var key in keys)
{
try
{
var result = db.StringGet(key);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Got exception as
StackExchange.Redis.RedisServerException: 'WRONGTYPE Operation against a key holding the wrong kind of value'
db.StringGet(key) method returns only string. I was looking for dynamic redisValueType. kindof
So, Is there any method to identify the RedisValue type ? To get the value based on the type?
Please help me out!
When you call this method :
var keys = Connection.GetServer("localhost", 6379).Keys();
You will get IEnamarable<RedisKey>, So use key.ToString() like
string[] keysArr = keys.Select(key => key.ToString()).ToArray();
to cast RedisKey to string
db.StringGet Method want string as parameter, So you have to get string to this method.
This method will return you string also you have to Deserilize it to your model use the code below :
var json = db.GetString(key);
if (string.IsNullOrEmpty(json))
return default;
var item = JsonConvert.DeserializeObject<T>(json);
T is the your model in generic so when you want to call this method you can use it like this:
public T TryGetItem<T>(string key)
{
var json = db.GetString(key);
if (string.IsNullOrEmpty(json))
return default;
var item = JsonConvert.DeserializeObject<T>(json);
return item;
}
Call it like below:
var result = TryGetItem(key);
I am using asp.net mvc, dapper and MySql stored procedures for my web app.
So far, for each page I have 1-3 stored procedures calls.
I recently found out my hosting only provides 10 parallel connections and so I want to combine my calls into one per page.
I already made the necessary stored procedures in the database, but my problem is using dapper in generic way to get the procedures results.
What I want is to make a generic function that will get:
1) Stored Procedure name.
2) List of types the stored procedure returns. And for each type if it is Single/ToList.
The generic function should result a list of variablese that are the results from the stored procedure.
The example in dapper page shows how to make a non-generic QueryMultiple call:
var sql =
#"
select * from Customers where CustomerId = #id
select * from Orders where CustomerId = #id
select * from Returns where CustomerId = #id";
using (var multi = connection.QueryMultiple(sql, new {id=selectedId}))
{
var customer = multi.Read<Customer>().Single();
var orders = multi.Read<Order>().ToList();
var returns = multi.Read<Return>().ToList();
...
}
Basicly, what I want is to insert the Customer,Order,Return types into a dictionary that will state each of them to be Single or ToList, and then do something like:
var result = new List<dynamic>();
var sql =
#"
select * from Customers where CustomerId = #id
select * from Orders where CustomerId = #id
select * from Returns where CustomerId = #id";
using (var multi = connection.QueryMultiple(sql, new {id=selectedId}))
{
foreach(var item in dictionary)
{
if (item.Value.equals("Single"))
result.Add(multi.Read<item.Key>().Single());
else if (item.Value.equals("ToList"))
result.Add(multi.Read<item.Key>().ToList());
}
}
return result;
My problem is, visual studio says:
The type or namespace name 'item' could not be found
Pointing at 'item' in: multi.Read()
What am I doing wrong?
Is there a better way to implement a generic function that uses dapper's QueryMultiple?
It's an old topic though but thought it might help others. In that case you can use below code to make it work. Pass type in Read function as parameter. You can return dynamic. Using Expando Object you can generate properties dynamically.
I would prefer to create object like below and pass list of object to function which will generate dynamic return type for you.
public class MapItem
{
public Type Type { get; private set; }
public DataRetriveTypeEnum DataRetriveType { get; private set; }
public string PropertyName { get; private set; }
public MapItem(Type type, DataRetriveTypeEnum dataRetriveType, string propertyName)
{
Type = type;
DataRetriveType = dataRetriveType;
PropertyName = propertyName;
}
}
//And then make a reusable function like below
public async Task<dynamic> ExecuteQueryMultipleAsync(IDbConnection con, string spName, object param = null,IEnumerable<MapItem> mapItems = null)
{
var data = new ExpandoObject();
using (var multi = await con.QueryMultipleAsync(spName,param, commandType:CommandType.StoredProcedure))
{
if (mapItems == null) return data;
foreach (var item in mapItems)
{
if (item.DataRetriveType == DataRetriveTypeEnum.FirstOrDefault)
{
var singleItem = multi.Read(item.Type).FirstOrDefault();
((IDictionary<string, object>) data).Add(item.PropertyName, singleItem);
}
if (item.DataRetriveType == DataRetriveTypeEnum.List)
{
var listItem = multi.Read(item.Type).ToList();
((IDictionary<string, object>)data).Add(item.PropertyName, listItem);
}
}
return data;
}
}
Below how you call the above method:
var mapItems = new List<MapItem>()
{
new MapItem(typeof(YourObject1), DataRetriveTypeEnum.FirstOrDefault, "Object1"),
new MapItem(typeof(YourObject2), DataRetriveTypeEnum.List, "Object2")
};
var response = await ExecuteQueryMultipleAsync(name, request, mapItems);
var object1 = response.Result.Object1;
var listOfObject2 = ((List<dynamic>)response.Result.Object2).Cast<YourObject2>();
Hope this helps.
Cheers
I think an alternative approach could be instead of QueryMultiple() method, you can make use of
IDbConnection.ExecuteReader()
extension method from Dapper.SqlMapper which returns IDataReader and you can loop through it and get the result sets by reader.NextResult() and map them to your object.
I'm using Sqlite-Net ORM and would like to delete all rows from a table that match a specific condition, like:
conn.Table<MyEntity>().Delete(t => t.someProperty == someValue);
There are deletion methods that take either an entity, or a primary key.
I can now of course get all entities that match my condition and then call delete for each entity individually but that feels wrong.
Currently I'm using
conn.Execute("DELETE FROM MyEntitiy...")
But I don't like this version. If I rename my class, the hardcoded SQL won't work anymore.
Are there other options I miss out on?
I have had a look through the source and do not see anywhere where you can use a predicate to delete. However the following extension method should provide the functionality that you are looking for:
using System.Linq.Expressions;
using SQLite;
using System.Collections.Generic;
public static class DeleteExtensions
{
public static int Delete<T>(this TableQuery<T> tableQuery, Expression<Func<T, bool>> predExpr)
{
BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
Type type = tableQuery.GetType();
MethodInfo method = type.GetMethod("CompileExpr", flags);
if (predExpr.NodeType == ExpressionType.Lambda) {
var lambda = (LambdaExpression)predExpr;
var pred = lambda.Body;
var args = new List<object> ();
var w = method.Invoke(tableQuery, new object[] {pred, args});
var compileResultType = w.GetType();
var prop = compileResultType.GetProperty("CommandText");
string commandText = prop.GetValue(w, null).ToString();
var cmdText = "delete from \"" + tableQuery.Table.TableName + "\"";
cmdText += " where " + commandText;
var command = tableQuery.Connection.CreateCommand (cmdText, args.ToArray ());
int result = command.ExecuteNonQuery();
return result;
} else {
throw new NotSupportedException ("Must be a predicate");
}
}
}
However, I am not sure how predictable this would be by using the TableQuery class and I assume thats why you are currently forced to do the delete and updates manually. So test and use at your own risk :)
If you just want to avoid hard-coding in the class name you can use conn.Execute("DELETE FROM " + typeof(T).Name + "...").
I need to figure out if it is possible to dynamically build a query with LINQ, dynamically selecting the table in which to perform the query.
This is an example of what I would do:
//Not working,just for example
public List<dynamic> _getGenericList(String tableName)
{
var l = from a in db.//I need to use here tableName
select a;
return l.ToList<dynamic>();
}
Is there a way to make this possible?
If the query is this simple you can dynamically create a standard sql statement and execute it, this is the most simplest way without using processor heavy reflection and complex code?
var query = "SELECT * FROM " + tableName;
var res = context.ExecuteQuery<dynamic>(query).ToList();
I've found a way to do it, but I'm not sure if I'd use this code. If you have a DataContext that contains two tables:
PrimaryTable
ID,
FirstValue,
SecondValue
SecondaryTable
ID,
FirstSecondaryValue
You could use the following DataHelper class:
class DataHelper
{
public MyDatabaseDataContext db = new MyDatabaseDataContext();
List<dynamic> GetDynamicList<T>() where T : class
{
System.Data.Linq.Table<T> table = db.GetTable<T>();
var result = from a in table select a;
return result.ToList<dynamic>();
}
public List<dynamic> GetWhatIWant(string tableName)
{
Type myClass = Type.GetType("DynamicLinqToSql." + tableName);
MethodInfo method = typeof(DataHelper).GetMethod("GetDynamicList", BindingFlags.NonPublic | BindingFlags.Instance);
method = method.MakeGenericMethod(myClass);
return (List<dynamic>)method.Invoke(this, null);
}
}
Then you can create an instance of your DataHelper and call the GetWhatIWant method, passing in the table name.
var dataHelper = new DataHelper();
List<dynamic> myFirstList = dataHelper.GetWhatIWant("PrimaryTable");
for (int i = 0; i < 5 && i < myFirstList.Count; i++)
{
System.Console.WriteLine(String.Format("{0} - {1}", myFirstList[i].FirstValue.ToString(), myFirstList[i].SecondValue.ToString()));
}
List<dynamic> mySecondList = dataHelper.GetWhatIWant("SecondaryTable");
for (int i = 0; i < 5 && i < mySecondList.Count; i++)
{
System.Console.WriteLine(mySecondList[i].FirstSecondaryValue.ToString());
}
System.Console.ReadKey();
I know this is old, but if you are here looking for answers like I was, then maybe this will help. I'm using a .NET ObjectContext directly instead of a DataContext data source. If you are using the DataContext version then you can simply (I hope) use queryResults = myGlobalContext.ExecuteQuery<dbGenericData>(query).ToList(); instead and I'm pretty sure it will work the same way.
Your tables will be a lot easier to work with if you have standards in naming and design like
the ID field for the table is always X type (INT, GUID, etc)
the ID field is always named tableNameID, the 'table name' with ID tagged on.
etc,
This will allow you to easily build the ID field by simply appending the 'ID' string onto the table name and will allow you to use a reliable CAST, if needed.
Speaking of CAST you will notice one in the query string. You will need to modify the use of the SQL string using CAST, changing field lengths like my nvarChar(50) example, etc, to overcome getting various TYPES of data from your database.
Final note: In the query string you will see I use the 'AS' key word to cast the DB field to a new name. I cast the 'tableIDField' into the name 'id' and I cast the 'requestedField' into the name 'dbData'. This allows the system to match up the renamed fields from the DB into the STRUCT object container we dump the data into. This allows you to construct generic containers to hold the data returned without having to worry about matching up with the DB field names.
I'm not a guru at this stuff, but I hope this helps somebody out.
private void testMethod(string requestedField, string tableName)
{
var tableIDField = tableName + "ID";
var query = "select " + tableIDField + " as id, CAST(" + requestedField + "as nvarchar(50)) as dbData from " + tableName;
List<dbGenericData> queryResults = null;
try
{
queryResults = myGlobalContext.ExecuteStoreQuery<dbGenericData>(query).ToList();
}
catch (Exception ex)
{
//Simply ignore any exceptions.
//These will need examined to determine best solution to unexpected results.
}
}
private struct dbGenericData
{
public dbGenericData(int id, string dbData)
{
this = new dbGenericData();
ID = id;
DBData = dbData;
}
public int ID { get; set; }
public string DBData { get; set; }
}
you can Generic Method and use db.Set<T> that return a DbSet Based on T
var esql = "select t from TypeName as t"
var q = db.CreateQuery(esql);
Use entity sql for linq to sql, http://esql.codeplex.com
My question is if I can predict or choose the exact order of the columns the sql generation through ToTraceString() returns.
I use ToTraceString() for an IQueryable to get the resulted SQL command and then insert the results directly in a database table.
So, I kind of need the generated SQL to be consistent with my table structure...
string insertQuery = string.Format("INSERT INTO {0} {1}", sqlTableName ((System.Data.Objects.ObjectQuery<TRow>)results).ToTraceString());
Context.ExecuteStoreCommand(string.Format("TRUNCATE TABLE {0}", sqlTableName));
Context.ExecuteStoreCommand(insertQuery);
results = IQueryable<Row> where Row is a type with the same properties as the table columns
I choose to do a direct insert into a table because i see no point in getting a ToList() enumerable on the webserver, just to send it back to SQL through some kind of bulk insert (which EF doesn;t support for the moment...) My query returns a considerable amount of rows and I do not want to use Stored Procedures.
Hope I make sense...thanks
I had this issue but the answer here still required a fair amount of work to get going. I used the part of How does Entity Framework manage mapping query result to anonymous type? to get the order and returned the names, then a simple parsing to extract the field names.
I made an extension method that pulls everything together:
public static string ToWrappedString(this ObjectQuery query, out ObjectParameterCollection parameters)
{
var trace = query.ToTraceString();
parameters = query.Parameters;
var positions = query.GetPropertyPositions();
// the query should be SELECT\n
// Column AS NNN
// FROM
// so we regex this out
var regex = new Regex("^SELECT(?<columns>.*?)FROM", RegexOptions.Multiline);
var result = regex.Match(trace.Replace(Environment.NewLine, ""));
var cols = result.Groups["columns"];
// then we have the columns so split to get each
const string As = " AS ";
var colNames = cols.Value.Split(',').Select(a => a.Substring(a.IndexOf(As, StringComparison.InvariantCulture) + As.Length)).ToArray();
var wrapped = "SELECT " + String.Join(Environment.NewLine + ", ", colNames.Select((a, i) => string.Format("{0}{1} [{2}]", a, As, positions[i]))) + " FROM (" + trace
+ ") WrappedQuery ";
return wrapped;
}
This is the code from the other link, updated to EF6 internals and returning the name in column order rather than the indexes.
public static string[] GetPropertyPositions(this ObjectQuery query)
{
// get private ObjectQueryState ObjectQuery._state;
// of actual type internal class
// System.Data.Objects.ELinq.ELinqQueryState
object queryState = GetProperty(query, "QueryState");
AssertNonNullAndOfType(queryState, "System.Data.Entity.Core.Objects.ELinq.ELinqQueryState");
// get protected ObjectQueryExecutionPlan ObjectQueryState._cachedPlan;
// of actual type internal sealed class
// System.Data.Objects.Internal.ObjectQueryExecutionPlan
object plan = GetField(queryState, "_cachedPlan");
AssertNonNullAndOfType(plan, "System.Data.Entity.Core.Objects.Internal.ObjectQueryExecutionPlan");
// get internal readonly DbCommandDefinition ObjectQueryExecutionPlan.CommandDefinition;
// of actual type internal sealed class
// System.Data.EntityClient.EntityCommandDefinition
object commandDefinition = GetField(plan, "CommandDefinition");
AssertNonNullAndOfType(commandDefinition, "System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition");
// get private readonly IColumnMapGenerator EntityCommandDefinition._columnMapGenerator;
// of actual type private sealed class
// System.Data.EntityClient.EntityCommandDefinition.ConstantColumnMapGenerator
var columnMapGeneratorArray = GetField(commandDefinition, "_columnMapGenerators") as object[];
AssertNonNullAndOfType(columnMapGeneratorArray, "System.Data.Entity.Core.EntityClient.Internal.EntityCommandDefinition+IColumnMapGenerator[]");
var columnMapGenerator = columnMapGeneratorArray[0];
// get private readonly ColumnMap ConstantColumnMapGenerator._columnMap;
// of actual type internal class
// System.Data.Query.InternalTrees.SimpleCollectionColumnMap
object columnMap = GetField(columnMapGenerator, "_columnMap");
AssertNonNullAndOfType(columnMap, "System.Data.Entity.Core.Query.InternalTrees.SimpleCollectionColumnMap");
// get internal ColumnMap CollectionColumnMap.Element;
// of actual type internal class
// System.Data.Query.InternalTrees.RecordColumnMap
object columnMapElement = GetProperty(columnMap, "Element");
AssertNonNullAndOfType(columnMapElement, "System.Data.Entity.Core.Query.InternalTrees.RecordColumnMap");
// get internal ColumnMap[] StructuredColumnMap.Properties;
// array of internal abstract class
// System.Data.Query.InternalTrees.ColumnMap
Array columnMapProperties = GetProperty(columnMapElement, "Properties") as Array;
AssertNonNullAndOfType(columnMapProperties, "System.Data.Entity.Core.Query.InternalTrees.ColumnMap[]");
int n = columnMapProperties.Length;
string[] propertyPositions = new string[n];
for (int i = 0; i < n; ++i)
{
// get value at index i in array
// of actual type internal class
// System.Data.Query.InternalTrees.ScalarColumnMap
object column = columnMapProperties.GetValue(i);
AssertNonNullAndOfType(column, "System.Data.Entity.Core.Query.InternalTrees.ScalarColumnMap");
string colName = (string)GetProperty(column, "Name");
// can be used for more advanced bingings
// get internal int ScalarColumnMap.ColumnPos;
object columnPositionOfAProperty = GetProperty(column, "ColumnPos");
AssertNonNullAndOfType(columnPositionOfAProperty, "System.Int32");
propertyPositions[(int)columnPositionOfAProperty] = colName;
}
return propertyPositions;
}
static object GetProperty(object obj, string propName)
{
PropertyInfo prop = obj.GetType().GetProperty(propName, BindingFlags.NonPublic | BindingFlags.Instance);
if (prop == null) throw EFChangedException();
return prop.GetValue(obj, new object[0]);
}
static object GetField(object obj, string fieldName)
{
FieldInfo field = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
if (field == null) throw EFChangedException();
return field.GetValue(obj);
}
static void AssertNonNullAndOfType(object obj, string fullName)
{
if (obj == null) throw EFChangedException();
string typeFullName = obj.GetType().FullName;
if (typeFullName != fullName) throw EFChangedException();
}
static InvalidOperationException EFChangedException()
{
return new InvalidOperationException("Entity Framework internals has changed, please review and fix reflection code");
}
The accepted answer to this question contains two links which describe how to figure out what order the various properties of the entity type appear in the SQL generated by ToTraceString(). With that information, you can do some simple parsing/restructuring of the raw SQL to replace the weird column names EF uses (e. g. C1, C2, etc) with the property column names. Then, you could wrap the generated SQL in a subquery that selects the relevant columns in the order you want:
SELECT prop1, prop2
FROM
(
// the result of ToTraceString(), with EF's generated column names replaced by the property names of the query type
) x