Entity framework transferred data in bytes - entity-framework

I'm using EF6
this is my class
public partial class tbl_Persons
{
public int ID { get; set; }
public string Name { get; set; }
public byte Age { get; set; }
}
How much (bytes) will be transferred from the server to the client when used this code ?
using (var db = new testEntities())
{
var q = db.tbl_Persons.FirstOrDefault(a => a.ID == 1234).Age;
}
Just transferred {(Age)(1 byte)} or transferred all properties {(ID + Name + Age)(10 bytes)} then select Age on client ?
How can I transfer only (Age)(1 byte) ? (I need to transfer minimum data from server)

The expression
var q = db.tbl_Persons.FirstOrDefault(a => a.ID == 1234).Age;
is equivalent of
var person = db.tbl_Persons.FirstOrDefault(a => a.ID == 1234);
var age = person.Age;
So you first retrieve and materialize a whole object (with all properties) from the database, and then take a single property (byte in your case) from the result.
In order to fetch just the property in question, you should use a not so concise, but more efficient Where + Select + FirstOrDefault (no predicate version):
var age = db.tbl_Persons.Where(p => p.ID == 1234).Select(p => p.Age).FirstOrDefault();
or with query syntax
var age = (from p in db.tbl_Persons where p.ID == 1234 select p.Age).FirstOrDefault();

Related

Linq to SQL How to write "not-in" query

I have the following 3 tables ( 1 base table and other 2 sattelite tables for each vehicle type.)
Vehicles
ID VehicleType
-----------------------------
1 Car
2 Truck
Cars
ID Make Model
-------------------------
1 Toyota Camry
2 Honda Accord
Trucks
ID Make Model
--------------------
1 Ford F150
2 Dodge Ram
Then i have corresponding DTO
public class VehicleDTO
{
public int ID {get;set;}
public int VehicleType {get;set;}
public IEnumerable<CarDTO> Cars {get;set;}
public IEnumerable<TruckDTO> Trucks {get;set;}
}
public class CarDTO
{
public int ID {get;set;}
public string Make {get;set;}
public string Model {get;set;}
}
public class TruckDTO
{
public int ID {get;set;}
public string Make {get;set;}
public string Model {get;set;}
}
Then i have list of Vehicle DTO as an argument to the method. I want find vehicles from DTO list that does not exists in the database by matching Make and Model for that Vehicle Type.
Idea is to then insert the missing vehicles into database.
I have the following query
public void FindMissingVehicles(IEnumerable<VehicleDTO> dtos)
{
var cars = (from dto in dtos
where !(from c in dbcontext.Cars
select new { c.Make, c.Model })
.Any(x => dto.VehicleType == 'Car' && dto.Car.Make == x.Make && dto.Car.Model == x.Model)
select dto).ToList();
var trucs = (from dto in dtos
where !(from t in dbcontext.Trucks
select new { t.Make, t.Model })
.Any(x => dto.VehicleType == 'Truck' && dto.Truck.Make == x.Make && dto.Truck.Model == x.Model)
select dto).ToList();
//insert missing cars and trucks into db here
}
The query above throws exception
Message "Non-static method requires a target." string
Questions
1> How do i construct this query.
2> Can i make this query async by using AnyAsync and ToListAsync. (I know i have to make method async with Task, and use await inside howevere i could not figure out the async query syntax)
Moreover, your approach has performance issue - you perform N queries - one for each of dto, instead of doing only two queries: one for cars and one for trucks:
var allCars = dtos.Where(x => x.VehicleType == "Car").ToList()
.SelectMany(x => x.Cars.Select(y => y.Make + "-" + y.Model).ToList()).ToList();
var existedCars = await dbcontext.Cars.Where(x => allCars.Contains(x.Make + "-" + x.Model))
.Select(x => x.Make + "-" + x.Model).ToListAsync();
var newCars = allCars.Except(existedCars).Select(x =>
{
var temp = x.Split('-');
return new CarDTO
{
Make = temp[0],
Model = temp[1]
};
}).ToList();
//exactly same code for Trucks
This is because of you can't have nested query in linq when one table is from dbContext and the other one is from in-memory enumerable, so if dbcontext.Cars and dbcontext.Trucks don't have a lot of rows, it's a good idea to load them in memory and use nested query like below:
var listCars = dbcontext.Cars.ToList();
var listTrucks = dbcontext.Trucks.ToList();
var cars = (from dto in dtos
where !(from c in listCars
select new { c.Make, c.Model })
.Any(x => dto.VehicleType == 'Car' && dto.Car.Make == x.Make && dto.Car.Model == x.Model)
select dto).ToList();
var trucs = (from dto in dtos
where !(from t in listTrucks
select new { t.Make, t.Model })
.Any(x => dto.VehicleType == 'Truck' && dto.Truck.Make == x.Make && dto.Truck.Model == x.Model)
select dto).ToList();

