IS it possible to write a function that accepts
=getContent("keyword",TODAY()-30,TODAY())
what should be the type of parameter then?
tried
public static object test(object date)
{
return DateTime.FromOADate(Convert.ToDouble(date));
} // output 41180
Yes - Excel will evaluate the TODAY() function before passing your function the value. You can declare parameters as DateTime if you want Excel-DNA to interpreted the Excel doubles as Date/Time values. So you might write:
public static object getContent(string keyword, DateTime startDate, DateTime endDate)
{
// do the work...
return result;
}
In the more recent versions of Excel DNA, 'DateTime' parameters are not supported. You will need to use double, and then in your C# method body convert the double to DateTime using DateTime.FromOADate().
public static object getContent(string keyword, double startDate, double endDate)
{
DateTime firstDate = DateTime.FromOADate(startDate);
DateTime lastDate = DateTime.FromOADate(endDate);
// do the work...
return result;
}
Related
I have a db table with a start date DateTime field (SQL Server). In my repository I have a "get all" method that returns all the columns. I also have a viewmodel with a DateTime Startdate field.
Repository property:
public IQueryable<Module> LeadershipModules
{
get
{
return from module in context.Modules where module.ModuleTypeId == 2 select module;
}
}
In my controller, how do I set the StartDate field to a DateTime object?
My controller
public ActionResult Leadership()
{
var viewModel = new LeadershipModulesViewModel
{
LeadershipModules = repository.LeadershipModules,
StartDate = **???**
};
return View(viewModel);
}
When I tried the following:
StartDate = from startDate in repository.LeadershipModules where startDate.StartDate > DateTime.Now select startDate
I get the error below error message
Error 1 Cannot implicitly convert type 'System.Linq.IQueryable<Domain.Module>' to 'System.DateTime' Controllers\ModuleController.cs 41 117 WebUI
What am I doing wrong?
public ActionResult Leadership()
{
DateTime startDate = DateTime.Now;
var modules = repository.LeadershipModules.Where(m => m.StartDate > startDate);
var viewModel = new LeadershipModulesViewModel
{
LeadershipModules = modules,
StartDate = startDate
};
return View(viewModel);
}
You will have to use the Single(), or First() extension methods. Example:
StartDate = (
from module in repository.LeadershipModules
where module.StartDate > DateTime.Now
select module.StartDate)
.First();
The query you defined is an IQueryable<Module> which is basically a collection of Module objects. C# cannot implicitly cast this to a DateTime. You you will have to do the proper mapping, which means select the StartDate object, not the module. And execute the query and get a single item of the list (the first, the last, the only, the max, the min, etc).
I'm still fairly new to ASP.NET and MVC and despite days of googling and experimenting, I'm drawing a blank on the best way to solve this problem.
I wrote a BirthdayAttribute that I want to work similar to the EmailAddressAttribute. The birthday attribute sets the UI hint so that the birthday DateTime will be rendered using an editor template that has 3 dropdown lists. The attribute can also be used to set some additional meta data that tells the year dropdown how many years it should display.
I know I could use jQuery's date picker, but in the case of a birthday I find the 3 dropdowns much more usable.
#model DateTime
#using System;
#using System.Web.Mvc;
#{
UInt16 numberOfVisibleYears = 100;
if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("NumberOfVisibleYears"))
{
numberOfVisibleYears = Convert.ToUInt16(ViewData.ModelMetadata.AdditionalValues["NumberOfVisibleYears"]);
}
var now = DateTime.Now;
var years = Enumerable.Range(0, numberOfVisibleYears).Select(x => new SelectListItem { Value = (now.Year - x).ToString(), Text = (now.Year - x).ToString() });
var months = Enumerable.Range(1, 12).Select(x => new SelectListItem{ Text = new DateTime( now.Year, x, 1).ToString("MMMM"), Value = x.ToString() });
var days = Enumerable.Range(1, 31).Select(x => new SelectListItem { Value = x.ToString("00"), Text = x.ToString() });
}
#Html.DropDownList("Year", years, "<Year>") /
#Html.DropDownList("Month", months, "<Month>") /
#Html.DropDownList("Day", days, "<Day>")
I also have a ModelBinder to rebuild my date afterwards. I've removed the content of my helper functions for brevity, but everything works great up to this point. Normal, valid dates, work just fine for creating or editing my members.
public class DateSelector_DropdownListBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext == null)
throw new ArgumentNullException("controllerContext");
if (bindingContext == null)
throw new ArgumentNullException("bindingContext");
if (IsDropdownListBound(bindingContext))
{
int year = GetData(bindingContext, "Year");
int month = GetData(bindingContext, "Month");
int day = GetData(bindingContext, "Day");
DateTime result;
if (!DateTime.TryParse(string.Format("{0}/{1}/{2}", year, month, day), out result))
{
//TODO: SOMETHING MORE USEFUL???
bindingContext.ModelState.AddModelError("", string.Format("Not a valid date."));
}
return result;
}
else
{
return base.BindModel(controllerContext, bindingContext);
}
}
private int GetData(ModelBindingContext bindingContext, string propertyName)
{
// parse the int using the correct value provider
}
private bool IsDropdownListBound(ModelBindingContext bindingContext)
{
//check model meta data UI hint for above editor template
}
}
Now that I'm looking at it, I should probably be using a nullable DateTime, but that's neither here nor there.
The problem I'm having is with very basic validation of invalid dates such as February 30th, or September 31st. The validation itself works great, but the invalid dates aren't ever saved and persisted when the form is reloaded.
What I'd like is to remember the invalid date of February 30th and redisplay it with the validation message instead of resetting the dropdowns to their default value. Other fields, like the email address (decorated with the EmailAddressAttribute) preserve invalid entries just fine out of the box.
At the moment I am just trying to get the server side validation working. To be honest, I haven't even started thinking about the client side validation yet.
I know there is lots I could do with javascript and ajax to make this problem a moot point, but I would still rather have the proper server side validation in place to fall back on.
I finally managed to solve my problem, so I wanted to share my solution.
DISCLAIMER:
Although I used to be great with .NET 2.0 back in the day, I'm only now updating my skills to the latest versions of C#, ASP.NET, MVC, and Entity Framework. If there are better ways to do anything I've done below please I'm always open to feedback.
TODO:
Implement client side validation for invalid dates such as February 30th. Client side validation for [Required] attribute is already built in.
Add support for cultures so that the date shows up in desired format
The solution came to me when I realized that the problem I was having is that DateTime will not allow itself to be constructed with an invalid date such as February 30th. It simply throws an exception. If my date wouldn't construct, I knew of no way to pass my invalid data back through the binder to the ViewModel.
To solve this problem, I had to do away with the DateTime in my view model and replace it with my own custom Date class. The solution below will provide fully functioning server side validation in the event that Javascript is disabled. In the event of a validation error the invalid selections will persist after the validation message is displayed allowing the user to easily fix their mistake.
It should be easy enough to map this view-ish Date class to the DateTime in your date model.
Date.cs
public class Date
{
public Date() : this( System.DateTime.MinValue ) {}
public Date(DateTime date)
{
Year = date.Year;
Month = date.Month;
Day = date.Day;
}
[Required]
public int Year { get; set; }
[Required, Range(1, 12)]
public int Month { get; set; }
[Required, Range(1, 31)]
public int Day { get; set; }
public DateTime? DateTime
{
get
{
DateTime date;
if (!System.DateTime.TryParseExact(string.Format("{0}/{1}/{2}", Year, Month, Day), "yyyy/M/d", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
return null;
else
return date;
}
}
}
This is just a basic date class that you can construct from a DateTime. The class has properties for Year, Month, and Day as well as a DateTime getter that can try to retrieve you a DateTime class assuming you have a valid date. Otherwise it returns null.
When the built in DefaultModelBinder is mapping your form back to this Date object, it will take care of the Required and Range validation for you. However, we will need a new ValidationAtribute to make sure that invalid dates such as February 30th aren't allowed.
DateValidationAttribute.cs
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public sealed class DateValidationAttribute : ValidationAttribute
{
public DateValidationAttribute(string classKey, string resourceKey) :
base(HttpContext.GetGlobalResourceObject(classKey, resourceKey).ToString()) { }
public override bool IsValid(object value)
{
bool result = false;
if (value == null)
throw new ArgumentNullException("value");
Date toValidate = value as Date;
if (toValidate == null)
throw new ArgumentException("value is an invalid or is an unexpected type");
//DateTime returns null when date cannot be constructed
if (toValidate.DateTime != null)
{
result = (toValidate.DateTime != DateTime.MinValue) && (toValidate.DateTime != DateTime.MaxValue);
}
return result;
}
}
This is a ValidationAttribute that you can put on your Date fields and properties. If you pass in the resource file class and the resource key it will search the corresponding resource file in your "App_GlobalResources" folder for the error message.
Inside the IsValid method, once we're sure we're validating a Date we check it's DateTime property to see if it's not null to confirm that it's valid. I throw in a check for DateTime.MinValue and MaxValue for good measure.
So that's about it really. With this Date class, I managed to do away completely with the custom ModelBinder. This solution relies completely on the DefaultModelBinder, which means all of the validation works right out of the box. It apparently even checks my new DateValidationAttribute, which I was super excited about. I stressed forever thinking I might have to muck with validators in a custom binder. This feels a lot cleaner.
Here is the complete code for the partial view I'm using.
DateSelector_DropdownList.cshtml
#model Date
#{
UInt16 numberOfVisibleYears = 100;
if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("NumberOfVisibleYears"))
{
numberOfVisibleYears = Convert.ToUInt16(ViewData.ModelMetadata.AdditionalValues["NumberOfVisibleYears"]);
}
var now = DateTime.Now;
var years = Enumerable.Range(0, numberOfVisibleYears).Select(x => new SelectListItem { Value = (now.Year - x).ToString(), Text = (now.Year - x).ToString() });
var months = Enumerable.Range(1, 12).Select(x => new SelectListItem { Text = new DateTime(now.Year, x, 1).ToString("MMMM"), Value = x.ToString() });
var days = Enumerable.Range(1, 31).Select(x => new SelectListItem { Value = x.ToString(), Text = x.ToString() });
}
#Html.DropDownList("Year", years, "<Year>") /
#Html.DropDownList("Month", months, "<Month>") /
#Html.DropDownList("Day", days, "<Day>")
I'll also include the attribute I use that sets up the template hint and the number of visible years to show.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class DateSelector_DropdownListAttribute : DataTypeAttribute, IMetadataAware
{
public DateSelector_DropdownListAttribute() : base(DataType.Date) { }
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues.Add("NumberOfVisibleYears", NumberOfVisibleYears);
metadata.TemplateHint = TemplateHint;
}
public string TemplateHint { get; set; }
public int NumberOfVisibleYears { get; set; }
}
I think the solution turned out a lot cleaner than I expected it to. It solves all of my problems in the exact way that I was hoping to. I do wish that I was somehow able to keep the DateTime, but this is the only way I could figure out how to maintain an invalid selection using only server side code.
Are there any improvements you would make?
I am building dynamic linq expressions which is working fine for a single entity.
For example:
I have a class called Employee and empeduinfo
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
public class EmpEduInfo
{
public int Id { get; set; }
public string Name { get; set; }
public int EmpId { get; set; }
}
I need to get all the the employees and empeduinfo class starts with "x"
I prepared expression for startswith("x")
var temp= entities.employees.Include("EmpEduInfo").Where(mydynamicexpression);
In this case it is filtering only parent table not on child.
I need to prepare generic expression so than i need to filter both parent and child objects dynamically.
Without using expression I know a solution:
var temp= (from ee in entities.Employee.Include("EmpEduInfo").Where(x => x.name.StartsWith("t"))
where ee.EmpEduInfo.Where(x => x.name.StartsWith("t")).Count()>0
select ee).ToList();
using expressions I am building generic expression to provide dynamic advance search rather than writing in each and every entity.
Here is my expression details
// Get the method information for the String.StartsWith() method
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
// Build the parameter for the expression
ParameterExpression empparam= Expression.Parameter(typeof(employee), "ename");;
// Build the member that was specified for the expression
MemberExpression field = Expression.PropertyOrField(empparam, "name");
// Call the String.StartsWith() method on the member
MethodCallExpression startsWith = Expression.Call(field, mi, Expression.Constant("t"));
var namelamda = Expression.Lambda<Func<employee, bool>>(startsWith, new ParameterExpression[] { empparam });
var temp = entities.employees.Include("empedudetails").Where(namelamda).ToList();
You can look at the Expression the compiler generates using IQueryable:
IQueryable<Employee> query =
from ee in entities.Employee ...
var expression = query.Expression;
Look at expression in a debugger to see what you need to generate - LINQPad is good for this.
You might want to simplify your query a bit first:
IQueryable<Employee> query =
from ee in entities.Employee.Include("EmpEduInfo")
where
ee.name.StartsWith("t") &&
ee.EmpEduInfo.Any(x => x.name.StartsWith("t"))
select ee;
I was trying in EF 4.0 either we have write DB extentions for the same.
Option is provided in EF 4.1
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application
Thanks.
I used Linq to Entity, for fetch a model from DB
var works = db.Work.Where(a => a.StartDate == DateTime.Now).ToList();
this model contain a DateTime that I want to change it to string before send it to view
beacuase need to show it as a persianDate Time,
like this :
foreach (var item in workslist)
{
item.StartDate = "1391/01/01"; //Just For Exapmle as you know this won't work
}
return View(workslist);
So is there any way to change a data type in a strongly typed model? or other way to do this except save DateTime as String in my DB
You can use the [DisplayFormat] attribute with EditorFor/DisplayFor helpers to format the data.
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy/mm/dd}")]
public DateTime StartDate { get; set; }
Then in view:
#Html.EditorFor(m => m.StartDate)
OR, you can also consider to define separate ViewModel for your 'Work' domain model. Though this approach results in more code, it's provide more flexibility and considered as good practice.
Here is sample code:
public class Work
{
public DateTime StartDate {get;set;}
}
public class WorkViewModel
{
public WorkViewModel()
{
}
public string StartDate { get; set; }
public static WorkViewModel Map(Work domainModel)
{
return new WorkViewModel() {
//Apply your Date format logic
StartDate = domainModel.StartDate.ToString("yyyy/MM/dd")
};
}
}
In action method:
var works = db.Work.Where(a => a.StartDate == DateTime.Now).ToList();
var workViewModels = new List<WorkViewModel>(works.Count);
foreach (var work in works)
workViewModels.Add(WorkViewModel.Map(work));
return View(workViewModels);
you can have a function in your controller to change datetime to string
public string changeDateTimeToPersianString ( DateTime time)
{
// your code
}
then you can call this function from your View.
in Razor something like this
#changeDateTimeToPersianString(workslistitem.StartDate)
I'm using Entity Framework 4.1 Code First. In my entity, I have three date/time properties:
public class MyEntity
{
[Key]
public Id { get; set; }
public DateTime FromDate { get; set; }
public DateTime ToDate { get; set; }
[NotMapped]
public DateTime? QueryDate { get; set; }
// and some other fields, of course
}
In the database, I always have the From/To dates populated. I query against them using a simple where clause. But in the result set, I want to include the date I queried for. I need to persist this for some other business logic to work.
I'm working on an extension method to do this, but I'm running into problems:
public static IQueryable<T> WhereDateInRange<T>(this IQueryable<T> queryable, DateTime queryDate) where T : MyEntity
{
// this part works fine
var newQueryable = queryable.Where(e => e.FromDate <= queryDate &&
e.ToDate >= queryDate);
// in theory, this is what I want to do
newQueryable = newQueryable.Select(e =>
{
e.QueryDate = queryDate;
return e;
});
return newQueryable;
}
This doesn't work. It works if I use an IEnumerable, but I want to keep it as IQueryable so everything runs on the database side, and this extention method can still be used in any part of another query. When it's IQueryable, I get a compile error of the following:
A lambda expression with a statement body cannot be converted to an expression tree
If this was SQL, I would just do something like this:
SELECT *, #QueryDate as QueryDate
FROM MyEntities
WHERE #QueryDate BETWEEN FromDate AND ToDate
So the question is, how can I transform the expression tree I already have to include this extra property assignment? I have looked into IQueryable.Expression and IQueryable.Provider.CreateQuery - there's a solution in there somewhere. Maybe an assignment expression can be appended to the existing expression tree? I'm not familiar enough with the expression tree methods to figure this out. Any ideas?
Example Usage
To clarify, the goal is to be able to perform something like this:
var entity = dataContext.Set<MyEntity>()
.WhereDateInRange(DateTime.Now)
.FirstOrDefault();
And have the DateTime.Now persisited into the QueryDate of the resulting row, WITHOUT having more than one row returned from the database query. (With the IEnumerable solution, multiple rows are returned before FirstOrDefault picks the row we want.)
Another Idea
I could go ahead and map QueryDate like a real field, and set its DatabaseGeneratedOption to Computed. But then I would need some way to inject the "#QueryDate as QueryDate" into the SQL created by EF's select statements. Since it's computed, EF won't try to provide values during update or insert. So how could I go about injecting custom SQL into the select statements?
Ladislav is absolutely right. But since you obviously want the second part of your question to be answered, here is how you can use Assign. This won't work with EF, though.
using System;
using System.Linq;
using System.Linq.Expressions;
namespace SO5639951
{
static class Program
{
static void Main()
{
AdventureWorks2008Entities c = new AdventureWorks2008Entities();
var data = c.Addresses.Select(p => p);
ParameterExpression value = Expression.Parameter(typeof(Address), "value");
ParameterExpression result = Expression.Parameter(typeof(Address), "result");
BlockExpression block = Expression.Block(
new[] { result },
Expression.Assign(Expression.Property(value, "AddressLine1"), Expression.Constant("X")),
Expression.Assign(result, value)
);
LambdaExpression lambdaExpression = Expression.Lambda<Func<Address, Address>>(block, value);
MethodCallExpression methodCallExpression =
Expression.Call(
typeof(Queryable),
"Select",
new[]{ typeof(Address),typeof(Address) } ,
new[] { data.Expression, Expression.Quote(lambdaExpression) });
var data2 = data.Provider.CreateQuery<Address>(methodCallExpression);
string result1 = data.ToList()[0].AddressLine1;
string result2 = data2.ToList()[0].AddressLine1;
}
}
}
Update 1
Here is the same code after some tweaking. I got rid of the "Block" expression, that EF choked on in the code above, to demonstrate with absolute clarity that it's "Assign" expression that EF does not support. Note that Assign works in principle with generic Expression trees, it is EF provider that does not support Assign.
using System;
using System.Linq;
using System.Linq.Expressions;
namespace SO5639951
{
static class Program
{
static void Main()
{
AdventureWorks2008Entities c = new AdventureWorks2008Entities();
IQueryable<Address> originalData = c.Addresses.AsQueryable();
Type anonType = new { a = new Address(), b = "" }.GetType();
ParameterExpression assignParameter = Expression.Parameter(typeof(Address), "value");
var assignExpression = Expression.New(
anonType.GetConstructor(new[] { typeof(Address), typeof(string) }),
assignParameter,
Expression.Assign(Expression.Property(assignParameter, "AddressLine1"), Expression.Constant("X")));
LambdaExpression lambdaAssignExpression = Expression.Lambda(assignExpression, assignParameter);
var assignData = originalData.Provider.CreateQuery(CreateSelectMethodCall(originalData, lambdaAssignExpression));
ParameterExpression selectParameter = Expression.Parameter(anonType, "value");
var selectExpression = Expression.Property(selectParameter, "a");
LambdaExpression lambdaSelectExpression = Expression.Lambda(selectExpression, selectParameter);
IQueryable<Address> finalData = assignData.Provider.CreateQuery<Address>(CreateSelectMethodCall(assignData, lambdaSelectExpression));
string result = finalData.ToList()[0].AddressLine1;
}
static MethodCallExpression CreateSelectMethodCall(IQueryable query, LambdaExpression expression)
{
Type[] typeArgs = new[] { query.ElementType, expression.Body.Type };
return Expression.Call(
typeof(Queryable),
"Select",
typeArgs,
new[] { query.Expression, Expression.Quote(expression) });
}
}
}
No, I don't think there is a solution. It is true that you can modify expression tree but you will get exactly the same exception as you got with your linq query because that query actually is what you will build in expression tree. The problem is not in expression tree but in the mapping. EF can't map QueryData to the result. Moreover you are trying to do projection. Projection can't be done to mapped entity and anonymous type can't be returned from the method.
You can off course do the select you mentioned but simply you can't map it to your entity. You must create a new type for that:
var query = from x in context.MyData
where x.FromDate <= queryDate && x.ToDate >= queryDate
select new MyDateWrapper
{
MyData = x,
QueryDate = queryDate
};
Automapper has Queryable Extensions, i think it can resolve your needs.
You can use ProjectTo to calculate property on runtime.
Ef Core 2 set value to ignored property on runtime
http://docs.automapper.org/en/stable/Queryable-Extensions.html
Example configuration:
configuration.CreateMap(typeof(MyEntity), typeof(MyEntity))
.ForMember(nameof(Entity.QueryDate), opt.MapFrom(src => DateTime.Now));
Usage:
queryable.ProjectTo<MyEntity>();
Thank you for all of the valuable feedback. It sounds like the answer is "no - you can't do it that way".
So - I figured out a workaround. This is very specific to my implementation, but it does the trick.
public class MyEntity
{
private DateTime? _queryDate;
[ThreadStatic]
internal static DateTime TempQueryDate;
[NotMapped]
public DateTime? QueryDate
{
get
{
if (_queryDate == null)
_queryDate = TempQueryDate;
return _queryDate;
}
}
...
}
public static IQueryable<T> WhereDateInRange<T>(this IQueryable<T> queryable, DateTime queryDate) where T : MyEntity
{
MyEntity.TempQueryDate = queryDate;
return queryable.Where(e => e.FromDate <= queryDate && e.ToDate >= queryDate);
}
The magic is that I'm using a thread static field to cache the query date so it's available later in the same thread. The fact that I get it back in the QueryDate's getter is specific to my needs.
Obviously this isn't an EF or LINQ solution to the original question, but it does accomplish the same effect by removing it from that world.