Select column from non-generic DbSet? - entity-framework-4.1

I want to implement a function that accepts a DbSet (non-generic), a string, and object, and returns DbSet. something like the following pseudu:
public static DbSet Any(DbSet set, string propertyName, objectParameter)
{
var tableName = set.TableName;
var columnName = set.GetColumnNameForProperty(propertyName);
var query = string.Format("SELECT TOP(1) {0} FROM {1} WHERE {0} = {2}",
columnName,
tableName,
objectParameter);
}
I think that SQL query is enough since I'll be able to execute it directly on the Database (context.Database.ExecuteSql).
What I want to do is get the table name from the given DbSet, then the column name in the database.

It is not possible from non generic DbSet but this problem can be easily solved by using:
public static IEnumerable<T> Any(DbSet<T> set, string property, objectParameter)
where T : class
{ ... }
Returning DbSet doesn't make sense because once you query data it is not DbSet anymore.
The bigger problem is getting table name from generic DbSet / ObjectSet because this information is not available from those classes. It is almost impossible to get it at all because it requires accessing non public members of items from MetadataWorkspace.

Related

How do you avoid the EF Core 3.0 "Restricted client evaluation" error when trying to order data from a one-to-many join?

I have a table I'm displaying in a site which is pulling data from a few different SQL tables. For reference, I'm following this guide to set up a sortable table. Simplify the model, say I have a main class called "Data" which looks like this (while the Quotes class stores the DataID):
namespace MyProject.Models
{
public class Data
{
public int Id { get; set; }
public string Name { get; set; }
public int LocationId { get; set; }
public Models.Location Location { get; set; }
public IList<Models.Quote> Quotes { get; set; }
}
}
Then I retrieve the IQueryable object using this code:
IQueryable<Models.Data> dataIQ = _context.Data
.Include(d => d.Quotes)
.Include(d => d.Location);
The "Quotes" table is a one-to-many mapping while the location is a one-to-one. How do I order the IQueryable object by a value in the Quotes table? Specifically I'm trying to do this when the user clicks a filter button. I tried just doing this on the first item in the list (which is guaranteed to be populated) but that throws the client-evaluation error I mentioned in the title. This is the code I'm using to apply the sorting:
//This one throws the client-evaluation error
dataIQ = dataIQ.OrderByDescending(d => d.Quotes[0].QuoteName);
//This one works as expected
dataIQ = dataIQ.OrderByDescending(d => d.Location.LocationName);
So you have a table called Models, filled with objects of class DataItems. and you have a table Quotes. There is a on-to-many relations between DataItems and Quotes: Every DataItem has zero of more Quotes, every Quote belongs to exactly one DataItem, namely the DataItem that the foreign key DataItemId refers to.
Furthermore, every Quote has a property QuoteName.
Note that I changed the identifier of your Data class, to DataItem, so it would be easier for me to talk in singular and plural nouns when referring to one DataItem or when referring to a collection of DataItems.
You want to order your DataItems, in ascending value of property QuoteName of the first Quote of the DataItem.
I see two problems:
What if a DataItem doesn't have any quotes?
Is the term "First Quote` defined: if you look at the tables, can you say: "This is the first Quote of DataItem with Id == 4"?
This is the reason, that it usually is better to design a one-to-many relation using virtual ICollection<Quote>, then using virtual IList<Quote>. The value of DataItem[3].Quotes[4] is not defined, hence it is not useful to give users access to the index.
But lets assume, that if you have an IQueryable<Quote>, that you can define a "the first quote". This can be the Quote with the lowest Id, or the Quote with the oldest Date. Maybe if it the Quote that has been Quoted the most often. In any case, you can define an extension method:
public static IOrderedQueryable<Quote> ToDefaultQuoteOrder(this IQueryable<Quote> quotes)
{
// order by quote Id:
return quotes.OrderBy(quote => quote.Id);
// or order by QuoteName:
return quotes.OrderBy(quote => quote.QuoteName);
// or a complex sort order: most mentioned quotes first,
// then order by oldest quotes first
return quotes.OrberByDescending(quote => quote.Mentions.Count())
.ThenBy(quote => quote.Date)
.ThenBy(quote => quote.Id);
}
It is only useful to create an extension method, if you expect it to be used several times.
Now that we've defined a order in your quotes, then from every DataItem you can get the first quote:
DataItem dataItem = ...
Quote firstQuote = dataItem.Quotes.ToDefaultQuoteOrder()
.FirstOrDefault();
Note: if the dataItem has no Quotes at all, there won't be a firstQuote, so you can't get the name of it. Therefore, when concatenating LINQ statements, it is usually only a good idea to use FirstOrDefault() as last method in the sequence.
So the answer of your question is:
var result = _context.DataItems.Select(dataItem => new
{
DataItem = dataItem,
OrderKey = dataItem.Quotes.ToDefaultQuoteOrder()
.Select(quote => quote.QuoteName)
.FirstOrDefault(),
})
.OrderBy(selectionResult => selectionResult.OrderKey)
.Select(selectioniResult => selectionResult.Data);
The nice thing about the extension method is that you hide how your quotes are ordered. If you want to change this, not order by Id, but by Oldest quote date, the users won't have to change.
One final remark: it is usually not a good idea to use Include as a shortcut for Select. If DataItem [4] has 1000 Quotes, then every of its Quote will have a DataItemId with a value of 4. It is quite a waste to send this value 4 for over a thousand times. When using Select you can transport only the properties that you actually plan to use:
.Select(dataItem => new
{
// Select only the data items that you plan to use:
Id = dataItem.Id,
Name = dataItem.Name,
...
Quotes = dataItem.Quotes.ToDefaultQuoteOrder().Select(quote => new
{
// again only the properties that you plan to use:
Id = quote.Id,
...
// not needed, you know the value:
// DataItemId = quote.DataItemId,
})
.ToList(),
});
In entity framework always use Select to select data and select only the properties that you really plan to use. Only use include if you plan to change / update the included data.
Certainly don't use Include because it saves you typing. Again: whenever you have to do something several times, create a procedure for it:
As an extension method:
public static IQueryable<MyClass> ToPropertiesINeed(this IQueryable<DataItem> source)
{
return source.Select(item => new MyClass
{
Id = item.Id,
Name = item.Name,
...
Quotes = item.Quotes.ToDefaultQuoteOrder.Select(...).ToList(),
});
}
Usage:
var result = var result = _context.DataItems.Where(dataItem => ...)
.ToPropertiesINeed();
The nice thing about Select is that you separate the structure of your database from the actually returned data. If your database structure changes, users of your classes won't have to see this.
Ok, I think I figured it out (at least partially**). I believe I was getting the error because what I had was really just not correct syntax for a Linq query--that is I was trying to use a list member in a query on a table that it didn't exist in (maybe?)
Correcting the syntax I was able to come up with this, which works for my current purposes. The downside is that it's only sorting by the first item in the link. I'm not sure how you'd do this for multiple items--would be interested to see if anyone else has thoughts
dataIQ = dataIQ.OrderByDescending(d => d.Quotes.FirstOrDefault().QuoteName);
**Edit: confirmed this is only partially fixing my issue. I'm still getting the original error if I try to access a child object of Quotes. Anyone have suggestions on how to avoid this error? The below example still triggers the error:
IQueryable<Models.Data> dataIQ = _context.Data
.Include(d => d.Quotes).ThenInclude(q => q.Owner)
.Include(d => d.Location);
dataIQ = dataIQ.OrderByDescending(d => d.Quotes.FirstOrDefault().Owner.OwnerName);

