Incorporate Enum.GetName(...) into Linq Query - c#

I have the enumeration:
public enum CmdType {
[Display(Name = "abc")]
AbcEnumIdentifier = 0,
[Display(Name = "xyz")]
XyzEnumIdentifier = 1,
...
}
I'd like to get the names of each enumeration into my query, but even using .WithTranslations() I'm getting this error:
LINQ to Entities does not recognize the method 'System.String
GetName(System.Type, System.Object)' method, and this method cannot be
translated into a store expression.
The query:
var joinedRecord =
(
from m in mTable
join b in bTable on m.Id equals b.aRefId
select new {
aId = a.Id,
aAttrib1 = a.Attrib1
...
bCmdType = Enum.GetName(typeof(CmdType), b.CmdType)
}
).WithTranslations();
How do I return a generated value using Enum.GetName(...) within the query?

LINQ to entities tries to translate your query to SQL and it fails to do so, because there is no equivalent of the Enum.GetName method in SQL.
You need to materialize the results of the query and convert the enum values to their name in the memory.
var joinedRecords = (
from m in mTable
join b in bTable on m.Id equals b.aRefId
select new {
aId = a.Id,
aAttrib1 = a.Attrib1
...
bCmdType = b.CmdType
}
).AsEnumerable() //Executes the query, further you have simple CLR objects
.Select(o => new {
aId = o.Id,
aAttrib1 = o.Attrib1
...
bCmdTypeName = Enum.GetName(typeof(CmdType), o.CmdType)
});

