I'm new to EntityFramework and came across a problem with an unsupported function used in the Query.
Therefore I hope the community might help to find a elegant way.
The query tries to find a user based on the username and/or the windows username.
[Flags]
public enum IdentifierType
{
Username = 1,
Windows = 2,
Any = Username | Windows,
}
The following code throws an NotSupportedException because the Enum.HasFlag is cannot be translated into a store expression.
public User GetUser(IdentifierType type, string identifier, bool loadRelations = false)
{
using (var context = contextFactory.Invoke())
{
var user = context.Users.FirstOrDefault(u => u.Username == identifier && type.HasFlag(IdentifierType.Username)
|| u.WindowsUsername == identifier && type.HasFlag(IdentifierType.Windows));
return user;
}
}
If I rewrite the query the old Fashion way, the Query works but the boolean logic is executed in the DB:
public User GetUser(IdentifierType type, string identifier, bool loadRelations = false)
{
using (var context = contextFactory.Invoke())
{
var user = context.Users.FirstOrDefault(u => u.Username == identifier && (type & IdentifierType.Username) == IdentifierType.Username
|| u.WindowsUsername == identifier && (type & IdentifierType.Windows) == IdentifierType.Windows);
return user;
}
}
WHERE ((Username = #username) AND (1 = (3 & (1)))) OR (WindowsUsername
= #windowsusername) AND (2 = (3 & (2))))
How can I force the framework to evaluate the boolean logic before it is sent to the DB, so the binary operation is not done at DB Level?
Any ideas much appreciated!
for example:
public User GetUser(IdentifierType type, string identifier, bool loadRelations = false)
{
using (var context = contextFactory.Invoke())
{
IdentifierType tw = type & IdentifierType.Windows;
IdentifierType tu = type & IdentifierType.Username;
var user = context.Users.FirstOrDefault(u => u.Username == identifier && tu == IdentifierType.Username
|| u.WindowsUsername == identifier && tw == IdentifierType.Windows);
return user;
}
}
But I hope and believe that the database server detect that 1 = (3 & (1)) is a constant
Here you go (I've converted my code to yours so may not be syntactically 100%) ...
public User GetUser(IdentifierType type, string identifier, bool loadRelations = false)
{
using (var context = contextFactory.Invoke())
{
var user = context.Users.FirstOrDefault(u => u.Username == ((type.HasFlag(IdentifierType.Username)) ? identifier : u.Username) &&
&& u.WindowsUsername == ((type.HasFlag(IdentifierType.Username)) ? identifier : u.WindowsUsername);
return user;
}
}
Sorry, I've realized that the question did not point out the effective problem. After reading more about that, I had to rewrite the question and finally got a great answer from Gert Arnold, see here.
The LINQKit provides powerful extensions like the PredicateBuilder to cover dynamic queries with OR conditions.
Related
My generic repository is as follows ( I have similar in synchronious and it works)
public virtual async Task<IEnumerable<TEntity>> GetAsyn(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return await orderBy(query).ToListAsync();
}
else
{
return await query.ToListAsync();
}
}
when I call this:
var custDB = await unitOfWork.CustomerRepository
.GetAsyn()
.Where(c => c.UserId == userID && c.IsDeleted != true)
.FirstOrDefault();
I get the following error...Can someone help me fix this
...Error CS1061 'Task<IEnumerable>' does not contain a
definition for 'Where' and no accessible extension method 'Where'
accepting a first argument of type 'Task<IEnumerable>' could
be found (are you missing a using directive or an assembly
reference?) ..
Because GetAsyn() return Task. You need await result of task.
var result = await unitOfWork.CustomerRepository.GetAsyn()
var custDB = result.Where(c => c.UserId == userID && c.IsDeleted != true)
.FirstOrDefault();
You could make this work by calling
var customers = await unitOfWork.CustomerRepository.GetAsyn();
var customer = customers.Where(c => c.UserId == userID && c.IsDeleted != true).FirstOrDefault();
But that would perform the filtering on the client. And your "generic repository" doesn't really do anything useful, so you should just delete that code and run:
var customer = await db.Customers.Where(c => c.UserId == userID && c.IsDeleted != true).FirstOrDefaultAsync();
GetAsyn returns a task, you should await it first.
But anyway why would you do the filtering on the client? Your GetAsyn method has a filter parameter, so you should write it like this:
var custDbList = await unitOfWork.CustomerRepository
.GetAsyn(c => c.UserId == userID && c.IsDeleted != true)
var custDB = custDbList.FirstOrDefault();
am not sure if this is still valid or needed ,but here is my input
you just need the ToListAsync() method, which is in the System.Linq.Async package .
some code to help
using part :
using System;
sing System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
code :
public async Task<JsonResult> getgaAreasList(string silter)
{
return Json(await new gaAreasVM()
.GetAll()
.Where(x=>x.name == filter)
.ToListAsync());
}
I am using within my code some EF LINQ expressions to keep complex queries over my model in one place:
public static IQueryable<User> ToCheck(this IQueryable<User> queryable, int age, bool valueToCheck = true)
{
return queryable.Where(ToBeReviewed(age, valueToCheck));
}
public static Expression<Func<User, bool>> ToCheck(int age, bool valueToCheck = true)
{
return au => au.Status == UserStatus.Inactive
|| au.Status == UserStatus.Active &&
au.Age.HasValue && au.Age.Value > age;
}
I am then able to use them in queries:
var globalQuery = db.Users.ToCheck(value);
And also in selects:
var func = EntityExtensions.ToCheck(value);
var q = db.Department.Select(d => new
{
OrdersTotal = d.Orders.Sum(o => o.Price),
ToCheck = d.Users.AsQueryable().Count(func),
})
What I am trying to achieve is to actually use the same expression/function within a select, to evaluate it for each row.
var usersQuery = query.Select(au => new {
Id = au.Id,
Email = au.Email,
Status = au.Status.ToString(),
ToBeChecked = ???, // USE FUNCTION HERE
CreationTime = au.CreationTime,
LastLoginTime = au.LastLoginTime,
});
I am pretty that threre would be a way using plain EF capabilities or LINQKit, but can't find it.
Answering my own question :)
As pointed by #ivan-stoev, the use of Linqkit was the solution:
var globalQueryfilter = db.Users.AsExpandable.Where(au => au.Department == "hq");
var func = EntityExtensions.ToCheck(value);
var usersQuery = globalQueryfilter.Select(au => new
{
Id = au.Id,
Email = au.Email,
Status = au.Status.ToString(),
ToBeChecked = func.Invoke(au),
CreationTime = au.CreationTime,
LastLoginTime = au.LastLoginTime,
});
return appUsersQuery;
It's required to use the AsExpandable extension method from Linqkit along with Invoke with the function in the select method.
I want to add one more example:
Expression<Func<AddressObject, string, string>> selectExpr = (n, a) => n == null ? "[no address]" : n.OFFNAME + a;
var result = context.AddressObjects.AsExpandable().Select(addressObject => selectExpr.Invoke(addressObject, "1"));
Also, expression can be static in a helper.
p.s. please not forget to add "using LinqKit;" and use "AsExpandable".
I have one table which contains employees records like first , middle, last name. I have 3 textboxes for entering all these.
Now on .net side I want to write a single LINQ query to filter data based on first name middle name and last name. Any of these 3 fields can be blank.
Is these any way to write a single generic LINQ query?
public IList<Employee> GetEmployees(string first, string middle, string last)
{
var query = context.Employees.AsQueryable();
if (!string.IsNullOrWhiteSpace(first))
{
query = query.Where(x => x.FirstName == first);
}
if (!string.IsNullOrWhiteSpace(middle))
{
query = query.Where(x => x.MiddleName == middle);
}
if (!string.IsNullOrWhiteSpace(last))
{
query = query.Where(x => x.LastName == last);
}
return query.Select(x =>
new Employee
{
FullName = string.Join(" ", new string[] { x.FirstName, x.MiddleName, x.LastName}.Where(y => !string.IsNullOrWhiteSpace(y)))
})
.ToList();
}
You can write one linq query with or condition like bellow
_context.tablename.where(p=>p.firstName.contains(txtFirstName) || p.middleName.contains(txtMiddleName) || p.lastName.contains(txtLastName)).ToList();
Change tablename with you database table in above linq query
when iterating just verify if the criteria in use has value
var criteria = new
{
FirstName = default(string),
MiddleName = default(string),
LastName = "Doe",
};
var query = from record in Context()
where !string.IsNullOrEmpty(criteria.FirstName)
&& record.FirstName == criteria.FirstName
|| !string.IsNullOrEmpty(criteria.MiddleName)
&& record.MiddleName == criteria.MiddleName
|| !string.IsNullOrEmpty(criteria.LastName)
&& record.LastName == criteria.LastName
select record;
Try something like that;
public List<Employee> RetrieveEmployees(string firstName, string lastName, string middleName)
{
var query = from employees in context.Employees
where (string.IsNullOrEmpty(firstName) || employees.FirstName == firstName) &&
(string.IsNullOrEmpty(lastName) || employees.LastName == lastName) &&
(string.IsNullOrEmpty(middleName) || employees.MiddleName == middleName)
select employees;
return query.ToList();
}
We have DB on which CDC is enabled. We had to find the data on basis of date supplied. So we had implemented the data fetch through db functions.
The code block is as follows:
private IQueryable<Letter> GetLetterQuery(DateTime? date = null)
{
IQueryable<Letter> dataSource = null;
dataSource = DBContextHelper.Context.fnGetLetters(date);
return dataSource;
}
public List<Letter> GetLetters(int attemptID, int cycle)
{
var dataSource = GetLetterQuery(DBContextHelper.CurrentDataVersion).Where(u => u.AttemptID == attemptID && u.Cycle == cycle).ToList();
populateLettersChildEntities(dataSource, DBContextHelper.CurrentDataVersion);
return dataSource;
}
private void populateLettersChildEntities(List<Letter> letters, DateTime? date = null)
{
letters.ForEach(u =>
{
u.Documents = DBContextHelper.Context.fnGetDocuments(date).Where(d => d.LetterID == u.ID).ToList();
u.LetterDoctors = DBContextHelper.Context.fnGetLetterDoctor(date).Where(d => d.LetterID == u.ID).ToList();
u.LetterFields = DBContextHelper.Context.fnGetLetterFields(date).Where(d => d.LetterID == u.ID).ToList();
u.LetterDoctors.ForEach(d =>
{
d.Doctor = DBContextHelper.Context.fnGetDoctors(date).FirstOrDefault(q => q.IDdoctor == d.DoctorID);
d.Letter = u;
});
});
}
We had to fill the associated fields. Our problem is that it results in many db calls definitely. Is there any way to reduce the calls? AFAIK we are not able to use the include with such db calls.
Can anyone tell me how to use DataType.Custom in ASP.NET MVC 2?
Don't use DataType.Custom.
Instead use [DataType("YourCustomDataTypeHere")] with an editor/display template named YourCustomDataTypeHere.
I'd read over Brad Wilson's ASP.NET MVC 2 Template series on his blog here. It explains what you're asking better than I could.
Hope that helps.
I haven't used it personally, but looking at the MSDN it seems to just be a matter of setting DataType = DataType.Custom and CustomDataType = "String". Then when you process your model you would check for DataType.Custom and if found perform different operations based on the value in CustomDataType.
Here's the sample they give on the MSDN - How to: Customize Data Field Appearance and Behavior For Non-Intrinsic Data Types in the Data Model:
public partial class TextField : System.Web.DynamicData.FieldTemplateUserControl {
string getNavUrl() {
var metadata = MetadataAttributes.OfType<DataTypeAttribute>().FirstOrDefault();
if (metadata == null)
return FieldValueString;
switch (metadata.DataType) {
case DataType.Url:
string url = FieldValueString;
if (url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
return url;
return "http://" + FieldValueString;
case DataType.EmailAddress:
return "mailto:" + FieldValueString;
default:
throw new Exception("Unknown DataType");
}
}
protected override void OnDataBinding(EventArgs e) {
base.OnDataBinding(e);
if (string.IsNullOrEmpty(FieldValueString))
return;
var metadata = MetadataAttributes.OfType<DataTypeAttribute>().FirstOrDefault();
if (metadata == null || string.IsNullOrEmpty(FieldValueString)) {
Literal literal = new Literal();
literal.Text = FieldValueString;
Controls.Add(literal);
return;
}
if (metadata.DataType == DataType.Url ||
metadata.DataType == DataType.EmailAddress) {
HyperLink hyperlink = new HyperLink();
hyperlink.Text = FieldValueString;
hyperlink.NavigateUrl = getNavUrl();
hyperlink.Target = "_blank";
Controls.Add(hyperlink);
return;
}
if (metadata.DataType == DataType.Custom &&
string.Compare(metadata.CustomDataType, "BoldRed", true) == 0) {
Label lbl = new Label();
lbl.Text = FieldValueString;
lbl.Font.Bold = true;
lbl.ForeColor = System.Drawing.Color.Red;
Controls.Add(lbl);
}
}
}