2

In the application am working on, We have an option for each user to select their own timezone and when displaying data for the particular user, we are fetching timezone opted by him and display accordingly. Now as per the answer mentioned here, which is really an awesome one, I went on implementing the mentioned options, i.e. conversion of date at model level, and I have done it as below:

NotificationViewModel.cs

public class NotificationViewModel
{

    public string Text{ get; set; }
    public DateTime Moment
    {
        get
        {
            return _Created;
        }
        set
        {
            _Created = Repository.GetUserTimeZoneDateTime(value);
        }
    }
    private DateTime _Created { get; set; }
    public string Icon { get; set; }
}

Repository.cs

GetUserTimeZoneDateTime has 2 overloads

public static DateTime GetUserTimeZoneDateTime(DateTime dTime)
{
    using (var context = new EntityContext())
    {
         var tZone = context.tbl_usrs.AsNoTracking().FirstOrDefault(x => x.uname == HttpContext.Current.User.Identity.Name).preferred_timezone;
         var tZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tZone);
         return TimeZoneInfo.ConvertTimeFromUtc(dTime, tZoneInfo);
    }
}

public static DateTime GetUserTimeZoneDateTime(EntityContext context, DateTime dTime)
{
    var tZone = context.tbl_usrs.AsNoTracking().FirstOrDefault(x => x.uname == HttpContext.Current.User.Identity.Name).preferred_timezone;
    var tZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tZone);
    return TimeZoneInfo.ConvertTimeFromUtc(dTime, tZoneInfo);
}

In the above case first overload will be called, but then, when called from model level, HttpContext.Current will be null and hence it fails.


In the 2nd approach, I tried, the timezone will be fetched from controller level.

NotificationViewModel.cs

public class NotificationViewModel
{
    public string Text { get; set; }
    public DateTime Moment { get; set; }
    public string Icon { get; set; }
}

TestController.cs

using (var context = new EntityContext())
{
     var localTime = Repository.GetUserTimeZoneDateTime(context, DateTime.UtcNow);
     List<NotificationViewModel> model = new List<NotificationViewModel>();
     int days = DateTime.UtcNow.DayOfWeek - DayOfWeek.Sunday;
     DateTime weekStart = localTime.AddDays(-days);
     DateTime weekEnd = weekStart.AddDays(6);
     var p = context.tbl_prchs
                     .Where(x => x.c_date <= weekEnd && x.c_date >= weekStart)
                     .Select(x => new NotificationViewModel()
                     {
                           Icon = "fa fa-gbp",
                           Moment = Repository.GetUserTimeZoneDateTime(context,x.c_date),
                           Text = "Test notes",
                     }).ToList();
     model.AddRange(p);
}

var localTime = Repository.GetUserTimeZoneDateTime(context, DateTime.UtcNow); fetches proper datetime according to the preferred user timezone. But then Moment= Repository.GetUserTimeZoneDateTime(context,x.c_date), inside linq expression throws error as below

LINQ to Entities does not recognize the method 'System.DateTime GetUserTimeZoneDateTime(Direct_Commercial_Van.Models.EntityDataModel.dcvEntities, System.DateTime)' method, and this method cannot be translated into a store expression.

which is expected. What else options I can try here to achieve this? or how in other ways I can handle timezone issue here?

Community
  • 1
  • 1
