添加一个LINQ或DBContext扩展方法来获取一个不存在的元素,然后在谓词(FirstOrCreate)中创建数据

本文关键字:一个 然后 谓词 数据 元素 创建 FirstOrCreate DBContext LINQ 扩展 方法 | 更新日期: 2023-09-27 18:16:05

我试图添加一个LINQ或DbContext扩展方法来获得一个元素(FirstOrDefault),但如果一个还不存在,那么创建一个新的实例与数据(FirstOrCreate)而不是返回null。

这可能吗?

。:

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
    });
}

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

添加一个LINQ或DBContext扩展方法来获取一个不存在的元素,然后在谓词(FirstOrCreate)中创建数据

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"};

结论:代替实现扩展方法的最佳解决方案是使用??操作符:

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

我是一个自学成才的程序员,我真的不擅长打字,所以我在寻找完全相同的东西。最后我自己写了一本。在它可以处理多个属性之前,需要进行一些步骤和修改。当然有一些限制,我还没有完全测试它,但到目前为止,它似乎为我的目的保持记录在数据库中不同,缩短代码(打字时间)。

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. 我这样做的项目是使用ObjectContext而不是DBContext(我还没有切换,所以我不知道这是否会与DBContext一起工作。我想这应该不难改变)
  3. 我是自学的,所以可能有很多方法来优化这个。如果您有任何意见,请告诉我。

这个扩展还将新创建的实体添加到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" });
相关文章: