C# 复杂PK的通用GetById

C# 复杂PK的通用GetById,c#,.net,entity-framework,C#,.net,Entity Framework,我正在寻找一种创建通用GetById的方法,该方法获取params object[]作为参数,知道如何查找key/s字段,并知道如何查找相关实体 在寻找解决方案的过程中,我考虑了返回PK字段定义的通用方法和基于字段返回实体的通用方法 我正在寻找一些我可以在一个或多个字段作为主键的表中使用的东西 编辑 一个或多个字段作为主键示例= 表客户有(公司ID、客户名称、地址、创建日期)。 客户的主键是CompanyId是CustomerName 我正在寻找通用的GetById,它将知道如何处理这些表。我认

我正在寻找一种创建通用GetById的方法,该方法获取
params object[]
作为参数,知道如何查找key/s字段,并知道如何查找相关实体

在寻找解决方案的过程中,我考虑了返回PK字段定义的通用方法和基于字段返回实体的通用方法

我正在寻找一些我可以在一个或多个字段作为主键的表中使用的东西

编辑 一个或多个字段作为主键示例=
表客户有(公司ID、客户名称、地址、创建日期)。
客户的主键是CompanyId是CustomerName


我正在寻找通用的GetById,它将知道如何处理这些表。

我认为您不能实现这样的事情,因为您将无法使用适当的键字段在每个传递的值之间进行连接

我建议对每个实体使用自定义方法:

假设
code
Name
Person
表中的键:

 public IEnumerable<Person> ReadById(int code, string name)
 {
     using (var entities = new Entities())
        return entities.Persons.Where(p => p.Code = code && p.Name = name);
 }
public IEnumerable ReadById(int代码,字符串名称)
{
使用(变量实体=新实体())
返回entities.Persons.Where(p=>p.Code=Code&&p.Name=Name);
}
如果您不知道密钥中有多少成员以及成员的类型,则无法获得“通用”方法。我修改为多个键,但正如您所看到的,它不是通用的-它使用键的定义顺序:

// Base repository class for entity with any complex key
public abstract class RepositoryBase<TEntity> where TEntity : class
{
    private readonly string _entitySetName;
    private readonly string[] _keyNames;

    protected ObjectContext Context { get; private set; }
    protected ObjectSet<TEntity> ObjectSet { get; private set; }

    protected RepositoryBase(ObjectContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        Context = context;
        ObjectSet = context.CreateObjectSet<TEntity>();

        // Get entity set for current entity type
        var entitySet = ObjectSet.EntitySet;
        // Build full name of entity set for current entity type
        _entitySetName = context.DefaultContainerName + "." + entitySet.Name;
        // Get name of the entity's key properties
        _keyNames = entitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray();
    }

    public virtual TEntity GetByKey(params object[] keys)
    {
        if (keys.Length != _keyNames.Length)
        {
            throw new ArgumentException("Invalid number of key members");
        }

        // Merge key names and values by its order in array
        var keyPairs = _keyNames.Zip(keys, (keyName, keyValue) => 
            new KeyValuePair<string, object>(keyName, keyValue));

        // Build entity key
        var entityKey = new EntityKey(_entitySetName, keyPairs);
        // Query first current state manager and if entity is not found query database!!!
        return (TEntity)Context.GetObjectByKey(entityKey);
    }

    // Rest of repository implementation
}
//具有任何复杂键的实体的基本存储库类
公共抽象类RepositoryBase,其中tenty:class
{
私有只读字符串_entitySetName;
私有只读字符串[]\u键名;
受保护的ObjectContext上下文{get;private set;}
受保护的对象集对象集{get;private set;}
受保护的RepositoryBase(ObjectContext上下文)
{
if(上下文==null)
{
抛出新的ArgumentNullException(“上下文”);
}
上下文=上下文;
ObjectSet=context.CreateObjectSet();
//获取当前实体类型的实体集
var entitySet=ObjectSet.entitySet;
//为当前实体类型生成实体集的全名
_entitySetName=context.DefaultContainerName+“+entitySet.Name;
//获取实体的键属性的名称
_keyNames=entitySet.ElementType.KeyMembers.Select(k=>k.Name.ToArray();
}
公共虚拟TEntity GetByKey(参数对象[]键)
{
if(keys.Length!=\u keyNames.Length)
{
抛出新ArgumentException(“关键成员数无效”);
}
//按其在数组中的顺序合并键名和值
var keyPairs=\u keyNames.Zip(键,(keyName,keyValue)=>
新的KeyValuePair(keyName,keyValue));
//生成实体密钥
var entityKey=新的entityKey(_entitySetName,keyPairs);
//查询第一个当前状态管理器,若找不到实体,则查询数据库!!!
return(TEntity)Context.GetObjectByKey(entityKey);
}
//存储库实现的其余部分
}

我不知道这有多有用,因为它是通用的,但您可以这样做:

public TEntity GetById<TEntity>(params Expression<Func<TEntity, bool>>[] keys) where TEntity : class
{
    if (keys == null)
      return default(TEntity);

    var table = context.CreateObjectSet<TEntity>();
    IQueryable<TEntity> query = null;
    foreach (var item in keys)
    {
        if (query == null)
            query = table.Where(item);
        else
            query = query.Where(item);
    }
    return query.FirstOrDefault();
}
public tenty GetById(参数表达式[]键),其中tenty:class
{
如果(键==null)
返回默认值(tenty);
var table=context.CreateObjectSet();
IQueryable查询=null;
foreach(键中的变量项)
{
if(查询==null)
查询=表。其中(项);
其他的
query=query.Where(项目);
}
返回query.FirstOrDefault();
}
然后你可以这样称呼它:

var result = this.GetById<MyEntity>(a => a.EntityProperty1 == 2, a => a.EntityProperty2 == DateTime.Now);
var result=this.GetById(a=>a.EntityProperty1==2,a=>a.EntityProperty2==DateTime.Now);

免责声明:这真的不是一个GetByid,而是一个“让我给你几个参数,并给我第一个匹配的实体”。但也就是说,它使用泛型,如果有匹配项,它将返回一个实体,您可以根据主键进行搜索。

好的,这是我的第二次尝试。我想这对你有用

public static class QueryExtensions
{
    public static Customer GetByKey(this IQueryable<Customer> query, int customerId,string customerName)
    {
        return query.FirstOrDefault(a => a.CustomerId == customerId && a.CustomerName == customerName);
    }

}

显然,对于每种类型,您都必须这样做,但如果您需要的话,这可能是值得的:)

我认为设置有点困难,但我确实认为创建一个可重用的模式将在长期内获得回报。我只是写了这个,还没有测试,但我基于一个我经常使用的搜索模式

所需接口:

public interface IKeyContainer<T>
{
    Expression<Func<T, bool>> GetKey();
}

public interface IGetService<T>
{
    T GetByKey(IKeyContainer<T> key);
}
实施示例:

public class FooKeyContainer : IKeyContainer<Foo>
{
    private readonly int _id;

    public FooKeyContainer(int id)
    {
        _id = id;
    }

    public Expression<Func<Foo, bool>> GetKey()
    {
        Expression<Func<Foo, bool>> key = x => x.Id == _id;
        return key;
    }
}

public class ComplexFooKeyContainer : IKeyContainer<ComplexFoo>
{
    private readonly int _id;
    private readonly int _id2;

    public ComplexFooKeyContainer(int id, int id2)
    {
        _id = id;
        _id2 = id2;
    }

    public Expression<Func<ComplexFoo, bool>> GetKey()
    {
        Expression<Func<ComplexFoo, bool>> key = x => x.Key1 == _id && x.Key2 == _id2;
        return key;
    }
}

public class ComplexFooService : IGetService<ComplexFoo>
{
    public ComplexFoo GetByKey(IKeyContainer<ComplexFoo> key)
    {
       var entities = new List<ComplexFoo>();            
       return entities.Where(key.GetKey()).FirstOrDefault();
    }
}

你能举例说明你想做什么吗?您如何能将“一个或多个字段作为主键”?你是说复合键吗?从概念上讲,这与编译器选择正确重载时需要做的事情类似吗?但这正是我想要避免的——编写每个类的GetById方法。@Napr:我认为没有通用的方法,但我认为不会太多。@Ladislav Mrnka:为什么将存储库标记为抽象?@Ladislav Mrnka:这里的解决方案和这里的解决方案有什么区别:?@Naor:区别在于EF的版本。这一个用于ObjectContext API和EFv4,链接的一个用于DbContext API和EFv4。1@Naor当前位置我为什么标记存储库摘要?因为我不相信通用存储库。泛型respository的思想只适用于非常简单的场景。@Ladislav Mrnka:哪里可以找到关于ObjectContext和DbContext的文档?直到现在我才意识到有两种方法。这是否意味着ObjectContext将被DbContext替换?这与where相同。@Naor,不完全一样,where返回
IQueryable
这将返回
TEntity
,因此这与Single相同:)Jose,这是一个很好的答案,但我正在寻找一种通用的方法,支持多个字段作为主键。在您的版本中,我必须为每个类编写一个方法,并使该方法成为泛型方法,这将使具有多个字段作为pk的表出现问题。
public class Foo
{
    public int Id { get; set; }
}

public class ComplexFoo
{
    public int Key1 { get; set; }

    public int Key2 { get; set; }
}
public class FooKeyContainer : IKeyContainer<Foo>
{
    private readonly int _id;

    public FooKeyContainer(int id)
    {
        _id = id;
    }

    public Expression<Func<Foo, bool>> GetKey()
    {
        Expression<Func<Foo, bool>> key = x => x.Id == _id;
        return key;
    }
}

public class ComplexFooKeyContainer : IKeyContainer<ComplexFoo>
{
    private readonly int _id;
    private readonly int _id2;

    public ComplexFooKeyContainer(int id, int id2)
    {
        _id = id;
        _id2 = id2;
    }

    public Expression<Func<ComplexFoo, bool>> GetKey()
    {
        Expression<Func<ComplexFoo, bool>> key = x => x.Key1 == _id && x.Key2 == _id2;
        return key;
    }
}

public class ComplexFooService : IGetService<ComplexFoo>
{
    public ComplexFoo GetByKey(IKeyContainer<ComplexFoo> key)
    {
       var entities = new List<ComplexFoo>();            
       return entities.Where(key.GetKey()).FirstOrDefault();
    }
}
var complexFoo = ComplexFooService.GetByKey(new ComplexFooKeyContainer(1, 2));