I have a class which has the following property:
[NotMapped]
public string Key
{
get
{
return string.Format("{0}_{1}", Process.Name, LocalSequenceNumber);
}
}
The local sequence number is a computed integer backed by a cache in form of a concurrent dictionary.
I wish to use the Key property above in a LINQ query but get the exception:
The specified type member 'Key' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
I understand why I'm getting this error, but I'm not too sure about how to remedy it. Currently, the Key property is providing a nice encapsulation over my class which I don't want to part with. Any suggestions in terms of libraries, or simple patterns to get around this?
Edit: Here's the query which is throwing the exception:
db.Cars.SingleOrDefault(c => c.Id == id && c.Key == key);
The DelegateDecompiler package https://github.com/hazzik/DelegateDecompiler handles this type of scenario.
Decorate your property with the Computed attribute, then queries like the following should work if you add the Decompile method:
db.Cars.Decompile().SingleOrDefault(c => c.Id == id && c.Key == key)
There are numerous third party packages that can solve this problem. I also believe that there are methods in EF.Core that can help, however, I will suggest 2 "pure Entity Framework 6" solutions.
Execute your query in two parts - the SQL part, then the "in code" part.
db.Cars.Where(c => c.Id == id).ToList().SingleOrDefault(c => c.Key == key)
this will still keep your logic encapsulated in the class, but you do not get the benefit of the SQL execution.
What I like to call the "projector" pattern. This one is a bit more long-winded.
Essentially, you create a "view" of the EF POCO that represents a data-transfer-object. it has the properties you need for your view, and also determines how to project the data from the database to the view.
// Poco:
public class Car {
public int Id {get;set;}
public string LocalSequenceNumber {get;set;}
public int ProcessId {get;set; }
public virtual Process Process {get;set;}
// ...
}
public class Process {
// ...
}
// View+Projector:
public class CarView
{
public int Id {get;set;}
public string Color {get;set;}
public string Key {get;set;}
public static Expression<Func<Car, CarView>> Projector = car => new CarView {
Id = car.Id,
Color = car.Color,
Key = car.Process.Name + " " + car.LocalSequenceNumber
}
}
// calling code
var car = db.Cars.Select(CarView.Project).SingleOrDefault(cv => cv.Id == id && cv.Key == key)
This will evaluate all code on the database, whilst encapsulating your business logic in code.
Alas you forgot to tell us what Process.Name and LocalSequenceNumber are. From the identifiers it seems that they are not part of your Cars, but values in your local process. Why not calculate the Key before your query?
var key = string.Format("{0}_{1}", Process.Name, LocalSequenceNumber);
db.Cars.SingleOrDefault(c => c.Id == id && c.Key == key);
If, on the other hand, Process.Name or LocalSequenceNumber are Car properties, you'll have to change the IQueryable.Expression that is in your LINQ query using only properties and methods that can be translated by your IQueryable.Provider into SQL.
Luckily, your Provider knows ToSTring() and the concept of string concatenation So you can use that
As you are using property Key in a Queryable.Where, I suggest extending IQueryable with a function WhereKey. If extension functions are a bit magic for you, see Extension Methods Demystified
public static IQueryable<Car> WhereKey(this IQueryable<Car> cars, int id, string key)
{
return cars.Where(car => car.Id == id
&& key == car.Process.Name.ToString() + "_" + car.LocalSequenceNumber.ToString());
}
Usage:
int carId = ...
string carKey = ...
var result = myDbContext.Cars
.WhereKey(carId, carKey)
.FirstOrDefault();
Consider creating a WhereKey that only checks the key. The concatenate with a Where that selects on Id.
var result = myDbContext.Cars
.Where(car => car.Id == id)
.WhereKey(carKey)
.FirstOrDefault();
If either Process.Name or LocalSequenceNumber is not a part of Car, add it as a parameter. You get the gist.
Consider creating a WhereKey that only checks the key. The concatenate with a Where that selects on Id.
If desired, you can create a WhereKeyFirstOrDefault(), but I doubt whether this would be of much use.
Related
I am new to Entity Framework and Linq. I am using .Net Core and EF Core 5. I like it so far but have hit a couple of issues that I am struggling with. This is the one I am really confused about and not understanding.
I have some products represented in a class. I have customers that buy these products in another class. Each customer may call one of my products a different name within their business so I need to allow them to define an alias that they use for my product.
Here are my two parent classes (Products & Customers)
public class Product
{
public int Id {get; set;}
public Guid IdGuid {get; set;}
public string Name {get; set;}
}
public class Customer
{
public int Id {get; set;}
public Guid IdGuid {get; set;}
public string Name {get; set;}
}
In these classes I have an Id column that is used by the Database for referential integrity. I do not pass this Id back to the user. Instead whenever the user gets one of these objects they get the Guid returned so they can uniquely identify the row in the DB. The alias class that joins these two tables is as follows:
public class ProductCustomerAlias
{
public Product Product {get; set;}
public Customer Customer {get; set;}
public string Alias {get; set;}
}
Since I need the database table to use a complex key consisting of the Product Id and the Customer Id I have to override the OnModelCreating method of the context object:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ProductCustomerAlias>()
.HasKey("ProductId", "CustomerId");
}
'''
This ends up creating a Database table that has the following structure (in Oracle)
'''
CREATE TABLE "RTS"."ProductCustomerAlias"
(
"ProductId" NUMBER(10,0),
"CustomerId" NUMBER(10,0),
"Alias" NVARCHAR2(100)
)
So far so good. I now have an intersect table to store Alias' in that has a primary key of ProductId and CustomerId both being the key integer values from the Products and Customers within the DB context.
So my next step is to start creating the Repo class that retrieves data from these objects. Keep in mind that the end user that submits the request can only pass me the Product.IdGuid and Customer.IdGuid because that is all they ever have. In my Repo I have the following code:
public async Task<ProductCustomerAlias> GetProductCustomerAliasAsync(Guid pProductId, Guid pCustomerId)
{
var alias = await (from ali in _context.ProductCustomerAlias
join prod in _context.Products on ali.Product.Id equals prod.Id
join cust in _context.Customers on ali.Customer.Id equals cust.Id
where cust.IdGuid == pCustomerId && prod.IdGuid == pProductId
select new {Product = prod,
Customer = cust,
Alias = ali.Alias}
).FirstOrDefaultAsync();
return (IEnumerable<ProductCustomerAlias>)alias;
}
My problem is that it is giving me the following error:
Cannot convert type '<anonymous type: Models.Product Product, Models.Customer Customer, string Alias>' to 'System.Collections.Generic.IEnumerable<Models.ProductCustomerAlias>'
Please don't tell me that the error is telling me exactly what is wrong. I am sure it is but if I understood where I was screwing up I would not be wasting my time typing out this ridiculously long explanation. So how can I cast the results from my Linq query to the specified type? Is there something else I am doing wrong? Any help would be greatly appreciated.
Answering your concrete questions.
So how can I cast the results from my Linq query to the specified type?
You can't, because the LINQ query result is anonymous type which is not compatible with the desired result type (concrete entity type), thus cannot be cast to it.
Is there something else I am doing wrong?
Sorry to say that, but basically everything is wrong.
Select is not needed because the desired result type is the exact type of the entity being queried. i.e. ali variable here
from ali in _context.ProductCustomerAlias
is exactly what you need as a result (after applying the filter and limiting operators)
Manual joins are also not needed, because they are provided automatically by navigation properties, i.e. here
join prod in _context.Products on ali.Product.Id equals prod.Id
the prod is exactly the same thing as ali.Product
attempt to cast single object to enumerable is wrong
return (IEnumerable<ProductCustomerAlias>)alias
Even if the alias variable was of correct type, this will fail because it is single object rather than a collection.
So, the solution is quite simple - use the corresponding DbSet, apply filter (Where), limit the result to zero or one (FirstOrDefault{Async}) and you are done.
With one small detail. Since you are querying and returning a full entity, its navigation properties (like Product and Customer) are considered to be a related data, and are not populated (loaded) automatically by EF Core. You have to explicitly opt-in for that, which is called eager loading and explained in the Loading Related Data section of the official EF Core documentation (I would also recommend familiarizing with navigation properties and the whole Relationships concept). With simple words, this requires usage of specifically provided EF Core extension methods called Include and ThenInclude.
With all that being said, the solution is something like this:
public async Task<ProductCustomerAlias> GetProductCustomerAliasAsync(
Guid pProductId, Guid pCustomerId)
{
return await _context.ProductCustomerAlias
.Include(a => a.Product)
.Include(a => a.Customer)
.Where(a => a.Customer.IdGuid == pCustomerId && a.Product.IdGuid == pProductId)
.FirstOrDefaultAsync();
}
You can even replace the last two lines with single call to the predicate overload of FirstOrDefaultAsync, but that's not essential since it is just a shortcut for the above
return await _context.ProductCustomerAlias
.Include(a => a.Product)
.Include(a => a.Customer)
.FirstOrDefaultAsync(a => a.Customer.IdGuid == pCustomerId && a.Product.IdGuid == pProductId);
In my case, I only have a specific repository. Lets say 'StudentRepository'. This repository hides the ISession instance from me and only thing I have is the IQueryOver< Student,Student> instance.
Consider below entities have a simplistic mapping with NHibernate.
class Student
{
public int Id {get;set;}
public string Number {get;set;}
}
class Exam{
public int Id {get;set;}
public double Score {get;set;}
public string StudentNumber {get;set;}
}
You are right, the basic way just add the real relation to Exam class like:
public Student Student {get; set;}
Unfortunately, that is not an option either
The problem: I need to query with some criteria like "Score>70" on Exam entity from studentRepository. How can I produce such a query with Nhibernate without knowing session and no relation defined on mapping. ?
So the main problem here as I see it: to make joins to unrelated entities with QueryOver it's required to have alias defined as variable for root query (your QueryOver for students).
How to do such joins explained in NHibernate QueryOver to join unrelated entities
So if you can modify your repository class to allow to provide optional alias variable for this QueryOver it would be the best solution. Something like this (I assume you are using NHibernate 5.1 or higher) :
Student studentAlias = null;
var studentsQueryOver= yourRepository.GetQueryOver<Student>(studentAlias);
Exam examAlias = null;
var students = studentsQueryOver
.JoinEntityAlias(() => examAlias, () => examAlias.StudentNumber == studentAlias.Number)
.Where(s => examAlias.Score > 70)
.List();
If it's not an option you still can create joins to unrelated entities but you need to build them directly with underlying root Criteria. Something like this:
Exam examAlias = null;
studentsQueryOver.RootCriteria
.CreateEntityAlias(
nameof(examAlias),
Restrictions.EqProperty("examAlias.StudentNumber", studentsQueryOver.RootCriteria.Alias + ".Number"),
JoinType.LeftOuterJoin,
typeof(Exam).FullName);
var students = studentsQueryOver
.Where(s => examAlias.Score > 70)
.List();
And on NHibernate versions before 5.1 you can use subqueries:
var subQuery = QueryOver.Of<Exam>()
.Where(e => e.Score > 70)
.Select(e => e.StudentNumber);
subQuery.RootCriteria.Add(Restrictions.EqProperty("StudentNumber", studentsQueryOver.RootCriteria.Alias + ".Number"))
//Or if root query alias variable available simply
//subQuery.And(e => e.StudentNumber == studentAlias.Number)
var students = studentsQueryOver
.WithSubquery.WhereExists(subQuery)
.List();
In a project I am working on, I'm adopting the newer QueryOver syntax in NHibernate. However, I'm having issues implementing a sort, on a composite property.
The model I'm querying on looks like this:
public class Person
{
public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
// Not really relevant; it has an ID, but that's all we care about
// for this question.
public virtual Group Group { get; set; }
// This is the culprit of my troubles.
public virtual string DisplayName
{
get { return LastName + ", " + FirstName; }
}
}
...My mapping looks like this:
public class PersonMap : ClassMap<Person>
{
Table("Persons");
Id(x => x.Id);
Map(x => x.FirstName);
Map(x => x.LastName);
References(x => x.Group)
.Not.Nullable()
.Column("GroupId")
.Fetch.Join();
}
Note: DisplayName only exists in the server/client stack! Not on the database side.
However, here's where the problem happens: my repository code.
public class PersonRepository
{
// ...Other methods...
public static IEnumerable<Person> GetPeopleByGroup(int groupId)
{
// This just gets a cached NHibernate session
using(var session = DataContext.GetSession())
{
var results = session
.QueryOver<Person>()
.Where(p => p.Group.GroupId == groupId)
// Exception thrown here!
.OrderBy(p => p.DisplayName)
.List().ToList();
return results;
}
}
}
As far as I can tell, this should be working. Question: why can't NHibernate resolve my composite property, despite the fact that both properties that are sourcing the result of that property exist?
Like #Radim Köhler pointed out, the golden QueryOver rule is pretty much "If it's not mapped, you can't query on it".
Even though your property's definition is quite simple, NHibernate's not going to dive into that property and try to understand the implementation and then translate that implementation into SQL.
There are, however, a few workarounds that might apply depending on your situation.
If your solution is working for you, then that's probably what you should go with, since it's so simple. However, there are some other things you could do:
Use a computed column and map it to DisplayName.
I'm not sure what database engine you're using, but if it supports computed columns, then you could actually create a computed column in the database representing DisplayName.
In SQL server for example:
alter table [Person] add [DisplayName] as [LastName] + N', ' + [FirstName]
This is straightforward, but it could be incorrect from a separation of concerns perspective to have your database engine care about how a particular row's columns are displayed.
Use a Projection:
Unfortunately, Projections.Concat doesn't take arbitrary Projections, so you'll have to use Projections.SqlFunction (which Projections.Concat uses anyway). You'd end up with something like this:
var orderByProjection =
Projections.SqlFunction(
"concat",
NHibernateUtil.String,
Projections.Property<Person>(p => p.LastName),
Projections.Constant(", "),
Projections.Property<Person>(p => p.FirstName));
var people = session.QueryOver<Person>()
.OrderBy(orderByProjection).Asc
.List<Person>();
Tell QueryOver what accessing the DisplayName property means in SQL
This is pretty involved, but if you want to use DisplayName inside of your QueryOver queries, you can actually tell QueryOver what accessing that property should translate into.
I actually wouldn't recommend this since it's pretty complicated and it duplicates logic (there would now be two places where DisplayName is defined). That said, it might be useful for others in a similar situation.
Anyway, if you are curious (or more likely a glutton for QueryOver punishment) here's what that would look like:
public static class PersonExtensions
{
/// <summary>Builds correct property access for use inside of
/// a projection.
/// </summary>
private static string BuildPropertyName(string alias, string property)
{
if (!string.IsNullOrEmpty(alias))
{
return string.Format("{0}.{1}", alias, property);
}
return property;
}
/// <summary>
/// Instructs QueryOver how to process the `DisplayName` property access
/// into valid SQL.
/// </summary>
public static IProjection ProcessDisplayName(
System.Linq.Expressions.Expression expression)
{
Expression<Func<Person, string>> firstName = p => p.FirstName;
Expression<Func<Person, string>> lastName = p => p.LastName;
string aliasName = ExpressionProcessor.FindMemberExpression(expression);
string firstNameName =
ExpressionProcessor.FindMemberExpression(firstName.Body);
string lastNameName =
ExpressionProcessor.FindMemberExpression(lastName.Body);
PropertyProjection firstNameProjection =
Projections.Property(BuildPropertyName(aliasName, firstNameName));
PropertyProjection lastNameProjection =
Projections.Property(BuildPropertyName(aliasName, lastNameName));
return Projections.SqlFunction(
"concat",
NHibernateUtil.String,
lastNameProjection,
Projections.Constant(", "),
firstNameProjection);
}
}
Then, you'd need to register the processing logic with NHibernate, probably right after your other configuration code:
ExpressionProcessor.RegisterCustomProjection(
() => default(Person).DisplayName,
expr => PersonExtensions.ProcessDisplayName(expr.Expression));
Finally, you'd be able to use your (unmapped) property inside of a QueryOver query:
var people = session.QueryOver<Person>()
.OrderBy(p => p.DisplayName).Asc
.List<Person>();
Which generates the following SQL:
SELECT
this_.Id as Id0_0_,
this_.FirstName as FirstName0_0_,
this_.LastName as LastName0_0_
FROM
Person this_
ORDER BY
(this_.LastName + ', ' + this_.FirstName) asc
You can find more about this technique here. Disclaimer: This is a link to my personal blog.
This is probably way too much information, and personally I'd go for #1 and then #2 if you're not happy with your solution for some reason.
The 'quick-and-dirty' solution to this problem was to OrderBy Last Name, then First Name.
var results = session
.QueryOver<Person>()
.Where(p => p.Group.GroupId == groupId)
.OrderBy(p => p.LastName).Asc()
.OrderBy(p => p.FirstName).Asc()
.List().ToList();
I could have also done a Projection, but I felt it less readable. In any event, given a list of sample people...
John Smith
Aberforth Scrooge
Tim Dumbledore
Giselle Potter
John Bane
Kit-Kat Chunky
...The 'correct' order based on my app's rules, and the list generated by this code, is
John Bane
Kit-Kat Chunky
Tim Dumbledore
Giselle Potter
Aberforth Scrooge
John Smith
Case closed...for now. I don't doubt there's incrementally better ways to do this; I am new to QueryOver syntax, after all.
Here's my problem: I have a class that have 2 list properties of the same class type (but with some different restriction as on how to be filled), let's say:
public class Team
{
[Key]
public int IDTeam { get; set; }
public string TeamName { get; set; }
public List<Programmer> Members { get; set; }
public List<Programmer> Leaders { get; set; }
public LoadLists(MyProjectDBContext db)
{
this.Members = db.Programmers.Where(p => p.IDTeam = this.IDTeam
&& (p.Experience == "" || p.Experience == null)).ToList();
this.Leaders = db.Programmers.Where(p => p.IDTeam = this.IDTeam
&& (p.Experience != null && p.Experience != "")).ToList();
}
}
public class Programmer
{
[Key]
public int IDProgrammer { get; set; }
[ForeignKey("Team")]
public int IDTeam { get; set; }
public virtual Team Team { get; set; }
public string Name { get; set; }
public string Experience { get; set; }
}
At some point, I need to take a list of Teams, with it's members and leaders, and for this I would assume something like:
return db.Teams
.Include(m => m.Members.Where(p => p.Experience == "" || p.Experience == null)
.Include(l => l.Leaders.Where(p => p.Experience != null && p.Experience != "")
.OrderBy(t => t.TeamName)
.ToList();
And, of course, in this case I would be assuming it wrong (cause it's not working at all).
Any ideas on how to achieve that?
EDIT: To clarify a bit more, the 2 list properties of the team class should be filled according to:
1 - Members attribute - Should include all related proggramers with no experience (proggramer.Experience == null or "");
2 - Leaders attribute - Should include all related proggramers with any experience (programmer.Experiente != null nor "");
EDIT 2: Here's the MyProjectDbContext declaration:
public class MyProjectDBContext : DbContext
{
public DbSet<Team> Teams { get; set; }
public DbSet<Programmer> Programmers { get; set; }
}
You are talking about EntityFramework (Linq to entities) right? If so, Include() is a Method of Linq To Entities to include a sub-relation in the result set. I think you should place the Where() outside of the Inlcude().
On this topic you'll find some examples on how to use the Include() method.
So I suggest to add the Include()'s first to include the relations "Members" and "Leaders" and then apply your Where-Statement (can be done with one Where()).
return db.Teams
.Include("Team.Members")
.Include("Team.Leaders")
.Where(t => string.IsNullOrWhitespace(t.Members.Experience) ... )
What is unclear to me is your where criteria and your use-case at all as you are talking of getting a list of Teams with Leaders and Members. May above example will return a list of Teams that match the Where() statement. You can look though it and within that loop you can list its members and leaders - if that is the use-case.
An alternative is something like this:
return db.Members
.Where(m => string.IsNullOrWhitespace(m.Experience))
.GroupBy(m => m.Team)
This get you a list of members with no experience grouped by Team. You can loop the groups (Teams) and within on its members. If you like to get each team only once you can add a Distinct(m => m.Team) at the end.
Hope this helps. If you need some more detailed code samples it would help to understand your requirements better. So maybe you can say a few more words on what you expect from the query.
Update:
Just read our edits which sound interesting. I don't think you can do this all in one Linq-To-Entities statement. Personally I would do that on the getters of the properties Members and Leaders which do their own query (as a read-only property). To get performance for huge data amount I would even do it with SQL-views on the DB itself. But this depends a little on the context the "Members" and "Leaders" are used (high frequent etc).
Update 2:
Using a single query to get a table of teams with sublists for members and leaders I would do a query on "Programmers" and group them nested by Team and Experience. The result is then a list of groups (=Teams) with Groups (Experienced/Non-experience) with Programmers in it. The final table then can be build with three nested foreach-Statements. See here for some grouping examples (see the example "GroupBy - Nested").
Whenever you fetch entities, they will be stored in the context -- regardless of the form they are "selected" in. That means you can fetch the teams along with all the necessary related entities into an anonymous type, like this:
var teams =
(from team in db.Teams
select new {
team,
relatedProgrammers = team.Programmers.Where(
[query that gets all leaders OR members])
}).ToList().Select(x => x.team);
It looks like we're throwing away the relatedProgrammers field here, but those Programmer entities are still in memory. So, when you execute this:
foreach (var team in teams) team.LoadLists(db);
...it will populate the lists from the programmers that were already fetched, without querying the database again (assuming db is the same context instance as above).
Note: I haven't tested this myself. It's based on a similar technique shown in this answer.
EDIT - Actually, it looks like your "leaders" and "members" cover all programmers associated with a team, so you should be able to just do Teams.Include(t => t.Programmers) and then LoadLists.
I am writing a generic repository to interface with EF using DBContext.
I have a generic Get() method which receives a primary key value and returns the entity:
public class DALRepository<DALEntity> : IDisposable, IGenericRepository<DALEntity> where DALEntity : class
{
private IDbSet<DALEntity> dbSet;
private NWEntities context;
public DALRepository()
{
context = new NWEntities();
context.Configuration.LazyLoadingEnabled = false;
dbSet = context.Set<DALEntity>();
}
Here's a simple get method - just works on the PK - exactly what I want.
public DALEntity Get(string ID)
{
return dbSet.Find(ID);
}
I now want to change this to allow the consumer to pass in a list of includes - so as well as returning just a customer they can request to return the orders as well. Here's where I'm running into trouble. If I do this:
public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
IQueryable<DALEntity> query = dbSet;
query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));
}
I can't use find with the IQueryable. And I can't Find() directly because I can't pass includes to it. If I use a where lambda instead on the IQueryable, how do I tell it to use the PK of the entity? I guess I could have a generic constraint that insists the generic type must implement some IPkey interface with a well-defined primary column name such as "ID", but then I can't use the entities generated by DBContext as they woudl have to implement this interface. I can change the T4 to do this if I need - and I've already changed it to emit XML comments so not too averse to that - but does anyone have a simpler way? I suppose what I need is an overloaded find() which accepts a list of includes.
So my question is either how to use Find with includes, or how to write the lambda where it knows the PK? I can't receive such a lambda as a parameter as this will ultimately be consumed by a WCF service.
Kind of wierd answering your own question but in case anyone else has this issue here's what I did. I used the dynamic LINQ stuff and used the string overloaded version of .Where(). I used one of the links mentioned to figure out how to grab the primary key (and mine is from a DBContext as well), and the method now looks like this:
public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
var set = ((IObjectContextAdapter)context).ObjectContext.CreateObjectSet<DALEntity>();
var entitySet = set.EntitySet;
string[] keyNames = entitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray();
Debug.Assert(keyNames.Length == 1, "DAL does not work with composite primary keys or tables without primary keys");
IQueryable<DALEntity> query = dbSet;
query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));
query = query.Where(keyNames[0] + "= #0", ID);
return query.FirstOrDefault();
}
You could get your Key dinamycally the way it is described here : https://stackoverflow.com/a/10796471/971693
That being said, I can't see a way without using some reflection :
public IEnumerable<DALEntity> Get(params string IDs)
{
var objectSet = objectContext.CreateObjectSet<YourEntityType>();
var keyNames = objectSet.EntitySet.ElementType.KeyMembers.First(k => k.Name);
return dbSet.Where(m => ID.Contains((string)m.GetType().GetProperty(keyNames ).GetValue(m, null));
}
First of, params string IDs will let you pass 1 or more ID and will result in an array of string.
The first part of the function is to dynamically get the name of your primary key.
The second part creates a query to return all elements from your set where the primary key value (obtained through reflection) is contained within the array of IDs received in parameter.
Use Linq's Single or First methods, which allow you to search on IQueryable objects.
public DALEntity Get(string ID, IEnumerable<string> IncludeEntities = null)
{
IQueryable<DALEntity> query = dbSet;
query = IncludeEntities.Aggregate(query, (current, includePath) => current.Include(includePath));
query = query.Single(x=>x.Id == ID);
}