Hangfire , EF context used in the Task is not updated, the query inside the task always gives the old values - entity-framework

The context used inside the Hangfire task , always gives the old database values, it seems like the context is not updating inside the task. How to get the updated info context data inside hangfire task.
calling the task
BLL.Extraction NewExtractionCls = new BLL.Extraction();
var jobId = BackgroundJob.Enqueue(() => NewExtractionCls.SearchEngineExtraction("SearchURL", "1","1", null));
This is implementation
[Authorize]
public void SearchEngineExtraction(string SearchURL, int PageLimit, int SearchEngineID, PerformContext context)
{
WebClient wc = new WebClient();
#region Main Table - SearchEngineTbl
var NewExtraction = db.SearchEngineTbls.Where(x => x.SearchEngineID == SearchEngineID).FirstOrDefault();
var JobID = context.BackgroundJob.Id;
NewExtraction.JobID = Convert.ToInt32(JobID);
NewExtraction.SeachEngineURL = SearchURL;
NewExtraction.Status = "Processing";
db.SaveChanges();
var LinkCollectionRefined = ExtractLinkFromThisPage(i, SearchURL, wc).Distinct().ToList();//.Skip(10);
foreach (var Link in LinkCollectionRefined)
{
using (Entities dbRefreshed = new Entities())
{
// I get the same old value here, even if I update the table manually, when I rerun, everything is fine.
var CurrentStatusOfExtraction = db.SearchEngineTbls.Where(x => x.SearchEngineID == NewExtraction.SearchEngineID).FirstOrDefault();
if (CurrentStatusOfExtraction.IsActive == false)
{
return;
}
}
}
#endregion
}

Related

How to stop NUnit ITestRunner?

