使用外键在MVC中使用SqlDependency和SignalR进行实时更新

时间:2014-09-15 09:58:36

标签: c# sql asp.net asp.net-mvc-5 signalr

我想开发一个HTML页面实时更新的项目。所以我开始在互联网上搜索,我发现只有这个有效的例子:http://venkatbaggu.com/signalr-database-update-notifications-asp-net-mvc-usiing-sql-dependency/

然后我试着让它适应我的项目。我先用一些代码解释一下。

这是我的数据库图表的一部分:

Database diagram

这里有一些代码:

[Table("Devices")]
public class Device
{
    [Key]
    public long DeviceId { get; set; }

    [DisplayName("Modello")]
    public long ModelId { get; set; }

    [DisplayName("Fornitore")]
    public long SupplierId { get; set; }

    [DisplayName("Numero di serie")]
    [StringLength(50)]
    [Required(AllowEmptyStrings = false, ErrorMessage = "E' richiesto un numero di serie")]
    public string SerialNumber { get; set; }

    [DisplayName("Codice fornitore")]
    [StringLength(50)]
    public string SupplierCode { get; set; }

    [DisplayName("Data di acquisto")]
    public DateTime DatePurchase { get; set; }

    public virtual Model Model { get; set; }
    public virtual Supplier Supplier { get; set; }
}

[Table("DeviceTypes")]
public class DeviceType
{
    [Key]
    public long DeviceTypeId { get; set; }

    [DisplayName("Tipo dispositivo")]
    [StringLength(50)]
    [Required(AllowEmptyStrings = false, ErrorMessage = "E' richiesto un tipo dispositivo")]
    public string Name { get; set; }
}

[Table("HttpPop3")]
public class HttpPop3
{
    [Key]
    public long ID { get; set; }

    public long DeviceId { get; set; }

    [DisplayName("Mittente")]
    [StringLength(50)]
    [Required]
    public string sms_num { get; set; }

    [DisplayName("Data")]
    [StringLength(50)]
    [Required]
    public string sms_date { get; set; }

    [DisplayName("Testo")]
    [StringLength(255)]
    [Required]
    public string sms_text { get; set; }

    [DisplayName("Code")]
    [StringLength(50)]
    public string sms_code { get; set; }

    public virtual Device Device { get; set; }
}

[Table("Models")]
public class Model
{
    [Key]
    public long ModelId { get; set; }

    [DisplayName("Prodotto")]
    public long ProductId { get; set; }

    [DisplayName("Tipo dispositivo")]
    public long DeviceTypeId { get; set; }

    [DisplayName("Modello")]
    [StringLength(50)]
    [Required(AllowEmptyStrings = false, ErrorMessage = "E' richiesto un modello")]
    public string Name { get; set; }

    public virtual Product Product { get; set; }
    public virtual DeviceType DeviceType { get; set; }
}

[Table("Products")]
public class Product
{
    [Key]
    public long ProductId { get; set; }

    [DisplayName("Produttore")]
    public long VendorId { get; set; }

    [DisplayName("Prodotto")]
    [StringLength(50)]
    [Required(AllowEmptyStrings = false, ErrorMessage = "E' richiesto un prodotto")]
    public string Name { get; set; }

    public virtual Vendor Vendor { get; set; }
}

[Table("Suppliers")]
public class Supplier
{
    [Key]
    public long SupplierId { get; set; }

    [DisplayName("Fornitore")]
    [StringLength(50)]
    [Required(AllowEmptyStrings = false, ErrorMessage = "E' richiesto un fornitore")]
    public string Name { get; set; }

    [DisplayName("Indirizzo")]
    [StringLength(50)]
    public string Address { get; set; }

    [DisplayName("Numero civico")]
    [StringLength(10)]
    public string Number { get; set; }

    [DisplayName("Telefono")]
    [StringLength(50)]
    public string Phone { get; set; }

    [DisplayName("Fax")]
    [StringLength(50)]
    public string Fax { get; set; }

    [DisplayName("Email")]
    [StringLength(50)]
    public string Email { get; set; }
}

[Table("Vendors")]
public class Vendor
{
    [Key]
    public long VendorId { get; set; }