Inlining collections with EF Projection

I have the following classes:
public class Customer {
public int Id {get;set;}
public string Name {get;set;}
public List<Order> Orders {get;set;}
//other attributes
}
public class Order{
public int Id {get;set;}
public string Name {get;set;}
public decimal Value {get;set;}
}
Given a customerId I wish to only select the customer name and the order Id using projection in EF.
I am doing the following:
IQueryable<Customer> customer = DataContextFactory.GetDataContext().Set<Customer>();
var tempCustomer = customer.Where(x => x.Id == customerId).Select( c=>
new
{
Name = c.Name
}
)
This gives me the customer name. Which I can then pass back to the entity like so:
var customerToReturn = tempCustomer.ToList().Select(x => new Customer
{ Name = x.Name});
If I include the order on the query like this:
var tempCustomer = customer.Where(x => x.Id == customerId).Select( c=>
new
{
Name = c.Name,
Orders = new {
Id = c.Orders.Id
}
}
)
Then I get a duplicate customer per order line (as per the SQL generated by EF). Is there a way I can inline this generation into a single SQL call?
Currently I am getting around this by calling each child object separately.

Context SqlQuery<>() without detailing all columns

Let's say I have a Person entity with 3 columns:
public PERSON {
public int OID { get; set; }
public string NAME { get; set; }
public string SURNAME { get; set; }
}
I want to query it with raw SQL but without specifying all columns so I write:
var query = "select NAME, SURNAME from PERSON";
var list = context.SqlQuery<PERSON>(query).ToList();
But it throws Exception:
System.Data.Entity.Core.EntityCommandExecutionException : The data reader is incompatible with the specified '...'. A member of the type, 'OID', does not have a corresponding column in the data reader with the same name.
So it seems like it tries to map all columns, and, if some are missing, it throws.
Is there a way to make it ignore columns that are not present in raw SQL query and map just the columns that are accessible?
The reason is, I have a lot of columns for some entities and sometimes I just want to query partial columns. I don't want to create new class with just the necessary columns for each query.
I can think of 3 options off the top of my head that could work for you.
Option 1: Rewrite your queries and use a standard Linq query:
var persons = from p in context.Persons
select new PERSON
{
NAME = p.NAME,
SURNAME = p.SURNAME
};
Option 2: Return a dummy value for columns you don't need
var query = "select 0 AS OID, NAME, SURNAME from PERSON";
var list = context.SqlQuery<AlmostPERSON>(query).ToList();
Option 3: Create your own intermediate classes with the columns you need from the database:
public class AlmostPERSON
{
public string NAME { get; set; }
public string SURNAME { get; set; }
}
var query = "select NAME, SURNAME from PERSON";
var list = context.SqlQuery<AlmostPERSON>(query).ToList();
You could even project this intermediate class onto your standard entity like this:
var list = context.SqlQuery<AlmostPERSON>(query)
.Select(ap => new PERSON
{
NAME = ap.NAME,
SURNAME = ap.SURNAME
})
.ToList();
Found one more possible solution: using ADO.NET IDataReader with AutoMapper
public List<T> ExecuteFor<T>(string sql)
{
using (_connection = new OracleConnection(_connectionString))
{
_connection.Open();
using (var command = new OracleCommand(sql, _connection))
{
using (OracleDataReader reader = command.ExecuteReader())
{
Mapper.CreateMap<IDataReader, List<T>>();
return Mapper.Map<IDataReader, List<T>>(reader);
}
}
}
}