Using nunit.engine 3.10.0, I can't stop an asynchronously running ITestRunner. The TestPackage is set up to be executed locally, i.e. InProcess and in the current AppDomain. No more tests are started after the second test as expected, but the while loop never ends.
public static void Main(string[] args)
{
// 2 assemblies x 2 TestFixtures each x 2 Tests each = 8 test cases
string[] testAssemblyFileNames = { TestAssemblyFileName1, TestAssemblyFileName2 };
string assemblyDirectory = Path.GetDirectoryName(Uri.UnescapeDataString(
new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path));
// Nunit 3.10.0
var minVersion = new Version("3.4");
ITestEngine testEngine = TestEngineActivator.CreateInstance(minVersion);
// configure a test package that executes
// in the current process and in the current domain
var testPackage = new TestPackage(testAssemblyFileNames);
testPackage.AddSetting(EnginePackageSettings.ProcessModel, "InProcess");
testPackage.AddSetting(EnginePackageSettings.DomainUsage, "None");
testPackage.AddSetting(EnginePackageSettings.DisposeRunners, "True");
testPackage.AddSetting(EnginePackageSettings.WorkDirectory, assemblyDirectory);
ITestRunner testRunner = testEngine.GetRunner(testPackage);
// prepare a listener that stops the test runner
// when the second test has been started
const bool StopAfterSecondTest = true;
int testStartedCount = 0;
var listener = new MyTestEventListener();
listener.TestStarted += (sender, eventArgs) =>
{
testStartedCount++;
if ( StopAfterSecondTest && testStartedCount == 2 )
{
testRunner.StopRun(force: true);
}
};
var testFilterBuilder = new TestFilterBuilder();
TestFilter testFilter = testFilterBuilder.GetFilter();
ITestRun testRun = testRunner.RunAsync(listener, testFilter);
bool keepRunning;
int loopCount = 0;
do
{
bool completed = testRun.Wait(500);
bool running = testRunner.IsTestRunning;
keepRunning = !completed && running;
loopCount++;
} while ( keepRunning );
Console.WriteLine($"Loop count: {loopCount}");
XmlNode resultNode = testRun.Result;
Console.WriteLine(resultNode.InnerText);
Console.ReadKey();
}
private class MyTestEventListener : ITestEventListener
{
private const string TestCaseStartPrefix = "<start-test";
private const string TestMethodTypeAttribute = " type=\"TestMethod\"";
public event EventHandler<EventArgs> TestStarted;
public void OnTestEvent(string report)
{
if ( report.StartsWith(TestCaseStartPrefix) &&
report.Contains(TestMethodTypeAttribute) )
{
TestStarted?.Invoke(this, new EventArgs());
}
}
}
If I skip waiting and try to get the test result, I get an InvalidOperationException: 'Cannot retrieve Result from an incomplete or cancelled TestRun.'
How can I stop the test runner and get the results of the tests that were completed before the stopping?
You can't do it from inside a test. Your listener is executed in the context of the test itself. For that reason, listeners are specifically forbidden from trying to change the outcome of a test. Additionally, the event is buffered and may not even be received in this case until after the test run is complete.
StopRun is intended to be called by the main runner itself, generally as triggered by some user input.
You should also take note of this issue: https://github.com/nunit/nunit/issues/3276 which prevents StopRun(true) from working under any circumstances. It was fixed in PR https://github.com/nunit/nunit/pull/3281 but is not yet in any release of the framework. You will have to either use a recent dev build of the framework or switch to StopRun(false).
Based on the answer by #Charlie, this is how to modify the code in order to stop all threads:
public static void Main(string[] args)
{
// 2 assemblies x 2 TestFixtures each x 2 Tests each = 8 test cases
// each test case includes a 200 ms delay
string[] testAssemblyFileNames = { TestAssemblyFileName1, TestAssemblyFileName2 };
string assemblyDirectory = Path.GetDirectoryName(Uri.UnescapeDataString(
new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path));
// Nunit 3.10.0
var minVersion = new Version("3.4");
ITestEngine testEngine = TestEngineActivator.CreateInstance(minVersion);
// configure a test package that executes
// in the current process and in the current domain
var testPackage = new TestPackage(testAssemblyFileNames);
testPackage.AddSetting(EnginePackageSettings.ProcessModel, "InProcess");
testPackage.AddSetting(EnginePackageSettings.DomainUsage, "None");
testPackage.AddSetting(EnginePackageSettings.DisposeRunners, "True");
testPackage.AddSetting(EnginePackageSettings.WorkDirectory, assemblyDirectory);
ITestRunner testRunner = testEngine.GetRunner(testPackage);
var listener = new TestStartListener();
var testFilterBuilder = new TestFilterBuilder();
TestFilter testFilter = testFilterBuilder.GetFilter();
ITestRun testRun = testRunner.RunAsync(listener, testFilter);
// wait until the first test case has been started
while ( listener.Count < 1 )
{
Thread.Sleep(50);
}
bool keepRunning = true;
while ( keepRunning )
{
int testStartedCount = listener.Count;
testRunner.StopRun(force: false);
Writer.WriteLine($"{GetTimeStamp()}, Stop requested after {testStartedCount} test cases.");
// wait for less time than a single test needs to complete
bool completed = testRun.Wait(100);
bool running = testRunner.IsTestRunning;
Writer.WriteLine($"{GetTimeStamp()} Completed: {completed}, running: {running}");
keepRunning = !completed && running;
}
listener.WriteReportsTo(Writer);
XmlNode resultNode = testRun.Result;
Writer.WriteLine("Test result:");
resultNode.WriteContentTo(ResultWriter);
Console.ReadKey();
}
private class TestStartListener : List<string>, ITestEventListener
{
private const string TestCaseStartPrefix = "<start-test";
private const string TestMethodTypeAttribute = " type=\"TestMethod\"";
public event EventHandler<EventArgs> TestStarted;
public void OnTestEvent(string report)
{
if ( report.StartsWith(TestCaseStartPrefix) &&
report.Contains(TestMethodTypeAttribute) )
{
Add($"{GetTimeStamp()}, {report}");
TestStarted?.Invoke(this, new EventArgs());
}
}
public void WriteReportsTo(TextWriter writer)
{
Writer.WriteLine($"Listener was called {Count} times.");
foreach ( var report in this )
{
Writer.WriteLine(report);
}
}
}
The two test assemblies get executed in the runner's process, in a single domain and on two threads, one for each test assembly. In total, two test methods get executed and pass; one for each of the two test assemblies. Other test methods do not get executed and not reported. Other test fixtures (classes) do not get executed and get reported with result="Failed" label="Cancelled".
Note that testRunner.StopRun(force: false) is called repeatedly. If only called once, the other thread will run to completion.

Timeouts + Prepared Transactions with ADO.NET / Npgsql

