Returning multiple resultsets with EntityFramework repository - entity-framework

I am working on a code where I need Multiple tables as result of a stored procedure. I am using Entity Framework repository pattern. It returns and bind an IEnumerable object, but I need to bind it with multiple IEnumerables at the same time.
Can anybody help?
This is the code I am using :
db.Database.SqlQuery("procReturnsMultipleResuiltSets")

the ways to achieve your goal are disclosed in this article.
From related article the most common way is:
using (var db = new BloggingContext())
{
// If using Code First we need to make sure the model is built before we open the connection
// This isn't required for models created with the EF Designer
db.Database.Initialize(force: false);
// Create a SQL command to execute the sproc
var cmd = db.Database.Connection.CreateCommand();
cmd.CommandText = "[dbo].[GetAllBlogsAndPosts]";
try
{
db.Database.Connection.Open();
// Run the sproc
var reader = cmd.ExecuteReader();
// Read Blogs from the first result set
var blogs = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<Blog>(reader, "Blogs", MergeOption.AppendOnly);
foreach (var item in blogs)
{
Console.WriteLine(item.Name);
}
// Move to second result set and read Posts
reader.NextResult();
var posts = ((IObjectContextAdapter)db)
.ObjectContext
.Translate<Post>(reader, "Posts", MergeOption.AppendOnly);
foreach (var item in posts)
{
Console.WriteLine(item.Title);
}
}
finally
{
db.Database.Connection.Close();
}
}
please note the important remark: The first result set must be consumed before moving to the next result set.

Related

Unable to get data using BulkRead method of EFCore.BulkExtensions

Hope you are doing well.
I'm using EFCore.BulkExtensions[3.1.6] in .Net Core 3.1 Web API to perform bulk operations. Bulk insert and update are working fine but I'm not able to use BulkRead method to get the bulk data.
I refer this link https://github.com/borisdj/EFCore.BulkExtensions#read-example and tried it but I'm not getting data. Maybe I didn't understand the example.
Here is the code which I've tried:
IList<VehicleSubModel> submodels = new List<VehicleSubModel>(); // VehicleSubModel is the domain Entity
var result = submodels.Select(s => new VehicleSubModel() { Id = s.Id, Name = s.Name }).ToList();
var bulkConfig = new BulkConfig { UpdateByProperties = new List<string> { nameof(VehicleSubModel.Id), nameof(VehicleSubModel.Name) } };
await Task.Run(() => Context.BulkRead(result, bulkConfig));
I want to get Id and Name of all VehicleSubModel but it's not returning any record. Could anyone please explain how we can use the BulkRead method of EFCore.BulkExtensions. I spend several hours to get it done, search many links but not getting its solution.
Can anyone please help?

Can I configure Audit.NET to create audit records in two tables on for one audit event?

I am currently implementing Audit.NET into an ASP.NET Core Web API project that is using EF Core. I am using the Entity Framework Data Provider and currently have it configured to map all entities to a single audit log (AuditLog) table with the code below.
Audit.Core.Configuration.Setup()
.UseEntityFramework(_ => _
.AuditTypeMapper(t => typeof(AuditLog))
.AuditEntityAction<AuditLog>((ev, entry, audit) =>
{
audit.Date = DateTime.UtcNow;
audit.AuditData = JsonConvert.SerializeObject(entry);
audit.UserIdentifier = userId;
})
.IgnoreMatchedProperties(true));
This is working great, however, I would like to write audit entries to the BlogApprovals table if the entity type is Blog - in addition to the entry getting added to AuditLog. So for a Blog entity I would like an audit record in both BlogApprovals and AuditLog. Is this possible?
Not really, since the EntityFrameworkDataProvider is designed to map each entity to only one audit entity.
But you could trigger the extra insert, after the operation is complete, by using an OnSaving Custom Action, like this:
Audit.Core.Configuration.AddOnSavingAction(scope =>
{
// OnSaving event fires after context SaveChanges
var efEvent = (scope.Event as AuditEventEntityFramework)?.EntityFrameworkEvent;
if (efEvent != null && efEvent.Success)
{
foreach (var e in efEvent.Entries)
{
if (e.Table == "Blogs" && e.Action == "Insert")
{
// there was an insert on blogs table, insert the blogapproval
var ctx = efEvent.GetDbContext() as MyContext;
ctx.BlogApproval.Add(new BlogApproval() { Note = "note..." });
(ctx as IAuditBypass).SaveChangesBypassAudit();
}
}
}
});