    [DisplayName("Produttore")]
    [StringLength(50)]
    [Required(AllowEmptyStrings = false, ErrorMessage = "E' richiesto un produttore")]
    public string Name { get; set; }
}

public class iCareEntities : DbContext
{
    public DbSet<Supplier> Suppliers { get; set; }
    public DbSet<DeviceType> DeviceTypes { get; set; }

    public DbSet<Vendor> Vendors { get; set; }
    public DbSet<Product> Products { get; set; }
    public DbSet<Model> Models { get; set; }

    public DbSet<Device> Devices { get; set; }

    public DbSet<HttpPop3> HttpPop3s { get; set; }
}

public class HttpPop3Repository
{
    iCareEntities db = new iCareEntities();   

    string connString = ConfigurationManager.ConnectionStrings["iCareEntities"].ConnectionString;

    public IEnumerable<HttpPop3> GetAllMessages()
    {
        //This method doesn't work

        List<HttpPop3> messages = new List<HttpPop3>();
        using (var connection = new SqlConnection(connString))
        {
            connection.Open();
            using (SqlCommand command = new SqlCommand(@"SELECT [Mittente], [Data], [Testo], [Code] FROM [iCare].[dbo].[HttpPop3View] ORDER BY [Data] DESC", connection))
            {
                command.Notification = null;

                SqlDependency dependency = new SqlDependency(command);
                dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);

                if (connection.State == ConnectionState.Closed)
                    connection.Open();

                var reader = command.ExecuteReader();

                while (reader.Read())
                {
                    messages.Add(new HttpPop3 { sms_num = (string)reader["Mittente"], sms_date = (string)reader["Data"], sms_text = (string)reader["Testo"], sms_code = (string)reader["Code"] });
                }
            }

        }

        return messages;
    }

    private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
    {
        if (e.Type == SqlNotificationType.Change)
        {
            MyHub.SendMessages();
        }
    }
}

[HubMethodName("sendMessages")]
public static void SendMessages()
{
    IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
    context.Clients.All.updateMessages();
}

HttpPop3是一台SMS机器,在收到消息时进行POST。

我的目标是在HttpPop3表中插入新消息时实时更新视图。这里是机器发出POST的代码隐藏:

public partial class _0090C2E7BD56 : System.Web.UI.Page
{
    private iCareEntities db = new iCareEntities();

    protected void Page_Load(object sender, EventArgs e)
    {
        if (Request["sms_num"] != null && Request["sms_date"] != null && Request["sms_text"] != null)
        {
            iCare.Models.HttpPop3 httppop3 = new iCare.Models.HttpPop3
            {
                // 0090C2E7BD56 is the serial number of the machine a 1 is its primary key in Devices table

                DeviceId = 1,
                sms_num = Request["sms_num"],
                sms_date = Request["sms_date"],
                sms_text = Request["sms_text"],
                sms_code = Request["sms_code"]
            };

            db.HttpPop3s.Add(httppop3);
            db.SaveChanges();
        }
    }
}

这样可行,当消息到达时,我的表格中有一个新行。

我使用观看次数生成HttpPop3Controller之后:

public class HttpPop3Controller : Controller
{
    private iCareEntities db = new iCareEntities();

    // GET: /HttpPop3/
    public ActionResult Index()
    {
        var httppop3s = db.HttpPop3s.Include(h => h.Device);
        return View(httppop3s.ToList());
    }

    // GET: /HttpPop3/Details/5
    public ActionResult Details(long? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }

        HttpPop3 httppop3 = db.HttpPop3s.Find(id);

        if (httppop3 == null)
        {
            return HttpNotFound();
        }