You are calling Enum.GetName(typeof(CmdType), b.CmdType) which cannot be translated to SQL as the Enum definition is not in the DB if you take a look at your rows you'll see there is an int instead of the name of the Enum value in question.
Try this:
var joinedRecord =
(
from m in mTable
join b in bTable on m.Id equals b.aRefId
select new {
aId = a.Id,
aAttrib1 = a.Attrib1
...
bCmdType = b.CmdType
}
)
.AsEnumerable() // or ToList()
.Select( // map to another type calling Enum.GetName(typeof(CmdType), b.CmdType) )
.WithTranslations();
What this does is that by calling AsEnumerable() or ToList() you are no longer processing an instance of IQueryable<T> (which is what your original query returns, on the bad side once you do this all returned objects will be on memory). So once you have objects in memory you can use them just as any other C# object which should allow you to use the methods you want.

Try casting to AsEnumerable() so you can use LINQ to Objects. LINQ to Entities will try to translate it to SQL for which there is no equivalent:
var joinedRecord =
(from m in mTable
join b in bTable on m.Id equals b.aRefId)
.AsEnumerable()
.Select(x => new {
aId = a.Id,
aAttrib1 = a.Attrib1
...
bCmdType = Enum.GetName(typeof(CmdType), b.CmdType)
})
.WithTranslations();
See http://www.lavinski.me/ef-linq-as-emumerable/

Related

Linq Join query returning empty dataset

I am using below code to join two tables based on officeId field. Its retuning 0 records.
IQueryable<Usage> usages = this.context.Usage;
usages = usages.Where(usage => usage.OfficeId == officeId);
var agencyList = this.context.Agencies.ToList();
var usage = usages.ToList();
var query = usage.Join(agencyList,
r => r.OfficeId,
a => a.OfficeId,
(r, a) => new UsageAgencyApiModel () {
Id = r.Id,
Product = r.Product,
Chain = a.Chain,
Name = a.Name
}).ToList();
I have 1000+ records in agencies table and 26 records in usage table.
I am expecting 26 records as a result with chain and name colums attached to result from agency table.
Its not returning anything. I am new to .net please guide me if I am missing anything
EDIT
#Tim Schmelter's solution works fine if I get both table context while executing join. But I need to add filter on top of usage table before applying join
IQueryable<Usage> usages = this.context.Usage;
usages = usages.Where(usage => usage.OfficeId == officeId);
var query = from a in usages
// works with this.context.usages instead of usages
join u in this.context.Agencies on a.OfficeId equals u.OfficeId
select new
{
Id = a.Id,
Product = a.Product,
Chain = u.Chain,
Name = u.Name
};
return query.ToList();
Attaching screenshot here
same join query works fine with in memory data as you see below
Both ways works fine if I add in memory datasource or both datasource directly. But not working if I add filter on usages based on officeId before applying join query
One problem ist that you load all into memory first(ToList()).
With joins i prefer query syntax, it is less verbose:
var query = from a in this.context.Agencies
join u in this.context.Usage on a.OfficeId equals u.OfficeId
select new UsageAgencyApiModel()
{
Id = u.Id,
Product = u.Product,
Chain = a.Chain,
Name = a.Name
};
List<UsageAgencyApiModel> resultList = query.ToList();
Edit: You should be able to apply the Where after the Join. If you still don't get records there are no matching:
var query = from a in this.context.Agencies
join u in this.context.Usage on a.OfficeId equals u.OfficeId
where u.OfficeId == officeId
select new UsageAgencyApiModel{ ... };
The following code can help to get the output based on the ID value.
Of course, I wrote with Lambda.
var officeId = 1;
var query = context.Agencies // your starting point - table in the "from" statement
.Join(database.context.Usage, // the source table of the inner join
agency => agency.OfficeId, // Select the primary key (the first part of the "on" clause in an sql "join" statement)
usage => usage.OfficeId , // Select the foreign key (the second part of the "on" clause)
(agency, usage) => new {Agency = agency, Usage = usage }) // selection
.Where(x => x.Agency.OfficeId == id); // where statement

Linq query w/ sub query and max

How do I rewrite this SQL into a Linq query?
Plain SQL
SELECT *
FROM contracts
INNER JOIN
(SELECT contractid, max(date) date
FROM contractlogs GROUP BY contractId) b
ON contracts.id = b.contractId
Attempt at Linq
from c in _db.Contracts
join sub in (from cl in _db.ContractLogs
group cl by cl.contractId into g
select new { contractId = g.contractId, changedate = g.Max(x => x.date)})
on c.id equals sub.contractId
select new { c, cl }
Goal of the query is to select all contracts w/ their newest update (first) (in contractLogs). I'm currently stumped on how the select would work. Ideally i'm trying to return an object with c & cl.
You can get the most recent log by sorting them in descending order and taking the first one:
from c in _db.Contracts
let mostRecentContractLog = c.ContractLogs
.OrderByDescending(cl => cl.date)
.FirstOrDefault()
select new { c, mostRecentContractLog }
As you see, I assume you have a navigation property Contract.ContractLogs. It's always strongly recommended to work with navigation properties in stead of manually coded joins.
The most literal translation is going to involve you calling groupby on ContractLogs and then joining that into Contacts. I think the ordering of your operations in your LINQ attempt is a little off however I don't often use the query syntax so I'm not positive about that. Regardless, I think you'd prefer something like this;
_db.ContractLogs.GroupBy(x => x.contractId).Select(x => new { contractid = x.Key, changedate = x.Max(y => y.date) })
With that you can do the join into _db.Contracts but I think you could write it more simply with a where though that might be less optimized by the LINQ to SQL provider. Anyway, just completing the example with a join;
OldQuery.Join(_db.Contracts, cl => cl.contractid,
c => c.contractid, (cl, c) => cl);
In cases like this it's often easier to write the query and subquery separately:
var subQuery =
from cl in _db.ContractLogs
group cl by cl.contractId into g
select new { contractId = g.Key, date = g.Max(cl => cl.date) };
var query =
from c in _db.Contracts
join cl in subQuery on c.contractId equals cl.contractId
select new { contract = c, cl.date };
You can try this:
from c in _db.Contracts
select new
{
c,
cl = _db.ContractLogs.Where(l => l.contractId == c.contractId).OrderByDescending(l => l.date).FirstOrDefault()
}

Type inference failed in the call to 'Join'

I am getting the following error on the word "join" in the code below.
The type of one of the expressions in the join clause is incorrect.
Type inference failed in the call to 'Join'.
var organisationQuery = ClientDBContext.Organisations.Where(x => true);
var orderGrouped = from order in ClientDBContext.Orders.Where(x => true)
group order by order.OrganisationId into grouping
select new { Id = grouping.Key.Value, OrderCount = grouping.Count() };
var orders = from og in orderGrouped
join org in organisationQuery on og.Id equals org.Id
select(x => new OrganisationOrdersReportPoco()
{
OrganisationNameThenCode = org.Name,
TotalOrders = og.OrderCount
});
I don't see a problem with the join clause? Can anyone please advise?
Edit:
This is the piece of SQL I am attempting to write as LINQ.
SELECT grp.OrganisationId,
grp.OrderCount,
organisations.Name
FROM (select OrganisationId,
count(*) as OrderCount
from orders where 1 = 1 group by OrganisationId) grp
LEFT OUTER JOIN organisations on grp.OrganisationId = organisations.OrganisationId
WHERE 1 = 1
I have complicated where clauses on both orders and organisations... simplified for this example.
You are selecting into an anonymous type in the first query:
var orderGrouped = ..
select new { Id = grouping.Key.Value, OrderCount = grouping.Count() };
This 'breaks' the connection with order.
The join looks like it should work for Linq-to-Objects but it can't be converted into SQL.
You'll have to eliminate the anonymous type and somehow make a more direct connection.
I wonder why you don't simply go from Organisations? With a proper mapping using nav-properties it should look like:
from org in ClientDBContext.Organisations
select(x => new OrganisationOrdersReportPoco()
{
OrganisationNameThenCode = org.Name,
TotalOrders = org.Orders.Count
};
using the Id properties should be a little more involved but follow the same pattern.
(Credit to Giorgi Nakeuri)
I was confusing LAMBDA with LINQ expressions.
Replacing my select with this solved it.
select new OrganisationOrdersReportPoco()
{
OrganisationNameThenCode = org.Name,
TotalOrders = og.OrderCount
};

Entity Framework LINQ query left join into new object instead of null

I am basically trying to perform a left outer join in my LINQ query but I want to return an empty instance of the left-joined object instead of null. My solution below results in an error:
The entity or complex type 'SubObject2' cannot be constructed in a LINQ to Entities query.
public MyObjectsHolder GetObjectHolder()
{
using (MyEntities ctx = new MyEntities())
{
var query = (from a in ctx.tableA
join b in ctx.tableB on b.FKID equals a.PKID into b_a
from b in b_a.DefaultIfEmpty()
select new MyObjectsHolder()
{
SubObject1 = a,
SubObject2 = b ?? new SubObject2()
});
return query.FirstOrDefault();
}
}
The LINQ to Entities provider is throwing that because it has no way of properly translating that instantiation into a SQL query (which is what LINQ to Entities does). Since your method is only returning one object, I would recommend that you do the following to compensate:
var query = (from a in ctx.tableA
join b in ctx.tableB on b.FKID equals a.PKID into b_a
from b in b_a.DefaultIfEmpty()
select new MyObjectsHolder()
{
SubObject1 = a,
SubObject2 = b
});
var result = query.FirstOrDefault();
if(result != null && result.SubObject2 == null) result.SubObject2 = new SubObject2();
return result;

Concatenating two ViewModels

I have two instances of the same ViewModel that I would like to concatenate:
var queryNew = from a in ICDUnitOfWork.AlphaGroups.Find()
join e in ICDUnitOfWork.Alphas.Find()
on a.AlphaGroupID equals e.AlphaGroupID into g
join c in ICDUnitOfWork.Codes.Find()
on a.CodeID equals c.CodeID into co
select new HomeSearchViewModel
{
Alphas = g,
AlphaGroups = a,
AlphaGroupCode = co.FirstOrDefault(),
SearchTerm = searchTerm,
AlphasCodes = null
};
var codequery = from a in ICDUnitOfWork.Alphas.Find()
join c in ICDUnitOfWork.Codes.Find()
on a.CodeID equals c.CodeID into g
select new HomeSearchViewModel
{
AlphasCodes = g
};
var allResults = queryNew.Concat(codequery);
This gives me an error stating:
The type 'ICD.ViewModels.HomeSearchViewModel' appears in two
structurally incompatible initializations within a single LINQ to
Entities query. A type can be initialized in two places in the same
query, but only if the same properties are set in both places and
those properties are set in the same order.
How can I join these results together?
Well the solution was really dumb on my part. I added a navigation property to the table I was trying join and everything is working now.
whoops!
Concat isn't really the right thing to do here, a simple for loop should be enough. From the looks of your query you could possibly use the AlphaGroupCode as your unique identifier for the mapping e.g.
var codequery = ...
select new HomeSearchViewModel
{
AlphaGroupCode = c.FirstOrDefault()
AlphasCodes = g
};
foreach (var q in queryNew)
{
q.AlphaCodes = codequery.Where(x => x.AlphaGroupCode == q.AlphaGroupCode)
.FirstOrDefault()
.AlphaCodes;
}
You could try evaluating the queries before hand, calling something like "ToList()":
var allResults = queryNew.ToList().Concat(codequery.ToList());
If you don't mind doing it as two queries then calling AsEnumerable() will concatenate in local memory with no problems.
var result = queryNew.AsEnumerable().Concat(codequery);
Here the AsEnumerable() will still defer execution of the queries (which is what your code seems to suggest), however if you want immediate execution do as Arthur suggests and call a cacheing function e.g. ToList() or ToArray()
You must fill with null all other properties
var codequery = from a in ICDUnitOfWork.Alphas.Find()
join c in ICDUnitOfWork.Codes.Find()
on a.CodeID equals c.CodeID into g
select new HomeSearchViewModel
{
Alphas = null,
AlphaGroups = null,
AlphaGroupCode = null,
SearchTerm = null,
AlphasCodes = g
};
var allResults = queryNew.Concat(codequery);

Categories

Resources