I have this code below. Gets data and sets data property to the values gathered.
public struct TrblShootData
{
public List<string> Logins;
public IEnumerable<Hieracrhy> Hierarchy;
public IEnumerable<EmployeeTeam> EmpTeam;
}
public TrblShootData TroubleShootData
{
get;
private set;
}
class DataGetter
{
public void GetData(string FirstName, string LastName, string Login, string Email, bool isFirstName, bool isLastName, bool isLogin, bool isEmail)
{
List<string> logins = null;
IEnumerable<Hieracrhy> hier = null;
IEnumerable<EmployeeTeam> tmemp = null;
TrblShootData tsData = new TrblShootData();
queries q = BuildQuery(FirstName, LastName, Login, Email, isFirstName, isLastName, isLogin, isEmail);
if (q.isValidQueries)
{
DataContext1 mscDB = new DataContext1 ();
using (DataContext2 opsDB = new DataContext2 ())
{
tmemp = opsDB.ExecuteQuery<EmployeeTeam>(q.qryEmployeeTeam);
}
using (DataContext3 rptDB = new DataContext3 ())
{
hier = rptDB.ExecuteQuery<Hieracrhy>(q.qryHierarchy);
if (hier != null)
{
logins = hier.Select(s => s.SalesRepLogin).Distinct().ToList();
}
}
tsData.EmpTeam = tmemp.Select(r=>r);
tsData.Hierarchy = hier.Select(r => r);
tsData.Logins = logins.Select(r => r).ToList();
TroubleShootData = tsData;
}//if
}
}
From another class I attempt to do this:
tshtr.GetData(txtFirstName.Text, txtLastName.Text, txtLogin.Text, txtEmail.Text, chkFirstName.Checked, chkLastName.Checked, chkLogin.Checked, chkEmail.Checked);
gvEmpTeams.DataSource = tshtr.TroubleShootData.EmpTeam;
gvHierarchy.DataSource = tshtr.TroubleShootData.Hierarchy;
gvEmpTeams.DataBind();
gvHierarchy.DataBind();
But at the DataBind() I get an error saying that I cannot read from a closed reader.
I'm not seeing why it would throw this error when I've set my property as above after I've assigned the values in the usings. So I'm not seeing how this is trying to use a closed reader.
Thanks for any help!
Because of deferred execution, your query only executes when the data-binding engine enumerates its results, after you close the DataContext.
You need to call .ToList() before closing the DataContext to force it to be evaluated immediately.
Related
we are a small development team.
We develop in ASP.NET and we are starting to use generic controllers and services.
The goal is to have solid methods for things that are repetitive.
What we ask ourselves is if it is a good idea to do some transformation in the data models to allow us to reuse our functions that we know are working?
Exemple: we have a combobox and we want to manage the display and search. It's always the same and redundant.
This is my class
[Table("stage.Test")]
public partial class Test : IBaseEntity, ICombobox
{
public virtual Product Product { get; set; }
public string nom { get; set; }
public string prenom { get; set; }
public string title { get; set; }
[NotMapped]
public virtual string AffichageCombobox => nom + prenom;
[NotMapped]
public virtual string TexteRecherche => Product.Gabarit.Description;
}
as you can see i have two columns with the tag [NotMapped]. These are the columns in the interface ICombobox
public interface ICombobox
{
string AffichageCombobox { get;}
string TexteRecherche { get; }
}
this is the first service where I use one of my two columns which redirects to other columns. [We use the column "AffichageCombobox" from the model]
public List<ComboboxViewModel> GetComboboxViewModel(int? id, bool actifseulement, string text)
{
var query = _requestDatabaseService.GetComboboxQuery<T>(id, actifseulement, text);
var list = query.Select(table => new ComboboxViewModel
{
Id = table.Id,
AffichageCombobox = table.DateHFin == null ? table.AffichageCombobox : table.AffichageCombobox + " (inactif)"
}).ToList();
return list;
}
This is the RequestDatabaseService [We use the column "TexteRecherche" from the model]
public List<T> GetComboboxQuery<T>(int? id, bool actifseulement, string text) where T : class, IBaseEntity, ICombobox
{
text = text.ToLower();
var list = _dbContext.Set<T>()
.If(id.HasValue,
q => q.Where(x => x.Id == id))
.If(actifseulement,
q => q.Where(x => x.DateHFin == null))
.If(text != "",
q => q.Where(x => x.TexteRecherche.ToLower() == text))
.ToList();
return list;
}
As you can see, I am using an interface to add columns to redirect to the correct columns to my data model to avoid overriding my methods for two column.
Is it a good idea, a good practice ?
What do you think is the best practice if we want to do generic functions, but the columns are not called the same way?
Thank you!
Your solution has a lot of weaknesses
You have extended Model to handle specific UI cases. In my opinion it is bad practice.
Your virtual properties will not work in LINQ query. EF translates only Expression because it canot look into compiled property body.
What we can do here is simplifying of building such comboboxes. I have defind set fo extensions which can be reused for such scenarios. Sorry if there some mistakes, written from memory.
How it can be used:
Assuming that GetComboboxViewModel is not in generic class
public List<ComboboxViewModel> GetComboboxViewModel(int? id, bool actifseulement, string text)
{
// uncover DbContext. All that we need is IQueryable<Test>
var ctx = _requestDatabaseService.GetContext();
var query = ctx.Test.AsQueryable();
var comboItems = query
.FilterItems(id, actifseulement)
.GetComboboxQuery(text, e => e.Product.Gabarit.Description, e => e.nom + e.prenom)
.ToList();
return comboItems;
}
Think about this solution and yes, we can register somewhere pair of Lmbdas Dictionary<Type, (LambdaExpression: searchProp, LambdaExpression: displayProp)> and dynamically build call above.
Realisation:
public static class QueryableExtensions
{
// more simlified version for filtering
public static IQueryable<T> WhereIf(this IQueryable<T> query, bool condition, Expression<Func<T, bool>> predicate)
{
return condition ? query.Where(predicate) : query;
}
// handy function for filtering
public static IQueryable<T> FilterItems<T>(this IQueryable<T> query, int? id, bool onlyActive)
where T : IBaseEntity
{
query = query
.WhereIf(id.HasValue, x => x.Id == id)
.WhereIf(onlyActive, x => x.DateHFin == null)
return query;
}
// dynamic generation of filtering and projection
public static IQueryable<ComboboxViewModel> GetComboboxQuery<T>(this IQueryable<T> query, string text, Expression<Func<T, string>> searchProp, Expression<Func<T, string>> dsiplayProp)
where T : IBaseEntity
{
if (!string.IsNullOrEmpty(text))
{
text = text.ToLower();
// defining search pattern
// this also extension point, you may use here `Contains` or FullText search functions
Expression<Func<string, string, bool>> filterFunc = (s, t) => s.ToLower() == t;
// reusing parameter from searchProp lambda
var param = searchProp.Parameters[0];
// applying pattern to searchprop
var filterBody = ExpressionReplacer.GetBody(filterFunc, searchProp.Body, Expression.Constant(text));
// applying generated filter
var filterPredicate = Expression.Lambda<Func<T, bool>>(filterBody, param);
query = query.Where(filterPredicate);
}
// defining template for Select
Expression<Func<T, string, ComboboxViewModel>> createTemplate = (entity, dp) => new ComboboxViewModel
{
Id = entity.Id,
AffichageCombobox = entity.DateHFin == null ? dp : dp + " (inactif)"
};
// reusing parameter from dsiplayProp lambda
var entityParam = dsiplayProp.Parameters[0];
// injecting dsiplayProp into createTemplate
var selectBody = ExpressionReplacer.GetBody(createTemplate, entityParam, dsiplayProp.Body);
var selectLambda = Expression.Lambda<Func<T, ComboboxViewModel>>(selectBody, entityParam);
// applying projection
var comboQuery = query.Select(selectLambda);
return comboQuery;
}
// helper class for correcting expressions
class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression exp)
{
if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
return replacement;
return base.Visit(exp);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
.ToDictionary(i => (Expression) lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
}
}
}
Well, after writing this sample, I think, it can be cardinally simplified by using LINQKit. Will post another answer with LINQKit usage if you are interested,
So in my global.asax, I've got the following code:
Inventory.BusinessTier bt = new Inventory.BusinessTier();
string UserLogin = bt.ExtractLogin (Request.ServerVariables ["AUTH_USER"]);
Inventory.User myUser = new Inventory.User (UserLogin);
Session ["User"] = myUser;
It works just fine on one development PC, but using the same version of Visual Studio, it craps out on the third line with this error:
System.TypeInitializationException: 'The type initializer for
'Inventory.DataTier' threw an exception.'
Inner Exception
NullReferenceException: Object reference not set to an instance of an
object.
Other than a line adding impersonation in my web.config (it has to be there now), I haven't changed a single thing. Is there a way to get more info on this? I can't even trace it, because if I put a debug line in the User object constructor, it never hits it. I'm at a bit of a loss. Would appreciate any advice.
EDIT to answer questions below:
InventoryUser is a very simple user object that reads the current from the database and stores some basic user info in properties, such as UserID, Role, RoleID, and IsAdmin.
The DataTier class is a class that interacts with the database. It is used in multiple projects, so I'm quite sure it's not the problem. I tried to paste in the code anyway, but it exceeded the limit for a post.
I'm reasonably sure the problem is related to the user class. It's short, so I can paste it in here:
using System;
using System.Data;
// This is the user business object. It contains information pertaining to the current user of the application. Notably, it
// contains the department ID, which determines what inventory items the user will see when using the application. Only
// specified employees with admin access can see all items for all departments, and that is determined by a specific department ID.
namespace Inventory {
public class User {
private Guid _UserID;
private Guid _RoleID;
private Guid _UserDepartmentID;
private string _UserRole = "";
private string _UserName = "";
private bool _IsAuthorizedUser = false;
private bool _IsAdmin = false;
// Attribute declarations
public Guid UserID {
get {
return _UserID;
}
set {
_UserID = value;
}
}
public string UserRole {
get {
return _UserRole;
}
set {
_UserRole = value;
}
}
public Guid RoleID {
get {
return _RoleID;
}
set {
_RoleID = value;
}
}
public string UserName {
get {
return _UserName;
}
set {
_UserName = value;
}
}
public Guid UserDepartmentID {
get {
return _UserDepartmentID;
}
set {
_UserDepartmentID = value;
}
}
public bool IsAdmin {
get {
return _IsAdmin;
}
set {
_IsAdmin = value;
}
}
public bool IsAuthorizedUser {
get {
return _IsAuthorizedUser;
}
set {
_IsAuthorizedUser = value;
}
}
// -----------------
// - Constructor -
// -----------------
public User (string UserLogin) {
string ShortUserLogin = ExtractLogin (UserLogin);
GetUser (ShortUserLogin);
}
// ------------------
// - ExtractLogin -
// ------------------
public string ExtractLogin (string Login) {
// The domain and "\" symbol must be removed from the string, leaving only the user name.
int pos = Login.IndexOf (#"\");
return Login.Substring (pos + 1, Login.Length - pos - 1);
}
// -------------
// - GetUser -
// -------------
// This method is called to fill the user object based on the user's login. It ultimately gets authorized user data
// from the user table.
public void GetUser (string UserName) {
DataTier dt1 = new DataTier();
DataTable dt = dt1.GetUserInfo (UserName);
int RecordCount = dt.Rows.Count;
switch (RecordCount) {
case 1: // There is one user name match, as there should be. This is the likely situation.
DataRow dr = dt.Rows[0];
UserID = (Guid)dr ["UserID"];
UserRole = (string)dr ["UserRole"];
RoleID = (Guid)dr ["RoleID"];
this.UserName = UserName;
UserDepartmentID = (Guid)dr ["DepartmentID"];
IsAdmin = (bool)dr ["IsAdmin"];
IsAuthorizedUser = true;
break;
case 0: // There are no user name matches (unauthorized use).
IsAdmin = false;
IsAuthorizedUser = false;
break;
default: // There are multiple user name matches (problem!).
IsAdmin = false;
IsAuthorizedUser = false;
break;
}
}
}
}
I am trying to use temp data to return messages but it gives an error :
InvalidOperationException: The 'Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TempDataSerializer' cannot serialize an object of type
I am already using
services.AddMvc().AddSessionStateTempDataProvider();
app.UseSession()
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
});
Here is my shared
FlashMessages.cshtml :
#using EnduroMotors.ViewModels
#{
var errorMessages = TempData["_error_messages"] as List<FlashMessageModel>
?? new List<FlashMessageModel>();
var warningMessages = TempData["_warning_messages"] as
List<FlashMessageModel> ?? new List<FlashMessageModel>();
var successMessages = TempData["_success_messages"] as
List<FlashMessageModel> ?? new List<FlashMessageModel>();
var infoMessages = TempData["_info_messages"] as List<FlashMessageModel> ??
new List<FlashMessageModel>();
}
Here is my viewmodel :
FlashMessageModel
public class FlashMessageModel
{
public string Title { get; set; }
public string Message { get; set; }
}
And here is use in controller :
Controller
protected void ShowSuccessMessage(string message, string title =
"Success!")
{
var messages =
(List<FlashMessageModel>)TempData["_success_messages"] ?? new
List<FlashMessageModel>();
messages.Add(new FlashMessageModel
{
Title = title,
Message = message
});
TempData["_success_messages"] = messages;
}
using this with return
ShowSuccessMessage("You have completed.");
it should show success message in index with #{Html.RenderPartial("FlashMessages");} but instead it gives
InvalidOperationException: The 'Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TempDataSerializer' cannot serialize an object of type 'EnduroMotors.ViewModels.FlashMessageModel'.
Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TempDataSerializer.EnsureObjectCanBeSerialized(object item)
TempData serialises objects to strings for storage. It supports string, int and boolean types natively. If you want to store more complex types, you have to serialise (and deserialise) them yourself. JSON is the recommended format. The following extension methods use the JSON.NET JsonConvert static methods to do this:
public static class TempDataExtensions
{
public static void Set<T>(this ITempDataDictionary tempData, string key, T value) where T : class
{
tempData[key] = JsonConvert.SerializeObject(value);
}
public static T Get<T>(this ITempDataDictionary tempData, string key) where T : class
{
tempData.TryGetValue(key, out object o);
return o ?? JsonConvert.DeserializeObject<T>((string)o);
}
}
You can read more about this here: https://www.learnrazorpages.com/razor-pages/tempdata#limitations
In Martin Fowler's "Patterns of Enterprise Application Architecture"
is described approach for organizing DAL like a set of mappers for entities. Each has it's own IdentityMap storing specific entity.
for example in my ASP.NET WebApplication:
//AbstractMapper - superclass for all mappers in DAL
public abstract class AbstractMapper
{
private readonly string _connectionString;
protected string ConnectionString
{
get { return _connectionString; }
}
private readonly DbProviderFactory _dbFactory;
protected DbProviderFactory DBFactory
{
get { return _dbFactory; }
}
#region LoadedObjects (IdentityMap)
protected Hashtable LoadedObjects = new Hashtable();
public void RegisterObject(long id, DomainObject obj)
{
LoadedObjects[id] = obj;
}
public void UnregisterObject(long id)
{
LoadedObjects.Remove(id);
}
#endregion
public AbstractMapper(string connectionString, DbProviderFactory dbFactory)
{
_connectionString = connectionString;
_dbFactory = dbFactory;
}
protected virtual string DBTable
{
get
{
throw new NotImplementedException("database table is not defined in class " + this.GetType());
}
}
protected virtual T Find<T>(long id, IDbTransaction tr = null) where T : DomainObject
{
if (id == 0)
return null;
T result = (T)LoadedObjects[id];
if (result != null)
return result;
IDbConnection cn = GetConnection(tr);
IDbCommand cmd = CreateCommand(GetFindStatement(id), cn, tr);
IDataReader rs = null;
try
{
OpenConnection(cn, tr);
rs = cmd.ExecuteReader(CommandBehavior.SingleRow);
result = (rs.Read()) ? Load<T>(rs) : null;
}
catch (DbException ex)
{
throw new DALException("Error while loading an object by id in class " + this.GetType(), ex);
}
finally
{
CleanUpDBResources(cmd, cn, tr, rs);
}
return result;
}
protected virtual T Load<T>(IDataReader rs) where T : DomainObject
{
long id = GetReaderLong(rs["ID"]);
T result = (T)LoadedObjects[id];
if (result != null)
return result;
result = (T)DoLoad(id, rs);
RegisterObject(id, result);
return result;
}
// another CRUD here ...
}
// Specific Mapper for entity Account
public class AccountMapper : AbstractMapper
{
internal override string DBTable
{
get { return "Account"; }
}
public AccountMapper(string connectionString, DbProviderFactory dbFactory) : base(connectionString, dbFactory) { }
public Account Find(long id)
{
return Find<Account>(id);
}
public override DomainObject DoLoad(long id, IDataReader rs)
{
Account account = new Account(id);
account.Name = GetReaderString(rs["Name"]);
account.Value = GetReaderDecimal(rs["Value"]);
account.CurrencyID = GetReaderLong(rs["CurrencyID"]);
return account;
}
// ...
}
The question is: where to store these mappers? How system services (entities) should call mappers?
I decided to create MapperRegistry containing all mappers. So services can call mappers like:
public class AccountService : DomainService
{
public static Account FindAccount(long accountID)
{
if (accountID > 0)
return MapperRegistry.AccountMapper.Find(accountID);
return null;
}
...
}
But where can I store MapperRegistry instance? I see following variants, but don't like any of them:
MapperRegistry is global for application (Singleton)
Not applicable because of necessity of synchronization in multi-thread ASP.NET application (at least Martin says that only mad can choose this variant)
MapperRegistry per Session
Seems not so good too. All ORMs (NHibernate, LINQ to SQL, EntityFramework) masters advise to use DataContext (NHibernateSession, ObjectContext) per Request and not to store context in Session.
Also in my WebApp almost all requests are AJAX-requests to EntityController.asmx (with attribute ScriptService) returning JSON. And session is not allowed.
MapperRegistry per Request
There are a lot of separate AJAX calls. In this case life cycle of MapperRegistry will be too small. So the data almost always will be retrieved from database, as a result - low performance.
Dear Experts, please help me with architectural solution.
Somehow this update code is not working:
Here is my Controller code:
private UserRepository repo = new UserRepository();
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, UserInfo user_)
{
try
{
repo.UpdateUser(user_);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
Here is the repo code that is used above (UserRepository)
private UsersDataContext db = new UsersDataContext();
public void UpdateUser(UserInfo user_)
{
UserInfo origUser = GetUser(user_.Id);
origUser.First = user_.First;
origUser.Last = user_.Last;
origUser.City = user_.City;
origUser.Country = user_.Country;
origUser.State = user_.State;
origUser.Street_Address = user_.Street_Address;
db.SubmitChanges();
}
public UserInfo GetUser(int id_)
{
return db.UserInfos.SingleOrDefault(d => d.Id == id_);
}
EDIT:
Note that when debugging everything is running fine (no exceptions) but when it redirects back to Index the data has not been updated when the changes from the update.
i just changed the userrepository to the following:
private UsersDataContext db = new UsersDataContext();
public void UpdateUser(UserInfo user_)
{
UserInfo origUser = db.UserInfos.SingleOrDefault(d => d.Id == id_);
origUser.First = user_.First;
origUser.Last = user_.Last;
origUser.City = user_.City;
origUser.Country = user_.Country;
origUser.State = user_.State;
origUser.Street_Address = user_.Street_Address;
db.SubmitChanges();
}
so all i did was move the GetUser() method inline and it worked.
It might have been a red herring and it was just a caching issue . .
You don't mention how you've defined UserInfo, is it a struct or a class?
If it's a struct, returning it from GetUser will create a new object and thus you will not update the database object, only a local copy of it.
Moving the GetUser inline avoid this temp copy creation and that's likely why it's working.
afaik you could do something like
public void GetUser(int id_, out UserInfo user_)
{
user_ = db.UserInfos.SingleOrDefault(d => d.Id == id_);
}
You would then call it like this
public void UpdateUser(UserInfo user_)
{
UserInfo origUser;
GetUser(user_.Id, out origUser);
origUser.First = user_.First;
origUser.Last = user_.Last;
origUser.City = user_.City;
origUser.Country = user_.Country;
origUser.State = user_.State;
origUser.Street_Address = user_.Street_Address;
db.SubmitChanges();
}
Maybe you disabled Object Tracking?