Entity Framework 5 - T4 generated context class causing 'duplicate parameter name'

I'm using EF5.0 in an ASP.NET MVC app. My Entity Model is named 'DataModel'. Included in the model is a table-valued function that exists in my MSSQL database, named MatchingEntries. It returns a table of integer ids.
I've looked at the DataModel.Context.cs file, that gets generated via the .tt (T4) template file. It has the following code in it:
[EdmFunction("DataEntities", "MatchingEntries")]
public virtual IQueryable<Nullable<int>> MatchingEntries(string term)
{
var termParameter = term != null ?
new ObjectParameter("Term", term) :
new ObjectParameter("Term", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<Nullable<int>>("[DataEntities].[MatchingEntries](#Term)", termParameter);
}
The error I am getting results from using this method twice within the one query, such as:
IQueryable<int> one = db.MatchingEntries("\"one*\"");
IQueryable<int> two = db.MatchingEntries("\"two*\"");
List<int> both = one.Intersect(two).ToList();
The error is:
A parameter named 'Term' already exists in the parameter collection. Parameter names must be unique in the parameter collection.
Parameter name: parameter
Is this a known limitation of the classes generated from an EDMX for table-valued functions? With LINQ2SQL I am able to execute this a a single query to the database (that does a JOIN between the 2 outputs from MatchingEntries) and it replaces the parameter name #Term with #p0 and #p1 for the two different instances of the call. I'd like to make Entity Framework do the same.
So, my question is, how can I get EF to work in the same manner and avoid the 'Duplicate parameter' error?
My fallback is to evaluate each call to db.MatchingEntries separately, by putting ToList() after them. My other idea has been to replace the ObjectParameter name in the T4 generated Context.cs class with something randomly generated each time. These feel like hacks that I should be able to avoid.
This answer is Linq to Entities specific. This doesn't have to be done in Linq to SQL (Linqpad).
Thanks to this question I got a pointer to a viable solution:
extend the autogenerated DBContext class (partial class)
add a method with two parameters in the partial class
at calling, pass an index as second parameter
Detailed Answer:
DataEntitys.my.cs:
[EdmFunction("DataEntities", "MatchingEntries")]
public virtual IQueryable<Nullable<int>> MatchingEntries(string term, int index)
{
string param_name = String.Format("k_{0}", index);
var termParameter = term != null ?
new ObjectParameter(param_name, term) :
new ObjectParameter(param_name, typeof(string));
return ((IObjectContextAdapter)this).
ObjectContext.CreateQuery<Nullable<int>>(
String.Format("[DataEntities].[MatchingEntries](#{0})", param_name),
termParameter);
}
Call the function:
foreach (string teil in such)
{
index++;
if (teil.Trim() != "")
res = res.Join(db.MatchingEntries("\"" + teil + "*\"", index), l => l.ID, s => s.KEY, (l, s) => l);
}

Unable to create a constant value of type 'T'

I have a table called Subjects,
I have an another Table called Allocations, which stores the Allocations of the Subjects
I have a Datagridview, which is populated with Subject Allocations from the Allocations Table
Now i need to get the Subjects that are not in the Datagridview
To do this
I Get All Subjects from the ObjectContext
Now i get all the Subjects that are alloted from the Datagridview (It Returns me an InMemory Collection)
Now i use the LINQ.EXCEPT method to filter the results, but it is throwing me the Following Exception,
"Unable To Create Constant Value of Type "ObjectContext.Subjects" Only primitive types ('such as Int32, String, and Guid') are supported in this context."
Below is my Code
public static IOrderedQueryable<Subject> GetSubjects()
{
return OBJECTCONTEXT.Subjects.OrderBy(s => s.Name);
}
private IQueryable<Subject> GetAllocatedSubjectsFromGrid()
{
return (from DataGridViewRow setRow in dgv.Rows
where !setRow.IsNewRow
select setRow.DataBoundItem).Cast<Allocation>() //I know the Problem lies somewhere in this Function
.Select(alloc =>alloc.Subject).AsQueryable();
}
private void RUN()
{
IQueryable<Subject> AllSubjects = GetSubjects(); //Gets
IQueryable<Subject> SubjectsToExclude = GetAllocatedSubjectsFromGrid();
IQueryable<Subject> ExcludedSubjects = AllSubjects.Except(SubjectsToExclude.AsEnumerable());
//Throwing Me "Unable to create a constant value of type 'OBJECTCONTEXT.Subject'. Only primitive types ('such as Int32, String, and Guid') are supported in this context."
}
As a result of googling i found that it happens because LINQ can't compare between InMemory collection(Records from DGV) and Objectcontext(FromDB)
A little short of time, have not tested it. But I guess you can try to get it all in memory. So instead of using
IQueryable<Subject> AllSubjects = GetSubjects(); //Gets
You do
List<Subject> AllSubjects = GetSubjects().ToList(); //
List<Subject> SubjectsToExclude = GetAllocatedSubjectsFromGrid().ToList();
List<Subject> ExcludedSubjects = AllSubjects.Except(SubjectsToExclude);
I got around this by comparing keys in a Where clause rather than using Except.
So instead of:
var SubjectsToExclude = GetAllocatedSubjectsFromGrid();
var ExcludedSubjects = AllSubjects.Except(SubjectsToExclude.AsEnumerable());
Something more like:
var subjectsToExcludeKeys =
GetAllocatedSubjectsFromGrid()
.Select(subject => subject.ID);
var excludedSubjects =
AllSubjects
.Where(subject => !subjectsToExcludeKeys.Contains(subject.ID));
(I'm guessing what your entity's key looks like though.)
This allows you to keep everything in Entity Framework, rather than pulling everything into memory.

LINQ to SQL Dynamic Sort question

How do I code the Select clause in my LINQ satament to select column aliases so I can sort on them basically I want to accomplish this SQL statement in LINQ:
select
type_id as id,
type_desc as description
from
dbo.equip_type_avt
order by
description
What do I replace the ????? in my .Select clause in my LINQ statement?
public IQueryable<equip_type_avt> GetGridEquipmentTypes(string sidx, string sord)
{
try
{
return
ulsDB.equip_type_avts
.Select(?????)
.OrderBy(sidx + " " + sord);
}
catch (Exception ex)
{
string strErr = ex.Message;
return null;
}
}
You can use an anonymous type:
table.Select(x => new
{
ID = x.type_id,
Description = x.type_desc
});
However, you can't access the properties of an anonymous type outside of the scope where it is declared (without reflection or other dirty hackery, anyway) so if you want to use the result outside of that function you just create a class and create an instance of it in the query using a type initializer:
public class Foobar
{
public int ID { get; set; }
public string Description { get; set; }
}
...
table.Select(x => new Foobar() // Note the difference here
{
ID = x.type_id,
Description = x.type_desc
});
Question though: if you want to name the columns differently, why don't you change it in the place where the column-property mapping is declared? In LINQ-to-SQL you can have the database column be named whatever you like but give the property the name "ID" or "Description".
I'm not sure i understand your question, how does sidx and sord relate to your query?
Isn't your problem rather that you have to end your query with OrderBy(...).ThenBy(...) instead of a combined OrderBy?
If you want to sort by a string in the easy way, download the Dynamic LINQ library.
However, that's 2000 lines of code, most of which are entirely redundant for just the purpose of sorting.
Doing it yourself shouldn't be too hard, but requires a fair bit of knowledge on expression trees. I can't really help you there though.
EDIT: I've added another answer, that hopefully answers your actual question :)

LINQ + type tables best practices

Whats the best design pattern to use for LINQ and type tables that exist in SQL.
I have tables in SQL that constrain values to type values, and I want to be able to use this in my C# code as strongly typed values.
My current approach for a 'PackageStatus' type is as follows:
SQL Table
PackageStatusType (int)
desc (varchar)
C# Class - using LINQ
public class PackageStatusType
{
static PackageStatusType()
{
var lookup = (from p in DataProvider.ShipperDB.PackageStatus
select p).ToDictionary(p => p.Desc);
Unknown = lookup["Unknown"];
LabelGenerated = lookup["Label generated"];
ReadyForCollection = lookup["Ready for pickup"];
PickedUp = lookup["Picked up"];
InTransit = lookup["In Transit"];
DeliveryAttempted = lookup["Delivery attempted"];
DeliveredByHand = lookup["By hand"];
DeliveryFailed = lookup["Delivery failed"];
Delivered = lookup["Delivered"];
Voided = lookup["Voided"];
}
public static ShipperDB.Model.PackageStatus Unknown;
public static ShipperDB.Model.PackageStatus LabelGenerated;
public static ShipperDB.Model.PackageStatus ReadyForCollection;
public static ShipperDB.Model.PackageStatus PickedUp;
public static ShipperDB.Model.PackageStatus InTransit;
public static ShipperDB.Model.PackageStatus DeliveryAttempted;
public static ShipperDB.Model.PackageStatus DeliveryFailed;
public static ShipperDB.Model.PackageStatus Delivered;
public static ShipperDB.Model.PackageStatus DeliveredByHand;
public static ShipperDB.Model.PackageStatus Voided;
}
I then can put PackageStatusType.Delivered in my C# code and it will correctly reference the right LINQ entity.
This works fine, but makes me wonder:
a) how can i make this more efficient
b) why doesn't Microsoft seem to provide anything to create strongly typed type tables
c) is my database design even a good one?
d) what is everyone else doing!
thanks!
Linq to SQL allows you to map a string or int column in a database to an enumeration in your C# code. This allows you to let Linq to SQL to map these values for you when you select from the database. In this case, I would change my package status column to be either an int column with the values from the enumeration or a string that represents the values from the enumeration.
In your case, I would have a PackageStatus enumeration with the different values that you specified, and then using the ORM designer or SQLMetal, map that column to that enumeration. The only caveat is that the string values in the column in the database must match the values in the enumeration as Linq to SQL will use Enum.Parse() to map the string values from the database to the enumeration or make sure that the int values in the database match the values from the enumeration.
This is more efficient as you don't even need to map the lookup table at all in the code.
http://msdn.microsoft.com/en-us/library/bb386947.aspx#EnumMapping describes how this works.