linq queries in train database - c#

I have a train database, with all required fields such as Train, Route, Station, Passenger, Reservation etc. I want to get trains between a pair of stations using LINQ(I know the SQL Query, but I am unable to convert it to LINQ). Please anyone help me.
One more thing, I want the Extension method approach of LINQ Queries(not the keywords like from, in, select), like
var trains = _context.Trains.FirstOrDefault(a => a.Id == text);
The SQL Query
SELECT Route.TrainId, Train.TrainName
FROM Route
INNER JOIN Train ON Route.TrainId=Train.TrainId
WHERE
(Route.StationId IN
(SELECT Source.StationId
FROM Route AS Source
INNER JOIN Route AS Destination ON Source.TrainId=Destination.TrainId
WHERE
(Source.StopNumber - Destination.StopNumber < 0)
AND
(Source.StationId = #Source)
AND
(Destination.StationId = #Destination)
)
)
Please comment me if you want any more details like table structures. I got the table structure and queries from an online slide.

I used classes to model your database. Try something like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Train> trains = new List<Train>();
string source = "abc";
string destination = "xyz";
var results = trains.Where(x => x.Routes.Any(y => y.StationId == source) && x.Routes.Any(y => y.StationId == destination))
.Select(x => new {
source = x.Routes.Where(y => y.StationId == source).FirstOrDefault(),
destination = x.Routes.Where(y => y.StationId == destination).FirstOrDefault()
})
.Where(x => x.destination.StopNumber > x.source.StopNumber)
.ToList();
}
}
public class Train
{
public string TrainName { get; set; }
public List<Route> Routes { get; set; }
}
public class Route
{
public string TrainId { get; set; }
public string StationId { get; set; }
public int StopNumber { get; set; }
}
}

Related

Custom mapping in Dapper

