How to retrieve specific columns in Entity Framework - c#

Can I make my EF objects retrieve only specific columns in the sql executed?
If I have a column that contains a large amount of data that really slows down the query, how can I have my objects exclude that column from the sql generated?
If my table has Id(int), Name(int), Data(blob), how can I make my query be
select Id, Name from TableName
instead of
select Id, Name, Data from TableName
From the suggestion below, my method is
public List<T> GetBy<T>(DbContext context,Expression<Func<T, bool>> exp, Expression<Func<T,T>> columns) where T : class
{
return dbContext.Set<T>().Where(exp).Select<T,T>(columns).ToList();
}
And I'm calling it like so
List<CampaignWorkType> list = GetBy<CampaignWorkType>(dbContext, c => c.Active == true, n => new { n.Id, n.Name });
i got an error like below.
Cannot implicitly convert type 'AnonymousType#1' to 'Domain.Campaign.CampaignWorkType'
how i can solve this?

The solution is:
First, define a surrogate type:
public class CampaignWorkTypesSimpleList
{
public int Id { get; set; }
public string Name { get; set; }
}
Then change generic method like this:
public List<U> GetBy<T,U>(DbContext context,Expression<Func<T, bool>> exp, Expression<Func<T,U>> columns)
where T : class
where U : class
{
return dbContext.Set<T>().Where(exp).Select<T, U>(columns).ToList();
}
Finally, execute it.
List<CampaignWorkTypesSimpleList> list = this.GetBy<CampaignWorkType, CampaignWorkTypesSimpleList>(dbContext, c => c.Active == true, n => new CampaignWorkTypesSimpleList { Id = n.Id, Name = n.Name });

Assuming you are using the EF designer, remove the [Data] column from the Entity Model using the Entity Model design surface.

Related

Dapper with Mapping by code: Multi-Mapping with repeating column names

