Entity Framework dataset mapping - c#

All of my DAL functions are using dbContext.Database.SqlQuery to map stored procedure results in business logic objects.
My application became more complicated and I'm looking for a modern, "up to date" way to handle the following situations. I know that I can achieve this using the low-level ADO.NET component like SqlDataReader and map the result manually, but I am sure there is the best way to do so using Entity Framework 6.
To the question: with this command dbContext.Database.SqlQuery<MyClass>, I can not handle:
The stored procedure that returns 2 result sets
Mapping the result set to a complex datatype
Example:
public class Order
{
public Customer customer { get; set; }
public Items[] items { get; set; }
}
Again, I know that I can map it manually or with AutoMapper, but I'm looking for an "up to date" approach based on Entity Framework 6.

Yes, there's a way using Translate.
Adapted from the official documentation:
var cmd = dbContext.Database.Connection.CreateCommand();
cmd.CommandText = "[dbo].[GetAllCustomersAndOrders]";
dbContext.Database.Connection.Open();
// Run the sproc
var reader = cmd.ExecuteReader();
var Customers= ((IObjectContextAdapter)dbContext)
.ObjectContext
.Translate<Customer>(reader, "Customers", MergeOption.AppendOnly);
reader.NextResult();
var Orders = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<Order>(reader, "Orders", MergeOption.AppendOnly);
As far as the problem of mapping
few columns from the result to a 2nd level complex type? for example:
SELECT FirstName, LastName, OrderId FROM Orders I want to map it to:
public class Order { public Customer customer { get; set; } public int
OrderId { get; set; } }
The best would be to use a CustomerId inside your Order table, referencing a Customer table, instead of FirstName/LastName. It would be a good refactoring, normalizing the database. Otherwise you will not have a real mapping between your objects and your database, since your Order object will have a Customer property that doesn't exist in your database. In that case, you will have to create a class, e.g. NormalizedOrder
public class NormalizedOrder {
int OrderId { get; set; };
Customer OrderCustomer { get; set; };
}
And then, after the code above where you retrieve all Orders, do something like
var NormalizedOrders = Orders.Select new Order(){OrderId = e.OrderId, OrderCustomer = new Customer(){FirstName=>e.FirstName,LastName=>e.LastName}};

Related

Priority in retrieving Data from mysql database and filtering the retrieved data

I want to retrieve only one column of my data base. The following code works truly:
This is my customer in mysql-table/model-in-EF6
public partial class customers
{
public customers()
public int CustomerID { get; set; }
public string FullName { get; set; }
public string Mobile { get; set; }
public string Email { get; set; }
public string Address { get; set; }
public string Image { get; set; }
}
public List<customers> GetAllCustomers()
{
return myContext.customers.ToList();
}
This is my question:
var GetOneColumn = myContext.CustomerRepository.GetAllCustomers().Select(f=>f.FullName);
Does it retrieve all columns from customers in database and then select only one column (FullName) from retrieved data or not, it retrieve just only one column(FullName) from data base? If it retrieve all data from data base what is the correct code (Linq)?
How could I find that??
Since you're using a .ToList() EF will
Retrieve all customers from database
Map them to customer objects
Later, when you compute GetOneColumn, you do a projection on them (iterating through already materialized object list)
To retrieve only one column,
Remove the .ToList() from the repository, and return a IQueryable<Customers>
Call a .ToList() after your select var GetOneColumn = myContext.CustomerRepository.GetAllCustomers().Select(f=>f.FullName).ToList();
So, your code would be
public IQueryable<customers> GetAllCustomers()
{
return myContext.customers;
}
// later in code
var GetOneColumn = myContext.CustomerRepository.GetAllCustomers().Select(f=>f.FullName).ToList();
See what's going on for yourself! Break up your code into steps and debug:
var allCustomers = myContext.CustomerRepository.GetAllCustomers();
var allCustomerNames = allCustomers.Select(f=>f.FullName);
Alternatively, run a profiler on your DB, or enable logging of queries in EF
To see all the queries that EF is generating you can do something like this
using (var context = new BlogContext())
{
context.Database.Log = Console.Write;
// Your code here...
}
See more details in the docs and Log Queries executed by Entity Framework DbContext
If you read this far, then it's worth knowing what will actually cause EF to send a query - see How Queries Work
Basically, this happens whenever you start enumerating through the elements of an IQueryable<T> (including LINQ methods like First(), Last(), Single(), ToList(), ToArray(), and the like)

Joining Entity Framework tables using Linq when IDs do not exist in both tables

