Retrieving data from database using conditional query concatenation as in SQL query - c#

I have a class which is used to carry filtering data. I want to retrieve data from database based on the filtering options.
My FilteringDto class:
public class FilteringDto
{
public int id { get; set; }
public string search_text { get; set; }
}
I want to retrieve data from CafeTableGroup table. This is what my query looks like:
using (ISession session = SessionFactory.OpenSession)
{
using (ITransaction transaction = session.BeginTransaction())
{
groups = session.CreateCriteria<CafeTableGroup>().List<CafeTableGroup>();
if (string.IsNullOrEmpty(filters.search_text))
{
groups = groups.Where(a => a.field_1.Like(filters.search_text)).ToList();
}
if (filters.id != 0)
{
groups = groups.Where(a => a.field_2== filters.id).ToList();
}
transaction.Commit();
}
}
But I have a problem here. In order to get filtered data, first it retrieves all the data in table and then filters it based on the condition. Is there any way that I can do it using a single query and retrieve only the filtered data not all the datas? Thanks in advance.

Problem in your code is .List<CafeTableGroup>(); which is causing materializing the instances too early. Just delay the call to List.
I am not using your exact example. Also, my code uses IQueryOver instead of CreateCriteria. You can achieve this with code something like below:
public IList<Table1Entity> GetList(FilterParams filterParams = null, PageParams pageParams = null)
{
IList<Table1Entity> instance = null;
Conjunction conjTable1 = Restrictions.Conjunction();
Conjunction conjTable2 = Restrictions.Conjunction();
if(filterParams == null)
filterParams = new FilterParams();
if(!string.IsNullOrEmpty(filterParams.Date))
conjTable1.Add(Restrictions.Eq(Projections.Property<Table1Entity>(x => x.Date), filterParams.Date));
if(!string.IsNullOrEmpty(filterParams.FromTime))
conjTable1.Add(Restrictions.Eq(Projections.Property<Table1Entity>(x => x.FromTime), filterParams.FromTime));
if(!string.IsNullOrEmpty(filterParams.ToTime))
conjTable1.Add(Restrictions.Eq(Projections.Property<Table1Entity>(x => x.ToTime), filterParams.ToTime));
if(!string.IsNullOrEmpty(filterParams.Id))
conjTable1.Add(Restrictions.Eq(Projections.Property<Table1Entity>(x => x.Id), Guid.Parse(filterParams.Id)));
if(!string.IsNullOrEmpty(filterParams.Pid))
conjTable2.Add(Restrictions.Eq(Projections.Property<Table2Entity>(x => x.Pid), Guid.Parse(filterParams.Pid)));
IQueryOver<Table1Entity> query = NHSession.QueryOver<Table1Entity>()
.Where(conjTable1)
.JoinQueryOver(x => x.Table2)
.And(conjTable2);
if(pageParams != null)
query = query.Skip(pageParams.SkipRecords).Take(pageParams.TakeRecords);
instance = query.List();
return instance;
}
This also demonstrates how to implement joins and paging.

Related

Filtering entity with another entity

