I'd like to assign schema name for my entity, without specifying table name. Now, I can do only: modelBuilder.Entity<T>().ToTable("MyEntities", "myschema"); is there a way to do something like: modelBuilder.Entity<T>().ToTable("myschema") ? Please, take into account that I cannot use PluralizationService and calculate tablename manually, since PluralizationService become internal...
How about...
var t = typeof (T);
var name= t.Name;
modelBuilder.Entity<T>().ToTable(name, "myschema")
If you need the DbSet plural name from the context
public DbSet<Single> Plural{ get; set; }
Then this little extension can be reworked to return the value you want. A combo of both without loop. But im sure you will find the right variation...
public static class BosDalExtensions
{
public static List<string> GetModelNames(this DbContext context ) {
var model = new List<string>();
var propList = context.GetType().GetProperties();
foreach (var propertyInfo in propList)
{
if (propertyInfo.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
{
model.Add(propertyInfo.Name);
}
}
return model;
}
public static List<string> GetModelTypes(this DbContext context)
{
var model = new List<string>();
var propList = context.GetType().GetProperties();
foreach (var propertyInfo in propList)
{
if (propertyInfo.PropertyType.GetTypeInfo().Name.StartsWith("DbSet" ))
{
model.Add(propertyInfo.PropertyType.GenericTypeArguments[0].Name);
}
}
return model;
}
}
Related
I want to build an expression for IQueryable GroupBy. While at the moment I'm just simplifying the problem to try and get it working, the eventual final implementation will involve the creation of quite complex expression trees so I want to build a complete expression that can then be integrated into other expressions.
I specifically want to build an expression of this overload:
public static System.Linq.IQueryable<TResult> GroupBy<TSource,TKey,TResult> (
this System.Linq.IQueryable<TSource> source,
System.Linq.Expressions.Expression<Func<TSource,TKey>> keySelector,
System.Linq.Expressions.Expression<Func<TKey,System.Collections.Generic.IEnumerable<TSource>,TResult>> resultSelector);
... my problem is in the implementation of the resultSelector and and the IEnumerable<TSource>.
I have a table of Customers (just dummy data for the purposes of working out this problem). This is stored in an SQL DB and I specifically want to use IQueryable to access the data.
public class Customer
{
public int Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public int Age { get; set; }
}
I also have a GroupResult class used to hold the results of the GroupBy (I have different constructors which I've been using in my testing to work out where my problem is occurring)
internal class GroupResult
{
public string? Name { get; set; }
public int NumRecords { get; set; }
public decimal AverageAge { get; set; }
public int TotalAge { get; set; }
public GroupResult() { }
public GroupResult(string name)
{
Name = name;
}
public GroupResult(IEnumerable<Customer> customers)
{
Name = Guid.NewGuid().ToString();
NumRecords = customers.Count();
}
public GroupResult(string name, IEnumerable<Customer> customers)
{
Name = name;
NumRecords = customers.Count();
}
}
The main static class that displays prompts to select column to group on, creates the relevant expression tree and executes it
internal static class SimpleGroupByCustomer
{
internal static DataContext db;
internal static void Execute()
{
using (db = new DataContext())
{
//get input
Console.WriteLine();
Console.WriteLine("Simple Customer GroupBy");
Console.WriteLine("=======================");
Console.WriteLine("Simple GroupBy on the Customer Table");
Console.WriteLine();
Console.WriteLine("Select the property that you want to group by.");
Console.WriteLine();
var dbSet = db.Set<Customer>();
var query = dbSet.AsQueryable();
//for this example we're just prompting for a column in the customer table
//GetColumnName is a helper function that lists the available columns and allows
//one to be selected
string colName = Wrapper.GetColumnName("Customer");
MethodInfo? method = typeof(SimpleGroupByCustomer).GetMethod("GetGroupBy",
BindingFlags.Static | BindingFlags.NonPublic);
if (method != null)
{
method = method.MakeGenericMethod(new Type[] { typeof(String), query.ElementType });
method.Invoke(null, new object[] { query, colName });
}
}
}
internal static void GetGroupBy<T, TTable>(IQueryable query, string colName)
{
Type TTmp = typeof(TTable);
var param = Expression.Parameter(TTmp, "c");
var prop = Expression.PropertyOrField(param, colName);
LambdaExpression keySelector = Expression.Lambda<Func<TTable, T>>(prop, param);
var param1 = Expression.Parameter(typeof(T), "Key");
var param2 = Expression.Parameter(typeof(IEnumerable<TTable>), "Customers");
var ci = typeof(GroupResult).GetConstructor(new[] { typeof(T), typeof(IEnumerable<TTable>) });
//var ci = typeof(GroupResult).GetConstructor(new[] { typeof(T) });
//var ci = typeof(GroupResult).GetConstructor(new[] { typeof(IEnumerable<TTable>) });
if (ci == null)
return;
var pExp = new ParameterExpression[] { param1, param2 };
var methodExpression = Expression.Lambda<Func<T, IEnumerable<TTable>, GroupResult>>(
Expression.New(ci, new Expression[] { param1, param2 }), //<--- ERROR HERE
pExp
);
Type[] typeArgs = new Type[] { typeof(TTable), typeof(T), typeof(GroupResult) };
Expression[] methodParams = new Expression[] { query.Expression, keySelector, methodExpression };
var resultExpression = Expression.Call(typeof(Queryable), "GroupBy", typeArgs, methodParams);
IQueryable dbQuery = query.Provider.CreateQuery(resultExpression);
if (dbQuery is IQueryable<GroupResult> results)
{
foreach (var result in results)
{
Console.WriteLine("{0,-15}\t{1}", result.Name, result.NumRecords.ToString());
}
}
}
}
When I run this and try and iterate through the results I get the following exception:
System.InvalidOperationException: 'variable 'Customers' of type 'System.Collections.Generic.IEnumerable`1[ExpressionTrees3.Data.Customer]' referenced from scope '', but it is not defined'
which is being caused by the param2 ParameterExpression marked above.
If I use the GroupResult constructor that just takes the key value
var ci = typeof(GroupResult).GetConstructor(new[] { typeof(T) });
and omit the param2 from the Lambda body definition the code works as expected and I get a collection of GroupResult records containing the distinct key values in the Name field (but obviously no summary value).
I've tried everything I can think of and just can't get past this error - it's as though the GroupBy is not actually producing the IEnumerable grouping of Customers for each key.
I suspect I'm missing something really obvious here, but just can't see it. Any help would really very much appreciated.
Please note that I am after answers to this specific issue, I'm not looking for alternative ways of doing a GroupBy (unless there's a fundamental reason why this shouldn't work) - this will be rolled into a much larger solution for building queries and I want to use the same process throughout.
Thanks Svyatoslav - as I thought, it was me being especially dumb!
Your comments, as well as a discussion with a friend who has a lot SQL knowledge pointed me in the right direction.
I had been thinking that the GroupBy expression was going to return an Enumerable for each key value and was trying to pass that into a function ... it always felt wrong, but I just ignored that and kept going.
It's obvious now that I need to tell the GroupBy what to calculate and return (i.e. your comment about aggregation).
So for this easy example, the solution is very simple:
var pExp = new ParameterExpression[] { param1, param2 };
var countTypes = new Type[] { typeof(TTable) };
var countParams = new Expression[] { param2 };
var countExp = Expression.Call(typeof(Enumerable), "Count", countTypes, countParams);
var methodExpression = Expression.Lambda<Func<T, IEnumerable<TTable>, GroupResult>>(
Expression.New(ci, new Expression[] { param1, countExp }),
pExp
);
Just by adding the 'Count' expression into the GroupBy method call it works!
.. and adding a new ctor for GroupResult:
public GroupResult(string name, int count)
{
Name = name;
NumRecords = count;
}
(yep, I feel a bit stupid!)
A lot of code examples use either named parameters or execute stored procedures, but not both. How do I do so when I don't have a pre-defined entity type being selected by the stored proc? (Which means that .FromSqlRaw is out.)
The code below allows you to call a stored procedure and generate a list of named parameters, just from the list of SqlParameters.
var sqlParams = new SqlParameter[] {
new SqlParameter("p1", valOfP1),
new SqlParameter("p2", valOfP2),
new SqlParameter("pOut", SqlDbType.Int)
{
Direction = System.Data.ParameterDirection.Output
}
};
// OK to use the same names as for the SqlParameter identifiers. Does not interfere.
var sql = "myStoredProc " + String.Join(", ", sqlParams.Select(x =>
$"#{x.ParameterName} = #{x.ParameterName}" +
(x.Direction == ParameterDirection.Output ? " OUT" : "")
));
myDbContext.Database.ExecuteSqlRaw(sql, sqlParams);
var outputId = (int)(sqlParams.First(p => p.Direction == ParameterDirection.Output).Value);
Try Below example code which lets you call sp and store result in a
datatable.
using (var command = db.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "sp_name";
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add(new SqlParameter("key", "Value"));
db.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
var dataTable = new DataTable();
dataTable.Load(result);
return dataTable;
}
}
Here Product is class where you can define property whatever you want to retrieve from procedure
public class DataBaseContext : DbContext
{
public DataBaseContext() : base("name=DataBaseContext")
{
}
public DbSet<Product> Products { get; set; }
}
-- // This below code you need to write where you want to execute
var context = new DataBaseContext();
var products = context.Database.SqlQuery<Product>("EXEC GetProductsList #ProductId",
new SqlParameter("#ProductId", "1")
).ToList();
Add DbSet as below code
public DbSet ChartModels { get; set; }
Set Dbset AS a HasNoKey() if it is use only for Query
builder.Entity< ChartModel >().HasNoKey();
Call Sp as below Code
string sqlQuery = "EXECUTE dbo.GetDashboardChart";
SqlParameter p = new SqlParameter("#name", "test");
var lst = await ChartModels.FromSqlRaw(sqlQuery,p).ToListAsync();
Pretty much the same as SAEED said above. Add the code below to your DbContext class:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Berk>().HasNoKey();
}
[NotMapped]
public DbSet<Berk> Berks { get; set; }
public virtual List<Berk> CallSP(string berkberk) =>
Berks.FromSqlRaw("exec dbo.berk #berkBerk = {0}", berkberk).ToList();
called with:
List<Berk> berk = = _whateverYouCalledTheDbContext.CallSP("berk berk berk!");
Will return a DbSet where Berk is just an object that matches the return values from the stored procedure. There is no Berks table in the database, but you have your stored procedure return values to play with as you wish.
By convention, each property will be set up to map to a column with the same name as the property. If I want to change the default mapping strategy, I can do it by using either Fluent API or Data Annotation. But, I want to set a custom mapping strategy for all the properties in all entities to the database columns. My database is exists and the column names are like ID, CUSTOMER_NAME, CREDIT_AMOUNT and so on, so the column names don't follow PascalCase notation. All object names are in upper case and individual words separated with "_" symbol. This is true for the entire database. And I want to map this naming to a class like this:
public class Payment
{
public int ID { set; get; }
public string CustomerName { get; set; }
public decimal CreditAmount { get; set; }
}
The database is large and I don't want to map each property and class names to the appropriated database objects. Is there any global way to define this type of mapping like this?
CustomerName -> CUSTOMER_NAME,
CreditAmount -> CREDIT_AMOUNT and so on.
Possible way of doing that convention with reflection like this:
Getting Entity types from DBSet properties of DbContext class
Then getting properties (columns) of entity types
So
In your DbContext class add this line:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//see below for this extension method
this.ApplyCaseMappingRule(modelBuilder);
base.OnModelCreating(modelBuilder);
}
Extension method source:
public static class Extensions
{
public static void ApplyCaseMappingRule<TDbContext>(this TDbContext _, ModelBuilder modelBuilder) where TDbContext:DbContext
{
var ignoreType = typeof(NotMappedAttribute);
var dbSetProps = typeof(TDbContext).GetProperties();
foreach (var dbSetProp in dbSetProps)
{
if (dbSetProp.PropertyType.TryGetEntityTypeFromDbSetType(out var entityType))
{
modelBuilder.Entity(entityType, option =>
{
option.ToTable(Mutate(dbSetProp.Name));
var props = entityType.GetProperties();
foreach (var prop in props)
{
//check if prop has Ignore attribute
var hasIgnoreAttribute =
prop.PropertyType.CustomAttributes.Any(x => x.AttributeType == ignoreType);
if (hasIgnoreAttribute) continue;
option.Property(prop.PropertyType, prop.Name).HasColumnName(Mutate(prop.Name));
}
});
}
}
}
private static bool TryGetEntityTypeFromDbSetType(this Type dbSetType, out Type entityType)
{
entityType = null;
if (dbSetType.Name != "DbSet`1") return false;
if (dbSetType.GenericTypeArguments.Length != 1) return false;
entityType = dbSetType.GenericTypeArguments[0];
return true;
}
public static IEnumerable<string> SplitCamelCase(this string source)
{
const string pattern = #"[A-Z][a-z]*|[a-z]+|\d+";
var matches = Regex.Matches(source, pattern);
foreach (Match match in matches)
{
yield return match.Value;
}
}
public static string Mutate(string propName)
{
return string.Join("_", propName.SplitCamelCase().Select(x => x.ToUpperInvariant()));
}
Tested on .NET 5 with EF 5.0.0
You just need to iterate the Entities and Properties from modelBuilder.Model, eg
string ToDatabaseIdentifier(string propertyName)
{
var sb = new System.Text.StringBuilder();
for (int i = 0; i < propertyName.Length; i++)
{
var c = propertyName[i];
if (i>0 && Char.IsUpper(c) && Char.IsLower(propertyName[i-1]))
{
sb.Append('_');
}
sb.Append(Char.ToUpper(c));
}
return sb.ToString();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var e in modelBuilder.Model.GetEntityTypes())
{
e.SetTableName(ToDatabaseIdentifier(e.Name));
foreach (var p in e.GetProperties())
{
p.SetColumnName(ToDatabaseIdentifier(p.Name));
}
}
base.OnModelCreating(modelBuilder);
}
I have an entity object which is connected to another entities.
I want to loop through all entity properties , if the property is String then do something with the value.
If the property is EntityReference, I want to get it's value (it has only one), and do something with the value as well.
I was able to determine if the property is string or EntityReference.
I get the String value by -
value = typeof(entity).GetProperty(property.Name).GetValue(request, null);
but how do I get the value of an entityreference ?
Just trace the property tree.
You have the first step. repeat for lower properties.
var TopLevelProp = poco.GetType().GetProperty(property.Name).GetValue(poco, null);
var LowerProp = TopLevelProp.GetType().GetProperty("aLowerPropName").GetValue(TopLevelProp, null);
although you tagged this EF. What did you mean by entity reference ?
edit: in the hope i have covered the entity and its key question
Here is a sample Repository covering EF Context and entity access. See the Entity field and Entity KEY field methods...
public class Repository<T> : IRepositoryEF<T> where T : BaseObject {
public RepositoryEF(DbContext context) { Context = context; }
public DbEntityEntry<T> Entry(T entity) { return Context.Entry(entity); }
public DbSet<T> EntityDbSet() { return Context.Set<T>(); }
public ObjectContext ObjectContext { get { return ((IObjectContextAdapter) this.Context).ObjectContext; } }
public DbContext Context { get; protected set; }
public EntityState GetEntityState(object entity) { return Context.Entry(entity).State; }
public ObjectSet<T> GetObjectSet() { return ObjectContext.CreateObjectSet<T>(); }
public IList<string> GetEntityFields() {
var entityFields = GetObjectSet().EntitySet.ElementType.Properties;
return entityFields.Select(e => e.Name).ToList();
}
public string[] GetEntityKeyFields() { return GetObjectSet().EntitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray(); }
public EntityKey GetEntityKey(T entity) {
if (entity == null) {
return null;
}
return ObjectContext.CreateEntityKey(GetObjectSet().EntitySet.Name, entity);
}
public string GetEntityKeyAsString(T entity) {
if (entity == null) {
return string.Empty;
}
var eK = GetEntityKey(entity);
var keyAsString = eK.EntityKeyValues.Aggregate("", (current, keyField) => current + keyField.Key + "=" + keyField.Value + ",");
return keyAsString;
}
}
If you want to get all the metadata in the Context:
ObjectContext objContext = ((IObjectContextAdapter)context).ObjectContext;
MetadataWorkspace workspace = objContext.MetadataWorkspace;
IEnumerable<EntityType> managedTypes = workspace.GetItems<EntityType>(DataSpace.OSpace);
You can go to town on the meta data. see all enums values in DataSpace to get at various parts of the model
I'm working with asp.net mvc3.
I have a edmx that was created ADO.NET Entity Data Model. (Data-First)
TestDb.Designer.cs
namespace Test.Web.DataModel
{
public partial class TestDbContext : ObjectContext
{
public ObjectSet<T_Members> T_Members { ... }
public ObjectSet<T_Documents> T_Documents { ... }
....
}
}
How to get an ObjectSet by name(string)?
For example, I want to be this.
var members = context.GetTable("T_Members"); // var members = context.T_Members;
I'm not that familiar with ObjectContext internals, so there might be a more 'native' way,
...but what you need is some reflection to get your properties out.
(note: I don't have any db-first handy to check, so you'd need to adjust if some typo - should work though)
public static object GetTableFromName(this TestDbContext db, string propertyName)
{
PropertyInfo property = null;
property = typeof(TestDbContext)
.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
return property.GetValue(db, null);
}
public static object GetTableFromType(this TestDbContext db, string tableTypeName)
{
// var tableType = typeof(TestDbContext).Assembly.GetType("YourNamespace.T_Members");
var tableType = Type.GetType(tableTypeName);
var genericType = typeof(ObjectSet<>).MakeGenericType(new[] { tableType });
var property = typeof(TestDbContext)
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(x => x.PropertyType == genericType).FirstOrDefault();
return property.GetValue(db, null);
}
and use it like
var table = db.GetTableFromName("T_Members");
table = db.GetTableFromType("YourNamespace.T_Members);
// this gets you the `object` you'd still need to 'cast' or something