        return View(httppop3);
    }

    // GET: /HttpPop3/Create
    public ActionResult Create()
    {
        ViewBag.DeviceId = new SelectList(db.Devices, "DeviceId", "SerialNumber");
        return View();
    }

    // POST: /HttpPop3/Create
    // Per proteggere da attacchi di overposting, abilitare le proprietà a cui eseguire il binding. 
    // Per ulteriori dettagli, vedere http://go.microsoft.com/fwlink/?LinkId=317598.
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include="ID,DeviceId,sms_num,sms_date,sms_text,sms_code")] HttpPop3 httppop3)
    {
        if (ModelState.IsValid)
        {
            db.HttpPop3s.Add(httppop3);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        ViewBag.DeviceId = new SelectList(db.Devices, "DeviceId", "SerialNumber", httppop3.DeviceId);
        return View(httppop3);
    }

    // GET: /HttpPop3/Edit/5
    public ActionResult Edit(long? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }

        HttpPop3 httppop3 = db.HttpPop3s.Find(id);

        if (httppop3 == null)
        {
            return HttpNotFound();
        }

        ViewBag.DeviceId = new SelectList(db.Devices, "DeviceId", "SerialNumber", httppop3.DeviceId);

        return View(httppop3);
    }

    // POST: /HttpPop3/Edit/5
    // Per proteggere da attacchi di overposting, abilitare le proprietà a cui eseguire il binding. 
    // Per ulteriori dettagli, vedere http://go.microsoft.com/fwlink/?LinkId=317598.
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit([Bind(Include="ID,DeviceId,sms_num,sms_date,sms_text,sms_code")] HttpPop3 httppop3)
    {
        if (ModelState.IsValid)
        {
            db.Entry(httppop3).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        ViewBag.DeviceId = new SelectList(db.Devices, "DeviceId", "SerialNumber", httppop3.DeviceId);

        return View(httppop3);
    }

    // GET: /HttpPop3/Delete/5
    public ActionResult Delete(long? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }

        HttpPop3 httppop3 = db.HttpPop3s.Find(id);

        if (httppop3 == null)
        {
            return HttpNotFound();
        }

        return View(httppop3);
    }

    // POST: /HttpPop3/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteConfirmed(long id)
    {
        HttpPop3 httppop3 = db.HttpPop3s.Find(id);
        db.HttpPop3s.Remove(httppop3);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    public ActionResult GetMessages()
    {
        HttpPop3Repository httpPop3Repository = new HttpPop3Repository();
        return PartialView("_HttpPop3List", httpPop3Repository.GetAllMessages());
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }
}

我不知道为什么还有列[Devices]。[SerialNumber]但是对我来说没关系,它可以帮助我识别哪台机器收到了这条消息。

这里是我在示例中修改的Index.cshtml:

    @model IEnumerable<iCare.Models.HttpPop3>

    @{
        ViewBag.Title = "Index";
    }

<div class="row">
    <div class="col-md-12">
        <div id="httpPop3Table"></div>
    </div>
</div>

    @section Scripts{
        <script src="/Scripts/jquery.signalR-2.1.1.js"></script>
        <!--Reference the autogenerated SignalR hub script. -->
        <script src="/signalr/hubs"></script>
        <script type="text/javascript">
            $(function () {
                // Declare a proxy to reference the hub.
                var notifications = $.connection.myHub;

                //debugger;
                // Create a function that the hub can call to broadcast messages.
                notifications.client.updateMessages = function () {
                    GetAllMessages()

                };
                // Start the connection.
                $.connection.hub.start().done(function () {
                    alert("connection started")
                    GetAllMessages();
                }).fail(function (e) {
                    alert(e);
                });
            });


            function GetAllMessages() {
                var tbl = $('#httpPop3Table');
                $.ajax({
                    url: '/HttpPop3/GetMessages',
                    contentType: 'application/html ; charset:utf-8',
                    type: 'GET',
                    dataType: 'html'
                }).success(function (result) {
                    tbl.empty().append(result);
                }).error(function () {

                });
            }
        </script>

    }
好的,现在问题了。这是我的HttpPop3Repository:

public class HttpPop3Repository
{
    iCareEntities db = new iCareEntities();   

    string connString = ConfigurationManager.ConnectionStrings["iCareEntities"].ConnectionString;

    public IEnumerable<HttpPop3> GetAllMessages()
    {
        //This method doesn't work

        List<HttpPop3> messages = new List<HttpPop3>();
        using (var connection = new SqlConnection(connString))
        {
            connection.Open();
            using (SqlCommand command = new SqlCommand(@"SELECT [Mittente], [Data], [Testo], [Code] FROM [iCare].[dbo].[HttpPop3View] ORDER BY [Data] DESC", connection))
            {
                command.Notification = null;

                SqlDependency dependency = new SqlDependency(command);
                dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);

                if (connection.State == ConnectionState.Closed)
                    connection.Open();

                var reader = command.ExecuteReader();

                while (reader.Read())
                {
                    messages.Add(new HttpPop3 { sms_num = (string)reader["Mittente"], sms_date = (string)reader["Data"], sms_text = (string)reader["Testo"], sms_code = (string)reader["Code"] });
                }
            }

        }

