Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DbSet.FirstOrDefault()?

I'm trying to do this but it says I can't use FirstOrDefault,

public static int GetId(this Context db, Type type, string name)
{
    return db.Set(type).FirstOrDefault(x => x.Name == name).Id;
}

The error is 'System.Data.Entity.DbSet' does not contain a definition for 'FirstOrDefault' and no extension method 'FirstOrDefault' accepting a first argument of type 'System.Data.Entity.DbSet' could be found (are you missing a using directive or an assembly reference?)

I then tried this Cast method but that gave an error Cannot create a DbSet from a non-generic DbSet for objects of type 'WindowStyle' (btw WindowStyle inherits from DomainEntity below),

var set = db.Set(type).Cast<DomainEntity>();
return set.FirstOrDefault(x => x.Name == name).Id;

Here is the class,

public class DomainEntity
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
}
like image 313
Benjamin Avatar asked Nov 21 '11 22:11

Benjamin


People also ask

What is DbSet <>?

A DbSet represents the collection of all entities in the context, or that can be queried from the database, of a given type. DbSet objects are created from a DbContext using the DbContext.

What is FirstOrDefault?

Returns the first element of a sequence, or a specified default value if the sequence contains no elements. FirstOrDefault<TSource>(IEnumerable<TSource>) Returns the first element of a sequence, or a default value if the sequence contains no elements.

What is Entity Framework FirstOrDefault?

FirstOrDefault() - when you expect zero or more items to be returned by a query but you only want to access the first item in your code (i.e. you are not sure if an item with a given key exists)

What does FirstOrDefault return if empty?

FirstOrDefault returns the default value of a type if no item matches the predicate. For reference types that is null . Thats the reason for the exception.


4 Answers

Maybe you are missing

using System.Linq;
like image 119
riseres Avatar answered Sep 20 '22 16:09

riseres


This answer possibly doesn't help you depending on how "dynamic" the situation is where you call this method, basically if you know the type at compile time or not. If you know it you can write a generic method instead:

public static class MyExtensions
{
    public static int? GetId<TEntity>(this Context db, string name)
        where TEntity : DomainEntity
    {
        return db.Set<TEntity>()
            .Where(x => x.Name == name)
            .Select(x => (int?)x.Id)
            .FirstOrDefault();
    }
}

I've changed it to a projection because you don't need to load the full entity if you only want the Id. You can let the database do the work to select the property to improve performance a bit. Also I return a nullable int in case that there is no match for the name in the database.

You can call this in your code like so:

int? id = db.GetId<WindowStyle>("abc");

As you can see for this solution you must specify the type WindowStyle at compile time.

This assumes that DomainEntity is not part of your model (there is no DbSet<DomainEntity>), but just a base class of your entities. Otherwise @Paul Keister's solution would be easier.

Edit

Alternatively you can also try the following:

public static class MyExtensions
{
    public static int? GetId(this Context db, Type entityType, string name)
    {
        return ((IQueryable<DomainEntity>)db.Set(entityType))
            .Where(x => x.Name == name)
            .Select(x => (int?)x.Id)
            .FirstOrDefault();
    }
}

And call it:

int? id = db.GetId("abc", someType);

It will throw an exception though at runtime if someType does not inherit from DomainEntity. The generic version will check this at compile time. So, if you can prefer the first version.

like image 43
Slauma Avatar answered Sep 19 '22 16:09

Slauma


The first construct won't work because you are working with a non-generic DbSet, so you can't apply the FirstOrDefault extension method, which only works with a generic. It sounds like you understand that already, because you're already trying to get the non-generic DbSet. The error you're getting with the Cast() method is caused by your attempt to cast a DbSet to a DbSet. This can't be allowed, because if it were it would be possible to add non-conforming members to the DbSet (objects of type other than WindowsStyle). Another way of saying this is that Covariance is not supported for DbSets because DbSets allow additions.

I think you'll have to find another way to do what you're trying to do. Mixing LINQ and inheritance this way obviously problematic. Since you have a base type defined, and you're only working with attributes that are available on the base type, why not just query the base type?

    public static int GetId(this Context db, string name)
    {
        return db.DomainEntities.FirstOrDefault(x => x.Name == name).Id;
    }

You're probably worried about name collisions between the various types, but you can probably start with this and look at the derived type associations to verify that you're looking at the correct type. One way to deal with this is to add a type flag to your DomainEntity definition.

like image 34
Paul Keister Avatar answered Sep 20 '22 16:09

Paul Keister


Here's the issue. The DbSet class has its own implementation of Cast<T>() that ONLY allows database types (such as Cast<WindowStyle>()), so this method does not allow Cast<DomainEntity>() and is throwing the exception.

Instead, you want to use the IQueryable.Cast<T>() extension method, which will simply cast your data to the base type. Here's an example:

var set = ((IQueryable)db.Set(type)).Cast<DomainEntity>();
return set.First(x => x.Name == name).Id;
like image 26
Scott Rippey Avatar answered Sep 20 '22 16:09

Scott Rippey