Ken*_*_hj 20 .net c# linq nhibernate linq-to-nhibernate
问题:Nhibernate解析"WHERE IN()"sql中的每个值作为参数,MS SQL服务器不支持足够的参数(超过2000).
我正在使用Nhibernate和Linq来从SQL服务器中检索我的数据,我需要根据已知的ID加载很多实体.
我的代码看起来像这样:
int[] knownIds = GetIDsFromFile();
var loadedEntities = _Repository.GetAll()
.Where(x => knownIds.Contains(x.ID))
.ToList();
Run Code Online (Sandbox Code Playgroud)
哪个给出这样的sql:
SELECT id, name FROM MyTable
WHERE id IN (1 /* @p0 */,2 /* @p1 */,3 /* @p2 */,4 /* @p3 */, 5 /* @p4 */)
Run Code Online (Sandbox Code Playgroud)
如果knownIds中的值太多,那么这段代码将抛出异常,因为NHibernate使用了许多参数.
我认为最好的解决方案是如果我能让NHibernate只为整个"WHERE IN()"使用1个参数,但我不知道如何做到这一点:
SELECT id, name FROM MyTable WHERE id IN (1, 2, 3, 4, 5 /* @p0 */)
Run Code Online (Sandbox Code Playgroud)
我很高兴听到有关如何解决这个问题的任何想法 - 通过扩展LINQ提供程序或通过其他方式.一个解决方案是简单地执行查询x次(knownIds.Count/1000),但我更想要一个适用于我所有实体的通用解决方案.
我试图通过搜索谷歌和Stackoverflow来扩展LINQ提供程序,但我找不到解决方案,我没有任何经验与HQL或treebuilder.这里有一些我去过的网站:
更新:
我知道在IN子句中有这么多的值是不好的做法,但我不知道我想要做的更好的解决方案.
考虑一家公司,每个月,所有客户都会为公司的服务付费一次.该公司本身不处理付款,但有另一家公司来收钱.公司每个月都会收到一个包含这些付款状态的文件:如果已付款或未付款.该文件仅包含特定付款的ID,而不包含客户的ID.一家拥有3000名月度客户的公司每月将进行3000次LogPayments,其状态需要更新.1年后将会有大约36,000个LogPayments,所以加载它们似乎也不是一个好的解决方案.
我的解决方案: 感谢所有有用的答案.最后,我选择使用答案的组合.对于这个具体案例,我做了类似于第四个建议的事情,因为这会大大提高性能.但是我已经实现了Stefan Steinegger建议的通用方法,因为我喜欢我可以做到这一点,如果那是我真正想要的.此外,我不希望我的程序因异常而崩溃,所以将来我也会使用这个ContainsAlot方法作为安全措施.
Ste*_*ger 12
看到这个类似的问题:NHibernate Restrictions.In有数百个值
我通常会设置几个查询,例如1000个条目.只需将你的数组分成几个部分.
像这样的东西:
// only flush the session once. I have a using syntax to disable
// autoflush within a limited scope (without direct access to the
// session from the business logic)
session.Flush();
session.FlushMode = FlushMode.Never;
for (int i = 0; i < knownIds; i += 1000)
{
var page = knownIds.Skip(i).Take(1000).ToArray();
loadedEntities.AddRange(
Repository.GetAll()
.Where(x => page.Contains(x.ID)));
}
session.FlushMode = FlushMode.Auto;
Run Code Online (Sandbox Code Playgroud)
使用标准的通用实现(仅过滤单个属性,这是常见情况):
public IList<T> GetMany<TEntity, TProp>(
Expression<Func<TEntity, TProp>> property,
IEnumerable<TProp> values)
{
string propertyName = ((System.Linq.Expressions.MemberExpression)property.Body).Member.Name;
List<T> loadedEntities = new List<T>();
// only flush the session once.
session.Flush();
var previousFlushMode = session.FlushMode;
session.FlushMode = FlushMode.Never;
for (int i = 0; i < knownIds; i += 1000)
{
var page = knownIds.Skip(i).Take(1000).ToArray();
loadedEntities.AddRange(session
.CreateCriteria(typeof(T))
.Add(Restriction.PropertyIn(propertyName, page)
.List<TEntity>();
}
session.FlushMode = previousFlushMode;
return loadedEntities;
}
Run Code Online (Sandbox Code Playgroud)
要像这样使用:
int[] ids = new [] {1, 2, 3, 4, 5 ....};
var entities = GetMany((MyEntity x) => x.Id, ids);
string[] names = new [] {"A", "B", "C", "D" ... };
var users = GetMany((User x) => x.Name, names);
Run Code Online (Sandbox Code Playgroud)