Looking for some clarification on the below scenarios. CanAddConcurrently and DoesNotTimeout are failing.
CanAddConcurrently - (Npgsql.PostgresException: 55000: prepared transactions are disabled). I understand it's because I have this disabled in my postgres config, but WHY is this escalating to a prepared transaction? Is it because it's actually getting a different NpgsqlConnection? If so, does this really call for a distributed transaction? I can run the same sample with knex, node-postgres with same pool limits, prepared transactions disabled in postgres without issue under node.js
DoesNotTimeout - (The connection pool has been exhausted) I don't understand why the pooled connections are not being reused here. Are they not being disposed because they are associated with the top level TransactionScope in the test? Even under this scenario why can't the connection be reused if it's associated with the same transaction. I can run the same test case with knex, node-postgres with the same pool limit without issue under node.js.
using Npgsql;
using System.Threading.Tasks;
using System.Transactions;
namespace TestCases
{
public class Service
{
private readonly string connectionString;
public Service(string connectionString)
{
this.connectionString = connectionString;
}
// I am aware this is only executing 1 query so does not have a need for an embedded transaction, this is just to keep example simple
// removing the TransactionScope does not fix the issue, but for closer sample to original code it is here
public async Task Add(string val)
{
using (var nestedScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
using (var conn = new NpgsqlConnection(this.connectionString))
{
await conn.OpenAsync();
var cmd = new NpgsqlCommand("INSERT INTO data(value) values(#p);", conn);
cmd.Parameters.AddWithValue("p", val);
await cmd.ExecuteNonQueryAsync();
nestedScope.Complete();
}
}
}
}
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Npgsql;
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Transactions;
namespace TestCases
{
[TestClass]
public class ServiceTests
{
readonly string connectionString = "Server=127.0.0.1;Port=5432;Database=test_db;User Id=postgres;Password=postgres;MaxPoolSize=10;Pooling=true;";
private Service service;
[TestInitialize]
public async Task Initialize()
{
service = new Service(this.connectionString);
using (var conn = new NpgsqlConnection(this.connectionString))
{
await conn.OpenAsync();
var query = "CREATE TABLE IF NOT EXISTS data(value varchar(255));";
var cmd = new NpgsqlCommand(query, conn);
await cmd.ExecuteNonQueryAsync();
}
}
[TestCleanup]
public async Task Cleanup()
{
using (var conn = new NpgsqlConnection(this.connectionString))
{
await conn.OpenAsync();
var query = "DROP TABLE IF EXISTS data;";
var cmd = new NpgsqlCommand(query, conn);
await cmd.ExecuteNonQueryAsync();
}
}
/// <summary>
/// Failing with prepared PG 55000
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task CanAddConcurrently()
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await Task.WhenAll(
Enumerable.Range(1, 10).Select(async i =>
{
var val = string.Format("CanAddConcurrently_Q{0};", i);
await service.Add(val);
})
);
scope.Complete();
}
using (var conn = new NpgsqlConnection(this.connectionString))
{
await conn.OpenAsync();
var query = "select count(*) from data WHERE value like 'CanAddConcurrently_Q%';";
var cmd = new NpgsqlCommand(query, conn);
long count = (long)await cmd.ExecuteScalarAsync();
Assert.AreEqual((long)100, count);
}
}
/// <summary>
/// Timing out
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task DoesNotTimeout()
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await Task.WhenAll(
Enumerable.Range(1, 100).Select(async i =>
{
var val = string.Format("DoesNotTimeout_Q{0};", i);
await service.Add(val);
})
);
scope.Complete();
}
using (var conn = new NpgsqlConnection(this.connectionString))
{
await conn.OpenAsync();
var query = "select count(*) from data WHERE value like 'DoesNotTimeout_Q%';";
var cmd = new NpgsqlCommand(query, conn);
long count = (long)await cmd.ExecuteScalarAsync();
Assert.AreEqual((long)100, count);
}
}
/// <summary>
/// Passes OK
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task CanAddSequentially()
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
for (long i = 0; i < 100; i++)
{
var val = string.Format("CanAddSequentially_Q{0};", i);
await service.Add(val);
}
scope.Complete();
}
using (var conn = new NpgsqlConnection(this.connectionString))
{
await conn.OpenAsync();
var query = "select count(*) from data WHERE value like 'CanAddSequentially_Q%';";
var cmd = new NpgsqlCommand(query, conn);
long count = (long)await cmd.ExecuteScalarAsync();
Assert.AreEqual((long)100, count);
}
}
/// <summary>
/// Passes OK
/// </summary>
/// <returns></returns>
[TestMethod]
public async Task RollsBackIfError()
{
try
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
for (long i = 0; i < 100; i++)
{
var val = string.Format("RollsBackIfError_Q{0};", i);
if (i == 99)
{
val = val.PadRight(300, 'e'); // trigger error on last item
}
await service.Add(val);
}
scope.Complete();
}
}
catch (Exception ex)
{
Assert.IsInstanceOfType(ex, typeof(NpgsqlException));
}
using (var conn = new NpgsqlConnection(this.connectionString))
{
await conn.OpenAsync();
var query = "select count(*) from data WHERE value like 'RollsBackIfError_Q%';";
var cmd = new NpgsqlCommand(query, conn);
long count = (long)await cmd.ExecuteScalarAsync();
Assert.AreEqual((long)0, count);
}
}
}
}
You need to understand exactly how Task.WhenAll() (and possibly async) operates.
Your code above invokes Service.Add() 10 times, concurrently, on the same transaction scope, and then waits for those 10 invocations to complete. Each invocation opens a new pooled connection and attempts to enlist to the single transaction scope that exists in your program; the moment more than one connection is enlisted to a transaction scope, that is a distributed transaction, which is why the escalation occurs.
The same likely explains the pool exhaustion in your second example - it's not about pool connections not being reused, it's about you trying to use too many at the same time.
You should run your code serially with a standard foreach, executing the next operation only after the previous one completed. It is possible to run multiple operations at the same time - for better performance - but you definitely can't share the same transaction scope between them.

