Dynamic call store procedure from context - c#

In code we found a lot of duplicate code that call function from Context then return a list result. If result is null, return null. Otherwise, return list result.
public static IList<RemittanceDetail> GetDetails()
{
var context = StartupBusinessLogic.CreateContext();
var sourceResult = context.SP_GetRemittanceDetails();
if (sourceResult == null)
return null;
IList<RemittanceDetail> result = new List<RemittanceDetail>();
Mapper.Map(sourceResult, result);
return result;
}
public static IList<PaymentType> GetPaymentTypes()
{
var context = StartupBusinessLogic.CreateContext();
var sourceResult = context.SP_GetPaymentTypes();
if (sourceResult == null)
return null;
IList<PaymentType> result = new List<PaymentType>();
Mapper.Map(sourceResult, result);
return result;
}
Could we create a generic method to handle this task? My concern is how to call dynamic function of context
context.SP_GetPaymentTypes()

The problem you have right now is that you're using 2 functions to fetch some data in my opinion, why don't you move all the logic from your current functions to SP_GetRemittanceDetails and SP_GetPaymetTypes. In those functions you should just return a list, by calling .ToList() method in the end of your query. It could look like this:
public IList<PaymentType> SP_GetPaymentTypes()
{
using(var ctx = new StartupBusinessLogic())
{
var query = ctx. blablabla
query = query.*more filtering*
return query.ToList();
}
}
2 remarks with this:
1) It's better to call your context in a using statement, because at the moment you're not disposing of your context which is bad.
2) When your query result contains nothing, and you call ToList(), the List you return will automatically be null, so the checks and use of Mapping function becomes obsolete.
To convert the IQueryable to IList you can use extension methods:
public static class Extensions
{
public static IList<T> GetList<T>(this IQueryable<T> query)
{
return query.ToList();
}
}
Then you can simply do
var context = StartupBusinessLogic.CreateContext();
var sourceResult = context.SP_GetRemittanceDetails().GetList();
And the result will be of type Ilist<T>

Related

Implementation of IEnumerable and IEnumerator in custom object, return the same data when called multiple times by LINQ with a different predicate

I wanted to be able to use LINQ queries on a custom object that does not use typical collections but nevertheless stores data in a sequential manner. The object is of type XTable that contains a collection of XRows and I have the following code that implements IEnumerable and IEnumerator.
public class EnumerableXTable : IEnumerable<XRow>
{
private readonly XTable _xTable;
public EnumerableXTable(XTable xTable)
{
_xTable=xTable;
}
IEnumerator<XRow> IEnumerable<XRow>.GetEnumerator()
{
return new XTableEnumerator(_xTable);
}
public IEnumerator GetEnumerator()
{
return new XTableEnumerator(_xTable);
}
}
public class XTableEnumerator : IEnumerator<XRow>
{
private readonly XTable _xTable;
private int _index = -1;
public XTableEnumerator(XTable xTable)
{
_xTable=xTable;
}
public void Dispose()
{
}
public bool MoveNext()
{
_index++;
if (_index == _xTable.Count) return false;
_xTable.Current.RecNo = _index;
return true;
}
public void Reset()
{
throw new NotImplementedException("IEnumerator Reset Method not implemented");
}
public XRow Current => _xTable.Current;
object IEnumerator.Current => Current;
}
public static EnumerableXTable AsEnumerable(this XTable xTable)
{
return new EnumerableXTable(xTable);
}
If I run the below code:
XTable t = GetXTable();
var xRow1 = t.AsEnumerable().First(xRow => xRow.Get<int>("CCCIntaleId") == 462);
var xRow2 = t.AsEnumerable().First(row => row.Get<int>("CCCIntaleId") == 465);
xRow1 and xRow2 are the exact same row, and according to the predicate, they should be different. If I set breakpoints, then when I break after the first statement, xRow1 has the correct value and if I break after the second statement, xRow2 has the correct value and now xRow1 is the value of xRow2. It looks like there is some form of deferred execution although I think that when calling First(), execution should be immediate. The following code returns the correct results on recNo1 and recNo2:
XTable t = GetXTable();
var xRow1 = t.AsEnumerable().First(xRow => xRow.Get<int>("CCCIntaleId") == 462);
int recNo1 = xRow1.RecNo;
var xRow2 = t.AsEnumerable().First(row => row.Get<int>("CCCIntaleId") == 465);
int recNo2 = xRow2.RecNo;
Furthermore, if I run the same code on a DataTable with the same structure as follows:
var row1 = datatable.AsEnumerable().First(row => row.Field<int>("CCCIntaleId") == 462);
var row2 = dd.AsEnumerable().First(row => row.Field<int>("CCCIntaleId") == 465);
the results I get are as expected. Is there anything wrong on my implementation of IEnumerator?
The solution was given in the comments. The problem was that _xTable.Current returns the same object everytime, with a different indexer. Thus, both xRow1 and xRow2 refer to the same object and at the end, both objects point to the values dictated by the indexer.

