I'm using DynamicLinq on my Net Core Web API. The main purpose is to select data based on multiple columns filtered by OR
This is the Model
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public string Brand { get; set; }
}
The query looks like this. (I create generic method to handle all of the tables)
public static async Task<List<T>> Query<T>(this DbContext context, Dictionary<string, string> filter) where T : class
{
var query = context.Set<T>().AsQueryable();
string whereClause = "";
foreach(var d in filter)
{
if (whereClause != "")
whereClause += " || ";
whereClause += $"{d.Key}.Contains(\"{d.Value}\")";
}
return await query.Where(whereClause).ToListAsync();
}
This code generates no errors but the where clause is skipped which means it's executed like there's no Where and resulted all the data. The result query looks like SELECT Id, Name, Brand FROM Car where it should be like SELECT Id, Name, Brand FROM Car WHERE Name LIKE '%something%' OR Brand LIKE '%something%'
[EDIT]
Trying this online example works well when running customers.AsQueryable().Where("Name.Contains(\"David\") || Name.Contains(\"Gail\")")
I tested your generic method in both Dotnet 5 & Dotnet 6. everything was work correctly. I think your problem solve if you use the latest version of Dotnet & EfCore & Dynamic LINQ.
Related
I'm having an issue where objects are coming back as null even if they passed linq tests and I can see the values in the db, and I am stuck unsure where to go to fix this. I'm not normally a c# developer so this is new territory for me.
My table looks like
Class Meeting {
...
public virtual List<MeetingParticipant> Participants { get; set; }
...
}
Class MeetingParticipant {
public bool isOrganiser { get; set; }
public Account Participant { get; set; }
public ParticipatingState ParticipatingState { get; set; }
public string responseText { get; set; }
}
the only bind i have is: modelBuilder.Entity<Meeting>().OwnsMany(meeting => meeting.Participants);
and my linq command is:
var meetings = (from m in _context.Meetings
where m.Participants.Any(val => val.Participant.PhoneNumber == passedInPhoneNumber && val.ParticipatingState == ParticipatingState.Pending)
select m);
Annoyingly when I dig into the meetup objects that are returned, there is participants however their Account object is null. However, for the meetup to pass the linq request, it had to exist so I could compare its phone number.
What am I missing?
A simple adjustment to your Linq command should get you the results you want:
var meetings = from m in _context.Meetings.Include(val => val.Participant)
where m.Participants.Any(val => val.Participant.PhoneNumber == passedInPhoneNumber && val.ParticipatingState == ParticipatingState.Pending)
select m;
The .Include(val => val.Participant) is the magic here - it tells EF to "eagerly" load and populate that entity in your results.
Learn more about eager loading here: https://www.entityframeworktutorial.net/eager-loading-in-entity-framework.aspx
Edit: As mentioned in Beau's comment, for this to work, you need to add the following using statement:
using System.Data.Entity;
I want to retrieve only one column of my data base. The following code works truly:
This is my customer in mysql-table/model-in-EF6
public partial class customers
{
public customers()
public int CustomerID { get; set; }
public string FullName { get; set; }
public string Mobile { get; set; }
public string Email { get; set; }
public string Address { get; set; }
public string Image { get; set; }
}
public List<customers> GetAllCustomers()
{
return myContext.customers.ToList();
}
This is my question:
var GetOneColumn = myContext.CustomerRepository.GetAllCustomers().Select(f=>f.FullName);
Does it retrieve all columns from customers in database and then select only one column (FullName) from retrieved data or not, it retrieve just only one column(FullName) from data base? If it retrieve all data from data base what is the correct code (Linq)?
How could I find that??
Since you're using a .ToList() EF will
Retrieve all customers from database
Map them to customer objects
Later, when you compute GetOneColumn, you do a projection on them (iterating through already materialized object list)
To retrieve only one column,
Remove the .ToList() from the repository, and return a IQueryable<Customers>
Call a .ToList() after your select var GetOneColumn = myContext.CustomerRepository.GetAllCustomers().Select(f=>f.FullName).ToList();
So, your code would be
public IQueryable<customers> GetAllCustomers()
{
return myContext.customers;
}
// later in code
var GetOneColumn = myContext.CustomerRepository.GetAllCustomers().Select(f=>f.FullName).ToList();
See what's going on for yourself! Break up your code into steps and debug:
var allCustomers = myContext.CustomerRepository.GetAllCustomers();
var allCustomerNames = allCustomers.Select(f=>f.FullName);
Alternatively, run a profiler on your DB, or enable logging of queries in EF
To see all the queries that EF is generating you can do something like this
using (var context = new BlogContext())
{
context.Database.Log = Console.Write;
// Your code here...
}
See more details in the docs and Log Queries executed by Entity Framework DbContext
If you read this far, then it's worth knowing what will actually cause EF to send a query - see How Queries Work
Basically, this happens whenever you start enumerating through the elements of an IQueryable<T> (including LINQ methods like First(), Last(), Single(), ToList(), ToArray(), and the like)
Assume entity, Patient exists in the context, which has been generated using the database-first approach, so I can not modify the database.
public class Patient
{
public string Id { get; set; }
public string Surname { get; set; }
public string Forename { get; set; }
}
I would like to filter patients by name e.g. first name or last name or full name. I currently have the following query:
await query
.Where(p => p.Forename != null && p.Forename.ToLower().Contains(filter) ||
p.Surname != null && p.Surname.ToLower().Contains(filter))
.ToListAsync();
This obviously only checks the forename/surname columns individually. If the filter is the full name, it does not work.
I tried string interpolation within the where clause to apply the contains filter against the combination of forename and surname but it is not supported in ef core and executes locally. Since there are over a million patients in the database, executing the query locally in the application is not an option and must be done in the database (it takes over a minute for a search).
Is there any way to solve this?
Interpolation can't be translated by EF.
Just use the '+' operator to concatenate the strings like below:
await query
.Where(p => p.Forename.ToLower().Contains(filter) ||
p.Surname.ToLower().Contains(filter)) ||
(p.Forename + " " + p.Surname).ToLower().Contains(filter))
.ToListAsync();
I am having a terrible time trying to get a LINQ statement working.
I have tried using both SQL syntax and lambda following this post:
C# Joins/Where with Linq and Lambda
This is what my working SQL looks like:
SELECT ws_lookup_OccupationGroup.Code
FROM ws_lookup_OccupationGroup
INNER JOIN ws_lookup_Occupation ON
ws_lookup_OccupationGroup.Code = ws_lookup_Occupation.ws_lookup_OccupationGroup_Code
WHERE (ws_lookup_Occupation.Code = N'413')
This is my first attempt and it yields no results:
var query = from occupationGroup in db.ws_lookup_OccupationGroups
join occupations in db.ws_lookup_Occupations on occupationGroup.Code equals occupations.Code
where occupations.Code == model.Client.Client_Details_Enhanced.Occupation.Code
select new
{
OccupationGroup = occupationGroup,
Occupations = occupations
};
Here is my second attempt using Lamdba which also yields no results:
var queryLambda = db.ws_lookup_OccupationGroups
.Join(db.ws_lookup_Occupations,
occupation => occupation.Code,
occupationGroup => occupationGroup.Code,
(occupation, occupationGroup) => new
{
OCCUPATION = occupation,
OCCUPATIONGROUP = occupationGroup
})
.Where(all => all.OCCUPATION.Code == model.Client.Client_Details_Enhanced.Occupation.Code);
I just cannot see what is going wrong...
I don't know is this has any relevance but I am using Code First Entity Framework - he is my model for OccupationGroups & Occupations:
public class ws_lookup_OccupationGroup {
[Key]
[MaxLength(250)]
public string Code { get; set; }
[MaxLength(250)]
public string Name { get; set; }
public int SortOrder { get; set; }
public List<ws_lookup_Occupation> Occupations { get; set; }
}
public class ws_lookup_Occupation {
[Key]
[MaxLength(10)]
public string Code { get; set; }
[MaxLength(250)]
public string Name { get; set; }
[MaxLength(250)]
public string BarbadosMotorFactor { get; set; }
[MaxLength(250)]
public string TrinidadMotorFactor { get; set; }
[MaxLength(250)]
public string OtherRegionsMotorFactor { get; set; }
}
Instead of directly answering your question I will rather come with a suggestion of strategy. One strategy then is to add an extension method that will reveal the SQL your Entity Framework query or IQueryable will run. This can be done in such a manner that you create a unit test and do a Test Driven Development approach or TDD.
You know the SQL you want to get the expected result. It is better then to work with your EF query until you get a SQL that will deliver the result you are after. You can debug an integration test and then work your way towards the end result - the SQL you are after - written in Entity Framework Linq to Entities code.
First off, we can create the following extension method:
public static class IQueryableExtensions
{
/// <summary>
/// Shows the sql the IQueryable query will be generated into and executed on the DbServer
/// </summary>
/// <param name="query">The IQueryable to analyze</param>
/// <param name="decodeParameters">Set to true if this method should try decoding the parameters</param>
/// <remarks>This is the generated SQL query in use for Entity Framework</remarks>
public static string ShowSql(this IQueryable query, bool decodeParameters = false)
{
var objectQuery = (ObjectQuery)query;
string result = ((ObjectQuery)query).ToTraceString();
if (!decodeParameters)
return result;
foreach (var p in objectQuery.Parameters)
{
string valueString = p.Value != null ? p.Value.ToString() : string.Empty;
if (p.ParameterType == typeof(string) || p.ParameterType == typeof(DateTime))
valueString = "'" + valueString + "'";
result = result.Replace("#" +p.Name, p.Value != null ? valueString : string.Empty);
}
return result;
}
}
Then we need some integration test, sample from my own system:
[TestFixture]
public class IqueryableExtensionsTest
{
[Test]
public void QueryableReturnsSqlAndDoesNotThrow()
{
using (var dbContext = ObjectContextManager.ScopedOpPlanDataContext)
{
var operations = from operation in dbContext.Operations
where operation.Status == (int) OperationStatusDataContract.Postponed
&& operation.OperatingDate >= new DateTime(2015, 2, 12)
select operation;
string sql = operations.ShowSql();
Assert.IsNotNull(sql);
}
}
}
While you can of course use Linqpad to find the EF query and SQL you are after for, the benefit of this strategy is that you can use it inside Visual Studio for the more complex real world scenarios, you also get a better debugging experience than switching between VS and Linqpad.
If you debug such an integration test you can then watch the SQL being generated. Note that you also can do Console.WriteLine or Debug.WriteLine to watch the output if you want to run the Integration Test and not debug it.
In your SQL you are joining on the following
ws_lookup_OccupationGroup.Code = ws_lookup_Occupation.ws_lookup_OccupationGroup_Code
But in the Linq you join on
occupationGroup.Code equals occupations.Code
Depending on exactly what your entity looks like I would assume you actually need this
occupationGroup.Code = occupations.ws_lookup_OccupationGroup_Code
Based on your entity it looks like you can do the following with navigation properties instead of joins
var query = from occupationGroup in db.ws_lookup_OccupationGroups
where occupationGroup.Occupations.Any(
o => o.Code == model.Client.Client_Details_Enhanced.Occupation.Code)
select occupationGroup;
To get all the occupation groups that have at least one occupation with the desired code. Or if you just want a combination of group and occupation then you could do
var query = from occupationGroup in db.ws_lookup_OccupationGroups
from occupation in occupationGroup.Occupations
where occupation.Code == model.Client.Client_Details_Enhanced.Occupation.Code
select new
{
occupationGroup,
occupation
};
I want to pass a property list of a class to a function. with in the function based on property list I'm going to generate a query. As exactly same functionality in Linq Select method.
Here I'm gonna implement this for Ingress Database.
As an example,
in front end I wanna run a select as this,
My Entity Class is like this
public class Customer
{
[System.Data.Linq.Mapping.ColumnAttribute(Name="Id",IsPrimaryKey=true)]
public string Id { get; set; }
[System.Data.Linq.Mapping.ColumnAttribute(Name = "Name")]
public string Name { get; set; }
[System.Data.Linq.Mapping.ColumnAttribute(Name = "Address")]
public string Address { get; set; }
[System.Data.Linq.Mapping.ColumnAttribute(Name = "Email")]
public string Email { get; set; }
[System.Data.Linq.Mapping.ColumnAttribute(Name = "Mobile")]
public string Mobile { get; set; }
}
I wanna call a Select function like this,
var result = dataAccessService.Select<Customer>(C=>C.Name,C.Address);
then,using result I can get the Name and Address properties' values.
I think my Select function should looks like this,
( *I think this should done using Linq Expression. But im not sure what are the input parameter and return type. * )
Class DataAccessService
{
// I'm not sure about this return type and input types, generic types.
public TResult Select<TSource,TResult>(Expression<Func<TSource,TResult>> selector)
{
// Here I wanna Iterate through the property list, which is passed from the caller.
// Here using the property list,
// I can get the ColumnAttribute name value and I can generate a select query.
}
}
This is a attempt to create a functionality like in Linq. But im not an expert in Linq Expressions.
There is a project call DbLinq from MIT, but its a big project and still i couldn't grab anything helpful from that.
Can someone please help me to start this, or can someone link me some useful resources to read about this.
What you're trying to do is creating a new anonymous type that consists of Name and Address. This is easily achievable via long form linq (I made that term up, for lack of a better explanation.) Here's a sample from Microsoft, link provided below:
public void Linq11()
{
List<Product> products = GetProductList();
var productInfos =
from p in products
select new { p.ProductName, p.Category, Price = p.UnitPrice };
Console.WriteLine("Product Info:");
foreach (var productInfo in productInfos)
{
Console.WriteLine("{0} is in the category {1} and costs {2} per unit.", productInfo.ProductName, productInfo.Category, productInfo.Price);
}
}
Details: Linq Select Samples
Update:
So are you trying to do something like this then?
var result = dataAccessService.Select<Customer>(c => c.Name, c => c.Address);
public object[] Select<TSource>(params Expression<Func<TSource, object>>[] selectors)
{
var toReturn = new object[selectors.Count()];
foreach (var s in selectors)
{
var func = s.Compile();
//TODO: If you implement Select a proper extension method, you can easily get the source
toReturn[i] = func(TSource);
}
return toReturn;
}
I don't understand why you're trying to implement Select as a function of DataAccessService? Are trying to create this as an extension method rather?
If this is not what you mean though, you need to rephrase you're question big time and as one commenter suggested, tell us what you need not how you want us to design it.