Working on pre-operation plug-in to update "Modified By" field in MSCRM -- Need help fixing code

I am trying to update the "Modified By" field based on a text field called "Prepared By", which contains the name of a user. I've created a pre-operation plug-in to do this and believe I am close to done. However, the "Modified By" field is still not successfully getting updated. I am relatively new to coding and CRM, and could use some help modifying the code and figuring out how I can get this to work.
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Linq;
namespace TimClassLibrary1.Plugins
{
public class CreateUpdateContact : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
var service = factory.CreateOrganizationService(context.UserId);
tracingService.Trace("Start plugin");
tracingService.Trace("Validate Target");
if (!context.InputParameters.Contains("Target") || !(context.InputParameters["Target"] is Entity))
return;
tracingService.Trace("Retrieve Target");
var target = (Entity)context.InputParameters["Target"];
String message = context.MessageName.ToLower();
SetCreatedByAndModifiedBy(tracingService, service, target, message);
}
private void SetCreatedByAndModifiedBy(ITracingService tracingService, IOrganizationService service, Entity target, string message)
{
tracingService.Trace("Start SetPriceList");
tracingService.Trace("Validate Message is Create or Update");
if (!message.Equals("create", StringComparison.OrdinalIgnoreCase) && !message.Equals("update", StringComparison.OrdinalIgnoreCase))
return;
tracingService.Trace("Retrieve Attributes");
var createdByReference = target.GetAttributeValue<EntityReference>("new_createdby");
var modifiedByReference = target.GetAttributeValue<EntityReference>("new_modifiedby");
tracingService.Trace("Retrieve And Set User for Created By");
RetrieveAndSetUser(tracingService, service, target, createdByReference, "createdby");
tracingService.Trace("Retrieve And Set User for Modified By");
RetrieveAndSetUser(tracingService, service, target, modifiedByReference, "modifiedby");
}
private void RetrieveAndSetUser(ITracingService tracingService, IOrganizationService service, Entity target, EntityReference reference, string targetAttribute)
{
tracingService.Trace("Validating Reference");
if (reference == null)
return;
tracingService.Trace("Retrieving and Validating User");
var user = RetrieveUserByName(service, reference.Name, new ColumnSet(false));
if (user == null)
return;
tracingService.Trace("Setting Target Attribute");
target[targetAttribute] = user.ToEntityReference();
}
private Entity RetrieveUserByName(IOrganizationService service, string name, ColumnSet columns)
{
var query = new QueryExpression
{
EntityName = "systemuser",
ColumnSet = columns,
Criteria = new FilterExpression
{
FilterOperator = LogicalOperator.And,
Conditions =
{
new ConditionExpression
{
AttributeName = "fullname",
Operator = ConditionOperator.Equal,
Values = { name }
}
}
}
};
var retrieveResponse = service.RetrieveMultiple(query);
if (retrieveResponse.Entities.Count == 1)
{
return retrieveResponse.Entities.FirstOrDefault();
}
else
{
return null;
}
}
}
}
If you do get use from method Retreiveusernyname then you have to use below code
target[“modifiedby”] = new EntityRefrence(user.logicalname,user.id);
I don't see anything obviously wrong with your update, however you are taking a complicated and unnecessary step with your RetrieveUserByName() method. You already have EntityReference objects from your new_createdby and new_modifiedby fields, you can simply assign those to the target:
if (message.Equals("create", StringComparison.OrdinalIgnoreCase))
{
target["createdby"] = target["new_createdby];
}
else if (message.Equals("update", StringComparison.OrdinalIgnoreCase))
{
target["modifiedby"] = target["new_modifiedby];
}
If new_createdby and new_modifiedby are not entity references, then that would explain why your existing code does not work, if they are, then use my approach.

Unstable application uses SqlDependency. Several states and errors

I have a windows application using SqlDependency running at separated thread pool, this application represents a log monitor UI get the latest rows added in a specific table in the database and view it in a DataGridView. You can see the application source code from this LINK, or follow this script.
const string tableName = "OutgoingLog";
const string statusMessage = "{0} changes have occurred.";
int changeCount = 0;
private static DataSet dataToWatch = null;
private static SqlConnection connection = null;
private static SqlCommand command = null;
public frmMain()
{
InitializeComponent();
}
private bool CanRequestNotifications()
{
// In order to use the callback feature of the
// SqlDependency, the application must have
// the SqlClientPermission permission.
try
{
SqlClientPermission perm = new SqlClientPermission(PermissionState.Unrestricted);
perm.Demand();
return true;
}
catch
{
return false;
}
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
// This event will occur on a thread pool thread.
// Updating the UI from a worker thread is not permitted.
// The following code checks to see if it is safe to
// update the UI.
ISynchronizeInvoke i = (ISynchronizeInvoke)this;
// If InvokeRequired returns True, the code
// is executing on a worker thread.
if (i.InvokeRequired)
{
// Create a delegate to perform the thread switch.
OnChangeEventHandler tempDelegate = new OnChangeEventHandler(dependency_OnChange);
object[] args = { sender, e };
// Marshal the data from the worker thread
// to the UI thread.
i.BeginInvoke(tempDelegate, args);
return;
}
// Remove the handler, since it is only good
// for a single notification.
SqlDependency dependency = (SqlDependency)sender;
dependency.OnChange -= dependency_OnChange;
// At this point, the code is executing on the
// UI thread, so it is safe to update the UI.
++changeCount;
lblChanges.Text = String.Format(statusMessage, changeCount);
// Reload the dataset that is bound to the grid.
GetData();
}
AutoResetEvent running = new AutoResetEvent(true);
private void GetData()
{
// Start the retrieval of data on another thread to let the UI thread free
ThreadPool.QueueUserWorkItem(o =>
{
running.WaitOne();
// Empty the dataset so that there is only
// one batch of data displayed.
dataToWatch.Clear();
// Make sure the command object does not already have
// a notification object associated with it.
command.Notification = null;
// Create and bind the SqlDependency object
// to the command object.
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
{
adapter.Fill(dataToWatch, tableName);
try
{
running.Set();
}
finally
{
// Update the UI
dgv.Invoke(new Action(() =>
{
dgv.DataSource = dataToWatch;
dgv.DataMember = tableName;
//dgv.FirstDisplayedScrollingRowIndex = dgv.Rows.Count - 1;
}));
}
}
});
}
private void btnAction_Click(object sender, EventArgs e)
{
changeCount = 0;
lblChanges.Text = String.Format(statusMessage, changeCount);
// Remove any existing dependency connection, then create a new one.
SqlDependency.Stop("Server=.; Database=SMS_Tank_Log;UID=sa;PWD=hana;MultipleActiveResultSets=True");
SqlDependency.Start("Server=.; Database=SMS_Tank_Log;UID=sa;PWD=hana;MultipleActiveResultSets=True");
if (connection == null)
{
connection = new SqlConnection("Server=.; Database=SMS_Tank_Log;UID=sa;PWD=hana;MultipleActiveResultSets=True");
}
if (command == null)
{
command = new SqlCommand("select * from OutgoingLog", connection);
//SqlParameter prm =
// new SqlParameter("#Quantity", SqlDbType.Int);
//prm.Direction = ParameterDirection.Input;
//prm.DbType = DbType.Int32;
//prm.Value = 100;
//command.Parameters.Add(prm);
}
if (dataToWatch == null)
{
dataToWatch = new DataSet();
}
GetData();
}
private void frmMain_Load(object sender, EventArgs e)
{
btnAction.Enabled = CanRequestNotifications();
}
private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
{
SqlDependency.Stop("Server=.; Database=SMS_Tank_Log;UID=sa;PWD=hana;MultipleActiveResultSets=True");
}
The problem:
I have many situations of errors, (images in the first comment)
(No. 1):
I got this error dialog, and I don't know its reason.
(No. 2):
I got nothing in my grid view (No errors, and no data).
(No. 3):
I got only columns names and no rows, although the table has rows.
I need help please.
I may be wrong but a DataSet does not seem to have notification capability so the DataGridView may be surprised if you change it behind its back.
You could try to explicitly show your're changing the data source by first setting it to null:
dgv.DataSource = null;
dgv.DataSource = dataToWatch;
dgv.DataMember = tableName;
It's worth a try...

Entity Framework not populating Navigation property on savechanges

I am currently trying to build a web front end for a Report editor that allows the user to create different sections that are basically text fields. The first screen I allow the user to see causes them to populate some very minor initial data and I use it to spawn a new Report table record. I am using a t4 to generate my simple repositories:
public Entities.Result Add(ref Report entity) {
Entities.Result result;
try {
this.Context.Reports.AddObject(entity);
this.Context.SaveChanges();
result = new Entities.Result {
Success = true,
Messages = new List<Entities.ResultMessage>()
};
} catch (System.Exception ex) {
this.EventLogger.Error(ex);
List<Entities.ResultMessage> messages = new List<Entities.ResultMessage>();
messages.Add(new Entities.ResultMessage { Message = ex.ToString(), MessageSeverity = 10 });
result = new Entities.Result {
Success = false,
Messages = messages
};
}
return result;
}
and the same thing when they add a new section
public Entities.Result Add(ref ReportSection entity) {
Entities.Result result;
try {
this.Context.ReportSections.AddObject(entity);
this.Context.SaveChanges();
result = new Entities.Result {
Success = true,
Messages = new List<Entities.ResultMessage>()
};
} catch (System.Exception ex) {
this.EventLogger.Error(ex);
List<Entities.ResultMessage> messages = new List<Entities.ResultMessage>();
messages.Add(new Entities.ResultMessage { Message = ex.ToString(), MessageSeverity = 10 });
result = new Entities.Result {
Success = false,
Messages = messages
};
}
return result;
}
I call this from the Index controller in the form of:
Report newReport = new Report() {
ApprovalDate = null,
Approver = string.Empty,
Author = System.Web.Security.Membership.GetUser().UserName,
Created = DateTime.Now,
IsActive = true,
IsSubmitted = false,
ReleaseDate = null,
SerialNumber1 = string.Empty,
SerialNumber2 = string.Empty
};
var result = this._reportRepository.Add(ref newReport);
this goes through just fine and I get the record in the database. I then take some of their entries a populate an initial ReportSection object like so:
var newSection = new ReportSection {
DisplayOrder = 1,
ReportId = newReport.Id,
ReportSectionTypeId = 1,
SectionText = title
};
var r = this._reportSectionRepository.Add(ref newSection);
You'll notice I'm referring to the newReport.Id in there so I have the valid ID returned from the repository add method. The section add method returns to me a valid newSection object that has a valid ID and when I check in the db it exists. However, EF is not populating the navigational property on newSection.Report that lets me get between the two. This is what I'm trying to figure out, why is this not accessible? I have a temporary work around that defeats some of my DAL validation logic, but I can load the Report object then add the ReportSection through Report.ReportSections and then call an update method on the repo that does an ApplyCurrentValues.
A bit more detail:
Visual Studio 2010
C# .Net 4.0
MVC 2.0
EF 4.0 ( or maybe 4.1 )
I am using Ninject to fill in the repositories:
Bind<IReportRepository>().To<ReportRepository>().WithConstructorArgument("connectionString",
ConfigurationManager.ConnectionStrings["ConnString"].ConnectionString);
You could directly set the navigation property instead of the foreign key property:
var newSection = new ReportSection {
DisplayOrder = 1,
Report = newReport,
ReportSectionTypeId = 1,
SectionText = title
};
If the context in _reportRepository and _reportSectionRepository are the same and newReport is still attached to it that's all you need to do.
If the contexts are not the same or newReport is not attached to the context, you must attach newReport first to the context:
public Entities.Result Add(ref ReportSection entity) {
Entities.Result result;
try {
this.Context.Reports.Attach(entity.Report);
this.Context.ReportSections.AddObject(entity);
this.Context.SaveChanges();
// etc.