EF Querying DbContext of Generic type using its unique key

I am creating a uni-directional sync which is I have 2 context (WebContext and StagingContext). Everytime I call SaveChangesAsync() it should sync the data updated on WebContext to StagingContext. Here's the following code I have:
Attributes
[AttributeUsage(AttributeTargets.Class)]
public class SyncEntityAttribute : Attribute
{
public Type Target { get; set; }
}
DbContext
public override async Task<int> SaveChangesAsync(CancellationToken cancellation = default)
{
await SyncEntityAsync(stagingContext, cancellation);
return await base.SaveChangesAsync(cancellation);
}
private async Task SyncEntityAsync<T>(T dbContext, CancellationToken cancellation = default) where T : DbContext
{
ChangeTracker.DetectChanges();
var entries = ChangeTracker.Entries()
.Where(x => Attribute.GetCustomAttribute(x.Entity.GetType(), typeof(SyncEntityAttribute)) != null)
.ToList();
try
{
foreach (var entry in entries)
{
if (entry.State == EntityState.Detached || entry.State == EntityState.Unchanged)
continue;
var attribute = Attribute.GetCustomAttribute(entry.Entity.GetType(), typeof(SyncEntityAttribute)) as SyncEntityAttribute;
if (attribute == null)
continue;
var uniqueKeys = entry.Properties.Where(x => x.Metadata.IsUniqueIndex()).ToList();
var targetEntity = await GetQueryable(dbContext, attribute.TargetEntity); // Not yet implemented, I want this to return a single entity based on the generic type filtered by its unique key
mapper.Map(entry.Entity, targetEntity);
switch (entry.State)
{
case EntityState.Added:
dbContext.Add(targetEntity);
break;
case EntityState.Deleted:
if (targetEntity.HasProperty("IsActive"))
{
targetEntity.TrySetProperty("IsActive", false);
}
break;
}
}
}
catch (Exception ex)
{
Log.Error(ex.Message);
}
}
I need to sync any entity model that has the SyncEntity attribute in it. I want to query the data on the StagingContext to see if it already exists or not. In order to do this, I need to query it by its Target attribute which is a Generic Type. If I am only querying using Primary Key then this would be easy since the DbContext has FindAsync method which allows to pass Generic Types. I want to do the same with FindAsync but this time I will be filtering it using the unique key which unique are get from entry.Properties.Where(x => x.Metadata.IsUniqueIndex()).ToList();
How can I achieve this? Looking for a solution like an extension method but I can't find any in the internet.
Update based on the suggested solution:
private static async Task<object> _GetQueryable<T>(DbContext dbContext, List<PropertyEntry> uniqueKeys)
where T : class
{
if (uniqueKeys is null) throw new ArgumentNullException();
if (uniqueKeys.Count <= 0) throw new ArgumentNullException();
var p = Expression.Parameter(typeof(T));
var filters = new List<Expression>(uniqueKeys.Count);
foreach (var key in uniqueKeys)
{
var wrapper = Expression.Constant(Activator.CreateInstance(typeof(Wrapper<>).MakeGenericType(key.CurrentValue.GetType()), key.CurrentValue));
var value = Expression.Property(wrapper, "Value");
filters.Add(Expression.Equal(p, value));
}
var body = filters.Aggregate((c, n) => Expression.AndAlso(c, n));
var predicate = Expression.Lambda<Func<T, bool>>(body, p);
return await dbContext.Set<T>().FirstOrDefaultAsync(predicate);
}
This is my current solution for now, but executing the code filters.Add(Expression.Equal(p, value)); throws an exception The binary operator Equal is not defined for the types 'Web.Gateway.Core.StagingModels.User' and 'System.String'.. It seems like it compared the unique key to the model and not on the model property.
Update: Final code that works
private static Task<object> GetQueryable(DbContext dbContext, Type entityType, List<PropertyEntry> uniqueKeys)
{
return (Task<object>)typeof(SharedContext).GetMethod(nameof(_GetQueryable), BindingFlags.NonPublic | BindingFlags.Static)
.MakeGenericMethod(entityType)
.Invoke(null, new object[] { dbContext, uniqueKeys });
}
private static async Task<object> _GetQueryable<T>(DbContext dbContext, List<PropertyEntry> uniqueKeys)
where T : class
{
if (uniqueKeys is null) throw new ArgumentNullException();
if (uniqueKeys.Count <= 0) throw new ArgumentNullException();
var entityType = typeof(T);
var p = Expression.Parameter(entityType);
var filters = new List<Expression>(uniqueKeys.Count);
foreach (var key in uniqueKeys)
{
var wrapper = Expression.Constant(Activator.CreateInstance(typeof(Wrapper<>).MakeGenericType(key.CurrentValue.GetType()), key.CurrentValue));
var value = Expression.Property(wrapper, "Value");
filters.Add(Expression.Equal(Expression.Property(p, entityType.GetProperty(key.Metadata.Name)), value));
}
var body = filters.Aggregate((c, n) => Expression.AndAlso(c, n));
var predicate = Expression.Lambda<Func<T, bool>>(body, p);
return await dbContext.Set<T>().FirstOrDefaultAsync(predicate);
}
You can create a generic version of your method GetQueryable and call it through reflection:
private static Task<object> GetQueryable(DbContext dbContext, Type entityType, List<PropertyEntry> uniqueKeys)
{
return (Task<object>)typeof(ApplicationContext).GetMethod(nameof(_GetQueryable), BindingFlags.NonPublic | BindingFlags.Static)
.MakeGenericMethod(entityType)
.Invoke(null, new object[] { dbContext, uniqueKeys });
}
Then the generic version should create a predicate dynamically. Linq expression could be created with Expression class:
private static async Task<object> _GetQueryable<T>(DbContext dbContext, List<PropertyEntry> uniqueKeys)
where T : class
{
if (uniqueKeys is null) throw new ArgumentNullException();
if (uniqueKeys.Count <= 0) throw new ArgumentNullException();
var p = Expression.Parameter(typeof(T));
var filters = new List<Expression>(uniqueKeys.Count);
foreach (var key in uniqueKeys)
{
var property = Expression.Property(p, key.Metadata.PropertyInfo);
var value = Expression.Constant(key.CurrentValue);
filters.Add(Expression.Equal(property, value));
}
var body = filters.Aggregate((c, n) => Expression.AndAlso(c, n));
var predicate = Expression.Lambda<Func<T, bool>>(body, p);
return await dbContext.Set<T>().FirstOrDefaultAsync(predicate);
}
Depending of your expectations, you should rework your code to be more efficient.
Your entities are fetched one by one, maybe you should consider batching.
The call of _GetQueryable<T>() is done by reflection, you could consider it as a slow API (but the previous point is much important). A delegate could be created and cached for each entity type.
Generated expressions with usage of Expression.Constant will lead to non parameterized sql queries: internal cache of EF can grow (with many CompiledQuery instances) and causes some memory leaks. If your database is sqlserver, query execution plan will be different for each entity. You can create a wrapper arround the value, expose the value with a public property and replace the constant expression by a property expression:
public class Wrapper<T>
{
public T Value { get; }
public Wrapper(T value)
{
Value = value;
}
}
//...
var wrapper = Expression.Constant(Activator.CreateInstance(typeof(Wrapper<>).MakeGenericType(key.CurrentValue.GetType()), key.CurrentValue));
var value = Expression.Property(wrapper, "Value");

