5

我正在尝试添加一个 LINQ 或 DbContext 扩展方法来获取一个元素(FirstOrDefault),但如果一个元素尚不存在,则使用数据(FirstOrCreate)创建一个新实例,而不是返回 null。

这可能吗?

IE:

public static class LINQExtension
{
    public static TSource FirstOrCreate<TSource>(
        this IEnumerable<TSource> source,
        Func<TSource, bool> predicate)
    {
        if (source.First(predicate) != null)
        {
            return source.First(predicate);
        }
        else
        {
            return // ??? 
        }
    }
}

用法可能是:

using (var db = new MsBoxContext())
{
    var status = db.EntitiesStatus.FirstOrCreate(s => s.Name == "Enabled"); 
    //Here we should get the object if we find one
    //and if it doesn't exist create and return a new instance

    db.Entities.Add(new Entity()
    {
         Name = "New Entity",
         Status = status
    });
}

我希望你能理解我的做法。

4

4 回答 4

2
public static class LINQExtension
{
    public static TSource FirstOrCreate<TSource>(
               this IQueryable<TSource> source, 
               Expression<Func<TSource, bool>> predicate, 
               Func<T> defaultValue)
    {
        return source.FirstOrDefault(predicate) ?? defaultValue();
    }
}

用法

var status = db.EntitiesStatus.FirstOrCreate(s => s.Name == "Enabled", 
                 () => new EntityStatus {Name = "Enabled"});

但是您必须注意,这不会像FirstOrDefault().

如果你做了以下

var listOfStuff = new List<string>() { "Enabled" };
var statuses = from s in listOfStuff
               select db.EntitiesStatus.FirstOrCreate(s => s.Name == "Enabled", 
                        () => new EntityStatus {Name = "Enabled"});

您将获得对数据库的 O(n) 次点击。

但是我怀疑如果你这样做...

var listOfStuff = new List<string>() { "Enabled" };
var statuses = from s in listOfStuff
               select db.EntitiesStatus.FirstOrDefault(s => s.Name == "Enabled") 
                             ?? new EntityStatus {Name = "Enabled"};

说不定它可以工作...

于 2013-05-31T02:09:09.860 回答
0

结论:最好的解决方案不是实现扩展方法,而是使用 ?? 以这种方式运营商:

var status = db.EntitiesStatus.FirstOrDefault(s => s.Name == "Enabled") ?? new EntityStatus(){Name = "Enabled"};
于 2013-05-31T04:08:49.853 回答
0

我是一名自学成才的程序员,而且我打字真的很糟糕,所以我一直在寻找完全相同的东西。我最终写了自己的。在它可以与超过 1 个属性一起使用之前,它需要一些步骤和修改。当然有一些限制,我还没有完全测试它,但到目前为止,它似乎对我的目的是保持数据库中的记录不同并缩短代码(键入时间)。

public static class DataExtensions
{
    public static TEntity InsertIfNotExists<TEntity>(this ObjectSet<TEntity> objectSet, Expression<Func<TEntity, bool>> predicate) where TEntity : class, new()
    {
        TEntity entity;

        #region Check DB

        entity = objectSet.FirstOrDefault(predicate);

        if (entity != null)
            return entity;

        #endregion

        //NOT in the Database... Check Local cotext so we do not enter duplicates
        #region Check Local Context

        entity = objectSet.Local().AsQueryable().FirstOrDefault(predicate);

        if (entity != null)
            return entity;

        #endregion

        ///********* Does NOT exist create entity *********\\\
        entity = new TEntity();

        // Parse Expression Tree and set properties
        //Hit a recurrsive function to get all the properties and values
        var body = (BinaryExpression)((LambdaExpression)predicate).Body;
        var dict = body.GetDictionary();

        //Set Values on the new entity
        foreach (var item in dict)
        {
            entity.GetType().GetProperty(item.Key).SetValue(entity, item.Value);
        }

        return entity;
    }

