I have EF core which I need to do heavy operation on it before return the result back for the end user but the issue is, the operation time it's taking too much time, So, How can I enhance the performance.
Note: I tried to add AsNoTracking().AsEnumerable(), but with no luck it needs around 30 seconds until it returns any data.
I have Service which is called by the controller,
the Service using two helpers functions one for preparing the stored procedure query and another one for doing the grouping based on parameter which received from the user:
here is the code:
The Service Core
public async Task<List<ResultDto>> MyService(DateTime? startDate, DateTime? endDate,string groupingType)
{
List<ResultDto> result = new List<ResultDto>();
try
{
SqlParameter[] param = StoredProcedureHelpers.GetAsSQLParameters2(startDate, endDate);
string sql = "EXEC MyStoredProcedure #StartDate,#EndDate";
var storedProcedureResult = _context.MyStoredProceduresResults.FromSqlRaw(sql, param).AsNoTracking().AsEnumerable();
var distinctedResult = storedProcedureResult.DistinctBy(x => x.Some_Id).OrderByDescending(x => x.Value).Take(10).AsEnumerable();
result = distinctedResult.GroupByChoosedGroupingType(groupingType).OrderBy(x => x.GroupingKey).ToList();
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return result;
}
The Stored Procedure Helper function:
public static SqlParameter[]? GetAsSQLParameters2(DateTime? startDate, DateTime? endDate)
{
SqlParameter[] param = Array.Empty<SqlParameter>();
try
{
param = new SqlParameter[]
{
new SqlParameter {
ParameterName = "#StartDate",
SqlDbType =System.Data.SqlDbType.Date,
Direction = System.Data.ParameterDirection.Input,
Value = startDate
},
new SqlParameter {
ParameterName = "#EndDate",
SqlDbType =System.Data.SqlDbType.Date,
Direction = System.Data.ParameterDirection.Input,
Value = endDate
}
};
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return param;
}
Grouping Helper Function:
public static IEnumerable<ResultDto> GroupByChoosedGroupingType(this IEnumerable<MyStoredProceduresResults> resultList, string groupingType)
{
List<ResultDto> result = new List<ResultDto>();
switch (groupingType)
{
case "Daily":
result = resultList.GroupBy(x => new { x.Date.Value.Date, x.PlaceName }).Select(x =>
{
return new ResultDto
{
Count = x.Count(),
Value = x.Sum(x => x.Value),
PlaceName = x.Key.PlaceName,
GroupingKey = x.Key.Date.ToString("dd/MM/yyyy")
};
}).ToList();
break;
case "Monthly":
result = resultList.GroupBy(x => new { Year = x.Date.Value.Year, MonthName = x.Date.Value.ToString("MMM"), x.PlaceName }).Select(x => new ResultDto
{
Count = x.Count(),
Value = x.Sum(x => x.Value),
PlaceName = x.Key.PlaceName,
GroupingKey = $"{x.Key.MonthName}-{x.Key.Year}"
}).ToList();
break;
}
return result;
}
Related
I need to save my list of objects to Firestore database. Below is my code:
How I Create the List of object
private async void LoadPlazaListAndSummary()
{
_plazaList = await _dc.GetPlazas();
foreach(var item in _plazaList)
{
var par = new ParameterManualTags
{
Plaza = item.Code,
Lane = item.Lane,
DateTo = System.DateTime.Now,
DateFrom = System.DateTime.Today
};
var manualCount = await _dc.GetManualAndTransactionCount(par);
var model = new Summary
{
Name = item.Name,
Lane = item.Lane,
Code = item.Code,
DateRun = System.DateTime.Now,
ManualAverage = 100.00,
ManualCount = manualCount[0],
ReadCount = 1,
TotalTransaction = manualCount[1],
Type = item.Type,
Plaza = item,
DateFrom = par.DateFrom,
DateTo = par.DateTo,
RfidAddress = item.ReaderIpAddress,
IsDedicated = item.IsDedicated ? "Dedicated" : "Mixed"
};
model.ManualAverage = Math.Round(ComputeManualPercentage(model), 2);
_summaryList.Add(model);
DgSummary.Items.Add(model);
}
}
This is my sample to save data to Firestore:
public void SetData()
{
if (_isConnected)
{
try
{
var doc = _database.Collection("MyCollection").Document("myDocument");
Dictionary<string, object> data = new Dictionary<string, object>()
{
{"FirstName", "MyFirstName"},
{"LastName", "MyLastName"},
{"PhoneNumber", "PhoneNumber"}
};
var x = doc.SetAsync(data);
Console.WriteLine("Saved");
}
catch (Exception ex)
{
Console.WriteLine($#"An error occured in saving the data: {ex.Message}");
}
}
}
Using my sample code, I can insert one at a time to Firestore but, I do not know how to save a list of objects to Firestore.
I have a search method that queries Solr for event items. I need to modify it to only get events where the date has not already passed (i.e. Where(x => x.EventDate.Date >= DateTime.Now.Date), but I'm not sure how to add this because I'm not very familiar with Solr. Here's my search function:
public SearchQueryResults Search(string keywords, int page,int perPage, List<Guid> contentTypeFilters, List<Guid> otherFilters, ISortBuilder<SearchResultItem> sortBuilder)
{
var searchFilters = new List<IPredicateBuilder<SearchResultItem>>()
{
new IsSearchablePredicateBuilder()
};
if (contentTypeFilters.Any())
{
var contentTypePredicateBuilder = new ContentTypePredicateBuilder();
contentTypePredicateBuilder.ContentTypes = contentTypeFilters;
searchFilters.Add(contentTypePredicateBuilder);
}
if (otherFilters.Any())
{
var tagFilterBuilder = new TagsAndPredicateBuilder(otherFilters,_sitecoreContext);
searchFilters.Add(tagFilterBuilder);
}
if (string.IsNullOrWhiteSpace(keywords))
{
keywords = "";
}
SearchRequest searchRequest = new SearchRequest();
var queryParams = new Dictionary<string, string>() { };
queryParams.Add("q", keywords);
searchRequest.QueryParameters = queryParams;
searchRequest.SortBy = "";
searchRequest.SortOrder = "";
SearchQuery<SearchResultItem> queryArguments = new SearchQuery<SearchResultItem>();
queryArguments.FilterBuilders = searchFilters;
queryArguments.Page = page;
queryArguments.PerPage = perPage;
queryArguments.FacetsBuilder = new SearchFacetBuilder<SearchResultItem>();
queryArguments.SearchRequest = searchRequest;
queryArguments.IndexName = _indexName;
if (string.IsNullOrWhiteSpace(keywords))
{
queryArguments.QueryBuilders =new List<IPredicateBuilder<SearchResultItem>>();
}
else
{
queryArguments.QueryBuilders = new[] { new KeywordPredicateBuilder<SearchResultItem>(new[] { keywords }) };
}
queryArguments.SortBuilder = sortBuilder;
try
{
var results = _searchManager.GetResults<SearchResultItem>(queryArguments);
SearchQueryResults queryResults = new SearchQueryResults();
queryResults.ResultItems = results.Results;
queryResults.CurrentPage = page;
queryResults.TotalResults = Int32.Parse(results.TotalResults.ToString());
queryResults.TotalPages = (queryResults.TotalResults + perPage - 1) / perPage; ;
return queryResults;
}
catch (Exception exc)
{
Sitecore.Diagnostics.Log.Error("Error with FilteredSearch, could be a loss of connection to the SOLR server: " + exc.Message, this);
return null;
}
}
and here is how it's being called:
Results = _searchService.Search(searchTerm, CurrentPage - 1, 10, contentTypes, searchFilters,
new GenericSortBuilder<SearchResultItem>(q => q.OrderByDescending(r => r.SearchDate)));
How do I add in date filtering so that it only returns items where the date is in the future?
I would add filter query to the list of existing ones filtering the date field. On the documentation page, I was able to find information about fluent API, which could help here
Query.Field("date").From(DateTime.Now)
I'm not C# developer, that this code could have some mistakes, but I think the main idea is clear what needs to be done.
While retrieving the results using stored procedure how can I retrieve and store multiple result set in view model in .net core
For e.g. from stored procedure I am returning records for below two queries
Select * LMS_Survey
Select * from LMS_SurveyQuestion
Select * from LMS_SurveyQuestionOptionChoice
and below is view model for two tables
public class LMS_SurveyTraineeViewModel
{
public LMS_SurveyDetailsViewModel SurveyDetailsViewModel { get; set; }
public LMS_SurveyQuestionsViewModel SurveyQuestionsViewModel { get; set; }
public LMS_SurveyQuestionOptionChoiceViewModel SurveyQuestionOptionChoiceViewModel { get; set; }
}
This is how I am executing the stored procedure
public List<LMS_SurveyTraineeViewModel> GetTraineeSurvey(int surveyID)
{
try
{
List<LMS_SurveyTraineeViewModel> modelList = new List<LMS_SurveyTraineeViewModel>();
modelList = dbcontext.Set<LMS_SurveyTraineeViewModel>().FromSql("LMSSP_GetTraineeSurvey #surveyID = {0},#LanguageID = {1}", surveyID, AppTenant.SelectedLanguageID).ToList();
return modelList;
}
catch (Exception ex)
{
throw ex;
}
}
How can stored the multiple result set using stored procedure in view model ?
Currently, EF Core doesn't not support this. There is an open issue to address this.
https://github.com/aspnet/EntityFramework/issues/8127
Update 12th Sep 2018: This is still not a priority for EF Core even for release 3.0; so best use Dapper or plain ADO.NET when you have multiple results scenario
Update 25th Jun 2020: still on the backlog for EF Core even for release 5.0; so best use Dapper or plain ADO.NET when you have multiple results scenario
Update 7th Feb 2021: still on the backlog for EF Core
Update 8th Aug 2022: still on the backlog for EF Core, looks like its not a high priority use-case. Recommend to follow alternatives like using straight ADO.NET or Dapr or the below workaround for this
In the interim an alternative solution can be achieved via extension method(s)
public static async Task<IList<IList>> MultiResultSetsFromSql(this DbContext dbContext, ICollection<Type> resultSetMappingTypes, string sql, params object[] parameters)
{
var resultSets = new List<IList>();
var connection = dbContext.Database.GetDbConnection();
var parameterGenerator = dbContext.GetService<IParameterNameGeneratorFactory>()
.Create();
var commandBuilder = dbContext.GetService<IRelationalCommandBuilderFactory>()
.Create();
foreach (var parameter in parameters)
{
var generatedName = parameterGenerator.GenerateNext();
if (parameter is DbParameter dbParameter)
commandBuilder.AddRawParameter(generatedName, dbParameter);
else
commandBuilder.AddParameter(generatedName, generatedName);
}
using var command = connection.CreateCommand();
command.CommandType = CommandType.Text;
command.CommandText = sql;
command.Connection = connection;
for (var i = 0; i < commandBuilder.Parameters.Count; i++)
{
var relationalParameter = commandBuilder.Parameters[i];
relationalParameter.AddDbParameter(command, parameters[i]);
}
var materializerSource = dbContext.GetService<IEntityMaterializerSource>();
if (connection.State == ConnectionState.Closed)
await connection.OpenAsync();
using var reader = await command.ExecuteReaderAsync();
foreach (var pair in resultSetMappingTypes.Select((x, i) => (Index: i, Type: x)))
{
var i = pair.Index;
var resultSetMappingType = pair.Type;
if (i > 0 && !(await reader.NextResultAsync()))
throw new InvalidOperationException(string.Format("No result set at index {0}, unable to map to {1}.", i, resultSetMappingType));
var type = resultSetMappingType;
var entityType = dbContext.GetService<IModel>()
.FindEntityType(type);
if (entityType == null)
throw new InvalidOperationException(string.Format("Unable to find a an entity type (or query type) matching '{0}'", type));
var relationalTypeMappingSource = dbContext.GetService<IRelationalTypeMappingSource>();
var columns = Enumerable.Range(0, reader.FieldCount)
.Select(x => new
{
Index = x,
Name = reader.GetName(x)
})
.ToList();
var relationalValueBufferFactoryFactory = dbContext.GetService<IRelationalValueBufferFactoryFactory>();
int discriminatorIdx = -1;
var discriminatorProperty = entityType.GetDiscriminatorProperty();
var entityTypes = entityType.GetDerivedTypesInclusive();
var instanceTypeMapping = entityTypes.Select(et => new
{
EntityType = et,
Properties = et.GetProperties()
.Select(x =>
{
var column = columns.FirstOrDefault(y => string.Equals(y.Name,
x.GetColumnName() ?? x.Name, StringComparison.OrdinalIgnoreCase)) ?? throw new InvalidOperationException(string.Format("Unable to find a column mapping property '{0}'.", x.Name));
if (x == discriminatorProperty)
discriminatorIdx = column.Index;
return new TypeMaterializationInfo(x.PropertyInfo.PropertyType, x, relationalTypeMappingSource, column.Index);
})
.ToArray()
})
.Select(x => new
{
EntityType = x.EntityType,
Properties = x.Properties,
ValueBufferFactory = relationalValueBufferFactoryFactory.Create(x.Properties)
})
.ToDictionary(e => e.EntityType.GetDiscriminatorValue() ?? e.EntityType, e => e)
;
var resultSetValues = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(type));
while (await reader.ReadAsync())
{
var instanceInfo = discriminatorIdx < 0 ? instanceTypeMapping[entityType] : instanceTypeMapping[reader[discriminatorIdx]];
var valueBuffer = instanceInfo.ValueBufferFactory.Create(reader);
var materializationAction = materializerSource.GetMaterializer(instanceInfo.EntityType);
resultSetValues.Add(materializationAction(new MaterializationContext(valueBuffer, dbContext)));
}
resultSets.Add(resultSetValues);
}
return resultSets;
}
And the extension typed methods
public static async Task<(IReadOnlyCollection<T1> FirstResultSet, IReadOnlyCollection<T2> SecondResultSet)> MultiResultSetsFromSql<T1, T2>(this DbContext dbContext, string sql, params object[] parameters)
{
var resultSetMappingTypes = new[]
{
typeof(T1), typeof(T2)
};
var resultSets = await MultiResultSetsFromSql(dbContext, resultSetMappingTypes, sql, parameters);
return ((IReadOnlyCollection<T1>)resultSets[0], (IReadOnlyCollection<T2>)resultSets[1]);
}
public static async Task<(IReadOnlyCollection<T1> FirstResultSet, IReadOnlyCollection<T2> SecondResultSet, IReadOnlyCollection<T3> ThirdResultSet)> MultiResultSetsFromSql<T1, T2, T3>(this DbContext dbContext, string sql, params object[] parameters)
{
var resultSetMappingTypes = new[]
{
typeof(T1), typeof(T2), typeof(T3)
};
var resultSets = await MultiResultSetsFromSql(dbContext, resultSetMappingTypes, sql, parameters);
return ((IReadOnlyCollection<T1>)resultSets[0], (IReadOnlyCollection<T2>)resultSets[1], (IReadOnlyCollection<T3>)resultSets[2]);
}
Currently, EF Core doesn't not support this. see this example for retrieve multiple result sets.
https://github.com/nilendrat/EfCoreMultipleResults/
It works with this tiny change on EF core 5 according to Ricky G answer
change
command.CommandType = CommandType.Text;
to
command.CommandType = CommandType.StoredProcedure;
and as sql parameter value for this extension method type your stored procedure name "dbo.testproc"
example of usage:
var t1 = await _context.MultiResultSetsFromSql(new [] {typeof(proctestprocResult) },"dbo.testproc", sqlParameters);
works for me
I was trying to get employee list which not already available in another list. but im getting only first element from array list.
ex : if i try EmployeeId = new int[2] {5, 2}; the list excluding only '5'. So please help me correct my below code.
public JsonResult GetEmployees(int[] EmployeeId)
{
var dbs = new dbContext();
if (EmployeeId != null)
{
foreach (var emp in EmployeeId)
{
var EmpList = dbs.Employees.Select(e => new
{
EmployeeId = e.EmployeeId,
Name = e.EmployeeName,
Job = e.Job.JobName,
Currency = e.Currency.CurrencyName,
Amount = e.Amount
}).Where(o => o.EmployeeId != emp);
return Json(EmpList, JsonRequestBehavior.AllowGet);
}
}
return null
}
Try this :
var employeeList = dbs.Employees.
.Where(e => EmployeeId.All(x=> x != e.EmployeeId))
.Select(e => new
{
EmployeeId = e.EmployeeId,
Name = e.EmployeeName,
Job = e.Job.JobName,
Currency = e.Currency.CurrencyName,
Amount = e.Amount
});
return Json(EmpList, JsonRequestBehavior.AllowGet);
}
Have you tried stepping through your code?
Your foreach iterates over your EmployeeId array.
Since you have a return statement in your foreach it exits the function at that point and it only uses the first element of your array.
You need something like this:
public JsonResult GetEmployees(int[] EmployeeId)
{
var dbs = new dbContext();
if (EmployeeId != null)
{
var EmpList = dbs.Employees.Where(EmployeeId.Contains(e.EmployeeId))
.Select(e => new
{
EmployeeId = e.EmployeeId,
Name = e.EmployeeName,
Job = e.Job.JobName,
Currency = e.Currency.CurrencyName,
Amount = e.Amount
}).Where(o => o.EmployeeId != emp);
return Json(EmpList, JsonRequestBehavior.AllowGet);
}
return null;
}
I have a IEnumerable collection:
public IEnumerable<Config> getConfig(string CID, string name)
{
var raw = db.ap_GetInfo(CID, name);
foreach (var item in raw.ToList().Where(x => x.Name!= null))
{
var x = raw.Count();
yield return new Config
{
Name = item.Name.ToString(),
Value = item.Value.ToString(),
};
}
}
The problem I am facing is that if this return a length of zero I am then unable to set the attributes to something else, If I have a response of length 1 the attributes are set from the database, however length zero I want to set a dfault value for Name and Value.
A LINQ solution - this returns the default if there are no items in the enumerable using DefaultIfEmpty:
public IEnumerable<Config> GetConfig(string CID, string name)
{
return db.ap_GetInfo(CID, name)
.Where(x => !string.IsNullOrEmpty(x.Name))
.Select(x => new Config
{
Name = x.Name.ToString(),
Value = x.Value.ToString(),
})
.DefaultIfEmpty(new Config
{
Name = "DefaultName",
Value = "DefaultValue"
});
}
If I understood your question correctly, you want to replace the case
0 results
with
1 result with a default value.
If that is correct, the easiest way is to fix this in the calling function:
var result = getConfig(...).ToList();
if (!result.Any())
{
result = new[] {new Config {Name = "DefaultName", Value = "DefaultValue"}};
}
Obviously, you can wrap this in a new function:
public IEnumerable<ClubConfig> getConfigOrDefault(string CID, string name)
{
var result = getConfig(CID, name).ToList();
if (result.Any())
return result;
else
return new[] {new Config {Name = "DefaultName", Value = "DefaultValue"}};
}
To check if your query did return any elements use Any-method.
public IEnumerable<ClubConfig> getConfig(string CID, string name)
{
var raw = db.ap_GetInfo(CID, name);
if (!raw.Any()) return new[] {
ClubConfig
{
Name = "defaultName",
Value = "defaultValue"
}};
foreach (var item in raw.Where(x => !string.IsNullOrEmpty(x.Name))
{
yield return new ClubConfig
{
Name = item.Name.ToString(),
Value = item.Value.ToString(),
};
}
}
EDIT: You can also omit the ToList from your input.
You can do this using LINQ and can maintain lazy evaluation of the IEnumerable result as follows:
public IEnumerable<ClubConfig> getConfig(string CID, string name)
{
var raw = db.ap_GetInfo(CID, name);
return raw.Where(x => !string.IsNullOrEmpty(x.Name))
.Select(item => new ClubConfig
{
Name = item.Name.ToString(),
Value = item.Value.ToString(),
})
.DefaultIfEmpty(new ClubConfig { Name = "n", Value="v" });
}