I'm attempting to use a CTE with Dapper and multi-mapping to get paged results. I'm hitting an inconvenience with duplicate columns; the CTE is preventing me from having to Name columns for example.
I would like to map the following query onto the following objects, not the mismatch between the column names and properties.
Query:
WITH TempSites AS(
SELECT
[S].[SiteID],
[S].[Name] AS [SiteName],
[S].[Description],
[L].[LocationID],
[L].[Name] AS [LocationName],
[L].[Description] AS [LocationDescription],
[L].[SiteID] AS [LocationSiteID],
[L].[ReportingID]
FROM (
SELECT * FROM [dbo].[Sites] [1_S]
WHERE [1_S].[StatusID] = 0
ORDER BY [1_S].[Name]
OFFSET 10 * (1 - 1) ROWS
FETCH NEXT 10 ROWS ONLY
) S
LEFT JOIN [dbo].[Locations] [L] ON [S].[SiteID] = [L].[SiteID]
),
MaxItems AS (SELECT COUNT(SiteID) AS MaxItems FROM Sites)
SELECT *
FROM TempSites, MaxItems
Objects:
public class Site
{
public int SiteID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<Location> Locations { get; internal set; }
}
public class Location
{
public int LocationID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Guid ReportingID { get; set; }
public int SiteID { get; set; }
}
For some reason I have it in my head that a naming convention exists which will handle this scenario for me but I can't find mention of it in the docs.
There are more than one issues, let cover them one by one.
CTE duplicate column names:
CTE does not allow duplicate column names, so you have to resolve them using aliases, preferably using some naming convention like in your query attempt.
For some reason I have it in my head that a naming convention exists which will handle this scenario for me but I can't find mention of it in the docs.
You probably had in mind setting the DefaultTypeMap.MatchNamesWithUnderscores property to true, but as code documentation of the property states:
Should column names like User_Id be allowed to match properties/fields like UserId?
apparently this is not the solution. But the issue can easily be solved by introducing a custom naming convention, for instance "{prefix}{propertyName}" (where by default prefix is "{className}_") and implementing it via Dapper's CustomPropertyTypeMap. Here is a helper method which does that:
public static class CustomNameMap
{
public static void SetFor<T>(string prefix = null)
{
if (prefix == null) prefix = typeof(T).Name + "_";
var typeMap = new CustomPropertyTypeMap(typeof(T), (type, name) =>
{
if (name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
name = name.Substring(prefix.Length);
return type.GetProperty(name);
});
SqlMapper.SetTypeMap(typeof(T), typeMap);
}
}
Now all you need is to call it (one time):
CustomNameMap.SetFor<Location>();
apply the naming convention to your query:
WITH TempSites AS(
SELECT
[S].[SiteID],
[S].[Name],
[S].[Description],
[L].[LocationID],
[L].[Name] AS [Location_Name],
[L].[Description] AS [Location_Description],
[L].[SiteID] AS [Location_SiteID],
[L].[ReportingID]
FROM (
SELECT * FROM [dbo].[Sites] [1_S]
WHERE [1_S].[StatusID] = 0
ORDER BY [1_S].[Name]
OFFSET 10 * (1 - 1) ROWS
FETCH NEXT 10 ROWS ONLY
) S
LEFT JOIN [dbo].[Locations] [L] ON [S].[SiteID] = [L].[SiteID]
),
MaxItems AS (SELECT COUNT(SiteID) AS MaxItems FROM Sites)
SELECT *
FROM TempSites, MaxItems
and you are done with that part. Of course you can use shorter prefix like "Loc_" if you like.
Mapping the query result to the provided classes:
In this particular case you need to use the Query method overload that allows you to pass Func<TFirst, TSecond, TReturn> map delegate and unitilize the splitOn parameter to specify LocationID as a split column. However that's not enough. Dapper's Multi Mapping feature allows you to split a single row to a several single objects (like LINQ Join) while you need a Site with Location list (like LINQ GroupJoin).
It can be achieved by using the Query method to project into a temporary anonymous type and then use regular LINQ to produce the desired output like this:
var sites = cn.Query(sql, (Site site, Location loc) => new { site, loc }, splitOn: "LocationID")
.GroupBy(e => e.site.SiteID)
.Select(g =>
{
var site = g.First().site;
site.Locations = g.Select(e => e.loc).Where(loc => loc != null).ToList();
return site;
})
.ToList();
where cn is opened SqlConnection and sql is a string holding the above query.
You can map a column name with another attribute using the ColumnAttributeTypeMapper.
See my first comment on the Gist for further details.
You can do the mapping like
public class Site
{
public int SiteID { get; set; }
[Column("SiteName")]
public string Name { get; set; }
public string Description { get; set; }
public List<Location> Locations { get; internal set; }
}
public class Location
{
public int LocationID { get; set; }
[Column("LocationName")]
public string Name { get; set; }
[Column("LocationDescription")]
public string Description { get; set; }
public Guid ReportingID { get; set; }
[Column("LocationSiteID")]
public int SiteID { get; set; }
}
Mapping can be done using either of the following 3 methods
Method 1
Manually set the custom TypeMapper for your Model once as:
Dapper.SqlMapper.SetTypeMap(typeof(Site), new ColumnAttributeTypeMapper<Site>());
Dapper.SqlMapper.SetTypeMap(typeof(Location), new ColumnAttributeTypeMapper<Location>());
Method 2
For class libraries of .NET Framework >= v4.0, you can use PreApplicationStartMethod to register your classes for custom type mapping.
using System.Web;
using Dapper;
[assembly: PreApplicationStartMethod(typeof(YourNamespace.Initiator), "RegisterModels")]
namespace YourNamespace
{
public class Initiator
{
private static void RegisterModels()
{
SqlMapper.SetTypeMap(typeof(Site), new ColumnAttributeTypeMapper<Site>());
SqlMapper.SetTypeMap(typeof(Location), new ColumnAttributeTypeMapper<Location>());
// ...
}
}
}
Method 3
Or you can find the classes to which ColumnAttribute is applied through reflection and set type mappings. This could be a little slower, but it does all the mappings in your assembly automatically for you. Just call RegisterTypeMaps() once your assembly is loaded.
public static void RegisterTypeMaps()
{
var mappedTypes = Assembly.GetAssembly(typeof (Initiator)).GetTypes().Where(
f =>
f.GetProperties().Any(
p =>
p.GetCustomAttributes(false).Any(
a => a.GetType().Name == ColumnAttributeTypeMapper<dynamic>.ColumnAttributeName)));
var mapper = typeof(ColumnAttributeTypeMapper<>);
foreach (var mappedType in mappedTypes)
{
var genericType = mapper.MakeGenericType(new[] { mappedType });
SqlMapper.SetTypeMap(mappedType, Activator.CreateInstance(genericType) as SqlMapper.ITypeMap);
}
}
The below code should work fine for you to load a list of sites with associated locations
var conString="your database connection string here";
using (var conn = new SqlConnection(conString))
{
conn.Open();
string qry = "SELECT S.SiteId, S.Name, S.Description, L.LocationId, L.Name,L.Description,
L.ReportingId
from Site S INNER JOIN
Location L ON S.SiteId=L.SiteId";
var sites = conn.Query<Site, Location, Site>
(qry, (site, loc) => { site.Locations = loc; return site; });
var siteCount = sites.Count();
foreach (Site site in sites)
{
//do something
}
conn.Close();
}

How to join two tables with Linq in an MVC controller

I have two sql database tables with a 1:n relationship. For my ASP.NET MVC-solution I have enabled EF-code-first-migration and the proper DBContext and classes established.
I would like to write an MVC-controller that joins both tables in order to select specific records for display in a view.
Here are the two classes:
public class Tbl_Group_Communities : Entity
{
public string GKZ { get; set; }
public int G_ID { get; set; }
}
public class Katastralgemeinden : Entity
{
public string KGNr { get; set; }
public string KGName { get; set; }
public string GKZ { get; set; }
public string GemeindeName { get; set; }
}
So far I have been able to come up with a working controller for the tables by themselves but not joined. Below the working controller for the first class:
public IEnumerable<Tbl_Group_Communities> Get()
{
var entities = UnitOfWork.GetAll<Tbl_Group_Communities>().ToList();
return entities;
}
I think, the join can be done with Linq but I have no idea how/where to start.
The common key for both tables is GKZ; so the join should be established via GKZ. And then I need to select specific records from the joined records where G_ID = a certain value.
If someone could give me some help, I'd be very thankful.
Manu
You can do inner join as shown below.
Assumption : Hope your table names are like Tbl_Group_Communities and Katastralgemeinden.In other words same name as the class names.
from s in db.Tbl_Group_Communities
join sa in db.Katastralgemeinden on s.GKZ equals sa.GKZ
where s.G_ID == 1
select s
You can learn more about join here : Join Operators
I figured it out - here is my controller that works:
using System.Linq;
using System.Web.Http;
using System.Web.Http.OData.Query;
using IndireKat.Data.Contracts;
using IndireKat.Data.Contracts.Entities;
using IndireKat.Shared.Framework.Security;
namespace Presentation.Host.Controllers
{
public class KatastralgemeindenController : BaseODataController
{
private readonly IIdentityStorage identityStorage;
public KatastralgemeindenController(IUnitOfWork unitOfWork, IIdentityStorage identityStorage)
{
UnitOfWork = unitOfWork;
this.identityStorage = identityStorage;
}
[Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]
public IQueryable<Katastralgemeinden> Get()
{
IIndireKatPrincipal indireKatPrincipal = identityStorage.GetPrincipal();
var comunityIds = UnitOfWork.GetAll<UserGroup>()
.Where(group => group.Id == indireKatPrincipal.GroupId)
.SelectMany(group => group.Tbl_Group_Communities).Select(comunity => comunity.GKZ).ToList();
IQueryable<Katastralgemeinden> result = null;
if (comunityIds.Any())
{
result = UnitOfWork.GetAll<Katastralgemeinden>().Where(company => comunityIds.Contains(company.GKZ));
}
return result;
}
}
}
Regards, Manu

LINQ - Left Join of 3 ObservableCollections

I have three classes defined like this:
public class MaterialByOperator
{
public int IdOperator{ get; set; }
public int IdMaterial { get; set;}
}
public class Material
{
public int Id { get; set; }
public string Name { get; set; }
}
public class AssignedOperator
{
public int idOperation { get; set; }
public int idOperator { get; set; }
}
IdMaterial in MaterialByOperator is a "ForeignKey" for Material. The relationship is One to Many.
IdOperator in MaterialByOperator is a "ForeignKey" for AssignedOperator in a One to One relationship.
Then I define this 3 ObservableCollection:
public ObservableCollection<Material> Materials;
public ObservableCollection<MaterialByOperator> MaterialsXOperator;
public ObservableCollection<AssignedOperator> AssignedOperators;
What i want is to get the operator names who does not have any materials asigned. I now do it like this:
var mate = MaterialsXOperator.GroupBy(x => x.idOperator); //Group materials by operatorId
//left join assignedOperators with the grouped materials
var opeasigmate = AssignedOperators.GroupJoin(mate, oper => oper.idOperator,
grupo => grupo.Key, (oper, grupo) => new { oper, grupo });
var operWithoutmate = opeasigmate.Where(x => x.grupo.Count() == 0);
What I want to know, as my LINQ knowledge is not very wide (believe it or not, i had it forbidden in my job for years) is there any simplest way of archieving what i want? As i have told, my solution works but i'd like to see other points of view to hopefully learn by the way.
Using Any is definitely simpler:
var operWithoutmate = AssignedOperators
.Where(ao => !MaterialsXOperator.Any(mo => mo.IdOperator == ao.idOperator);
But using join in general is more efficient, so I would suggest you keeping it that way. The only improvement could be to replace x.grupo.Count() == 0 with !x.grupo.Any(). Also the GroupBy in this case is redundant, so the query could be:
var operWithoutmate = AssignedOperators
.GroupJoin(MaterialsXOperator, ao => ao.idOperator, mo => mo.IdOperator,
(ao, moGroup) => new { ao, moGroup })
.Where(r => !r.moGroup.Any())
.Select(r => r.ao);
I personally find the query syntax to be easier and more readable when there are joins involved:
var operWithoutmate =
from ao in AssignedOperators
join mo in MaterialsXOperator on ao.idOperator equals mo.IdOperator into moGroup
where !moGroup.Any()
select ao;

Linq "join" with a IList<T> getting "Error Unable to create a constant value.."

I have a Save Method that saves with a Linq query a manually re-orderd list (in a web form) that is passed as the parameter to my method, and I try to update the Order Property of the IEnumerable<VM_CategoryLabel> I retrieve from the database (EF) with the corresponding value in the list (maybe would that be clearer with my code below):
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
//Order = x.Sequence_Cat,
Order = (int)listTemplate.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
I used the "test" var to see if my "sub-query" gets the correct value, and it does, but when I use my Linq expression inside the Select (the commented Order line), I get the following error:
Unable to create a constant value of type 'Namespace.Models.VM_CategoryLabelExtra. "Only primitive types and enumeration types are supported in this context.
Here are my classes:
public class VM_CategoryLabel
{
public int Id { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
public class VM_CategoryLabelExtra
{
public int Id { get; set; }
public int IdCat { get; set; }
public int Order { get; set; }
public string Label { get; set; }
public string Unit { get; set; }
public bool Checked { get; set; }
}
So I suppose that I should not query the list inside my query ? So how do I "match" the 2 lists of values ?
I also tried the following (after having replace in the Linq query: Order = x.Sequence_Cat)that is not working neither because the iteration variable is
read-only:
foreach (var item in requete)
{
item.Order = listTemplate.Where(x => x.Id == item.Id).Select(x => x.Order).FirstOrDefault();
}
try
{
context.SaveChanges();
I suggest using this.
It is the let clause.
public static void SaveFromList(IList<VM_CategoryLabelExtra> listTemplate)
{
int idCat = listTemplate.Select(x => x.IdCat).FirstOrDefault();
var test = (int)listTemplate.Where(z => z.Id == 8).Select(z => z.Order).FirstOrDefault();
using (var context = new my_Entities())
{
var requete = from x in context.arc_CatLabel
where x.ID_Categorie == idCat
orderby x.Sequence_Cat
let list = listTemplate
select new VM_CategoryLabel
{
Id = x.ID_LabelPerso,
Order = list.Where(z => z.Id == x.ID_LabelPerso).Select(z => z.Order).First(),
Label = x.arc_Label.Label,
Unit = x.arc_Label.Unit
};
context.SaveChanges();
}
}
edit: instead offrom you can just do let list = listTemplate
Should work now :)
example for let:
// The let keyword in query expressions comes in useful with subqueries: it lets
// you re-use the subquery in the projection:
from c in Customers
let highValuePurchases = c.Purchases.Where (p => p.Price > 1000)
where highValuePurchases.Any()
select new
{
c.Name,
highValuePurchases
}
If you do not know how Let working than please download LinqPad and see an example

Fill property of DTO with SubQuery in NHibernate Query

I have a DTO object like this:
public class TreeViewDTO
{
public string Value { get; set; }
public string Text { get; set; }
public bool HasChildren { get; set; }
}
and my entity mapped with Nhibernate is:
public class Entity
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Entity Parent { get; set; }
/* other properties */
}
I would like to know, how can I get a List of my DTOs and fill the HasChildren property using a count method or a subquery to know if there are childrens?
I have tried this, but does not work:
return Session.QueryOver<Entity>
.Select(entity => new TreeViewViewModel() {
Value = entity.Id.ToString(),
Text = entity.Name,
HasChildren = (Session.QueryOver<Entity>().Where(x => x.ParentId == entity.Id).RowCount() > 0)})
.ToList();
I got an exception with this: NotSupportedException and the messages says: x => (x.Parent.Id == [100001].Id) and it is not supported.
How could I create a query to fill this property?
PS: I would like to have a query to select only the Id, Name and Count... because my entity can have 30 fields or more...
Thank you.
Using the NHibernate Linq provider then you can do this:-
public class dto
{
public long Value { get; set; }
public int Count { get; set; }
public bool HasCount { get { return Count > 0; } }
}
Note: my DTO has a read-only property that looks at the actual count, the query is then:-
var a = Db.Session.Query<Support>().Select(
s => new dto {
Value = s.Id,
Count = s.CommentList.Count
}
).ToList();
This generates the following sQL
select support0_.Id as col_0_0_,
(select cast(count(*) as SIGNED)
from supportcomment commentlis1_
where support0_.Id = commentlis1_.SupportId) as col_1_0_
from support support0_
I have never seen a working example of this using QueryOver. I have had had a stab at it but couldn't get it working..
Didn't you consider the option of using something else rather than NHibernate for this job?
In my opinion, lightweight library like Dapper can be a brilliant solution for this use case. You'll end up with a resonably simple sql query instead of jiggling with Nhibernate.
Edit:
dapper code will be as simple as this:
public IDbConnection ConnectionCreate()
{
IDbConnection dbConnection = new SQLiteConnection("Data Source=:memory:;pooling = true;");
dbConnection.Open();
return dbConnection;
}
public void Select()
{
using (IDbConnection dbConnection = ConnectionCreate())
{
var query = #"SELECT e1.id as Value, e1.name as Text, CASE WHEN EXISTS
(SELECT TOP 1 1 FROM Entity e2 WHERE e2.parent = e1.id)
THEN 1 ELSE 0 END as HasChildren
FROM Entity e1";
var productDto = dbConnection.Query<TreeViewDTO>(query);
}
}

Categories