使用Entity Framework创建或更新通用方法

4
我创建了一个通用的Repository类,所有其他Repository类都继承自它。这很好,因为这意味着几乎所有的基础设施只需要一次性地完成即可应用于所有的Repository。我在这里提供了对我所说的内容的全面说明(链接),但以下是我的GenericRepository的代码(为简洁起见,已删除部分代码):
public abstract class GenericRepository<T> : IGenericRepository<T> where T : class, new()
{
    private IMyDbContext _myDbContext;

    public GenericRepository(IMyDbContext myDbContext)
    {
        _myDbContext = myDbContext;
    }

    protected IMyDbContext Context
    {
        get
        {
            return _myDbContext;
        }
    }

    public IQueryable<T> AsQueryable()
    {
        IQueryable<T> query = Context.Set<T>();
        return query;
    }

    public virtual void Create(T entity)
    {
        Context.Set<T>().Add(entity);
    }

    public virtual void Update(T entity)
    {
        Context.Entry(entity).State = System.Data.EntityState.Modified;
    }
}

正如您所见,我有一个Create方法和一个Update方法。拥有一个“CreateOrUpdate”方法会非常方便,这样我就不必每次保存到数据库时手动检查现有对象。
我的Entity Framework中的每个对象都有一个“Id”,但是在此处的挑战是GenericRepository使用“T”。
现在,在那个相当长的介绍之后,来回答我的具体问题。
如何为我的GenericRepository创建一个通用的CreateOrUpdate方法?
更新:
在Marcin的回复之后,我在我的GenericRepository中实现了以下通用方法。在我测试它是否按预期工作之前,需要一些时间,但它看起来非常有前途。
public virtual bool Exists(Guid id)
{
    return Context.Set<T>().Any(t => t.Id == id);
}

public virtual void CreateOrUpdate(T entity)
{
    if (Exists(entity.Id))
    {
        var oldEntity = GetSingle(entity.Id);
        Context.Entry(oldEntity).CurrentValues.SetValues(entity);
        Update(oldEntity);
    }
    else
    {
        Create(entity);
    }
}

上述代码在更新时需要至少3次往返数据库。我相信它可以被优化,但这并不是这个问题的重点。

这个问题更好地处理了这个主题: 一个带有相同键的对象已经存在于ObjectStateManager中。ObjectStateManager无法跟踪具有相同键的多个对象


1
您需要访问数据库以确定实体是需要插入还是更新。您只需要检查实体的ID是否为Guid的默认值(entity.ID == default(Guid))。此外,如果您打算使用延迟加载,您可能希望确保始终创建代理。请参考以下示例:https://dev59.com/P3LYa4cB1Zd3GeqPYoIC#16811976 - Colin
一些业务逻辑可能希望在创建时将Guid设置为主键,因此在我的情况下,我必须进行小的数据库往返。否则,这是一个非常好的观点,在大多数其他情况下都是相关的。谢谢。 - Niels Brinch
实际上,每当我尝试更新“对象状态管理器中已经存在具有相同键的对象。对象状态管理器无法跟踪具有相同键的多个对象。”时,我的实现会出现以下错误。这会在发送具有与现有对象相同的Guid的新对象时发生。这与此问题无关,但是我想让您知道,以防其他人想使用此代码。 - Niels Brinch
让调用者决定是创建还是更新有什么问题?为什么要将逻辑添加到API /应用程序/服务层中? - I Stand With Russia
因为用户界面可能无法确定它是在创建还是更新,所以它只是保存一些信息,并且无论是创建还是更新都是以相同的方式进行。因此,在前端拥有一个单一的方法来处理这两种情况可以节省很多逻辑工作。可能还有其他原因。 - Niels Brinch
1个回答

4
创建一个带有Id属性的接口,在每个实体上实现它,并在你的类中添加另一个泛型约束:
public interface IEntity
{
    int Id { get; set;}
}

并且
public abstract class GenericRepository<T> : IGenericRepository<T> where T : class, IEntity, new()

有了这个,你就可以在通用的存储库类中使用Id属性。

当然,Id不一定是一个int,它也可以是Guid


好主意,我会尝试一下。顺便说一下,在“IEntity”之前需要加上“class”。 - Niels Brinch
@NielsBrinch 不错的发现!MSDN上没有提到这一点,但你是对的:structclass约束必须放在列表的第一位。我已经编辑了我的问题。 - MarcinJuraszek
如果我想让我的id属性成为classNameId,比如userId或clientId,该怎么办? - koryakinp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接