Below is a class I have used to generate a table in my database using Entity Framework. I'd like to be able to link this table to another table, Property. However, the way my code is set up there is not an Id column in the Instruction table, there is a Property property within the class, which then generates a PropertyId column in the actual database, but since the Property property is not an Id I am unable to using Linq to join these tables.
Instruction table
[Table("Instruction")]
public class Instruction
{
[Key]
public int Id { get; set; }
public InstructionTypes InstructionType { get; set; }
public Property Property { get; set; } //Generates the EF property FK, but is not an ID so therefore cannot be used in linq.
}
Property table
[Table("Property")]
public partial class Property
{
[Key]
public int Id { get; set; }
public Address Correspondence { get; set; }
}
Join Query
var instruction =
from instructions in _context.Instructions
join properties in _context.Properties on instructions.Property equals properties.Id
where ...
The above query gives a compiler error of: `The type of one of the expressions in the join clause is incorrect.
This error is being generated as I'm attempting to use a property object to join with a propertyId.
How can I alter this query so that I am able to join these two tables?
In 99% of all cases, you do not want to use the join operator. Entity Framework automatically generates SQL JOINS for you when you are using Navigation Properties.
var instruction = await _context.Instructions.Where(i => i.Property...).FirstOrDefaultAsync().ConfigureAwait(false);
Note, that depending on whether you are using EF6 or EF Core or with different configuration, Lazy Loading may be disabled (if not, I strongly encourage you to disable it as it is a massive performance bottleneck).
So you have to use the Include Method to eagerly load the related entity.
var instruction = await _context.Instructions.Include(i => i.Property).Where(i => i.Property...).FirstOrDefaultAsync().ConfigureAwait(false);
But before doing this, think if you really need the Instruction. If not, your code could become:
var property = await _context.Properties.Where(p => p.Instructions.Any(i => ...)).FirstOrDefaultAsync().ConfigureAwait(false);
Please note that you have to extend your Property class for this to work to have a back-reference
public partial class Property
{
// No need for the Key attribute, as this is convention
public int Id { get; set; }
public Address Correspondence { get; set; }
public int CorrespondenceId { get; set; } // Not needed in this scenario, but good practice
public ICollection<Instruction> Instructions { get; } = new HashSet<Instruction>();
}
You seems to be a newcomer to linq. As such you are still thinking as if you still are in an sql world.
With linq to entities, the use of join is the exception. SQL join are generated silently by EF using the navigation properties.
So your query can be:
var instruction =
from instruction in _context.Instructions
where instruction.Porperty.Correspondence.Contains("abc");
then you can access
instruction.First().Property.Correspondence
As a good practice you can delclare the foreign keys as class members and use the fluent API to bind them.
To test you can use the following code,
//assuming that Instructions is a DbSet<Instruction>
using (var context = new MyContext() ) {
context.Instructions.Add(
new instruction {
Property = new Property {
Correspondence = new Address {}
}
});
}
using (var context = new MyContext() ) {
var c = context.Instructions.First();
console.WriteLine($"{c.Id}, {c?.Property.Id}, {c?.Property?.Correspondence.Id}");
});

Is there a work around for unioning two entities of the same interface using entity framework?

I have a search model class that searches different entity sets with the entity itself implementing a IAssignable interface. The code looks like this.
public void Search()
{
List<T> lessons = new List<T>();
List<T> courses = new List<T>();
if (ShowLessons)
lessons = db.Set<Lesson>()
.Where(IAssignableExtensions.SearchPredicate(q))
.Select(LessonMapping).ToList();
if (ShowCourses)
courses = db.Set<Course>()
.Where(IAssignableExtensions.SearchPredicate(q))
.Select(CourseMapping).ToList();
Results = lessons.Union(courses).ToList<T>();
}
The static extension is irrelevant, it just searched based on the query. I would prefer to bust this into it's own rather than static extension but eh. Now this works as expected. I am pulling to memory two datasets, lessons and courses, I am unioning them into a IEnumerable of a generic type based on teh Course Mapping or Lesson Mapping Expressions.
public Expression<Func<IAssignable, T>> LessonMapping { get; set; }
public Expression<Func<IAssignable, T>> CourseMapping { get; set; }
The problem is when I want to do any type of paging. As you can see the lessons and courses are searched, brought into memory and then unioned and returned. If I do any paging using an IPagedList for example, it is bringing back ALL lessons and courses then it is only using a subset of the total data in the list for the pages.
If Entity Framework supported interfaces I would just do a cast on the interface and union right at the db call. I haven't changed this code yet but I feel I might have to create a custom stored procedure or use the Query call on the datacontext, but if I use a stored procedure I have to make sure to update it on any changes to the domain, and if I use the Query I have to re-jig the selects, interfaces and still have to worry about inline sql...
Anyone have any ideas?
UPDATE
The solution that I ended up using after thinking about Erik's solution was to just use a projected object that implemented IAssignable.
public class SomeProjection : IAssignable
{
public int ID { get; set; }
public string Name { get; set; }
public string Description {get;set;}
public string Privacy {get;set;}
}
And then used it within the union call queryable
Results = db.Set<Lesson>().Select(p => new SomeProjection() { Privacy = p.Privacy, ID = p.ID, Name = p.Name, Description = p.Description })
.Union(db.Set<Course>().Select(p => new SomeProjection() { Privacy = p.Privacy, ID = p.ID, Name = p.Name, Description = p.Description }))
.Where(IAssignableExtensions.SearchPredicate(q))
.Select(Mapping).ToList<T>();
If Entity Framework supported interfaces I would just do a cast on the interface and union right at the db call.
It has nothing to do with what Entity Framework supports. If you create an interface, it is independent of the SQL technology in the back end and you want EF to somehow magically select properties based on an interface with no mappings or configuration? Not going to happen.
Instead you could simply use inheritance if there are some properties that are the same between objects, then you don't even need to union them, unless you are where-ing on properties that don't exist between both.

Merge 2 POCO into 1 Class and execute CRUD

I am using PetaPoco as an ORM and want to combine 2 POCO into 1 class and then execute CRUD operations with this class.
Right now I got something like this:
*Table Person has an fk address_id.*
public class Person
{
public PersonPOCO Person { get; set; }
public AddressPOCO Address { get; set; }
public Person(string sql)
{
Person = Db.SingleOrDefault<PersonPOCO>(sql);
Address = Db.SingleOrDefault<PersonPOCO>("SELECT * FROM addresses WHERE id = #0, PersonPoco.address_id");
}
public void Save()
{
var addressId = Db.Save(Address); // Returns inserted ID
Person.address_id = addressId;
Db.Save(Person);
}
}
This is working fine so far.
But it gets really annoying and repetive doing this for every needed Combination.
Especially saving is a pain, since I have to map the inserted ID to the dependent object.
Are there better ways to achieve this?
PetaPoco is designed to be fast and lightweight, so, you won't find this kind of complex mapping like Linq-to-sql or EF have in it.

Can I specify DB column names for dapper-dot-net mappings?

Is there a way with dapper-dot-net to use an attribute to specify column names that should be used and not the property name?
public class Code
{
public int Id { get; set; }
public string Type { get; set; }
// This is called code in the table.
public string Value { get; set; }
public string Description { get; set; }
}
I'd like to be able to name my properties whatever I choose. Our database has no consistent naming convention.
If not with dapper, are there any additional similar options?
You can also check out Dapper-Extensions.
Dapper Extensions is a small library that complements Dapper by adding
basic CRUD operations (Get, Insert, Update, Delete) for your POCOs.
It has an auto class mapper, where you can specify your custom field mappings. For example:
public class CodeCustomMapper : ClassMapper<Code>
{
public CodeCustomMapper()
{
base.Table("Codes");
Map(f => f.Id).Key(KeyType.Identity);
Map(f => f.Type).Column("Type");
Map(f => f.Value).Column("Code");
Map(f => f.Description).Column("Foo");
}
}
Then you just do:
using (SqlConnection cn = new SqlConnection(_connectionString))
{
cn.Open();
var code= new Code{ Type = "Foo", Value = "Bar" };
int id = cn.Insert(code);
cn.Close();
}
Keep in mind that you must keep your custom maps in the same assembly as your POCO classes. The library uses reflection to find custom maps and it only scans one assembly.
Update:
You can now use SetMappingAssemblies to register a list of assemblies to scan:
DapperExtensions.SetMappingAssemblies(new[] { typeof(MyCustomClassMapper).Assembly });
If you are using a select statement directly or in a procedure you can just alias the columns.
SELECT code as Value FROM yourTable
Another approach is to just manually map it with the dynamic result.
var codes = conn.Query<dynamic>(...sql and params here...)
.Select<dynamic,Code>(s=>new Code{Id = s.Id, Type = s.Type, Value = s.code, Description = s.Description});
Clearly this introduces type-safety scenarios because you are querying on dynamic. Also, you have to manually map columns which is a bummer.
However, I tend to like this approach because it's so darned transparent. You can cast if need be (as is the case with Enums), and basically just do whatever it is you need to do to go from the db recordset to your properties.
For selects, you can add constructors to your classes to perform the mapping.
The constructor parameter names must match the table columns.
Below is an example from the source. The table will be correctly mapped to the class.
Table:
CREATE TABLE #Users (Id int, Name varchar(20))
Class:
class UserWithConstructor
{
public UserWithConstructor(int id, string name)
{
Ident = id;
FullName = name;
}
public int Ident { get; set; }
public string FullName { get; set; }
}

Categories

Resources