HibernateCriteria left join with OR where clause - mysql

We have a problem with one of our queries where the query returns an inner join instead of a left join. The query is intended to retrieve all projects that a user has explicit access rights to (one of the usergroups the user is part of is defined as the project "User" Usergroup) OR implicit action (the project has no "User" Usergroup defined).
The relevant code:
// HibernateCriteria is a custom class that has junction, conjunction, disjunction objects (the ones from Hibernate)
ICriteria criteriaInProjectUserGroup = new HibernateCriteria();
criteriaInProjectUserGroup.addEqualTo(Project.USERGROUP+"." +UserGroup.USERGROUPASSOCIATIONS+"."+UserGroupAssociation.USEROID, user.getOid());
ICriteria criteriaNoProjectUserGroup = CriteriaFactory.createCriteria();
criteriaNoProjectUserGroup.addIsNull(Project.USERGROUP);
/*The join (criteriaIn...) must be added to the simple is null criteria (criteriaNo...),
* to result in a right outer join, otherwise an inner join is created by ojb which returns
* an empty result set */
criteriaNoProjectUserGroup.addOrCriteria(criteriaInProjectUserGroup);
IQuery query = QueryFactory.createQueryByCriteria(ProjectBean.class, criteriaNoProjectUserGroup);
Collection projects = broker.getCollectionByQuery(query);
addEqualTo is defined as:
public void addEqualTo(String attribute, Object value) {
if (value!=null && value instanceof String && containsWildcards((String)value)) {
addLike(attribute, value);
}
else {
attribute = createAliasIfNecessary(attribute);
conjunction.add(eq(attribute, value));
}
}
addIsNull is defined as:
public void addIsNull(String attribute) {
attribute = createAliasIfNecessary(attribute);
conjunction.add(isNull(attribute));
}
addOrCriteria is defined as:
public void addOrCriteria(ICriteria criteria) {
if(criteria instanceof HibernateCriteria) {
HibernateCriteria subCriteria = (HibernateCriteria) criteria;
Junction subJunction = subCriteria.resolveJunction();
if(disjunction == null ) {
disjunction = disjunction();
}
disjunction.add(subJunction);
aliasNameSet.addAll(subCriteria.aliasNameSet);
}
}
resolveJunction is defined as:
private Junction resolveJunction() {
if(junction == null) {
if(disjunction == null) { // no 'or' subcriteria have been added
junction = conjunction; // then the main junction is simply the conjunction
} else {
// disjunction != null means an 'or' subcriteria has been already added.
// then the main junction is disjunction with a conjunction as an operand
junction = disjunction;
junction.add(conjunction);
}
}
return junction;
}
getCollectionByQuery extracts the critera into an org.hibernate.criterion.DetachedCriteria object:
try {
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(clazz);
// create aliases if necessary
Set<String> createdAliasSet = new HashSet<String>();
for(String aliasAssociationPath: aliasNameSet) {
String[] nestedPropertiesArray = aliasAssociationPath.split("\\.");
String previousNestedProperty = null;
for(String nestedProperty: nestedPropertiesArray) {
String currentAliasName =
(previousNestedProperty == null ? nestedProperty : previousNestedProperty + "_" + nestedProperty);
if(!createdAliasSet.contains(currentAliasName)) {
String currentAssociationPath =
(previousNestedProperty == null ? nestedProperty : previousNestedProperty + "." + nestedProperty);
detachedCriteria.createAlias(currentAssociationPath, currentAliasName);
createdAliasSet.add(currentAliasName);
}
previousNestedProperty = currentAliasName;
}
}
detachedCriteria.add(resolveJunction());
return detachedCriteria;
} catch (HibernateException e) {
throw new PersistenceBrokerException(e);
}
It then converts this to a org.hibernate.Criteria object and calls Criteria.list() on that function.
The resulting Mysql query is:
select
this_.oid as oid1_41_2_,
this_.archived as archived2_41_2_,
this_.archivedDateTime as archived3_41_2_,
this_.version as version4_41_2_,
this_.adminGroupOid as adminGro5_41_2_,
this_.buildScript as buildScr6_41_2_,
this_.buildToolType as buildToo7_41_2_,
this_.deployScript as deploySc8_41_2_,
this_.deployToolType as deployTo9_41_2_,
this_.description as descrip10_41_2_,
this_.issueTrackingSystemOid as issueTr11_41_2_,
this_.locked as locked12_41_2_,
this_.name as name13_41_2_,
this_.projectType as project14_41_2_,
this_.testScript as testScr15_41_2_,
this_.testToolType as testToo16_41_2_,
this_.userGroupOid as userGro17_41_2_,
this_.vcrOid as vcrOid18_41_2_,
this_.vcrProjectName as vcrProj19_41_2_,
usergroup1_.oid as oid1_65_0_,
usergroup1_.archived as archived2_65_0_,
usergroup1_.archivedDateTime as archived3_65_0_,
usergroup1_.version as version4_65_0_,
usergroup1_.description as descript5_65_0_,
usergroup1_.name as name6_65_0_,
usergroup1_.type as type7_65_0_,
usergroup_2_.oid as oid1_58_1_,
usergroup_2_.archived as archived2_58_1_,
usergroup_2_.archivedDateTime as archived3_58_1_,
usergroup_2_.version as version4_58_1_,
usergroup_2_.scmUserOid as scmUserO6_58_1_,
usergroup_2_.userGroupOid as userGrou5_58_1_
from
.PROJECT this_
inner join
.USERGROUP usergroup1_
on this_.userGroupOid=usergroup1_.oid
inner join
.SCMUSER_USERGROUP usergroup_2_
on usergroup1_.oid=usergroup_2_.userGroupOid
where
(
(
usergroup_2_.scmUserOid=3
)
or (
this_.userGroupOid is null
)
)
The problem is that this only returns the projects that have a related UserGroup, because it's an inner join. This means that we don't get the projects without a usergroup defined.
I have found that the way to fix this is by using a left outer join for both of the joins, but I can't figure out how to make this work. The problem is that I can't touch the addX methods, because those are used absolutely everywhere in the service classes.
How do I adjust this code so the resultant SQL uses a left outer join instead of an inner join?