IQueryable.Select to a List type sub POCO

I have an Entity Framework model in which there is a "Customers" and a "CustomerPhones" table. A customer can have multiple phone numbers so the "Customer" entity has a collection of "Phone". I can query the model with no problem :
using (CustomerEntities context = new CustomerEntities())
{
Customer customer = context.Customers.FirstOrDefault();
CustomerPhone phone = customer.Phones.FirstOrDefault();
MessageBox.Show(customer.Name + " " + phone.Number);
}
The model is too complex for what I need to do (even though my example is basic) so I'm trying to boil it down to simpler POCOs. Here are the 2 simple classes :
public class SimplePhone
{
public int Id { get; set; }
public string Number { get; set; }
}
public class SimpleCustomer
{
public int Id { get; set; }
public string Name { get; set; }
//Phones is a list because a single Customer can have multiple phone numbers
public List<SimplePhone> Phones { get; set; }
}
I can populate the simple properties of the object using the "Select" method of "IQueryable" :
using (CustomerEntities context = new CustomerEntities())
{
IQueryable<SimpleCustomer> customers = context.Customers.Select(
c => new SimpleCustomer
{
Id = c.Id,
Name = c.Name
}
);
SimpleCustomer customer = customers.FirstOrDefault();
MessageBox.Show(customer.Name);
}
So my question is pretty simple : how can I populate the "Phones" property which is a list?
using (CustomerEntities context = new CustomerEntities())
{
IQueryable<SimpleCustomer> customers = context.Customers.Select(
c => new SimpleCustomer
{
Id = c.Id,
Name = c.Name
Phones = ///????
}
);
SimpleCustomer customer = customers.FirstOrDefault();
SimplePhone phone = customer.Phones.FirstOrDefault();
MessageBox.Show(customer.Name + " " + phone.Number);
}
Let me know if I'm unclear and/or you need more details.
Thanks!
I'm not sure if there isn't something more to your question, but as far as I understand, you can just call ToList and it will be materialized as a list:
IQueryable<SimpleCustomer> customers =
context.Customers.Select(c => new SimpleCustomer
{
Id = c.Id,
Name = c.Name,
Phones = c.Phones.Select(p => new SimplePhone
{
Id = p.Id, // Unless you want the custom Id, i.e. c.Id
Number = p.Number
}).ToList();
});

Converting ESQL to LINQ to Entities. Sort by related entities