Im trying to filter an entity with another entity with this approach:
[HttpGet]
public async Task<IActionResult> GetViewIO()
{
int uid = (int)HttpContext.Session.GetInt32("userId").Value;
if(uid == 2)
{
uid = 33;
}
var listAeGrp = _context.ListofAE.FromSql($"Execute ListOfAe {uid}");
var _orders = _context.OrderHeaderView.FromSql(#"OrderHeaderView").OrderByDescending(f => f.id).OrderBy(p => p.platform_id);
var orders = await _orders.Where(x => listAeGrp.Any(y => y.id == x.created_by)).ToListAsync();
return Json(orders);
}
So basically, Im getting the values of listAeGrp and _orders using FromSQL and a stored procedure in MSSQL. listAeGrp relies on the current user that is logged in while _orders has a plain select of data created.
With this separate variable I can check that they have values but when im doing the filtering using .Any of linq, an error is returned:
Could not parse expression 'value(Microsoft.Data.Entity.Query.Internal.EntityQueryable`1[DigitalMVC.API.Models.listofAE]).FromSql("Execute ListOfAe 33", value(System.Object[]))': This overload of the method 'Microsoft.Data.Entity.RelationalQueryableExtensions.FromSql' is currently not supported.
Im currently using VS2015, dnx4.5.1
So my questions is:
Where did I go wrong? Im fairly new to MVC and Linq.
Is there other way to do this?
Thank you so much for the help!
What I did is instead of using an Entity to filter another entity, I created a function that will return a list of int
public List<int>ListOfUsers(int uid)
{
List<int> listOfAeGroup = new List<int>();
var listAeGrp = _context.ListofAE.FromSql($"Execute ListOfAe {uid}");
listOfAeGroup = listAeGrp.Select(x => x.id).ToList();
return listOfAeGroup;
}
Then I used the returned function and use it this way:
List<int> allowedUserId = ListOfUsers(uid);
var orders = await _context.OrderHeaderView.FromSql(#"OrderHeaderView").Where(x => allowedUserId.Contains(x.created_by)).OrderByDescending(f => f.id).OrderBy(p => p.platform_id).ToListAsync();
return Json(orders);

Generic query with Entity Framework ORM

So currently i am writing a specific SQL function to get a specific row from a specific table.
However, I have dozens of tables, and noticed that I am writing these same 'get row' repository functions each time I make a new table.
Is it possible to write a generic function that works for every table, in this case to get a specific row?
Current (Example)
public Purchase GetPurchase(long purchaseId)
{
using (var db = new DbContext(_connStringKey))
{
var result = (from p in db.Purchases
where p.PurchaseId.Equals(purchaseId)
select p).FirstOrDefault();
return result;
}
}
Generic Example (To give you an idea)
public Object GenericGet (string tableName, string rowName, long rowId)
{
using (var db = new DbContext(_connStringKey))
{
var result = (from p in db.tableName
where p.rowName.Equals(rowId)
select p).FirstOrDefault();
return result;
}
}
You can do it using reflection but it is not a good approach. Instead of this, you could try something using the generics aspects of the language, and make sure what you want, for sample:
public T Get<T>(Expression<Func<T, bool>> filter)
where T : class
{
T result;
using (var db = new DbContext(_connStringKey))
{
result = db.Set<T>().FirstOrDefault(filter);
}
return result;
}
Remember that the T must be a reference type, so, define a constraint for class
Then, you could try this:
var product = Get<Product>(p => p.Name == "Something");
var supplier = Get<Supplier>(p => p.Sector == "Manufacturing");

Linq select from CRM public view

I have searching for a while now, but couldn't find how to query from public view. For example, I have predefined public view called Active Accounts and I want data from it.
So far I only know this way, but that not include any views:
using (var xrm = new XrmServiceContext("Xrm"))
{
var activeAccounts = from a in xrm.AccountSet
where a.StateCode == 0
select new { a.Id, a.Name };
// TODO ...
}
But I would like to do it like this (not working, ActiveAccountsView not exist, it's pseudo):
using (var xrm = new XrmServiceContext("Xrm"))
{
var activeAccounts = from a in xrm.ActiveAccountsView
select new { a.Id, a.Name };
// TODO ...
}
Is this even possible?
The query definitions of public views are stored in the savedquery entity, that can be queried using common techniques.
Out-of-the-box views are stored with a fixed ID, so querying Active Accounts on the OrganizationServiceContext object could be done in the following way:
private static IEnumerable<Entity> GetActiveAccounts(OrganizationServiceContext serviceContext)
{
string fetchXml = serviceContext
.CreateQuery("savedquery")
.Where(sq =>
sq.GetAttributeValue<Guid>("savedqueryid") == new Guid("00000000-0000-0000-00AA-000010001002"))
.Select(sq => sq.GetAttributeValue<string>("fetchxml"))
.First();
var request = new RetrieveMultipleRequest
{
Query = new FetchExpression(fetchXml)
};
var response = (RetrieveMultipleResponse) serviceContext.Execute(request);
return response.EntityCollection.Entities;
}
It is not possible to use LINQ here. LINQ relies on the QueryExpression class, but does not implement all its capabilities (OUTER JOIN is a painful omission for example). So, while it is possible to convert a LINQ query to a QueryExpression, the other way around is not.
Paging can be applied by editing the Fetch XML string, but if that is too much hassle, you can also consider to convert the Fetch XML to a QueryExpression and apply paging on that object:
private IEnumerable<Entity> GetActiveAccounts(int pageNumber)
{
string fetchXml = _serviceContext
.CreateQuery("savedquery")
.Where(sq =>
sq.GetAttributeValue<Guid>("savedqueryid") == new Guid("00000000-0000-0000-00AA-000010001002"))
.Select(sq => sq.GetAttributeValue<string>("fetchxml"))
.First();
var conversionRequest = new FetchXmlToQueryExpressionRequest
{
FetchXml = fetchXml
};
var response = (FetchXmlToQueryExpressionResponse)_serviceContext.Execute(conversionRequest);
response.Query.PageInfo = new PagingInfo { Count = 1, PageNumber = pageNumber };
var queryRequest = new RetrieveMultipleRequest
{
Query = response.Query
};
var result = (RetrieveMultipleResponse) _serviceContext.Execute(queryRequest);
return result.EntityCollection.Entities;
}
Additional advantage of the QueryExpression vs. Fetch XML is that it is processed in a bit more efficient way.
The very same can be done with user defined views; these views are stored in the userquery entity. The only difference here is you cannot rely on a fixed view ID. Instead you would need to filter your query on querytype, name, returnedtypecode, ownerid and/or other criteria.
Dynamics CRM also has an OrganizationRequest that allows you to execute the savedquery immediately. However, it returns its result as a resultset XML string, so you would still need to deserialize the response. (A nice example can be found here.) Also, I am not sure if it is possible to limit the result set to a specific page when using the ExecuteByIdSavedQueryRequest:
var request = new ExecuteByIdSavedQueryRequest
{
EntityId = new Guid("00000000-0000-0000-00AA-000010001002")
};
var response = (ExecuteByIdSavedQueryResponse)serviceContext.Execute(request);
string resultset = response.String;

LINQ query for multiple databases c#

I have not found any good material for querying multiple databases using LINQ. I have been using connection strings to change between databases and display data for user, now i want to implement a search function which query's all databases and returns a list instead of selecting the database before hand.
Here's what I've thrown together so far. which returns one list from one database which obviously is not what i want.
public ActionResult getCustomers(string cust)
{
List<trakman_Entities> teInstances = new List<trakman_Entities>();
IEnumerable<customer> customers = null;
for (var i = 1; i < ConfigurationManager.ConnectionStrings.Count; i++)
{
if (ConfigurationManager.ConnectionStrings[i].ConnectionString.ToLower().Contains("metadata"))
{
string con = ConfigurationManager.ConnectionStrings[i].ConnectionString;
teInstances.Add(new trakman_Entities(con));
}
}
foreach (trakman_Entities entitiy in teInstances)
{
customers = entitiy.customers.Where(c => c.code.StartsWith(cust));
}
foreach(customer c in customers)
{
Response.Write(c.code);
Response.Write(c.name);
}
Response.End();
return View(customers);
}
The problem is that you keep reassigning the customers variable in your foreach loop:
foreach (trakman_Entities entitiy in teInstances)
{
// overwrites on each iteration!
customers = entitiy.customers.Where(c => c.code.StartsWith(cust));
}
Instead, consider:
var customers = teInstances.SelectMany(e => e.customers.Where(c => c.code.StartsWith(cust)))
.ToList();
Or, do do the whole thing using a single LINQ query:
// start with the list of connection string settings cast to IEnumerable<T>
var customers = ConfigurationManager.ConnectionStrings.Cast<ConnectionStringSettings>()
// filter to the relevant connection strings
.Where(s => s.ConnectionString.ToLower().Contains("metadata"))
.SelectMany(s => {
// for each connection string, select a data context
using( var context = new trakman_entities(s.ConnectionString)) {
// for each context, select all relevant customers
return context.customers.Where(c => c.code.StartsWith(cust)).ToArray();
} // and dispose of the context when we're done with it
})
.ToList();

Reuse query in many object context

I was working this weekend on parallelizing a section of code using Tasks to run all the queries I needed for a dashboard page.
What I have now is many copy/paste methods with almost exactly the same query and a different line at the very end of the method.
Is there a way to write a query against one object context then detach it and pass to a method?
I want to do something like this:
using(DbContext db = new DbContext)
{
var query = db.cars.where(x => x.make == "Ford");
int handleCounts = getDoorHandleCounts(query);
}
public int getDoorHandleCounts(type? query)
{
using(DbContext db = new DbContext())
{
return query.where(x => x.partType == "DoorHandle").Count();
}
}
Any ideas?
Keep in mind all my count() methods are launched from a Task array so they'll be running in parallel. I need a new object context to run each count query on.
I've done some googling and thought about trying to use a pre-compiled query and call it from different object context's, but my real query is kind of complex with allot of if blocks to determine the where condition. Can you compile a query that isn't really simple?
You can't detach and attach a query from/to a context. However, you could reuse the first expression:
Expression<Func<Car,bool>> InitialSelection()
{
return x => x.make == "Ford";
}
public int GetDoorHandleCounts()
{
using(DbContext db = new DbContext())
{
return db.Cars()
.Where(InitialSelection())
.Where(x => x.partType == "DoorHandle").Count();
}
}
And in your task:
int handleCounts = getDoorHandleCounts();
This only works if the initial query is "simple", i.e. does not contain joins and predicates on joined sets that you should repeat over and over in each getX method.
As an alternative, you could initialize a context and feed it to a method that returns a query:
public IQueryable<Car> GetInitialQuery(DbContext db)
{
return db.Cars().Join(....)
.Where(x => x.make == "Ford")
.Where(....);
}
public int GetDoorHandleCounts()
{
using(DbContext db = new DbContext())
{
return GetInitialQuery(db)
.Where(x => x.partType == "DoorHandle").Count();
}
}
Maybe I'm misunderstanding the question, but wouldn't this do what you're looking for?
using(DbContext db = new DbContext)
{
var carsResult = db.cars.where(x => x.make == "Ford");
int handleCounts = getDoorHandleCounts(carsResult);
}
public int getDoorHandleCounts(IEnumerable<Car> result)
{
return result.where(x => x.partType == "DoorHandle").Count();
}
Edit: never mind, I only now saw your mention of the Task array.
Change
public int getDoorHandleCounts(type? query)
to
public int getDoorHandleCounts(IQueryable<cars> query)
And replace cars with the of objects the query will return.
Edit
I would just recommend passing value you're looking to filter on, like this:
{
...
int handleCounts = getDoorHandleCounts("Ford");
}
public int getDoorHandleCounts(string Make)
{
using(DbContext db = new DbContext())
{
return db.cars.where(x => x.make == Make && x.partType == "DoorHandle").Count();
}
}

Categories