After doing some more research, I found that the proper way to fix this is by adding a joinType to createAlias():
detachedCriteria.createAlias(currentAssociationPath, currentAliasName, JoinType.LEFT_OUTER_JOIN);
However, this is a pretty involved fix because there is no current place where we use the joinType parameter, which means I need to further discuss this with more senior developers. For now, I will be using separate queries to retrieve the explicit and implicit access rights and joining them together in Java.

Related

Multiple fields have the same columnName Android Room

I have 3 tables ruser, accounts, accountgroup. Each one has a same column called rsuerId.
I created a POJO class with 3 Embedded objects as below.
class GroupChatItem(
#Embedded
val rUserDto: RUserDto,
#Embedded
val account: AccountDto,
#Embedded
val accountGroup: AccountGroupDto
)
Now, i want to make a query that fetches a GroupChatItem with a given rUserId and accountGroupId like the following.
#Query("""
Select ruser.*, accounts.*, accountgroup.*
from ruser
inner join accounts on accounts.rUserId = ruser.rUserId and accounts.active = 1
inner join accountgroup on accountgroup.rUserId = :rUserId and accountGroup.accountGroupId = :accountGroupId
where ruser.rUserId = :rUserId
""")
suspend fun getGroupChatItem(rUserId: Long, accountGroupId: Int): GroupChatItem
Unfortunately i get the following error.
Multiple fields have the same columnName: rUserId. Field names: rUserDto > rUserId, account > rUserId, accountGroup > rUserId.
I have tried to add a prefix to each embedded object but i get also an error. I dont want to retrieve columns one-by-one because there are many of them.
Is there anything that i missed...??
Thank you
Alternatively you can use the prefix attribute of the Embedded anotation:
class GroupChatItem(
#Embedded(prefix = "user_")
val rUserDto: RUserDto,
#Embedded(prefix = "acc_")
val account: AccountDto,
#Embedded(prefix = "accgr_")
val accountGroup: AccountGroupDto
)
and then alias all the columns of each entity in your SQL query.
I think the prefix attribute is s recent update but I am not sure
I don't believe you have any option other than to have/use :-
a) have distinct columns names across the tables that are to be included in joins (then there is no need to prefix the column names),
or
b) to rename the columns using AS when extracting the values along with a prefix when embedding the entity ensuring that the names match.
I believe that a) would be the simpler option as there is a reduction in the chance of inadvertently using the wrong column name.
As I understand it, the column names have to match for Room to be able to know how to be able to copy a value from the underlying result set, which has no indication of what table a value came from to the value in the returned object or objects.
This is an example of the generated code of a similar scenario 3 embedded entities (User, Office and Places) where some of the column names are the same. They each have and id column and User and Places both have a columns named name.
#Override
public UserOfficePlacesCombined getAllUserOfficePlacesCombined() {
final String _sql = "SELECT user.id AS userid, user.name AS username, office.id AS officeid, office.address AS officeaddress, places.id AS placesid, places.name AS placesname FROM User JOIN Office ON User.id = Office.id JOIN Places ON User.id = Places.id";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
__db.assertNotSuspendingTransaction();
final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "userid");
final int _cursorIndexOfName = CursorUtil.getColumnIndexOrThrow(_cursor, "username");
final int _cursorIndexOfId_1 = CursorUtil.getColumnIndexOrThrow(_cursor, "officeid");
final int _cursorIndexOfAddress = CursorUtil.getColumnIndexOrThrow(_cursor, "officeaddress");
final int _cursorIndexOfId_2 = CursorUtil.getColumnIndexOrThrow(_cursor, "placesid");
final int _cursorIndexOfName_1 = CursorUtil.getColumnIndexOrThrow(_cursor, "placesname");
final UserOfficePlacesCombined _result;
if(_cursor.moveToFirst()) {
final User _tmpUser;
if (! (_cursor.isNull(_cursorIndexOfId) && _cursor.isNull(_cursorIndexOfName))) {
final long _tmpId;
_tmpId = _cursor.getLong(_cursorIndexOfId);
final String _tmpName;
_tmpName = _cursor.getString(_cursorIndexOfName);
_tmpUser = new User(_tmpId,_tmpName);
} else {
_tmpUser = null;
}
final Office _tmpOffice;
if (! (_cursor.isNull(_cursorIndexOfId_1) && _cursor.isNull(_cursorIndexOfAddress))) {
final long _tmpId_1;
_tmpId_1 = _cursor.getLong(_cursorIndexOfId_1);
final String _tmpAddress;
_tmpAddress = _cursor.getString(_cursorIndexOfAddress);
_tmpOffice = new Office(_tmpId_1,_tmpAddress);
} else {
_tmpOffice = null;
}
final Places _tmpPlaces;
if (! (_cursor.isNull(_cursorIndexOfId_2) && _cursor.isNull(_cursorIndexOfName_1))) {
final long _tmpId_2;
_tmpId_2 = _cursor.getLong(_cursorIndexOfId_2);
final String _tmpName_1;
_tmpName_1 = _cursor.getString(_cursorIndexOfName_1);
_tmpPlaces = new Places(_tmpId_2,_tmpName_1);
} else {
_tmpPlaces = null;
}
_result = new UserOfficePlacesCombined();
_result.setUser(_tmpUser);
_result.setOffice(_tmpOffice);
_result.setPlaces(_tmpPlaces);
} else {
_result = null;
}
return _result;
} finally {
_cursor.close();
_statement.release();
}
}
The critical lines are the ones like :-
final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "userid")
This is used to search for the column's names in the Cursor (aka result set) and return the offset to the column, the index then being used to get the actual value from the Cursor.
In your scenario the result set will include some like
rUserId rUserId rUserId*
Which one should it use for which? You may know/understand that first is ruser.rUserId, and that the second is account.rUserId and that the third is accountgroup.rUserId but Room, as it stands, will not know when generating the code. So in all 3 instances when getColumnIndex("rUserId") is used, it will return either 0 (the first) it breaks out of the loop, or 2 if it continues rather than breaks out of the loop (I believe it doesn't break out of the loop).

how to concatenate a field in linq to sql

I would like to achieve the following sql query in Linq to SQL.
SELECT TOP dbo.SiteDept.SiteDeptId, dbo.Site.SiteName + ' / ' + dbo.Dept.DeptName AS SiteDeptName, dbo.SiteDept.SiteId, dbo.SiteDept.DeptId,
dbo.Dept.DeptName
FROM dbo.SiteDept INNER JOIN
dbo.Site ON dbo.SiteDept.SiteId = dbo.Site.SiteId INNER JOIN
dbo.Dept ON dbo.SiteDept.DeptId = dbo.Dept.DeptId
I have a Linq to SQL data context with both a site and Dept entity and a SiteDept Entity that associates departments to the sites.
I have also added in a custom field in the SiteDept partial class for the SiteDeptName.
I was thinking something along the following lines.
public IEnumerable<SiteDept> GetAllSiteDepts()
{
var dataContext = new AtomWebDataContext(_connectionString);
var allSiteDepts = from sd in dataContext.SiteDepts
join s in dataContext.Sites
on sd.SiteId equals s.SiteId
join d in dataContext.Depts
on sd.DeptId equals d.DeptId
select new SiteDept()
{
SiteDeptId = sd.SiteDeptId,
SiteId = sd.SiteId,
DeptId = sd.DeptId,
SiteDeptName = s.SiteName + "/" + d.DeptName
};
return allSiteDepts;
}
However I get a "Explicit construction of entity type 'GPSO.Repository.SiteDept' in query is not allowed."
Whats the best way to achieve what I want?
The problem is that SiteDept is one of your entities and it won't let you create one directly. The way to handle this is by setting up the relationships between the entities and having LINQ to SQL fetch them all, then use the property on the partial class to fill in the name you need.
public IEnumerable<SiteDept> GetAllSiteDepts()
{
var dataContext = new AtomWebDataContext(_connectionString);
var allSiteDepts = from sd in dataContext.SiteDepts
select s;
return allSiteDepts.ToList();
}
// Site and Dept are EntityRefs on SiteDept and Site, respectively
public partial class SiteDept
{
public string SiteDeptName
{
get { return this.SiteName + "/" + this.Site.Dept.Name; }
}
}

How do I return values from a function with LINQ

Below is the code sample I cant figure the return type, this function belongs to a class clsExhibitorlist. I want to bind this to my gridview.
public ??? GetExhibitorList()
{
using (DataClasses1DataContext context = new DataClasses1DataContext())
{
var Exhibitors = from c in context.Companies
join b in context.Booths on c.CoID equals b.CoID
join bc in context.BoothCategories
on b.BoothID equals bc.BoothID
join sp in context.SubProductCategories
on bc.SubProdCatID equals sp.SubProdCatID
join p in context.ProductCategories on
sp.ProdCatID equals p.ProdCatID
orderby c.CoID
select new clsExhibitorList { c.CoID, c.CompanyName, b.FPCoName,p.ProdCatID,sp.SubProdCatID};
if (Keyword != "")
{
Exhibitors = Exhibitors.Where(c => c.CompanyName.Contains(Keyword));
}
if (ProdCatID != "")
{
Exhibitors = Exhibitors.Where(c => c.ProdCatID.Equals(ProdCatID.Split(',')));
}
if (SubProdCatID != "")
{
Exhibitors = Exhibitors.Where(c => c.SubProdCatID.Equals(SubProdCatID.Split(',')));
}
return Exhibitors;
}
}
Ah I dont see anything in the code to actually invoke the query. So the method returns an expression tree representing the query.
If you add something like .ToList() on the return statement the query will be forced to be evaulated and the return type will then be
List < clsExhibitorList >
You can find an explaination of what is going on here (Delayed Evaluation) in this blog post:
http://devlicio.us/blogs/derik_whittaker/archive/2008/04/07/linq-and-delayed-execution.aspx
Note that i dont beleive you can bind against an expression tree to you will have add the .ToList or .ToArray or .ToDictionary or similar, best place to add it would be on the return statement so that as much processing as possible will occur in the database

Excluding list items from query

I have a method which is working fine. I am trying to add some modification to the query and am not able to get through.
public List<ProductDetails> getQuestionsNotRelatedWithProduct(int iProductID, int iSpaceID)
{
var oQuest = (from s in db.Questionaires
join t in db._Product_UserQuestionaires
on s.Questionaire_ID equals t.Questionaire_ID
where !(from n in db.Product_UserQuestionaires
join u in db.Products
on n.Product_ID equals u._Product_ID
where (u.Space_Type_ID == iSpaceID && u.Product_ID == iProductID)
select n.Questionaire_ID).Contains(s.Questionaire_ID)
select new ProductDetails
{
Question = s.Questionaire.ToString(),
QuestionID = s.Questionaire_ID,
}).Distinct();
return oQuest.ToList();
}
i want to go like
public List<ProductDetails> getQuestionsNotRelatedWithProduct(int iProductID, int iSpaceID, IList questid)
{
//here i want to filter out/exclude the "IList questid" items from the query result
not getting how to do.
}
Can anyone please help me
Thanks
Just modify the return statement in your original method to something like this
return oQuest.Where(i => !questid.Contains(i.QuestionID)).ToList();

Explicit construction of entity type '###' in query is not allowed.

Using Linq commands and Linq To SQL datacontext, Im trying to instance an Entity called "Produccion" from my datacontext in this way:
Demo.View.Data.PRODUCCION pocoProduccion =
(
from m in db.MEDICOXPROMOTORs
join a in db.ATENCIONs on m.cmp equals a.cmp
join e in db.EXAMENXATENCIONs on a.numeroatencion equals e.numeroatencion
join c in db.CITAs on e.numerocita equals c.numerocita
where e.codigo == codigoExamenxAtencion
select new Demo.View.Data.PRODUCCION
{
cmp = a.cmp,
bonificacion = comi,
valorventa = precioEstudio,
codigoestudio = lblCodigoEstudio.Content.ToString(),
codigopaciente = Convert.ToInt32(lblCodigoPaciente.Content.ToString()),
codigoproduccion = Convert.ToInt32(lblNroInforme.Content.ToString()),
codigopromotor = m.codigopromotor,
fecha = Convert.ToDateTime(DateTime.Today.ToShortDateString()),
numeroinforme = Convert.ToInt32(lblNroInforme.Content.ToString()),
revisado = false,
codigozona = (c.codigozona.Value == null ? Convert.ToInt32(c.codigozona) : 0),
codigoclinica = Convert.ToInt32(c.codigoclinica),
codigoclase = e.codigoclase,
}
).FirstOrDefault();
While executing the above code, I'm getting the following error that the stack trace is included:
System.NotSupportedException was caught
Message="The explicit construction of the entity type 'Demo.View.Data.PRODUCCION' in a query is not allowed."
Source="System.Data.Linq"
StackTrace:
en System.Data.Linq.SqlClient.QueryConverter.VisitMemberInit(MemberInitExpression init)
en System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.Visit(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.VisitSelect(Expression sequence, LambdaExpression selector)
en System.Data.Linq.SqlClient.QueryConverter.VisitSequenceOperatorCall(MethodCallExpression mc)
en System.Data.Linq.SqlClient.QueryConverter.VisitMethodCall(MethodCallExpression mc)
en System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.Visit(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.VisitFirst(Expression sequence, LambdaExpression lambda, Boolean isFirst)
en System.Data.Linq.SqlClient.QueryConverter.VisitSequenceOperatorCall(MethodCallExpression mc)
en System.Data.Linq.SqlClient.QueryConverter.VisitMethodCall(MethodCallExpression mc)
en System.Data.Linq.SqlClient.QueryConverter.VisitInner(Expression node)
en System.Data.Linq.SqlClient.QueryConverter.ConvertOuter(Expression node)
en System.Data.Linq.SqlClient.SqlProvider.BuildQuery(Expression query, SqlNodeAnnotations annotations)
en System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Execute(Expression query)
en System.Data.Linq.DataQuery`1.System.Linq.IQueryProvider.Execute[S](Expression expression)
en System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source)
en Demo.View.InformeMedico.realizarProduccionInforme(Int32 codigoExamenxAtencion, Double precioEstudio, Int32 comi) en D:\cs_InformeMedico\app\InformeMedico.xaml.cs:línea 602
en Demo.View.InformeMedico.UpdateEstadoEstudio(Int32 codigo, Char state) en D:\cs_InformeMedico\app\InformeMedico.xaml.cs:línea 591
en Demo.View.InformeMedico.btnGuardar_Click(Object sender, RoutedEventArgs e) en D:\cs_InformeMedico\app\InformeMedico.xaml.cs:línea 683
InnerException:
Is that now allowed in LINQ2SQL?
Entities can be created outside of queries and inserted into the data store using a DataContext. You can then retrieve them using queries. However, you can't create entities as part of a query.
I am finding this limitation to be very annoying, and going against the common trend of not using SELECT * in queries.
Still with c# anonymous types there is a workaround, by fetching the objects into an anonymous type, and then copy it over into the correct type.
For example:
var q = from emp in employees where emp.ID !=0
select new {Name = emp.First + " " + emp.Last, EmployeeId = emp.ID }
var r = q.ToList();
List<User> users = new List<User>(r.Select(new User
{
Name = r.Name,
EmployeeId = r.EmployeeId
}));
And in the case when we deal with a single value (as in the situation described in the question) it is even easier, and we just need to copy directly the values:
var q = from emp in employees where emp.ID !=0
select new { Name = emp.First + " " + emp.Last, EmployeeId = emp.ID }
var r = q.FirstOrDefault();
User user = new User { Name = r.Name, EmployeeId = r.ID };
If the name of the properties match the database columns we can do it even simpler in the query, by doing select
var q = from emp in employees where emp.ID !=0
select new { emp.First, emp.Last, emp.ID }
One might go ahead and write a lambda expression that can copy automatically based on the property name, without needing to specify the values explictly.
Here's another workaround:
Make a class that derives from your LINQ to SQL class. I'm assuming that the L2S class that you want to return is Order:
internal class OrderView : Order { }
Now write the query this way:
var query = from o in db.Order
select new OrderView // instead of Order
{
OrderID = o.OrderID,
OrderDate = o.OrderDate,
// etc.
};
Cast the result back into Order, like this:
return query.Cast<Order>().ToList(); // or .FirstOrDefault()
(or use something more sensible, like BLToolkit / LINQ to DB)
Note: I haven't tested to see if tracking works or not; it works to retrieve data, which is what I needed.
I have found that if you do a .ToList() on the query before trying to contruct new objects it works
I just ran into the same issue.
I found a very easy solution.
var a = att as Attachment;
Func<Culture, AttachmentCulture> make =
c => new AttachmentCulture { Culture = c };
var culs = from c in dc.Cultures
let ac = c.AttachmentCultures.SingleOrDefault(
x => x.Attachment == a)
select ac == null ? make(c) : ac;
return culs;
I construct an anonymous type, use IEnumerable (which preserves deferred execution), and then re-consruct the datacontext object. Both Employee and Manager are datacontext objects:
var q = dc.Employees.Where(p => p.IsManager == 1)
.Select(p => new { Id = p.Id, Name = p.Name })
.AsEnumerable()
.Select(item => new Manager() { Id = item.Id, Name = item.Name });
Within the book "70-515 Web Applications Development with Microsoft .NET Framework 4 - Self paced training kit", page 638 has the following example to output results to a strongly typed object:
IEnumerable<User> users = from emp in employees where emp.ID !=0
select new User
{
Name = emp.First + " " + emp.Last,
EmployeeId = emp.ID
}
Mark Pecks advice appears to contradict this book - however, for me this example still displays the above error as well, leaving me somewhat confused. Is this linked to version differences? Any suggestions welcome.
I found another workaround for the problem that even lets you retain your result as IQueryale, so it doesn't actually execute the query until you want it to be executed (like it would with the ToList() method).
So linq doesn't allow you to create an entity as a part of query? You can shift that task to the database itself and create a function that will grab the data you want. After you import the function to your data context, you just need to set the result type to the one you want.
I found out about this when I had to write a piece of code that would produce a IQueryable<T> in which the items don't actually exist in the table containing T.
pbz posted a work around by creating a View class inherited from an entity class that you could be working with. I'm working with a dbml model of a table that has > 200 columns. When I try and return the whole table I get "Root Element missing" errors. I couldn't find anyone who wanted to deal with my particular issue so I was looking at rewriting my entire approach. Just creating a view class for the entitiy class worked in my case.
As pbz suggests : Create a view class that inherits from your entity class. For me this is tbCamp so :
internal class tbCampView : tbCamp
{
}
Then use the view class in your query :
using (var dc = ConnectionClass.Connect(Dev))
{
var camps = dc.tbCamps.Select(s => new tbCampView
{
active = s.active,
idCamp = s.idCamp,
campName = s.campName
});
SmartTableViewer(camps, dg1);
}
private void SmartTableViewer<T>(IEnumerable<T> allRecords)
{
// Build sorted rows back into new table
var table = new DataTable();
// Create columns based on type
if (allRecords is IEnumerable<tbCamp> tbCampRecords)
{
// Get the columns you want
table.Columns.Add("idCamp");
table.Columns.Add("campName");
foreach (var record in tbCampRecords)
{
// Make a new row
var r = table.NewRow();
// Add the contents to each column of the row
r["idCamp"] = record.idCamp;
r["campName"] = record.campName;
// Add the row to the table.
table.Rows.Add(r);
}
}
else
{
MessageBox.Show("Unhandled type. Add support for new data type in SmartTableViewer()");
return;
}
// Update table in grid
dg1.DataSource = table.DefaultView;
}
Here is what happens when you try and create an entity class object in the query.
I didn't want to have to use an anonymous type if I could help it because I wanted the type to be tbCamp. Since tbCampView is of type tbCamp the is operator works well. see Brian Hasden's answer Passing a generic List<> in C#
I'm surprised this is even an issue but with larger tables I run into this error so I thought I would just show it here :
When trying to read this table into memory I get the following error. There are < 2000 rows but the columns are > 200 for each. I don't know if that is an issue or not.
If I just want a few columns I need to create a custom class and handle that which isn't that big of a pain. With the approach pbz provided I don't have to worry about it.
Here is the entire project in case it helps someone.
public partial class Form1 : Form
{
private const bool Dev = true;
public Form1()
{
InitializeComponent();
}
private void btnGetAllCamps_Click(object sender, EventArgs e)
{
using (var dc = ConnectionClass.Connect(Dev))
{
IQueryable<tbCampView> camps = dc.tbCamps.Select(s => new tbCampView
{
// Project columns as needed.
active = s.active,
idCamp = s.idCamp,
campName = s.campName
});
// pass in as a
SmartTableViewer(camps);
}
}
private void SmartTableViewer<T>(IEnumerable<T> allRecords)
{
// Build sorted rows back into new table
var table = new DataTable();
// Create columns based on type
if (allRecords is IEnumerable<tbCamp> tbCampRecords)
{
// Get the columns you want
table.Columns.Add("idCamp");
table.Columns.Add("campName");
foreach (var record in tbCampRecords)
{
//var newRecord = record;
// Make a new row
var r = table.NewRow();
// Add the contents to each column of the row
r["idCamp"] = record.idCamp;
r["campName"] = record.campName;
// Add the row to the table.
table.Rows.Add(r);
}
}
else
{
MessageBox.Show("Unhandled type. Add support for new data type in SmartTableViewer()");
return;
}
// Update table in grid
dg1.DataSource = table.DefaultView;
}
internal class tbCampView : tbCamp
{
}
}