        return messages;
    }

    private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
    {
        if (e.Type == SqlNotificationType.Change)
        {
            MyHub.SendMessages();
        }
    }
}

如果HttpPop3类没有此属性,如何设置SerialNumber列?

谢谢

3 个答案:

答案 0 :(得分:0)

您是否在global.asax

中启动了SQL依赖项

请查看步骤3,此代码段丢失之前我更新了帖子

public class MvcApplication : System.Web.HttpApplication
{
    string connString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        GlobalConfiguration.Configure(WebApiConfig.Register);
        //Start SqlDependency with application initialization
        SqlDependency.Start(connString);
    }

    protected void Application_End()
    {
        //Stop SQL dependency
        SqlDependency.Stop(connString);
    }
}    

答案 1 :(得分:0)

尝试检查您可以从SqlDependency获取的其他信息。例如:

private static void Dependency_OnChanged(object sender, SqlNotificationEventArgs e)
{
    bool updated  = e.Info == SqlNotificationInfo.Update;
    bool changed  = e.Type == SqlNotificationType.Change;
    bool isClient = e.Source == SqlNotificationSource.Data;

    bool acceptable = updated && changed && isClient;

    if (acceptable)
    {
        MyHub.SendMessages();
    }
}

另一个想法我应该提到的是把

SqlDependency.Start(connString);
如果要创建具有迁移和代码的部署包,那么

进入Global.asax中的Application_Start()并不是一个好主意。当您尝试部署它并希望迁移在第一次运行时为您创建新数据库时,这将会严重崩溃,因为您正在设置对不存在的数据库的依赖性......希望这有帮助...

答案 2 :(得分:0)

所以,另一个论坛的人建议我解决这个问题。我在Product类中添加了一个VendorName变量,其属性为[NotMapped]:

[Table("Products")]
public class Product
{
    [Key]
    [DisplayName("ID Prodotto")]
    public long ProductID { get; set; }

    [DisplayName("ID Produttore")]
    public long VendorID { get; set; }

    [DisplayName("Prodotto")]
    [StringLength(50)]
    [Required(AllowEmptyStrings = false, ErrorMessage = "E' richiesto un prodotto")]
    public string Name { get; set; }

    public virtual Vendor Vendor { get; set; }

    [NotMapped]
    [DisplayName("Produttore")]
    public string VendorName { get; set; }
}

我的GetAllProducts方法变为:

        public IEnumerable<Product> GetAllProducts()
    {
        List<Product> products = new List<Product>();
        using (var connection = new SqlConnection(connString))
        {
            StringBuilder query = new StringBuilder();
            query.Append("SELECT dbo.Products.ProductID, dbo.Vendors.Name AS VendorName, dbo.Products.Name AS ProductName ");
            query.Append("FROM dbo.Products ");
            query.Append("INNER JOIN dbo.Vendors ON dbo.Products.VendorID = dbo.Vendors.VendorID");

            connection.Open();
            using (SqlCommand command = new SqlCommand(query.ToString(), connection))
            {
                command.Notification = null;

                SqlDependency dependency = new SqlDependency(command);
                dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);

                if (connection.State == ConnectionState.Closed)
                    connection.Open();

                var reader = command.ExecuteReader();
                while (reader.Read())
                {
                    products.Add(new Product { ProductID = (long)reader["ProductID"], VendorName = (string)reader["VendorName"], Name = (string)reader["ProductName"] });
                }
            }
        }
        return products;
    }

所以我有一个VendorName,在那里分配值,我没有得到错误'列不存在'。 要编写Sql查询,我使用SQL Server Management Studio视图设计器,它可以工作。

我已经有些问题了。 如果填充了所有列,则一切正常。如果有一些空值我无法从数据库获得任何东西。

我该如何解决?