i struggling to find the correct way to query for a specific entity in my database. I have a table TeamMember which looks as follows:
public class TeamMember
{
public Guid TeamId { get; set; }
public Guid ManagerUserId { get; set; }
...
}
At several positions i want to query for a "TeamManager" in my teams.
public TeamMember GetTeamManager(Guid teamId, List<TeamMember> teamMembers)
{
return teamMembers.FirstOrDefault(member => member.TeamId == teamId && member.ManagerUserId == null);
}
If i want to use a method in an expression e.g (not real code),
...
// IQueryable<TeamMember>
teamMembers.Where(member => member.team.GetTeamManager(teamId, teamMembers))
it works fine for in memory objects, but this does not work in combination with the entity framework, so real db objects.
Therefore i was experimenting with static expressions, but i did not find any solution which uses a static expression in combination with a variable e.g.:
// this should be an ideal solution
Expression<Func<TeamMember, bool>> exp = teammember => teamMember.TeamId == **CUSTOM_VARIABLE** && teamMember.ManagerUserId == null;
teamMembers.Where(exp)
I want to reuse the expression, but i also want to be able to modify the variable. My goal is to avoid the creation of an object in between because the following would work, but is less efficient (correct me if i'm wrong)
teamMembers.ToList().Where(member => member.team.GetTeamManager(teamId, teamMembers))
So please help me with my problem :).
Thank you in advance!
You can't make exp a property because of the need to capture a variable, but you can make it a function returning Expression<Func<TeamMember,bool>>:
Expression<Func<TeamMember,bool>> MakeMemberExpression(Guid teamId) {
return teammember => teamMember.TeamId == teamId && teamMember.ManagerUserId == null;
}
or even
Expression<Func<TeamMember,bool>> MakeMemberExpression(Guid teamId) =>
teammember => teamMember.TeamId == teamId && teamMember.ManagerUserId == null;
You can use it as follows:
var members = teamMembers.Where(MakeMemberExpression(teamId));
You can create a function that operates on IQueryable.
public TeamMember GetTeamManager(IQueryable<TeamMember> baseQuery,Guid teamId)
{
return baseQuery.FirstOrDefault(member => member.TeamId == teamId && member.ManagerUserId == null);
}
Related
I have this function that returns 2 strings
public object GetNamePhoneByUserId(int userId)
{
return _db.ApplicationUsers.Where(q => q.UserId == userId).Select(a => new {
a.FullName,a.PhoneNumber}).ToList();
}
When calling the function, i'm getting the values, but unable to extract them from the returned value(appU)
object appU = _unitOfWork.ApplicationUser.GetNamePhoneByUserId(ca.UserId);
i was able to cast it to Ienumerable but still can't extract the values..
IEnumerable list = (IEnumerable)appU;
I'm sure its a simple solution, but tried many things and still not working.
Thank you
Don't say that your method returns an object. You're going to have a really tough time working with the data, as you've seen.
Instead, create a class to represent the fields you're returning, and return a collection of that class.
public class ApplicationUserMinimalInfo // make this more descriptive as needed
{
public string FullName { get; set;}
public string PhoneNumber {get; set;}
}
public List<ApplicationUserMinimalInfo> GetNamePhoneByUserId(int userId)
{
return _db.ApplicationUsers
.Where(q => q.UserId == userId)
.Select(a => new ApplicationUserMinimalInfo
{
a.FullName,
a.PhoneNumber
}).ToList();
}
Now, I also suspect that because you're filtering by the UserId, you should only be getting a single result back, or no results back. It doesn't make sense to get two records with the same UserId.
public ApplicationUserMinimalInfo? GetNamePhoneByUserId(int userId)
{
var user = _db.ApplicationUsers
.SingleOrDefault(q => q.UserId == userId); // uses "Where" logic
if (user is null)
return null;
else
return new ApplicationUserMinimalInfo
{
user.FullName,
user.PhoneNumber
});
}
Now, unless you really have some super pressing reason to have a method that returns a subset of properties, just return the full object.
public ApplicationUser? GetUserById(int userId)
{
return _db.ApplicationUsers
.SingleOrDefault(q => q.UserId == userId);
}
Or better yet, this method is a single line, it doesn't really need to be its own method.
So I read that it is bad design to have an interface parameter be checked as sending in an interface member is supposed to associate itself as an contract that the only the interface members are going to be used. As such I thought to simply overload the method. This seems like it would quickly spiral out of control however if multiple objects that implement the interface needs different implementations of the method.
public IArtist FindArtist(IArtist artist)
{
var activeArtist = _database.Artists.FirstOrDefault(a => a.Name == artist.Name);
return activeArtist;
}
public IArtist FindArtist(SpotifyArtist artist)
{
var spotifyArtists = _database.Artists.Where(a => a is SpotifyArtist).Cast<SpotifyArtist>();
SpotifyArtist activeArtist = spotifyArtists.FirstOrDefault(a => a.Name == artist.Name && a.SpotifyID == artist.SpotifyID);
return activeArtist;
}
In the above code snippet, when I need to call the FindArtist with a SpotifyArtist object (which implements IArtist), the function should look for an object that has the same Name and also SpotifyID. Whereas if it is any other type of IArtist, it is to just return based on the name (probably will modify it to prioritize non-SpotifyArtist objects later). Do you have any suggestions to what I should be doing here instead?
EDIT TableImplementation:
public class MusicObjectTable
{
public List<IArtist> Artists;
public List<IAlbum> Albums;
public List<ITrack> Tracks;
public MusicObjectTable()
{
this.Artists = new List<IArtist>();
this.Albums = new List<IAlbum>();
this.Tracks = new List<ITrack>();
}
public MusicObjectTable(List<IArtist> artists, List<IAlbum> albums, List<ITrack> tracks)
{
this.Artists = artists;
this.Albums = albums;
this.Tracks = tracks;
}
}
}
OK this is not directly answering your question, because I think it might be a little XY.
However, this is what I'd do, let relational data be relational data. I.e use a relational database.
Artists are a key concept, there is only of them. Make an Artists table.
One Artist may have multiple Spotify accounts, so we might need a Spotify table to hold things like urls, band name, pictures, whatever… I mean one Artist can be in multiple bands right. So the solve here is to have a one-to-many relationship with Artist to Spotify.
You could have the same with YouTube, one artist could have many videos. One-to-Many again.
Every time you need to add more connections (relationships) you just add a new table, you don’t have to expand on the one table (you don't have to keep adding loosely-related junk to the artist table) the only thing you need to add (if you wanted is a navigation collection property).
An example usage is this in a simple pseudo-code way
var artist = db.Include(x => x.SpotifyAccounts)
.Include(x => x.YouTubeVideos)
.Include(x => x.EbayStores)
.FirstOrDefault(x => x.Name == SearchName);
Console.WriteLine(artist.Name);
if(artist.SpotifyAccounts.Any)
foreach(var item in artist.SpotifyAccounts)
Console.WriteLine(" -- " + item.Url);
var spotify = db.SpotifyAccounts
.Include(x => x.Arists)
.FirstOrDefault(x => x.Id == SpotifyId);
Console.WriteLine(spotify.Id);
Console.WriteLine(spotify.Url);
Console.WriteLine(spotify.Artist.Name);
Note: This does away with your search methods and your inheritance and replaced with relationships. What are the down sides? Well not everything is in the one table, inheritance is really not an option. The pros are, as your models become more complex you just add relationships, you can add as many as you like, which actually doesn't touch your Artist table (unless it's one to one).
When searching for an Artist name, you have access to everything they have. if you search for a particular Spotify account you always have access to the Artist.
However this really depends how far you want to go down the rabbit-hole. If this is going to be any kind of system, I think relational is the way to go. It's scalable, and it's self consistent and it's how most large systems work.
It would be bad form if IArtist behaved differently for different types. It's OK if the implementation is different, and it does different things behind the scenes; it is not OK if the functional behavior differs when viewed as a black box. The contract promises something, and it ought to be consistent.
If you happen to be writing code where you know you have a SpotifyArtist, you also know you can call a different method, if you want to.
public IArtist FindArtistByName(IArtist artist)
{
var activeArtist = _database.Artists.FirstOrDefault(a => a.Name == artist.Name);
return activeArtist;
}
public IArtist FindArtistByNameAndID(SpotifyArtist artist)
{
var spotifyArtists = _database.Artists.Where(a => a is SpotifyArtist).Cast<SpotifyArtist>();
SpotifyArtist activeArtist = spotifyArtists.FirstOrDefault(a => a.Name == artist.Name && a.SpotifyID == artist.SpotifyID);
return activeArtist;
}
And you can also provide a convenience method:
public IArtist FindArtistByBestMethod(IArtist artist)
{
if (artist is SpotifyArtist)
{
return repo.FindArtistByNameAndID((SpotifyArtist)artist);
}
else
{
return repo.FindArtistByName(artist);
}
}
An answer using filters:
The filter:
public ArtistFilter
{
public string SearchString { get; set; }
public Type? Type { get; set; }
public int? MinimumRating { get; set; }
}
MinimumRating is just to show you how to extend the filter easily.
Secondly you have a method that converts the filter into a function:
private static Expression<Func<IArtist, bool>> CreateArtistFilterExpression(ArtistFilter filter)
{
Expression<Func<IArtist, bool>> expression = x => true;
if(filter == null)
{
return expression;
}
if(!string.IsNullOrWhiteSpace(filter.SearchString))
{
expression = expression.And(x => x.Name.Contains(filter.SearchString));
}
if(filter.Type != null)
{
expression = expression.And(x => x is Type.Value);
}
if(filter.MinimumRating != null)
{
expression = expression.And(x => x.Rating >= filter.MinimumRating);
}
return expression;
}
The And-extension-method is pretty small:
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2) {
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
}
A last method reduces redundant code:
public List<IArtist> GetArtistsByFilter(ArtistFilter filter)
{
var expression = CreateArtistFilterExpression(filter);
return _database.Artists.Where(expression).ToList();
}
And you can get the best matching one like this:
var filter = new ArtistFilter {
SearchString = "Lennon",
Type = typeof(SpotifyArtist)
};
var matchingArtists = GetArtistsByFilter(filter);
var bestMatching = matchingArtists.FirstOrDefault();
You ignore the rating then. By setting the MinimumRating as well you can also filter for only-good artists.
Note: I typed most of this in stackoverflow, so I might have missed a semicolon or so.
For simplicity, let's guess there are two entities:
public class Entity
{
public string Value { get; set; }
public ChildEntity Child { get; set; }
}
public class ChildEntity
{
public string Value { get; set; }
}
I need to find all entities where either Value or Child.Value are insensitive like specified string query.
That's what I have by now:
Entity entity = null;
ChildEntity child = null;
var nhibernateQuery = session
.QueryOver(() => entity)
.JoinAlias(() => entity.Child, () => child);
if (!string.IsNullOrWhiteSpace(query))
{
nhibernateQuery = nhibernateQuery
.Where(
Restrictions.Or(
Restrictions.On(() => entity).IsInsensitiveLike(query),
Restrictions.On(() => child).IsInsensitiveLike(query)
)
);
}
return nhibernateQuery.List().ToArray();
I get the NullReferenceException - it seems like Restrictions.On does not handle alias correctly.
Another approach that I have tried is .JoinQueryOver() which is suggested by this post:
return session
.QueryOver<Entity>()
.Where(Restrictions.InsensitiveLike("Value", query))
.JoinQueryOver(e => e.Child)
.Where(Restrictions.InsensitiveLike("Value", query));
This thing works except for one thing: it returns all items where both Value and Child.Value are like query. I need the same thing, but with or logic.
What should be done to make it work? I would like to use .QueryOver(), either with or without aliases, but without .CreateCriteria(), but will appreciate if you help me with any working solution.
The problem has been solved by using NHibernate LINQ .Query<>().
It can resolve all joins and relations by itself.
At the same time, .Contains() method is translated into case-insensitive LIKE statement for MS SQL which suits me needs.
var nhibernateQuery = session
.Query<Entity>();
if (!string.IsNullOrWhiteSpace(query))
{
nhibernateQuery = nhibernateQuery
.Where(e => e.Value.Contains(query) || e.Child.Value.Contains(query));
}
return nhibernateQuery.ToArray();
When checking that the correct data is being used to call a method I can do this:
mockedClass.Verify(method => method.WriteToConsole(It.Is<Result>(item => item.Name == "Bob" && item.Age == 44)));
where Results is an object that is the output from a Linq query:
public class Result
{
public string Name { get; set; }
public int Age { get; set; }
}
However, I also use IEnumerable as the input to a 2nd method.
How do I amend the Verify above to check that the IEnumerable of Results contains Results I can test against.
mockedClass.Verify(method => method.WriteListToConsole(It.Is<IEnumerable<ResultRecord>>(item => What GOES HERE?)));
"item" at that point is an IEnumerable<ResultRecord>. Just write what you want to test.
Something like this?
mockedClass.Verify(method => method.WriteListToConsole(It.Is<IEnumerable<ResultRecord>>(item => item.Count() == 1 && item.ToList()[0].SomeProperty == "Something")));
I am trying to refactor Linq to Entity query to prevent writing duplicate code but I can't avoid a "LINQ to Entities does not recognize the method" exception. The reason is IsUserActive method.
I am providing the code below as example
public static bool IsUserActive(this User user)
{
return user.Orders.Any(c => c.Active && (c.TransactionType == TransactionType.Order || c.TransactionType == TransactionType.Subscription));
}
public IQueryable<UserView> GetView()
{
return context.users.Select(p => new UserView
{
Id = p.Id,
Active =p.IsUserActive()
});
}
Is it possible to refactor Linq code to prevent duplication in my situation?
I can't use IList instead of IQueryable.
I know why this exception happens, no need to explain.
If you want to use your method in a lot of places, I don't think you can do anything except store it as an expression and then referring to that expression wherever you were calling the original method:
public static Expression<Func<User, bool>> IsUserActive = user =>
user.Orders.Any(c => c.Active &&
(c.TransactionType == TransactionType.Order ||
c.TransactionType == TransactionType.Subscription));
public IQueryable<UserView> GetView()
{
return context.users.Select(p => new UserView
{
Id = p.Id,
Active = IsUserActive(p)
});
}
EDIT: OK, so this doesn't work. You can't just "call" an expression like this. The only alternative I can think of is to turn your whole Select parameter into an expression:
public static Expression<Func<User, UserView>> UserToUserView = user =>
new UserView
{
Id = user.Id,
Active = user.Orders.Any(c => c.Active &&
(c.TransactionType == TransactionType.Order ||
c.TransactionType == TransactionType.Subscription)
};
public IQueryable<UserView> GetView()
{
return context.users.Select(UserToUserView);
}
This is little tricky, but this is how we are doing it. You can combine multiple IQueryables into one as shown below.
public static IQueryable<Order> ActiveOrdersQuery(this MyDBEntities db)
{
return db.orders.Where(
o=> o.Active &&
(o.TransactionType == TransactionType.Order ||
o.TransactionType == TransactionType.Subscription )));
}
public IQueryable<UserView> GetView()
{
var q = context.ActiveOrdersQuery();
return context.users.Select( x=> new UserView{
Id = x.Id,
Active = q.Any( o=> o.UserId == x.Id)
});
}
The only problem is you have to explicitly compare foreign keys. But this results in exactly same SQL and will perform same.
This way you can refactor common queries and use them inside your views.