c# return both list of string and list of generic type

I need to return multiple items from a function. How can I do that?
public List<string> GetInfo()
{
var result = new List<string>();
return result;
}
How can I return something like this? Basically List of string and maybe also a list of generic type.
public List<string> GetInfo()
{
var result = new List<string>();
return result and someType;
}
public class someType
{
public List<SomeOthertype> someOthertype { get; set; }
}
If I understand you right, you can try using named tuples:
public (List<string> result, List<SomeOthertype> other) GetInfo()
{
var result = new List<string>();
someType instance = new someType();
//TODO: put relevant code here
// we return both result and someOthertype in one tuple
return (result, instance.someOthertype);
}
Usage:
var info = GetInfo();
var result = info.result;
var other = info.other;
I would propose to use a generic method to be possible use different types:
public static (List<string>, List<U>) GetInfo<U>()
{
var list = new List<string>();
var another = new List<U>();
//...
return (list, another);
}
From c# 7 onward there are tuple value types that are the cleanest way to return multiple values from the function.
Pre c# 7 you can use out parameters

Get custom object from lambda expression

I have this method:
public static SiteSettingEntity
GetSettings<SiteSettingEntity>(string siteId,
Expression<Func<SiteSettingEntity, object>> properties)
{
// This method returns a SiteSettingEntity
// filled with the values selected in my expression
}
If want to invoke:
var _siteSetting = SiteSettingService.GetSettings<SiteSettingEntity>(SiteID,
s => new { s.BillCycleType, s.InvoiceComment,
s.AllowInvoiceDetailApproval, s.JobMinimumHours });
So this returns an object with the properties filled that was selected by my lambda expression.
My question is: how I do to return a custom object dynamically same using my expression?
Taking an example: when if you want to use .Select(s => new{ filed1 = data.FieldX}), you can just use the property .filed1 in return object.
Thanks!
If you are trying to return a dynamic object you can do something like this simple example of using ExpandoObject:
public class DuckFactory
{
public ExpandoObject GetDuck()
{
dynamic duck = new ExpandoObject();
duck.Name = "Fauntleroy";
return duck;
}
}
And then call it like:
dynamic duck = new DuckFactory().GetDuck();
// Check the property exists before using it
if (((IDictionary<string, Object>)duck).ContainsKey("Name"))
{
Console.WriteLine(duck.Name); // Prints Fauntleroy
}
else
{
Console.WriteLine("Poor duck doesn't have a name.");
}
Just remember you won't get the benefits of the object being strongly typed.
This is my original method:
public static T GetSettings<T>(string siteId, Expression<Func<SiteSettingEntity, object>> properties)
{
string query = $"SELECT TOP 1 {DbTool.GetSqlFields(properties)} FROM {SiteSettingEntity.TABLE_NAME} (NOLOCK) WHERE Sites.SiteID = #SiteID";
var parameters = new Dictionary<string, object>
{
{"SiteID", siteId},
};
var _data = DbTool.SqlExec<T>(PowerDetailContext.GetConnectionString(siteId), CommandType.Text, query, parameters);
return _data != null ? _data.FirstOrDefault() : default(T);
}
Ang the when I made the call I have this:
var _siteSetting = SiteSettingService.GetSettings<SiteSettingEntity>(SiteID, s => new { s.BillCycleType, s.InvoiceComment, s.AllowInvoiceDetailApproval, s.JobMinimumHours });
the var _siteSetting I want just have the properties SELECTED between new{} when I called. If I try use _siteSetting.OtherProperty, the "OtherProperty" won't available in the code.
After reading it again I suppose you are just trying to return SiteSettingEntity from GetSettings method.
public static SiteSettingEntity GetSettings<T>(string siteId, Expression<Func<SiteSettingEntity, object>> properties)
{
string query = $"SELECT TOP 1 {DbTool.GetSqlFields(properties)} FROM {SiteSettingEntity.TABLE_NAME} (NOLOCK) WHERE Sites.SiteID = #SiteID";
var parameters = new Dictionary<string, object>
{
{"SiteID", siteId},
};
var _data = DbTool.SqlExec<SiteSettingEntity>(PowerDetailContext.GetConnectionString(siteId), CommandType.Text, query, parameters);
return _data != null ? _data.FirstOrDefault() : default(SiteSettingEntity);
}