I'm trying to perform a simple query and the result data is almost all null.
I have this table structure
Table Registros
ID | Autonumeric
TareaM_Id | Numeric
Fecha | Date/Time
and Macro_tareas table
ID | Autonumeric
Nombre | Short Text
I have mapped the classes in C# like this:
[Table("Registros")]
public class Registro
{
[Column("ID")]
public virtual int ID { get; set; }
[Column("Fecha")]
public virtual DateTime Fecha { get; set; }
[Column("TareaM_Id")]
public virtual int TareaM_Id { get; set; }
public virtual MacroTarea MacroT { get; set; }
}
[Table("Macro_tarea")]
public class MacroTarea
{
[Column("ID")]
public virtual int ID { get; set; }
[Column("Nombre")]
public virtual string Nombre{ get; set; }
public virtual ICollection<Registro> Registros { get; set; }
}
This is the query i'm trying to use
string sql = #"SELECT reg.ID, mac.ID
FROM Registros as reg INNER JOIN Macro_tarea as mac on reg.TareaM_Id = mac.ID
WHERE Fecha = #Fecha";
using (IDbConnection db = new OleDbConnection(ConnectionString))
{
var result = db.Query<Registro,MacroTarea, Registro>(sql,
(reg,mac) =>
{
reg.MacroTarea = mac;
return reg;
}
,new { #Fecha = new DateTime(2019, 1, 4).Date }
, splitOn: "mac.ID")
.AsList();
}
I'm trying to only retrieve ids, but both id become null why is this happening?
The thing is, if I add Registros.Fecha and Macro_tarea.Nombre to the query, it got the value correctly. But id keep coming null.
Apparently the issue is happening only with ids. I suspect this issue is due to duplicate column names.
I'm working with Microsoft Access just in cast that matters.
My question is not similar to the possible duplicate because I have the classes defined as they should be mapped.
Renaming your database columns because your code cannot cope with the data is not a good idea. In the world of separation of concerns, why should your database care? There are good database reasons to name ID columns "Id", and you may not even have the option to change them.
There's another issue with Dapper mapping that renaming columns does not get around; repeated types. If you are trying to map to more than one instance of a class Dapper gets confused, and renaming columns won't work because you will rename both instances.
Here is the solution I have come up with. It's similar to a lot of examples that use a dictionary, except:
it can nest to as many levels as you like
can cope with Dappers 7 item limit
can cope with duplicates of the same class
can be reused e.g., for Get, GetCurrent and GetAll
In this example there is an Auction that has many Lots. Each Lot may have 1 or many Items. Items might be packs of Items. The Items are from a limited catalogue and we like relational data, so a Things table contains the details on each Item, like colour, size, etc. Here we are only getting a single Lot, but getting an Auction is the same with another level on top for Auction.
Parameter 1 - The SQL to get everything in one go
Parameter 2 - A Type array of each object we'll get back. For this reason it's best to order your SELECT to group the fields into the classes
Parameter 3 - Call the method we're about to write with the SQL result
Parameter 4 - Standard parameter array for the SQL. SQL Injection is bad, m'kay?
public async Task<List<Lot>> GetAll(int auctionId)
{
using (var connection = new SqlConnection(_appSettings.ConnectionString))
{
await connection.OpenAsync();
var result = new List<Lot>();
await connection.QueryAsync($#"
SELECT [Lot].*,
[Item].[Id],
[Item].[LotId],
[Item].[Notes],
itemDetails.[Id],
itemDetails.[ThingId],
itemDetails.[Colour],
itemDetails.[Size],
[SubItem].[Id],
[SubItem].[ItemId],
[SubItem].[Notes],
subItemDetails.[Id],
subItemDetails.[ThinId],
subItemDetails.[Colour],
subItemDetails.[Size]
FROM [Lot]
INNER JOIN [Item] ON [Item].[LotId] = [Lot].[Id]
LEFT JOIN [Thing] AS itemDetails ON itemDetails.[Id] = [Item].[ThingId]
LEFT JOIN [SubItem] ON [SubItem].[ItemId] = [Item].[Id]
LEFT JOIN [Thing] AS subItemDetails ON subItemDetails.[Id] = [SubItem].[ThingId]
WHERE [AuctionId] = #{nameof(auctionId)}
ORDER BY [Lot].[Id], [Item].[Id], [Expansion].[Id];",
new Type[] {
typeof(Lot),
typeof(Item),
typeof(Thing),
typeof(Expansion),
typeof(Thing)
},
MapResult(result),
new
{
AuctionId = auctionId
}
);
return result.ToList();
}
}
private Func<object[], Lot> MapResult(List<Lot> result)
{
return (obj) =>
{
Lot lot = (Lot)obj[0];
Item item = (Item)obj[1];
Thing itemDetails = (Thing)obj[2];
SubItem subItem = (SubItem)obj[3];
Thing subItemDetails = (Thing)obj[4];
if (lot != null)
{
if (result.Any(a => a.Id == lot.Id))
{
lot = result.First(a => a.Id == lot.Id);
}
else
{
result.Add(lot);
}
}
if (item != null)
{
if (lot.Items.Any(i => i.Id == item.Id))
{
item = lot.Items.First(i => i.Id == item.Id);
}
else
{
lot.Items.Add(item.FromThing(itemDetails));
}
}
if (expansion != null)
{
if (item.SubItems.Any(e => e.Id == subItem.Id) == false)
{
item.SubItems.Add(subItem.FromThing(subItemDetails));
}
}
return null;
};
}
MapResult is the meat of the code. It returns a Func with two types, the Type array we defined above and the return Type, and takes a List of the top level object.
I then map each item from the object array to another of it's actual type. This keeps the code easier to read, and enables properties and methods of the object to be accessed without issue.
Then it's a case of stepping down the hierarchy, checking at each step if one already exists with a matching id, and swapping the iterator to a reference to it if it does. This means that following code will add to the existing item.
In the particular case I've also added a FromThing function to allow easier combining of object properties.
As we discussed in comments, this is an issue due to duplicate column names in two tables. This is where the similar issue and solution could be found. But, it does not include "mapping by code" as you said. So it is not exact duplicate.
I suggest you change the names of ID fields in your tables to avoid colliding them. Of-course, you should also change the name of your POCO properties and mappings accordingly.
If you cannot change the column names in table, change the POCO property name, and use the column alias in SQL query to match those new property names.
I hope this helps you.
The problem was effectively the name of the properties.
I solved it using Custom Column Mapping to do it i got two possible solutions:
Without extensions
First, we define a Dictionary with the name of the column as key, and the name of the property as value
IDictionary<string, string> columnMaps = new Dictionary<string, string>()
{
{ "Macro_tarea.ID", "ID" },
{ "Registros.ID", "ID" }
};
Then, we define a delegate to obtain the PropertyInfo object of the property to which we intend to assign the alias of the previous dictionary
var mapper = new Func<Type, string, PropertyInfo>((type, columnName) =>
{
if (columnMaps.ContainsKey(columnName))
return type.GetProperty(columnMaps[columnName]);
else
return type.GetProperty(columnName);
});
Now, we define an object that implements the ITypeMap interface using CustomPropertyTypeMap implementation
ITypeMap MacroTareaMapper = new CustomPropertyTypeMap(typeof(Macro_tarea),
(type, columnName) => mapper(type, columnName));
ITypeMap RegistrosMapper = new CustomPropertyTypeMap(typeof(Registros),
(type, columnName) => mapper(type, columnName));
Then we register them
SqlMapper.SetTypeMap(typeof(Macro_tarea), MacroTareaMapper);
SqlMapper.SetTypeMap(typeof(Registros), RegistrosMapper);
Simpler solution with Dapper.FluentMap
It is implemented as follows:
We create a class that inherits from EntityMap<T> and using the Map method we define which column corresponds to each property. For example,
internal class Macro_tareaMap : EntityMap<Macro_tarea>
{
internal Macro_tareaMap()
{
//Mi propiedad ID esta asociada a la columna Macro_tarea.ID
Map(x => x.ID).ToColumn("Macro_tarea.ID");
}
}
Then just register it
FluentMapper.Initialize((config) =>
{
config.AddMap(new Macro_tareaMap());
});
Hope it helps another people!
Source: https://medium.com/dapper-net/custom-columns-mapping-1cd45dfd51d6

Dynamically building Entity Framework Linq to SQL Query

I am trying to build a dynamic query in linq to SQL. In short, the problem I am trying to solve is as follows:
We have an existing SQL Server database with a number of tables that we want to be able to search in generically. This is a legacy database, so we are using Entity framework data-first (not code first).
In our database, we also have a metadata table that holds the table and column names of the other tables, and which columns are searchable.
For example, we might have a table named 'Vehicles' with a PK column called 'Vehicle_Id', and searchable columns named 'Type' and 'Colour'. Our metadata table would contain entries for the columns 'Type' and 'Colour' marking them as searchable.
The EF model and mapping for the Vehicle entity would look something like this:
public class Vehicle
{
public int Id { get; set; }
public string Type { get; set; }
public string Colour { get; set; }
}
public class VehicleMap : EntityTypeConfiguration<Vehicle>
{
public VehicleMap()
{
This.ToTable("VEHICLES");
This.HasKey(k => k.Id);
This.Property(p => p.Id).HasColumnName("VEHICLE_ID");
This.Property(p => p.Type).HasColumnName("TYPE");
This.Property(p => p.Colour).HasColumnName("COLOUR");
}
}
A user then defines the search requirements (for instance, 'Type' of 'Car' or 'Colour' of 'Red' or 'Blue'). These search requirements are based on the column names.
I would like to be able to dynamically build the query, based on the field names defined in our metadata table, something like this:
var query = from v in context.Vehicles select v.Id;
foreach (var criterion in criteria)
{
var fieldName = criterion.Field; // Database field name, e.g. "TYPE"
var value = criterion.Value; // Search value, e.g. "CAR"
var filter = [Filter based on field/value]
results = query.Where(filter).ToList();
// do something with results...
}
At the moment, I think I am about half-way there with the following extension method which is getting the entity mapping for a given table from the EF context. What I'm getting lost with is then finding the field based on field name (e.g. 'COLOUR') and building the filter based on this.
public static EntityType GetTableMapping(this DbContext context, string tableName)
{
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
var storageMetadata = ((EntityConnection)objectContext.Connection).GetMetadataWorkspace().GetItems(DataSpace.SSpace);
var entityProps = storageMetadata.Where(s => s.BuiltInTypeKind == BuiltInTypeKind.EntityType).Select(s => s as EntityType);
return (from m in entityProps where string.Compare(m.Name, tableName, StringComparison.OrdinalIgnoreCase) == 0 select m).SingleOrDefault();
}

Linq Select items that are mapped as many-to-many?

I am trying to build a select query to a table while only contains concatenated Primary Key values and no attributes. I managed to get the select query alright, but I can't quite figure out how to get the select statement. Basically I am trying to get a list of DegreeID's, from the DegreeRelationship table, (which is mapped and not an entity) from a ProgramID in DiplomaCertificate entity. Then I want to get the Degree name as well.
My context with the mapped tables looks like this:
modelBuilder.Entity<Degree>()
.HasMany(e => e.DiplomaCertificates)
.WithMany(e => e.Degrees)
.Map(m => m.ToTable("DegreeRelationship").MapLeftKey("DegreeID").MapRightKey("ProgramID"));
and basically, I am trying to put values into this object:
public class DegreeRelationshipInfo
{
public int ProgramID { get; set; }
public int DegreeID { get; set; }
public string LinkedProgramName { get; set; }
}
I am trying a method something like this, but I am not sure how to write this exactly (and this is completely wrong):
[DataObjectMethod(DataObjectMethodType.Select, false)]
public List<DegreeRelationshipInfo> Select_DegRel(int programID)
{
using (Pathway_Model context = new Pathway_Model())
{
var results = from data in context.Degrees
where data.DiplomaCertificates.Where(x => x.ProgramID == programID)
select new DegreeRelationshipInfo
{
ProgramID = data.ProgramID,
// no idea how to get this value....
};
return results.ToList();
}
}
Any help would be appreciated!
Select the entities by SelectMany and collect their key values:
from data in context.Degrees
from cert in data.DiplomaCertificates
select new DegreeRelationshipInfo
{
ProgramID = data.ProgramID,
DegreeID = cert.DegreeID,
LinkedProgramName = data.Name // I guess...
}
This from - from construction is compiled into SelectMany, but the syntax is much better readable.

Returning IQueryable type: The entity or complex type '' cannot be constructed in a LINQ to Entities query

I am running a query to populate options in a single select drop down menu. When I debug the function below, the query variable contains the resultset that I am expecting. However when I skip next to where it should be returned to, I get the error:
'The entity type or complex type 'Models.zz_Member' cannot be constructed in a LINQ to Entities query."
public IQueryable<zz_Member> GetMembers(string searchText)
{
var _db = new Portal.Models.AuthorizationContext();
IQueryable<zz_Member> query = _db.zz_Members;
return query //.Where(a => a.memLastName.Contains(searchText))
.Select(a => new zz_Member()
{
ID = a.ID,
memFirstName = a.memFirstName,
memLastName = a.memLastName
}
);
}
The zz_Member model object is defined as:
public class zz_Member
{
[ScaffoldColumn(false)]
public int ID { get; set; }
public string memFirstName { get; set; }
public string memLastName { get; set; }
}
The error is thrown when I try to convert to an IList, but when I check the value of memList using the debugger, it shows the error text in the results view.
IQueryable<zz_Member> memList = GetMembers(e.Text);
IList<zz_Member> memList2 = memList.ToList();
I have also tried writing the GetMembers functions to return the list as so:
public IList<zz_Member> GetMembers(string searchText)
{
var _db = new WWCPortal.Models.AuthorizationContext();
return (from m in _db.zz_Members
where m.memLastName.Contains(searchText)
select new zz_Member { ID = m.ID, memFirstName = m.memFirstName, memLastName = m.memLastName }).ToList();
}
Any hints or answers to why the resultset appears to not be getting returned to the caller and put into memList? Thanks.
You cannot use framework dependant/generated entities in projection (with select new), hence the error.
Looks like you are trying to select specific columns instead of all columns, your options are
Project to a new class with those specific members
return all fields/columns for your entities like:
Code:
return query.Where(a => a.memLastName.Contains(searchText)); //without `select new`

How do I return data from joined tables through subsonic's objects?

I'm using ActiveRecord on Subsonic 3 and I effectively want to do this:
select * from foo
left outer join bar on bar.Id = foo.barId
where foo.someProperty = 2
I've written a stored procedure to fetch the data but Subsonic has only created objects to hold the columns from foo and bar.
What's the best way of returning the data into a single object so I can just bind it. Ideally I want it to be in a list<> but without writing my own class, there doesn't seem to be a way provided by subsonic.
You have a couple options here...
You could create a database view that does your join, and have SubSonic generate a data type for your view, then your select would be just like selecting from any other table.
Alternatively, you could use a Linq expression to do the join into an anonymous or dynamic type (if you are using .net 4) For example:
public List<dynamic> LoadData(int id)
{
var data = from f in db.Foo
from b in db.Bar.Where(x => x.Id == f.BarId).DefaultIfEmpty()
where f.SomeProperty == id
select new
{
SomeProperty = f.Something,
AnotherProperty = b.SomethingElse
};
return data.Cast<dynamic>().ToList();
}
Of course another alternative is to do the Linq expression above, but define your own class to hold the returned data, and select into it.
public class MyData
{
public string SomeProperty { get; set; }
public string AnotherProperty { get; set; }
}
public List<MyData> LoadData(int id)
{
var data = from f in db.Foo
from b in db.Bar.Where(x => x.Id == f.BarId).DefaultIfEmpty()
where f.SomeProperty == id
select new MyData()
{
SomeProperty = f.Something,
AnotherProperty = b.SomethingElse
};
return data.ToList();
}

Categories

Resources