I have a table with these columns(type) as described below.
TABLE
------------------------------------------------------------------
Dir(str) | Twnshp(int) | Rng(int) | Section(int) | Xcell(int) | Ycell(int)
------------------------------------------------------------------
I am trying to do this query using EF.
SELECT Xcell,Ycell FROM [CIR].[dbo].[TRS2Cell] where Twnshp = 1 and Rng = 4 and Section =31
After some study, I created a DAL Context and class as below.
PlotXYContext.cs
public class PlotXYContext :DbContext
{
public DbSet<PlotXY> XYCells { get; set; }
}
PlotXY.cs
[Table("TRS2Cell")]
public class PlotXY
{
public string Dir { get; set; }
[Key]
public int Twnshp { get; set; }
public int Rng { get; set; }
public int Section { get; set; }
public int Xcell { get; set; }
public int Ycell { get; set; }
}
Here is the code in my controller where I pass the three parameters.
PlotXYContext plotXYContext = new PlotXYContext();
var query = from TRS2Cell in plotXYContext.XYCells
where TRS2Cell.Twnshp == 1
&& TRS2Cell.Rng == 4
&& TRS2Cell.Section == 31
select TRS2Cell.Xcell;
I need help with EF as I am new to it and also is this the right query?
If so how do I retrieve the Xcell and Ycell values from the query.
Also the table has no unique column, no nulls, nothing needs to be updated here. All I want is to do a select.
Normally your not gonna want to do any data access code in your controller. You want to keep those separated.
Also when I first started using EF i got hung up on DB Context as well when I started with MVC. If you added your Ado.Net Entity Data Model correctly the db context should be automatically created for you. If you look at "YourEntity".cs file under "Entity".edmx => "Entity".Context.tt it will look something like
public partial class VuittonEntities : DbContext
{
public VuittonEntities()
: base("name=VuittonEntities")
{
}
To help you out with EF I'm gonna post all my code for a query.
So your Model class in the models folder will look like.
public class RoleGridViewModel
{
public int UserID { get; set; }
public string UserFirst { get; set; }
public string UserLast { get; set; }
public string UserRole { get; set; }
public string UserRoleDesc { get; set; }
}
This is your Data Access layer function: Here I'm creating a list of my model class because I'm gonna populate it in a gridview later on.
public List<RoleGridViewModel> GridRoles()
{
using (VuittonEntities db = new VuittonEntities())
{
return (from users in db.User
join roles in db.UserRole on users.RoleID equals roles.RoleID
select new RoleGridViewModel
{
UserID = users.UserID,
UserFirst = users.FirstName,
UserLast = users.LastName,
UserRole = roles.Role,
UserRoleDesc = roles.Role_Desc
}).ToList();
}
}
Here in your Controller you can call it like this. Normally you would call a businezz layer from your controller I'm going straight to the Data layer to show you how its done. Here var roles holds your query. I'm using Json result here but this can also be done in an action result
public JsonResult RolesGrid()
{
var roles = new UserDAL().GridRoles();
return Json(roles, JsonRequestBehavior.AllowGet);
}
If you just want to select a single item you have to use .First() at the end of the query like this...
public string currentRole(UserViewModel uvm)
{
using (VuittonEntities db = new VuittonEntities())
{
return (from us in db.User
join usRole in db.UserRole on us.RoleID equals usRole.RoleID
where (us.RoleID == uvm.RoleID) && (us.UserID == uvm.UserID)
select usRole.Role).First();
}
}
I found that I wasn't using the similar datatype as in my table to declare the class for it.Thant is the only issue I came across that resolved it and hence the error.
Thank you for all the replies.
Related
I am using a DATABASE-FIRST approach in C# MVC and all of my generated models are in a sub-folder Models>Generated. One of these models is called SourceSystem which contains the field definitions of the table and the related table entities.
public partial class SourceSystem
{
[Key]
[Column("ID")]
public int Id { get; set; }
[StringLength(100)]
public string SystemName { get; set; } = null!;
[Column("ROWSTAMP")]
public byte[] Rowstamp { get; set; } = null!;
[StringLength(100)]
public string? LinkedServerName { get; set; }
[StringLength(100)]
public string? DatabaseName { get; set; }
[StringLength(20)]
public string? DefaultSourceSchema { get; set; }
[StringLength(20)]
public string? DefaultTargetSchema { get; set; }
[InverseProperty("SourceSystem")]
public virtual ICollection<Domain> Domains { get; } = new List<Domain>();
[InverseProperty("SourceSystem")]
public virtual ICollection<EventProfile> EventProfiles { get; } = new List<EventProfile>();
}
As part of the application there are also a number of synonyms created which will link back to the source databases (based on the Linked Server Name, Database Name and Default Source Schema. This list of synonymns does not live in MY database but are in the msdb database so I have a view that enables me to generate a dataset of the synonyms and associate them back to the SourceSystem table. For note, DelimitedSpit8K takes a string and spits it up, into a record set. Because synonyms use a 2/3/4 part naming convention, I have to reverse them as I need to definately have the last two parts (schema and object name) but the first two (linked server name and database) are optional. Note also that the schema for the view is pow, not the default dbo.
CREATE VIEW pow.Synonymn AS
SELECT
SYN.object_id AS [SystemID]
,SYN.name AS [Synonym]
,SCH.name AS [SourceSchema]
,SYN.base_object_name
,REPLACE(REPLACE(REVERSE(object_name.Item),'[',''),']','') AS [object_name]
,REPLACE(REPLACE(REVERSE(object_schema.Item),'[',''),']','') AS [object_schema]
,REPLACE(REPLACE(REVERSE(object_db.Item),'[',''),']','') AS [object_db]
,REPLACE(REPLACE(REVERSE(object_linked_server.Item),'[',''),']','') AS [object_linked_server]
,SS.ID AS [SourceSystem_Id]
FROM
sys.synonyms AS SYN
JOIN
sys.schemas AS SCH ON SCH.schema_id = SYN.schema_id
JOIN
pow.SourceSystem AS SS ON SS.DefaultTargetSchema = SCH.name
CROSS APPLY
pow.DelimitedSplit8K(REVERSE(SYN.base_object_name), '.') AS [object_name]
CROSS APPLY
pow.DelimitedSplit8K(REVERSE(SYN.base_object_name), '.') AS [object_schema]
CROSS APPLY
pow.DelimitedSplit8K(REVERSE(SYN.base_object_name), '.') AS [object_db]
CROSS APPLY
pow.DelimitedSplit8K(REVERSE(SYN.base_object_name), '.') AS [object_linked_server]
WHERE
object_name.ItemNumber =1
AND
object_schema.ItemNumber = 2
AND
object_db.ItemNumber = 3
AND
(
object_linked_server.ItemNumber IS NULL
OR
object_linked_server.ItemNumber = 4
)
I have manually added a model to my models folder (not Models>Generated):
using Overwatch_API.Models.Generated;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using Microsoft.EntityFrameworkCore;
namespace Overwatch_API.Models;
//[Table("Synonym", Schema = "pow")]
public partial class Synonym
{
[Key]
[Column("SystemID")]
public int SystemID { get; set; }
[Column("Synonym")]
public string SynonymName { get; set; }
[Column("SourceSystemTargetSchema")]
public string SourceSchema { get; set; } = null!;
[Column("SourceSystemId")]
public int SourceSystem_Id { get; set; }
public string base_object_name { get; set; }
public string object_name { get; set; }
public string object_schema { get; set; }
public string object_db { get; set; }
public string object_linked_server { get; set; }
[ForeignKey("SourceSystemId")]
[InverseProperty("Synonyms")]
//[JsonIgnore]
public virtual SourceSystem SourceSystem { get; set; } = null!;
}
and I have modified the database context:
using Overwatch_API.Models;
...
public virtual DbSet<Synonym> Synonyms { get; set; }
...
modelBuilder.Entity<Synonym>(entity =>
{
entity.ToView(nameof(Synonym))
.HasKey(t => t.SystemID);
});
and I have updated the ViewModel for the SourceSystemVM:
using Overwatch_API.Models;
...
public class SourceSystemVM
{
public int Id { get; set; }
[DisplayName("System Name")]
public string SystemName { get; set; }
[DisplayName("Linked Server")]
public string? LinkedServerName { get; set; }
[DisplayName("Linked Database")]
public string? DatabaseName { get; set; }
[DisplayName("Source Schema")]
public string? DefaultSourceSchema { get; set; }
[DisplayName("Target Schema")]
public string? DefaultTargetSchema { get; set; }
public ICollection<DomainVM> domains { get; set; }
public ICollection<Synonym> synonyms { get; set; }
public SourceSystemVM(SourceSystem ss)
{
Id = ss.Id;
SystemName = ss.SystemName;
LinkedServerName = ss.LinkedServerName;
DatabaseName = ss.DatabaseName;
DefaultSourceSchema = ss.DefaultSourceSchema;
DefaultTargetSchema = ss.DefaultTargetSchema;
domains = new List<DomainVM>();
synonyms = new List<Synonym>();
}
}
When I start the server and run Swagger, and choose the api endpoint
https://localhost:7001/api/SourceSystems
I get the following error message:
InvalidOperationException: The [InverseProperty] attribute on property 'Synonym.SourceSystem' is not valid. The property 'Synonyms' is not a valid navigation on the related type 'SourceSystem'. Ensure that the property exists and is a valid reference or collection navigation.
I am not sure what part of the configuration I have got wrong. I don't want to touch the SourceSytem.cs in the Models>Generated folder as it will get overwritten if the DF models are re-generated. Do I need to create a new partial class in the models folder to extend the generated model, and if so, what would that look like and how do I disambiguate between the Models>SourceSystem.cs and the Models>Generated>SourceSystem.cs when referencing it in the VM and DTOs. Or am I missing an entire concept somewhere?
For context, the Synonym collection is used for view (read) only. The functionality to add a new synonym will have to be managed through a call to a SQL stored procedure, but I need to understand what I have screwed up here first :)
UPDATE
I have added the partial class to the Models folder:
using Overwatch_API.Models;
using Overwatch_API.Models.Generated;
using System.ComponentModel.DataAnnotations.Schema;
namespace Overwatch_API.Models.Generated
{
public partial class SourceSystem
{
[InverseProperty("SourceSystem")]
public virtual ICollection<Synonym> Synonyms { get; } = new List<Synonym>();
}
}
and updated the context:
modelBuilder.Entity<Synonym>(entity =>
{
entity.ToView("Synonym", "pow");
});
and now the API doesn't throw an error message but the synonym array is empty and I'm not sure why: Whether the relationship between the Synonym and SourceSystems is not defined correctly or if the view is not being found/executed to return the details.
UPDATE 2: As per the question from Alex:
I have set up the following in the dbContext:
modelBuilder.Entity<Synonym>(entity =>
{
entity.ToView("Synonym", "pow");
entity.HasOne(d => d.SourceSystem)
.WithMany(p => p.Synonyms)
.HasForeignKey(x => x.SourceSystemId);
});
The Profiler is showing the following queries being run. For the Synonymns API [HttpGet]
SELECT [s].[SystemID], [s].[SourceSchema], [s].[SourceSystem_ID], [s].[Synonym], [s].[base_object_name], [s].[object_db], [s].[object_linked_server], [s].[object_name], [s].[object_schema]
FROM [pow].[Synonym] AS [s]
For the SourceSystem API [HttpGet]
SELECT [s].[ID], [s].[DatabaseName], [s].[DefaultSourceSchema], [s].[DefaultTargetSchema], [s].[LinkedServerName], [s].[ROWSTAMP], [s].[SystemName], [d].[ID], [d].[DomainName], [d].[ROWSTAMP], [d].[SourceSystem_ID]
FROM [pow].[SourceSystem] AS [s]
LEFT JOIN [pow].[Domain] AS [d] ON [s].[ID] = [d].[SourceSystem_ID]
ORDER BY [s].[ID]
Domain is another collection within SourceSystem but it unrelated to the Synonyms. A single join here would create a cartesion collection with both the Domains and Synonymns being repeated. Could this be the problem? The data fetch would either need to do an N+1 query or bring back the cartesian collection and then filter distinct. If so, how do I get around the problem. Is there a way to lazy-load the synonymns in MVC. I could just load them all in the front end (React/Next) and apply a filter in JS to only show the ones connected with the selected SourceSystem but this is spreading the logic about throughout the application stack.
OK. So I worked it out and it is non-trivial so hoping that this helps someone else.
SourceSystems contains multiple ICollections (Domains and Synonyms amongst them) however these create cyclic dependencies so the get uses a SourceSystemViewModel which uses a DomainViewModel which does not contain the cyclic reference back to the SourceSystem. I had to add the Synonyms to the SourceSystemViewModel, but as the Synonyms also contain the cyclic reference I have to create a SynonymViewModel as well.
and then in the SourceSystemsController, when executing the _context.SourceSystems you have to tell it to .Include("ChildCollection") which I had not done.
var ssList = _context.SourceSystems
.Include("Domains")
.Include("Synonyms")
.ToList();
Once this is included you then have to specifically iterate through the ssList, and for each SourceSystemDTO, iterate through both the Domains list and the Synonyms list and map the list items into the relevant arrays.
[HttpGet]
[ResponseType(typeof(List<SourceSystemVM>))]
public async Task<IEnumerable<SourceSystemVM>> GetSourceSystems()
{
List<SourceSystem> ss = _context.SourceSystems.ToList();
IEnumerable<SourceSystemVM> ssDTO = from s in ss select new SourceSystemVM(s);
var ssList = _context.SourceSystems
.Include("Domains")
.Include("Synonyms")
.ToList();
var ssDTOList = new List<SourceSystemVM>();
ssList.ForEach(ss =>
{
var ssDTO = new SourceSystemVM(ss);
foreach (var domain in ss.Domains)
{
var domainDTO = new DomainVM(domain);
ssDTO.domains.Add(domainDTO);
}
foreach (var synonym in ss.Synonyms)
{
var synonymDTO = new SynonymVM(synonym);
ssDTO.synonyms.Add(synonymDTO);
}
ssDTOList.Add(ssDTO);
});
return ssDTOList;
}
I have a model in Entity Framework Core that goes something like this:
public class Anime
{
public int EpisodeCount { get { return Episodes.Count() } }
public virtual ICollection<Episode> Episodes { get; set; }
}
I'm having the issue of EpisodeCount being 0. The solution currently is to run a .Include(x => x.Episodes) within my EF query, but that loads the entire collection of episodes where it's not needed. This also increases my HTTP request time, from 100ms to 700ms which is just not good.
I'm not willing to sacrifice time for simple details, so is there a solution where I can have EF only query the COUNT of the episodes, without loading the entire collection in?
I was suggested to do this
var animeList = context.Anime.ToPagedList(1, 20);
animeList.ForEach(x => x.EpisodeCount = x.Episodes.Count());
return Json(animeList);
but this also returns 0 in EpisodeCount, so it's not a feasible solution.
You need to project the desired data into a special class (a.k.a. ViewModel, DTO etc.). Unfortunately (or not?), in order to avoid N + 1 queries the projection must not only include the count, but all other fields as well.
For instance:
Model:
public class Anime
{
public int Id { get; set; }
public string Name { get; set; }
// other properties...
public virtual ICollection<Episode> Episodes { get; set; }
}
ViewModel / DTO:
public class AnimeInfo
{
public int Id { get; set; }
public string Name { get; set; }
// other properties...
public int EpisodeCount { get; set; }
}
Then the following code:
var animeList = db.Anime.Select(a => new AnimeInfo
{
Id = a.Id,
Name = a.Name,
EpisodeCount = a.Episodes.Count()
})
.ToList();
produces the following single SQL query:
SELECT [a].[Id], [a].[Name], (
SELECT COUNT(*)
FROM [Episode] AS [e]
WHERE [a].[Id] = [e].[AnimeId]
) AS [EpisodeCount]
FROM [Anime] AS [a]
I am having trouble accessing a property from a joined table, using EF Code First. Here is my query in my home controller:
var pcs = from h in db.Hardwares
join hwt in db.HardwareTypes
on h.HardwareTypeId equals hwt.Id
where h.HardwareType.HType == "PC"
select new { Hardware = h, HardwareType = hwt };
ViewBag.Pcs = pcs.ToList();
Here is my HardwareTypes class:
public class HardwareType
{
public int Id { get; set; }
//[Required]
//[StringLength(128)]
public string HType { get; set; }
}
Here is my Hardware class:
public class Hardware
{
public int Id { get; set; }
public int HardwareTypeId { get; set;}
public virtual HardwareType HardwareType { get; set; }
}
How do I change my LINQ query so HType is in my ViewBag? The database seems to be generating correctly, it's just I can't seem to access HType. I get an error 'object' does not contain a definition for 'HType'
Ok, time for a full reset. Based on the chat, I want you to try the following:
1) Keep your original LINQ query, but don't make it an anonymous type. Create another object type that will have a Hardware and a HardwareType property in it. This will make Pcs (as well as ViewBag.Pcs) a List of that particular type.
2) Put the following foreach loop in your View code:
foreach (var item in ViewBag.Pcs as List<YourTypeName>)
{
var htype = item.HardwareType.HType;
}
And if you don't want to do some lazy loading each time through the loop, you can do the following in the controller to just load up the entities right away:
var hardwareTypes = pcs.Select(p => p.HardwareType).ToList();
Let me know if that works.
I am encountered an error that I am not familier with. I tried to google with no success.
I wrote the following query where I am having this error.
The entity or complex type 'MyWebProject.Models.UserDetail' cannot be constructed in a LINQ to Entities query.
The query:
UsersContext db = new UsersContext();
var userdata = (from k in db.UserDetails
where k.UserId == WebSecurity.CurrentUserId
select new UserDetail()
{
FullName = k.FullName,
Email = k.Email,
About = k.About,
Link = k.Link,
UserSchool = new School()
{
SchoolId = k.UserSchool.SchoolId,
SchoolName = k.UserSchool.SchoolName
},
UserCourse = new Course()
{
CourseId=k.UserCourse.CourseId,
CourseName=k.UserCourse.CourseName
},
Country=k.Country
}).FirstOrDefault();
Class:
public class UserDetail
{
public int Id { get; set; }
public int UserId { get; set; }
public string FullName { get; set; }
public string Link { get; set; }
public bool? Verified { get; set; }
public string Email { get; set; }
public string About { get; set; }
public School UserSchool { get; set; }
public Course UserCourse { get; set; }
public string Country { get; set; }
}
public class School
{
public int SchoolId { get; set; }
public string SchoolName { get; set; }
public string Country { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string CourseName { get; set; }
public School School { get; set; }
}
Any idea what went wrong??
It looks like it is due to how you are creating the complex properties School and Course in the middle of the query. It would be better to select the User (remove the select transformation), then use navigation properties to access those objects instead of building them manually. The navigation are meant for this as long as you have the proper relations built with foreign keys.
UsersContext db = new UsersContext();
var userdata = (from k in db.UserDetails
where k.UserId == WebSecurity.CurrentUserId})
.FirstOrDefault();
// access navigation properties which will perform the joins on your behalf
// this also provides for lazy loading which would make it more effecient. (it wont load the school object until you need to access it)
userdata.School
userdata.Course
MSDN article about navigation properties: http://msdn.microsoft.com/en-us/library/vstudio/bb738520(v=vs.100).aspx
This should give you what you want. It will load your objects as part of the query (and not rely on lazy loading).
UsersContext db = new UsersContext();
var userdata = db.UserDetails.Include(x => x.UserSchool)
.Include(x => x.UserCourse)
.Include(x => x.Country)
.Where(x => x.UserId == WebSecurity.CurrentUserId)
.FirstOrDefault();
I think it's because your entity has the same name of the object you're trying to create. Try renaming the object you want to return back. If you want to return the same type as your entity try the eager loading with .Include("relationshipname") feature.
A great answer from #Yakimych is given below.
You cannot (and should not be able to) project onto a mapped entity. You can, however, project onto an annonymous type or onto a DTO:
public class ProductDTO
{
public string Name { get; set; }
// Other field you may need from the Product entity
}
And your method will return a List of DTO's.
public List<ProductDTO> GetProducts(int categoryID)
{
return (from p in db.Products
where p.CategoryID == categoryID
select new ProductDTO { Name = p.Name }).ToList();
}
Mapped entities in EF basically represent database tables. If you project onto a mapped entity, what you basically do is partially load an entity, which is not a valid state. EF won't have any clue how to e.g. handle an update of such an entity in the future (the default behaviour would be probably overwriting the non-loaded fields with nulls or whatever you'll have in your object). This would be a dangerous operation, since you would risk losing some of your data in the DB, therefore it is not allowed to partially load entities (or project onto mapped entities) in EF.
For more details please go to the following link:
The entity cannot be constructed in a LINQ to Entities query
I'm new to RaveDB (Actually I've started learn it only yesterday). And try to implement some functionality.
Lets we have next class hierarchy:
public abstract class Transaction
{
public string Id { get; set; }
}
public class CategoryTransaction : Transaction
{
public string AccountId { get; set; }
public string Name { get; set; }
public string Category { get; set; }
public decimal Amount { get; set; }
}
public class ExchangeTransaction : Transaction
{
public string DebitAccountId { get; set; }
public string CreditAccountId { get; set; }
public decimal DebitAmount { get; set; }
public decimal CreditAmount { get; set; }
}
Everything storing excelent int db. I event add Conventions = FindTypeTagName ... for store documents as 'transactions/*' document id.
But I want to create index for select all transactions for specific Account (by field AccountId). I've create index:
public class AccountTransactionResult
{
public string AccId { get; set; }
}
public class Account_Transactions : AbstractMultiMapIndexCreationTask<AccountTransactionResult>
{
public Account_Transactions()
{
this.AddMap<CategoryTransaction>( transactions => from transaction in transactions
select new
{
AccId = transaction.AccountId
} );
this.AddMap<ExchangeTransaction>( transactions => from transaction in transactions
select new
{
AccId = transaction.DebitAccountId
} );
this.AddMap<ExchangeTransaction>( transactions => from transaction in transactions
select new
{
AccId = transaction.CreditAccountId
} );
}
}
This index works well I can't get all type of transactions from DB (Exchange and Category) as single IEnumerable<Transaction>. That is great.
But I want to use the index to return transactions only for particular account. Because ExchangeTransaction can belong to two Accounts. I want to see ExchangeTransaction for both Accounts. I can make query in Raven Studio by AccId (index field) and it works greate! But I can't create the same request in the code :(.
Can someone help me? How can I use index field AccId in C# LINQ query?
This code
var query = session.Query<Transaction, Account_Transactions>()
return all type of transaction, but I don't how to filter the transactions by index field in the DB.
Thanks in advance. And please sorry me my English, it is not my native language.
My fault, in documentation everythings is clear. I missed some useful paragraph.
The easiest way to do this is by writing a multi-map index like this
one:
public class AnimalsIndex : AbstractMultiMapIndexCreationTask
{
public AnimalsIndex()
{
AddMap<Cat>(cats => from c in cats
select new { c.Name });
AddMap<Dog>(dogs => from d in dogs
select new { d.Name });
}
}
And query it like this:
var results = session.Advanced.LuceneQuery<object>("AnimalsIndex")
.WhereEquals("Name", "Mitzy");
>
In my case I should use next construction:
var transactions = session.Advanced.LuceneQuery<Transaction>( "Account/Transactions" )
.WhereEquals( "AccId", account2.Id )
And everything is working now.