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?

7
  • 1
    For the 2nd one,materialize the query before you call .Select() i.e - var p = context.tbl_prchs.Where(....).ToList().Select({ ...}).ToList(); Commented Aug 12, 2016 at 21:41
  • Well, that would be good option, but wouldn't that be performance overhead? Commented Aug 13, 2016 at 8:23
  • Also, is there any way where I can deal with ToListAsync() instead of ToList()?? Commented Aug 13, 2016 at 8: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. Commented Aug 13, 2016 at 8: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) Commented Aug 13, 2016 at 8:48

1 Answer 1

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();
Sign up to request clarification or add additional context in comments.

9 Comments

Getting Only parameterless constructors and initializers are supported in LINQ to Entities. exception bro.. :( Also, I am using ToListAsync() :( Forgot to mention..
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
Is it CDate to assign or Moment here in the query?
Its likely to be insignificant, but only you can really test it :)
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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.