Replace GET handler in TableController

In my table controller, I have:
public IQueryable<MyTable> GetAllMyTable()
I would like to replace the above with my own:
[HttpGet, Route("tables/MyTable")]
public IEnumerable<MyTable> GetAllMyTable()
But I get this response when I call it:
HTTP/1.1 405 Method Not Allowed
Somehow the Web API routing does not reach my method.
Why I'm doing this: the original method produces an inefficient Entity Framework SQL query that takes 3 seconds per call on my local test environment. This is running the query captured from SQL Profiler directly in SQL Mgt Studio. An equivalent query takes less than a second to run. Terrible.
Worse, the inefficient EF queries consumes lots of Azure SQL DTUs, tempting you to up your Azure subscription level if you want a quick fix.
Azure Mobile Apps is wonderful, but the multiple layers of abstraction makes it hard to really see what's going on under the hood, and therefore harder to tune.
Any help would be much appreciated.
HTTP/1.1 405 Method Not Allowed
Per my understanding, the error is obvious. You could send the GET HTTP verb to your endpoint tables/MyTable for retrieving the data. You need to check your request against your mobile app backend via fiddler.
Azure Mobile Apps is wonderful, but the multiple layers of abstraction makes it hard to really see what's going on under the hood, and therefore harder to tune.
For the common table controller, it would look like this:
public IQueryable<Message> GetAllMessage()
{
return Query();
}
The Query() method under EntityDomainManager.cs would equal as follows:
IQueryable<TData> query = this.Context.Set<TData>();
if (!includeDeleted)
{
query = query.Where(item => !item.Deleted);
}
return query;
If it deals with the ODATA queries (e.g. $top, $skip, $filter, etc.), the Nested SQL statement would be generated. We could modify the action to clarify it as follows:
public IEnumerable<Message> GetAllMessage(ODataQueryOptions opt)
{
var message = context.Set<Message>();
var query2=opt.ApplyTo(message, new ODataQuerySettings());
return query2.Cast<Message>().ToList();
}
Here's my rather crude attempt at bypassing the Entity Framework/OData plumbing and using direct SQL. (Wouldn't it be great if Dapper is supported!) This one works well, and is faster than the nested SQL that EF produces. The handling of OData is hacky; I have not had time to investigate using OData to extract the values for UpdatedAt, skip, and top.
I'm only using this approach for one method that needs optimisation. This is the method that the Azure Mobile App client calls when doing a pull.
public IEnumerable<MyTable> GetAllMyTable()
{
var qryValues = HttpUtility.ParseQueryString(Request.RequestUri.Query);
var updatedAtFilter = qryValues["$filter"];
var skip = qryValues["$skip"];
var top = qryValues["$top"];
if (updatedAtFilter != null)
{
var r = new Regex(#"^.+datetimeoffset'(?<time>.+)'.+$", RegexOptions.None);
var m = r.Match(updatedAtFilter);
if (m.Success)
{
var updatedAt = m.Groups["time"].Value.Replace("T", " ");
var sqlString = #"SELECT T0.*
FROM MyTable T0
WHERE T0.UpdatedAt >= #UpdatedAt
ORDER BY UpdatedAt, Id
OFFSET #Skip ROWS
FETCH NEXT #Top ROWS ONLY";
var updatedAtParam = new SqlParameter("UpdatedAt", SqlDbType.DateTimeOffset);
updatedAtParam.Value = updatedAt;
var skipParam = new SqlParameter("Skip", SqlDbType.Int);
skipParam.Value = int.Parse(skip);
var topParam = new SqlParameter("Top", SqlDbType.Int);
topParam.Value = int.Parse(top);
var data = _context.Database.SqlQuery<MyTable>(sqlString, new object[] { updatedAtParam, skipParam, topParam }).AsEnumerable<MyTable>();
return data;
}
}
return null;
}

Meteor: Unique MongoDB URL for different users

I'm very keen to utilize Meteor as the framework for my next project. However, there is a requirement to keep customer data separated into different MongoDB instances for users from different customers.
I have read on this thread that it could be as simple as using this:
var d = new MongoInternals.RemoteCollectionDriver("<mongo url>");
C = new Mongo.Collection("<collection name>", { _driver: d });
However, I was dished this error on my server/server.js. I'm using meteor 0.9.2.2
with meteor-platform 1.1.0.
Exception from sub Ep9DL57K7F2H2hTBz Error: A method named '/documents/insert' is already defined
at packages/ddp/livedata_server.js:1439
at Function._.each._.forEach (packages/underscore/underscore.js:113)
at _.extend.methods (packages/ddp/livedata_server.js:1437)
at Mongo.Collection._defineMutationMethods (packages/mongo/collection.js:888)
at new Mongo.Collection (packages/mongo/collection.js:208)
at Function.Documents.getCollectionByMongoUrl (app/server/models/documents.js:9:30)
at null._handler (app/server/server.js:12:20)
at maybeAuditArgumentChecks (packages/ddp/livedata_server.js:1594)
at _.extend._runHandler (packages/ddp/livedata_server.js:943)
at packages/ddp/livedata_server.js:737
Can anyone be so kind as to enlighten me whether or not I have made a mistake somewhere?
Thanks.
Br,
Ethan
Edit: This is my server.js
Meteor.publish('userDocuments', function () {
// Get company data store's mongo URL here. Simulate by matching domain of user's email.
var user = Meteor.users.findOne({ _id: this.userId });
if (!user || !user.emails) return;
var email = user.emails[0].address;
var mongoUrl = (email.indexOf('#gmail.com') >= 0) ?
'mongodb://localhost:3001/company-a-db' :
'mongodb://localhost:3001/company-b-db';
// Return documents
return Documents.getCollectionByMongoUrl(mongoUrl).find();
});
and this is the server side model.js
Documents = function () { };
var documentCollections = { };
Documents.getCollectionByMongoUrl = function (url) {
if (!(url in documentCollections)) {
var driver = new MongoInternals.RemoteCollectionDriver(url);
documentCollections[url] = new Meteor.Collection("documents", { _driver: driver });
}
return documentCollections[url];
};
Observation: The first attempt to new a Meteor.Collection works fine. I can continue to use that collection multiple times. But when I log out and login as another user from another company (in this example by using an email that is not from #gmail.com), the error above is thrown.
Downloaded meteor's source codes and peeked into mongo package. There is a way to hack around having to declare different collection names on the mongodb server based on Hubert's suggestion.
In the server side model.js, I've made these adaptation:
Documents.getCollectionByMongoUrl = function (userId, url) {
if (!(userId in documentCollections)) {
var driver = new MongoInternals.RemoteCollectionDriver(url);
documentCollections[userId] = new Meteor.Collection("documents" + userId, { _driver: driver });
documentCollections[userId]._connection = driver.open("documents", documentCollections[userId]._connection);
}
return documentCollections[userId];
};
Super hack job here. Be careful when using this!!!!
I believe Meteor distinguish its collections internally by the name you pass to them as the first argument, so when you create the "documents" collection the second time, it tries to override the structure. Hence the error when trying to create the /documents/insert method the second time.
To work around this, you could apply a suffix to your collection name. So instead of:
new Meteor.Collection('documents', { _driver: driver });
you should try:
new Meteor.Collection('documents_' + userId, { _driver: driver })

How to insert data to SQL Server DB using EF in MVC 2?

How do insert data to a SQl Server 2008 database server using Entity Framework from a user input on a web application.
I added a new EF connection o my model. I am able to see all the mappings correctly.
I currently have the following in my view:
m.PhoneNumber) %>
and in my controller
public ActionResult DialTone(Models.TelephoneModels telephone)
{
List<string> callType = new List<string>();
callType.Add("Standard");
callType.Add("Emergency");
ViewData["TypeOfCalls"] = new SelectList(callType);
if (!ModelState.IsValid)
{
return View("DialTone", telephone);
}
ViewData["Number"] = "You are currently connected to: " + telephone.PhoneNumber;
using (LogEntities db = new LogEntities())
{
var log = db.Logs.Single(m => m.PhoneNumber == telephone.PhoneNumber);
TryUpdateModel(log);
db.SaveChanges();
}
return View();
I want to log all phone numbers (or strings) that the user inputs in the TextBox - phonenNumber into the database.
I created a LOG database table with a column called "PhoneNumber". I cant seem to figure out how to insert the data into the table.
Any ideas?
Thanks!
:)
I figure it out. I used:
using (LogEntities db = new LogEntities())
{
Random random = new Random();
Log p = new Log();
p.ID = "400"; //Just a test
p.PhoneNumber = telephone.PhoneNumber;
db.AddToLogs(p);
db.SaveChanges();
}