Guruprasad J Rao
  • 28,253
  • 13
  • 87
  • 176
  • 1
    For the 2nd one,materialize the query before you call `.Select()` i.e - `var p = context.tbl_prchs.Where(....).ToList().Select({ ...}).ToList();` –  Aug 12 '16 at 21:41
  • Well, that would be good option, but wouldn't that be performance overhead? – Guruprasad J Rao Aug 13 '16 at 08:23
  • Also, is there any way where I can deal with `ToListAsync()` instead of `ToList()`?? – Guruprasad J Rao Aug 13 '16 at 08:46
  • Probably insignificant, but depends how many records your returning. What is poor performance is that `Moment = Repository.GetUserTimeZoneDateTime(context,x.c_date),` is making a database call for each record. –  Aug 13 '16 at 08:46
  • Another option would be to get the `tZone` first (one call only). You could then have a `DateTime CDate` property in the view model which is set to `x.c_date`, and make the `DateTime Moment` a `get` only calculated on the value of `CDate` and `tZone` (tZone` might be injected in a constructor) –  Aug 13 '16 at 08:48
  • Yea.. That's true.. Even I do not like to make database call on every request to fetch the property.. So I was thinking to make it onetime call, but did not knew where to put it.. If you don't mind, could you please explain you last comment with a sample as an answer? – Guruprasad J Rao Aug 13 '16 at 08:50
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/120853/discussion-between-stephen-muecke-and-guruprasad-rao). –  Aug 13 '16 at 08:52

1 Answers1

1

In the first case, you would need to inject HttpContext (or User.Identity.Name) into the model (view a constructor), and from there, into the method via an additional parameter in the method (all very messy and not recommended).

In the second case, you would need to materialize the query first before your .Select()

var p = context.tbl_prchs
    .Where(...)
    .ToList() // materialize query in in-memory set
    .Select(x => new NotificationViewModel()
    {
    }).ToList();

However you code is very inefficient because you calling the database (via the GetUserTimeZoneDateTime() method) for each row your query is returning. You should change you code to get the TimeZoneInfo before calling your query.

// Get the time zone info
var tZone = context.tbl_usrs.AsNoTracking().......'
var tZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tZone);
// Generate the view model
var p = context.tbl_prchs
    .Where(...)
    .ToList() // materialize query in in-memory set
    .Select(x => new NotificationViewModel()
    {
        ....
        Moment = TimeZoneInfo.ConvertTimeFromUtc(x.c_date, tZoneInfo);
    }).ToList();

Alternatively, if you do not want to materialize the query first, you could inject the TimeZoneInfo into your view model and modify the Moment to a calculated property.

public class NotificationViewModel
{
    public string Text { get; set; }
    public DateTime CDate { get; set; }
    public string Icon { get; set; }
    public TimeZoneInfo TimeZoneInfo { get; set; }
    public DateTime Moment
    {
        get { return TimeZoneInfo.ConvertTimeFromUtc(CDate, TimeZoneInfo); }
    }
}

and then the query would be

....
var tZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(tZone);
var p = context.tbl_prchs
    .Where(...)
    .Select(x => new NotificationViewModel()
    {
        ....
        CDate = x.c_date,
        TimeZoneInfo = tZoneInfo
    }).ToList();
  • Getting `Only parameterless constructors and initializers are supported in LINQ to Entities.` exception bro.. :( Also, I am using `ToListAsync()` :( Forgot to mention.. – Guruprasad J Rao Aug 13 '16 at 10:46
  • 1
    In that case, you will need to materialize the query first (but unless you query returns tens of thousands of rows, its nothing to worry about). But I'll edit the answer shortly to show an alternative that I think should work –  Aug 13 '16 at 10:52
  • Is it `CDate` to assign or `Moment` here in the query? – Guruprasad J Rao Aug 13 '16 at 11:17
  • Sorry for asking again, are there any overhead in terms of performance and memory when we materialize the query? – Guruprasad J Rao Aug 13 '16 at 11:18
  • In the first example its assigning a value to `Moment` (with your current view model). In the 2nd example, its assigning values to `Cdate` and `TimeZoneInfo` (and using the modified view model where `Moment` is a calculated property. –  Aug 13 '16 at 11:21
  • 1
    Its likely to be insignificant, but only you can really test it :) –  Aug 13 '16 at 11:22
  • Do I need to use `Moment` property in my view or `CDate` itself? Am getting confused here as in how `CDate` would get userTimezone `DateTime` value because in model we are assigning it to `Moment`. – Guruprasad J Rao Aug 13 '16 at 11:26
  • 1
    No, the `Moment` property is used in the view. In any case, I would favor the first option even though it means materializing the query. –  Aug 13 '16 at 11:28
  • Well, Thank you so much buddy.. :) Always feel special, when you help in such an elegant way and make me understand.. :) Thank you once again from bottom of my heart.. :) – Guruprasad J Rao Aug 13 '16 at 11:30