Generic method for multiple db tables

I have multiple tables with the same structure and want to write a generic function that return data from the correct table.
Now:
var a = context.TableA.Where(a => a.id == id).FirstOrDefault.Name;
var b = context.TableB.Where(b => b.id == id).FirstOrDefault.Name;
I want something like this:
var a = GetName<TableA>(id);
var b = GetName<TableB>(id);
GetName<T>(int id){
//right table
return context<T>.Where(x => x.id == id).FirstOrDefault.Name;
}
But I can't get the context to get the right table with generic T.
You can not do this without getting context, so, you can add another class, pass context to it as argument of constructor, save it to private field and define GetName method inside. Like this:
public class EntityHelper
{
private readonly DbContext _context;
public EntityHelper(DbContext context)
{
this._context = context;
}
public string GetName<T>(int id)
{
return this._context.Set<T>().Where(x => x.id == id).FirstOrDefault().Name;
}
}
for method GetName there should be constraint, because compiler don't know does T has property called Name
also I recommend you to change FirstOrDefault() to First(), because if you will access property of FirstOrDefault() result, when it's return null - you'll get NullReferenceException
Try something like:
context.Set(typeof(T)).Where(a => a.id == id).FirstOrDefault.Name;
Where T should have restriction on having ID.
You can use the following helper (no changes to your classes is needed)
public static class Utils
{
public static string GetName<T>(this DbContext db, int id)
where T : class
{
var source = Expression.Parameter(typeof(T), "source");
var idFilter = Expression.Lambda<Func<T, bool>>(
Expression.Equal(Expression.Property(source, "id"), Expression.Constant(id)),
source);
var nameSelector = Expression.Lambda<Func<T, string>>(
Expression.Property(source, "Name"),
source);
return db.Set<T>().Where(idFilter).Select(nameSelector).FirstOrDefault();
}
}
like this
var a = context.GetName<TableA>(id);
var b = context.GetName<TableB>(id);

Categories

Resources