I am using EF + RIA and unfortunately meet some problems with sorting by related entities.
For such purpose there is ESQL query that I implemented (found only this solution):
var queryESQL = string.Format(
#" select VALUE ent from SomeEntities as ent
join Attributes as ea ON ea.EntityId = ent.Id
where ea.AttributeTypeId = #typeId
order by ea.{0} {1}", columnName, descending ? "desc" : "asc");
var query = ObjectContext.CreateQuery<SomeEntity>(queryESQL, new ObjectParameter("typeId", attributeTypeId));
Tables have following structure:
<Attribute>:
int Id;
decimal DecimalColumn;
string StringColumn;
int EntityId;
int AttributeTypeId;
<SomeEntity>:
int Id;
string Name;
Is there any way to rewrite this stuff(sorting), using LINQ to Entities approach?
Here's my attempt, I can't guarantee it will work. I need to think more on how to get a dynamic column name, I'm not sure on that one. EDIT: you can use a string for the order column.
int typeId = 1115;
bool orderAscending = false;
string columnName = "StringColumn";
var query = from ent in SomeEntities
join ea in Attributes on ea.EntityId = ent.Id
where ea.AttributeTypeId = typeId;
if(orderAscending)
{
query = query.OrderBy(ea => columnName).Select(ea => ea.Value);
}
else
{
query = query.OrderByDescending(ea => columnName).Select(ea => ea.Value);
}
var results = query.ToList(); // call toList or enumerate to execute the query, since LINQ has deferred execution.
EDIT: I think that ordering after the select stops is from ordering by. I moved the select statement to after the order by. I also added the "query =", but I'm not sure if that is needed. I don't have a way to test this at the moment.
EDIT 3: I fired up LINQPad today and made a few tweaks to what I had before. I modeled your data in a Code-first approach to using EF and it should be close to what you have.
This approach works better if you're just trying to get a list of Attributes (which you aren't). To get around that I added an Entity property to the MyAttribute class.
This code works in LINQPAD.
void Main()
{
// add test entities as needed. I'm assuming you have an Attibutes collection on your Entity based on your tables.
List<MyEntity> SomeEntities = new List<MyEntity>();
MyEntity e1 = new MyEntity();
MyAttribute a1 = new MyAttribute(){ StringColumn="One", DecimalColumn=25.6M, Id=1, EntityId=1, AttributeTypeId = 1, Entity=e1 };
e1.Attributes.Add(a1);
e1.Id = 1;
e1.Name= "E1";
SomeEntities.Add(e1);
MyEntity e2 = new MyEntity();
MyAttribute a2 = new MyAttribute(){ StringColumn="Two", DecimalColumn=198.7M, Id=2, EntityId=2, AttributeTypeId = 1, Entity=e2 };
e2.Attributes.Add(a2);
e2.Id = 2;
e2.Name = "E2";
SomeEntities.Add(e2);
MyEntity e3 = new MyEntity();
MyAttribute a3 = new MyAttribute(){ StringColumn="Three", DecimalColumn=65.9M, Id=3, EntityId=3, AttributeTypeId = 1, Entity=e3 };
e3.Attributes.Add(a3);
e3.Id = 3;
e3.Name = "E3";
SomeEntities.Add(e3);
List<MyAttribute> attributes = new List<MyAttribute>();
attributes.Add(a1);
attributes.Add(a2);
attributes.Add(a3);
int typeId = 1;
bool orderAscending = true;
string columnName = "StringColumn";
var query = (from ent in SomeEntities
where ent.Attributes.Any(a => a.AttributeTypeId == typeId)
select ent.Attributes).SelectMany(a => a).AsQueryable();
query.Dump("Pre Ordering");
if(orderAscending)
{
// query = is needed
query = query.OrderBy(att => MyEntity.GetPropertyValue(att, columnName));
}
else
{
query = query.OrderByDescending(att => MyEntity.GetPropertyValue(att, columnName));
}
// returns a list of MyAttributes. If you need to get a list of attributes, add a MyEntity property to the MyAttribute class and populate it
var results = query.Select(att => att.Entity).ToList().Dump();
}
// Define other methods and classes here
}
class MyAttribute
{
public int Id { get; set; }
public decimal DecimalColumn { get; set; }
public string StringColumn { get; set; }
public int EntityId { get; set; }
public int AttributeTypeId { get; set; }
// having this property will require an Include in EF to return it then query, which is less effecient than the original ObjectQuery< for the question
public MyEntity Entity { get; set; }
}
class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<MyAttribute> Attributes { get; set; }
public MyEntity()
{
this.Attributes = new List<MyAttribute>();
}
// this could have been on any class, I stuck it here for ease of use in LINQPad
// caution reflection may be slow
public static object GetPropertyValue(object obj, string property)
{
// from Kjetil Watnedal on http://stackoverflow.com/questions/41244/dynamic-linq-orderby
System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
return propertyInfo.GetValue(obj, null);
}