    public static Dictionary<string, object> GetDictionary(this BinaryExpression exp)
    {
        //Recurssive function that creates a dictionary of the properties and values from the lambda expression
        var result = new Dictionary<string, object>();

        if (exp.NodeType == ExpressionType.AndAlso)
        {
            result.Merge(GetDictionary((BinaryExpression)exp.Left));
            result.Merge(GetDictionary((BinaryExpression)exp.Right));
        }
        else
        {
            result[((MemberExpression)exp.Left).Member.Name] = exp.Right.GetExpressionVaule();
        }

        return result;
    }

    public static object GetExpressionVaule(this Expression exp)
    {
        if (exp.NodeType == ExpressionType.Constant)
            return ((ConstantExpression)exp).Value;

        if (exp.Type.IsValueType)
            exp = Expression.Convert(exp, typeof(object));

        //Taken From http://stackoverflow.com/questions/238413/lambda-expression-tree-parsing
        var accessorExpression = Expression.Lambda<Func<object>>(exp);
        Func<object> accessor = accessorExpression.Compile();
        return accessor();
    }

    public static IEnumerable<T> Local<T>(this ObjectSet<T> objectSet) where T : class
    {
        //Taken From http://blogs.msdn.com/b/dsimmons/archive/2009/02/21/local-queries.aspx?Redirected=true
        return from stateEntry in objectSet.Context.ObjectStateManager.GetObjectStateEntries(
                   EntityState.Added | 
                   EntityState.Modified | 
                   EntityState.Unchanged)
               where stateEntry.Entity != null && stateEntry.EntitySet == objectSet.EntitySet
               select stateEntry.Entity as T;
    }

    public static void Merge<TKey, TValue>(this Dictionary<TKey, TValue> me, Dictionary<TKey, TValue> merge)
    {
        //Taken From http://stackoverflow.com/questions/4015204/c-sharp-merging-2-dictionaries

        foreach (var item in merge)
        {
            me[item.Key] = item.Value;
        }
    }
}

用法很简单:

var status = db.EntitiesStatus.InsertIfNotExists(s => s.Name == "Enabled");

扩展将首先检查数据库,如果没有找到它将检查本地上下文(所以你不要添加它两次),如果仍然没有找到它会创建实体,解析表达式树以获取属性和值lambda 表达式,在新实体上设置这些值,将实体添加到上下文并返回新实体。

有几点需要注意...

  1. 这不能处理所有可能的用途(假设 lambda 中的所有表达式都是 ==)
  2. 我在其中执行此操作的项目是使用与 DBContext 相对的 ObjectContext(我还没有切换,所以我不知道这是否适用于 DBContext。我认为更改并不难)
  3. 我是自学成才的,所以可能有很多方法可以优化这一点。如果您有任何意见,请告诉我。
于 2013-09-17T21:16:56.230 回答
0

这个扩展也将新创建的实体添加到 DbSet。

public static class DbSetExtensions
{
    public static TEntity FirstOrCreate<TEntity>(
            this DbSet<TEntity> dbSet,
            Expression<Func<TEntity, bool>> predicate,
            Func<TEntity> defaultValue)
        where TEntity : class
    {
        var result = predicate != null
            ? dbSet.FirstOrDefault(predicate)
            : dbSet.FirstOrDefault();

        if (result == null)
        {
            result = defaultValue?.Invoke();
            if (result != null)
                dbSet.Add(result);
        }

        return result;
    }

    public static TEntity FirstOrCreate<TEntity>(
            this DbSet<TEntity> dbSet,
            Func<TEntity> defaultValue)
        where TEntity : class
    {
        return dbSet.FirstOrCreate(null, defaultValue);
    }

}

谓词的用法:

var adminUser = DbContext.Users.FirstOrCreate(u => u.Name == "Admin", () => new User { Name = "Admin" });

或没有谓词:

var adminUser = DbContext.Users.FirstOrCreate(() => new User { Name = "Admin" });
于 2017-09-17T11:03:08.967 回答